lvkun 2 лет назад
Родитель
Сommit
45326727b0

+ 3 - 3
config/proxy.ts

@@ -10,10 +10,10 @@ module.exports = {
       changeOrigin: true,
       pathRewrite: { '^/user': '' }
     },
-    '/rts': {
+    '/cvss': {
       target: 'http://124.222.113.37:8080',
-      changeOrigin: true
-      // pathRewrite: { '^/cvs': '' }
+      changeOrigin: true,
+      pathRewrite: { '^/cvss': '' }
     },
     '/iot': {
       target: 'http://124.222.113.37:8888',

+ 87 - 1
src/api/cvs/video.ts

@@ -11,7 +11,7 @@ export const getSpace = (params: COMMON.API.QueryParams) => {
 
 export const addSpace = (data: Partial<CVS.space>) => {
   return request<string>({
-    url: '/cvs/space',
+    url: '/space',
     method: 'POST',
     data
   })
@@ -31,3 +31,89 @@ export const delSpace = (id: string) => {
     method: 'DELETE'
   })
 }
+
+export const getDevicePage = (params: COMMON.API.QueryParams & {deviceName: string}) => {
+  return request<CVS.device[]>({
+    url: '/device/page',
+    method: 'GET',
+    params
+  })
+}
+
+// export const getDeviceList = (params: COMMON.API.QueryParams & {deviceName: string}) => {
+//   return request<CVS.device[]>({
+//     url: '/device/list?deviceName=xxxx',
+//     method: 'GET',
+//     params
+//   })
+// }
+
+export const getDeviceById = (deviceId: string) => {
+  return request<CVS.device>({
+    url: `/device/${deviceId}`,
+    method: 'GET'
+  })
+}
+
+export const addDevice = (data: Partial<CVS.device>) => {
+  return request<string>({
+    url: '/device',
+    method: 'POST',
+    data
+  })
+}
+
+export const getDeviceRecordById = (params: {
+  deviceId: string
+  startTs?: number,
+  endTs?: number,
+  recordFormat: 'FLV' | 'TS' | 'MP4' | 'RAW'
+}) => {
+  return request<{
+    deviceId: string
+    recordFormat: 'FLV' | 'TS' | 'MP4' | 'RAW'
+    recordStartTs: number
+    recordEndTs: number
+    recordUrl: string
+  }>({
+    url: `/device/record/${params.deviceId}`,
+    method: 'GET',
+    params
+  })
+}
+
+export const getDeviceThumbById = (params: {
+  deviceId: string
+  startTs?: number,
+  endTs?: number
+}) => {
+  return request<{
+    deviceId: string
+    thumbTs: number
+    thumbUrl: string
+  }>({
+    url: `/device/thumb/${params.deviceId}`,
+    method: 'GET',
+    params
+  })
+}
+
+export const getDeviceAiRetById = (params: {
+  deviceId: string
+  startTs?: number,
+  endTs?: number
+  aiId?: string
+}) => {
+  return request<{
+    deviceId: string
+    aiId: string
+    aiDesc: string
+    aiRetType: 'IMG' | 'VIDEO' | 'TXT'
+    aiRetUrl: string
+    aiTs: number
+  }>({
+    url: `/device/aiRet/${params.deviceId}`,
+    method: 'GET',
+    params
+  })
+}

+ 1 - 1
src/components/MicroComponents/index.tsx

@@ -145,7 +145,7 @@ export const InputTsx = defineComponent({
     const onInput = (value: string) => ctx.emit('update:value', value)
 
     return () => (
-      <Input style={{ minWidth: '170px' }} placeholder={props.placeholder} value={props.modelValue} />
+      <Input style={{ minWidth: '170px' }} placeholder={props.placeholder} value={props.modelValue} onChange={(e) => onInput(e.target.value!)} />
     )
   }
 })

+ 2 - 0
src/components/StepModal/index.less

@@ -23,6 +23,8 @@ v-deep .ant-modal {
     bottom: 0;
     padding-left: 40px;
     background-color: #fff;
+    display: flex;
+    justify-content: center;
   }
 
 }

+ 14 - 6
src/components/StepModal/index.tsx

