feat: 新增搜索记录

This commit is contained in:
jmal 2025-04-27 11:10:48 +08:00
parent 6b103af57e
commit ba1ea93a4a
4 changed files with 250 additions and 38 deletions

View File

@ -137,6 +137,22 @@ export default {
params params
}) })
}, },
// 最近搜索记录🔍
recentlySearchHistory: function(params) {
return request({
url: '/search/recentlySearchHistory',
method: 'get',
params
})
},
// 删除搜索记录🔍
deleteSearchHistory: function(params) {
return request({
url: '/search/deleteSearchHistory',
method: 'delete',
params
})
},
// 搜索文件🔍 // 搜索文件🔍
searchFile: function(params) { searchFile: function(params) {
return request({ return request({

View File

@ -15,6 +15,22 @@
<div class="search-bar"> <div class="search-bar">
<div class="search-input-wrapper"> <div class="search-input-wrapper">
<div class="search-filters"> <div class="search-filters">
<div>
<el-checkbox
v-model="searchOverall"
class="search-option-checkbox"
@change="handleSearchOverallChange"
>全盘搜索
</el-checkbox>
</div>
<div>
<el-checkbox
v-model="searchMount"
class="search-option-checkbox"
@change="handleSearchMountChange"
>挂载目录
</el-checkbox>
</div>
<el-dropdown @command="handleFileType" trigger="click" class="filter-dropdown"> <el-dropdown @command="handleFileType" trigger="click" class="filter-dropdown">
<el-button type="text" class="filter-btn"> <el-button type="text" class="filter-btn">
<i class="el-icon-document"></i> <i class="el-icon-document"></i>
@ -101,22 +117,31 @@
</div> </div>
</el-dropdown-menu> </el-dropdown-menu>
</el-dropdown> </el-dropdown>
<el-checkbox
v-model="searchMount"
class="search-mount-checkbox"
@change="handleSearchMountChange"
>包含挂载目录</el-checkbox>
</div> </div>
</div> </div>
</div> </div>
<!-- 搜索条件标签 --> <!-- 搜索条件标签 -->
<div class="search-tags"> <div class="search-tags">
<div v-if="searchPath.length > 1"> <div v-if="searchPath.length > 1 && !searchOverall">
<el-tag <el-tag
@close="resetSearchPath"> @close="resetSearchPath">
路径: {{ searchPath }} 路径: {{ searchPath }}
</el-tag> </el-tag>
</div> </div>
<div v-if="searchOverall">
<el-tag
closable
@close="resetSearchOverall">
全盘搜索
</el-tag>
</div>
<div v-if="searchMount">
<el-tag
closable
@close="resetSearchMount">
包含挂载目录
</el-tag>
</div>
<div v-if="fileTypeText !== '全部文件'"> <div v-if="fileTypeText !== '全部文件'">
<el-tag <el-tag
closable closable
@ -138,13 +163,6 @@
时间范围: {{ formatTimeRange }} 时间范围: {{ formatTimeRange }}
</el-tag> </el-tag>
</div> </div>
<div v-if="searchMount">
<el-tag
closable
@close="resetSearchMount">
包含挂载目录
</el-tag>
</div>
</div> </div>
</div> </div>
</div> </div>
@ -187,19 +205,24 @@ export default {
sizeRange: [0, -1], // 默认范围:0 到 1GB sizeRange: [0, -1], // 默认范围:0 到 1GB
fileTypeText: '全部文件', fileTypeText: '全部文件',
searchMount: false, searchMount: false,
filterOption: { searchOverall: false,
type: null, filterOption: this.emptyFilterOption
modifyStart: null,
searchMount: false,
modifyEnd: null,
sizeMin: null,
sizeMax: null
}
} }
}, },
computed: { computed: {
hasSearchConditions() { hasSearchConditions() {
return this.fileType !== 'all' || this.timeRange || this.hasSizeRange || this.searchMount return this.fileType !== 'all' || this.timeRange || this.hasSizeRange || this.searchMount || this.searchOverall
},
emptyFilterOption() {
return {
type: null,
modifyStart: null,
modifyEnd: null,
sizeMin: null,
sizeMax: null,
searchMount: false,
searchOverall: false,
}
}, },
hasSizeRange () { hasSizeRange () {
return this.sizeRange[0] >= 0 && this.sizeRange[1] > 0 return this.sizeRange[0] >= 0 && this.sizeRange[1] > 0
@ -231,6 +254,9 @@ export default {
} }
}, },
watch: { watch: {
filterOption(newValue) {
console.log('filterOption changed', newValue)
}
}, },
methods: { methods: {
// 更新筛选条件并通知父组件 // 更新筛选条件并通知父组件
@ -241,12 +267,52 @@ export default {
modifyEnd: this.timeRange ? new Date(this.timeRange[1]).getTime() : null, modifyEnd: this.timeRange ? new Date(this.timeRange[1]).getTime() : null,
sizeMin: this.hasSizeRange ? this.sizeRange[0] : null, sizeMin: this.hasSizeRange ? this.sizeRange[0] : null,
sizeMax: this.hasSizeRange ? this.sizeRange[1] : null, sizeMax: this.hasSizeRange ? this.sizeRange[1] : null,
searchMount: this.searchMount searchMount: this.searchMount,
searchOverall: this.searchOverall
} }
this.$emit('filter-change') this.$emit('filter-change')
this.$emit('update:filter-option-param', this.filterOption) this.$emit('update:filter-option-param', this.filterOption)
this.$emit('update:has-search-conditions-param', this.hasSearchConditions) this.$emit('update:has-search-conditions-param', this.hasSearchConditions)
localStorage.setItem('searchFilterOption', JSON.stringify(this.filterOption))
},
clearFilterOption() {
this.filterOption = this.emptyFilterOption
console.log('clearFilterOption')
this.setFilterOption(this.filterOption)
},
setFilterOption(filterOption) {
if (!filterOption) return
// 设置文件类型
this.handleFileType(filterOption.type || 'all')
// 设置时间范围
if (filterOption.modifyStart && filterOption.modifyEnd) {
this.timeRange = [
new Date(filterOption.modifyStart),
new Date(filterOption.modifyEnd)
]
} else {
this.timeRange = null
}
// 设置文件大小范围
if (filterOption.sizeMin !== null && filterOption.sizeMax !== null) {
this.sizeRange = [filterOption.sizeMin, filterOption.sizeMax]
} else {
this.sizeRange = [-1, -1]
}
// 设置搜索选项
this.searchMount = !!filterOption.searchMount
this.searchOverall = !!filterOption.searchOverall
// 更新过滤条件
console.log('setFilterOption')
this.updateFilterOption()
}, },
handleFileType(command) { handleFileType(command) {
@ -298,9 +364,8 @@ export default {
this.updateFilterOption() this.updateFilterOption()
}, },
resetKeyword() { resetSearchOverall() {
this.$emit('update:keyword', '') this.searchOverall = false
this.$emit('update:visible', false)
this.updateFilterOption() this.updateFilterOption()
}, },
@ -376,12 +441,15 @@ export default {
handleSearchMountChange() { handleSearchMountChange() {
this.updateFilterOption() this.updateFilterOption()
},
handleSearchOverallChange() {
this.updateFilterOption()
} }
} }
} }
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@import "src/styles/custom-ui.scss";
.search-option-wrapper { .search-option-wrapper {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@ -439,7 +507,7 @@ export default {
.search-filters { .search-filters {
display: flex; display: flex;
align-items: center; align-items: flex-start;
flex-direction: column; flex-direction: column;
gap: 8px; gap: 8px;
@ -485,7 +553,7 @@ export default {
} }
} }
.search-mount-checkbox { .search-option-checkbox {
padding: 8px 12px; padding: 8px 12px;
cursor: pointer; cursor: pointer;
} }

View File

@ -156,17 +156,20 @@
></button-upload> ></button-upload>
</el-popover> </el-popover>
<el-input <el-autocomplete
ref="searchInput"
:class="searchInputClass" :class="searchInputClass"
v-show="showSearchButton" v-show="showSearchButton"
ref="searchInput" :fetch-suggestions="queryRecentlySearchHistory"
hide-loading
:debounce="300"
:popper-append-to-body="false"
:placeholder="`搜索 ${cmdKey} P`" :placeholder="`搜索 ${cmdKey} P`"
v-model="searchFileName" v-model="searchFileName"
@keyup.enter.native="searchFileEnter(searchFileName)" @keyup.enter.native="searchFileEnter(searchFileName)"
@focus="searchInputFocus" @focus="searchInputFocus"
@input="searchFileEnter(searchFileName)"
@blur="searchInputBlur" @blur="searchInputBlur"
@clear="searchFileEnter(searchFileName)" @select="handleSelectSearchHistory"
> >
<template v-slot:suffix> <template v-slot:suffix>
<div> <div>
@ -174,11 +177,12 @@
<i class="el-icon-circle-close"></i> <i class="el-icon-circle-close"></i>
</el-button> </el-button>
<el-popover <el-popover
v-if="searchFileName" v-show="searchFileName"
placement="bottom-end" placement="bottom-end"
popper-class="search-filter-popover" popper-class="search-filter-popover"
trigger="click"> trigger="click">
<search-option <search-option
ref="searchOption"
:has-search-conditions-param.sync="hasSearchFilterOption" :has-search-conditions-param.sync="hasSearchFilterOption"
:keyword.sync="searchFileName" :keyword.sync="searchFileName"
:filter-option-param.sync="filterOption" :filter-option-param.sync="filterOption"
@ -186,13 +190,27 @@
:search-result-count="pagination['total']" :search-result-count="pagination['total']"
@filter-change="searchFilterChange" > @filter-change="searchFilterChange" >
</search-option> </search-option>
<el-button slot="reference" type="text" :class="searchOptionBtnClass"> <el-button slot="reference" type="text" :class="searchOptionBtnClass" @click.native="showSearchOption">
<i class="el-icon-s-operation"></i> <i class="el-icon-s-operation"></i>
</el-button> </el-button>
</el-popover> </el-popover>
</div> </div>
</template> </template>
</el-input>
<!-- 自定义提示项 -->
<template #default="{ item }" class="autocomplete-suggestion-custom">
<div class="suggestion-item">
<div class="suggestion-item-title">
<i class="el-icon-time"></i>
<span class="suggestion-item-text">{{ item.keyword }}</span>
</div>
<div>
<el-button type="text" icon="el-icon-close" class="clear-search-history-btn" @click.native.prevent="clearSearchHistory(item, $event)"></el-button>
</div>
</div>
</template>
</el-autocomplete>
<el-dropdown <el-dropdown
size="medium" size="medium"
style="height: 40px;" style="height: 40px;"
@ -1175,6 +1193,7 @@ export default {
debounceSearch: null,// 搜索防抖 debounceSearch: null,// 搜索防抖
debounceGetFileList: null,// 获取文件列表防抖 debounceGetFileList: null,// 获取文件列表防抖
filterOption: {}, // 搜索选项 filterOption: {}, // 搜索选项
searchHistoryList: [],// 搜索历史列表
hasSearchFilterOption: false, // 是否有搜索选项 hasSearchFilterOption: false, // 是否有搜索选项
fileUsername: '', // 一般用于挂载文件 fileUsername: '', // 一般用于挂载文件
searchInputClass: 'search-input' // 搜索输入框样式 searchInputClass: 'search-input' // 搜索输入框样式
@ -1313,6 +1332,7 @@ export default {
searchFileName(newValue) { searchFileName(newValue) {
if (!newValue) { if (!newValue) {
this.hasSearchFilterOption = false this.hasSearchFilterOption = false
this.$refs.searchOption.clearFilterOption()
} }
} }
}, },
@ -1380,6 +1400,11 @@ export default {
this.$router.replace({ query }) this.$router.replace({ query })
} }
if (this.$route.query.keyword) {
this.filterOption = localStorage.getItem('searchFilterOption') ? JSON.parse(localStorage.getItem('searchFilterOption')) : {}
this.$refs.searchOption.setFilterOption(this.filterOption)
}
let that = this let that = this
window.onresize = function() { window.onresize = function() {
that.clientHeight = that.clientHeight =
@ -2938,6 +2963,7 @@ export default {
this.vmode this.vmode
}&path=${path}${keyword}${searchOpenFolder}${queryTagId}${basePath}${folder}` }&path=${path}${keyword}${searchOpenFolder}${queryTagId}${basePath}${folder}`
) )
console.log('this.filterOption', this.filterOption)
api.searchFile({ api.searchFile({
userId: this.$store.state.user.userId, userId: this.$store.state.user.userId,
username: this.$store.state.user.name, username: this.$store.state.user.name,
@ -2959,6 +2985,7 @@ export default {
querySizeMin: this.filterOption.sizeMin, querySizeMin: this.filterOption.sizeMin,
querySizeMax: this.filterOption.sizeMax, querySizeMax: this.filterOption.sizeMax,
searchMount: this.filterOption.searchMount, searchMount: this.filterOption.searchMount,
searchOverall: this.filterOption.searchOverall,
}).then(res => { }).then(res => {
this.loadData(res, onLoad) this.loadData(res, onLoad)
this.listModeSearch = true this.listModeSearch = true
@ -4511,6 +4538,38 @@ export default {
searchFilterChange() { searchFilterChange() {
this.debounceSearch(this.searchFileName, false) this.debounceSearch(this.searchFileName, false)
}, },
showSearchOption() {
},
queryRecentlySearchHistory(queryString, cb) {
if (this.searchFileName) {
cb([])
return
}
api.recentlySearchHistory({keyword: queryString}).then(res => {
this.searchHistoryList = res.data || []
console.log('clearSearchHistory1', this.searchHistoryList)
cb(this.searchHistoryList)
}).catch(error => {
console.error("Error fetching search history:", error)
this.searchHistoryList = []
cb([])
});
},
handleSelectSearchHistory(item) {
this.searchFileName = item.keyword
this.filterOption = item
this.debounceSearch(this.searchFileName, false)
this.$refs.searchOption.setFilterOption(item)
},
clearSearchHistory(itemToDelete, event) {
event.stopPropagation();
event.preventDefault();
const idToDelete = itemToDelete.id
api.deleteSearchHistory({id: idToDelete}).then(() => {
const index = this.searchHistoryList.findIndex(item => item.id === idToDelete)
this.searchHistoryList.splice(index, 1)
})
},
checkPreviewVisible() { checkPreviewVisible() {
if (this.iframePreviewVisible) { if (this.iframePreviewVisible) {
return true return true
@ -4775,12 +4834,15 @@ export default {
} }
.search-input { .search-input {
width: 150px; width: 230px;
transition: width 0.3s ease-in-out; transition: width 0.3s ease-in-out;
>>> .el-input--suffix .el-input__inner {
padding-right: 60px;
}
} }
.search-input-focus { .search-input-focus {
width: 240px; width: 230px;
} }
.search-close-btn { .search-close-btn {
@ -4808,4 +4870,39 @@ export default {
} }
} }
>>> .el-autocomplete-suggestion li {
padding: 0 5px 0 15px;
}
.suggestion-item {
display: flex;
justify-content: space-between;
align-items: center;
.suggestion-item-title {
display: flex;
gap: 10px;
justify-content: flex-start;
flex-direction: row;
align-items: center;
.suggestion-item-text {
width: 152px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
}
.clear-search-history-btn {
padding: 8px;
display: none;
}
&:hover {
.clear-search-history-btn {
display: block;
}
}
}
</style> </style>

31
src/styles/custom-ui.scss Normal file
View File

@ -0,0 +1,31 @@
>>> .el-checkbox {
&:hover {
background-color: #f5f7fa;
border-radius: 6px;
}
.el-checkbox__inner {
background-color: #f0f0f0; /* 浅灰色背景 */
border-color: #dcdcdc; /* 浅灰色边框 */
border-radius: 4px; /* 圆角 */
}
.el-checkbox__input.is-checked .el-checkbox__inner {
background-color: #a0a0a0; /* 深灰色背景 */
border-color: #a0a0a0; /* 深灰色边框 */
}
.el-checkbox__input.is-checked .el-checkbox__inner::after {
border-color: #ffffff; /* 白色勾号 */
}
.el-checkbox__inner:hover {
//border-color: #b0b0b0; /* hover 时边框颜色 */
cursor: pointer;
}
.el-checkbox__input.is-checked+.el-checkbox__label {
color: #606266;
}
}