Vuejs2 Bootstrap Vue中的智能过滤器
我想知道是否有任何方法可以在Bootstrap Vue表中实现Intelligent DataTables过滤器,我到处都搜索过,但是我没有找到任何功能解决方案可以在我的项目中实现。Vuejs2 Bootstrap Vue中的智能过滤器,vuejs2,datatables,bootstrap-vue,Vuejs2,Datatables,Bootstrap Vue,我想知道是否有任何方法可以在Bootstrap Vue表中实现Intelligent DataTables过滤器,我到处都搜索过,但是我没有找到任何功能解决方案可以在我的项目中实现。 我们需要这个组件,所以我们创建了它。希望有帮助: SearchTable.vue <template> <div class="search-table h-100 justify-content-center align-items-center" v-bind:class=
我们需要这个组件,所以我们创建了它。希望有帮助: SearchTable.vue
<template>
<div class="search-table h-100 justify-content-center align-items-center"
v-bind:class="{row: data.length === 0}" v-if="isMounted">
<div v-if="data.length > 0">
<div class="d-flex justify-content-between">
<!-- main search -->
<b-input-group size="xs">
<b-form-input v-model="searchInput"></b-form-input>
<b-input-group-append is-text>
<b-icon icon="search"></b-icon>
</b-input-group-append>
</b-input-group>
</div>
<div class="d-flex justify-content-between mt-2 mb-0">
<b-button-group>
<!-- dropdown -->
<b-dropdown id="col-dropdown" class="col-dropdown" no-flip text="Visibilité">
<b-dropdown-item :key="field.key" class="p-0" style="padding: 0" v-for="field in fields"
v-if="field.key !== 'action'">
<div @click.stop="onDropdownClick(field.key)"
class="checkbox-wrapper">
<b-form-checkbox
:checked="isColumnDisplayed(field.key)"
disabled
>
{{ field.label || field.key }}
</b-form-checkbox>
</div>
</b-dropdown-item>
</b-dropdown>
<b-button :variant="noneOfSearchMethodIsUsed ? '' : 'danger'" @click="cancelFilters">Enlever filtre</b-button>
<!-- dropdown action groupées -->
<slot name="groupped-actions"></slot>
</b-button-group>
<div align="right" style="display: inline-flex">
<span style="margin: 4px;">Afficher</span>
<b-form-select
v-model="perPage"
:options="perPageOptions"
size="sm"
></b-form-select>
<span style="margin: 4px;">éléments</span>
</div>
</div>
<div class="d-flex justify-content-between mt-0 mb-2">
<span style="margin-top: 5px;">{{ buildInformationLine }}</span>
<!-- pagination -->
<b-pagination
:per-page="perPage"
:total-rows="formattedData.length"
align="right"
class="my-0 mt-1"
size="sm"
v-model="currentPage"
></b-pagination>
</div>
<!-- TABLE -->
<b-table
:current-page="currentPage"
:fields="fieldsToShow"
:items="formattedData"
:per-page="perPage"
foot-clone
no-footer-sorting
primary-key="id"
:sticky-header="true"
responsive
striped
>
<!-- action col template -->
<template
v-if="!!$scopedSlots.action"
v-slot:cell(action)="row">
<slot name="action" v-bind="row.item"></slot>
</template>
<!-- html escape template -->
<template v-slot:cell()="data">
<span v-html="data.value"></span>
</template>
<!-- footer -->
<template v-slot:foot()="data">
<input :value="getFieldFromKey(data.column).searchVal"
@input="setFieldSearchValue(data.column, $event.target.value)"
v-if="getFieldFromKey(data.column).key !== 'action'"
class="w-100"
placeholder="Recherche">
</template>
</b-table>
<div class="d-flex justify-content-between mt-0">
<span style="margin-top: 5px;">{{ buildInformationLine }}</span>
<!-- pagination -->
<b-pagination
:per-page="perPage"
:total-rows="formattedData.length"
align="right"
class="my-0 mt-1"
size="sm"
v-model="currentPage"
></b-pagination>
</div>
</div>
<div v-else>
<p>Aucun résultat</p>
</div>
</div>
</template>
<script lang="ts">
import { Component, Prop, Vue } from 'vue-property-decorator'
import BvTableField from '../../interfaces/BvTableField'
enum SearchFusionMethod {
Union = 'union',
Intersection = 'intersection',
}
interface FieldsInteractiveInterface extends BvTableField {
searchVal: string
stickyColumn: boolean
}
@Component
export default class SearchTable extends Vue {
// The array containing the data objects
@Prop(Array) readonly data!: any[]
// The array containing the info of each column. key must be equal to key in object data
@Prop(Array) readonly fields!: BvTableField[]
@Prop({default: SearchFusionMethod.Intersection}) readonly searchFusionMethod!: SearchFusionMethod
@Prop({default: 'highlight'}) readonly highlighterClass!: string
mainHighlighterClass: string = this.highlighterClass
@Prop({default: 'field-highlight'}) readonly fieldHighlighterClass!: string
currentPage = 1
perPage = 10
perPageOptions = [10, 25, 50, 100]
searchInput = ''
isMounted = false
// Contains the value of each column search field
fieldsInteractive: FieldsInteractiveInterface[] = []
// ---
mainHilightColor: string = 'yellow'
fieldHilightColor: string = 'orange'
get fieldsToShow(): BvTableField[] {
return this.fieldsInteractive.filter(field => {
return field.display
})
}
get noneColumnSearchFieldIsUsed(): boolean {
return this.numberOfSearchFieldsUsed === 0
}
get numberOfSearchFieldsUsed(): number {
return this.fieldsInteractive.reduce((count: number, field) => {
return count + (field.searchVal !== '' ? 1 : 0)
}, 0)
}
// (01), (10)
get exactlyOneSearchMethodIsUsed(): boolean {
return (this.searchInput !== '' && this.noneColumnSearchFieldIsUsed) || (this.searchInput === '' && !this.noneColumnSearchFieldIsUsed)
}
// (00)
get noneOfSearchMethodIsUsed(): boolean {
return (this.searchInput === '' && this.noneColumnSearchFieldIsUsed)
}
// (11)
get bothSearchMethodsAreUsed(): boolean {
return (this.searchInput !== '' && !this.noneColumnSearchFieldIsUsed)
}
get onlyMainSearchIsUsed(): boolean {
return (this.searchInput !== '' && this.noneColumnSearchFieldIsUsed)
}
get onlyFieldSearchIsUsed(): boolean {
return (this.searchInput === '' && !this.noneColumnSearchFieldIsUsed)
}
get buildInformationLine(): string {
const txt: String[] = []
txt.push("Affichage de l'élément")
txt.push(this.formattedData.length === 0 ? '0' : (((this.currentPage-1) * this.perPage)+1).toString())
txt.push('à')
txt.push((this.currentPage * this.perPage < this.formattedData.length ? this.currentPage * this.perPage : this.formattedData.length).toString())
txt.push('sur')
txt.push((this.formattedData.length).toString())
txt.push('éléments')
if (this.formattedData.length < this.data.length) {
txt.push('(filtré de')
txt.push((this.data.length).toString())
txt.push('éléments au total)')
}
return txt.join(' ')
}
// Data with
get formattedData() {
const mapped = this.data
.map((item: any) => {
const itemWithHighlight: any = {}
this.fields.forEach(field => {
itemWithHighlight[field.key] = this.replaceBySearch(field.key, item[field.key])
})
return itemWithHighlight
})
return mapped
.filter((item: any) => {
// (searchInput,columnSearchField)
// If there is no filter at all, return the row (00)
if (this.noneOfSearchMethodIsUsed) return true
let countFromMainHighlight = 0
let countFromFieldHighlight = 0
// loop through each field
for (const [key, col] of Object.entries(item)) {
if (!this.fieldsInteractive[this.fieldsInteractive.findIndex(x => x.key === key)].display) continue // Only search in displayed column
if (typeof col !== 'string') continue // only check in string values
if (this.onlyMainSearchIsUsed) {
// if only one of the search method has been used, return anything having a 'highlight' class (01), (10)
if (col.includes('fromMainSearch') || col.includes(this.fieldHighlighterClass)) {
return true
}
} else {
// if both of the search method have been used, filter according to the searchFusionMethod (11)
if (this.searchFusionMethod === SearchFusionMethod.Intersection) {
// TODO: search only in class attribute of markup (faster)
if (col.includes('fromMainSearch')) {
countFromMainHighlight++
}
if (col.includes('fromFieldSearch')) {
countFromFieldHighlight++
}
} else if (this.searchFusionMethod === SearchFusionMethod.Union) {
if (col.includes(`<span class="${this.highlighterClass}`)) {
// TODO
return true
}
}
}
}
// determine whether we keep the row
if (this.bothSearchMethodsAreUsed) {
return countFromMainHighlight > 0 && countFromFieldHighlight === this.numberOfSearchFieldsUsed
} else {
if (this.onlyMainSearchIsUsed) {
return countFromFieldHighlight > 0
} else if (this.onlyFieldSearchIsUsed) {
return countFromFieldHighlight === this.numberOfSearchFieldsUsed
}
}
})
}
isColumnDisplayed(key: string) {
const field = this.getFieldFromKey(key)
return field.display
}
setFieldSearchValue(key: string, searchVal: string) {
const index = this.fieldsInteractive.findIndex(field => field.key === key)
if (index === -1) throw new DOMException('Key not found')
Vue.set(this.fieldsInteractive, index, {
...this.fieldsInteractive[index],
searchVal: searchVal
})
// this.fieldsInteractive[index].searchVal = searchVal
}
mounted() {
// programatically add action column if slot given
if (!!this.$scopedSlots.action) {
const fieldAction = {key: 'action'}
this.fields.push(fieldAction)
}
// init column search values
this.fields.forEach(field => {
if (field.key === 'action') {
this.fieldsInteractive.unshift({
...field,
searchVal: '',
sortable: false,
display: field.display ?? true,
stickyColumn: true
})
} else {
this.fieldsInteractive.push({
...field,
searchVal: '',
sortable: field.sortable ?? true,
display: field.display ?? true,
stickyColumn: false
})
}
})
this.isMounted = true
}
onDropdownClick(key: string) {
for (const index in this.fieldsInteractive) {
if (this.fieldsInteractive[index].key === key) {
this.fieldsInteractive[index].display = !this.fieldsInteractive[index].display // toggle
return
}
}
}
private cancelFilters(): void {
this.fieldsInteractive = this.fieldsInteractive.map((field) => {
field.searchVal = ''
return field
})
this.searchInput = ''
}
private getFieldFromKey(key: string): FieldsInteractiveInterface {
const f = this.fieldsInteractive.find(field => field.key === key)
if (f === undefined) {
throw new DOMException('Key not found')
}
return f
}
private replaceBySearch(key: string, str: string | any) {
if ((this.searchInput === '' && this.noneColumnSearchFieldIsUsed)
|| str === undefined || str === null) return str
str = String(str)
// main search bar
if (this.exactlyOneSearchMethodIsUsed || this.bothSearchMethodsAreUsed) {
const regexMain: RegExp | undefined = this.searchInput !== '' ? new RegExp(`${this.searchInput}`, 'i') : undefined
const regexField: RegExp | undefined = this.getFieldFromKey(key).searchVal !== '' ? new RegExp(`${this.getFieldFromKey(key).searchVal}`, 'i') : undefined
const matchMain: string[] | null = regexMain ? (str).match(regexMain) : null
const matchField: string[] | null = regexField ? (str).match(regexField) : null
if (matchMain || matchField) {
str = this.surroundWithHilightClass(str, matchMain, matchField)
}
}
return str
}
// https://stackoverflow.com/questions/1144783/how-can-i-replace-all-occurrences-of-a-string
// replace only if not already contains a highlight class
/**
* @param str string to be surrounded
* @param findMain what is matching with main search
* @param findField what is matching with field search
*/
private surroundWithHilightClass(str: string, findMain: string[] | null, findField: string[] | null) {
const main: string | null = findMain && findMain.length > 0 ? findMain[0] : null
const field: string | null = findField && findField.length > 0 ? findField[0] : null
str = String(str)
// if a search is in another search, put two classes
if (field && main?.includes(field)) {
str = str.replace(new RegExp(main, 'g'), `<span class="${this.mainHighlighterClass} fromFieldSearch fromMainSearch">${main}</span>`)
} else if (main && field?.includes(main)) {
str = str.replace(new RegExp(field, 'g'), `<span class="${this.mainHighlighterClass} fromMainSearch fromFieldSearch">${field}</span>`)
} else {
// here we are sur the highlightning will be separated (this prevents having span in span)
if (main) {
str = str.replace(new RegExp(main, 'g'), `<span class="${this.mainHighlighterClass} fromMainSearch">${main}</span>`)
}
if (field) {
str = str.replace(new RegExp(field, 'g'), `<span class="${this.fieldHighlighterClass} fromFieldSearch">${field}</span>`)
}
}
return str
}
}
</script>
<style lang="scss">
.search-table {
div {
p {
color: gray;
text-align: center;
}
}
span.fromFieldSearch {
background-color: orange; // not defined : var(--main-highlighter-class);
}
/* Why this overrides fromFielSearch even if fromFieldSearch appear after in class order ? */
span.fromMainSearch {
background-color: yellow; // not defined : var(--field-highlighter-class);
}
span.field-highlight {
background-color: orange;
}
.col-dropdown {
.dropdown-item {
padding: 0 !important;
}
}
.checkbox-wrapper {
padding: 4px 24px;
width: 100%;
}
.custom-control-input[disabled] ~ .custom-control-label, .custom-control-input:disabled ~ .custom-control-label {
color: #000 !important;
}
.b-table-sticky-header > .table.b-table > thead > tr > th {
top: -2px !important;
}
.b-table-sticky-header {
max-height: calc(125vh - 400px) !important;
}
.b-table-sticky-header > .table.b-table > tfoot > tr > th {
position: sticky;
bottom: 0;
background-color: white;
z-index: 0;
}
th.b-table-sticky-column {
z-index: 4 !important;
}
}
</style>
{{field.label | | field.key}
恩列弗过滤器
阿菲舍尔
勒芒
{{buildInformationLine}
{{buildInformationLine}
奥昆雷苏尔塔特酒店
从“Vue属性装饰器”导入{Component,Prop,Vue}
从“../../interfaces/BvTableField”导入BvTableField
枚举搜索融合方法{
联合='联合',
交叉点='交叉点',
}
接口字段插入活动接口扩展表字段{
searchVal:string
stickyColumn:布尔值
}
@组成部分
导出默认类SearchTable扩展Vue{
//包含数据对象的数组
@道具(数组)只读数据!:任意[]
//包含每列信息的数组。key必须等于对象数据中的key
@属性(数组)只读字段!:BvTableField[]
@Prop({default:SearchFusionMethod.Intersection})只读SearchFusionMethod!:SearchFusionMethod
@Prop({default:'highlight'})只读highlighterClass!:字符串
mainHighlighterClass:string=this.highlighterClass
@属性({default:'fieldhighlighter'})只读字段HighlighterClass!:字符串
当前页面=1
每页=10
perPageOptions=[10,25,50,100]
searchInput=“”
isMounted=错误
//包含每个列搜索字段的值
fieldsInteractive:FieldsInteractiveInterface[]=[]
// ---
mainhillightcolor:string='yellow'
fieldHilightColor:string='orange'
get fieldsToShow():BvTableField[]{
返回此.fieldsInteractive.filter(字段=>{
返回字段。显示
})
}
get-noneColumnSearchFieldIsUsed():布尔值{
返回此值。numberOfSearchFieldsUsed==0
}
get numberOfSearchFieldsUsed():number{
返回此.fieldsInteractive.reduce((计数:数字,字段)=>{
返回计数+(field.searchVal!=''1:0)
}, 0)
}
// (01), (10)
get exactlyOneSearchMethodIsUsed():布尔值{
return(this.searchInput!=''&&this.noneColumnSearchFieldIsUsed)| |(this.searchInput==''&&this.noneColumnSearchFieldIsUsed)
}
// (00)
获取非OfSearchMethodUsed():布尔值{
返回(this.searchInput==''&&this.noneColumnSearchFieldIsUsed)
}
// (11)
get bothSearchMethodsAreUsed():布尔值{
返回(this.searchInput!=''&&!this.noneColumnSearchFieldIsUsed)
}
仅获取mainseArchisused():布尔值{
返回(this.searchInput!=''&&this.noneColumnSearchFieldIsUsed)
}
仅获取字段searchisused():布尔值{
返回(this.searchInput==''&&!this.noneColumnSearchFieldIsUsed)
}
获取buildInformationLine():字符串{
常量txt:String[]=[]
txt.push(“附件”)
txt.push(this.formattedData.length==0?'0':((this.currentPage-1)*this.perPage)+1.toString())
txt.push('a')
txt.push((this.currentPage*this.perPage{
const itemWithHighlight:any={}
this.fields.forEach(field=>{
itemWithHighlight[field.key]=this.replaceBySearch(field.key,item[field.key])
})
返回带有突出显示的项目
})
返回映射
.过滤器((项目:任何)=>{
//(searchInput,columnSearchField)
//如果根本没有筛选器,则返回行(00)
如果(this.noneofSearchMethodUsed)返回true
让CountFromMain高亮显示=0
让countFromFieldHighlight=0
//循环遍历每个字段
for(对象条目的常量[键,列]){
如果(!this.fieldsInteractive[this.fieldsInteractive.findIndex(x=>x.key==key)].display)继续//仅在显示列中搜索
如果(typeof col!=“string”)继续//只签入字符串值
如果(仅使用此项){
//如果只使用了一个搜索方法,则返回任何具有“highlight”类(01)、(10)的内容
if(col.includes('fromMainSearch')| | col.includes(this.fieldHighlighterClass)){
返回真值
}
}否则{
//如果两种搜索方法都已使用,请根据searchFusionMethod(11)进行过滤
if(this.searchFusionMethod