mirror of
https://github.com/fugary/simple-element-plus-template.git
synced 2025-12-31 03:17:49 +00:00
基本自动完成控件
This commit is contained in:
@@ -99,6 +99,10 @@ html, body, #app, .index-container {
|
|||||||
.form-edit-width-100 {
|
.form-edit-width-100 {
|
||||||
width:100%
|
width:100%
|
||||||
}
|
}
|
||||||
|
.autocomplete-table .el-table__cell{
|
||||||
|
padding: 3px 0;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* slide-fade动画
|
* slide-fade动画
|
||||||
|
|||||||
267
src/components/common-autocomplete/index.vue
Normal file
267
src/components/common-autocomplete/index.vue
Normal 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>
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import CommonTableColumn from '@/components/common-table/common-table-column.vue'
|
import CommonTableColumn from '@/components/common-table/common-table-column.vue'
|
||||||
import { computed } from 'vue'
|
import { computed, ref } from 'vue'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef {TableProps} CommonTableProps
|
* @typedef {TableProps} CommonTableProps
|
||||||
@@ -108,11 +108,18 @@ const currentPageChange = (pageNumber) => {
|
|||||||
emit('currentPageChange', pageNumber)
|
emit('currentPageChange', pageNumber)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const table = ref()
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
table
|
||||||
|
})
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<el-table
|
<el-table
|
||||||
:v-bind="$attrs"
|
ref="table"
|
||||||
|
v-bind="$attrs"
|
||||||
:highlight-current-row="highlightCurrentRow"
|
:highlight-current-row="highlightCurrentRow"
|
||||||
:stripe="stripe"
|
:stripe="stripe"
|
||||||
:data="data"
|
:data="data"
|
||||||
@@ -130,7 +137,8 @@ const currentPageChange = (pageNumber) => {
|
|||||||
>
|
>
|
||||||
<slot
|
<slot
|
||||||
v-if="column.slot"
|
v-if="column.slot"
|
||||||
:scope="scope"
|
:row="scope.row"
|
||||||
|
:column="scope.column"
|
||||||
:item="scope.row"
|
:item="scope.row"
|
||||||
:column-conf="scope.columnConf"
|
:column-conf="scope.columnConf"
|
||||||
:name="column.slot"
|
:name="column.slot"
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import CommonTabsView from '@/components/common-tabs-view/index.vue'
|
|||||||
import CommonTable from '@/components/common-table/index.vue'
|
import CommonTable from '@/components/common-table/index.vue'
|
||||||
import CommonBreadcrumb from '@/components/common-breadcrumb/index.vue'
|
import CommonBreadcrumb from '@/components/common-breadcrumb/index.vue'
|
||||||
import CommonWindow from '@/components/common-window/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('CommonTable', CommonTable)
|
||||||
Vue.component('CommonBreadcrumb', CommonBreadcrumb)
|
Vue.component('CommonBreadcrumb', CommonBreadcrumb)
|
||||||
Vue.component('CommonWindow', CommonWindow)
|
Vue.component('CommonWindow', CommonWindow)
|
||||||
|
Vue.component('CommonAutocomplete', CommonAutocomplete)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,11 +4,19 @@
|
|||||||
*/
|
*/
|
||||||
export const PAGE_SIZE = 10
|
export const PAGE_SIZE = 10
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {Object} CommonPage
|
||||||
|
* @property {number} pageSize 单页数量
|
||||||
|
* @property {number} pageNumber 当前第几页
|
||||||
|
* @property {number} totalCount 总数
|
||||||
|
* @property {number} pageCount 总页数
|
||||||
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 默认分页数据
|
* 默认分页数据
|
||||||
*
|
*
|
||||||
* @param pageSize
|
* @param pageSize
|
||||||
* @return {{pageCount: number, pageSize: number, totalCount: number, pageNumber:number}}
|
* @return {CommonPage}
|
||||||
*/
|
*/
|
||||||
export const useDefaultPage = (pageSize = PAGE_SIZE) => {
|
export const useDefaultPage = (pageSize = PAGE_SIZE) => {
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -1,7 +1,35 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
|
import { useDefaultPage } from '@/config'
|
||||||
|
import { loadUsersResult } from '@/services/user/UserService'
|
||||||
|
|
||||||
const modelIcon = ref('Apple')
|
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>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -11,7 +39,31 @@ const modelIcon = ref('Apple')
|
|||||||
<el-form-item label="图标选择">
|
<el-form-item label="图标选择">
|
||||||
<common-icon-select v-model="modelIcon" />
|
<common-icon-select v-model="modelIcon" />
|
||||||
</el-form-item>
|
</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-form>
|
||||||
|
<el-container>
|
||||||
|
{{ modelAuto }}
|
||||||
|
{{ modelAutoLabel }}
|
||||||
|
</el-container>
|
||||||
</el-main>
|
</el-main>
|
||||||
</el-container>
|
</el-container>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
Reference in New Issue
Block a user