基本自动完成控件

This commit is contained in:
gary.fu
2024-01-04 18:28:22 +08:00
parent 2937c6c3f3
commit 144d68bc0c
6 changed files with 345 additions and 4 deletions

View File

@@ -99,6 +99,10 @@ html, body, #app, .index-container {
.form-edit-width-100 {
width:100%
}
.autocomplete-table .el-table__cell{
padding: 3px 0;
cursor: pointer;
}
/**
* slide-fade动画

View File

@@ -0,0 +1,267 @@
<script setup>
/**
* @typedef {Object} CommonAutocompleteOption 自动完成的配置信息
* @property {[CommonTableColumn]} columns 表格显示列配置
* @property {string} emptyMessage 没有数据的提示信息
* @method searchMethod 搜索方法
*/
/**
* @typedef {Object} CommonSelectPageOption 默认选择页配置
*/
/**
* @typedef {Object} CommonAutocompleteProps
* @property {CommonAutocompleteOption} autocompleteConfig 自动完成配置
* @property {string} modelValue 输出数据
* @property {boolean} useIdModel 输出对象还是id
* @property {boolean} clearable 是否可以显示清空
* @property {boolean} readonly 只读
* @property {boolean} disabled 是否禁用
* @property {boolean} emptySearchEnabled 没有关键字时是否点击就开始搜索
* @property {string} title popover标题
* @property {string} autocompleteLabel 自动完成label
* @property {string} idKey id字段名
* @property {string} labelKey label字段名
* @property {number} debounceTime 防抖时间
* @property {CommonPage} page 分页数据
* @property {string} autocompleteWidth 宽度
* @property {CommonSelectPageOption} selectPageConfig 分页
* @property {Object} inputAttrs 输入框配置项
*/
import { nextTick, onMounted, ref, watch } from 'vue'
import { debounce } from 'lodash'
import { onClickOutside, onKeyStroke, useVModel } from '@vueuse/core'
/**
* @type {CommonAutocompleteProps}
*/
const props = defineProps({
modelValue: {
type: [String, Object],
default: null
},
useIdModel: {
type: Boolean,
default: true
},
title: {
type: String,
default: ''
},
autocompleteLabel: {
type: String,
default: ''
},
idKey: {
type: String,
default: 'value'
},
labelKey: {
type: String,
default: 'label'
},
debounceTime: { // 防抖
type: Number,
default: 300
},
autocompleteWidth: {
type: String,
default: '600px'
},
page: {
type: Object,
default: null
},
autocompleteConfig: {
type: Object,
required: true
},
selectPageConfig: {
type: Object,
default: null
},
clearable: {
type: Boolean,
default: true
},
inputAttrs: {
type: Object,
default: null
},
disabled: {
type: Boolean,
default: false
},
readonly: {
type: Boolean,
default: false
},
emptySearchEnabled: {
type: Boolean,
default: false
}
})
const emit = defineEmits(['update:modelValue', 'onSelectData', 'update:page', 'update:autocompleteLabel'])
const keywords = ref(props.autocompleteLabel)
const lastAutocompleteLabel = ref(props.autocompleteLabel)
const pageAttrs = { layout: 'total, prev, pager, next' }
const dataList = ref([])
const popoverVisible = ref(false)
const autocompletePopover = ref()
const pageConfig = useVModel(props, 'page', emit)
const onInputKeywords = debounce((click) => {
if (!props.disabled && !props.readonly) {
const val = keywords.value
if (val || props.emptySearchEnabled) {
popoverVisible.value = true
props.autocompleteConfig.searchMethod(val, (result) => {
dataList.value = result.items || []
if (props.page) {
pageConfig.value = { ...result.page }
}
})
}
if (!val && !click) {
onSelectData()
}
}
}, props.debounceTime)
onMounted(() => {
onClickOutside(autocompletePopover.value?.popperRef?.contentRef, (event) => {
popoverVisible.value = false
})
})
watch(() => popoverVisible.value, (val) => {
if (!val) {
nextTick(() => {
console.info('=======================', lastAutocompleteLabel.value, keywords.value)
if (lastAutocompleteLabel.value && keywords.value && keywords.value !== lastAutocompleteLabel.value) {
keywords.value = lastAutocompleteLabel.value
}
})
}
})
//* ********************数据选择*********************
const vModel = useVModel(props, 'modelValue', emit)
const vAutocompleteLabel = useVModel(props, 'autocompleteLabel', emit)
const onSelectData = (row) => {
popoverVisible.value = false
if (!vModel.value && !row) {
console.info('==================', row)
return
}
let label = ''
let value = null
if (row) {
label = row[props.labelKey]
value = props.useIdModel ? row[props.idKey] : row
}
keywords.value = label
vAutocompleteLabel.value = label
lastAutocompleteLabel.value = label
vModel.value = value
emit('onSelectData', row)
}
// =======================按键处理===================
const tableRef = ref()
const currentOnRow = ref()
const currentOnIndex = ref(-1)
const moveSelection = function (down) {
if (dataList.value.length > 0) {
if (down) {
if (currentOnIndex.value < dataList.value.length - 1) {
currentOnIndex.value++
} else {
currentOnIndex.value = 0
}
} else {
if (currentOnIndex.value > 1) {
currentOnIndex.value--
} else {
currentOnIndex.value = 0
}
}
currentOnRow.value = dataList.value[currentOnIndex.value]
} else {
currentOnRow.value = null
}
console.info('=================', tableRef.value.table, currentOnIndex.value, currentOnRow.value)
tableRef.value.table?.setCurrentRow(currentOnRow.value)
}
// 向下按键移动元素
onKeyStroke('ArrowDown', e => moveSelection(true))
// 向上按键移动元素
onKeyStroke('ArrowUp', e => moveSelection(true))
// 选中回车
onKeyStroke('Enter', e => {
onSelectData(currentOnRow.value)
})
</script>
<template>
<div>
<el-popover
ref="autocompletePopover"
transition="el-"
:visible="popoverVisible"
class="common-autocomplete"
placement="bottom-start"
:width="autocompleteWidth"
:title="title"
>
<template #reference>
<el-input
v-model="keywords"
:clearable="clearable"
:placeholder="title"
:disabled="disabled"
:readonly="readonly"
v-bind="inputAttrs"
@input="onInputKeywords()"
@click="onInputKeywords(true)"
/>
</template>
<template #default>
<!--自动完成内容-->
<common-table
ref="tableRef"
v-model:page="pageConfig"
class="autocomplete-table"
:columns="autocompleteConfig.columns"
:empty-text="autocompleteConfig.emptyMessage"
:data="dataList"
:page-attrs="pageAttrs"
@row-click="onSelectData($event)"
@current-page-change="onInputKeywords()"
>
<template
v-for="column in autocompleteConfig.columns"
#[column.slot]="scope"
>
<slot
v-if="column.slot"
:item="scope.item"
:column-conf="scope.columnConf"
:name="column.slot"
/>
</template>
</common-table>
</template>
</el-popover>
</div>
</template>
<style scoped>
</style>

