Bladeren bron

no message

DESKTOP-SVI9JE1\muzen 1 jaar geleden
bovenliggende
commit
ae8f131035

+ 36 - 0
src/api/device/class.js

@@ -0,0 +1,36 @@
+import request from '@/utils/request'
+
+// 查询分类列表
+export function list(query) {
+  return request({
+    url: `/device/manage/category/list`,
+    method: 'get',
+    params: query
+  })
+}
+
+// 添加设备类型
+export function create(data) {
+  return request({
+    url: `/device/manage/category/add`,
+    method: 'post',
+    data: data
+  })
+}
+
+// 编辑设备类型
+export function edit(data) {
+  return request({
+    url: `/device/manage/category/edit`,
+    method: 'post',
+    data: data
+  })
+}
+
+// 删除
+export function remove(id) {
+  return request({
+    url: `/device/manage/category/${id}`,
+    method: 'delete'
+  })
+}

+ 28 - 0
src/api/device/function.js

@@ -0,0 +1,28 @@
+import request from '@/utils/request'
+
+// 查询权限列表
+export function list(query) {
+  return request({
+    url: `/device/manage/function/list`,
+    method: 'get',
+    params: query
+  })
+}
+
+// 新增权限
+export function create(data) {
+  return request({
+    url: `/device/manage/function/add`,
+    method: 'post',
+    data: data
+  })
+}
+
+// 编辑权限
+export function edit(data) {
+  return request({
+    url: `/device/manage/function/edit`,
+    method: 'post',
+    data: data
+  })
+}

+ 54 - 0
src/api/device/list.js

@@ -0,0 +1,54 @@
+import request from '@/utils/request'
+
+// 分页列表
+export function list(query) {
+  return request({
+    url: `/device/manage/list/page`,
+    method: 'get',
+    params: query
+  })
+}
+
+// 设备详情
+export function detail(query) {
+  return request({
+    url: `/device/manage/view`,
+    method: 'get',
+    params: query
+  })
+}
+
+// 删除设备
+export function remove(id) {
+  return request({
+    url: `/device/manage/${id}`,
+    method: 'delete'
+  })
+}
+
+// 添加设备
+export function create(data) {
+  return request({
+    url: `/device/manage/add`,
+    method: 'post',
+    data: data
+  })
+}
+
+// 编辑设备
+export function edit(data) {
+  return request({
+    url: `/device/manage/edit`,
+    method: 'post',
+    data: data
+  })
+}
+
+// 上下架
+export function change(data){
+  return request({
+    url: `/device/manage/batchChangerStatus`,
+    method: 'post',
+    data
+  })
+}

+ 67 - 0
src/api/device/version.js

@@ -0,0 +1,67 @@
+import request from '@/utils/request'
+
+// 分页
+export function getPageList(data) {
+  return request({
+    url: `/device/version/pageList`,
+    method: 'post',
+    data: data
+  })
+}
+
+// 详情
+export function getDetailInfo(data) {
+  return request({
+    url: `/device/version/detailInfo`,
+    method: 'post',
+    data: data
+  })
+}
+
+// 新增
+export function getVersionAdd(data) {
+  return request({
+    url: `/device/version/add`,
+    method: 'post',
+    data: data
+  })
+}
+
+// 编辑
+export function updateEdit(data) {
+  return request({
+    url: `/device/version/update`,
+    method: 'post',
+    data: data
+  })
+}
+
+// 删除
+export function getVersionDelete(data) {
+  return request({
+    url: `/device/version/delete`,
+    method: 'post',
+    data: data
+  })
+}
+
+// 产品型号列表
+export function getClientTypeList(data) {
+  return request({
+    url: `/device/version/clientTypeList`,
+    method: 'post',
+    data: data
+  })
+}
+
+// 
+export function upload(data){
+  return request({
+    url: `/system/file/file/upload`,
+    method: 'post',
+    data: data,
+    headers:{
+      'Content-Type': 'multipart/form-data'
+    }
+  })
+}

+ 10 - 5
src/components/Upload/index.vue

@@ -1,8 +1,8 @@
 <template>
 <template>
   <el-upload ref="upload" :action="url ? url : reactiveData.action[listType]" :headers="reactiveData.headers"
   <el-upload ref="upload" :action="url ? url : reactiveData.action[listType]" :headers="reactiveData.headers"
-    :list-type="listType" name="multipartFile" :multiple="multiple" :data="data" :show-file-list="showFileList"
-    :before-upload="beforeUpload" :on-success="onSuccess" :on-error="onError" :before-remove="beforeRemove"
-    :on-remove="handleRemove" :disabled="disabled">
+    :list-type="listType" name="multipartFile" :multiple="multiple" :data="data" :accept="accept"
+    :show-file-list="showFileList" :before-upload="beforeUpload" :on-success="onSuccess" :on-error="onError"
+    :before-remove="beforeRemove" :on-remove="handleRemove" :disabled="disabled">
     <div class="image" v-if="listType === 'picture-card'">
     <div class="image" v-if="listType === 'picture-card'">
       <el-icon v-if="src === ''" size="28">
       <el-icon v-if="src === ''" size="28">
         <Plus />
         <Plus />
@@ -39,6 +39,11 @@ const props = defineProps({
     type: String,
     type: String,
     default: 'text'
     default: 'text'
   },
   },
+  // 文件类型
+  accept: {
+    type: String,
+    default: ''
+  },
   // 是否显示上传列表
   // 是否显示上传列表
   showFileList: {
   showFileList: {
     type: Boolean,
     type: Boolean,
@@ -78,7 +83,7 @@ const title = ref('点击上传')
 function beforeUpload(file) {
 function beforeUpload(file) {
   loading.value = true
   loading.value = true
   title.value = 'Loading'
   title.value = 'Loading'
-  if(!props.multiple) {
+  if (!props.multiple) {
     reactiveData.form.size = file.size
     reactiveData.form.size = file.size
     reactiveData.form.name = file.name
     reactiveData.form.name = file.name
   }
   }
@@ -92,7 +97,7 @@ function onSuccess(file) {
     title.value = '上传成功'
     title.value = '上传成功'
     if (props.multiple) {
     if (props.multiple) {
       file.data.map(i => reactiveData.form.push(i))
       file.data.map(i => reactiveData.form.push(i))
-    }else{
+    } else {
       reactiveData.form.file = file.data
       reactiveData.form.file = file.data
     }
     }
     proxy.$emit('upload', reactiveData.form)
     proxy.$emit('upload', reactiveData.form)

+ 29 - 0
src/router/index.js

@@ -208,6 +208,35 @@ export const dynamicRoutes = [
       },
       },
     ],
     ],
   },
   },
