eagleye

Quasar QSelect 组件企业级实用教程

Quasar QSelect 组件企业级实用教程

QSelect 是 Quasar 框架中功能强大的下拉选择组件,提供丰富的交互能力和高度可定制性,广泛应用于企业级项目中。本教程从基础用法到高级功能全面覆盖,帮助开发者快速掌握其核心特性与最佳实践。

核心特性

QSelect 组件的核心优势包括:

  • 多模式支持:单选、多选、标签输入等多种交互模式
  • 异步能力:内置异步搜索与过滤,支持远程数据加载
  • 性能优化:虚拟滚动技术处理大量数据,提升渲染效率
  • 高度定制:支持选项渲染、选中项显示等全链路自定义
  • 主题集成:与 Quasar 设计系统无缝融合,保持 UI 一致性

1. 基础用法与配置

1.1 基本单选选择器

适用于从固定选项中选择单个值的场景,如用户选择、状态切换等。

<template>

<q-select

v-model="selectedUser"

:options="userOptions"

label="选择用户"

option-label="name" <!-- 选项显示字段 -->

option-value="id" <!-- 选项值字段 -->

emit-values <!-- 提交时发射 value 而非整个对象 -->

map-options <!-- 自动映射选项,便于回显 -->

filled <!-- 填充式样式 -->

/>

</template>

<script setup>

import { ref } from 'vue';

const selectedUser = ref(null); // 选中值(初始为空)

const userOptions = ref([ // 选项数据源

{ id: 1, name: '张三', department: '研发部' },

{ id: 2, name: '李四', department: '市场部' },

{ id: 3, name: '王五', department: '财务部' }

]);

</script>

1.2 多选模式

支持同时选择多个选项,常用于标签选择、角色分配等场景,配合use-chips可实现标签化展示。

<template>

<q-select

v-model="selectedDepartments" <!-- 绑定数组类型值 -->

:options="departmentOptions"

label="选择部门"

multiple <!-- 启用多选 -->

use-chips <!-- 标签化显示选中项 -->

option-label="name"

option-value="id"

emit-values

map-options

filled

/>

</template>

<script setup>

import { ref } from 'vue';

const selectedDepartments = ref([]); // 初始为空数组

const departmentOptions = ref([

{ id: 'dev', name: '研发部' },

{ id: 'marketing', name: '市场部' },

{ id: 'finance', name: '财务部' }

]);

</script>

2. 选项配置与数据绑定

企业级应用中,选项数据结构多样,QSelect 提供灵活的配置项适配不同场景。

2.1 选项数据结构

支持三种主流数据格式,可根据业务需求选择:

// 1. 简单数组(适用于无关联值的场景)

const simpleOptions = ['选项1', '选项2', '选项3'];

// 2. 对象数组(推荐,支持复杂数据)

const objectOptions = [

{ id: 1, label: '张三', value: 'zhangsan' },

{ id: 2, label: '李四', value: 'lisi' }

];

// 3. 分组选项(适用于分类展示)

const groupedOptions = [

{

label: '技术部门', // 分组标题

options: [ // 分组选项列表

{ id: 1, name: '前端开发' },

{ id: 2, name: '后端开发' }

]

},

{

label: '职能部门',

options: [

{ id: 3, name: '人力资源' },

{ id: 4, name: '财务管理' }

]

}

];

2.2 值处理最佳实践

通过option-label和option-value分离显示文本与实际值,避免直接操作原始数据对象:

<template>

<q-select

v-model="selectedValue"

:options="options"

option-label="displayName" <!-- 显示组合信息 -->

option-value="id" <!-- 提交唯一标识 -->

emit-values

map-options

label="选择项目"

/>

</template>

<script setup>

import { ref } from 'vue';

const selectedValue = ref(null);

const options = ref([

{ id: 'user_001', displayName: '张三 (研发部)', fullName: '张三', department: '研发部' },

{ id: 'user_002', displayName: '李四 (市场部)', fullName: '李四', department: '市场部' }

]);

</script>

3. 异步数据加载

企业级应用常需从后端 API 动态加载选项,QSelect 通过@filter事件与异步逻辑实现高效搜索。

3.1 基本异步搜索

