Ver código fonte

feat:ota包管理

lvkun996 2 anos atrás
pai
commit
1092ae2a62

+ 23 - 0
src/api/common/index.ts

@@ -23,3 +23,26 @@ export const getSysConf = () => {
     method: 'GET'
   })
 }
+
+/**
+ * 文件上传
+ * @returns
+ */
+export const uploadFile = () => {
+  return request<COMMON.FIle.Detail>({
+    url: '/file',
+    method: 'POST'
+  })
+}
+
+/**
+ * @param filePath 文件地址
+ * @param fileName 文件名称
+ * @returns
+ */
+export const delFile = (filePath: string, fileName: string) => {
+  return request<string>({
+    url: `/file/${filePath}/${fileName}`,
+    method: 'DELETE'
+  })
+}

+ 47 - 0
src/api/iot/ota.ts

@@ -0,0 +1,47 @@
+import request from '@/service/request'
+
+export const getOtaPkgPage = (params: IOT.API.OTA.QueryParams) => {
+  return request<IOT.API.OTA.Detail[]>({
+    url: '/otaPkg/page',
+    method: 'GET',
+    params
+  })
+}
+
+export const getOtaPkgList = (params: IOT.API.OTA.QueryParams) => {
+  return request<IOT.API.OTA.Detail[]>({
+    url: '/otaPkg/page',
+    method: 'GET',
+    params
+  })
+}
+
+export const addOtaPkg = (params: IOT.API.OTA.BodyParams) => {
+  return request<string>({
+    url: '/otaPkg',
+    method: 'POST',
+    params
+  })
+}
+
+export const updateOtaPkg = (params: IOT.API.OTA.BodyParams) => {
+  return request<string>({
+    url: '/otaPkg/page',
+    method: 'GET',
+    params
+  })
+}
+
+export const delOtaPkg = (id: string) => {
+  return request<string>({
+    url: `/otaPkg/${id}`,
+    method: 'DELETE'
+  })
+}
+
+export const getOtaPkgById = (id: string) => {
+  return request<IOT.API.OTA.Detail>({
+    url: `/otaPkg/${id}`,
+    method: 'GET'
+  })
+}

+ 55 - 0
src/components/UploadPro/index.tsx

@@ -0,0 +1,55 @@
+import { defineComponent } from 'vue'
+import { InboxOutlined } from '@ant-design/icons-vue'
+import { UploadChangeParam, message } from 'ant-design-vue'
+import { instance } from '@/service/request'
+
+/**
+ * @description 通用文件上传组件
+ */
+
+export const UploadPro = defineComponent({
+  name: 'upload-pro',
+  props: {
+    action: {
+      type: String,
+      default: instance.defaults.baseURL + '/file'
+    },
+    accept: {
+      type: String,
+      default: '.zip'
+    }
+  },
+  emits: ['update:value'],
+  setup (props, ctx) {
+    const handleChange = (info: UploadChangeParam) => {
+      const status = info.file.status
+      if (status !== 'uploading') {
+        console.log(info.file, info.fileList)
+      }
+      if (status === 'done') {
+        ctx.emit('update:value', info)
+        message.success(`${info.file.name} 文件上传成功`)
+      } else if (status === 'error') {
+        message.error(`${info.file.name} 文件上传失败`)
+      }
+    }
+
+    return () => (
+            <a-upload-dragger
+              accept={props.accept}
+              value="fileList"
+              name="file"
+              action={props.action}
+              onChange={handleChange}
+            >
+              <p class="ant-upload-drag-icon">
+                <InboxOutlined />
+              </p>
+              <p class="ant-upload-text">点击选择文件或者拖拽文件到此处</p>
+              <p class="ant-upload-hint">
+                只能上传{props.accept}尾缀文件
+              </p>
+            </a-upload-dragger>
+    )
+  }
+})

+ 5 - 1
src/controller/common/common.ts

@@ -1,4 +1,4 @@
-import { getTransport } from '@/api/common'
+import { delFile, getTransport } from '@/api/common'
 import { TransportEnum } from '@/enum/common'
 export class CommonController {
   static dataTypeByKeyMap = new Map([
@@ -18,4 +18,8 @@ export class CommonController {
       }
     })
   }
+
+  static async delOtaPkg (filePath: string, fileName: string) {
+    await delFile(filePath, fileName)
+  }
 }

+ 1 - 0
src/controller/iot/index.ts

@@ -2,3 +2,4 @@ export { ModelController } from './model'
 export { ModelAttrController } from './modelAttr'
 export { ModelCmdController } from './modelCmd'
 export { EventController } from './event'
+export { OtaController } from './ota'

+ 44 - 0
src/controller/iot/ota.ts

