特性:
- 支持本地记录搜索关键词
- 后台接口匹配搜索关键词
- 支持自定义填充字段名
- 支持user或address两种匹配列表布局样式
sgAutocomplete_v2
<template><div :class="$options.name" @mouseover="inside = true" @mouseout="inside = false"><div class="search-layer"><el-inputclass="search-input"v-model.trim="inputSearchValue"maxlength="50":show-word-limit="false":placeholder="`你在找什么?快告诉我...`"clearable@keyup.native.enter="search"@focus="focusSearchInput"@clear="clearSearchInput"@input="inputingSearchInput"><el-button slot="append" icon="el-icon-search" @click="search"></el-button></el-input></div><div class="absolute-dropdownlist-layer" v-if="showAbsoluteDropdownlistLayer"><!-- 搜索建议 --><divclass="search-suggestion"v-if="historyKeywords.length > 0 && visible_search_suggestion"><!-- 搜索历史 --><div class="search-suggestion-item history"><div class="search-suggestion-item-head"><h1>最近搜索</h1><el-linkicon="el-icon-delete"type="danger":underline="false"@click.stop="clearAll">清除</el-link></div><ul class="historyKeywords"><li v-for="(a, i) in historyKeywords" :key="i"><el-tag:title="a"type="info"closable@click.stop="clickKeyword(a)"@close="closeTag(a)"><span class="text">{{ a }}</span></el-tag></li></ul></div></div><!-- 后台查询推荐匹配项 --><div class="matchList" v-if="matchList.length > 0"><ul><liv-for="(a, i) in matchList":key="i"@click.stop="clickMatchItem(a)":type="matchType"><template v-if="matchType == `user`"><div class="face"><img:src="a.src"v-if="a.src && !a.hideImg"@error="error($event, a, i)"/><el-avatarv-else:style="$g.convertData({ obj: `ROLE.PROFESSIONAL`, value: a.type }).style">{{a.username? a.username.slice(-1): a[matchKeyID] === "master"? "超": "未"}}</el-avatar></div><b class="username" v-html="a.username_HTML"> </b><span class="userid" v-html="a.userid_HTML"></span><spanclass="unit"v-html="`${a.unit_HTML ? `(${a.unit_HTML})` : ``}`"></span></template><template v-if="matchType == `address`"><b class="username" v-html="a.username_HTML"> </b><spanclass="address"v-html="`${a.address_HTML ? `[${a.address_HTML}]` : ``}`"></span></template></li></ul><div class="foot" v-if="showMoreBtn"><div class="more-btn" @click.stop="readmoreMatch" :loading="moreBtnLoading">{{ moreBtnLoading ? `加载中…` : `查看更多` }}</div></div></div></div></div>
</template>
<script>
export default {name: "sgAutocomplete_v2",components: {},data() {return {form: {},// ----------------------------------------inside: false, //鼠标是否在组件内showAbsoluteDropdownlistLayer: false, //显示absolute-dropdownlist-layerinputSearchValue: this.$route.query.keyword || "", //搜索关键词// 搜索建议----------------------------------------visible_search_suggestion: false, //显示搜索建议historyKeywords: [],// 搜索匹配----------------------------------------visible_search_match: false, //显示搜索匹配mainID: `ID`, //主键matchKeyID: `NAME`, //当点击匹配项,自动填充输入框的字段matchType: `user`, //默认匹配类型是user,枚举值:user、addressmatchList: [],keywordClassName: "match-autocomplete-keyword", //匹配高亮关键词样式showMoreBtn: false,moreBtnLoading: false,// ----------------------------------------};},props: ["value", "data"],computed: {},watch: {value: {handler(d) {this.inputSearchValue = d;},deep: true,immediate: true,},inputSearchValue(d) {this.$emit("input", d);},data: {handler(newValue, oldValue) {//console.log(`深度监听${this.$options.name}:`, newValue, oldValue);if (Object.keys(newValue || {}).length) {this.form = JSON.parse(JSON.stringify(newValue));this.form.matchType && (this.matchType = this.form.matchType);this.form.matchKeyID && (this.matchKeyID = this.form.matchKeyID);this.showMoreBtn = this.form.showMoreBtn;this.moreBtnLoading = this.form.moreBtnLoading;this.matchList = this.form.matchList || [];this.matchList = [...new Set(this.matchList.map((v) => v[this.matchKeyID])),].map((ID) => this.matchList.find((v) => v[this.matchKeyID] === ID)); //去重同样的ID数据this.setHighlightMatchList();this.$nextTick(() => {let matchListScrollDOM = this.$el.querySelector(`.matchList>ul`);matchListScrollDOM &&matchListScrollDOM.scrollTo({top: matchListScrollDOM.scrollHeight,behavior: "smooth",});});}},deep: true, //深度监听immediate: true, //立即执行},matchList: {handler(newValue, oldValue) {//console.log(`深度监听${this.$options.name}:`, newValue, oldValue);if (Object.keys(newValue || {}).length) {newValue.length > 0 && (this.visible_search_suggestion = false);}},deep: true, //深度监听immediate: true, //立即执行},},created() {this.initHistoryKeywords();},mounted() {this.__add();},destroyed() {this.__remove();},methods: {// 搜索输入框----------------------------------------search({ keyword = this.inputSearchValue } = {}) {this.visible_search_suggestion = false;this.saveHistoryKeywords();this.$emit(`search`, { keyword });},inputingSearchInput(keyword) {if (keyword === "") {this.visible_search_suggestion = true;}this.$emit(`change`, { keyword, isReset: true });},clearSearchInput(d) {this.visible_search_suggestion = true;},getmatchList({ matchList } = {}) {if (matchList.length > 0) {this.visible_search_suggestion = false;}},focusSearchInput(d) {this.showAbsoluteDropdownlistLayer = true;if (this.inputSearchValue.length === 0) {this.visible_search_suggestion = this.matchList.length === 0;} else {this.visible_search_suggestion = false;this.$emit(`change`, { keyword: this.inputSearchValue, isReset: true });}},saveHistoryKeywords(d) {let historyKeywords = (localStorage.historyKeywords || "").split("|");historyKeywords.unshift(this.inputSearchValue);historyKeywords = historyKeywords.filter(Boolean); //清空数组中的empty、undefined、nullhistoryKeywords = [...new Set(historyKeywords)];this.historyKeywords = historyKeywords;localStorage.historyKeywords = historyKeywords.join("|");},// 搜索建议----------------------------------------//初始化历史搜索记录initHistoryKeywords({ d } = {}) {this.historyKeywords = [...new Set((localStorage.historyKeywords || "").split("|").filter((v, i, ar) => v !== ``)),];},clickKeyword(d) {this.inputSearchValue = d;this.visible_search_suggestion = false;this.search();this.$nextTick(() => {this.focusSearchInput();});},closeTag(d) {this.historyKeywords = this.historyKeywords.filter((v, i, ar) => v !== d);localStorage.historyKeywords = this.historyKeywords.join("|");},clearAll(d) {this.visible_search_suggestion = false;this.historyKeywords = [];delete localStorage.historyKeywords;},// 搜索匹配----------------------------------------error($event, a, i) {this.$set(this.matchList[i], "hideImg", true);},clickMatchItem(a) {this.matchList = [];this.inputSearchValue = a[this.matchKeyID];this.search();},// 高亮匹配内容里面的关键词setHighlightMatchList() {let cls = this.keywordClassName;let keyword = this.inputSearchValue;this.matchList.forEach((d) => {Object.keys(d || {}).forEach((k) => {let originContent = this.$g.stripHTML(d[k]);d[`${k}_HTML`] = this.$g.highLightMatchString(originContent, keyword, cls);});});},readmoreMatch(d) {this.$emit(`readmoreMatch`, d);},// 全局监听----------------------------------------__add() {this.__remove();addEventListener("mousedown", this.__mousedown);},__remove() {removeEventListener("mousedown", this.__mousedown);},__mousedown(e) {this.inside || this.__hide(e); // 点击其他区域隐藏},__hide(e) {this.$emit("hide", e);this.showAbsoluteDropdownlistLayer = false;},__show(e) {this.$emit("show", e);this.showAbsoluteDropdownlistLayer = true;},},
};
</script>
<style lang="scss" scoped>
.sgAutocomplete_v2 {// 输入框搜索图层.search-layer {position: relative;z-index: 1;margin-bottom: 5px;.search-input {}}// 建议区域图层.absolute-dropdownlist-layer {z-index: 1;position: relative;width: 100%;$maxHeight: 400px; //下拉框最大高度// 搜索建议.search-suggestion {max-height: $maxHeight;position: absolute;top: 0;left: 0;box-shadow: 0 5px 20px 0 #00000022;background-color: white;border-radius: 4px;font-size: 14px;width: 100%;box-sizing: border-box;padding: 20px;.search-suggestion-item {margin-bottom: 20px;&:last-of-type {margin-bottom: 0;}.search-suggestion-item-head {display: flex;justify-content: space-between;align-items: center;margin-bottom: 20px;h1 {font-size: 14px;color: #2c343e;}}// 搜索历史.historyKeywords {display: flex;flex-wrap: wrap;margin-right: -10px;margin-bottom: -10px;max-height: calc(#{$maxHeight} - 80px);overflow-y: auto;li {margin-right: 10px;margin-bottom: 10px;>>> .el-tag {cursor: pointer;$tagMaxWidth: 200px;max-width: $tagMaxWidth;display: flex;align-items: center;&:hover,&:focus,&:active {border-color: #999;background-color: #00000009;span {color: black;}}.text {max-width: calc(#{$tagMaxWidth} - 20px);overflow: hidden;white-space: nowrap;text-overflow: ellipsis;display: inline-block;}.el-tag__close {flex-shrink: 0;}}}}}}// 搜索匹配.matchList {max-height: $maxHeight;position: absolute;top: 0;left: 0;box-shadow: 0 5px 20px 0 #00000022;background-color: white;border-radius: 8px;font-size: 14px;width: 100%;box-sizing: border-box;padding: 10px;ul {width: 100%;max-height: calc(#{$maxHeight} - 80px);overflow-y: auto;li {width: 100%;display: flex;align-items: center;box-sizing: border-box;padding: 10px;cursor: pointer;transition: 0.2s;border-radius: 8px;&[type="user"] {}&[type="address"] {}&:hover {filter: brightness(1.1);background-color: #00000009;}.face {margin-right: 10px;img,.el-avatar {width: 30px;height: 30px;display: flex;justify-content: center;align-items: center;object-position: center;object-fit: cover;border-radius: 88px;overflow: hidden;background-color: #00000009;}}.username {margin-right: 5px;color: black;}.userid {color: #999;margin-right: 10px;}.unit {color: #999;}.address {color: #999;}// 匹配高亮关键词样式>>> .match-autocomplete-keyword {color: #409eff;}}}.foot {box-sizing: border-box;display: flex;justify-content: center;align-items: center;.more-btn {padding: 10px;width: 100%;height: 100%;text-align: center;border-radius: 4px;cursor: pointer;transition: 0.2s;color: #409eff;&[loading] {pointer-events: none;}&:hover {background-color: #00000009;}}}}}
}
</style>
demo
<template><div :class="$options.name" style="display: flex; flex-wrap: nowrap"><sgAutocomplete_v2style="width: 500px":data="data_sgAutocomplete_v2"v-model="inputValue"@change="changeMatch"@readmoreMatch="readmoreMatch"@search="search"/><el-radio-groupv-model="data_sgAutocomplete_v2.matchType"size="small"style="margin-left: 10px; margin-top: 10px"><el-radiov-for="(radio, index) in radios":key="index":label="radio.value":disabled="radio.disabled">{{ radio.label }}</el-radio></el-radio-group></div>
</template>
<script>
import sgAutocomplete_v2 from "@/vue/components/admin/sgAutocomplete_v2";export default {name: "demoSgAutocomplete_v2",components: { sgAutocomplete_v2 },data() {return {radios: [{ value: `user`, label: "用户" },{ value: `address`, label: "地址" },],// ----------------------------------------inputValue: ``,data_sgAutocomplete_v2: {matchType: `user`, //默认匹配类型是user,枚举值:user、addressmatchKeyID: `NAME`, //当点击匹配项,自动填充输入框的字段matchList: [], //匹配项列表},// ----------------------------------------currentPage: 1,pageSize: 6,};},methods: {// 动态获取匹配项changeMatch({ keyword = this.inputValue, isReset } = {}) {if (!keyword) return (this.data_sgAutocomplete_v2.matchList = []);if (isReset) {this.data_sgAutocomplete_v2.matchList = [];this.currentPage = 1;}let data = {start: this.currentPage - 1, //当前页数(从0开始)limit: this.pageSize, //每页显示条目个数KEY: keyword, //匹配关键词};this.$f.biz_person_query({...data,l: {show: () => this.$set(this.data_sgAutocomplete_v2, "moreBtnLoading", true),close: () => this.$set(this.data_sgAutocomplete_v2, "moreBtnLoading", false),},cb: (d) => {//回调函数if (isReset) {this.data_sgAutocomplete_v2.matchList = []; //避免重复追加}let old_matchList = this.data_sgAutocomplete_v2.matchList;let matchList = old_matchList.concat(d.data || []);//转义适配字段matchList.forEach((v) => {v.type = v.TYPE;v.src = v.PHOTO_T ? this.$d.responseFile(v.PHOTO_T) : null; //头像路径v.username = v.NAME;v.userid = v.ACCOUNT;v.unit = v.UNIT_ID_TEXT;v.address = v.ADDRESS;});this.data_sgAutocomplete_v2.matchList = matchList;this.data_sgAutocomplete_v2.showMoreBtn = matchList.length < d.totalCount;},},this);},readmoreMatch(d) {this.currentPage++;this.changeMatch();},search(d) {// console.log(`搜索内容`, d, this.inputValue);},},
};
</script>
应用到了【全网最简短代码】去重对象数组中同一个ID的数据-CSDN博客【代码】【全网最简短代码】去重对象数组中同一个ID的数据。https://blog.csdn.net/qq_37860634/article/details/146376768