浏览代码

设备属性模版页面优化

Zimo 3 天之前
父节点
当前提交
2f0aa3d12d
共有 2 个文件被更改,包括 498 次插入227 次删除
  1. 220 0
      src/components/DeviceCategoryTree/index.vue
  2. 278 227
      src/views/pms/devicetemplate/index.vue

+ 220 - 0
src/components/DeviceCategoryTree/index.vue

@@ -0,0 +1,220 @@
+<script lang="ts" setup>
+import { defaultProps, handleTree } from '@/utils/tree'
+import * as DeviceCategoryApi from '@/api/pms/productclassify'
+import { CaretLeft, CaretRight, Search } from '@element-plus/icons-vue'
+import { ElTree } from 'element-plus'
+
+defineOptions({ name: 'DeviceCategoryTree' })
+
+interface DeviceCategoryNode extends Tree {
+  parentId?: number
+  code?: string
+  status?: number
+}
+
+const props = defineProps({
+  modelValue: {
+    type: Number as PropType<number | undefined>,
+    default: undefined
+  },
+  title: {
+    type: String,
+    default: '设备分类'
+  },
+  rootId: {
+    type: Number,
+    default: 0
+  },
+  rootName: {
+    type: String,
+    default: '顶级设备分类'
+  },
+  initSelect: {
+    type: Boolean,
+    default: false
+  },
+  showTitle: {
+    type: Boolean,
+    default: true
+  }
+})
+
+const emits = defineEmits<{
+  (event: 'update:modelValue', value?: number): void
+  (event: 'node-click', value: DeviceCategoryNode): void
+}>()
+
+const isCollapsed = ref(false)
+const deviceCategoryName = ref('')
+const deviceCategoryList = ref<DeviceCategoryNode[]>([])
+const treeRef = ref<InstanceType<typeof ElTree>>()
+const expandedKeys = ref<number[]>([])
+
+const sortTreeBySort = (treeNodes: DeviceCategoryNode[]) => {
+  if (!Array.isArray(treeNodes)) return treeNodes
+
+  return [...treeNodes]
+    .sort((a, b) => (a.sort ?? 999999) - (b.sort ?? 999999))
+    .map((node) => ({
+      ...node,
+      children: node.children ? sortTreeBySort(node.children as DeviceCategoryNode[]) : undefined
+    }))
+}
+
+const setCurrentNode = (id?: number) => {
+  if (id === undefined || id === null || !treeRef.value) return
+
+  treeRef.value.setCurrentKey(id)
+  if (!expandedKeys.value.includes(id)) {
+    expandedKeys.value.push(id)
+  }
+}
+
+const loadTree = async () => {
+  try {
+    const res = await DeviceCategoryApi.IotProductClassifyApi.getSimpleProductClassifyList()
+    const rootNode: DeviceCategoryNode = {
+      id: props.rootId,
+      name: props.rootName,
+      children: sortTreeBySort(handleTree(res) as DeviceCategoryNode[])
+    }
+
+    deviceCategoryList.value = [rootNode]
+    expandedKeys.value = [props.rootId]
+
+    await nextTick()
+
+    const targetId = props.modelValue ?? (props.initSelect ? props.rootId : undefined)
+    if (targetId !== undefined) {
+      setCurrentNode(targetId)
+      if (props.initSelect && props.modelValue === undefined) {
+        emits('update:modelValue', targetId)
+      }
+    }
+  } catch (error) {
+    console.error(error)
+  }
+}
+
+const handleNodeClick = (data: DeviceCategoryNode) => {
+  emits('update:modelValue', data.id)
+  emits('node-click', data)
+}
+
+const filterNode = (val: string, data: DeviceCategoryNode) => !val || data.name.includes(val)
+
+watch(deviceCategoryName, (val) => treeRef.value?.filter(val))
+watch(
+  () => props.modelValue,
+  (val) => setCurrentNode(val),
+  { immediate: true }
+)
+
+onMounted(loadTree)
+</script>
+
+<template>
+  <div
+    class="device-category-aside relative bg-white dark:bg-[#1d1e1f] shadow rounded-lg row-span-2 transition-all duration-300 ease-in-out overflow-visible"
+    :class="[isCollapsed ? 'is-collapsed' : 'p-4']"
+  >
+    <div v-show="!isCollapsed" class="h-full flex flex-col gap-4 overflow-hidden w-full">
+      <h1 v-if="showTitle" class="text-lg font-medium truncate shrink-0">{{ title }}</h1>
+
+      <div class="shrink-0">
+        <el-input
+          v-model="deviceCategoryName"
+          placeholder="请输入设备分类名称"
+          clearable
+          :prefix-icon="Search"
+        />
+      </div>
+
+      <div class="flex-1 relative overflow-hidden">
+        <el-auto-resizer class="absolute">
+          <template #default="{ height }">
+            <el-scrollbar :style="{ height: `${height}px` }">
+              <el-tree
+                ref="treeRef"
+                :data="deviceCategoryList"
+                :props="defaultProps"
+                :expand-on-click-node="false"
+                :filter-node-method="filterNode"
+                :default-expanded-keys="expandedKeys"
+                node-key="id"
+                highlight-current
+                @node-click="handleNodeClick"
+              />
+            </el-scrollbar>
+          </template>
+        </el-auto-resizer>
+      </div>
+    </div>
+
+    <div class="collapse-handle" @click="isCollapsed = !isCollapsed">
+      <el-icon size="12">
+        <CaretLeft v-if="!isCollapsed" />
+        <CaretRight v-else />
+      </el-icon>
+    </div>
+  </div>
+</template>
+
+<style scoped>
+.device-category-aside {
+  width: 14vw;
+  height: calc(
+    100vh - 20px - var(--top-tool-height) - var(--tags-view-height) - var(--app-footer-height)
+  );
+  min-width: 200px;
+  box-sizing: border-box;
+  flex-shrink: 0;
+}
+
+.device-category-aside.is-collapsed {
+  width: 0 !important;
+  min-width: 0 !important;
+  padding: 0 !important;
+  margin-right: -16px;
+  overflow: visible !important;
+  pointer-events: none;
+  box-shadow: none;
+}
+
+.collapse-handle {
+  position: absolute;
+  top: 50%;
+  right: -14px;
+  z-index: 200;
+  display: flex;
+  width: 14px;
+  height: 60px;
+  color: var(--el-text-color-secondary);
+  pointer-events: auto;
+  cursor: pointer;
+  background-color: var(--el-bg-color);
+  border: 1px solid var(--el-border-color-light);
+  border-left: none;
+  border-radius: 0 12px 12px 0;
+  transform: translateY(-50%);
+  box-shadow: 2px 0 6px rgb(0 0 0 / 5%);
+  align-items: center;
+  justify-content: center;
+}
+
+.is-collapsed .collapse-handle {
+  right: -8px;
+  border-left: 1px solid var(--el-border-color-light);
+}
+
+.collapse-handle:hover {
+  color: var(--el-color-primary);
+  background-color: var(--el-fill-color-light);
+}
+
+.truncate {
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+}
+</style>

