Skip to content

层级组件

层级组件提供了多级联动选择功能,适用于地区选择、分类选择等场景。

导入

typescript
import { DuxLevel } from '@duxweb/dvha-pro'

组件总览

DVHA Pro 提供以下层级组件:

  • DuxLevel - 多级联动选择组件

DuxLevel 多级联动选择

多级联动选择组件,支持省市区、分类等多级数据的级联选择,具有自动加载下级数据的能力。

属性

属性名类型默认值说明
valuestring[][]选中的值数组
pathstring'area'API 接口路径
maxLevelnumber4最大层级数(0 表示无限层级)
nameFieldstring'name'用于查询下级数据的字段名
labelFieldstring'name'显示标签的字段名
valueFieldstring'value'选项值的字段名

事件

事件名类型说明
update:value(value: string[]) => void值更新时触发

基础用法

vue
<script setup>
import { DuxLevel } from '@duxweb/dvha-pro'
import { ref } from 'vue'

const selectedRegion = ref([])

function handleRegionChange(value) {
  console.log('选中的地区:', value)
}
</script>

<template>
  <DuxLevel
    v-model:value="selectedRegion"
    path="/api/regions"
    :max-level="3"
    @update:value="handleRegionChange"
  />
</template>

地区选择示例

vue
<script setup>
import { DuxLevel } from '@duxweb/dvha-pro'
import { ref } from 'vue'

const address = ref({
  province: '',
  city: '',
  district: '',
  region: []
})

function handleRegionChange(value) {
  address.value.region = value
  // 根据选择的层级设置对应的值
  address.value.province = value[0] || ''
  address.value.city = value[1] || ''
  address.value.district = value[2] || ''
}
</script>

<template>
  <div class="space-y-4">
    <div>
      <label class="block text-sm font-medium mb-2">选择地区</label>
      <DuxLevel
        v-model:value="address.region"
        path="/api/area"
        :max-level="3"
        name-field="code"
        label-field="name"
        value-field="code"
        @update:value="handleRegionChange"
      />
    </div>

    <!-- 显示选择结果 -->
    <div v-if="address.region.length > 0" class="p-4 bg-gray-50 rounded">
      <h4 class="font-medium mb-2">
        选择结果:
      </h4>
      <div class="space-y-1 text-sm">
        <div>省份:{{ address.province }}</div>
        <div>城市:{{ address.city }}</div>
        <div>区县:{{ address.district }}</div>
        <div>完整路径:{{ address.region.join(' / ') }}</div>
      </div>
    </div>
  </div>
</template>

分类选择示例

vue
<script setup>
import { DuxLevel } from '@duxweb/dvha-pro'
import { ref } from 'vue'

const productCategory = ref([])

function handleCategoryChange(value) {
  console.log('选中的分类路径:', value)
}
</script>

<template>
  <div class="space-y-4">
    <div>
      <label class="block text-sm font-medium mb-2">选择商品分类</label>
      <DuxLevel
        v-model:value="productCategory"
        path="/api/categories"
        :max-level="4"
        name-field="id"
        label-field="title"
        value-field="id"
        @update:value="handleCategoryChange"
      />
    </div>

    <!-- 面包屑导航 -->
    <div v-if="productCategory.length > 0" class="flex items-center space-x-2 text-sm">
      <span class="text-gray-500">当前位置:</span>
      <template v-for="(item, index) in productCategory" :key="index">
        <span v-if="index > 0" class="text-gray-400">/</span>
        <span class="text-blue-600">{{ item }}</span>
      </template>
    </div>
  </div>
</template>

表单集成

vue
<script setup>
import { DuxFormItem, DuxFormLayout, DuxLevel } from '@duxweb/dvha-pro'
import { NButton, NInput } from 'naive-ui'
import { ref } from 'vue'

const form = ref({
  name: '',
  address: {
    region: [],
    detail: ''
  },
  category: []
})

function handleSubmit() {
  console.log('表单数据:', form.value)
}
</script>

<template>
  <div class="max-w-2xl mx-auto p-6">
    <h2 class="text-xl font-bold mb-6">
      商品信息
    </h2>

    <DuxFormLayout label-placement="top" divider>
      <DuxFormItem label="商品名称" path="name" rule="required">
        <NInput v-model:value="form.name" placeholder="请输入商品名称" />
      </DuxFormItem>

      <DuxFormItem label="商品分类" path="category" rule="required">
        <DuxLevel
          v-model:value="form.category"
          path="/api/categories"
          :max-level="3"
          placeholder="请选择商品分类"
        />
      </DuxFormItem>

      <DuxFormItem label="发货地区" path="address.region" rule="required">
        <DuxLevel
          v-model:value="form.address.region"
          path="/api/area"
          :max-level="3"
          placeholder="请选择发货地区"
        />
      </DuxFormItem>

      <DuxFormItem label="详细地址" path="address.detail" rule="required">
        <NInput
          v-model:value="form.address.detail"
          placeholder="请输入详细地址"
          type="textarea"
          :rows="3"
        />
      </DuxFormItem>
    </DuxFormLayout>

    <div class="mt-6 flex justify-end">
      <NButton type="primary" @click="handleSubmit">
        保存商品
      </NButton>
    </div>
  </div>