@@ -0,0 +1,44 @@
+import { addOtaPkg, delOtaPkg, getOtaPkgById, getOtaPkgPage, updateOtaPkg } from '@/api/iot/ota'
+import { PkgTypeEnum } from '@/enum/common'
+import { message } from 'ant-design-vue'
+
+export class OtaController {
+  static pkgTypeMap = new Map([
+    [PkgTypeEnum.FILE_URL, { value: PkgTypeEnum.FILE_URL, label: '地址访问' }],
+    [PkgTypeEnum.FILE_ZIP, { value: PkgTypeEnum.FILE_ZIP, label: 'zip包' }]
+  ])
+
+  static pkgTypeList = [{ value: PkgTypeEnum.FILE_URL, label: '地址访问' }, { value: PkgTypeEnum.FILE_ZIP, label: 'zip包' }]
+
+  static async page (params: IOT.API.OTA.QueryParams) {
+    return await getOtaPkgPage(params)
+  }
+
+  static async list (params = {
+    label: '',
+    version: '',
+    pkgType: '',
+    limit: 10000
+  }) {
+    return await getOtaPkgPage(params as any)
+  }
+
+  static async add (data: IOT.API.OTA.BodyParams) {
+    await addOtaPkg(data)
+    message.success('新增成功')
+  }
+
+  static async update (data: IOT.API.OTA.BodyParams) {
+    await updateOtaPkg(data)
+    message.success('修改成功')
+  }
+
+  static async del (id: string) {
+    await delOtaPkg(id)
+    message.success('删除成功')
+  }
+
+  static async byId (id: string) {
+    return await getOtaPkgById(id)
+  }
+}

+ 5 - 0
src/enum/common.ts

@@ -52,3 +52,8 @@ export enum SubjectEventEnum {
   'MODEL_CREATE' = 'MODEL_CREATE',
   'MODEL_DELETE' = 'MODEL_DELETE'
 }
+
+export enum PkgTypeEnum {
+  'FILE_URL' = 'FILE_URL',
+  'FILE_ZIP' = 'FILE_ZIP'
+}

+ 0 - 1
src/hooks/effect.ts

@@ -63,7 +63,6 @@ export const usePort = (title: string) => {
     setBaseUrl('/user')
   }
 }