+ 278 - 227
src/views/pms/devicetemplate/index.vue

@@ -1,202 +1,40 @@
-<template>
-  <doc-alert title="用户体系" url="https://doc.iocoder.cn/user-center/" />
-  <doc-alert title="三方登陆" url="https://doc.iocoder.cn/social-user/" />
-  <doc-alert title="Excel 导入导出" url="https://doc.iocoder.cn/excel-import-and-export/" />
-
-  <el-row :gutter="20">
-    <!-- 左侧 设备分类 树 -->
-    <el-col :span="4" :xs="24">
-      <ContentWrap class="h-1/1">
-        <DeviceCategoryTree @node-click="handleDeviceCategoryTreeNodeClick" />
-      </ContentWrap>
-    </el-col>
-    <el-col :span="20" :xs="24">
-      <!-- 搜索 -->
-      <ContentWrap>
-        <el-form
-          class="-mb-15px"
-          :model="queryParams"
-          ref="queryFormRef"
-          :inline="true"
-          label-width="68px"
-        >
-          <el-form-item label="模板名称" prop="name">
-            <el-input
-              v-model="queryParams.name"
-              placeholder="请输入模板名称"
-              clearable
-              @keyup.enter="handleQuery"
-              class="!w-240px"
-            />
-          </el-form-item>
-          <el-form-item label="模板编码" prop="code">
-            <el-input
-              v-model="queryParams.code"
-              placeholder="请输入模板编码"
-              clearable
-              @keyup.enter="handleQuery"
-              class="!w-240px"
-            />
-          </el-form-item>
-          <el-form-item label="状态" prop="status">
-            <el-select
-              v-model="queryParams.status"
-              placeholder="属性模板状态"
-              clearable
-              class="!w-240px"
-            >
-              <el-option
-                v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
-                :key="dict.value"
-                :label="dict.label"
-                :value="dict.value"
-              />
-            </el-select>
-          </el-form-item>
-          <el-form-item label="创建时间" prop="createTime">
-            <el-date-picker
-              v-model="queryParams.createTime"
-              value-format="YYYY-MM-DD HH:mm:ss"
-              type="datetimerange"
-              start-placeholder="开始日期"
-              end-placeholder="结束日期"
-              class="!w-240px"
-            />
-          </el-form-item>
-          <el-form-item>
-            <el-button @click="handleQuery"><Icon icon="ep:search" />搜索</el-button>
-            <el-button @click="resetQuery"><Icon icon="ep:refresh" />重置</el-button>
-            <el-button
-              type="primary"
-              plain
-              @click="openForm('create')"
-              v-hasPermi="['rq:iot-device-template:create']"
-            >
-              <Icon icon="ep:plus" /> 新增
-            </el-button>
-            <!--
-            <el-button @click="handleAllQuery"><Icon icon="ep:search" />查询所有</el-button> -->
-          </el-form-item>
-        </el-form>
-      </ContentWrap>
-      <ContentWrap>
-        <el-table v-loading="loading" :data="list">
-          <el-table-column label="序号" width="60" align="center">
-            <template #default="scope">
-              {{ scope.$index + 1 }}
-            </template>
-          </el-table-column>
-          <el-table-column
-            label="模板编码"
-            align="center"
-            prop="code"
-            :show-overflow-tooltip="true"
-          />
-          <el-table-column
-            label="模板名称"
-            align="center"
-            prop="name"
-            :show-overflow-tooltip="true"
-          />
-          <el-table-column
-            label="设备分类名称"
-            align="center"
-            key="deviceCategoryName"
-            prop="deviceCategoryName"
-            :show-overflow-tooltip="true"
-          />
-          <el-table-column label="状态" key="status">
-            <template #default="scope">
-              <el-switch
-                v-model="scope.row.status"
-                :active-value="0"
-                :inactive-value="1"
-                @change="handleStatusChange(scope.row)"
-                :disabled="!checkPermi(['rq:iot-device-template:update'])"
-              />
-            </template>
-          </el-table-column>
-          <el-table-column
-            label="创建时间"
-            align="center"
-            prop="createTime"
-            :formatter="dateFormatter"
-            width="180"
-          />
-          <el-table-column label="操作" align="center" width="160">
-            <template #default="scope">
-              <div class="flex items-center justify-center">
-                <el-button
-                  type="primary"
-                  link
-                  @click="openDetail(scope.row)"
-                >
-                  <Icon icon="ep:edit" />查看
-                </el-button>
-                <el-button
-                  type="primary"
-                  link
-                  @click="openForm('update', scope.row.id)"
-                  v-hasPermi="['rq:iot-device-template:update']"
-                >
-                  <Icon icon="ep:edit" />修改
-                </el-button>
-                <el-dropdown
-                  @command="(command) => handleCommand(command, scope.row)"
-                  v-hasPermi="[
-                    'rq:iot-device-template:delete'
-                  ]"
-                >
-                  <el-button type="primary" link><Icon icon="ep:d-arrow-right" /> 更多</el-button>
-                  <template #dropdown>
-                    <el-dropdown-menu>
-                      <el-dropdown-item
-                        command="handleDelete"
-                        v-if="checkPermi(['rq:iot-device-template:delete'])"
-                      >
-                        <Icon icon="ep:delete" />删除
-                      </el-dropdown-item>
-                    </el-dropdown-menu>
-                  </template>
-                </el-dropdown>
-              </div>
-            </template>
-          </el-table-column>
-        </el-table>
-        <Pagination
-          :total="total"
-          v-model:page="queryParams.pageNo"
-          v-model:limit="queryParams.pageSize"
-          @pagination="getList"
-        />
-      </ContentWrap>
-    </el-col>
-  </el-row>
-
-  <!-- 添加或修改 属性模板 对话框 -->
-  <TemplateForm ref="formRef" :category_id="selectedId" @success="getList" />
-</template>
 <script lang="ts" setup>