</template>

自定义字段映射

vue
<script setup>
import { DuxLevel } from '@duxweb/dvha-pro'
import { ref } from 'vue'

// 适配不同的 API 数据结构
const organizationPath = ref([])
</script>

<template>
  <div class="space-y-6">
    <!-- 标准地区选择 -->
    <div>
      <h3 class="font-medium mb-2">
        地区选择(标准字段)
      </h3>
      <DuxLevel
        v-model:value="organizationPath"
        path="/api/area"
        name-field="code"
        label-field="name"
        value-field="code"
      />
    </div>

    <!-- 组织架构选择 -->
    <div>
      <h3 class="font-medium mb-2">
        组织架构(自定义字段)
      </h3>
      <DuxLevel
        v-model:value="organizationPath"
        path="/api/organizations"
        name-field="org_id"
        label-field="org_name"
        value-field="org_id"
        :max-level="5"
      />
    </div>

    <!-- 商品分类选择 -->
    <div>
      <h3 class="font-medium mb-2">
        商品分类(ID字段)
      </h3>
      <DuxLevel
        v-model:value="organizationPath"
        path="/api/product-categories"
        name-field="id"
        label-field="title"
        value-field="id"
        :max-level="4"
      />
    </div>
  </div>
</template>

无限层级示例

vue
<script setup>
import { DuxLevel } from '@duxweb/dvha-pro'
import { ref } from 'vue'

const unlimitedPath = ref([])
</script>

<template>
  <div>
    <h3 class="font-medium mb-2">
      无限层级选择
    </h3>
    <p class="text-sm text-gray-600 mb-4">
      设置 max-level 为 0 可以支持无限层级的数据选择
    </p>

    <DuxLevel
      v-model:value="unlimitedPath"
      path="/api/unlimited-categories"
      :max-level="0"
      name-field="id"
      label-field="name"
      value-field="id"
    />

    <!-- 显示完整路径 -->
    <div v-if="unlimitedPath.length > 0" class="mt-4 p-3 bg-blue-50 rounded">
      <div class="text-sm">
        <strong>选择路径:</strong>
        {{ unlimitedPath.join(' → ') }}
      </div>
      <div class="text-xs text-gray-600 mt-1">
        层级深度:{{ unlimitedPath.length }}
      </div>
    </div>
  </div>
</template>

API 接口规范

请求参数

组件会向指定的 API 路径发送 GET 请求,请求参数如下:

typescript
interface LevelApiParams {
  level: number // 当前层级(从 0 开始)
  [nameField]: string // 上级选中的值(第一级时不传)
}

响应格式

API 应返回符合以下格式的数据:

typescript
interface LevelApiResponse {
  data: Array<{
    [labelField]: string // 显示文本
    [valueField]: string // 选项值
    // 其他字段...
  }>
}

示例接口

javascript
// GET /api/area?level=0
{
  "data": [
    { "code": "110000", "name": "北京市" },
    { "code": "120000", "name": "天津市" },
    { "code": "130000", "name": "河北省" }
  ]
}

// GET /api/area?level=1&code=130000
{
  "data": [
    { "code": "130100", "name": "石家庄市" },
    { "code": "130200", "name": "唐山市" },
    { "code": "130300", "name": "秦皇岛市" }
  ]
}

最佳实践

性能优化

vue
<script setup>
import { ref, watch } from 'vue'

const region = ref([])

// 监听变化,实现业务逻辑
watch(region, (newValue, oldValue) => {
  // 只在完整选择后触发业务逻辑
  if (newValue.length === 3) {
    // 执行相关业务操作
    console.log('地区选择完成:', newValue)
  }
}, { deep: true })
</script>

错误处理

vue
<script setup>
import { DuxLevel } from '@duxweb/dvha-pro'
import { ref } from 'vue'

const region = ref([])
const error = ref('')

function handleError(err) {
  error.value = '加载数据失败,请重试'
  console.error('Level component error:', err)
}
</script>

<template>
  <div>
    <DuxLevel
      v-model:value="region"
      path="/api/area"
      @error="handleError"
    />
    <div v-if="error" class="mt-2 text-sm text-red-600">
      {{ error }}
    </div>
  </div>
</template>

常见问题

如何处理数据加载失败?

组件内部已集成错误处理,可以通过监听 error 事件来处理加载失败的情况。

如何设置默认选中值?

通过 v-model:value 绑定初始值数组即可:

vue
<script setup>
const defaultRegion = ref(['130000', '130100', '130102'])
</script>

<template>
  <DuxLevel v-model:value="defaultRegion" path="/api/area" />
</template>

如何自定义样式?

组件使用 UnoCSS 类名,可以通过 CSS 覆盖或使用 class 属性自定义样式:

vue
<DuxLevel
  v-model:value="region"
  path="/api/area"
  class="custom-level-component"
/>

组件是否支持异步数据?

是的,组件内部使用 TanStack Query 进行数据管理,支持异步加载、缓存、错误重试等功能。