@@ -24,23 +24,31 @@ export const StepModal = defineComponent({
       default: false
     }
   },
-  emits: ['next', 'pre', 'ok', 'close'],
+  emits: ['next', 'pre', 'ok', 'close', 'submit'],
   setup (props, ctx) {
-    const isLastStep = computed(() => props.step === props.steps.length)
+    const isLastStep = computed(() => props.step + 1 === props.steps.length)
 
     // 已经填写过的step的下标
     const writeSteped = ref([0])
 
     // 是否允许当前进行下一步
     const nextStep = () => {
-      writeSteped.value.push(writeSteped.value.length)
-      ctx.emit('next')
+      if (isLastStep.value) {
+        ctx.emit('submit')
+      } else {
+        writeSteped.value.push(writeSteped.value.length)
+        ctx.emit('next')
+      }
     }
 
     const close = () => {
       ctx.emit('close')
     }
 
+    const preStep = () => {
+      ctx.emit('pre')
+    }
+
     return () => (
       <Modal
         class='step-modal'
@@ -62,8 +70,8 @@ export const StepModal = defineComponent({
         <Row class='footer' align='middle' justify='space-between' >
           <Col>
             <Space>
-              <Button disabled={props.step === 0} >上一步</Button>
-              <Button disabled={isLastStep.value} onClick={nextStep} >{isLastStep.value ? '提交' : '下一步'}</Button>
+              <Button disabled={props.step === 0} onClick={preStep} >上一步</Button>
+              <Button onClick={nextStep} >{isLastStep.value ? '提交' : '下一步'}</Button>
               <Button onClick={close} >取消</Button>
             </Space>
           </Col>

+ 138 - 144
src/components/TableProV2/index.tsx

@@ -3,16 +3,15 @@ import {
   Button, DropdownButton, PaginationProps
 } from 'ant-design-vue'
 import { DownOutlined } from '@ant-design/icons-vue'
-import { PropType, computed, defineComponent, reactive, ref, defineEmits, FunctionalComponent, onMounted } from 'vue'
+import {
+  PropType, computed, defineComponent, reactive, ref, defineEmits, FunctionalComponent,
+  onMounted
+} from 'vue'
 
 /**
  * @description Table Pro 超级table 将各种业务与操作融合起来
  */
 
-interface GenericListProps<T> {
-  dataSource: T;
-}
-
 interface Props {
   columns: TableColumnProps[],
   pagination: PaginationProps | false
@@ -23,143 +22,138 @@ interface Props {
   easy: boolean
 }
 
-const TablePro: FunctionalComponent<Props> = (props) => {
-  const { request, easy, columns } = props
-
-  console.log('defineComponent:', props)
-
-  // const columns = [{ title: '', key: 'ky' }]
-
-  const emits = defineEmits(['add'])
-
-  const loading = ref<boolean>(false)
-
-  const columnsPro = ref<(TableColumnProps & {hidden: boolean}) []>(columns.map(column => ({ ...column, hidden: false })))
-
-  const columnsFilter = computed(() => columnsPro.value.filter(column => !column.hidden))
-
-  const rowCustomized = computed(() => columns.map(item => ({ title: item.title, key: item.key })))
-
-  const pagination = reactive<PaginationProps>(Object.assign({}, {
-    current: 1,
-    pageSize: 10,
-    total: 0,
-    onChange: (page: number, pageSize: number) => onChangePage(page, pageSize)
-  }, typeof props.pagination === 'boolean' ? {} : { ...props.pagination }))
-
-  const dataSource = ref([])
-
-  const opraMeun = (
-    <Menu>
-      <Menu.item key={0} onClick={emits('add')} > 新增 </Menu.item>
-      <Menu.item key={2} onClick={() => exportExcel()}> 导出 </Menu.item>
-      <Menu.item key={3} onClick={() => pure()}> 纯净 </Menu.item>
-    </Menu>
-  )
-
-  const exportExcel = () => {}
-
-  const pure = () => {}
-
-  const reload = () => dispatchRequest()
-
-  const onChangePage = (page: number, pageSize: number) => {
-    pagination.current = page
-    pagination.pageSize = pageSize
-    dispatchRequest()
-  }
-
-  const dispatchRequest = async () => {
-    console.log('调用')
-
-    loading.value = true
-    const { data, sum } = await request.get({
-      ...request.params,
-      page: pagination.current,
-      pageSize: pagination.pageSize
-    })
-    loading.value = false
-    pagination.total = sum
-    dataSource.value = data
-  }
-
-  // dispatchRequest()
-
-  onMounted(() => {
-    console.log('onMounted')
-
-    dispatchRequest()
-  })
-
-  /**
-   * 展示或者隐藏对应的column
-   */
-  const swicthColumn = (index: number) => {
-    columnsPro.value[index].hidden = !columnsPro.value[index].hidden
-  }
-
-  const RenderOpraRow = () => {
-    return (
-      <>
-        {easy
-          ? null
-          : <Row gutter={[8, 8]}>
-          <Col span={12} >
-            {/* <solt name="search" ></solt> */}
-          </Col>
-          <Col span={12} >
-            <Space>
-              <Tooltip title='列表定制' >
-                 {/*  @click.prevent */}
-                 {/*  <a class="ant-dropdown-link"> <menu-outlined /> </a> */}
-                <Dropdown
-                  trigger={['click']}
-                  overlay={
-                  <Menu>
-                    {
-                      rowCustomized.value.map((item, index) => (
-                        <Menu.item key={item.key} onClick={() => swicthColumn(index)} >
-                          {item.title}
-                        </Menu.item>
-                      ))
-                    }
-                  </Menu>}
-                >
-                </Dropdown>
-              </Tooltip>
-                 {/*  @click.prevent */}
-                 {/*  <a class="ant-dropdown-link"> <menu-outlined /> </a> */}
-                <DropdownButton
-                  trigger={['click']}
-                  overlay={opraMeun}
-                  icon={<DownOutlined />}
-                  onClick={reload}
-                >
-                  <Button>刷新</Button>
-                </DropdownButton>
-            </Space>
-          </Col>
-        </Row>}
-      </>
-    )
+// const TablePro: FunctionalComponent<Props> = (props) => {
+//   const { request, easy, columns } = props
+
+//   const emits = defineEmits(['add'])
+
+//   const loading = ref<boolean>(false)
+
+//   const columnsPro = ref<(TableColumnProps & {hidden: boolean}) []>(columns.map(column => ({ ...column, hidden: false })))
+
+//   const columnsFilter = computed(() => columnsPro.value.filter(column => !column.hidden))
+
+//   const rowCustomized = computed(() => columns.map(item => ({ title: item.title, key: item.key })))
+
+//   const pagination = reactive<PaginationProps>(Object.assign({}, {
+//     current: 1,
+//     pageSize: 10,
+//     total: 0,
+//     onChange: (page: number, pageSize: number) => onChangePage(page, pageSize)
+//   }, typeof props.pagination === 'boolean' ? {} : { ...props.pagination }))
+
+//   const dataSource = ref([])
+
+//   const opraMeun = (
+//     <Menu>
+//       <Menu.item key={0} onClick={emits('add')} > 新增 </Menu.item>
+//       <Menu.item key={2} onClick={() => exportExcel()}> 导出 </Menu.item>
+//       <Menu.item key={3} onClick={() => pure()}> 纯净 </Menu.item>
+//     </Menu>
+//   )
+
+//   const exportExcel = () => {}
+
+//   const pure = () => {}
+
+//   const reload = () => dispatchRequest()
+
+//   const onChangePage = (page: number, pageSize: number) => {
+//     pagination.current = page
+//     pagination.pageSize = pageSize
+//     dispatchRequest()
+//   }
+
+//   const dispatchRequest = async () => {
+//     loading.value = true
+//     const { data, sum } = await request.get({
+//       ...request.params,
+//       page: pagination.current,
+//       pageSize: pagination.pageSize
+//     })
+//     loading.value = false
+//     pagination.total = sum
+//     dataSource.value = data
+//   }
+
+//   /**
+//    * 展示或者隐藏对应的column
+//    */
+//   const swicthColumn = (index: number) => {
+//     columnsPro.value[index].hidden = !columnsPro.value[index].hidden
+//   }
+
+//   const RenderOpraRow = () => {
+//     return (
+//       <>
+//         {easy
+//           ? null
+//           : <Row gutter={[8, 8]}>
+//           <Col span={12} >
+//             {/* <solt name="search" ></solt> */}
+//           </Col>
+//           <Col span={12} >
+//             <Space>
+//               <Tooltip title='列表定制' >
+//                  {/*  @click.prevent */}
+//                  {/*  <a class="ant-dropdown-link"> <menu-outlined /> </a> */}
+//                 <Dropdown
+//                   trigger={['click']}
+//                   overlay={
+//                   <Menu>
+//                     {
+//                       rowCustomized.value.map((item, index) => (
+//                         <Menu.item key={item.key} onClick={() => swicthColumn(index)} >
+//                           {item.title}
+//                         </Menu.item>
+//                       ))
+//                     }
+//                   </Menu>}
+//                 >
+//                 </Dropdown>
+//               </Tooltip>
+//                  {/*  @click.prevent */}
+//                  {/*  <a class="ant-dropdown-link"> <menu-outlined /> </a> */}
+//                 <DropdownButton
+//                   trigger={['click']}
+//                   overlay={opraMeun}
+//                   icon={<DownOutlined />}
+//                   onClick={reload}
+//                 >
+//                   <Button>刷新</Button>
+//                 </DropdownButton>
+//             </Space>
+//           </Col>
+//         </Row>}
+//       </>
+//     )
+//   }
+
+//   return (
+//       <>
+//         <RenderOpraRow />
+//         {/*       */}
+//         <Table
+//           style="margin-top: 20px;"
+//           columns={columnsFilter.value}
+//           loading={loading.value}
+//           pagination={typeof props.pagination === 'boolean' ? false : pagination}
+//           dataSource={dataSource.value}
+//         >
+//           <slot></slot>
+//           <slot name='action'></slot>
+//         </Table>
+//       </>
+//   )
+// }
+
+// export default TablePro
+
+const TablePro = defineComponent({
+  props: {
+    columns: {
+      type: Array as PropType<TableColumnProps[]>,
+      require: true
+    }
   }
-
-  return (
-      <>
-        <RenderOpraRow />
-        {/*       */}
-        <Table
-          style="margin-top: 20px;"
-          columns={columnsFilter.value}
-          loading={loading.value}
-          pagination={typeof props.pagination === 'boolean' ? false : pagination}
-          dataSource={dataSource.value}
-        >
-          <slot></slot>
-          <slot name='action'></slot>
-        </Table>
-      </>
-  )
-}
-
-export default TablePro
+})

+ 61 - 1
src/controller/cvs/spaceController.ts

@@ -1,4 +1,4 @@
-import { addSpace, delSpace, getSpace, updateSpace } from '@/api/cvs/video'
+import { addDevice, addSpace, delSpace, getDeviceAiRetById, getDeviceById, getDevicePage, getDeviceRecordById, getDeviceThumbById, getSpace, updateSpace } from '@/api/cvs/video'
 import { message } from 'ant-design-vue'
 
 export class SpaceController {
@@ -36,4 +36,64 @@ export class SpaceController {
     const { code, msg } = await delSpace(id)
     code === 200 ? message.success('删除成功') : message.error(msg)
   }
+
+  static async devicePage (params: COMMON.API.QueryParams & {deviceName: string}) {
+    const { code, data } = await getDevicePage(params)
+    if (code === 200) {
+      return data
+    }
+  }
+
+  static async deviceById (id: string) {
+    const { code, data } = await getDeviceById(id)
+    if (code === 200) {
+      return data
+    }
+  }
+
+  static async addDevice (data: Partial<CVS.device>) {
+    const { code, msg } = await addDevice(data)
+    code === 200 ? message.success('新增成功') : message.error(msg)
+  }
+
+  static async deviceRecordById (
+    params: {
+      deviceId: string
+      startTs?: number,
+      endTs?: number,
+      recordFormat: 'FLV' | 'TS' | 'MP4' | 'RAW'
+    }
+  ) {
+    const { code, data } = await getDeviceRecordById(params)
+    if (code === 200) {
+      return data
+    }
+  }
+
+  static async deviceThumbById (
+    params: {
+      deviceId: string
+      startTs?: number,
+      endTs?: number
+    }
+  ) {
+    const { code, data } = await getDeviceThumbById(params)
+    if (code === 200) {
+      return data
+    }
+  }
+
+  static async deviceAiRetById (
+    params: {
+      deviceId: string
+      startTs?: number,
+      endTs?: number
+      aiId?: string
+    }
+  ) {
+    const { code, data } = await getDeviceAiRetById(params)
+    if (code === 200) {
+      return data
+    }
+  }
 }

+ 1 - 1
src/hooks/effect.ts

@@ -55,7 +55,7 @@ export const usePort = (title: string) => {
   if (title === '物联网') {
     setBaseUrl('/iot')
   } else if (title === '视联网') {
-    setBaseUrl('/cvs')
+    setBaseUrl('/cvss')
   } else if (title === '用户群组') {
     setBaseUrl('/user')
   } else {

+ 106 - 2
src/pages/cvs/video/device.vue

@@ -1,9 +1,113 @@
 <template>
-<a-card>
-  device
+<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>
+
+  <modal-pro
+    style="width: 1000px;"
+    label="接入设备"
+    :open="state.visible"
+    @cancel="state.visible = false"
+    @ok="ok"
+  >
+  <a-form  style="width: 100%;" :labelCol="{span: 3}" :wrapperCol="{span: 14}" >
+      <a-form-item label="设备名称" v-bind="validateInfos.deviceName"  >
+        <InputTsx allowClear placeholder="请输入设备名称" />
+      </a-form-item>
+      <a-form-item  label="app" v-bind="validateInfos.app"  >
+        <InputTsx allowClear placeholder="请输入英文、数字组成的app" />
+      </a-form-item>
+      <a-form-item  label="stream" v-bind="validateInfos.stream"  >
+        <InputTsx allowClear placeholder="请输入英文、数字组成的stream" />
+      </a-form-item>
+      <a-form-item  label="设备类型"  >
+        {{deviceState.type}}
+      </a-form-item>
+      <a-form-item  label="设备地址"  >
+        <InputTsx allowClear placeholder="请输入设备地址" />
+      </a-form-item>
+      <a-form-item  label="设备经纬度"  >
+        <InputTsx allowClear placeholder="输入地址后自动填入经纬度,例如(116.404,39.915)" />
+      </a-form-item>
+      <a-form-item  label="设备描述"  >
+        <a-textarea v-model:value="deviceState.description" placeholder="请输入设备描述" :rows="4" />
+      </a-form-item>
+    </a-form>
+  </modal-pro>
 </a-card>
 </template>
 <script lang='ts' setup >
+import { InputTsx } from '@/components/MicroComponents/index'
+import { reactive } from 'vue'
+import { Form } from 'ant-design-vue'
+
+const columns = [
+  {
+    title: '设备ID',
+    dataIndex: 'deviceId',
+    key: 'deviceId'
+  },
+  {
+    title: '设备名称',
+    dataIndex: 'deviceName',
+    key: 'deviceName'
+  },
+  {
+    title: '空间类型',
+    dataIndex: 'type',
+    key: 'type'
+  },
+  {
+    title: '描述',
+    dataIndex: 'description',
+    key: 'description'
+  },
+  {
+    title: '状态',
+    dataIndex: 'status',
+    key: 'status'
+  },
+  {
+    title: '操作',
+    dataIndex: 'action',
+    key: 'action'
+  }
+]
+
+const useForm = Form.useForm
+
+const state = reactive({
+  visible: false
+})
+
+const deviceState = reactive({
+  spaceId: '',
+  deviceName: '',
+  app: '',
+  stream: '',
+  description: '',
+  deviceStreamId: '',
+  gisLongitude: '',
+  gisLatitude: '',
+  gisName: '',
+  type: 'RTSP'
+})
+
+const { resetFields, validate, validateInfos } = useForm(deviceState, {
+  deviceName: [{ required: true, message: '请填写设备名称' }],
+  app: [{ required: true, message: '请填写app名称' }],
+  stream: [{ required: true, message: '请填写stream名称' }]
+})
+
+const ok = () => {
+  validate().then(() => {
+
+  }).catch(() => {})
+}
+
+const openModal = () => state.visible = true
 </script>
 <style lang='less' scoped >
 </style>

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

@@ -6,7 +6,9 @@
   </a-row>
 
   <table-pro
-
+    :request="{
+      get: SpaceController.page
+    }"
     :columns="columns"
     :easy="true"
   >
@@ -19,13 +21,15 @@
     :visible="visible"
     @close="visible = false"
     @next="stepNext"
+    @pre="preNext"
+    @submit="submit"
   >
   <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>
-      <a-form-item  label="空间类型" v-bind="validateInfos.edgeName"  >
+      <a-form-item  label="空间类型" v-bind="validateInfos.type"  >
         <a-radio-group v-model:value="spaceState.type" button-style="solid">
           <a-radio-button
             v-for="item in SpaceController.type"
@@ -47,19 +51,19 @@
           <a-row class="content-item"  >
             <a-col span="4" >推流配置:</a-col>
             <a-col>
-              <a-radio-group v-model:value="spaceState.config.upstreamAuth.enabled" name="radioGroup">
+              <a-radio-group v-model:value="spaceState.upstreamAuth.enabled" name="radioGroup">
                 <a-radio :value="false">关闭</a-radio>
                 <a-radio :value="true">开启</a-radio>
               </a-radio-group>
             </a-col>
           </a-row>
-          <a-row class="content-item" v-show="spaceState.config.upstreamAuth.enabled">
+          <a-row class="content-item" v-show="spaceState.upstreamAuth.enabled">
             <a-col span="4" >推流鉴权密钥:</a-col>
-            <a-col><InputTsx placeholder="请输入推流鉴权密钥" v-model:value="spaceState.config.upstreamAuth.key" /> </a-col>
+            <a-col><InputTsx placeholder="请输入推流鉴权密钥" v-model:value="spaceState.upstreamAuth.key" /> </a-col>
           </a-row>
-          <a-row class="content-item" v-show="spaceState.config.upstreamAuth.enabled">
+          <a-row class="content-item" v-show="spaceState.upstreamAuth.enabled">
             <a-col span="4" >过期时间: </a-col>
-            <a-col><a-input-number id="inputNumber"  :min="1"    v-model:value="spaceState.config.upstreamAuth.expire"  /> 分钟 </a-col>
+            <a-col><a-input-number id="inputNumber"  :min="1"    v-model:value="spaceState.upstreamAuth.expire"  /> 分钟 </a-col>
           </a-row>
         </a-col>
       </a-row>
@@ -69,19 +73,19 @@
           <a-row class="content-item" >
             <a-col span="4" >观看配置:</a-col>
             <a-col>
-              <a-radio-group v-model:value="spaceState.config.downstreamAuth.enabled" name="radioGroup">
+              <a-radio-group v-model:value="spaceState.downstreamAuth.enabled" name="radioGroup">
                 <a-radio :value="false">关闭</a-radio>
                 <a-radio :value="true">开启</a-radio>
               </a-radio-group>
             </a-col>
           </a-row>
-          <a-row class="content-item" v-show="spaceState.config.downstreamAuth.enabled"  >
+          <a-row class="content-item" v-show="spaceState.downstreamAuth.enabled"  >
             <a-col span="4" >观看鉴权密钥:</a-col>
-            <a-col><InputTsx placeholder="请输入观看鉴权密钥" v-model:value="spaceState.config.downstreamAuth.key" /> </a-col>
+            <a-col><InputTsx placeholder="请输入观看鉴权密钥" v-model:value="spaceState.downstreamAuth.key" /> </a-col>
           </a-row>
-          <a-row class="content-item" v-show="spaceState.config.downstreamAuth.enabled"  >
+          <a-row class="content-item" v-show="spaceState.downstreamAuth.enabled"  >
             <a-col span="4" >过期时间: </a-col>
-            <a-col><a-input-number id="inputNumber"  :min="1" v-model:value="spaceState.config.downstreamAuth.expire"   /> 分钟 </a-col>
+            <a-col><a-input-number id="inputNumber"  :min="1" v-model:value="spaceState.downstreamAuth.expire"   /> 分钟 </a-col>
           </a-row>
         </a-col>
       </a-row>
@@ -91,27 +95,27 @@
           <a-row class="content-item" >
             <a-col span="4" >基础配置:</a-col>
             <a-col>
-              <a-radio-group v-model:value="spaceState.config.recording.enabled" name="radioGroup">
+              <a-radio-group v-model:value="spaceState.recording.enabled" name="radioGroup">
                 <a-radio :value="false">关闭</a-radio>
                 <a-radio :value="true">开启</a-radio>
               </a-radio-group>
             </a-col>
           </a-row>
-          <a-row class="content-item" v-show="spaceState.config.recording.enabled"  >
+          <a-row class="content-item" v-show="spaceState.recording.enabled"  >
             <a-col span="4">单个文件时常: </a-col>
-            <a-col><a-input-number id="inputNumber"  :min="1" v-model:value="spaceState.config.recording.duration"   /> 分钟 </a-col>
+            <a-col><a-input-number id="inputNumber"  :min="1" v-model:value="spaceState.recording.duration"   /> 分钟 </a-col>
           </a-row>
-          <a-row class="content-item" v-show="spaceState.config.recording.enabled"  >
+          <a-row class="content-item" v-show="spaceState.recording.enabled"  >
             <a-col span="4" >存储格式: </a-col>
             <a-col>
-              <a-radio-group v-model:value="spaceState.config.recording.format" name="radioGroup">
+              <a-radio-group v-model:value="spaceState.recording.format" name="radioGroup">
                 <a-radio value="MP4">MP4</a-radio>
                 <a-radio value="FLV">FLV</a-radio>
                 <a-radio value="M3U8">M3U8</a-radio>
               </a-radio-group>
             </a-col>
           </a-row>
-          <a-row class="content-item" v-show="spaceState.config.recording.enabled"  >
+          <a-row class="content-item" v-show="spaceState.recording.enabled"  >
             <a-col span="4" >录制方式: </a-col>
             <a-col>周期录制 </a-col>
           </a-row>
@@ -123,15 +127,15 @@
           <a-row class="content-item" >
             <a-col span="4" >截图配置:</a-col>
             <a-col>
-              <a-radio-group v-model:value="spaceState.config.thumbnail.enabled" name="radioGroup">
+              <a-radio-group v-model:value="spaceState.thumbnail.enabled" name="radioGroup">
                 <a-radio :value="false">关闭</a-radio>
                 <a-radio :value="true">开启</a-radio>
               </a-radio-group>
             </a-col>
           </a-row>
-          <a-row class="content-item" v-show="spaceState.config.thumbnail.enabled"  >
+          <a-row class="content-item" v-show="spaceState.thumbnail.enabled"  >
             <a-col span="4" >截图周期: </a-col>
-            <a-col><a-input-number id="inputNumber"  :min="1" v-model:value="spaceState.config.thumbnail.interval"   /> 秒 </a-col>
+            <a-col><a-input-number id="inputNumber"  :min="1" v-model:value="spaceState.thumbnail.interval"   /> 秒 </a-col>
           </a-row>
         </a-col>
       </a-row>
@@ -141,7 +145,7 @@
           <a-row class="content-item" >
             <a-col span="4" >AI配置:</a-col>
             <a-col>
-              <a-radio-group v-model:value="spaceState.config.aiConfig.enabled" name="radioGroup">
+              <a-radio-group v-model:value="spaceState.aiConfig.enabled" name="radioGroup">
                 <a-radio :value="false">关闭</a-radio>
                 <a-radio :value="true">开启</a-radio>
               </a-radio-group>
@@ -155,7 +159,7 @@
       </a-row>
     </div>
   </a-form>
-  </StepModal>
+ </StepModal>
 </a-card>
 </template>
 <script lang='ts' setup >
@@ -170,9 +174,9 @@ const useForm = Form.useForm
 
 const steps = [{ title: '空间基本信息' }, { title: '空间配置' }]
 
-const step = ref(1)
+const step = ref(0)
 
-const visible = ref<boolean>(true)
+const visible = ref<boolean>(false)
 
 const columns = [
   {
@@ -227,35 +231,34 @@ const spaceState = reactive<CVS.space>({
   spaceId: null,
   status: '',
   type: 'RTMP',
-  config: {
-    upstreamAuth: {
-      enabled: false,
-      key: '',
-      expire: 1
-    },
-    downstreamAuth: {
-      enabled: false,
-      key: '',
-      expire: 1
-    },
-    recording: {
-      enabled: true,
-      duration: 1,
-      format: 'FLV',
-      recordType: 'PERIOD',
-      bucket: 'cgtt8ufaq9pp493hqy8'
-    },
-    thumbnail: {
-      enabled: true,
-      interval: 1,
-      bucket: 'cgtt8ufaq9pp493hqy8'
-    },
-    aiConfig: {
-      enabled: true,
-      configuration: [],
-      bucket: 'cgtt8ufaq9pp493hqy8'
-    }
+  upstreamAuth: {
+    enabled: false,
+    key: '',
+    expire: 1
+  },
+  downstreamAuth: {
+    enabled: false,
+    key: '',
+    expire: 1
+  },
+  recording: {
+    enabled: true,
+    duration: 1,
+    format: 'FLV',
+    recordType: 'PERIOD',
+    bucket: ''
+  },
+  thumbnail: {
+    enabled: true,
+    interval: 1,
+    bucket: ''
+  },
+  aiConfig: {
+    enabled: true,
+    configuration: [],
+    bucket: ''
   }
+
 })
 
 const { resetFields, validate, validateInfos } = useForm(spaceState, {
@@ -267,8 +270,18 @@ const openModal = () => visible.value = true
 
 const closeModal = () => visible.value = false
 
+const submit = () => {
+  SpaceController.add(spaceState)
+}
+
 const stepNext = () => {
-  step.value++
+  validate().then(() => {
+    step.value++
+  }).catch(() => {})
+}
+
+const preNext = () => {
+  step.value--
 }
 
 </script>

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

@@ -9,7 +9,7 @@ declare namespace CVS {
     spaceId: number | null
     status: 'RUNNING' | 'STOPPED' | 'OPERATING' | ''
     type: 'RTMP' | 'GB28181' | 'ONVIF' | 'BVCP' | 'RTSP' | 'JT808' | '',
-    config: {
+
       upstreamAuth: {
         enabled: boolean
         key: string
@@ -25,19 +25,33 @@ declare namespace CVS {
         'duration': number, // 单个文件市长,显示分钟 传递秒
         'format': 'FLV',
         'recordType': 'PERIOD' // 这个保留 只有周期录制
-        'bucket': 'cgtt8ufaq9pp493hqy8'
+        'bucket': string
       }
       thumbnail: {
         enabled: boolean
         interval: number
-        bucket: 'cgtt8ufaq9pp493hqy8'
+        bucket: string
       }
       // ai 配置
       'aiConfig': {
         'enabled': boolean,
         'configuration': [],
-        'bucket': 'cgtt8ufaq9pp493hqy8',
+        'bucket': string,
       }
-    }
+
+  }
+
+  interface device {
+    deviceId: number, // 设备id
+    deviceName: string // 设备名称
+    type: 'RTSP' | 'RTMP' | 'GB28181' // 空间类型 RTSP RTMP GB28181
+    description: string
+    status: 'ONLINE' | 'OFFLINE' | 'PUSHING' | 'PULLING' | 'ERROR' // 设备状态,对应的值有 ONLINE 在线 OFFLINE 离线 PUSHING 推流中 PULLING 拉流中 ERROR 异常
+    spaceId: string // 空间id
+    spaceName: string // 空间名称
+    deviceStreamId: string // 视频流id
+    gisLongitude: string
+    gisLatitude: string
+    gisName: string
   }
 }