监听用户输入,触发 API 请求并更新选项列表:

<template>

<q-select

v-model="selectedItem"

:options="options"

use-input <!-- 启用输入框 -->

hide-selected <!-- 隐藏已选选项 -->

fill-input <!-- 输入框占满宽度 -->

input-debounce="300" <!-- 输入防抖(毫秒) -->

@filter="filterOptions" <!-- 输入变化时触发搜索 -->

label="搜索并选择"

/>

</template>

<script setup>

import { ref } from 'vue';

import { api } from 'src/boot/axios'; // 假设已配置 axios

const selectedItem = ref(null);

const options = ref([]);

const filterOptions = async (val, update, abort) => {

if (val.length < 2) { // 输入长度不足时中止搜索

abort();

return;

}

try {

const response = await api.get('/api/users/search', {

params: { keyword: val } // 传递搜索关键词

});

update(() => { // 更新选项列表

options.value = response.data;

});

} catch (error) {

console.error('搜索失败:', error);

abort(); // 出错时中止更新

}

};

</script>

3.2 带加载状态的异步搜索

添加加载动画与空状态提示,提升用户体验:

<template>

<q-select

v-model="selectedUser"

:options="userOptions"

:loading="loading" <!-- 加载状态 -->

use-input

hide-selected

fill-input

input-debounce="500"

@filter="asyncFilter"

label="搜索用户"

>

<!-- 无选项时显示 -->

<template v-slot:no-option>

<q-item>

<q-item-section class="text-grey">

{{ loading ? '搜索中...' : '无匹配结果' }}

</q-item-section>

</q-item>

</template>

</q-select>

</template>

<script setup>

import { ref } from 'vue';

import { api } from 'src/boot/axios';

const selectedUser = ref(null);

const userOptions = ref([]);

const loading = ref(false); // 加载状态标记

const asyncFilter = async (val, update, abort) => {

if (val.length < 2) {

abort();

return;

}

loading.value = true; // 开始加载

try {

const response = await api.get('/api/users', { params: { search: val } });

update(() => {

userOptions.value = response.data.users;

});

} catch (error) {

console.error('搜索失败:', error);

abort();

} finally {

loading.value = false; // 结束加载

}

};

</script>

4. 高级功能与自定义

4.1 自定义选项渲染

通过option插槽完全控制选项的 UI 结构,支持头像、图标、多行为展示:

<template>

<q-select

v-model="selectedUser"

:options="users"

label="选择用户"

option-label="name"

option-value="id"

emit-values

map-options

>

<template v-slot:option="scope"> <!-- scope 包含选项数据与属性 -->

<q-item v-bind="scope.itemProps"> <!-- 绑定内置属性(必选) -->

<!-- 头像区域 -->

<q-item-section avatar>

<q-avatar>

<img :src="scope.opt.avatar"> <!-- 动态头像 -->

</q-avatar>

</q-item-section>

<!-- 文本区域 -->

<q-item-section>

<q-item-label>{{ scope.opt.name }}</q-item-label>

<q-item-label caption>{{ scope.opt.department }} · {{ scope.opt.position }}</q-item-label>

</q-item-section>

<!-- 侧边图标 -->

<q-item-section side>

<q-icon name="star" color="yellow-8" v-if="scope.opt.isAdmin" /> <!-- 管理员标记 -->

</q-item-section>

</q-item>

</template>

</q-select>

</template>

4.2 自定义选中项显示

通过selected-item插槽修改多选模式下的标签样式:

<template>

<q-select

v-model="selectedTags"

:options="tagOptions"

multiple

use-chips

label="选择标签"

>

<template v-slot:selected-item="scope"> <!-- scope 包含选中项数据 -->

<q-chip

removable

@remove="scope.removeAtIndex(scope.index)" <!-- 移除选中项 -->

color="primary"

text-color="white"

>

<q-avatar icon="label" color="white" text-color="primary" /> <!-- 标签图标 -->

{{ scope.opt.label }} <!-- 标签文本 -->

</q-chip>

</template>

</q-select>

</template>

4.3 懒加载优化(大量数据)

使用虚拟滚动(virtual-scroll)仅渲染可视区域选项,支持万级数据量:

<template>

<q-select

