Appearance
BizRemoteSelect 远程搜索选择器
el-select 远程搜索封装:输入关键词防抖检索、同关键词首屏缓存、下拉滚动到底自动翻页追加、id 回显。适用于万级数据源(选人 / 选商品 / 选机构)无法一次性拉全量的表单场景。检索实现由业务侧注入,组件不含任何业务接口。
基础用法
请输入关键词搜索
request 注入检索实现(keyword / page / pageSize 入参,list / total 返回);输入防抖 300ms,下拉滚动到底自动追加下一页,底部附「加载更多」兜底。<script setup lang="ts">
/** 基础用法:千条 mock 数据远程搜索(防抖 300ms)+ 下拉滚动到底自动翻页追加 */
import { ref } from 'vue'
import { BizRemoteSelect } from 'vue3-ep-biz-lib'
const userId = ref<number>()
/** 模拟千条数据源(真实项目替换为服务端检索接口) */
const allUsers = Array.from({ length: 1000 }, (_unused, i) => ({
value: 1000 + i,
label: `用户-${String(i + 1).padStart(4, '0')}(user${i + 1}@demo.com)`,
}))
const request = ({ keyword, page, pageSize }: { keyword: string; page: number; pageSize: number }): Promise<{ list: Record<string, unknown>[]; total: number }> => {
return new Promise((resolve) => {
setTimeout(() => {
const filtered = keyword === '' ? allUsers : allUsers.filter((row) => row.label.includes(keyword))
resolve({ list: filtered.slice((page - 1) * pageSize, page * pageSize), total: filtered.length })
}, 300)
})
}
</script>
<template>
<BizRemoteSelect v-model="userId" :request="request" style="max-width: 320px" />
</template>
显示代码
回显与 reload
v-model 传入 id,组件拉取首屏后自动回显 label;
reload() 清缓存重查,清空后 emit change(null)。<script setup lang="ts">
/** 进阶:回显 + 清空 + reload;禁用状态与占位文案 */
import { ref } from 'vue'
import { ElButton, ElMessage } from 'element-plus'
import { BizRemoteSelect } from 'vue3-ep-biz-lib'
import type { BizRemoteSelectInstance } from 'vue3-ep-biz-lib'
const selectRef = ref<BizRemoteSelectInstance>()
/** 初始只有 id:打开下拉拉取首屏后即回显 label */
const goodsId = ref<string | number | boolean | undefined>(3005)
const warehouses = Array.from({ length: 80 }, (_unused, i) => ({ value: `WH-${i + 1}`, label: `仓库-${i + 1}(华东 ${i % 9 + 1} 区)` }))
const request = ({ keyword, page, pageSize }: { keyword: string; page: number; pageSize: number }): Promise<{ list: Record<string, unknown>[]; total: number }> => {
return new Promise((resolve) => {
setTimeout(() => {
const filtered = keyword === '' ? warehouses : warehouses.filter((row) => row.label.includes(keyword))
resolve({ list: filtered.slice((page - 1) * pageSize, page * pageSize), total: filtered.length })
}, 200)
})
}
const onChange = (option: { label: string; value: string | number | boolean } | null): void => {
ElMessage.success(option === null ? '已清空' : `选中:${option.label}`)
}
</script>
<template>
<div style="display: flex; gap: 12px; align-items: center; flex-wrap: wrap">
<BizRemoteSelect
ref="selectRef"
v-model="goodsId"
:request="request"
:debounce="200"
placeholder="搜索仓库(回显示例)"
style="max-width: 320px"
@change="onChange"
/>
<ElButton @click="goodsId = undefined">清空选中</ElButton>
<ElButton @click="selectRef?.reload()">reload()</ElButton>
</div>
</template>
显示代码
编辑回显(initial-options 注入)
v-model = 88 未注入 initialOptions 时此处只显示裸 id
当前回显:成员088(供应链部)|输入关键词或滚动加载翻页检索全部 100 人
编辑页经典问题:v-model 的 id 不在首屏检索结果中时只显示裸 id。
initial-options 注入当前选中项(与检索结果合并去重、不参与分页计数),打开即正确回显 label。<script setup lang="ts">
/** 场景:编辑页回显——选中项不在首屏检索结果中,经 initialOptions 注入保证 label 回显 */
import { ref } from 'vue'
import { ElMessage, ElTag } from 'element-plus'
import { BizRemoteSelect } from 'vue3-ep-biz-lib'
import type { BizRemoteOption, BizRemoteSelectParams, BizRemoteSelectResult } from 'vue3-ep-biz-lib'
type MemberRow = {
id: number
name: string
dept: string
}
/** 全量成员 100 人(服务端分页,每页 20):目标负责人 id=88 在第 5 页 */
const allMembers: MemberRow[] = Array.from({ length: 100 }, (_unused, i) => ({
id: i + 1,
name: `成员${String(i + 1).padStart(3, '0')}`,
dept: i % 4 === 0 ? '研发部' : i % 4 === 1 ? '运营部' : i % 4 === 2 ? '财务部' : '供应链部',
}))
const request = (params: BizRemoteSelectParams): Promise<BizRemoteSelectResult> => {
const keyword = params.keyword.trim()
const filtered = keyword === '' ? allMembers : allMembers.filter((m) => m.name.includes(keyword) || m.dept.includes(keyword))
const start = (params.page - 1) * params.pageSize
return new Promise<BizRemoteSelectResult>((resolve) => {
setTimeout(() => resolve({ list: filtered.slice(start, start + params.pageSize), total: filtered.length }), 300)
})
}
/** 编辑态:表单里的负责人 id=88(编辑时业务侧一般能从详情接口拿到该项) */
const ownerId = ref<number | undefined>(88)
const ownerLabel = ref('成员088(供应链部)')
const initial: BizRemoteOption[] = [{ label: ownerLabel.value, value: 88 }]
const onChange = (option: BizRemoteOption | null): void => {
ownerLabel.value = option?.label ?? '—'
if (option !== null) ElMessage.info(`已选择 ${option.label}`)
}
</script>
<template>
<div style="max-width: 420px">
<div style="display: flex; gap: 8px; align-items: center; margin-bottom: 12px">
<ElTag size="small" type="info">v-model = 88</ElTag>
<span style="font-size: 13px; color: var(--el-text-color-secondary)">
未注入 initialOptions 时此处只显示裸 id
</span>
</div>
<BizRemoteSelect v-model="ownerId" :request="request" :initial-options="initial" @change="onChange" />
<div style="margin-top: 8px; font-size: 13px; color: var(--el-text-color-secondary)">
当前回显:{{ ownerLabel }}|输入关键词或滚动加载翻页检索全部 100 人
</div>
</div>
</template>
显示代码
指派代码评审人
搜索姓名或团队指派评审人
当前指派:未指派
指派场景:change 事件回传完整选项对象(含部门 / 角色),选中后在下方展示指派结果。
<script setup lang="ts">
/** 场景:指派代码评审人——搜索并选择,change 回调拿完整选项(含部门) */
import { ref } from 'vue'
import { ElMessage } from 'element-plus'
import { BizRemoteSelect } from 'vue3-ep-biz-lib'
import type { BizRemoteOption, BizRemoteSelectParams, BizRemoteSelectResult } from 'vue3-ep-biz-lib'
type DevRow = { id: number; name: string; dept: string; role: string }
const devs: DevRow[] = Array.from({ length: 60 }, (_unused, i) => ({
id: i + 1,
name: `开发${String(i + 1).padStart(2, '0')}`,
dept: ['前端组', '服务端组', '测试组', '架构组'][i % 4],
role: i % 3 === 0 ? '高级工程师' : '工程师',
}))
const request = (params: BizRemoteSelectParams): Promise<BizRemoteSelectResult> => {
const keyword = params.keyword.trim()
const filtered = keyword === '' ? devs : devs.filter((d) => d.name.includes(keyword) || d.dept.includes(keyword))
const start = (params.page - 1) * params.pageSize
return new Promise((resolve) => {
setTimeout(() => resolve({ list: filtered.slice(start, start + params.pageSize), total: filtered.length }), 250)
})
}
const reviewerId = ref<number | undefined>(undefined)
const reviewerInfo = ref('未指派')
const onChange = (option: BizRemoteOption | null): void => {
if (option === null) {
reviewerInfo.value = '未指派'
return
}
const row = devs.find((d) => d.id === option.value)
reviewerInfo.value = row === undefined ? String(option.label) : `${row.name} · ${row.dept} · ${row.role}`
ElMessage.success(`已指派评审人:${String(option.label)}`)
}
</script>
<template>
<div style="max-width: 480px">
<BizRemoteSelect
v-model="reviewerId"
:request="request"
:label-field="'name'"
placeholder="搜索姓名或团队指派评审人"
@change="onChange"
/>
<div style="margin-top: 8px; font-size: 13px; color: var(--el-text-color-secondary)">当前指派:{{ reviewerInfo }}</div>
</div>
</template>
显示代码
商品 SKU 选择
输入 SKU 编码或名称(共 500 条)
label-field 取 code;实际项目可在 label 上拼接「编码 + 名称 + 规格」由后端返回
选品场景:500 条 SKU 分页检索(滚动到底自动翻页),value-field 取 code 作为提交值。
<script setup lang="ts">
/** 场景:商品选择——500 条分页检索,label 带 编码/规格 */
import { ref } from 'vue'
import { BizRemoteSelect } from 'vue3-ep-biz-lib'
import type { BizRemoteSelectParams, BizRemoteSelectResult } from 'vue3-ep-biz-lib'
type SkuRow = { code: string; title: string; spec: string }
const skus: SkuRow[] = Array.from({ length: 500 }, (_unused, i) => ({
code: `SKU-${String(10000 + i)}`,
title: `商品 ${String.fromCharCode(65 + (i % 26))}${i}`,
spec: ['500ml×24', '整箱', '10kg', '单件'][i % 4],
}))
const request = (params: BizRemoteSelectParams): Promise<BizRemoteSelectResult> => {
const keyword = params.keyword.trim().toUpperCase()
const filtered = keyword === '' ? skus : skus.filter((s) => s.code.includes(keyword) || s.title.includes(params.keyword.trim()))
const start = (params.page - 1) * params.pageSize
return new Promise((resolve) => {
setTimeout(() => resolve({ list: filtered.slice(start, start + params.pageSize), total: filtered.length }), 200)
})
}
const skuCode = ref<string | undefined>(undefined)
</script>
<template>
<div style="max-width: 480px">
<BizRemoteSelect
v-model="skuCode"
:request="request"
label-field="code"
value-field="code"
:page-size="20"
placeholder="输入 SKU 编码或名称(共 500 条)"
/>
<div style="margin-top: 8px; font-size: 13px; color: var(--el-text-color-secondary)">
label-field 取 code;实际项目可在 label 上拼接「编码 + 名称 + 规格」由后端返回
</div>
</div>
</template>
显示代码
供应商绑定(冻结联动)
合同冻结期禁止变更供应商(disabled 联动)
合同场景:debounce 500ms 重查询友好 + 冻结期 disabled 联动 + initialOptions 回显当前绑定方。
<script setup lang="ts">
/** 场景:供应商绑定——慢接口加大防抖 + 冻结期禁用 + 清空联动 */
import { ref } from 'vue'
import { ElSwitch } from 'element-plus'
import { BizRemoteSelect } from 'vue3-ep-biz-lib'
import type { BizRemoteSelectParams, BizRemoteSelectResult } from 'vue3-ep-biz-lib'
type SupplierRow = { id: number; name: string; level: string }
const suppliers: SupplierRow[] = Array.from({ length: 88 }, (_unused, i) => ({
id: i + 1,
name: `供应商${String(i + 1).padStart(3, '0')}`,
level: i % 3 === 0 ? 'A 级' : i % 3 === 1 ? 'B 级' : 'C 级',
}))
const request = (params: BizRemoteSelectParams): Promise<BizRemoteSelectResult> => {
const keyword = params.keyword.trim()
const filtered = keyword === '' ? suppliers : suppliers.filter((s) => s.name.includes(keyword))
const start = (params.page - 1) * params.pageSize
return new Promise((resolve) => {
setTimeout(() => resolve({ list: filtered.slice(start, start + params.pageSize), total: filtered.length }), 200)
})
}
const supplierId = ref<number | undefined>(12)
const frozen = ref(false)
</script>
<template>
<div style="max-width: 480px; display: flex; flex-direction: column; gap: 10px">
<BizRemoteSelect
v-model="supplierId"
:request="request"
:debounce="500"
:disabled="frozen"
:initial-options="[{ label: '供应商012(A 级 · 当前绑定)', value: 12 }]"
placeholder="搜索供应商重新绑定"
/>
<div style="display: flex; gap: 10px; align-items: center">
<ElSwitch v-model="frozen" />
<span style="font-size: 13px; color: var(--el-text-color-secondary)">合同冻结期禁止变更供应商(disabled 联动)</span>
</div>
</div>
</template>
显示代码
表格下拉模式(type="table")
下拉内嵌 el-table:编码 + 名称 + 规格等多列信息辨认后单选回填——ERP 选物料 / 选客户、财务选科目、选仓库等「字段级表格选择」场景(弹窗级的批量选择用 BizTablePicker)。request 协议与 select 模式完全一致,v-model 依旧传 id;隐藏 Option 池承接回显。
输入编码 / 名称搜索物料
选物料:
table-columns 声明多列,行点选回填;change 事件回传完整原始行(raw)。<script setup lang="ts">
/** 场景:选物料——表格下拉基础(编码 / 名称 / 规格多列辨认后回填) */
import { ref } from 'vue'
import { BizRemoteSelect } from 'vue3-ep-biz-lib'
import type { BizRemoteTableColumn } from 'vue3-ep-biz-lib'
interface MaterialRow extends Record<string, unknown> {
code: string
name: string
spec: string
unit: string
}
const MATERIALS: MaterialRow[] = Array.from({ length: 46 }, (_, i) => ({
code: `MAT-${String(1000 + i)}`,
name: `${['轴承', '联轴器', '密封圈', '电机', '传感器'][i % 5]} ${['6204', 'LX-3', 'O 型', 'Y802', 'PT100'][i % 5]}-${i}`,
spec: `${(i % 4) + 1} 号标准`,
unit: i % 3 === 0 ? '套' : '件',
}))
const columns: BizRemoteTableColumn[] = [
{ prop: 'code', label: '物料编码', width: 110 },
{ prop: 'name', label: '物料名称' },
{ prop: 'spec', label: '规格', width: 100 },
{ prop: 'unit', label: '单位', width: 60, align: 'center' },
]
const value = ref<string>()
const picked = ref<Record<string, unknown> | null>(null)
const request = async ({ keyword, page, pageSize }: { keyword: string; page: number; pageSize: number }) => {
const rows = MATERIALS.filter(
(row) => row.code.includes(keyword.toUpperCase()) || row.name.includes(keyword)
)
return { list: rows.slice((page - 1) * pageSize, page * pageSize), total: rows.length }
}
</script>
<template>
<div style="display: flex; flex-direction: column; gap: 12px; max-width: 560px">
<BizRemoteSelect
v-model="value"
type="table"
:table-columns="columns"
:request="request"
label-field="name"
value-field="code"
placeholder="输入编码 / 名称搜索物料"
@change="(option) => (picked = option?.raw ?? null)"
/>
<span style="font-size: 13px; color: var(--el-text-color-secondary)">
{{ picked ? `已选:${picked.code} · ${picked.name}(${picked.spec})` : '(未选择)' }}
</span>
</div>
</template>
显示代码
选择客户(选中后回显行业副描述)
desc-field 副描述:选中后回显「名称(行业)」;panel-width 定制面板宽。<script setup lang="ts">
/** 场景:选客户——descField 副描述回显 + 面板宽度定制 */
import { ref } from 'vue'
import { BizRemoteSelect } from 'vue3-ep-biz-lib'
import type { BizRemoteTableColumn } from 'vue3-ep-biz-lib'
interface CustomerRow extends Record<string, unknown> {
id: string
name: string
industry: string
level: string
credit: string
}
const CUSTOMERS: CustomerRow[] = Array.from({ length: 38 }, (_, i) => ({
id: `CU${2000 + i}`,
name: `${['杭州', '苏州', '深圳', '成都'][i % 4]}${['智造', '精密', '光电', '数控'][i % 4]}科技${i > 15 ? '(集团)' : ''}`,
industry: ['装备制造', '电子信息', '医疗器械', '汽车零部件'][i % 4],
level: ['A', 'B', 'C'][i % 3],
credit: `${60 + (i % 40)} 万`,
}))
const columns: BizRemoteTableColumn[] = [
{ prop: 'id', label: '客户号', width: 90 },
{ prop: 'name', label: '客户名称' },
{ prop: 'industry', label: '行业', width: 110 },
{ prop: 'level', label: '等级', width: 64, align: 'center' },
]
const value = ref<string>()
const request = async ({ keyword, page, pageSize }: { keyword: string; page: number; pageSize: number }) => {
const rows = CUSTOMERS.filter((row) => row.name.includes(keyword) || row.id.includes(keyword.toUpperCase()))
return { list: rows.slice((page - 1) * pageSize, page * pageSize), total: rows.length }
}
</script>
<template>
<div style="display: flex; flex-direction: column; gap: 12px; max-width: 560px">
<BizRemoteSelect
v-model="value"
type="table"
:table-columns="columns"
:request="request"
label-field="name"
value-field="id"
desc-field="industry"
:panel-width="560"
placeholder="选择客户(选中后回显行业副描述)"
/>
<span style="font-size: 13px; color: var(--el-text-color-secondary)">当前值:{{ value ?? '(未选择)' }}</span>
</div>
</template>
显示代码
编辑回显:默认值在首屏结果中即正确显示 label(隐藏 Option 池命中)。
<script setup lang="ts">
/** 场景:财务选科目——四列科目表(编码 / 名称 / 方向 / 余额方向) */
import { ref } from 'vue'
import { BizRemoteSelect } from 'vue3-ep-biz-lib'
import type { BizRemoteTableColumn } from 'vue3-ep-biz-lib'
interface AccountRow extends Record<string, unknown> {
code: string
name: string
direction: string
category: string
}
const ACCOUNTS: AccountRow[] = [
{ code: '1001', name: '库存现金', direction: '借', category: '资产' },
{ code: '1002', name: '银行存款', direction: '借', category: '资产' },
{ code: '1122', name: '应收账款', direction: '借', category: '资产' },
{ code: '1403', name: '原材料', direction: '借', category: '资产' },
{ code: '1405', name: '库存商品', direction: '借', category: '资产' },
{ code: '1601', name: '固定资产', direction: '借', category: '资产' },
{ code: '2001', name: '短期借款', direction: '贷', category: '负债' },
{ code: '2202', name: '应付账款', direction: '贷', category: '负债' },
{ code: '2211', name: '应付职工薪酬', direction: '贷', category: '负债' },
{ code: '2221', name: '应交税费', direction: '贷', category: '负债' },
{ code: '4001', name: '实收资本', direction: '贷', category: '权益' },
{ code: '6001', name: '主营业务收入', direction: '贷', category: '损益' },
{ code: '6401', name: '主营业务成本', direction: '借', category: '损益' },
{ code: '6601', name: '销售费用', direction: '借', category: '损益' },
{ code: '6602', name: '管理费用', direction: '借', category: '损益' },
{ code: '6603', name: '财务费用', direction: '借', category: '损益' },
]
const columns: BizRemoteTableColumn[] = [
{ prop: 'code', label: '科目编码', width: 100 },
{ prop: 'name', label: '科目名称' },
{ prop: 'direction', label: '余额方向', width: 90, align: 'center' },
{ prop: 'category', label: '类别', width: 80, align: 'center' },
]
const value = ref<string>('2202')
const request = async ({ keyword }: { keyword: string }) => ({
list: ACCOUNTS.filter((row) => row.name.includes(keyword) || row.code.startsWith(keyword)),
total: ACCOUNTS.filter((row) => row.name.includes(keyword) || row.code.startsWith(keyword)).length,
})
</script>
<template>
<div style="display: flex; flex-direction: column; gap: 12px; max-width: 560px">
<BizRemoteSelect
v-model="value"
type="table"
:table-columns="columns"
:request="request"
label-field="name"
value-field="code"
:table-height="260"
placeholder="选择记账科目"
/>
<span style="font-size: 13px; color: var(--el-text-color-secondary)">默认值 2202(应付账款)——编辑回显:隐藏 Option 池命中即正确显示 label</span>
</div>
</template>
显示代码
嵌入 el-form:采购单表单里选供应商,占满 form-item 宽度。
<script setup lang="ts">
/** 场景:采购单表单——table 模式嵌入 el-form 选供应商 */
import { reactive } from 'vue'
import { ElButton, ElForm, ElFormItem, ElInput, ElInputNumber, ElMessage } from 'element-plus'
import { BizRemoteSelect } from 'vue3-ep-biz-lib'
import type { BizRemoteTableColumn } from 'vue3-ep-biz-lib'
interface SupplierRow extends Record<string, unknown> {
id: string
name: string
contact: string
phone: string
}
const SUPPLIERS: SupplierRow[] = Array.from({ length: 30 }, (_, i) => ({
id: `SUP-${100 + i}`,
name: `${['华信', '正大', '联创', '中兴'][i % 4]}${['电子', '五金', '塑胶'][i % 3]}有限公司`,
contact: `联系人${i + 1}`,
phone: `138${String(80000000 + i * 137).slice(0, 8)}`,
}))
const columns: BizRemoteTableColumn[] = [
{ prop: 'id', label: '供应商编号', width: 110 },
{ prop: 'name', label: '名称' },
{ prop: 'contact', label: '联系人', width: 90 },
]
const form = reactive({ supplier: undefined as string | undefined, item: '', qty: 1 })
const request = async ({ keyword, page, pageSize }: { keyword: string; page: number; pageSize: number }) => {
const rows = SUPPLIERS.filter((row) => row.name.includes(keyword) || row.id.includes(keyword.toUpperCase()))
return { list: rows.slice((page - 1) * pageSize, page * pageSize), total: rows.length }
}
const submit = (): void => {
ElMessage.success(`采购单:${form.item || '未填品名'} × ${form.qty},供应商 ${form.supplier ?? '未选'}`)
}
</script>
<template>
<ElForm :model="form" label-width="90px" style="max-width: 620px">
<ElFormItem label="供应商" prop="supplier">
<BizRemoteSelect
v-model="form.supplier"
type="table"
:table-columns="columns"
:request="request"
label-field="name"
value-field="id"
placeholder="选择供应商"
/>
</ElFormItem>
<ElFormItem label="品名">
<ElInput v-model="form.item" placeholder="采购品名" />
</ElFormItem>
<ElFormItem label="数量">
<ElInputNumber v-model="form.qty" :min="1" />
</ElFormItem>
<ElFormItem>
<ElButton type="primary" @click="submit">暂存采购单</ElButton>
</ElFormItem>
</ElForm>
</template>
显示代码
滚动表格到底自动加载下一页
大数据量:500 条库位,表格体内滚动到底自动翻页追加(底部「加载更多」兜底)。
<script setup lang="ts">
/** 场景:大数据量滚动加载——500 条库位,表格体内滚动到底自动翻页追加 */
import { ref } from 'vue'
import { BizRemoteSelect } from 'vue3-ep-biz-lib'
import type { BizRemoteTableColumn } from 'vue3-ep-biz-lib'
interface BinRow extends Record<string, unknown> {
code: string
area: string
capacity: number
}
const BINS: BinRow[] = Array.from({ length: 500 }, (_, i) => ({
code: `BIN-${String(i + 1).padStart(4, '0')}`,
area: `${['A', 'B', 'C', 'D', 'E'][i % 5]} 区`,
capacity: 100 + (i % 9) * 50,
}))
const columns: BizRemoteTableColumn[] = [
{ prop: 'code', label: '库位编码', width: 130 },
{ prop: 'area', label: '库区', width: 90 },
{ prop: 'capacity', label: '容量', width: 90, align: 'right' },
]
const value = ref<string>()
const request = async ({ keyword, page, pageSize }: { keyword: string; page: number; pageSize: number }) => {
const rows = BINS.filter((row) => row.code.includes(keyword.toUpperCase()) || row.area.includes(keyword))
return { list: rows.slice((page - 1) * pageSize, page * pageSize), total: rows.length }
}
</script>
<template>
<div style="display: flex; flex-direction: column; gap: 12px; max-width: 560px">
<BizRemoteSelect
v-model="value"
type="table"
:table-columns="columns"
:request="request"
label-field="code"
value-field="code"
:page-size="20"
:table-height="280"
placeholder="滚动表格到底自动加载下一页"
/>
<span style="font-size: 13px; color: var(--el-text-color-secondary)">当前值:{{ value ?? '(未选择)' }}</span>
</div>
</template>
显示代码
选择门店
不传
table-columns 的兜底列:名称(label-field)+ 编号(value-field)两列。<script setup lang="ts">
/** 场景:默认两列——不传 tableColumns 时的兜底(labelField + valueField 两列) */
import { ref } from 'vue'
import { BizRemoteSelect } from 'vue3-ep-biz-lib'
const value = ref<string>()
const request = async ({ keyword, page, pageSize }: { keyword: string; page: number; pageSize: number }) => {
const all = Array.from({ length: 25 }, (_, i) => ({ name: `门店-${String(i + 1).padStart(2, '0')}`, id: `S${100 + i}` }))
const rows = all.filter((row) => row.name.includes(keyword) || row.id.includes(keyword.toUpperCase()))
return { list: rows.slice((page - 1) * pageSize, page * pageSize), total: rows.length }
}
</script>
<template>
<div style="display: flex; flex-direction: column; gap: 12px; max-width: 560px">
<BizRemoteSelect
v-model="value"
type="table"
:request="request"
label-field="name"
value-field="id"
:panel-width="420"
placeholder="选择门店"
/>
<span style="font-size: 13px; color: var(--el-text-color-secondary)">缺省列:名称(labelField)+ 编号(valueField)</span>
</div>
</template>
显示代码
请输入关键词搜索
nullchange 事件回传完整选项(含 raw 原始行);点 ✕ 清空后回传 null
change 负载:完整选项(含 raw 原始行);点 ✕ 清空回传 null。<script setup lang="ts">
/** 场景:清空与 change 负载——完整原始行回传 / 清空后 null */
import { ref } from 'vue'
import { BizRemoteSelect } from 'vue3-ep-biz-lib'
import type { BizRemoteOption, BizRemoteTableColumn } from 'vue3-ep-biz-lib'
interface DeptRow extends Record<string, unknown> {
id: number
name: string
leader: string
headcount: number
}
const DEPTS: DeptRow[] = Array.from({ length: 12 }, (_, i) => ({
id: 100 + i,
name: `${['研发部', '产品部', '质检部', '市场部'][i % 4]}${i > 7 ? '(二部)' : ''}`,
leader: `${['张', '李', '王', '赵'][i % 4]}${['敏', '强', '磊', '静'][i % 4]}`,
headcount: 8 + i * 3,
}))
const columns: BizRemoteTableColumn[] = [
{ prop: 'id', label: '部门 ID', width: 90 },
{ prop: 'name', label: '部门名称' },
{ prop: 'leader', label: '负责人', width: 90 },
{ prop: 'headcount', label: '人数', width: 70, align: 'right' },
]
const value = ref<number>()
const lastChange = ref<BizRemoteOption | null>(null)
const request = async () => ({ list: DEPTS, total: DEPTS.length })
</script>
<template>
<div style="display: flex; flex-direction: column; gap: 12px; max-width: 560px">
<BizRemoteSelect
v-model="value"
type="table"
:table-columns="columns"
:request="request"
label-field="name"
value-field="id"
@change="(option) => (lastChange = option)"
/>
<pre style="margin: 0; padding: 8px 12px; font-size: 12px; border-radius: 6px; background: var(--el-fill-color-light)">{{ JSON.stringify(lastChange, null, 2) ?? '(未选择)' }}</pre>
<span style="font-size: 12px; color: var(--el-text-color-secondary)">change 事件回传完整选项(含 raw 原始行);点 ✕ 清空后回传 null</span>
</div>
</template>
显示代码
试搜「轴承」与任意无结果词
检索无结果:表格空态 + footer「输入关键词搜索」引导。
<script setup lang="ts">
/** 场景:空态——检索无结果时表格空提示与 footer 引导 */
import { ref } from 'vue'
import { BizRemoteSelect } from 'vue3-ep-biz-lib'
import type { BizRemoteTableColumn } from 'vue3-ep-biz-lib'
const columns: BizRemoteTableColumn[] = [
{ prop: 'code', label: '编码', width: 120 },
{ prop: 'name', label: '名称' },
]
const value = ref<string>()
const request = async ({ keyword }: { keyword: string }) => {
/* 只有包含「轴」的词能命中——试搜其他词看空态 */
const rows = ['轴承 6204', '轴承座 SN-2', '轴用挡圈'].map((name, i) => ({ code: `P-${100 + i}`, name })).filter((row) => row.name.includes(keyword))
return { list: rows, total: rows.length }
}
</script>
<template>
<div style="display: flex; flex-direction: column; gap: 12px; max-width: 560px">
<BizRemoteSelect
v-model="value"
type="table"
:table-columns="columns"
:request="request"
label-field="name"
value-field="code"
:cache="false"
placeholder="试搜「轴承」与任意无结果词"
/>
</div>
</template>
显示代码
单据已锁定
发货仓库(单据锁定后不可改)
禁用:单据锁定后输入与下拉均不可用。
<script setup lang="ts">
/** 场景:禁用与自定义占位——审批锁定后不可改选 */
import { ref } from 'vue'
import { ElSwitch } from 'element-plus'
import { BizRemoteSelect } from 'vue3-ep-biz-lib'
import type { BizRemoteTableColumn } from 'vue3-ep-biz-lib'
const columns: BizRemoteTableColumn[] = [
{ prop: 'name', label: '名称' },
{ prop: 'code', label: '编码', width: 120 },
]
const value = ref<string>()
const locked = ref(true)
const request = async () => ({
list: [
{ code: 'WH-01', name: '华东一号仓' },
{ code: 'WH-02', name: '华南二号仓' },
{ code: 'WH-03', name: '西南三号仓' },
],
total: 3,
})
</script>
<template>
<div style="display: flex; flex-direction: column; gap: 14px; max-width: 560px">
<div style="display: flex; align-items: center; gap: 8px">
<ElSwitch v-model="locked" active-text="单据已锁定" />
</div>
<BizRemoteSelect
v-model="value"
type="table"
:table-columns="columns"
:request="request"
label-field="name"
value-field="code"
:disabled="locked"
placeholder="发货仓库(单据锁定后不可改)"
/>
</div>
</template>
显示代码
Props
| 属性名 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| v-model | string / number / boolean | — | 选中值(id) |
| type | 'select' | 'table' | 'select' | select 下拉列表 / table 下拉内嵌表格 |
| table-columns | BizRemoteTableColumn[] | 名称 + 编号两列 | table 模式列配置(prop / label / width / align) |
| desc-field | string | — | table 模式副描述字段,选中回显在 label 旁 |
| panel-width | number | 520 | table 模式下拉面板宽(px) |
| table-height | number | 300 | table 模式表格高度(px) |
| request | (params) => Promise<{ list, total }> | —(必填) | 远程检索实现;list 行经 label-field / value-field 归一化 |
| label-field | string | 'label' | 选项 label 取值字段 |
| value-field | string | 'value' | 选项 value 取值字段 |
| page-size | number | 20 | 每页条数 |
| debounce | number | 300 | 搜索防抖(ms) |
| cache | boolean | true | 同关键词命中首屏缓存不重复请求 |
| clearable | boolean | true | 可清空 |
| placeholder | string | '请输入关键词搜索' | 占位文案 |
| disabled | boolean | false | 禁用 |
| initial-options | BizRemoteOption[] | — | 编辑回显注入:与检索结果合并去重(不参与分页计数),保证 v-model 值的 label 可回显 |
其余 attrs 透传内部 el-select(multiple / size / popper-class...)。
Events
| 事件名 | 参数 | 说明 |
|---|---|---|
| update:modelValue | (value) | 选中值变化 |
| change | (option / null) | 选中项变化(完整选项对象,清空为 null;table 模式选项附 desc 副描述与 raw 原始行) |
Slots
| 插槽名 | 说明 |
|---|---|
| — | 无自定义插槽(选项经 request 驱动) |
Expose
| 属性/方法 | 说明 |
|---|---|
| reload() | 清缓存并按当前关键词重新检索 |
| clearCache() | 仅清空首屏缓存 |