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. 复用封装:抽象为通用组件减少重复代码
掌握以上内容后,可高效开发出交互流畅、性能优异的选择器功能。
浙公网安备 33010602011771号