v-model="selectedItems"

:options="lazyOptions"

virtual-scroll <!-- 启用虚拟滚动 -->

:virtual-scroll-item-size="48" <!-- 预估项高度(像素) -->

:virtual-scroll-slice-size="50" <!-- 单次渲染数量 -->

@virtual-scroll="onScroll" <!-- 滚动事件(触底加载) -->

label="选择项目(懒加载)"

multiple

use-chips

/>

</template>

<script setup>

import { ref, onMounted } from 'vue';

import { api } from 'src/boot/axios';

const selectedItems = ref([]);

const lazyOptions = ref([]);

const loading = ref(false);

const hasMore = ref(true); // 是否还有更多数据

const page = ref(1); // 当前页码

// 加载更多数据

const loadOptions = async () => {

if (loading.value || !hasMore.value) return;

loading.value = true;

try {

const response = await api.get('/api/items', {

params: { page: page.value, limit: 50 } // 分页参数

});

if (response.data.items.length === 0) {

hasMore.value = false; // 无数据时停止加载

return;

}

lazyOptions.value = [...lazyOptions.value, ...response.data.items]; // 追加数据

page.value++; // 页码自增

} catch (error) {

console.error('加载选项失败:', error);

} finally {

loading.value = false;

}

};

// 滚动触底检测

const onScroll = ({ index, to, ref }) => {

if (to > lazyOptions.value.length - 10 && hasMore.value) { // 距离底部 10 项时加载

loadOptions();

}

};

onMounted(() => {

loadOptions(); // 初始加载第一页

});

</script>

5. 样式自定义与主题集成

5.1 条件样式

通过动态 class 控制组件状态(如选中/未选中):

<template>

<q-select

v-model="selectedValue"

:options="options"

:class="{ 'custom-selected': selectedValue, 'custom-empty': !selectedValue }"

label="选择选项"

filled

/>

</template>

<style scoped>

/* 选中状态样式 */

.custom-selected :deep(.q-field__control) { /* :deep 穿透 scoped */

border: 2px solid #1976d2;

font-weight: bold;

}

/* 未选中状态样式 */

.custom-empty :deep(.q-field__control) {

border: 1px solid #c0c0c0;

}

/* 标签样式 */

.custom-selected :deep(.q-field__label) {

font-weight: bold;

color: #1976d2;

}

</style>

5.2 响应式设计

结合 Quasar 的$q.screen实现不同设备下的样式适配:

<template>

<q-select

v-model="selectedItem"

:options="options"

:dense="$q.screen.lt.md" <!-- 小屏设备紧凑模式 -->

:outlined="$q.screen.gt.sm" <!-- 中大屏设备边框模式 -->

:filled="$q.screen.lt.sm" <!-- 小屏设备填充模式 -->

label="响应式选择器"

class="q-mb-md"

style="min-width: 200px; max-width: 400px;" <!-- 宽度限制 -->

/>

</template>

6. 企业级最佳实践

6.1 封装可复用组件

QSelect 封装为通用组件,统一处理远程数据加载、样式与验证逻辑:

<!-- AppSelect.vue -->

<template>

<q-select

v-bind="$attrs" <!-- 透传属性 -->

v-model="internalValue"

:options="processedOptions"

:loading="loading"

option-label="label"

option-value="value"

emit-values

map-options

@update:model-value="onUpdate"

>

<!-- 透传插槽 -->

<template v-for="(slot, name) in $slots" #[name]="slotProps">

<slot :name="name" v-bind="slotProps" />

</template>

</q-select>

</template>

<script setup>

import { computed, ref, watch } from 'vue';

const props = defineProps({

modelValue: { type: [String, Number, Array, Object], default: null },

options: { type: [Array, Object], default: () => [] }, // 本地选项

apiUrl: { type: String, default: '' }, // 远程 API 地址

optionLabel: { type: String, default: 'label' },

optionValue: { type: String, default: 'value' }

});

const emit = defineEmits(['update:modelValue', 'change']);

const internalValue = ref(props.modelValue);

const loading = ref(false);

const remoteOptions = ref([]); // 远程加载的选项

// 处理选项数据(优先远程数据)

