23 KiB
title | description | date | slug | categories | tags | lastmod | links | |||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
【SearchPanel组件】详细配置文档 | SearchPanel是一个基于Vue的搜索组件,包含各类搜索项和搜索结果表格,支持响应式数据更新 | 2025-02-14T17:44:00+08:00 | SearchPanel组件文档 |
|
|
2025-02-17T20:37:00+08:00 |
|
- 2025.02.17 更新:联想输入框的
fetchSuggestFn
字段支持返回对象数组,支持显示值和绑定值分离。- 2025.02.14 创建本文档。
组件说明
SearchPanel是我封装的一个基于Vue的搜索组件,包含各类搜索项和搜索结果表格,UI设计继承企业微信团队一致风格,支持响应式调用接口实现实时数据更新。具体来说,组件分为三个部分:搜索项(search-items
)、操作项(search-actions
)和搜索结果表格(search-result
)。
效果图:


引入组件
在需要使用SearchPanel的页面中引入组件:
<template>
<div class="record-page">
<!-- SearchPanel -->
<SearchPanel
:fields="fields"
:actions="actions"
:columns="columns"
:fetchTableFn="fetchTableData"
:pageSize="page_size"
showPagination
showTable
autoFetch
@action="onPanelAction"
@select="onSuggestItemSelect"
@change="onFieldChange"
@row-click="onRowClick"
/>
</div>
</template>
<script>
import SearchPanel from '/path/to/SearchPanel.vue';
export default {
components: {
SearchPanel
}
}
</script>
引号内的字符串可以不做更改,在父组件的data()
或methods
中定义为实际值即可。各字段含义如下。
功能字段
fields
:Array
。定义搜索面板中的各个搜索项。每个搜索项可以是普通输入框、联想输入框、日期选择框、单选按钮组、单选下拉列表等。通过配置fields
,可以灵活地定制搜索面板的内容和样式。actions
:Array
。定义搜索面板中的操作按钮。可以配置按钮的显示文本、点击事件等。可以用于提供手动搜索、导出结果等功能。columns
:Array
。定义表格的列配置。每个列配置项包括列的标识、显示标签、样式等。用于展示搜索结果的表格结构。fetchTableFn
:Function
。用于获取表格数据的函数。推荐值为一个异步函数,接收搜索条件、当前页码、每页条数等参数,并返回表格数据和总条数。pageSize
:Number
。定义每页显示的条数。用于分页控制。showPagination
:Boolean
。是否显示分页控件。用于控制表格的分页显示。showTable
:Boolean
。是否显示表格。用于控制搜索结果的显示。autoFetch
:Boolean
。是否在组件加载时自动获取数据。通常用于初始化时自动加载数据。
事件字段
@select
:Function
。监听联想输入框的选项选择事件。用于处理用户选择联想项后的逻辑。@change
:Function
。监听搜索项值变化事件。用于处理用户输入或选择后的逻辑。@action
:Function
。用于处理搜索面板操作按钮的点击事件。@row-click
:Function
。用于处理表格行点击事件。
组件结构
总体配置
配置方式
data()
配置
以前述引入时的字段名为示例,在父组件的data()
中配置以下字段:
data() {
return {
fields: [], // Object 数组,此字段名与引入时保持一致,每个对象代表一个搜索项
page_size: 10, // Number,此字段名与引入时保持一致,用于每页显示的条数
actions: [], // Object 数组,此字段名与引入时保持一致,每个对象代表一个操作按钮
columns: [], // Object 数组,此字段名与引入时保持一致,每个对象代表一个表格列
autoFetch: true, // Boolean,是否在组件加载时自动获取数据
showTable: true, // Boolean,是否显示表格
showPagination: true // Boolean,是否显示分页控件
}
}
各搜索项的相关参数在data()
中的fields
字段中按顺序配置,以普通输入框为例,配置如下:
export default {
data() {
return {
fields: [
{
type: "text",
key: "name",
label: "姓名",
placeholder: "请输入姓名"
}
]
}
}
}
各操作项的相关参数在data()
中的actions
字段中按顺序配置,示例配置如下:
export default {
data() {
return {
actions: [
{
key: "search",
label: "搜索"
}
]
}
}
}
所有类型的搜索项、操作项都支持以下通用配置:
{
key: "string", // 必填,字段标识
label: "string", // 必填,字段标签文本
itemClass: "string", // 可选,整个搜索项的自定义类名
titleClass: "string", // 可选,标签文本的自定义类名
titleStyle: "object", // 可选,标签文本的内联样式
wrapperClass: "string" // 可选,输入区域的包装容器类名
}
这里解释一下itemClass
是什么。看这段组件源码你就明白了:
<div class="search-area" :class="customAreaClass">
<template v-for="(field, idx) in fields" :key="idx">
<div
class="search-item"
:class="[field.itemClass]"
>
<!-- ... -->
</div>
</template>
</div>
itemClass
是每个搜索项或操作项最外层的容器类名。
methods
配置
以前述引入时的字段名为示例,在父组件的methods
中配置以下方法:
methods: {
async fetchTableData(searchValues, currentPage, pageSize) {
// 实现获取表格数据的逻辑
},
onPanelAction(actionKey) {
// 按钮被点击时的操作
},
onSuggestItemSelect({ key, value }) {
// 用户选中了联想项的操作
},
onFieldChange({ key, value }) {
// 字段值变化时的操作
},
onRowClick(row) {
// 表格行被点击时的操作
}
}
fetchTableData
:Function
。用于获取表格数据的函数。此函数名与引入时保持一致,推荐值为一个异步函数。- 形参:
searchValues
:Object
。搜索条件。currentPage
:Number
。当前页码。pageSize
:Number
。每页显示的条数。
- 返回值:
Promise<{ list: Array, total: number }>
。返回一个 Promise,resolve 的对象包含两个属性表格数据和总条数。
- 形参:
onPanelAction
:Function
。用于处理搜索面板操作按钮的点击事件。- 形参:
actionKey
:String
。点击的按钮的标识。
- 形参:
onSuggestItemSelect
:Function
。用于处理联想输入框的选项选择事件。- 形参:
{ key, value }
:Object
。选中的联想项的标识和值。
- 形参:
onFieldChange
:Function
。用于处理搜索项值变化事件。- 形参:
{ key, value }
:Object
。变化的搜索项的标识和值。
- 形参:
onRowClick
:Function
。用于处理表格行点击事件。- 形参:
row
:Object
。点击的行数据。
- 形参:
搜索项配置
搜索项包含常用的5类输入组件:普通输入框、联想输入框、日期选择框、单选按钮组、单选下拉列表。
普通输入框
普通输入框是最常用的输入组件,type
字段为text
。示例:
{
type: "text", // 必填,字段类型:'text'
key: "name", // 必填,字段标识,也是发起请求时对应的参数名
label: "姓名", // 必填,字段标签文本
placeholder: "请输入姓名", // 可选,输入框的占位文本
}
如果要增加自定义样式,可以增加itemClass
、titleClass
、titleStyle
、wrapperClass
字段。如前所述,这些字段适用于所有类型的搜索项和操作项,后面不再赘述。
{
type: "text",
key: "name",
label: "姓名",
placeholder: "请输入姓名",
itemClass: "name-input", // 可选,整个搜索项的自定义类名
titleClass: "name-label", // 可选,标签文本的自定义类名
titleStyle: {
color: "#333",
"margin-right": "10px"
}, // 可选,标签文本的内联样式
wrapperClass: "name-wrapper" // 可选,输入区域的包装容器类名
}
普通输入框另有2个字段:inputClass
和inputStyle
,用于自定义输入框的样式。
{
inputClass: "name-input-inner", // 可选,输入框的自定义类名
inputStyle: {
color: "#333",
"border-radius": "5px"
} // 可选,输入框的内联样式
}
当然,我们可以增加一个示例,假设接口返回的是新的格式,支持显示值和绑定值分离。
联想输入框
联想输入框支持输入文字并在输入框下方显示联想项,type
字段为suggest
。示例:
{
type: "suggest", // 必填,字段类型:'suggest'
key: "senderData", // 必填,字段标识,也是发起请求时对应的参数名
label: "发送人", // 必填,字段标签文本
placeholder: "请输入发送人", // 可选,输入框的占位文本
inputClass: "sender-input", // 可选,输入框的自定义类名
inputStyle: {
color: "#333",
"border-radius": "5px"
}, // 可选,输入框的内联样式
fetchSuggestFn: async (value) => {
// 必填,用于获取联想项的函数。
}
}
其中fetchSuggestFn
字段是用于从API获取联想项的函数。
-
形参:
val
:String
。输入框的值。
-
返回值:
Promise<Array>
。返回一个 Promise,resolve 的对象为联想项的数组。每个联想项可以是一个字符串或一个对象,支持以下两种格式:- 字符串数组:直接返回字符串数组,显示值和绑定值相同。
- 对象数组:返回对象数组,支持显示值和绑定值分离。对象格式为
{ label: string, value: any }
。
假设有一个接口POST /api/sender
,请求体为{ "searchTerm": "三" }
,返回{ "results": ["张三", "李三", "王三"] }
,则可以这样实现:
fetchSuggestFn: async (val) => {
try {
const response = await fetch(`/api/sender`, {
method: "POST",
body: JSON.stringify({ searchTerm: val })
})
const data = await response.json()
// 返回字符串数组
return data?.results || []
} catch (error) {
console.error("获取联想项失败", error)
return []
}
}
或者用axios
实现:
fetchSuggestFn: async (val) => {
try {
const response = await axios.post('/api/sender', { searchTerm: val })
// 返回字符串数组
return response.data?.results || []
} catch (error) {
console.error("获取联想项失败", error)
return []
}
}
假设接口返回的是对象数组(每个对象是键值对的形式),则支持显示值和绑定值分离,例如:
{
"results": [
{ "label": "张三 <zhangsan@example.com>", "value": "zhangsan" },
{ "label": "李四 <lisi@example.com>", "value": "lisi" },
{ "label": "王五 <wangwu@example.com>", "value": "wangwu" }
]
}
则可以如下实现:
fetchSuggestFn: async (val) => {
try {
const response = await axios.post('/api/sender', { searchTerm: val })
// 返回对象数组,支持显示值和绑定值分离
return response.data?.results || []
} catch (error) {
console.error("获取联想项失败", error)
return []
}
}
日期选择框
日期选择框支持选择日期,type
字段为date
。其返回值为一个字符串,格式为YYYY年MM月DD日
。示例:
{
type: "date", // 必填,字段类型:'date'
key: "beginDate", // 必填,字段标识,也是发起请求时对应的参数名
label: "开始日期", // 必填,字段标签文本
inputClass: "date-input", // 可选,输入框的自定义类名
inputStyle: {
color: "#333",
"border-radius": "5px"
}, // 可选,输入框的内联样式
dateConfig: {
min: '2025年01月01日', // 可选,最小日期
max: '2025年12月31日' // 可选,最大日期
// 注:如果不设置min和max,则默认max是当前日期
defaultValue: '2025年02月01日' // 可选,默认日期
}
}
也可以动态获取相关值,比如设置defaultValue
为当前日期:
dateConfig: {
defaultValue: (() => {
const date = new Date();
return `${date.getFullYear()}年${String(date.getMonth() + 1).padStart(2, '0')}月${String(date.getDate()).padStart(2, '0')}日`;
})()
}
单选按钮组
单选按钮组支持单选按钮组,type
字段为radioGroup
。示例:
{
type: "radioGroup", // 必填,字段类型:'radioGroup'
key: "statusFilter", // 必填,字段标识,也是发起请求时对应的参数名
label: "操作状态", // 必填,字段标签文本
buttonClass: "status-button", // 可选,按钮的统一自定义类名
buttonStyle: {
color: "#333",
"border-radius": "5px"
}, // 可选,按钮的统一内联样式
options: [
{ label: "全部", value:'' },
{ label: "成功", value:'true' },
{ label: "失败", value:'false' },
] // 必填,单选按钮组选项数组,label是显示文本,value是请求参数值
}
单选按钮组支持为每个按钮增加自定义样式,在options
字段的每个对象中添加customClass
和customStyle
字段:
{
options: [
{ label: "全部", value:'' },
{ label: "成功", value:'true', customClass: "success-button", customStyle: { "margin-right": "10px" } },
{ label: "失败", value:'false', customClass: "failed-button", customStyle: { "margin-right": "10px" } },
]
}
单选下拉列表
单选下拉列表支持点击显示下拉列表,type
字段为select
。示例:
{
type: "select", // 必填,字段类型:'select'
key: "sortType", // 必填,字段标识,也是发起请求时对应的参数名
label: "排序方式", // 必填,字段标签文本
dropdownClass: "sort-dropdown", // 可选,下拉选项的统一自定义类名
dropdownStyle: {
color: "#333",
"border-radius": "5px"
}, // 可选,下拉选项的统一内联样式
options: [
{ label: "最近记录在前", value:'time_desc' },
{ label: "最早记录在前", value:'time_asc' },
] // 必填,单选下拉列表选项数组,label是显示文本,value是请求参数值
}
单选下拉列表支持为每个选项增加自定义类,在options
字段的每个对象中添加customClass
字段:
{
options: [
{ label: "最近记录在前", value:'time_desc', customClass: "time-desc-option" },
{ label: "最早记录在前", value:'time_asc', customClass: "time-asc-option" },
]
}
操作项配置
操作项即为按钮组件,在data()
中的actions
字段中配置。示例:
{
key: "export", // 必填,按钮点击事件时监测的参数
label: "导出记录", // 必填,按钮显示文本
btnClass: "export-btn" // 可选,按钮的自定义类名
}
表格配置
表格的列在data()
中的columns
字段中配置。示例:
data() {
return {
columns: [
{ key: "sender", label: "发件人", style: { "width": "180px" } },
{ key: "subject", label: "主题", style: { "width": "200px" } },
{ key: "timestamp", label: "发送时间", style: { "width": "120px" } },
{ key: "status", label: "操作状态", style: { "width": "80px" } },
]
}
}
key
参数是从API接收到的数据中的字段名。label
参数是表格列的显示文本。style
参数是对应列的内联样式。
事件配置
事件逻辑在methods
中配置。
自动获取表格数据事件
获取表格数据在fetchTableData
方法中配置。注意:此函数名应为父组件引入时配置的fetchTableFn
字段名。
methods: {
async fetchTableData(searchValues, currentPage, pageSize) {
// 实现获取表格数据的逻辑
}
}
- 形参:
searchValues
:Object
。搜索条件。currentPage
:Number
。当前页码。pageSize
:Number
。每页显示的条数。
- 返回值:
Promise<{ list: Array, total: number }>
。返回一个 Promise,resolve 的对象包含两个属性表格数据和总条数。
注:此逻辑假定后端API接口支持分页,如果后端不支持分页,则需要自行实现分页逻辑。
假设我们有一个接口POST /api/email_list
,请求体示例如下:
{
"senderData": "5",
"themeData": "",
"beginDate": "2025-02-06T16:00:00.000Z",
"endDate": "2025-02-14T15:59:59.999Z",
"statusFilter": "true",
"sortType": "time_desc",
"page": 1, // 当前页码
"limit": 8 // 每页显示的条数
}
返回的结果如:
{
"results": [
{
"sender": "5@example.com",
"subject": "Theme 5",
"timestamp": "2025-02-13T10:27:36.430Z",
"status": true
},
{
"sender": "15@example.com",
"subject": "Theme 15",
"timestamp": "2025-02-13T00:27:36.430Z",
"status": true
},
{
"sender": "25@example.com",
"subject": "Theme 25",
"timestamp": "2025-02-12T14:27:36.430Z",
"status": true
},
{
"sender": "50@example.com",
"subject": "Theme 50",
"timestamp": "2025-02-11T13:27:36.430Z",
"status": true
}
],
"total": 4,
"currentPage": 1,
"totalPages": 1
}
那么我们可以这样实现fetchTableData
方法:
async fetchTableData(searchValues, currentPage, pageSize) {
try {
// 抽出外部字段
let { senderData, themeData, beginDate, endDate, statusFilter, sortType } = searchValues
// 保证 beginDate<=endDate
if (beginDate && endDate) {
// 比较日期大小
const beginTime = new Date(beginDate.replace(/年|月|日/g, '')).getTime()
const endTime = new Date(endDate.replace(/年|月|日/g, '')).getTime()
if (endTime < beginTime) {
// 交换开始和结束日期
const temp = beginDate
beginDate = endDate
endDate = temp
}
}
// 转换成ISO日期字符串
const parseDate = (dateStr) => {
if (!dateStr) return null;
const [year, month, day] = dateStr.split(/年|月|日/);
return new Date(Number(year), Number(month) - 1, Number(day));
}
const beginDateISO = beginDate ? new Date(parseDate(beginDate).setHours(0,0,0,0)).toISOString() : ''
const endDateISO = endDate ? new Date(parseDate(endDate).setHours(23,59,59,999)).toISOString() : ''
const body = {
senderData: senderData || '',
themeData: themeData || '',
beginDate: beginDateISO,
endDate: endDateISO,
statusFilter: statusFilter || '',
sortType: sortType || 'time_desc',
page: currentPage,
limit: pageSize
}
// 发起请求
const resp = await axios.post('https://test.ember.ac.cn/api/email_list', body)
// 返回结果
const results = resp.data?.results || []
const total = resp.data?.total || 0
return {
list: results.map(item => {
// item.status是boolean -> 转成 '成功' / '失败'
// item.timestamp转成易读格式
const date = new Date(item.timestamp)
const formattedDate = `${date.getFullYear()}年${String(date.getMonth() + 1).padStart(2, '0')}月${String(date.getDate()).padStart(2, '0')}日 ${String(date.getHours()).padStart(2, '0')}:${String(date.getMinutes()).padStart(2, '0')}`
return {
...item,
status: item.status === true ? '成功' : '失败',
timestamp: formattedDate
}
}),
total: total
}
} catch (err) {
console.error(err)
return { list: [], total: 0 }
}
}
这样配置后,如果打开了autoFetch
选项,则组件会自动监听搜索条件的变化,每次变化时都会自动调用fetchTableData
方法来更新表格数据。如果没打开autoFetch
,则可以在按钮点击事件中手动更新表格数据。
按钮点击事件
按钮点击事件在@action
事件中配置。在前述的引入中,我们在onPanelAction
方法中配置。示例:
methods: {
onPanelAction(actionKey) {
// 实现按钮点击事件的逻辑
if (actionKey === 'export') {
alert('导出按钮被点击了!')
// 导出表格数据
}
}
}
用户选中联想项事件
注意:自动更新表格数据只需要打开autoFetch
选项,不需要再次配置onSuggestItemSelect
事件。
用户选中联想项事件的逻辑在@select
事件中配置。在前述的引入中,我们在onSuggestItemSelect
方法中配置。示例:
methods: {
onSuggestItemSelect({ key, value }) {
console.log(`用户选中联想项:${value},组件为:${key}`)
// 实现用户选中联想项的其他逻辑
}
}
用户输入内容变化事件
注意:自动更新表格数据只需要打开autoFetch
选项,不需要再次配置onFieldChange
事件。
用户输入内容变化事件的逻辑在引入组件时的@change
事件配置。在前述的引入中,我们应该在onFieldChange
方法中配置。示例:
methods: {
onFieldChange({ key, value }) {
console.log(`用户输入内容变化为:${value},组件为:${key}`)
// 实现用户输入内容变化的其他逻辑
}
}
用户点击表格特定行事件
用户点击表格特定行事件的逻辑在@row-click
事件中配置。在前述的引入中,我们在onRowClick
方法中配置。示例:
methods: {
onRowClick(row) {
alert(`用户点击表格,记录:${JSON.stringify(row)}`)
// 实现用户点击表格特定行的其他逻辑
}
}