liujie
2026-08-05 c9935678a74d53d90b13f4da898fa84ec82f77b6
ruoyi-ui/src/views/visitor-admin/device/index.vue
@@ -1,5 +1,5 @@
<template>
  <main class="visitor-page">
  <main class="visitor-page visitor-device-page">
    <section class="visitor-card">
      <header class="visitor-header">
        <h1>{{ pageTitle }}管理</h1>
@@ -19,7 +19,12 @@
            default-expand-all
            highlight-current
            @node-click="handleAreaClick"
          />
          >
            <span slot-scope="{ node, data }" class="area-tree-node">
              <i :class="areaNodeIconClass(data)" />
              <span class="area-tree-label">{{ node.label }}</span>
            </span>
          </el-tree>
        </aside>
        <section class="device-panel">
@@ -44,23 +49,20 @@
          </el-form>
          <div v-if="!isMiddlePlatformReadonly && hasDevicePerm('add')" class="visitor-toolbar">
            <el-button type="primary" @click="handleAdd">新增设备</el-button>
            <el-button type="primary" icon="el-icon-plus" @click="handleAdd">新增设备</el-button>
          </div>
          <el-table
            v-loading="loading"
            class="visitor-table"
            :data="deviceList"
            @selection-change="handleSelectionChange"
          >
            <el-table-column type="selection" width="48" align="center" />
            <el-table-column label="序号" width="64" align="center">
              <template slot-scope="scope">{{ scope.$index + 1 }}</template>
            </el-table-column>
            <el-table-column label="设备名称" prop="deviceName" min-width="120" show-overflow-tooltip />
            <el-table-column label="品牌" prop="brand" min-width="100" show-overflow-tooltip />
            <el-table-column label="设备ID" prop="deviceId" min-width="150" show-overflow-tooltip />
            <el-table-column v-if="deviceType === 'machine'" label="百度SDK指纹" prop="fingerprint" min-width="160" show-overflow-tooltip />
            <el-table-column label="所属区域" prop="areaName" min-width="130" show-overflow-tooltip />
            <el-table-column label="启用状态" align="center" width="100">
              <template slot-scope="scope">
@@ -78,10 +80,16 @@
                <el-button v-if="hasDevicePerm('edit')" type="text" @click="handleUpdate(scope.row)">编辑</el-button>
                <el-dropdown v-if="hasAnyDevicePerm(['status', 'logout', 'remove'])" trigger="click" @command="command => handleMoreCommand(command, scope.row)">
                  <el-button type="text">更多<i class="el-icon-arrow-down el-icon--right" /></el-button>
                  <el-dropdown-menu slot="dropdown">
                    <el-dropdown-item v-if="hasDevicePerm('status')" command="status">{{ scope.row.enabled ? '禁用' : '启用' }}</el-dropdown-item>
                    <el-dropdown-item v-if="hasDevicePerm('logout')" command="logout">注销</el-dropdown-item>
                    <el-dropdown-item v-if="hasDevicePerm('remove')" command="delete" class="danger-item">删除</el-dropdown-item>
                  <el-dropdown-menu slot="dropdown" class="visitor-device-more-menu">
                    <el-dropdown-item v-if="hasDevicePerm('logout')" command="logout">
                      <i class="el-icon-refresh-left" />注销
                    </el-dropdown-item>
                    <el-dropdown-item v-if="hasDevicePerm('status')" command="status">
                      <i :class="scope.row.enabled ? 'el-icon-circle-close' : 'el-icon-view'" />{{ scope.row.enabled ? '禁用' : '启用' }}
                    </el-dropdown-item>
                    <el-dropdown-item v-if="hasDevicePerm('remove')" command="delete" class="danger-item">
                      <i class="el-icon-delete" />删除
                    </el-dropdown-item>
                  </el-dropdown-menu>
                </el-dropdown>
              </template>
@@ -94,16 +102,16 @@
      </div>
    </section>
    <el-dialog :title="title" :visible.sync="open" width="654px" append-to-body>
      <el-form ref="form" :model="form" :rules="rules" label-width="92px">
    <el-dialog :title="title" :visible.sync="open" width="654px" append-to-body custom-class="visitor-device-dialog">
      <el-form ref="form" :model="form" :rules="rules" label-position="top">
        <el-form-item label="区域" prop="areaId">
          <el-cascader
            v-model="form.areaId"
            :options="areaTree"
            :options="modalAreaOptions"
            :props="areaCascaderProps"
            clearable
            filterable
            placeholder="下拉菜单"
            placeholder="请选择"
          />
        </el-form-item>
        <el-form-item label="设备名称" prop="deviceName">
@@ -119,7 +127,7 @@
          <el-input v-model="form.remark" placeholder="请输入" />
        </el-form-item>
        <el-form-item label="启用状态" prop="enabled">
          <el-switch v-model="form.enabled" active-text="启用" inactive-text="禁用" />
          <el-switch v-model="form.enabled" active-color="#13ce66" inactive-color="#dcdfe6" />
        </el-form-item>
      </el-form>
      <div slot="footer" class="dialog-footer">
@@ -190,6 +198,10 @@
    // 目的:返回当前设备类型的页面标题前缀。
    pageTitle() {
      return this.deviceType === 'door' ? '门禁' : '访客机'
    },
    // 目的:弹窗区域选择只保留一级和二级,避免二级继续展开空面板。
    modalAreaOptions() {
      return this.normalizeModalAreaOptions(this.areaTree)
    }
  },
  watch: {
@@ -224,6 +236,21 @@
        this.areaTree = []
      })
    },
    // 目的:按弹窗交互要求移除二级以下区域和叶子节点空 children。
    normalizeModalAreaOptions(list, level = 1) {
      return (list || []).map(item => {
        const option = {
          ...item
        }
        const children = Array.isArray(item.children) ? item.children : []
        if (level < 2 && children.length) {
          option.children = this.normalizeModalAreaOptions(children, level + 1)
        } else {
          delete option.children
        }
        return option
      })
    },
    // 目的:按当前设备类型和查询条件加载分页列表。
    getList() {
      this.loading = true
@@ -247,6 +274,11 @@
    // 目的:根据数据库在线状态枚举返回对应的标签样式。
    onlineClass(value) {
      return onlineStatusClass(value)
    },
    // 目的:根据区域是否有子级展示目录或文档图标,匹配区域树层级语义。
    areaNodeIconClass(data) {
      const hasChildren = Array.isArray(data.children) && data.children.length > 0
      return hasChildren ? 'el-icon-folder area-tree-node-icon is-folder' : 'el-icon-document area-tree-node-icon is-file'
    },
    // 目的:按区域名称关键字过滤区域树节点。
    filterAreaNode(value, data) {
@@ -375,3 +407,42 @@
  }
}
</script>
<style lang="scss">
.visitor-device-dialog {
  .el-dialog__body {
    padding: 18px 24px 24px;
  }
  .el-form {
    padding: 0 8px;
  }
  .el-form-item {
    margin-bottom: 18px;
  }
  .el-form-item__label {
    padding: 0 0 8px;
    color: #303133;
    font-weight: 600;
    line-height: 20px;
    text-align: left;
  }
  .el-form-item__content {
    line-height: 1;
  }
  .el-cascader,
  .el-input,
  .el-select {
    width: 100%;
  }
  .el-switch {
    display: inline-flex;
    align-items: center;
  }
}
</style>