+  {
+    path: "/device",
+    component: Layout,
+    hidden: true,
+    permissions: ["device:deviceList:list"],
+    name: "deviceList",
+    children: [
+      {
+        path: "deviceList/detail",
+        component: () => import("@/views/device/list/detail"),
+        name: "deviceListDetail",
+        meta: { title: "设备详情", activeMenu: "/device/deviceList" },
+      },
+    ],
+  },
+  {
+    path: "/device",
+    component: Layout,
+    hidden: true,
+    permissions: ["device:version:list"],
+    children: [
+      {
+        path: "version/detail",
+        component: () => import("@/views/device/version/detail"),
+        name: "deviceVersionDetail",
+        meta: { title: "设备升级详情", activeMenu: "/device/version" },
+      },
+    ],
+  },
 ];
 ];
 
 
 const router = createRouter({
 const router = createRouter({

+ 140 - 23
src/views/device/class/index.vue

@@ -1,53 +1,170 @@
 <template>
 <template>
-  <div class='app-container'>
-    <el-form>
+  <div class="app-container">
+    <!-- 搜索 -->
+    <el-form inline label-width="100px" @submit.native.prevent>
       <el-form-item>
       <el-form-item>
-        <el-button type="primary" icon="Plus" plain @click="getDialog">新增</el-button>
+        <el-button type="primary" icon="Plus" plain @click="getChange()">新增</el-button>
       </el-form-item>
       </el-form-item>
     </el-form>
     </el-form>
-    <!-- 列表 -->
+
+    <!-- 表格 -->
     <el-table :data="data.tableData" v-loading="loading">
     <el-table :data="data.tableData" v-loading="loading">
-      <el-table-column type="selection" align="center"></el-table-column>
-      <el-table-column label="分类ID" align="center"></el-table-column>
-      <el-table-column label="分类名称" align="center"></el-table-column>
-      <el-table-column label="当前状态" align="center"></el-table-column>
-      <el-table-column label="创建时间" align="center"></el-table-column>
+      <el-table-column label="分类ID" prop="id" align="center" />
+      <el-table-column label="分类名称" prop="name" align="center" />
+      <el-table-column label="状态" align="center">
+        <template #default="scope">
+          <el-tag :type="scope.row.status === 1 ? '' : 'danger'">
+            {{ selectDictLabel(data.statusOptions, scope.row.status) }}
+          </el-tag>
+        </template>
+      </el-table-column>
       <el-table-column label="操作" align="center">
       <el-table-column label="操作" align="center">
         <template #default="scope">
         <template #default="scope">
-          <el-button type="primary" link @click="getDialog">编辑</el-button>
-          <el-button type="danger" link>删除</el-button>
+          <el-button type="primary" link @click="getChange(scope.row)">
+            编辑
+          </el-button>
+          <el-button type="danger" link @click="getDelete(scope.row.id)">
+            删除
+          </el-button>
         </template>
         </template>
       </el-table-column>
       </el-table-column>
     </el-table>
     </el-table>
-    <!-- 弹窗 -->
-    <el-dialog v-model="dialogVisible" :title="title" width="500">
-      <el-form>
-        <el-form-item label="分类名称:">
-          <el-input placeholder="请输入分类名称" />
+
+    <!-- 新增弹窗 -->
+    <el-dialog v-model="dialogVisible" :title="title" width="500px" :before-close="getCancel">
+      <el-form :model="data.form" :rules="data.rules" ref="form" label-width="100px">
+        <el-form-item label="分类名称:" prop="name">
+          <el-input v-model="data.form.name" placeholder="请输入分类名称" />
+        </el-form-item>
+        <el-form-item label="启用状态:" prop="status">
+          <el-radio-group v-model="data.form.status">
+            <el-radio :label="1">正常</el-radio>
+            <el-radio :label="2">停用</el-radio>
+          </el-radio-group>
         </el-form-item>
         </el-form-item>
       </el-form>
       </el-form>
       <template #footer>
       <template #footer>
-        <el-button>取消</el-button>
-        <el-button type="primary">确定</el-button>
+        <el-button @click="getCancel">取消</el-button>
+        <el-button type="primary" @click="getCreate">确定</el-button>
       </template>
       </template>
     </el-dialog>
     </el-dialog>
   </div>
   </div>
 </template>
 </template>
 
 
 <script setup>
 <script setup>
+import { list, create, edit, remove } from "@/api/device/class";
+
+const { proxy } = getCurrentInstance();
+
 // 遮罩层
 // 遮罩层
 const loading = ref(false)
 const loading = ref(false)
 
 
 const data = reactive({
 const data = reactive({
-  form: {},
-  tableData: [{}]
+  // 表单
+  form: {
+    name: '',
+    status: 1
+  },
+  // 表格数据
+  tableData: [],
+  // 表单验证
+  rules: {
+    name: [
+      {
+        required: true,
+        message: "请输入分类名称",
+        trigger: "blur",
+      },
+    ],
+    status: [
+      {
+        required: true,
+        message: "请选择状态",
+        trigger: "change",
+      },
+    ],
+  },
+  // 设备状态
+  statusOptions: [{
+    value: 1,
+    label: '正常'
+  }, {
+    value: 2,
+    label: '停用'
+  }]
 })
 })
 
 
 // 弹窗
 // 弹窗
 const dialogVisible = ref(false)
 const dialogVisible = ref(false)
-const title = ref("新增")
+// 弹窗标题
+const title = ref("")
+
+// 列表
+const getList = () => {
+  loading.value = true;
+  list().then((res) => {
+    if (res.code === 0) {
+      data.tableData = res.data;
+      loading.value = false;
+    }
+  });
+}
+getList()
+
+// 新增 / 编辑按钮
+const getChange = (row) => {
+  dialogVisible.value = true;
+  data.form = row ? row : {
+    name: '',
+    status: 1
+  };
+  title.value = row ? "编辑" : "新增";
+}
+
+// 取消
+const getCancel = () => {
+  proxy.$refs.form.clearValidate();
+  dialogVisible.value = false;
+}
+
+// 确定
+const getCreate = () => {
+  proxy.$refs.form.validate((valid) => {
+    if (valid) {
+      if (data.form.id) {
+        // 编辑
+        edit(data.form).then((res) => {
+          if (res.code === 0) {
+            proxy.$modal.msgSuccess("修改成功!");
+            dialogVisible.value = false;
+            getList();
+          }
+        });
+      } else {
+        // 新增
+        create(this.form).then((res) => {
+          if (res.code === 0) {
+            proxy.$modal.msgSuccess("创建成功!");
+            dialogVisible.value = false;
+            getList();
+          }
+        });
+      }
+    } else {
+      return false;
+    }
+  });
+}
 
 
-function getDialog() {
-  dialogVisible.value = true
+// 删除
+const getDelete = (id) => {
+  proxy.$modal.confirm("确定要删除?").then(() => {
+    remove(id).then((res) => {
+      if (res.code === 0) {
+        proxy.$modal.msgSuccess("删除成功!");
+        getList();
+      }
+    });
+  }).catch(() => { });
 }
 }
 </script>
 </script>

+ 165 - 0
src/views/device/function/index.vue

@@ -0,0 +1,165 @@
+<template>
+  <div class="app-container">
+    <!-- 搜索 -->
+    <el-form inline label-width="100px" @submit.native.prevent>
+      <el-form-item>
+        <el-button type="primary" icon="Plus" plain @click="getChange()">新增</el-button>
+      </el-form-item>
+    </el-form>
+    <!-- 表格 -->
+    <el-table :data="data.tableData" v-loading="loading">
+      <el-table-column label="权限ID" prop="id" align="center" />
+      <el-table-column label="功能名称" prop="name" align="center" />
+      <el-table-column label="功能分类" prop="type" align="center"></el-table-column>
+      <el-table-column label="状态" align="center">
+        <template #default="scope">
+          <dict-tag :options="sys_normal_disable" :value="scope.row.status" />
+        </template>
+      </el-table-column>
+      <el-table-column label="操作" align="center">
+        <template #default="scope">
+          <el-button type="primary" link @click="getChange(scope.row)">
+            编辑
+          </el-button>
+          <el-button type="danger" link @click="getDelete(scope.row)">
+            删除
+          </el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+    <!-- 新增弹窗 -->
+    <el-dialog v-model="dialogVisible" :title="title" width="500px" :before-close="getCancel">
+      <el-form :model="data.form" :rules="data.rules" ref="form" label-width="100px">
+        <el-form-item label="功能名称:" prop="name">
+          <el-input v-model="data.form.name" placeholder="请输入分类名称" />
+        </el-form-item>
+        <el-form-item label="功能分类:" prop="type">
+          <el-input-number v-model="data.form.type" />
+        </el-form-item>
+        <el-form-item label="启用状态:" prop="status">
+          <el-radio-group v-model="data.form.status">
+            <el-radio :label="0">正常</el-radio>
+            <el-radio :label="1">停用</el-radio>
+          </el-radio-group>
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <el-button @click="getCancel">取消</el-button>
+        <el-button type="primary" @click="getSubmit">确定</el-button>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup>
+import { list, create, edit } from "@/api/device/function";
+
+const { proxy } = getCurrentInstance();
+const { sys_normal_disable } = proxy.useDict("sys_normal_disable");
+
+// 遮罩层
+const loading = ref(false)
+
+const data = reactive({
+  // 表单
+  form: {
+    status: 0,
+  },
+  // 表格
+  tableData: [],
+  // 校验
+  rules: {
+    name: [{
+      required: true,
+      message: "请输入功能名称",
+      trigger: "blur"
+    }],
+    type: [{
+      required: true,
+      message: "请选择功能分类",
+      trigger: "change"
+    }],
+    status: [{
+      required: true,
+      message: "请选择状态",
+      trigger: "change"
+    }]
+  },
+})
+
+// 弹窗
+const dialogVisible = ref(false)
+// 弹窗标题
+const title = ref("")
+
+// 权限列表
+const getList = () => {
+  loading.value = true;
+  list({ isDelete: 0 }).then((res) => {
+    if (res.code === 0) {
+      data.tableData = res.data;
+      loading.value = false;
+    }
+  });
+}
+getList()
+
+
+// 新增 / 编辑
+const getChange = (row) => {
+  dialogVisible.value = true
+  data.form = row
+    ? row
+    : {
+      status: 0,
+    };
+  title.value = row ? "编辑" : "新增";
+}
+
+// 取消
+const getCancel = () => {
+  proxy.$refs.form.clearValidate();
+  dialogVisible.value = false;
+}
+
+// 确定
+const getSubmit = () => {
+  proxy.$refs.form.validate((valid) => {
+    if (valid) {
+      if (data.form.id) {
+        // 编辑
+        edit(data.form).then((res) => {
+          if (res.code === 0) {
+            proxy.$modal.msgSuccess("修改成功!")
+            dialogVisible.value = false;
+            getList();
+          }
+        });
+      } else {
+        // 新增
+        create(data.form).then((res) => {
+          if (res.code === 0) {
+            proxy.$modal.msgSuccess("新增成功!")
+            dialogVisible.value = false;
+            getList();
+          }
+        });
+      }
+    } else {
+      return false;
+    }
+  });
+}
+
+// 删除
+const getDelete = (row) => {
+  data.form = row;
+  data.form.isDelete = 1;
+  edit(data.form).then((res) => {
+    if (res.code === 0) {
+      proxy.$modal.msgSuccess("删除成功!")
+      getList();
+    }
+  })
+}
+</script>

+ 96 - 0
src/views/device/hooks/index.js

@@ -0,0 +1,96 @@
+import { list as classList } from "@/api/device/class";
+
+// 设备分类
+export function useDeviceClass() {
+  const deviceClassOptions = ref([]);
+  const getDevClass = () => {
+    classList().then((res) => {
+      if (res.code === 0) {
+        deviceClassOptions.value = res.data;
+      }
+    });
+  };
+
+  onMounted(() => getDevClass());
+  onUnmounted(() => getDevClass());
+
+  return { deviceClassOptions };
+}
+
+// 设备类型
+export function useDeviceType() {
+  const deviceTypeOptions = ref([
+    {
+      value: 0,
+      label: "音响",
+    },
+    {
+      value: 1,
+      label: "耳机",
+    },
+    {
+      value: 2,
+      label: "穿戴",
+    },
+  ]);
+
+  return { deviceTypeOptions };
+}
+
+// 热门设备
+export function useHot() {
+  const hotOptions = ref([
+    {
+      value: 0,
+      label: "否",
+    },
+    {
+      value: 1,
+      label: "是",
+    },
+  ]);
+
+  return { hotOptions };
+}
+
+// 功能权限
+import { list as functionList } from "@/api/device/function";
+export function useDeviceFun() {
+  const deviceFunOptions = ref([]);
+
+  const getDevFun = () => {
+    functionList({ isDelete: 0 }).then((res) => {
+      if (res.code === 0) {
+        deviceFunOptions.value = res.data;
+      }
+    });
+  };
+
+  onMounted(() => getDevFun());
+  onUnmounted(() => getDevFun());
+
+  return { deviceFunOptions };
+}
+
+// 设备模式
+export function useDeviceMode () {
+  const devModeOptions = [{
+    value: 1,
+    label: '蓝牙',
+    disabled: false
+  }, {
+    value: 2,
+    label: 'WIFI',
+    disabled: false
+  }, {
+    value: 3,
+    label: '传统蓝牙',
+    disabled: false
+  }, {
+    value: 4,
+    label: '4G',
+    disabled: false
+  }]
+
+  return { devModeOptions }
+}

+ 351 - 0
src/views/device/list/detail.vue

@@ -0,0 +1,351 @@
+<template>
+  <div class="app-container">
+    <el-form :model="data.form" :rules="data.rules" ref="form" label-width="auto" :disabled="disabled">
+      <el-form-item label="设备型号:" prop="clientType">
+        <el-input v-model="data.form.clientType" placeholder="请输入设备型号" />
+      </el-form-item>
+      <el-form-item label="设备名称:" prop="name">
+        <el-input v-model="data.form.name" placeholder="请输入设备名称" />
+      </el-form-item>
+      <el-form-item v-if="data.form.id" label="旧版蓝牙名称:">
+        <el-input v-model="data.form.bluetoothName" disabled />
+      </el-form-item>
+      <el-form-item label="蓝牙名称:" prop="bluetoothNames">
+        <el-tag v-for="(item, index) in data.form.bluetoothNames" :key="item" :closable="!disabled" size="large"
+          :disable-transition="false" @close="handleClose(index)" style="margin-right: 10px;">{{ item }}</el-tag>
+        <el-input v-if="inputVisible" v-model="bluetoothName" ref="saveTagInput" @input="handleInput"
+          @keyup.enter.native="handleInputConfirm" @blur="handleInputConfirm" style="width: 100px" />
+        <el-button v-else @click="showInput">+ 新增</el-button>
+      </el-form-item>
+      <el-form-item label="制造商:" prop="manufacturer">
+        <el-select v-model="data.form.manufacturer" placeholder="请选择设备制造商" clearable>
+          <el-option v-for="item in data.manuOptions" :key="item.value" :label="item.label" :value="item.value" />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="设备分类:" prop="categoryId">
+        <el-select v-model="data.form.categoryId" placeholder="请选择设备分类">
+          <el-option v-for="item in deviceClassOptions" :key="item.id" :label="item.name" :value="item.id" />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="设备类型:" prop="deviceType">
+        <el-select v-model="data.form.deviceType" placeholder="请选择设备类型">
+          <el-option v-for="item in deviceTypeOptions" :key="item.value" :value="item.value" :label="item.label" />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="是否热门:" prop="isHot">
+        <el-select v-model="data.form.isHot" placeholder="请选择是否热门">
+          <el-option v-for="item in hotOptions" :key="item.value" :label="item.label" :value="Number(item.value)" />
+        </el-select>
+      </el-form-item>
+      <el-row>
+        <el-col :span="12">
+          <el-form-item label="在线图片:" prop="img">
+            <CustomUpload listType="picture-card" :src="data.form.img" @upload="handleUploadImg($event, 'online')"
+              :disabled="disabled" />
+          </el-form-item>
+        </el-col>
+        <el-col :span="12">
+          <el-form-item label="离线图片:" prop="offlineImg" style="float:right">
+            <CustomUpload listType="picture-card" :src="data.form.offlineImg"
+              @upload="handleUploadImg($event, 'offline')" :disabled="disabled" />
+          </el-form-item>
+        </el-col>
+      </el-row>
+      <!-- 子表单 -->
+      <el-button v-if="disabled === false && data.form.typeList.length < 4" type="primary" icon="Plus" style="margin-bottom: 20px;"
+        @click="getAdd">新增</el-button>
+      <div class="sub-form" v-for="(item, index) in data.form.typeList" :key="item.id">
+        <el-form-item label="设备模式:" :prop="`typeList.${index}.type`"
+          :rules="{ required: true, message: '请选择设备模式', trigger: 'change' }">
+          <el-select v-model="item.type" placeholder="请选择设备模式" @change="getTypeChange(item)">
+            <el-option v-for="item in data.devModeOptions" :key="item.value" :label="item.label"
+              :value="Number(item.value)" :disabled="item.disabled" />
+          </el-select>
+        </el-form-item>
+        <el-form-item v-if="item.type !== 3" label="功能权限:" :prop="`typeList.${index}.functionList`"
+          :rules="{ type: 'array', required: true, message: '请选择功能权限', trigger: 'change' }">
+          <el-select v-model="item.functionList" placeholder="请选择功能权限" multiple>
+            <el-option v-for="item in deviceFunOptions" :key="item.id" :label="item.name" :value="item.id" />
+          </el-select>
+        </el-form-item>
+        <el-form-item v-if="item.type == 2" label="芯片方案:" :prop="`typeList.${index}.is5g`"
+          :rules="{ required: true, message: '请选择芯片方案', trigger: 'change' }">
+          <el-select v-model="item.is5g" placeholder="请选择芯片方案">
+            <el-option v-for="item in planOptions" :key="item.value" :value="item.value" :label="item.label" />
+          </el-select>
+        </el-form-item>
+        <el-form-item v-if="item.type == 2" label="连接方式:" :prop="`typeList.${index}.connectType`"
+          :rules="{ required: true, message: '请选择连接方式', trigger: 'change' }">
+          <el-select v-model="item.connectType" placeholder="请选择连接方式">
+            <el-option v-for="item in connectTypeOptions" :key="item.value" :label="item.label"
+              :value="Number(item.value)" />
+          </el-select>
+        </el-form-item>
+        <el-row>
+          <el-col :span="12">
+            <el-form-item :label="item.type === 1 || item.type === 3 ? '连接引导图:' : '开机引导图:'"
+              :prop="`typeList.${index}.icon1`" :rules="{ required: true, message: '请上传引导图片', trigger: 'change' }">
+              <CustomUpload listType="picture-card" :src="item.icon1" @upload="handleUploadIcon($event, index, 'icon1')"
+                :disabled="disabled" />
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="配对键引导图:" :prop="`typeList.${index}.icon2`" style="float:right"
+              :rules="{ required: true, message: '请上传配对键引导图', trigger: 'change' }">
+              <CustomUpload listType="picture-card" :src="item.icon2" @upload="handleUploadIcon($event, index, 'icon2')"
+                :disabled="disabled" />
+            </el-form-item>
+          </el-col>
+        </el-row>
+        <el-form-item v-if="item.type === 1 || item.type === 3" label="引导页内容:"
+          :prop="`typeList.${index}.guidePageContent`"
+          :rules="{ required: true, message: '请输入引导页内容', trigger: 'blur' }">
+          <Editor v-model="item.guidePageContent" :min-height="250" :readOnly="disabled" />
+        </el-form-item>
+        <!-- 删除按钮 -->
+        <el-link class="el-icon-close" icon="Close" v-if="data.form.typeList.length > 1" :underline="false"
+          @click="getDelete(item.type, index)" />
+      </div>
+    </el-form>
+    <div>
+      <el-button @click="getCancel">取消</el-button>
+      <el-button v-if="disabled === false" type="primary" @click="getSubmit">提交</el-button>
+    </div>
+  </div>
+</template>
+
+<script setup>
+import { create, detail, edit } from "@/api/device/list";
+import { useDeviceClass, useDeviceType, useHot, useDeviceFun } from '../hooks/index'
+const { deviceClassOptions } = useDeviceClass()
+const { deviceTypeOptions } = useDeviceType()
+const { hotOptions } = useHot()
+const { deviceFunOptions } = useDeviceFun()
+
+const { proxy } = getCurrentInstance();
+
+const data = reactive({
+  // 表单
+  form: {
+    typeList: [{
+      functionList: [],
+    }],
+    bluetoothNames: []
+  },
+  // 表单验证
+  rules: {
+    clientType: [{ required: true, message: "请输入设备型号", trigger: "blur" }],
+    name: [{ required: true, message: "请输入设备名称", trigger: "blur" }],
+    bluetoothNames: [{ type: 'array', required: true, message: '请输入蓝牙名称', trigger: 'blur' }],
+    isHot: [{ required: true, message: "是否热门", trigger: "change" }],
+    categoryId: [{ required: true, message: "请选择设备分类", trigger: "change" }],
+    deviceType: [{ required: true, message: "请选择设备类型", trigger: 'change' }],
+    img: [{ required: true, message: "请上传在线图片", trigger: "change" }],
+    offlineImg: [{ required: true, message: "请上传离线图片", trigger: "change" }],
+  },
+  // 设备连接
+  connectTypeOptions: [{
+    value: 2,
+    label: 'UPnP'
+  }, {
+    value: 3,
+    label: 'MQTT'
+  }],
+  // 制造商
+  manuOptions: [{
+    value: 'ShanJing',
+    label: 'ShanJing'
+  }, {
+    value: 'JieLi',
+    label: 'JieLi'
+  }, {
+    value: 'LingXin',
+    label: 'LingXin'
+  }, {
+    value: 'QiXinWei',
+    label: 'QiXinWei'
+  }],
+  // 芯片方案
+  planOptions: [{
+    value: 1,
+    label: '兼容5GHZ频段芯片方案'
+  }, {
+    value: 0,
+    label: '不兼容5GHZ频段芯片方案'
+  }],
+  // 设备模式
+  devModeOptions: [{
+    value: 1,
+    label: '蓝牙',
+    disabled: false
+  }, {
+    value: 2,
+    label: 'WIFI',
+    disabled: false
+  }, {
+    value: 3,
+    label: '传统蓝牙',
+    disabled: false
+  }, {
+    value: 4,
+    label: '4G',
+    disabled: false
+  }]
+})
+
+// 显示蓝牙输入框
+const inputVisible = ref(false)
+// 蓝牙名称
+const bluetoothName = ref('')
+// 只读
+const disabled = ref(Boolean(proxy.$route.query.boolean))
+
+// 详情
+const getList = () => {
+  if (proxy.$route.query.id) {
+    detail({
+      id: proxy.$route.query.id,
+    }).then(res => {
+      if (res.code === 0) {
+        data.form = res.data;
+        data.form.typeList.map((i) => {
+          data.devModeOptions[i.type - 1].disabled = true;
+        })
+      }
+    })
+  }
+}
+getList()
+
+// 删除蓝牙名称
+const handleClose = (index) => {
+  data.form.bluetoothNames.splice(index, 1)
+}
+
+// 显示输入框
+const showInput = () => {
+  inputVisible.value = true
+  nextTick(() => {
+    proxy.$refs.saveTagInput.$refs.input.focus()
+  })
+}
+
+// 禁止输入逗号
+const handleInput = (e) => {
+  bluetoothName.value = e.replace(/[,,]/g, "")
+}
+
+// 保存蓝牙名称
+const handleInputConfirm = () => {
+  let inputValue = bluetoothName.value
+  if (inputValue) {
+    data.form.bluetoothNames.push(inputValue)
+  }
+  inputVisible.value = false
+  bluetoothName.value = ''
+}
+
+// 上传设备图片
+const handleUploadImg = (e, key) => {
+  key === "online"
+    ? (data.form.img = e.file)
+    : (data.form.offlineImg = e.file);
+}
+
+// 上传设备图标
+const handleUploadIcon = (e, index, obj) => {
+  data.form.typeList[index][obj] = e.file;
+}
+
+// 选中一个设备类型就从数组中去掉
+const getTypeChange = (i) => {
+  for (let key in data.devModeOptions) {
+    data.devModeOptions[key].disabled = false
+  }
+  // 选中的设备模式禁止再选
+  data.form.typeList.map(item => {
+    if (item.type) {
+      let index = data.devModeOptions.findIndex(j => j.value == item.type)
+      data.devModeOptions[index].disabled = true
+    }
+  })
+  // 除了设备模式都为空
+  Object.keys(i).map(e => {
+    if (!['type', 'icon1', 'icon2'].includes(e)) {
+      i[e] = e === 'functionList' ? [] : null
+    }
+  })
+}
+
+// 新增子表单
+const getAdd = () => {
+  data.form.typeList.push({
+    type: null,
+    functionList: [],
+    connectType: null,
+    icon1: '',
+    icon2: '',
+    guidePageContent: ''
+  });
+}
+
+// 删除子表单
+const getDelete = (type, index) => {
+  data.form.typeList.splice(index, 1);
+  if (type) {
+    data.devModeOptions.find(i => i.value == type).disabled = false
+  }
+}
+
+// 取消
+const getCancel = () => {
+  proxy.$tab.closeOpenPage("/device/deviceList");
+}
+
+// 提交
+const getSubmit = () => {
+  proxy.$refs.form.validate((valid) => {
+    if (valid) {
+      if (proxy.$route.query.id) {
+        // 编辑
+        edit(data.form).then((res) => {
+          if (res.code === 0) {
+            proxy.$modal.msgSuccess("修改成功!");
+            getCancel();
+          }
+        });
+      } else {
+        // 新建
+        create(data.form).then((res) => {
+          if (res.code === 0) {
+            proxy.$modal.msgSuccess("提交成功!");
+            getCancel();
+          }
+        });
+      }
+    } else {
+      return false;
+    }
+  });
+}
+</script>
+
+<style lang="scss" scoped>
+.el-form {
+  width: 580px;
+}
+
+.sub-form {
+  position: relative;
+  border: 1px solid #e8e8e8;
+  padding: 25px 25px 0 0;
+  margin-bottom: 20px;
+
+  .el-icon-close {
+    position: absolute;
+    top: 5px;
+    right: 5px;
+    color: #c0c4cc;
+  }
+}
+</style>

+ 152 - 32
src/views/device/list/index.vue

@@ -1,71 +1,191 @@
 <template>
 <template>
-  <div class='app-container'>
-    <!-- 搜索 -->
+  <div class="app-container">
+    <!-- 搜索表单 -->
     <el-form inline>
     <el-form inline>
       <el-form-item label="设备名称:">
       <el-form-item label="设备名称:">
-        <el-input placeholder="请输入设备名称" />
+        <el-input v-model="data.form.name" placeholder="请输入设备名称" clearable />
       </el-form-item>
       </el-form-item>
       <el-form-item label="设备型号:">
       <el-form-item label="设备型号:">
-        <el-input placeholder="请输入设备型号" />
-      </el-form-item>
-      <el-form-item label="设备ID:">
-        <el-input placeholder="请输入设备ID" />
+        <el-select v-model="data.form.clientTypeId" placeholder="请选择设备型号" filterable clearable>
+          <el-option v-for="item in deviceOptions" :key="item.clientTypeId" :label="item.name"
+            :value="item.clientTypeId" />
+        </el-select>
       </el-form-item>
       </el-form-item>
       <el-form-item label="设备分类:">
       <el-form-item label="设备分类:">
-        <el-select placeholder="请选择设备分类">
-          <!-- <el-option /> -->
+        <el-select v-model="data.form.categoryId" placeholder="请选择设备分类" clearable>
+          <el-option v-for="item in deviceClassOptions" :key="item.id" :value="item.id" :label="item.name" />
         </el-select>
         </el-select>
       </el-form-item>
       </el-form-item>
-      <el-form-item label="所属门店:">
-        <el-select placeholder="请选择所属门店">
-          <!-- <el-option /> -->
+      <el-form-item label="当前状态:">
+        <el-select v-model="data.form.status" placeholder="请选择当前状态" clearable>
+          <el-option v-for="item in device_change_status" :key="item.value" :value="item.value" :label="item.label" />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="是否热门:">
+        <el-select v-model="data.form.isHot" placeholder="请选择是否热门" clearable>
+          <el-option v-for="item in hotOptions" :key="item.value" :value="item.value" :label="item.label" />
         </el-select>
         </el-select>
       </el-form-item>
       </el-form-item>
       <el-form-item>
       <el-form-item>
-        <el-button type="primary" icon="Search">搜索</el-button>
-        <el-button icon="Refresh">重置</el-button>
-        <el-button type="success" plain>批量上架</el-button>
-        <el-button type="success" plain>批量下架</el-button>
-        <el-button type="warning" plain icon="Download">导出</el-button>
+        <el-button type="primary" icon="Search" @click="getSearch">搜索</el-button>
+        <el-button icon="Refresh" @click="getRefresh">重置</el-button>
+        <el-button type="primary" icon="Plus" plain @click="getRouter()">
+          新增
+        </el-button>
       </el-form-item>
       </el-form-item>
     </el-form>
     </el-form>
-    <!-- 列表 -->
-    <el-table :data="data.tableData">
-      <el-table-column type="selection" align="center" />
-      <el-table-column label="设备ID" align="center"></el-table-column>
-      <el-table-column label="设备型号" align="center"></el-table-column>
-      <el-table-column label="设备名称" align="center"></el-table-column>
-      <el-table-column label="所属门店" align="center"></el-table-column>
-      <el-table-column label="设备分类" align="center"></el-table-column>
-      <el-table-column label="创建时间" align="center"></el-table-column>
+    <!-- 表格 -->
+    <el-table :data="data.tableData" v-loading="loading">
+      <el-table-column label="设备ID" prop="id" align="center" />
+      <el-table-column label="设备型号" prop="clientType" align="center" />
+      <el-table-column label="设备名称" prop="name" align="center" />
+      <el-table-column label="蓝牙名称" prop="bluetoothName" align="center" />
+      <el-table-column label="设备图片" prop="img" align="center" width="100">
+        <template #default="scope">
+          <el-image :src="scope.row.img" />
+        </template>
+      </el-table-column>
+      <el-table-column label="设备分类" prop="categoryName" align="center" />
+      <el-table-column label="设备类型" prop="deviceType" align="center">
+        <template #default="scope">
+          {{ deviceTypeOptions.find(i => i.value === scope.row.deviceType).label }}
+        </template>
+      </el-table-column>
+      <el-table-column label="是否热门" prop="isHot" align="center">
+        <template #default="scope">
+          {{ hotOptions.find(i => i.value === scope.row.isHot).label }}
+        </template>
+      </el-table-column>
+      <el-table-column label="创建时间" align="center" >
+        <template #default="scope">
+          {{ parseTime(scope.row.createTime, '{y}-{m}-{d} {h}:{i}:{s}') }}
+        </template>
+      </el-table-column>
       <el-table-column label="操作" align="center">
       <el-table-column label="操作" align="center">
         <template #default="scope">
         <template #default="scope">
-          <el-button type="primary" link>下架</el-button>
-          <el-button type="primary" link>上架</el-button>
+          <el-button type="primary" link @click="getRouter(scope.row.id, true)">查看</el-button>
+          <span v-if="scope.row.status === 2" style="margin-left: 10px;" >
+            <el-button type="primary" link @click="getRouter(scope.row.id)">
+              编辑
+            </el-button>
+            <el-button type="primary" link @click="getChange(scope.row.id, 1, '上架')">
+              上架
+            </el-button>
+            <el-button type="danger" link @click="getDelete(scope.row)">
+              删除
+            </el-button>
+          </span>
+          <el-button v-else type="primary" link @click="getChange(scope.row.id, 2, '下架')">
+            下架
+          </el-button>
         </template>
         </template>
       </el-table-column>
       </el-table-column>
     </el-table>
     </el-table>
-    <pagination v-show="total > 0" :total="total" v-model:page="data.form.pageNum" v-model:limit="data.form.pageSize"
+    <pagination v-show="total > 0" :total="total" :page.sync="data.form.pageNum" :limit.sync="data.form.pageSize"
       @pagination="getList" />
       @pagination="getList" />
   </div>
   </div>
 </template>
 </template>
 
 
 <script setup>
 <script setup>
+import { list, remove, change } from '@/api/device/list'
+import { useDeviceList } from '@/hooks/index'
+import { useDeviceClass, useDeviceType, useHot } from '../hooks/index'
+const { deviceOptions } = useDeviceList()
+const { deviceClassOptions } = useDeviceClass()
+const { deviceTypeOptions } = useDeviceType()
+const { hotOptions } = useHot()
+
+const { proxy } = getCurrentInstance()
+const { device_change_status } = proxy.useDict("device_change_status")
+
+// 遮罩层
+const loading = ref(false)
+
 const data = reactive({
 const data = reactive({
+  // 搜索表单
   form: {
   form: {
     pageNum: 1,
     pageNum: 1,
     pageSize: 10
     pageSize: 10
   },
   },
-  tableData: [{}]
+  // 表格
+  tableData: []
 })
 })
 
 
 // 总数据
 // 总数据
 const total = ref(0)
 const total = ref(0)
 
 
 // 列表
 // 列表
-function getList() {
+const getList = () => {
+  loading.value = true
+  list(data.form).then(res => {
+    if (res.code === 0) {
+      data.tableData = res.data.records
+      total.value = res.data.total
+      loading.value = false
+    }
+  })
+}
+getList()
 
 
+// 搜索
+const getSearch = () => {
+  data.form.pageNum = 1
+  getList()
 }
 }
 
 
-getList()
+// 重置
+const getRefresh = () => {
+  data.form = {
+    name: '',
+    pageNum: 1,
+    pageSize: 10
+  }
+  getList()
+}
+
+// 上下架
+const getChange = (id, status, title) => {
+  change({ ids: id, type: status }).then(res => {
+    if (res.code === 0) {
+      proxy.$modal.msgSuccess(`${title}成功!`)
+      getList()
+    }
+  })
+}
+
+// 新增 / 编辑 / 查看
+const getRouter = (id, boolean) => {
+  proxy.$router.push({
+    path: `/device/deviceList/detail`,
+    query: {
+      id: id,
+      boolean: boolean
+    }
+  })
+}
+
+// 多选
+const handleSelectionChange = (e) => {
+  if (e.length > 0) {
+    let arr = []
+    e.filter(i => {
+      arr.push(i.id)
+      data.changeForm.ids = arr.join(',')
+    })
+  } else {
+    data.changeForm.ids = ''
+  }
+}
+
+// 删除
+const getDelete = (id) => {
+  proxy.$modal.confirm('确定要删除?').then(() => {
+    remove(id).then(res => {
+      if (res.code === 0) {
+        proxy.$modal.msgSuccess('删除成功!')
+        getList()
+      }
+    })
+  }).catch(() => { })
+}
 </script>
 </script>

+ 216 - 0
src/views/device/version/detail.vue

@@ -0,0 +1,216 @@
+<template>
+  <div class="app-container" v-loading="loading">
+    <el-form :model="data.form" :rules="data.rules" ref="form" label-width="100px">
+      <el-form-item prop="deviceTypeId" label="设备型号:">
+        <el-select v-model="data.form.deviceTypeId" placeholder="请选择设备型号" filterable>
+          <el-option v-for="item in deviceOptions" :key="item.clientTypeId" :label="item.name"
+            :value="item.clientTypeId" />
+        </el-select>
+      </el-form-item>
+      <el-form-item prop="type" label="设备模式:">
+        <el-select v-model="data.form.type" placeholder="请选择设备模式">
+          <el-option v-for="item in devModeOptions" :key="item.value" :label="item.label" :value="item.value" />
+        </el-select>
+      </el-form-item>
+      <el-form-item prop="version" label="版本号:">
+        <el-input v-model="data.form.version" placeholder="请输入版本号" />
+      </el-form-item>
+      <el-form-item prop="fileType" label="上传类型:">
+        <el-select v-model="data.form.fileType" placeholder="请选择上传类型">
+          <el-option v-for="item in data.updateOptions" :key="item.value" :value="item.value" :label="item.label" />
+        </el-select>
+      </el-form-item>
+      <el-form-item prop="url" label="下载路径:">
+        <div style="display: flex; width: 100%">
+          <el-input v-model="data.form.url" placeholder="请输入 或 上传下载路径" clearable style="width: 100%; margin-right: 10px" />
+          <CustomUpload :url="url" :accept="accept" @upload="getUpload" />
+        </div>
+      </el-form-item>
+      <el-form-item prop="title" label="升级标题:">
+        <el-input v-model="data.form.title" placeholder="请输入升级标题" />
+      </el-form-item>
+      <el-form-item prop="content" label="升级描述:">
+        <el-input v-model="data.form.content" type="textarea" rows="4" placeholder="请输入升级描述" />
+      </el-form-item>
+      <el-form-item prop="isEnforcement" label="设备升级:">
+        <el-select v-model="data.form.deviceIsEnforcement" placeholder="是否强制设备升级">
+          <el-option v-for="item in data.statusOptions" :key="item.value" :label="item.label" :value="item.value" />
+        </el-select>
+      </el-form-item>
+      <el-form-item prop="isEnforcement" label="APP升级:">
+        <el-select v-model="data.form.isEnforcement" placeholder="是否强制APP升级">
+          <el-option v-for="item in data.statusOptions" :key="item.value" :label="item.label" :value="item.value" />
+        </el-select>
+      </el-form-item>
+      <el-form-item prop="isPush" label="同步推送:">
+        <el-select v-model="data.form.isPush" placeholder="是否同步推送">
+          <el-option v-for="item in data.statusOptions" :key="item.value" :label="item.label" :value="item.value" />
+        </el-select>
+      </el-form-item>
+      <el-form-item prop="userType" label="升级对象:">
+        <el-select v-model="data.form.userType" placeholder="请选择升级对象">
+          <el-option v-for="item in data.pushOptions" :key="item.value" :label="item.label" :value="item.value" />
+        </el-select>
+      </el-form-item>
+      <el-form-item :prop="user" v-if="data.form.userType === 3" label="指定用户:">
+        <el-input v-model="user" type="textarea" rows="4" placeholder="请输入指定用户手机号,一行一个" />
+      </el-form-item>
+    </el-form>
+    <div class="form-btn">
+      <el-button @click="getCancel">取消</el-button>
+      <el-button type="primary" @click="getSubmit">提交</el-button>
+    </div>
+  </div>
+</template>
+
+<script setup>
+import { getDetailInfo, updateEdit, getVersionAdd } from "@/api/device/version";
+import { useDeviceList } from '@/hooks/index'
+import { useDeviceMode } from '../hooks/index'
+const { deviceOptions } = useDeviceList()
+const { devModeOptions } = useDeviceMode()
+const baseUrl = ref(import.meta.env.VITE_APP_BASE_API)
+
+const { proxy } = getCurrentInstance()
+
+// 遮罩层
+const loading = ref(false)
+
+const data = reactive({
+  form: {
+    id: proxy.$route.query.id,
+    fileType: 1,
+    url: "",
+  },
+  // 设备是否
+  statusOptions: [{
+    value: 1,
+    label: '是'
+  }, {
+    value: 2,
+    label: '否'
+  }],
+  // 升级对象
+  pushOptions: [{
+    value: 1,
+    label: '全部'
+  }, {
+    value: 3,
+    label: '指定'
+  }],
+  // 上传类型
+  updateOptions: [{
+    value: 1,
+    label: '文件'
+  }, {
+    value: 2,
+    label: '文件夹'
+  }],
+  // 表单验证
+  rules: {
+    deviceTypeId: [{
+      required: true, message: "请选择设备型号", trigger: "change"
+    }],
+    type: [{
+      required: true, message: "请选择设备类型", trigger: "change"
+    }],
+    version: [{
+      required: true, message: "请输入版本号", trigger: "blur"
+    }],
+    fileType: [{
+      required: true, message: '请选择上传类型', trigger: 'change'
+    }],
+    url: [{
+      required: true, message: "请输入 或 上传下载路径", trigger: "change"
+    }],
+    title: [{
+      required: true, message: "请输入升级标题", trigger: "blur"
+    }],
+    content: [{
+      required: true, message: "请输入升级描述", trigger: "blur"
+    }],
+    isEnforcement: [{
+      required: true, message: "是否强制升级", trigger: "change"
+    }],
+    isPush: [{
+      required: true, message: "是否同步推送", trigger: "change"
+    }],
+    userType: [{
+      required: true, message: "请选择推送类型", trigger: "change"
+    }]
+  },
+})
+
+// 指定用户表单中间参数
+const user = ref("")
+const url = ref('text')
+// 文件类型
+const accept = ref('.bin')
+
+const getDetail = () => {
+  if (data.form.id) {
+    getDetailInfo({
+      id: data.form.id,
+    }).then((res) => {
+      if (res.code === 0) {
+        data.form = res.data;
+        user.value = res.data.user.join("\n");
+      }
+    })
+  }
+}
+getDetail()
+
+// 上传文件
+const getUpload = (e) => {
+  data.form.url = e.file
+}
+
+// 提交
+const getSubmit = () => {
+  proxy.$refs.form.validate((valid) => {
+    if (valid) {
+      data.form.userType === 3
+        ? (data.form.user = user.value.split("\n"))
+        : delete data.form.user;
+      if (data.form.id) {
+        // 编辑
+        updateEdit(data.form).then((res) => {
+          if (res.code === 0) {
+            proxy.$modal.msgSuccess("修改成功!")
+            getCancel()
+          }
+        });
+      } else {
+        // 新增
+        delete data.form.id;
+        getVersionAdd(this.form).then((res) => {
+          if (res.code === 0) {
+            this.$message.success("新增成功!");
+            getCancel()
+          }
+        });
+      }
+    } else {
+      return false;
+    }
+  });
+}
+
+// 取消
+const getCancel = () => {
+  proxy.$tab.closeOpenPage("/device/version");
+}
+
+watch(() => data.form.fileType, (val) => {
+  url.value = val === 1 ? '' : `${baseUrl.value}/system/file/file/uploadFolderZip`
+  accept.value = val === 1 ? '.bin' : '.zip'
+})
+
+</script>
+
+<style lang="scss" scoped>
+.el-form {
+  width: 500px;
+}
+</style>

+ 136 - 0
src/views/device/version/index.vue

@@ -0,0 +1,136 @@
+<template>
+  <div class="app-container">
+    <!-- 搜索 -->
+    <el-form :model="data.form" inline>
+      <el-form-item label="升级标题:">
+        <el-input v-model="data.form.title" placeholder="请输入升级标题" clearable />
+      </el-form-item>
+      <el-form-item label="设备型号:">
+        <el-select v-model="data.form.clientTypeId" placeholder="请选择设备型号" filterable clearable>
+          <el-option v-for="item in deviceOptions" :key="item.clientTypeId" :label="item.name" :value="item.clientTypeId" />
+        </el-select>
+      </el-form-item>
+      <el-form-item>
+        <el-button type="primary" icon="Search" @click="getSearch">搜索</el-button>
+        <el-button icon="Refresh" @click="getRefresh">重置</el-button>
+        <el-button type="primary" icon="Plus" plain @click="getChange()">新增</el-button>
+      </el-form-item>
+    </el-form>
+    <!-- 列表 -->
+    <el-table :data="data.tableData" v-loading="loading">
+      <el-table-column prop="id" label="型号ID" align="center" />
+      <el-table-column prop="title" label="升级标题" align="center" show-overflow-tooltip />
+      <el-table-column prop="deviceTypeName" label="设备名称" align="center" show-overflow-tooltip />
+      <el-table-column prop="type" label="设备模式" align="center" :formatter="typeFormatter" />
+      <el-table-column prop="version" label="版本号" align="center" />
+      <el-table-column label="下载路径" align="center" show-overflow-tooltip>
+        <template #default="scope">
+          <el-link :href="scope.row.url" :underline="false">{{ scope.row.url }}</el-link>
+        </template>
+      </el-table-column>
+      <el-table-column prop="userTypeValue" label="升级对象" align="center" />
+      <el-table-column prop="enforcementValue" label="APP强制升级" align="center" />
+      <el-table-column prop="deviceIsEnforcement" label="设备强制升级" align="center" />
+      <el-table-column prop="updateTimeStr" label="更新时间" align="center" />
+      <el-table-column label="操作" align="center">
+        <template #default="scope">
+          <el-button type="primary" link @click="getChange(scope.row.id)">
+            编辑
+          </el-button>
+          <el-button type="danger" link @click="getDelete(scope.row.id)">
+            删除
+          </el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+    <!-- 分页 -->
+    <pagination v-show="total > 0" :total="total" :page.sync="data.form.pageIndex" :limit.sync="data.form.pageSize"
+      @pagination="getList" />
+  </div>
+</template>
+
+<script setup>
+import { getPageList, getVersionDelete } from '@/api/device/version'
+import { useDeviceList } from '@/hooks/index'
+import { useDeviceMode } from '../hooks/index'
+const { deviceOptions } = useDeviceList()
+const { devModeOptions } = useDeviceMode()
+
+const { proxy } = getCurrentInstance()
+
+// 遮罩层
+const loading = ref(false)
+
+const data = reactive({
+  // 表单
+  form: {
+    pageIndex: 1,
+    pageSize: 10
+  },
+  // 列表
+  tableData: []
+})
+
+// 总数
+const total = ref(0)
+
+// 列表
+const getList = () => {
+  loading.value = true
+  getPageList(data.form).then(res => {
+    if (res.code === 0) {
+      data.tableData = []
+      res.data.records.forEach(i => {
+        data.tableData.push({
+          ...i,
+          deviceIsEnforcement: i.deviceIsEnforcement ? i.deviceIsEnforcement : '否',
+          enforcementValue: i.enforcementValue ? i.enforcementValue : '否'
+        })
+      })
+      total.value = res.data.total
+      loading.value = false
+    }
+  })
+}
+getList()
+
+// 搜索
+const getSearch = () => {
+  data.form.pageIndex = 1
+  getList()
+}
+
+// 重置
+const getRefresh = () => {
+  data.form = {
+    pageIndex: 1,
+    pageSize: 10
+  }
+  getList()
+}
+
+// 新增
+const getChange = (id) => {
+  proxy.$router.push({
+    path: `/device/version/detail`,
+    query: {
+      id: id
+    }
+  })
+}
+
+// 删除
+const getDelete = (id) => {
+  getVersionDelete({ id: id }).then(res => {
+    if (res.code === 0) {
+      proxy.$modal.msgSuccess("删除成功!")
+      getList()
+    }
+  })
+}
+
+// 字典翻译
+const typeFormatter = (row) => {
+  return proxy.selectDictLabel(devModeOptions, row.type)
+}
+</script>