前后端分离实现五级行政区划树形菜单及设备查询管理

springboot +vue 前后端分离实现五级行政区划树形菜单及设备管理

一、项目概述

本文基于下图中的"设备查询系统"功能,实现一个完整的五级行政区划(省/市/区县/镇/村)树形菜单 ,点击树节点右侧展示该区域下的设备列表。项目采用Spring Boot + JPA 作为后端,Vue + Element UI作为前端,提供完整的API接口和前端交互。如果我要查询某个地区的设备情况,如下图:

1.1 功能需求

  • 左侧树形菜单:展示省→市→区县→镇→村五级行政区划,支持懒加载或一次性加载(本文采用懒加载,性能更优)。

  • 右侧设备列表:点击树节点,表格展示该区域下的设备信息(机器码、机器名称、供应商、位置、创建时间等)。

  • 分页功能:设备列表支持分页查询。

  • 搜索功能:支持按设备名称、机器码等条件筛选(可扩展)。

1.2 技术栈

层次 技术选型
后端框架 Spring Boot 2.x
ORM Spring Data JPA + Hibernate
数据库 MySQL 8.0/sqlite
前端框架 Vue 2.x / 3.x + Element UI
HTTP客户端 Axios
构建工具 Maven / npm

二、数据库设计

2.1 行政区划表(region)

设计一张自关联的行政区划表,支持无限层级(五级),也可按前文五张表设计。为简化,这里采用单表递归结构。

行政区的数据固定我们可以存在sqlite里面,这样获取数据非常快,当然也可以放在缓存中

复制代码
CREATE TABLE `area` (`code` VARCHAR(255) PRIMARY KEY, `name` VARCHAR(255), `cityCode` VARCHAR(255) REFERENCES `city` (`code`) ON DELETE SET NULL ON UPDATE CASCADE, `provinceCode` VARCHAR(255) REFERENCES `province` (`code`) ON DELETE SET NULL ON UPDATE CASCADE);

CREATE TABLE `city` (`code` VARCHAR(255) PRIMARY KEY, `name` VARCHAR(255), `provinceCode` VARCHAR(255) REFERENCES `province` (`code`) ON DELETE SET NULL ON UPDATE CASCADE);

CREATE TABLE `province` (`code` VARCHAR(255) PRIMARY KEY, `name` VARCHAR(255));

CREATE TABLE `street` (`code` VARCHAR(255) PRIMARY KEY, `name` VARCHAR(255), `areaCode` VARCHAR(255) REFERENCES `area` (`code`) ON DELETE SET NULL ON UPDATE CASCADE, `provinceCode` VARCHAR(255) REFERENCES `province` (`code`) ON DELETE SET NULL ON UPDATE CASCADE, `cityCode` VARCHAR(255) REFERENCES `city` (`code`) ON DELETE SET NULL ON UPDATE CASCADE);

CREATE TABLE `village` (`code` VARCHAR(255) PRIMARY KEY, `name` VARCHAR(255), `streetCode` VARCHAR(255) REFERENCES `street` (`code`) ON DELETE SET NULL ON UPDATE CASCADE, `provinceCode` VARCHAR(255) REFERENCES `province` (`code`) ON DELETE SET NULL ON UPDATE CASCADE, `cityCode` VARCHAR(255) REFERENCES `city` (`code`) ON DELETE SET NULL ON UPDATE CASCADE, `areaCode` VARCHAR(255) REFERENCES `area` (`code`) ON DELETE SET NULL ON UPDATE CASCADE);

实际项目中也可按省、市、区、镇、村分五张表,但单表更易维护,且JPA通过自关联查询方便。

2.2 设备表(device)

sql