+import { useTableComponents } from '@/components/ZmTable/useTableComponents'
 import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
 import { checkPermi } from '@/utils/permission'
-import { dateFormatter } from '@/utils/formatTime'
+import { dateFormatter, rangeShortcuts } from '@/utils/formatTime'
 import { CommonStatusEnum } from '@/utils/constants'
 import * as DeviceTemplateApi from '@/api/pms/devicetemplate'
+import DeviceCategoryTree from '@/components/DeviceCategoryTree/index.vue'
 import TemplateForm from './TemplateForm.vue'
-import DeviceCategoryTree from './DeviceCategoryTree.vue'
-import { useTreeStore } from '@/store/modules/attrTemplateTreeStore';
+import { useTreeStore } from '@/store/modules/attrTemplateTreeStore'
 
 defineOptions({ name: 'DeviceAttrsTemplate' })
 
-const treeStore = useTreeStore();
-const message = useMessage() // 消息弹窗
-const { t } = useI18n() // 国际化
+type DeviceTemplateRow = Omit<DeviceTemplateApi.DeviceAttrTemplateVO, 'status'> & {
+  status?: number
+  deviceCategoryName?: string
+  createTime?: string
+}
+
+interface QueryParams extends PageParam {
+  name?: string
+  code?: string
+  status?: number
+  deviceCategoryId?: number
+  createTime?: string[]
+}
 