const processedOptions = computed(() => {

return props.apiUrl ? remoteOptions.value : props.options;

});

// 监听外部值变化

watch(() => props.modelValue, (newValue) => {

internalValue.value = newValue;

});

// 加载远程选项

const loadOptions = async () => {

if (!props.apiUrl) return;

loading.value = true;

try {

const response = await api.get(props.apiUrl); // 假设全局 api 对象

remoteOptions.value = response.data;

} catch (error) {

console.error('加载选项失败:', error);

} finally {

loading.value = false;

}

};

// 通知外部值变化

const onUpdate = (value) => {

emit('update:modelValue', value);

emit('change', value);

};

// 组件挂载时加载远程数据

if (props.apiUrl) {

loadOptions();

}

</script>

6.2 表单验证集成

结合 Quasar 表单验证机制,实现必填、长度限制等规则:

<template>

<q-form @submit="onSubmit" class="q-gutter-md">

<!-- 部门选择(单选验证) -->

<app-select

v-model="formData.department"

:options="departmentOptions"

label="选择部门"

:rules="[

val => !!val || '请选择部门', // 必填

val => val !== 'invalid' || '请选择有效部门' // 自定义规则

]"

required

/>

<!-- 用户选择(多选验证) -->

<app-select

v-model="formData.users"

:api-url="/api/users"

label="选择用户"

multiple

use-chips

:rules="[

val => val && val.length > 0 || '请至少选择一个用户', // 至少选一个

val => val.length <= 5 || '最多选择5个用户' // 最多5个

]"

required

/>

<q-btn label="提交" type="submit" color="primary" />

</q-form>

</template>

<script setup>

import { ref } from 'vue';

import { useQuasar } from 'quasar';

import AppSelect from './AppSelect.vue'; // 引入封装组件

const $q = useQuasar();

const formData = ref({ department: null, users: [] });

const departmentOptions = ref([

{ value: 'dev', label: '研发部' },

{ value: 'marketing', label: '市场部' },

{ value: 'finance', label: '财务部' }

]);

const onSubmit = async () => {

try {

// 提交逻辑(如 API 请求)

$q.notify({ type: 'positive', message: '提交成功' });

} catch (error) {

$q.notify({ type: 'negative', message: '提交失败' });

}

};

</script>

6.3 性能优化建议

  • 虚拟滚动:数据量 > 100 时启用virtual-scroll<q-select virtual-scroll :virtual-scroll-item-size="48" ... />
  • 防抖搜索:输入防抖input-debounce="500"减少 API 请求
  • 分页加载:超大数据量(> 1000)时实现分页或滚动触底加载
  • 选项缓存:缓存已加载数据,避免重复请求(如使用localStorage或全局状态)

7. 常见问题与解决方案

7.1 选项显示[object Object]

原因:未配置option-label或emit-values,导致直接显示对象。

解决:指定显示字段并启用值发射:

<q-select

option-label="name" <!-- 显示字段 -->

option-value="id" <!-- 值字段 -->

emit-values <!-- 发射值而非对象 -->

map-options <!-- 映射选项(回显用) -->

...

/>

7.2 异步加载性能问题

错误示例:使用固定超时阻塞线程

// 错误

setTimeout(() => { update(() => options.value = data); }, 2000);

// 正确:直接使用 API 响应

const filterFn = async (val, update, abort) => {

const response = await api.get('/api/data', { params: { search: val } });

update(() => options.value = response.data); // 无需超时

};

7.3 事件处理

常用事件:

  • @update:model-value:值变化时触发
  • @filter:输入搜索时触发
  • @popup-show/@popup-hide:下拉框显示/隐藏时触发

总结

QSelect 组件通过灵活的配置与强大的自定义能力,可满足企业级应用的复杂需求。核心要点包括:

1. 数据绑定:通过option-label/option-value分离显示与值

2. 异步优化:结合防抖、加载状态与虚拟滚动提升性能

3. 自定义渲染:利用插槽实现个性化 UI

4. 复用封装:抽象为通用组件减少重复代码

掌握以上内容后,可高效开发出交互流畅、性能优异的选择器功能。

 

posted on 2025-09-01 17:07  GoGrid  阅读(18)  评论(0)    收藏  举报

导航