复制代码
CREATE TABLE `equipment` (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
  `machine_code` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '机器码',
  `machine_name` varchar(128) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '机器名称',
  `supplier` varchar(128) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '供应商',
  `supplier_code` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '供应商编码',
  `user_name` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '用户名',
  `location` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '省/市/区/县/镇',
  `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `updated_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  PRIMARY KEY (`id`) USING BTREE,
  UNIQUE KEY `machine_code` (`machine_code`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=11 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ROW_FORMAT=DYNAMIC COMMENT='设备表';

三、后端 Spring Boot + JPA 实现

3.1 项目依赖(pom.xml 核心片段)

xml

复制代码
<dependency>
    <groupId>org.xerial</groupId>
    <artifactId>sqlite-jdbc</artifactId>
    <version>3.45.1.0</version>
</dependency>

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

<dependency>
    <groupId>com.mysql</groupId>
    <artifactId>mysql-connector-j</artifactId>
    <version>8.0.33</version>
</dependency>

<dependency>
    <groupId>com.github.gwenn</groupId>
    <artifactId>sqlite-dialect</artifactId>
    <version>0.1.2</version>
</dependency>

yaml

复制代码
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/device_db?useSSL=false&serverTimezone=Asia/Shanghai
    username: root
    password: 123456
  jpa:
    hibernate:
      ddl-auto: update
    show-sql: true
    properties:
      hibernate:
        dialect: org.hibernate.dialect.MySQL8Dialect

3.3 实体类

Area.java

java

复制代码
@Entity
@Table(name = "area")
public class Area {
    @Id
    @Column(name = "code")
    private String code;

    private String name;

    @Column(name = "cityCode")
    private String cityCode;

    public Area() {}

    public Area(String code, String name, String cityCode) {
        this.code = code;
        this.name = name;
        this.cityCode = cityCode;
    }

    public String getCode() { return code; }
    public void setCode(String code) { this.code = code; }
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
    public String getCityCode() { return cityCode; }
    public void setCityCode(String cityCode) { this.cityCode = cityCode; }
}
复制代码
@Entity
@Table(name = "city")
public class City {
    @Id
    @Column(name = "code")
    private String code;

    private String name;

    @Column(name = "provinceCode")
    private String provinceCode;

    public City() {}

    public City(String code, String name, String provinceCode) {
        this.code = code;
        this.name = name;
        this.provinceCode = provinceCode;
    }

    public String getCode() { return code; }
    public void setCode(String code) { this.code = code; }
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
    public String getProvinceCode() { return provinceCode; }
    public void setProvinceCode(String provinceCode) { this.provinceCode = provinceCode; }
}
复制代码
@Entity
@Table(name = "province")
public class Province {
    @Id
    @Column(name = "code")
    private String code;

    private String name;

    public Province() {}

    public Province(String code, String name) {
        this.code = code;
        this.name = name;
    }

    public String getCode() { return code; }
    public void setCode(String code) { this.code = code; }
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
}
复制代码
@Entity
@Table(name = "street")
public class Street {
    @Id
    @Column(name = "code")
    private String code;

    private String name;

    @Column(name = "areaCode")
    private String areaCode;

    public Street() {}

    public Street(String code, String name, String areaCode) {
        this.code = code;
        this.name = name;
        this.areaCode = areaCode;
    }

    public String getCode() { return code; }
    public void setCode(String code) { this.code = code; }
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
    public String getAreaCode() { return areaCode; }
    public void setAreaCode(String areaCode) { this.areaCode = areaCode; }
}
复制代码
@Entity
@Table(name = "village")
public class Village {
    @Id
    @Column(name = "code")
    private String code;

    private String name;

    @Column(name = "streetCode")
    private String streetCode;

    public Village() {}

    public Village(String code, String name, String streetCode) {
        this.code = code;
        this.name = name;
        this.streetCode = streetCode;
    }

    public String getCode() { return code; }
    public void setCode(String code) { this.code = code; }
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
    public String getStreetCode() { return streetCode; }
    public void setStreetCode(String streetCode) { this.streetCode = streetCode; }
}
Machine.java

java

复制代码
@Entity
@Table(name = "equipment")
public class Machine {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;

    @Column(name = "machine_code")
    private String machineCode;

    @Column(name = "machine_name")
    private String machineName;

    private String supplier;

    @Column(name = "supplier_code")
    private String supplierCode;

    @Column(name = "user_name")
    private String userName;

    private String location;

    @Column(name = "created_at")
    private Timestamp createdAt;

    @Column(name = "updated_at")
    private Timestamp updatedAt;

    public Machine() {}

    public Integer getId() { return id; }
    public void setId(Integer id) { this.id = id; }
    public String getMachineCode() { return machineCode; }
    public void setMachineCode(String machineCode) { this.machineCode = machineCode; }
    public String getMachineName() { return machineName; }
    public void setMachineName(String machineName) { this.machineName = machineName; }
    public String getSupplier() { return supplier; }
    public void setSupplier(String supplier) { this.supplier = supplier; }
    public String getSupplierCode() { return supplierCode; }
    public void setSupplierCode(String supplierCode) { this.supplierCode = supplierCode; }
    public String getUserName() { return userName; }
    public void setUserName(String userName) { this.userName = userName; }
    public String getLocation() { return location; }
    public void setLocation(String location) { this.location = location; }
    public Timestamp getCreatedAt() { return createdAt; }
    public void setCreatedAt(Timestamp createdAt) { this.createdAt = createdAt; }
    public Timestamp getUpdatedAt() { return updatedAt; }
    public void setUpdatedAt(Timestamp updatedAt) { this.updatedAt = updatedAt; }
}

3.4 Repository 层

AreaRepository .java

java

复制代码
@Repository
public interface AreaRepository extends JpaRepository<Area, String> {

    List<Area> findByCityCode(String cityCode);
}

其他类似这个,就不写了。

MachineRepository.java

java

复制代码
@Repository
public interface MachineRepository extends JpaRepository<Machine, Integer>, JpaSpecificationExecutor<Machine> {

    @Query("SELECT m FROM Machine m WHERE " +
           "(:locationPrefix IS NULL OR m.location LIKE :locationPrefix) AND " +
           "(:machineCode IS NULL OR m.machineCode LIKE :machineCode) AND " +
           "(:machineName IS NULL OR m.machineName LIKE :machineName) AND " +
           "(:supplier IS NULL OR m.supplier LIKE :supplier) AND " +
           "(:supplierCode IS NULL OR m.supplierCode LIKE :supplierCode)")
    List<Machine> findByConditions(
            @Param("locationPrefix") String locationPrefix,
            @Param("machineCode") String machineCode,
            @Param("machineName") String machineName,
            @Param("supplier") String supplier,
            @Param("supplierCode") String supplierCode,
            Pageable pageable);

    @Query("SELECT COUNT(m) FROM Machine m WHERE " +
           "(:locationPrefix IS NULL OR m.location LIKE :locationPrefix) AND " +
           "(:machineCode IS NULL OR m.machineCode LIKE :machineCode) AND " +
           "(:machineName IS NULL OR m.machineName LIKE :machineName) AND " +
           "(:supplier IS NULL OR m.supplier LIKE :supplier) AND " +
           "(:supplierCode IS NULL OR m.supplierCode LIKE :supplierCode)")
    long countByConditions(
            @Param("locationPrefix") String locationPrefix,
            @Param("machineCode") String machineCode,
            @Param("machineName") String machineName,
            @Param("supplier") String supplier,
            @Param("supplierCode") String supplierCode);
}

3.5 Service 层

RegionService.java
复制代码
@Service
public class RegionService {

    @Autowired
    private ProvinceRepository provinceRepository;

    @Autowired
    private CityRepository cityRepository;

    @Autowired
    private AreaRepository areaRepository;

    @Autowired
    private StreetRepository streetRepository;

    @Autowired
    private VillageRepository villageRepository;

    @Autowired
    private MachineRepository machineRepository;

    @Transactional("sqliteTransactionManager")
    public List<Province> getAllProvinces() {
        return provinceRepository.findAll();
    }

    @Transactional("sqliteTransactionManager")
    public Province getProvinceByCode(String code) {
        return provinceRepository.findByCode(code).orElse(null);
    }

    @Transactional("sqliteTransactionManager")
    public List<City> getCitiesByProvince(String provinceCode) {
        return cityRepository.findByProvinceCode(provinceCode);
    }

    @Transactional("sqliteTransactionManager")
    public List<Area> getAreasByCity(String cityCode) {
        return areaRepository.findByCityCode(cityCode);
    }

    @Transactional("sqliteTransactionManager")
    public List<Street> getStreetsByArea(String areaCode) {
        return streetRepository.findByAreaCode(areaCode);
    }

    @Transactional("sqliteTransactionManager")
    public List<Village> getVillagesByStreet(String streetCode) {
        return villageRepository.findByStreetCode(streetCode);
    }

    @Transactional("mysqlTransactionManager")
    public PageResult<Machine> getMachinesPaged(int page, int size, String locationPrefix,
                                               String machineCode, String machineName,
                                               String supplier, String supplierCode) {
        Pageable pageable = PageRequest.of(page - 1, size);
        List<Machine> data = machineRepository.findByConditions(
                locationPrefix, machineCode, machineName, supplier, supplierCode, pageable);
        long total = machineRepository.countByConditions(
                locationPrefix, machineCode, machineName, supplier, supplierCode);
        return new PageResult<>(data, total, page, size);
    }
}
DeviceService.java

java

复制代码
package com.example.demo.service;

import com.example.demo.entity.Device;
import com.example.demo.repository.DeviceRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;

@Service
public class DeviceService {

    @Autowired
    private DeviceRepository deviceRepository;

    public Page<Device> getDevicesByLocation(String locationCode, int page, int size) {
        Pageable pageable = PageRequest.of(page - 1, size);
        return deviceRepository.findByLocationCode(locationCode, pageable);
    }
}

3.6 Controller 层

RegionController.java

java

复制代码
package com.example.demo.controller;

import com.example.demo.entity.Region;
import com.example.demo.service.RegionService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequestMapping("/api/region")
public class RegionController {

    @Autowired
    private RegionService regionService;

    @GetMapping("/provinces")
    public List<Region> getProvinces() {
        return regionService.getProvinces();
    }

    @GetMapping("/children")
    public List<Region> getChildren(@RequestParam String parentCode) {
        return regionService.getChildren(parentCode);
    }

    // 懒加载专用(树节点点击时调用)
    @GetMapping("/lazy")
    public List<Region> getLazyChildren(@RequestParam String parentCode) {
        return regionService.getLazyChildren(parentCode);
    }

    // 获取完整树(可选)
    @GetMapping("/tree")
    public List<Region> getFullTree() {
        return regionService.getFullTree();
    }
}
复制代码
RegionController

java

复制代码
@RestController
@RequestMapping("/api")
public class RegionController {

    @Autowired
    private RegionService regionService;

    @GetMapping("/provinces")
    public List<Province> getAllProvinces() {
        return regionService.getAllProvinces();
    }

    @GetMapping("/provinces/{code}")
    public Province getProvinceByCode(@PathVariable String code) {
        return regionService.getProvinceByCode(code);
    }

    @GetMapping("/provinces/{provinceCode}/cities")
    public List<City> getCitiesByProvince(@PathVariable String provinceCode) {
        return regionService.getCitiesByProvince(provinceCode);
    }

    @GetMapping("/cities/{cityCode}/areas")
    public List<Area> getAreasByCity(@PathVariable String cityCode) {
        return regionService.getAreasByCity(cityCode);
    }

    @GetMapping("/areas/{areaCode}/streets")
    public List<Street> getStreetsByArea(@PathVariable String areaCode) {
        return regionService.getStreetsByArea(areaCode);
    }

    @GetMapping("/streets/{streetCode}/villages")
    public List<Village> getVillagesByStreet(@PathVariable String streetCode) {
        return regionService.getVillagesByStreet(streetCode);
    }

    @GetMapping("/machines")
    public PageResult<Machine> getMachines(
            @RequestParam(defaultValue = "1") int page,
            @RequestParam(defaultValue = "10") int size,
            @RequestParam(required = false) String locationPrefix,
            @RequestParam(required = false) String machineCode,
            @RequestParam(required = false) String machineName,
            @RequestParam(required = false) String supplier,
            @RequestParam(required = false) String supplierCode) {
        return regionService.getMachinesPaged(page, size, locationPrefix, machineCode, machineName, supplier, supplierCode);
    }
}

3.7 跨域配置(解决前后端分离跨域问题)

java

复制代码
@Bean
public CorsFilter corsFilter() {
    CorsConfiguration config = new CorsConfiguration();
    config.setAllowCredentials(true);
    config.addAllowedOriginPattern("*");
    config.addAllowedHeader("*");
    config.addAllowedMethod("*");

    UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
    source.registerCorsConfiguration("/**", config);
    return new CorsFilter(source);
}

四、前端 Vue + Element UI 实现

4.1 初始化 Vue 项目

bash

复制代码
vue create device-manager
cd device-manager
npm install element-ui axios

4.2 封装 API 请求(src/api/index.js)

javascript

复制代码
export default defineConfig({
  plugins: [vue()],
  server: {
    port: 5173,
    proxy: {
      '/api': {
        target: 'http://localhost:9988',
        changeOrigin: true
      }
    }
  }
})

4.3 主页面组件(src/views/DeviceQuery.vue)

vue

复制代码
<template>
  <div class="app">
    <div class="header">设备查询系统</div>
    <div class="container">
      <div class="tree-panel">
        <h3>省/市/区县/镇/村查询</h3>
        <el-input v-model="treeSearch" placeholder="搜索省/市/区县/镇/村" clearable @input="filterTree" />
        <el-tree
          v-loading="treeLoading"
          :data="filteredTreeData"
          :props="treeProps"
          :load="loadNode"
          :default-expanded-keys="expandedKeys"
          :expand-on-click-node="false"
          lazy
          node-key="code"
          @node-click="handleNodeClick"
        />
      </div>
      <div class="table-panel">
        <h3>设备列表 - {{ currentLocationName }} (共 {{ total }} 条)</h3>

        <!-- 搜索条件 -->
        <div class="search-form">
          <el-input v-model="searchForm.machineCode" placeholder="机器码" clearable @keyup.enter="search" />
          <el-input v-model="searchForm.machineName" placeholder="机器名称" clearable @keyup.enter="search" />
          <el-input v-model="searchForm.supplier" placeholder="供应商" clearable @keyup.enter="search" />
          <el-input v-model="searchForm.supplierCode" placeholder="供应商编码" clearable @keyup.enter="search" />
          <el-button type="primary" @click="search">搜索</el-button>
          <el-button @click="reset">重置</el-button>
        </div>

        <el-table :data="equipmentList" border stripe style="width: 100%">
          <el-table-column prop="machineCode" label="机器码" width="150" />
          <el-table-column prop="machineName" label="机器名称" width="150" />
          <el-table-column prop="supplier" label="供应商" width="120" />
          <el-table-column prop="supplierCode" label="供应商编码" width="120" />
          <el-table-column prop="userName" label="用户名" width="100" />
          <el-table-column prop="location" label="位置" />
          <el-table-column prop="createdAt" label="创建时间" width="160" />
        </el-table>
        <el-empty v-if="equipmentList.length === 0 && hasSearched" description="暂无数据" />
        <div class="pagination">
          <el-pagination
            v-model:current-page="currentPage"
            v-model:page-size="pageSize"
            :page-sizes="[10, 20, 50, 100]"
            :total="total"
            layout="total, sizes, prev, pager, next, jumper"
            @size-change="handleSizeChange"
            @current-change="handleCurrentChange"
          />
        </div>
      </div>
    </div>
  </div>
</template>

<script setup>
import { ref, reactive } from 'vue'
import axios from 'axios'
import { ElMessage } from 'element-plus'

const API_BASE = 'http://localhost:9988/api'

const treeData = ref([])
const treeProps = {
  label: 'name',
  children: 'children',
  isLeaf: (data, node) => data.isLeaf === true
}

const currentLocationName = ref('请选择区域')
const currentLocationCode = ref('')
const equipmentList = ref([])
const hasSearched = ref(false)

// 树形搜索
const treeSearch = ref('')
const filteredTreeData = ref([])
const expandedKeys = ref([])
const treeLoading = ref(false)

// 分页参数
const currentPage = ref(1)
const pageSize = ref(10)
const total = ref(0)

// 搜索表单
const searchForm = reactive({
  machineCode: '',
  machineName: '',
  supplier: '',
  supplierCode: ''
})

// 加载省份
const loadProvinces = async () => {
  try {
    const res = await axios.get(`${API_BASE}/provinces`)
    const provinces = res.data.map(p => ({
      code: p.code,
      name: p.name,
      level: 1
    }))
    treeData.value = provinces
    filteredTreeData.value = provinces
  } catch (err) {
    ElMessage.error('加载省份失败')
  }
}

loadProvinces()

// 搜索树节点
const filterTree = async () => {
  const keyword = treeSearch.value.trim()
  if (!keyword) {
    filteredTreeData.value = treeData.value
    expandedKeys.value = []
    return
  }

  treeLoading.value = true

  try {
    const res = await axios.get(`${API_BASE}/dictRegion/search`, {
      params: { keyword }
    })
    filteredTreeData.value = res.data

    const keys = new Set()
    const collectKeys = (nodes) => {
      nodes.forEach(node => {
        keys.add(node.code)
        if (node.children && node.children.length > 0) {
          collectKeys(node.children)
        }
      })
    }
    collectKeys(res.data)
    expandedKeys.value = Array.from(keys)
  } catch (err) {
    console.error('搜索失败:', err)
  } finally {
    treeLoading.value = false
  }
}

// 懒加载子节点
const loadNode = async (node, resolve) => {
  if (node.level === 0) return

  const parentCode = node.data.code
  const level = node.level + 1
  let url = ''

  if (level === 2) {
    url = `${API_BASE}/provinces/${parentCode}/cities`
  } else if (level === 3) {
    url = `${API_BASE}/cities/${parentCode}/areas`
  } else if (level === 4) {
    url = `${API_BASE}/areas/${parentCode}/streets`
  } else if (level === 5) {
    url = `${API_BASE}/streets/${parentCode}/villages`
  } else if (level === 6) {
    url = `${API_BASE}/villages/${parentCode}/children`
  }

  try {
    const res = await axios.get(url)
    const data = res.data.map(item => ({
      code: item.code,
      name: item.name,
      level,
      isLeaf: level >= 5
    }))
    resolve(data)
  } catch (err) {
    resolve([])
  }
}

// 加载设备数据(带分页和搜索)
const loadMachines = async () => {
  try {
    const params = {
      page: currentPage.value,
      size: pageSize.value
    }
    if (currentLocationCode.value) {
      params.locationPrefix = currentLocationCode.value
    }
    if (searchForm.machineCode) {
      params.machineCode = searchForm.machineCode
    }
    if (searchForm.machineName) {
      params.machineName = searchForm.machineName
    }
    if (searchForm.supplier) {
      params.supplier = searchForm.supplier
    }
    if (searchForm.supplierCode) {
      params.supplierCode = searchForm.supplierCode
    }
    const res = await axios.get(`${API_BASE}/machines`, { params })
    equipmentList.value = res.data.data
    total.value = res.data.total
    hasSearched.value = true
  } catch (err) {
    console.error('查询设备失败:', err)
    ElMessage.error('查询设备失败')
    equipmentList.value = []
  }
}

// 点击节点查询设备
const handleNodeClick = async (data) => {
  currentLocationName.value = data.name
  currentLocationCode.value = data.code
  currentPage.value = 1
  await loadMachines()
}

// 搜索
const search = () => {
  currentPage.value = 1
  loadMachines()
}

// 重置
const reset = () => {
  searchForm.machineCode = ''
  searchForm.machineName = ''
  searchForm.supplier = ''
  searchForm.supplierCode = ''
  currentPage.value = 1
  loadMachines()
}

// 页码改变
const handleCurrentChange = async (page) => {
  currentPage.value = page
  await loadMachines()
}

// 每页条数改变
const handleSizeChange = async (size) => {
  pageSize.value = size
  currentPage.value = 1
  await loadMachines()
}
</script>

<style scoped>
.app { height: 100vh; display: flex; flex-direction: column; }
.header { padding: 15px; background: #409EFF; color: white; font-size: 18px; }
.container { flex: 1; display: flex; overflow: hidden; }
.tree-panel { width: 300px; border-right: 1px solid #e0e0e0; padding: 20px; overflow-y: auto; }
.tree-panel .el-input { margin-bottom: 15px; }
.table-panel { flex: 1; padding: 20px; overflow-y: auto; display: flex; flex-direction: column; }
h3 { margin-bottom: 15px; color: #333; }
.search-form { display: flex; gap: 10px; margin-bottom: 15px; flex-wrap: wrap; }
.search-form .el-input { width: 150px; }
.pagination { margin-top: 20px; display: flex; justify-content: flex-end; }
</style>


五、运行与测试

  1. 启动后端 Spring Boot 应用(默认端口 9988)。

  2. 启动前端 Vue 项目:npm run serve(默认端口 8080可能冲突,可修改前端端口为 5173,并在跨域配置中允许)。

  3. 访问 http://localhost:5173,左侧展示省级树,点击展开子级,右侧展示该区域设备列表。


六、扩展建议

  • 搜索功能:可在后端增加接口,根据关键字匹配设备名称或机器码,并支持分页。

  • 权限控制:可根据用户角色限制可见区域。

通过以上步骤,即可完整实现图中的"设备查询系统"功能。

相关推荐
92year1 小时前
从零写一个MCP Server:让Claude Code直接操作你的数据库
typescript·sqlite·ai agent·mcp·claude code
码界筑梦坊2 小时前
282-基于Python的豆瓣音乐可视化分析推荐系统
开发语言·python·信息可视化·数据分析·flask·vue
码哥字节4 小时前
升到 Spring Boot 4.1,虚拟线程开了,HikariCP 连接池却崩了
java·springboot·claude code
ggabb4 小时前
中英诗歌对比:各有千秋,中文诗词独具极致美学与思想高度
sqlite
chushiyunen6 小时前
滑块验证(滑动验证)
vue
abcy0712136 小时前
【无标题】
数据库·sqlite
Dxy12393102168 小时前
Django 模型查询中的数据库连接池配置指南
数据库·django·sqlite
极光代码工作室19 小时前
基于SpringBoot的校园论坛系统
java·springboot·web开发·后端开发
源码宝2 天前
MES系统源码:Java8 + SpringBoot2.7 + MySQL8 + Redis,后端源码清爽易扩展
java·后端·源码·springboot·mes系统·源码二开·mes源码