-
 /**
  * `useDeviceResolution` 函数是一个 TypeScript 函数,允许您跟踪设备的屏幕宽度和高度,并在屏幕大小调整时执行回调函数。
  * @param {Function} cb - 参数“cb”是一个回调函数,每当设备分辨率发生变化时就会调用该函数。

+ 177 - 2
src/pages/Iot/ota/index.vue

@@ -1,9 +1,184 @@
 <template>
-<a-card>
-  ota
+<a-card title="ota包管理" >
+  <a-row>
+    <a-col>
+
+    </a-col>
+    <a-col>
+      <a-button type="primary" @click="openModal('add', {})">新增</a-button>
+    </a-col>
+  </a-row>
+  <a-table
+      style="margin-top: 10px;"
+      :columns="columns"
+      :data-source="state.dataSource"
+      :loading="state.loading"
+      :pagination="queryState"
+      @change="changePage"
+    >
+      <template #bodyCell="{column, record}">
+        <template v-if="column.key === 'action'">
+            <a-space>
+              <a href="#" @click="openModal('update', record)">编辑</a>
+              <a-popconfirm
+                  title="确实要删除吗?"
+                  ok-text="确定"
+                  cancel-text="取消"
+                  @confirm="delOtaPkg(record.id)"
+                >
+                  <a href="#">删除</a>
+              </a-popconfirm>
+            </a-space>
+        </template>
+      </template>
+    </a-table>
 </a-card>
+
+<modal-pro
+  :label="modalTitle"
+  :open="state.visible"
+  @cancel="closeModal"
+  @ok="ok"
+>
+<a-form  :labelCol="{span: 6}" :wrapperCol="{span: 14}" >
+      <a-form-item label="包名" v-bind="validateInfos.label" >
+        <a-input allowClear v-model:value="otaPkgState.label" />
+      </a-form-item>
+      <a-form-item label="版本" v-bind="validateInfos.version" >
+        <a-input allowClear v-model:value="otaPkgState.version" />
+      </a-form-item>
+      <a-form-item label="类型" v-bind="validateInfos.pkgType"  >
+        <a-select allowClear v-model:value="otaPkgState.pkgType" >
+          <a-select-option
+              v-for="item in OtaController.pkgTypeList"
+              :key="item.value"
+              :value="item.value"
+            >
+              {{item.label}}
+            </a-select-option>
+        </a-select>
+      </a-form-item>
+      <a-form-item label="类型" v-bind="validateInfos.pkgUrl"  >
+        <a-input v-if="otaPkgState.pkgType === PkgTypeEnum.FILE_URL" allowClear v-model:value="otaPkgState.pkgUrl" />
+        <upload-pro v-else></upload-pro>
+      </a-form-item>
+</a-form>
+</modal-pro>
 </template>
 <script lang='ts' setup >
+import { OtaController } from '@/controller/iot'
+import { reactive, onMounted, computed } from 'vue'
+import { Form, message } from 'ant-design-vue'
+import { PkgTypeEnum } from '@/enum/common'
+import { CommonController } from '@/controller'
+
+const columns = [
+  {
+    title: '包名',
+    dataIndex: 'label'
+  },
+  {
+    title: '版本',
+    dataIndex: 'version'
+  },
+  {
+    title: '包类型',
+    dataIndex: 'pkgType'
+  },
+  {
+    title: '包地址',
+    dataIndex: 'pkgUrl'
+  },
+  {
+    title: '操作',
+    dataIndex: 'action',
+    key: 'action'
+  }
+]
+
+const modalTitle = computed(() => state.opraState === 'add' ? '新增ota包' : '编辑ota包')
+
+const queryState = reactive({
+  page: 1,
+  pageSize: 10,
+  total: 0,
+  label: '',
+  version: '',
+  pkgType: ''
+})
+
+const state = reactive<{
+  loading: boolean,
+  dataSource: IOT.API.OTA.Detail[],
+  visible: boolean
+  opraState: 'add' | 'update'
+}>({
+  loading: false,
+  dataSource: [],
+  visible: false,
+  opraState: 'add'
+})
+
+const otaPkgState = reactive({
+  version: '',
+  pkgType: '',
+  pkgUrl: '',
+  label: ''
+})
+
+const fileState = reactive({
+  filePath: '', // 文件地址
+  filename: '',
+  fileUrl: ''
+})
+
+const useForm = Form.useForm
+
+const { resetFields, validate, validateInfos } = useForm(otaPkgState, reactive({
+  version: [{ required: true, message: '请填写版本号' }],
+  pkgType: [{ required: true, message: '请选择包类型' }],
+  pkgUrl: [{ required: true, message: '请填写包地址' }],
+  label: [{ required: true, message: '请填写包名' }]
+}))
+
+const changePage = ({ current }) => {
+  queryState.page = current
+  getOtaPkgPage()
+}
+
+// 关闭弹窗 同时因为没有确定创建ota包,所以如果是上传了压缩包,则需要删除压缩包
+const closeModal = async () => {
+  state.visible = false
+  CommonController.delOtaPkg(fileState.filePath, fileState.filename)
+}
+
+const ok = () => {
+  validate().then(() => {
+
+  })
+}
+
+const openModal = (key: 'add' | 'update', record: IOT.API.OTA.Detail) => {
+  state.visible = true
+  resetFields(record)
+}
+
+const delOtaPkg = async (id: string) => {
+  await OtaController.del(id)
+  getOtaPkgPage()
+}
+
+const getOtaPkgPage = async () => {
+  state.loading = true
+  const { data } = await OtaController.page(queryState as IOT.API.OTA.QueryParams)
+  state.dataSource = data
+  state.loading = false
+}
+
+onMounted(() => {
+  getOtaPkgPage()
+})
+
 </script>
 <style lang='less' scoped >
 </style>

+ 1 - 1
src/service/request.ts

@@ -4,7 +4,7 @@ import defaultSetting from '../../config/defaultSetting'
 import { useModule } from '@/hooks'
 import { useUserStore } from '@/store'
 
-const instance = axios.create({
+export const instance = axios.create({
   baseURL: '',
   timeout: 10000
 })

+ 7 - 0
src/type/common.d.ts

@@ -47,4 +47,11 @@ declare namespace COMMON {
       }
     }
 
+    namespace FIle {
+      interface Detail {
+        'filePath': string, // 文件地址
+        'filename': string, // 文件名称
+        'fileUrl': string // 文件下载地址
+      }
+    }
 }

+ 26 - 1
src/type/iot.d.ts

@@ -1,4 +1,3 @@
-
 declare namespace IOT {
 
   // MQTT
@@ -434,6 +433,32 @@ declare namespace IOT {
       }
     }
 
+    namespace OTA {
+
+      interface QueryParams {
+        page: number
+        pageSize: number
+        label: string
+        version: string
+        pkgType: 'FILE_URL' | 'FILE_ZIP'
+      }
+      interface BodyParams {
+        label: string
+        version: string
+        pkgUrl: string
+        pkgType: 'FILE_URL' | 'FILE_ZIP',
+      }
+      interface Detail {
+        'id'?: string,
+        'createAt': number,
+        'updateAt': null,
+        'version': string,
+        'pkgType': 'FILE_URL' | 'FILE_ZIP',
+        'pkgUrl': string,
+        'label': string,
+      }
+    }
+
   }
 
 }