mirror of https://gitee.com/xiaonuobase/snowy
【更新】优化前端搜索组源码及个人中心优化
parent
1a828f8378
commit
cad24eb2a8
|
@ -1,58 +0,0 @@
|
||||||
import { mapState, mapActions } from 'pinia'
|
|
||||||
import hotkeys from 'hotkeys-js'
|
|
||||||
import { searchStore } from '@/store'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
mounted() {
|
|
||||||
// 绑定搜索功能快捷键 [ 打开 ]
|
|
||||||
hotkeys(this.searchHotkey.open, (event) => {
|
|
||||||
event.preventDefault()
|
|
||||||
this.searchPanelOpen()
|
|
||||||
})
|
|
||||||
// 绑定搜索功能快捷键 [ 关闭 ]
|
|
||||||
hotkeys(this.searchHotkey.close, (event) => {
|
|
||||||
event.preventDefault()
|
|
||||||
this.searchPanelClose()
|
|
||||||
})
|
|
||||||
},
|
|
||||||
beforeDestroy() {
|
|
||||||
hotkeys.unbind(this.searchHotkey.open)
|
|
||||||
hotkeys.unbind(this.searchHotkey.close)
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
...mapState(searchStore, {
|
|
||||||
searchActive: (state) => state.active,
|
|
||||||
searchHotkey: (state) => state.hotkey
|
|
||||||
})
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
...mapActions(searchStore, ['toggleActive', 'setActive']),
|
|
||||||
// 接收点击搜索按钮
|
|
||||||
handleSearchClick() {
|
|
||||||
this.toggleActive()
|
|
||||||
if (this.searchActive) {
|
|
||||||
setTimeout(() => {
|
|
||||||
if (this.$refs.panelSearch) {
|
|
||||||
this.$refs.panelSearch.focus()
|
|
||||||
}
|
|
||||||
}, 300)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
searchPanelOpen() {
|
|
||||||
if (!this.searchActive) {
|
|
||||||
this.setActive(true)
|
|
||||||
setTimeout(() => {
|
|
||||||
if (this.$refs.panelSearch) {
|
|
||||||
this.$refs.panelSearch.focus()
|
|
||||||
}
|
|
||||||
}, 300)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
// 关闭搜索面板
|
|
||||||
searchPanelClose() {
|
|
||||||
if (this.searchActive) {
|
|
||||||
this.setActive(false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,210 +1,262 @@
|
||||||
<template>
|
<template>
|
||||||
<div @keyup.up="handleKeyUp" @keyup.down="handleKeyDown" @keyup.enter="handleKeyEnter" @click.self="handlePanelClick">
|
<div class="search panel-item" @click="searchPanelOpen">
|
||||||
<a-input
|
<search-outlined />
|
||||||
ref="input"
|
|
||||||
v-model="searchText"
|
|
||||||
class="search-box"
|
|
||||||
style="width: 100%"
|
|
||||||
allowClear
|
|
||||||
placeholder="搜索页面(支持拼音检索)"
|
|
||||||
@change="querySearch"
|
|
||||||
>
|
|
||||||
<template #prefix>
|
|
||||||
<search-outlined />
|
|
||||||
</template>
|
|
||||||
</a-input>
|
|
||||||
<a-card
|
|
||||||
:body-style="{ padding: '0 0' }"
|
|
||||||
hoverable
|
|
||||||
@mouseenter="onCardIn"
|
|
||||||
@mouseleave="onCardOut"
|
|
||||||
@keypress.up="handleKeyUp"
|
|
||||||
@keypress.down="handleKeyDown"
|
|
||||||
style="margin: 10px 0"
|
|
||||||
>
|
|
||||||
<div ref="cardList" class="search-card beauty-scroll">
|
|
||||||
<a-list size="small" :data-source="resultsList">
|
|
||||||
<template #renderItem="{ item, index }">
|
|
||||||
<a-list-item
|
|
||||||
@click="handleSelect(item.fullPath)"
|
|
||||||
@mouseover="onCardItemHover(index)"
|
|
||||||
:class="{ active: index === cardIndex }"
|
|
||||||
style="padding-right: 10px"
|
|
||||||
>
|
|
||||||
<template #actions>
|
|
||||||
<a>
|
|
||||||
<enter-outlined />
|
|
||||||
</a>
|
|
||||||
</template>
|
|
||||||
<a-list-item-meta :description="item.fullName">
|
|
||||||
<template #title>
|
|
||||||
<a>{{ item.name }}</a>
|
|
||||||
</template>
|
|
||||||
<template #avatar>
|
|
||||||
<a-avatar style="color: var(--text-color); background-color: transparent" :type="item.icon">
|
|
||||||
<template #icon>
|
|
||||||
<component :is="item.icon" />
|
|
||||||
</template>
|
|
||||||
</a-avatar>
|
|
||||||
</template>
|
|
||||||
</a-list-item-meta>
|
|
||||||
</a-list-item>
|
|
||||||
</template>
|
|
||||||
</a-list>
|
|
||||||
</div>
|
|
||||||
</a-card>
|
|
||||||
<div class="search-tips">
|
|
||||||
<span class="key">S</span>
|
|
||||||
<span class="tips">打开搜索面板</span>
|
|
||||||
|
|
||||||
<span class="key">
|
|
||||||
<arrow-up-outlined />
|
|
||||||
</span>
|
|
||||||
<span class="key">
|
|
||||||
<arrow-down-outlined />
|
|
||||||
</span>
|
|
||||||
<span class="tips">选择</span>
|
|
||||||
|
|
||||||
<span class="key">
|
|
||||||
<enter-outlined />
|
|
||||||
</span>
|
|
||||||
<span class="tips">确认</span>
|
|
||||||
|
|
||||||
<span class="key left">Esc</span>
|
|
||||||
<span class="tips">关闭</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
<xn-form-container
|
||||||
|
title="搜索"
|
||||||
|
:visible="searchActive"
|
||||||
|
:closable="false"
|
||||||
|
:footer="null"
|
||||||
|
:width="600"
|
||||||
|
destroyOnClose
|
||||||
|
dialogClass="searchModal"
|
||||||
|
:bodyStyle="{ maxHeight: '520px', overflow: 'auto', padding: '14px' }"
|
||||||
|
@close="searchPanelClose"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
@keyup.up="handleKeyUp"
|
||||||
|
@keyup.down="handleKeyDown"
|
||||||
|
@keyup.enter="handleKeyEnter"
|
||||||
|
@click.self="handlePanelClick"
|
||||||
|
>
|
||||||
|
<a-input
|
||||||
|
ref="inputRef"
|
||||||
|
v-model="searchText"
|
||||||
|
class="search-box"
|
||||||
|
style="width: 100%"
|
||||||
|
allowClear
|
||||||
|
placeholder="搜索页面(支持拼音检索)"
|
||||||
|
@change="querySearch"
|
||||||
|
>
|
||||||
|
<template #prefix>
|
||||||
|
<search-outlined />
|
||||||
|
</template>
|
||||||
|
</a-input>
|
||||||
|
<a-card
|
||||||
|
:body-style="{ padding: '0 0' }"
|
||||||
|
hoverable
|
||||||
|
@mouseenter="onCardIn"
|
||||||
|
@mouseleave="onCardOut"
|
||||||
|
@keypress.up="handleKeyUp"
|
||||||
|
@keypress.down="handleKeyDown"
|
||||||
|
style="margin: 10px 0"
|
||||||
|
>
|
||||||
|
<div ref="cardListRef" class="search-card beauty-scroll">
|
||||||
|
<a-list size="small" :data-source="resultsList">
|
||||||
|
<template #renderItem="{ item, index }">
|
||||||
|
<a-list-item
|
||||||
|
@click="handleSelect(item.fullPath)"
|
||||||
|
@mouseover="onCardItemHover(index)"
|
||||||
|
:class="{ active: index === cardIndex }"
|
||||||
|
style="padding-right: 10px"
|
||||||
|
>
|
||||||
|
<template #actions>
|
||||||
|
<a>
|
||||||
|
<enter-outlined />
|
||||||
|
</a>
|
||||||
|
</template>
|
||||||
|
<a-list-item-meta :description="item.fullName">
|
||||||
|
<template #title>
|
||||||
|
<a>{{ item.name }}</a>
|
||||||
|
</template>
|
||||||
|
<template #avatar>
|
||||||
|
<a-avatar style="color: var(--text-color); background-color: transparent" :type="item.icon">
|
||||||
|
<template #icon>
|
||||||
|
<component :is="item.icon" />
|
||||||
|
</template>
|
||||||
|
</a-avatar>
|
||||||
|
</template>
|
||||||
|
</a-list-item-meta>
|
||||||
|
</a-list-item>
|
||||||
|
</template>
|
||||||
|
</a-list>
|
||||||
|
</div>
|
||||||
|
</a-card>
|
||||||
|
<div class="search-tips">
|
||||||
|
<span class="key">S</span>
|
||||||
|
<span class="tips">打开搜索面板</span>
|
||||||
|
|
||||||
|
<span class="key">
|
||||||
|
<arrow-up-outlined />
|
||||||
|
</span>
|
||||||
|
<span class="key">
|
||||||
|
<arrow-down-outlined />
|
||||||
|
</span>
|
||||||
|
<span class="tips">选择</span>
|
||||||
|
|
||||||
|
<span class="key">
|
||||||
|
<enter-outlined />
|
||||||
|
</span>
|
||||||
|
<span class="tips">确认</span>
|
||||||
|
|
||||||
|
<span class="key left">Esc</span>
|
||||||
|
<span class="tips">关闭</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</xn-form-container>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script setup>
|
||||||
import Fuse from 'fuse.js'
|
import Fuse from 'fuse.js'
|
||||||
import { mapState } from 'pinia'
|
|
||||||
import { searchStore } from '@/store'
|
import { searchStore } from '@/store'
|
||||||
|
import { useRouter, useRoute } from 'vue-router'
|
||||||
|
import hotkeys from 'hotkeys-js'
|
||||||
|
|
||||||
export default {
|
const route = useRoute()
|
||||||
data() {
|
const router = useRouter()
|
||||||
return {
|
const searchText = ref('')
|
||||||
searchText: '',
|
const cardIndex = ref(0)
|
||||||
cardIndex: 0,
|
const results = ref([])
|
||||||
results: []
|
const search = searchStore()
|
||||||
}
|
const inputRef = ref()
|
||||||
},
|
const cardListRef = ref()
|
||||||
computed: {
|
const setActive = search.setActive
|
||||||
...mapState(searchStore, ['pool']),
|
const toggleActive = search.toggleActive
|
||||||
// 这份数据是展示在搜索面板下面的
|
const pool = computed(() => {
|
||||||
resultsList() {
|
return search.pool
|
||||||
return this.results.length === 0 || this.searchText === '' ? this.pool : this.results
|
})
|
||||||
},
|
const searchActive = computed(() => {
|
||||||
// 根据 pool 更新 fuse 实例
|
return search.active
|
||||||
fuse() {
|
})
|
||||||
return new Fuse(this.pool, {
|
const searchHotkey = computed(() => {
|
||||||
shouldSort: true, // 按分数对结果列表进行排序
|
return search.hotkey
|
||||||
threshold: 0.6, // 什么时候放弃
|
})
|
||||||
location: 0, // 大致位置
|
const mixinSearch = computed(() => {
|
||||||
distance: 100, // 接近程度
|
return mixinSearch
|
||||||
minMatchCharLength: 1, // 匹配长度
|
})
|
||||||
keys: ['name', 'namePinyin', 'namePinyinFirst']
|
// 这份数据是展示在搜索面板下面的
|
||||||
})
|
const resultsList = computed(() => {
|
||||||
}
|
return results.value.length === 0 || searchText.value === '' ? pool.value : results.value
|
||||||
},
|
})
|
||||||
methods: {
|
// 根据 pool 更新 fuse 实例
|
||||||
// 过滤选项 这个方法在每次输入框的值发生变化时会触发
|
const fuse = computed(() => {
|
||||||
querySearch(e) {
|
return new Fuse(pool.value, {
|
||||||
let queryString = e.target.value || ''
|
shouldSort: true, // 按分数对结果列表进行排序
|
||||||
const results = queryString && this.fuse.search(queryString).map((e) => e.item)
|
threshold: 0.6, // 什么时候放弃
|
||||||
this.searchText = queryString
|
location: 0, // 大致位置
|
||||||
this.results = results
|
distance: 100, // 接近程度
|
||||||
},
|
minMatchCharLength: 1, // 匹配长度
|
||||||
// 聚焦输入框
|
keys: ['name', 'namePinyin', 'namePinyinFirst']
|
||||||
focus() {
|
})
|
||||||
this.searchText = ''
|
})
|
||||||
setTimeout(() => {
|
// 过滤选项 这个方法在每次输入框的值发生变化时会触发
|
||||||
if (this.$refs.input) {
|
const querySearch = (e) => {
|
||||||
this.$refs.input.focus()
|
let queryString = e.target.value || ''
|
||||||
}
|
const result = queryString && fuse.value.search(queryString).map((e) => e.item)
|
||||||
// 还原
|
searchText.value = queryString
|
||||||
this.searchText = ''
|
results.value = result
|
||||||
this.results = []
|
}
|
||||||
}, 300)
|
// 聚焦输入框
|
||||||
},
|
const focus = () => {
|
||||||
handleKeyEnter() {
|
searchText.value = ''
|
||||||
let idx = this.cardIndex
|
setTimeout(() => {
|
||||||
if (this.resultsList[idx]) {
|
if (inputRef.value) {
|
||||||
this.handleSelect(this.resultsList[idx].fullPath)
|
inputRef.value.focus()
|
||||||
}
|
|
||||||
},
|
|
||||||
handleKeyUp() {
|
|
||||||
this.handleKeyUpOrDown(true)
|
|
||||||
},
|
|
||||||
handleKeyDown() {
|
|
||||||
this.handleKeyUpOrDown(false)
|
|
||||||
},
|
|
||||||
handleKeyUpOrDown(up) {
|
|
||||||
let len = this.resultsList.length - 1
|
|
||||||
let idx = this.cardIndex
|
|
||||||
if (up) {
|
|
||||||
// 上
|
|
||||||
if (idx > 0) {
|
|
||||||
idx--
|
|
||||||
} else {
|
|
||||||
idx = len
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// 下
|
|
||||||
if (idx < len) {
|
|
||||||
idx++
|
|
||||||
} else {
|
|
||||||
idx = 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.cardIndex = idx
|
|
||||||
if (this.$refs.cardList.getElementsByClassName('ant-list-item')[idx]) {
|
|
||||||
this.$refs.cardList.scrollTop = this.$refs.cardList.getElementsByClassName('ant-list-item')[idx].offsetTop
|
|
||||||
} else {
|
|
||||||
this.$refs.cardList.scrollTop = 0
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onCardIn() {
|
|
||||||
this.$refs.input.activated = false
|
|
||||||
this.$refs.input.blur()
|
|
||||||
},
|
|
||||||
onCardOut() {
|
|
||||||
this.cardIndex = -1
|
|
||||||
},
|
|
||||||
onCardItemHover(index) {
|
|
||||||
this.cardIndex = index
|
|
||||||
},
|
|
||||||
// 接收用户在下拉菜单中选中事件
|
|
||||||
handleSelect(path) {
|
|
||||||
// 如果用户选择的就是当前页面 就直接关闭搜索面板
|
|
||||||
if (path === this.$route.path) {
|
|
||||||
this.handleEsc()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
this.$router.push({ path })
|
|
||||||
this.handleEsc()
|
|
||||||
},
|
|
||||||
// 关闭输入框的下拉菜单
|
|
||||||
closeSuggestion() {
|
|
||||||
if (this.$refs.input.activated) {
|
|
||||||
this.results = []
|
|
||||||
this.$refs.input.activated = false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
// 接收用户点击空白区域的关闭
|
|
||||||
handlePanelClick(e) {
|
|
||||||
if ('INPUT' !== e.target.tagName) {
|
|
||||||
this.handleEsc()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
// 接收用户触发的关闭
|
|
||||||
async handleEsc() {
|
|
||||||
this.closeSuggestion()
|
|
||||||
await this.$nextTick()
|
|
||||||
this.$emit('close')
|
|
||||||
}
|
}
|
||||||
|
// 还原
|
||||||
|
searchText.value = ''
|
||||||
|
results.value = []
|
||||||
|
}, 300)
|
||||||
|
}
|
||||||
|
const handleKeyEnter = () => {
|
||||||
|
let idx = cardIndex.value
|
||||||
|
if (resultsList.value[idx]) {
|
||||||
|
handleSelect(resultsList.value[idx].fullPath)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
const handleKeyUp = () => {
|
||||||
|
handleKeyUpOrDown(true)
|
||||||
|
}
|
||||||
|
const handleKeyDown = () => {
|
||||||
|
handleKeyUpOrDown(false)
|
||||||
|
}
|
||||||
|
const handleKeyUpOrDown = (up) => {
|
||||||
|
let len = resultsList.value.length - 1
|
||||||
|
let idx = cardIndex.value
|
||||||
|
if (up) {
|
||||||
|
// 上
|
||||||
|
if (idx > 0) {
|
||||||
|
idx--
|
||||||
|
} else {
|
||||||
|
idx = len
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 下
|
||||||
|
if (idx < len) {
|
||||||
|
idx++
|
||||||
|
} else {
|
||||||
|
idx = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cardIndex.value = idx
|
||||||
|
if (cardListRef.value.getElementsByClassName('ant-list-item')[idx]) {
|
||||||
|
cardListRef.value.scrollTop = cardListRef.value.getElementsByClassName('ant-list-item')[idx].offsetTop
|
||||||
|
} else {
|
||||||
|
cardListRef.value.scrollTop = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const onCardIn = () => {
|
||||||
|
inputRef.value.activated = false
|
||||||
|
inputRef.value.blur()
|
||||||
|
}
|
||||||
|
const onCardOut = () => {
|
||||||
|
cardIndex.value = -1
|
||||||
|
}
|
||||||
|
const onCardItemHover = (index) => {
|
||||||
|
cardIndex.value = index
|
||||||
|
}
|
||||||
|
// 接收用户在下拉菜单中选中事件
|
||||||
|
const handleSelect = (path) => {
|
||||||
|
// 如果用户选择的就是当前页面 就直接关闭搜索面板
|
||||||
|
if (path === route.path) {
|
||||||
|
searchPanelClose()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
router.push({ path })
|
||||||
|
searchPanelClose()
|
||||||
|
}
|
||||||
|
// 接收用户点击空白区域的关闭
|
||||||
|
const handlePanelClick = (e) => {
|
||||||
|
if ('INPUT' !== e.target.tagName) {
|
||||||
|
searchPanelClose()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 打开搜索面板
|
||||||
|
const searchPanelOpen = () => {
|
||||||
|
if (!searchActive.value) {
|
||||||
|
setActive(true)
|
||||||
|
setTimeout(() => {
|
||||||
|
if (inputRef.value) {
|
||||||
|
inputRef.value.focus()
|
||||||
|
}
|
||||||
|
}, 300)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 关闭搜索面板
|
||||||
|
const searchPanelClose = () => {
|
||||||
|
if (searchActive.value) {
|
||||||
|
setActive(false)
|
||||||
|
}
|
||||||
|
results.value = []
|
||||||
|
if (inputRef.value.activated) {
|
||||||
|
inputRef.value.activated = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
onMounted(() => {
|
||||||
|
// 绑定搜索功能快捷键 [ 打开 ]
|
||||||
|
hotkeys(searchHotkey.value.open, (event) => {
|
||||||
|
event.preventDefault()
|
||||||
|
searchPanelOpen()
|
||||||
|
})
|
||||||
|
// 绑定搜索功能快捷键 [ 关闭 ]
|
||||||
|
hotkeys(searchHotkey.value.close, (event) => {
|
||||||
|
event.preventDefault()
|
||||||
|
searchPanelClose()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
// 挂载
|
||||||
|
hotkeys.unbind(searchHotkey.value.open)
|
||||||
|
hotkeys.unbind(searchHotkey.value.close)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
|
@ -249,7 +301,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.search-card {
|
.search-card {
|
||||||
height: 220px;
|
height: 380px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
overflow-y: scroll;
|
overflow-y: scroll;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,36 +1,34 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="d2-panel-search-item" :class="hoverMode ? 'can-hover' : ''" flex>
|
<div class="d2-panel-search-item" :class="props.hoverMode ? 'can-hover' : ''" flex>
|
||||||
<div class="d2-panel-search-item__icon" flex-box="0">
|
<div class="d2-panel-search-item__icon" flex-box="0">
|
||||||
<div class="d2-panel-search-item__icon-box" flex="main:center cross:center">
|
<div class="d2-panel-search-item__icon-box" flex="main:center cross:center">
|
||||||
<a-icon v-if="item.icon" :type="item.icon" />
|
<a-icon v-if="props.item.icon" :type="props.item.icon" />
|
||||||
<a-icon v-else type="menu" />
|
<a-icon v-else type="menu" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="d2-panel-search-item__info" flex-box="1" flex="dir:top">
|
<div class="d2-panel-search-item__info" flex-box="1" flex="dir:top">
|
||||||
<div class="d2-panel-search-item__info-title" flex-box="1" flex="cross:center">
|
<div class="d2-panel-search-item__info-title" flex-box="1" flex="cross:center">
|
||||||
<span>{{ item.title }}</span>
|
<span>{{ props.item.title }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="d2-panel-search-item__info-fullTitle" flex-box="0">
|
<div class="d2-panel-search-item__info-fullTitle" flex-box="0">
|
||||||
<span>{{ item.fullTitle }}</span>
|
<span>{{ props.item.fullTitle }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="d2-panel-search-item__info-path" flex-box="0">
|
<div class="d2-panel-search-item__info-path" flex-box="0">
|
||||||
<span>{{ item.path }}</span>
|
<span>{{ props.item.path }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script setup>
|
||||||
export default {
|
const props = defineProps({
|
||||||
props: {
|
item: {
|
||||||
item: {
|
default: () => ({})
|
||||||
default: () => ({})
|
},
|
||||||
},
|
hoverMode: {
|
||||||
hoverMode: {
|
default: false
|
||||||
default: false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
|
|
|
@ -1,41 +0,0 @@
|
||||||
<template>
|
|
||||||
<a-modal
|
|
||||||
v-model:visible="visible"
|
|
||||||
title="修改密码"
|
|
||||||
:mask-closable="false"
|
|
||||||
:width="800"
|
|
||||||
:destroy-on-close="true"
|
|
||||||
@ok="handleOk"
|
|
||||||
@cancel="handleCancel"
|
|
||||||
>
|
|
||||||
</a-modal>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
visible: false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
// 打开
|
|
||||||
showUpdPwdModal(value) {
|
|
||||||
this.visible = true
|
|
||||||
},
|
|
||||||
// 切换icon风格
|
|
||||||
radioGroupChange(e) {
|
|
||||||
this.iconItemDefault = e.target.value
|
|
||||||
},
|
|
||||||
// 选择图标后关闭并返回
|
|
||||||
handleOk() {
|
|
||||||
this.visible = false
|
|
||||||
this.$emit('updPwdCallBack')
|
|
||||||
},
|
|
||||||
handleCancel() {
|
|
||||||
this.visible = false
|
|
||||||
this.$emit('updPwdCallBack')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
|
@ -1,12 +1,11 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="user-bar">
|
<div class="user-bar">
|
||||||
<div v-if="!ismobile" class="search panel-item hidden-sm-and-down" @click="handleSearchClick">
|
<!-- 搜索面板 -->
|
||||||
<search-outlined />
|
<panel-search v-if="!isMobile" />
|
||||||
</div>
|
<div v-if="!isMobile" class="screen panel-item hidden-sm-and-down" @click="fullscreen">
|
||||||
<div v-if="!ismobile" class="screen panel-item hidden-sm-and-down" @click="fullscreen">
|
|
||||||
<fullscreen-outlined />
|
<fullscreen-outlined />
|
||||||
</div>
|
</div>
|
||||||
<devUserMessage />
|
<dev-user-message />
|
||||||
<a-dropdown class="user panel-item">
|
<a-dropdown class="user panel-item">
|
||||||
<div class="user-avatar">
|
<div class="user-avatar">
|
||||||
<a-avatar :src="userInfo.avatar" />
|
<a-avatar :src="userInfo.avatar" />
|
||||||
|
@ -30,7 +29,7 @@
|
||||||
</a-menu>
|
</a-menu>
|
||||||
</template>
|
</template>
|
||||||
</a-dropdown>
|
</a-dropdown>
|
||||||
<a-dropdown v-if="!ismobile" class="panel-item">
|
<a-dropdown v-if="!isMobile" class="panel-item">
|
||||||
<global-outlined />
|
<global-outlined />
|
||||||
<template #overlay>
|
<template #overlay>
|
||||||
<a-menu :selected-keys="lang">
|
<a-menu :selected-keys="lang">
|
||||||
|
@ -43,7 +42,7 @@
|
||||||
</a-menu>
|
</a-menu>
|
||||||
</template>
|
</template>
|
||||||
</a-dropdown>
|
</a-dropdown>
|
||||||
<div v-if="setDeawer === 'true'" class="setting panel-item" @click="openSetting">
|
<div v-if="setDrawer === 'true'" class="setting panel-item" @click="openSetting">
|
||||||
<layout-outlined />
|
<layout-outlined />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -52,141 +51,109 @@
|
||||||
<a-drawer v-model:visible="settingDialog" :closable="false" width="300">
|
<a-drawer v-model:visible="settingDialog" :closable="false" width="300">
|
||||||
<setting />
|
<setting />
|
||||||
</a-drawer>
|
</a-drawer>
|
||||||
<!-- 搜索面板 -->
|
|
||||||
<xn-form-container
|
|
||||||
title="搜索"
|
|
||||||
:visible="searchActive"
|
|
||||||
:closable="false"
|
|
||||||
:footer="null"
|
|
||||||
:width="600"
|
|
||||||
destroyOnClose
|
|
||||||
dialogClass="searchModal"
|
|
||||||
:bodyStyle="{ maxHeight: '520px', overflow: 'auto', padding: '14px' }"
|
|
||||||
@close="searchPanelClose"
|
|
||||||
>
|
|
||||||
<panel-search ref="panelSearch" @close="searchPanelClose" />
|
|
||||||
</xn-form-container>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script setup name="layoutUserBar">
|
||||||
import { createVNode } from 'vue'
|
import { createVNode } from 'vue'
|
||||||
import { ExclamationCircleOutlined } from '@ant-design/icons-vue'
|
import { ExclamationCircleOutlined } from '@ant-design/icons-vue'
|
||||||
import screenfull from 'screenfull'
|
import { Modal } from 'ant-design-vue'
|
||||||
|
import i18n from '@/locales/index'
|
||||||
|
import screenFull from 'screenfull'
|
||||||
import { message } from 'ant-design-vue'
|
import { message } from 'ant-design-vue'
|
||||||
import setting from './setting.vue'
|
import Setting from './setting.vue'
|
||||||
import router from '@/router'
|
import router from '@/router'
|
||||||
import tool from '@/utils/tool'
|
import tool from '@/utils/tool'
|
||||||
|
import config from '@/config/index'
|
||||||
import loginApi from '@/api/auth/loginApi'
|
import loginApi from '@/api/auth/loginApi'
|
||||||
import devUserMessage from './message.vue'
|
import DevUserMessage from './message.vue'
|
||||||
import panelSearch from './panel-search/index.vue'
|
import PanelSearch from './panel-search/index.vue'
|
||||||
import mixinSearch from './mixins/search'
|
|
||||||
import { mapState } from 'pinia'
|
|
||||||
import { globalStore } from '@/store'
|
import { globalStore } from '@/store'
|
||||||
export default {
|
|
||||||
components: {
|
|
||||||
setting,
|
|
||||||
devUserMessage,
|
|
||||||
panelSearch
|
|
||||||
},
|
|
||||||
mixins: [mixinSearch],
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
lang: [],
|
|
||||||
settingDialog: false,
|
|
||||||
userName: '',
|
|
||||||
userNameF: '',
|
|
||||||
setDeawer: import.meta.env.VITE_SET_DRAWER
|
|
||||||
}
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
...mapState(globalStore, ['ismobile', 'userInfo'])
|
|
||||||
},
|
|
||||||
|
|
||||||
created() {
|
const lang = ref(new Array(tool.data.get('APP_LANG') || config.LANG))
|
||||||
// 获取默认语言
|
const settingDialog = ref(false)
|
||||||
this.lang = new Array(this.$TOOL.data.get('APP_LANG') || this.$CONFIG.LANG)
|
const setDrawer = ref(import.meta.env.VITE_SET_DRAWER)
|
||||||
this.userName = this.userInfo?.userName || ''
|
const store = globalStore()
|
||||||
this.userNameF = this.userName.substring(0, 1)
|
const isMobile = computed(() => {
|
||||||
},
|
return store.ismobile
|
||||||
methods: {
|
})
|
||||||
// 个人信息
|
const userInfo = computed(() => {
|
||||||
handleUser(key) {
|
return store.userInfo
|
||||||
if (key === 'uc') {
|
})
|
||||||
router.push({ path: '/usercenter' })
|
const userName = ref(userInfo.value?.userName || '')
|
||||||
}
|
|
||||||
if (key === 'clearCache') {
|
// 个人信息
|
||||||
this.$confirm({
|
const handleUser = (key) => {
|
||||||
title: '提示',
|
if (key === 'uc') {
|
||||||
content: '确认清理所有缓存?',
|
router.push({ path: '/usercenter' })
|
||||||
icon: createVNode(ExclamationCircleOutlined),
|
}
|
||||||
maskClosable: false,
|
if (key === 'clearCache') {
|
||||||
okText: '确定',
|
Modal.confirm({
|
||||||
cancelText: '取消',
|
title: '提示',
|
||||||
onOk() {
|
content: '确认清理所有缓存?',
|
||||||
message.loading('正在清理中...', 1)
|
icon: createVNode(ExclamationCircleOutlined),
|
||||||
|
maskClosable: false,
|
||||||
|
okText: '确定',
|
||||||
|
cancelText: '取消',
|
||||||
|
onOk() {
|
||||||
|
message.loading('正在清理中...', 1)
|
||||||
|
tool.data.clear()
|
||||||
|
setTimeout(() => {
|
||||||
|
router.replace({ path: '/login' })
|
||||||
|
location.reload()
|
||||||
|
}, 100)
|
||||||
|
},
|
||||||
|
onCancel() {}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if (key === 'outLogin') {
|
||||||
|
Modal.confirm({
|
||||||
|
title: '提示',
|
||||||
|
content: '确认退出当前用户?',
|
||||||
|
icon: createVNode(ExclamationCircleOutlined),
|
||||||
|
maskClosable: false,
|
||||||
|
onOk() {
|
||||||
|
// 取得缓存中的token
|
||||||
|
const token = tool.data.get('TOKEN')
|
||||||
|
const param = {
|
||||||
|
token: token
|
||||||
|
}
|
||||||
|
message.loading('退出中...', 1)
|
||||||
|
loginApi
|
||||||
|
.logout(param)
|
||||||
|
.then(() => {
|
||||||
|
// 清理掉个人的一些信息
|
||||||
|
tool.data.remove('TOKEN')
|
||||||
|
tool.data.remove('USER_INFO')
|
||||||
|
tool.data.remove('MENU')
|
||||||
|
tool.data.remove('PERMISSIONS')
|
||||||
|
router.replace({ path: '/login' })
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
tool.data.clear()
|
tool.data.clear()
|
||||||
setTimeout(() => {
|
router.replace({ path: '/login' })
|
||||||
router.replace({ path: '/login' })
|
location.reload()
|
||||||
location.reload()
|
})
|
||||||
}, 100)
|
},
|
||||||
},
|
onCancel() {}
|
||||||
onCancel() {}
|
})
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
if (key === 'outLogin') {
|
// 设置多语言语种
|
||||||
this.$confirm({
|
const handleIn18 = (key) => {
|
||||||
title: '提示',
|
lang.value = []
|
||||||
content: '确认退出当前用户?',
|
lang.value.push(key)
|
||||||
icon: createVNode(ExclamationCircleOutlined),
|
i18n.locale = key
|
||||||
maskClosable: false,
|
tool.data.set('APP_LANG', key)
|
||||||
onOk() {
|
}
|
||||||
// 取得缓存中的token
|
// 设置抽屉
|
||||||
const token = tool.data.get('TOKEN')
|
const openSetting = () => {
|
||||||
const param = {
|
settingDialog.value = true
|
||||||
token: token
|
}
|
||||||
}
|
// 全屏
|
||||||
message.loading('退出中...', 1)
|
const fullscreen = () => {
|
||||||
loginApi
|
const element = document.documentElement
|
||||||
.logout(param)
|
if (screenFull.isEnabled) {
|
||||||
.then(() => {
|
screenFull.toggle(element)
|
||||||
// message.c
|
|
||||||
// 清理掉个人的一些信息
|
|
||||||
tool.data.remove('TOKEN')
|
|
||||||
tool.data.remove('USER_INFO')
|
|
||||||
tool.data.remove('MENU')
|
|
||||||
tool.data.remove('PERMISSIONS')
|
|
||||||
router.replace({ path: '/login' })
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
tool.data.clear()
|
|
||||||
router.replace({ path: '/login' })
|
|
||||||
location.reload()
|
|
||||||
})
|
|
||||||
},
|
|
||||||
onCancel() {}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
},
|
|
||||||
// 设置多语言语种
|
|
||||||
handleIn18(key) {
|
|
||||||
this.lang = []
|
|
||||||
this.lang.push(key)
|
|
||||||
this.$i18n.locale = key
|
|
||||||
this.$TOOL.data.set('APP_LANG', key)
|
|
||||||
},
|
|
||||||
// 设置抽屉
|
|
||||||
openSetting() {
|
|
||||||
this.settingDialog = true
|
|
||||||
},
|
|
||||||
// 全屏
|
|
||||||
fullscreen() {
|
|
||||||
const element = document.documentElement
|
|
||||||
if (screenfull.isEnabled) {
|
|
||||||
screenfull.toggle(element)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
// 搜索
|
|
||||||
fullSearch() {}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -77,7 +77,9 @@
|
||||||
|
|
||||||
const global_store = globalStore()
|
const global_store = globalStore()
|
||||||
|
|
||||||
const userInfo = ref(tool.data.get('USER_INFO'))
|
const userInfo = computed(() => {
|
||||||
|
return global_store.userInfo
|
||||||
|
})
|
||||||
const cropUpload = ref()
|
const cropUpload = ref()
|
||||||
const avatarLoading = ref(false)
|
const avatarLoading = ref(false)
|
||||||
const uploadLogo = () => {
|
const uploadLogo = () => {
|
||||||
|
|
|
@ -39,15 +39,14 @@
|
||||||
import { required } from '@/utils/formRules'
|
import { required } from '@/utils/formRules'
|
||||||
import userCenterApi from '@/api/sys/userCenterApi'
|
import userCenterApi from '@/api/sys/userCenterApi'
|
||||||
import tool from '@/utils/tool'
|
import tool from '@/utils/tool'
|
||||||
|
import { cloneDeep } from 'lodash-es'
|
||||||
import { globalStore } from '@/store'
|
import { globalStore } from '@/store'
|
||||||
|
|
||||||
const store = globalStore()
|
const store = globalStore()
|
||||||
|
|
||||||
const formRef = ref()
|
const formRef = ref()
|
||||||
// 获取用户信息
|
|
||||||
const userInfo = tool.data.get('USER_INFO')
|
|
||||||
let formData = ref({})
|
let formData = ref({})
|
||||||
formData.value = userInfo
|
formData.value = cloneDeep(store.userInfo)
|
||||||
const submitLoading = ref(false)
|
const submitLoading = ref(false)
|
||||||
// 默认要校验的
|
// 默认要校验的
|
||||||
const formRules = {
|
const formRules = {
|
||||||
|
@ -64,7 +63,7 @@
|
||||||
userCenterApi.userUpdateUserInfo(formData.value).then(() => {
|
userCenterApi.userUpdateUserInfo(formData.value).then(() => {
|
||||||
submitLoading.value = false
|
submitLoading.value = false
|
||||||
// 更新前端缓存
|
// 更新前端缓存
|
||||||
store.setUserInfo(formData.value)
|
store.setUserInfo(cloneDeep(formData.value))
|
||||||
tool.data.set('USER_INFO', formData.value)
|
tool.data.set('USER_INFO', formData.value)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
Loading…
Reference in New Issue