+const treeStore = useTreeStore()
+const message = useMessage()
+const { t } = useI18n()
 const { push } = useRouter()
-const loading = ref(true) // 列表的加载中
-const total = ref(0) // 列表的总页数
-const list = ref([]) // 列表的数
-const queryParams = reactive({
+const { ZmTable, ZmTableColumn } = useTableComponents<DeviceTemplateRow>()
+
+const loading = ref(true)
+const total = ref(0)
+const list = ref<DeviceTemplateRow[]>([])
+const queryParams = reactive<QueryParams>({
   pageNo: 1,
   pageSize: 10,
   name: undefined,
@@ -205,110 +43,323 @@ const queryParams = reactive({
   deviceCategoryId: undefined,
   createTime: []
 })
-const queryFormRef = ref() // 搜索的表单
+const queryFormRef = ref()
+
+const selectedId = computed(() => treeStore.selectedId)
 
-// 从 Store 中获取左侧 设备分类树 选中的 节点ID
-const selectedId = computed(() => treeStore.selectedId);
+const normalizeStatus = (status: unknown) => {
+  const value = Number(status)
+  if (value === CommonStatusEnum.ENABLE || value === CommonStatusEnum.DISABLE) {
+    return value
+  }
+  return undefined
+}
+
+const isValidStatus = (status: unknown) => normalizeStatus(status) !== undefined
 
-/** 查询 设备属性模板 列表 */
 const getList = async () => {
   loading.value = true
   try {
     const data = await DeviceTemplateApi.getDeviceTemplatePage(queryParams)
-    list.value = data.list
+    list.value = data.list.map((item: DeviceTemplateRow) => ({
+      ...item,
+      status: normalizeStatus(item.status)
+    }))
     total.value = data.total
   } finally {
     loading.value = false
   }
 }
 
-/** 搜索按钮操作 */
 const handleQuery = () => {
   queryParams.pageNo = 1
   getList()
 }
 
-/** 搜索按钮操作 */
-const handleAllQuery = () => {
-  queryParams.pageNo = 1
-  queryParams.deviceCategoryId = ''
-  getList()
-}
-
-/** 重置按钮操作 */
 const resetQuery = () => {
   queryFormRef.value?.resetFields()
   handleQuery()
 }
 
-/** 处理 设备分类树 被点击 */
-const handleDeviceCategoryTreeNodeClick = async (row) => {
+const handleSizeChange = (val: number) => {
+  queryParams.pageSize = val
+  handleQuery()
+}
+
+const handleCurrentChange = (val: number) => {
+  queryParams.pageNo = val
+  getList()
+}
+
+const handleDeviceCategoryTreeNodeClick = async (row: Tree) => {
+  treeStore.setSelectedId(row.id)
   queryParams.deviceCategoryId = row.id
+  queryParams.pageNo = 1
   await getList()
 }
 
-/** 添加/修改操作 */
 const formRef = ref()
 const openForm = (type: string, id?: number) => {
   formRef.value.open(type, id)
 }
 
-/** 打开详情 */
-const openDetail = (row) => {
+const openDetail = (row: DeviceTemplateRow) => {
   push({
     name: 'DeviceAttrTemplateModel',
     params: {
       id: row.deviceCategoryId,
-      // 添加额外参数
       templateName: row.name,
       categoryName: row.deviceCategoryName
     }
   })
 }
 
-/** 修改 设备属性模板 状态 */
-const handleStatusChange = async (row: DeviceTemplateApi.DeviceAttrTemplateVO) => {
+const getStatusText = (status: unknown) => {
+  const value = normalizeStatus(status)
+  if (value === CommonStatusEnum.ENABLE) return '启用'
+  if (value === CommonStatusEnum.DISABLE) return '停用'
+  return '未知'
+}
+
+const handleStatusChange = async (
+  row: DeviceTemplateRow,
+  nextStatus: string | number | boolean
+) => {
+  const status = normalizeStatus(nextStatus)
+  const oldStatus = normalizeStatus(row.status)
+  if (
+    !row.id ||
+    !row.name ||
+    status === undefined ||
+    oldStatus === undefined ||
+    status === oldStatus
+  ) {
+    return
+  }
+
   try {
-    // 修改状态的二次确认
-    const text = row.status === CommonStatusEnum.ENABLE ? '启用' : '停用'
-    await message.confirm('确认要"' + text + '""' + row.name + '"属性模板吗?')
-    // 发起修改状态
-    await DeviceTemplateApi.updateDeviceTemplateStatus(row.id, row.status)
-    // 刷新列表
+    await message.confirm(`确认要${getStatusText(status)}"${row.name}"属性模板吗?`)
+    row.status = status
+    await DeviceTemplateApi.updateDeviceTemplateStatus(row.id, status)
     await getList()
   } catch {
-    // 取消后,进行恢复按钮
-    row.status =
-      row.status === CommonStatusEnum.ENABLE ? CommonStatusEnum.DISABLE : CommonStatusEnum.ENABLE
+    row.status = oldStatus
   }
 }
 
-/** 操作分发 */
-const handleCommand = (command: string, row: DeviceTemplateApi.DeviceAttrTemplateVO) => {
+const handleCommand = (command: string, row: DeviceTemplateRow) => {
   switch (command) {
     case 'handleDelete':
-      handleDelete(row.id)
+      handleDelete(row.id!)
       break
     default:
       break
   }
 }
 
-/** 删除按钮操作 */
 const handleDelete = async (id: number) => {
   try {
-    // 删除的二次确认
     await message.delConfirm()
-    // 发起删除
     await DeviceTemplateApi.deleteDeviceTemplate(id)
     message.success(t('common.delSuccess'))
-    // 刷新列表
     await getList()
   } catch {}
 }
 
-/** 初始化 */
 onMounted(() => {
   getList()
 })
 </script>
+
+<template>
+  <div
+    class="grid grid-cols-[auto_1fr] grid-rows-[auto_1fr] gap-4 h-[calc(100vh-20px-var(--top-tool-height)-var(--tags-view-height)-var(--app-footer-height))]"
+  >
+    <DeviceCategoryTree
+      v-model="queryParams.deviceCategoryId"
+      :show-title="false"
+      @node-click="handleDeviceCategoryTreeNodeClick"
+    />
+
+    <el-form
+      ref="queryFormRef"
+      :model="queryParams"
+      size="default"
+      label-width="68px"
+      class="bg-white dark:bg-[#1d1e1f] rounded-lg shadow px-8 py-3 gap-6 flex items-center justify-between flex-wrap min-w-0"
+    >
+      <div class="flex items-center gap-6 flex-wrap">
+        <el-form-item label="模板名称" prop="name">
+          <el-input
+            v-model="queryParams.name"
+            placeholder="请输入模板名称"
+            clearable
+            class="!w-200px"
+            @keyup.enter="handleQuery"
+          />
+        </el-form-item>
+        <el-form-item label="模板编码" prop="code">
+          <el-input
+            v-model="queryParams.code"
+            placeholder="请输入模板编码"
+            clearable
+            class="!w-200px"
+            @keyup.enter="handleQuery"
+          />
+        </el-form-item>
+        <el-form-item label="状态" prop="status">
+          <el-select
+            v-model="queryParams.status"
+            placeholder="请选择状态"
+            clearable
+            class="!w-180px"
+          >
+            <el-option
+              v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
+              :key="dict.value"
+              :label="dict.label"
+              :value="dict.value"
+            />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="创建时间" prop="createTime">
+          <el-date-picker
+            v-model="queryParams.createTime"
+            value-format="YYYY-MM-DD HH:mm:ss"
+            type="datetimerange"
+            start-placeholder="开始日期"
+            end-placeholder="结束日期"
+            :shortcuts="rangeShortcuts"
+            :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
+            class="!w-320px"
+          />
+        </el-form-item>
+      </div>
+
+      <el-form-item>
+        <el-button type="primary" @click="handleQuery">
+          <Icon icon="ep:search" class="mr-5px" />搜索
+        </el-button>
+        <el-button @click="resetQuery"> <Icon icon="ep:refresh" class="mr-5px" />重置 </el-button>
+        <el-button
+          type="primary"
+          plain
+          @click="openForm('create')"
+          v-hasPermi="['rq:iot-device-template:create']"
+        >
+          <Icon icon="ep:plus" class="mr-5px" />新增
+        </el-button>
+      </el-form-item>
+    </el-form>
+
+    <div class="bg-white dark:bg-[#1d1e1f] shadow rounded-lg flex flex-col p-4 min-w-0">
+      <div class="flex-1 relative min-h-0">
+        <el-auto-resizer class="absolute">
+          <template #default="{ width, height }">
+            <ZmTable
+              :data="list"
+              :loading="loading"
+              :width="width"
+              :height="height"
+              :max-height="height"
+              show-border
+            >
+              <ZmTableColumn
+                type="index"
+                label="序号"
+                :width="60"
+                fixed="left"
+                hide-in-column-settings
+              />
+              <ZmTableColumn prop="code" label="模板编码" fixed="left" />
+              <ZmTableColumn prop="name" label="模板名称" fixed="left" />
+              <ZmTableColumn prop="deviceCategoryName" label="设备分类名称" />
+              <ZmTableColumn
+                prop="status"
+                label="状态"
+                :real-value="(row) => getStatusText(row.status)"
+                :width="90"
+              >
+                <template #default="{ row }">
+                  <el-switch
+                    v-if="isValidStatus(row.status)"
+                    :model-value="row.status"
+                    :active-value="0"
+                    :inactive-value="1"
+                    :disabled="!checkPermi(['rq:iot-device-template:update'])"
+                    @change="(status) => handleStatusChange(row, status)"
+                  />
+                  <el-tag v-else type="info">未知</el-tag>
+                </template>
+              </ZmTableColumn>
+              <ZmTableColumn
+                prop="createTime"
+                label="创建时间"
+                :formatter="dateFormatter"
+                width="180"
+              />
+              <ZmTableColumn label="操作" width="220" fixed="right" action>
+                <template #default="{ row }">
+                  <div class="flex items-center justify-center gap-2">
+                    <el-button type="primary" link @click="openDetail(row)">
+                      <Icon icon="ep:view" class="mr-3px" />查看
+                    </el-button>
+                    <el-button
+                      type="primary"
+                      link
+                      @click="openForm('update', row.id)"
+                      v-hasPermi="['rq:iot-device-template:update']"
+                    >
+                      <Icon icon="ep:edit" class="mr-3px" />修改
+                    </el-button>
+                    <el-dropdown
+                      @command="(command) => handleCommand(command, row)"
+                      v-hasPermi="['rq:iot-device-template:delete']"
+                    >
+                      <el-button type="primary" link>
+                        <Icon icon="ep:more-filled" class="mr-3px" />更多
+                      </el-button>
+                      <template #dropdown>
+                        <el-dropdown-menu>
+                          <el-dropdown-item
+                            command="handleDelete"
+                            v-if="checkPermi(['rq:iot-device-template:delete'])"
+                          >
+                            <Icon icon="ep:delete" class="mr-5px" />删除
+                          </el-dropdown-item>
+                        </el-dropdown-menu>
+                      </template>
+                    </el-dropdown>
+                  </div>
+                </template>
+              </ZmTableColumn>
+            </ZmTable>
+          </template>
+        </el-auto-resizer>
+      </div>
+
+      <div class="h-8 mt-2 flex items-center justify-end">
+        <el-pagination
+          v-show="total > 0"
+          size="default"
+          :current-page="queryParams.pageNo"
+          :page-size="queryParams.pageSize"
+          :background="true"
+          :page-sizes="[10, 20, 30, 50, 100]"
+          :total="total"
+          layout="total, sizes, prev, pager, next, jumper"
+          @size-change="handleSizeChange"
+          @current-change="handleCurrentChange"
+        />
+      </div>
+    </div>
+  </div>
+
+  <TemplateForm ref="formRef" :category_id="selectedId" @success="getList" />
+</template>
+
+<style scoped>
+:deep(.el-form-item) {
+  margin-bottom: 0;
+}
+</style>