View File

@@ -1,6 +1,6 @@
<script setup>
import CommonTableColumn from '@/components/common-table/common-table-column.vue'
import { computed } from 'vue'
import { computed, ref } from 'vue'
/**
* @typedef {TableProps} CommonTableProps
@@ -108,11 +108,18 @@ const currentPageChange = (pageNumber) => {
emit('currentPageChange', pageNumber)
}
const table = ref()
defineExpose({
table
})
</script>
<template>
<el-table
:v-bind="$attrs"
ref="table"
v-bind="$attrs"
:highlight-current-row="highlightCurrentRow"
:stripe="stripe"
:data="data"
@@ -130,7 +137,8 @@ const currentPageChange = (pageNumber) => {
>
<slot
v-if="column.slot"
:scope="scope"
:row="scope.row"
:column="scope.column"
:item="scope.row"
:column-conf="scope.columnConf"
:name="column.slot"

View File

@@ -8,6 +8,7 @@ import CommonTabsView from '@/components/common-tabs-view/index.vue'
import CommonTable from '@/components/common-table/index.vue'
import CommonBreadcrumb from '@/components/common-breadcrumb/index.vue'
import CommonWindow from '@/components/common-window/index.vue'
import CommonAutocomplete from '@/components/common-autocomplete/index.vue'
/**
* 自定义通用组件自动注册
@@ -27,5 +28,6 @@ export default {
Vue.component('CommonTable', CommonTable)
Vue.component('CommonBreadcrumb', CommonBreadcrumb)
Vue.component('CommonWindow', CommonWindow)
Vue.component('CommonAutocomplete', CommonAutocomplete)
}
}

View File

@@ -4,11 +4,19 @@
*/
export const PAGE_SIZE = 10
/**
* @typedef {Object} CommonPage
* @property {number} pageSize 单页数量
* @property {number} pageNumber 当前第几页
* @property {number} totalCount 总数
* @property {number} pageCount 总页数
*/
/**
* 默认分页数据
*
* @param pageSize
* @return {{pageCount: number, pageSize: number, totalCount: number, pageNumber:number}}
* @return {CommonPage}
*/
export const useDefaultPage = (pageSize = PAGE_SIZE) => {
return {

View File

@@ -1,7 +1,35 @@
<script setup>
import { ref } from 'vue'
import { useDefaultPage } from '@/config'
import { loadUsersResult } from '@/services/user/UserService'
const modelIcon = ref('Apple')
const modelAuto = ref('99999')
const modelAutoLabel = ref('Gary Fu')
const autoPage = ref(useDefaultPage(8))
const autocompleteConfig = {
columns: [{
label: '中文名',
property: 'nameCn'
}, {
label: '性别',
property: 'gender',
slot: 'gender'
}, {
label: '地址',
property: 'address'
}],
searchMethod (query, cb) {
loadUsersResult({ page: autoPage.value })
.then(result => {
const data = {
page: result.resultData.page,
items: result.resultData.userList
}
cb(data)
})
}
}
</script>
<template>
@@ -11,7 +39,31 @@ const modelIcon = ref('Apple')
<el-form-item label="图标选择">
<common-icon-select v-model="modelIcon" />
</el-form-item>
<el-form-item label="用户">
<common-autocomplete
v-model="modelAuto"
v-model:autocomplete-label="modelAutoLabel"
v-model:page="autoPage"
id-key="id"
:label-key="$i18nMsg('nameCn', 'nameEn')"
empty-search-enabled
title="请选择用户"
:autocomplete-config="autocompleteConfig"
>
<template #gender="{item}">
<el-tag
:type="item.gender === 'male' ? '' : 'success'"
>
{{ item.gender }}
</el-tag>
</template>
</common-autocomplete>
</el-form-item>
</el-form>
<el-container>
{{ modelAuto }}
{{ modelAutoLabel }}
</el-container>
</el-main>
</el-container>
</template>