kun-galgame-vue/src/components/KUNGalgameSearchBox.vue

311 lines
7.4 KiB
Vue
Raw Normal View History

2023-11-07 09:37:33 +00:00
<script setup lang="ts">
import { Icon } from '@iconify/vue'
import { ref, onBeforeMount } from 'vue'
2023-11-13 14:19:52 +00:00
import { debounce } from '@/utils/debounce'
import { usePersistKUNGalgameHomeStore } from '@/store/modules/home'
import { useTempHomeStore } from '@/store/temp/home'
2023-11-07 09:37:33 +00:00
import { storeToRefs } from 'pinia'
2023-11-13 14:19:52 +00:00
const { searchHistory } = storeToRefs(usePersistKUNGalgameHomeStore())
const { keywords, category } = storeToRefs(useTempHomeStore())
2023-11-07 09:37:33 +00:00
// Value of the input field
const inputValue = ref('')
// Whether to show search history
const isShowSearchHistory = ref(false)
// Style when the input field is active
const inputActiveClass = ref({})
// Define props to tell the input field in which category to search for topics
const props = defineProps(['category'])
// Initialize the content of the search box to prevent content from persisting after page refresh
// Assign the topic category to be searched because the search box will be rendered on three pages
// , corresponding to three categories
onBeforeMount(() => {
keywords.value = ''
category.value = props.category
})
// Define the debounce handling function
const debouncedSearch = debounce((inputValue: string) => {
// Reset page status and loading state before searching
2023-11-13 14:19:52 +00:00
useTempHomeStore().resetPageStatus()
2023-11-07 09:37:33 +00:00
keywords.value = inputValue
}, 300) // 300 milliseconds debounce delay
// When the search box is focused
const handleInputFocus = () => {
if (searchHistory.value.length !== 0) {
isShowSearchHistory.value = true
}
inputActiveClass.value = {
backgroundColor: 'var(--kungalgame-white)',
}
}
// When the search box is blurred
const handleInputBlur = () => {
// Delay hiding the search history so that clicking on the search history can trigger the fill event
setTimeout(() => {
isShowSearchHistory.value = false
inputActiveClass.value = {}
}, 100)
}
// Search function logic
const search = () => {
debouncedSearch(inputValue.value)
if (!searchHistory.value.includes(inputValue.value)) {
// Push the element into the array only when there are no identical elements in the array
searchHistory.value.push(inputValue.value)
}
}
// When the user presses Enter
const handleClickEnter = (event: KeyboardEvent) => {
event.preventDefault()
search()
}
// Clicking the search button triggers the search logic
const handleClickSearch = () => {
if (inputValue.value.trim()) {
search()
}
}
// Clicking on search history
const handleClickHistory = (index: number) => {
inputValue.value = searchHistory.value[index]
}
// Clear search history
const clearSearchHistory = () => {
searchHistory.value = []
}
// Delete search history
const handleDeleteHistory = (historyIndex: number) => {
searchHistory.value.splice(historyIndex, 1)
}
</script>
<template>
<!-- Interactive area search box -->
<div class="container">
<!-- Search box form -->
<form class="search-form">
<!-- Search box content -->
<div class="content">
<!-- Input field -->
<input
v-model="inputValue"
type="search"
class="input"
:style="inputActiveClass"
:placeholder="`${$tm('mainPage.header.search')}`"
@focus="handleInputFocus"
@blur="handleInputBlur"
@input="debouncedSearch(inputValue)"
@keydown.enter="handleClickEnter"
/>
</div>
<!-- Search box icon -->
<div class="search-btn" @click="handleClickSearch">
<Icon icon="line-md:search" />
</div>
</form>
<!-- Search history container -->
<div v-if="isShowSearchHistory" class="history">
<!-- Search history title -->
<div class="title">
<span>{{ $tm('mainPage.header.history') }}</span>
<span @click="clearSearchHistory">
{{ $tm('mainPage.header.clear') }}
</span>
</div>
<!-- Search history -->
<div class="history-container">
<div
class="single-history"
v-for="(history, index) in searchHistory"
:key="index"
@click="handleClickHistory(index)"
>
<span>{{ history }} </span>
<span @click="handleDeleteHistory(index)">
<Icon class="delete" icon="line-md:close-circle" />
</span>
</div>
</div>
</div>
</div>
</template>
<style lang="scss" scoped>
/* Search topics */
.container {
height: 39px;
width: 1px;
justify-content: center;
align-items: center;
/* Prevent line breaks when the page is resized */
white-space: nowrap;
background-color: var(--kungalgame-trans-blue-2);
border: 1px solid var(--kungalgame-blue-4);
flex-grow: 2;
/* Position relative to the secondary menu */
position: relative;
display: flex;
color: var(--kungalgame-font-color-3);
}
/* Search box form */
.search-form {
display: flex;
height: 39px;
/* Grows with the page */
width: 1px;
flex-grow: 1;
/* Centered */
justify-content: center;
align-items: center;
}
/* Search content area */
.content {
width: 100%;
}
/* Input field */
.input {
padding: 0 15px;
height: 39px;
width: 100%;
/* Font size for input during search */
font-size: 16px;
border: none;
background-color: var(--kungalgame-trans-white-5);
color: var(--kungalgame-font-color-3);
transition: all 0.2s;
&::placeholder {
color: var(--kungalgame-font-color-1);
}
}
/* Search button */
.search-btn {
/* Square shape, does not shrink */
height: 39px;
width: 39px;
flex-shrink: 0;
border-left: 1px solid var(--kungalgame-trans-blue-4);
/* Center the search icon */
display: flex;
justify-content: center;
align-items: center;
font-size: 18px;
cursor: pointer;
transition: all 0.2s;
&:hover {
background-color: var(--kungalgame-red-1);
}
&:active {
background-color: var(--kungalgame-red-2);
}
}
/* Search history container */
.history {
width: 100%;
/* Absolute positioning relative to the search area in the nav */
position: absolute;
/* Tight positioning under the topic search area */
top: 39px;
left: 0;
flex-direction: column;
background-color: var(--kungalgame-white);
color: var(--kungalgame-font-color-3);
border: 1px solid var(--kungalgame-red-1);
border-radius: 7px;
box-shadow: var(--shadow);
}
/* Text for the search history title */
.title {
display: flex;
margin: 10px;
/* Distribute two hint texts left and right */
justify-content: space-between;
span {
font-size: 14px;
&:nth-child(2) {
cursor: pointer;
border-bottom: 1.5px solid var(--kungalgame-trans-white-5);
&:hover {
border-bottom: 1.5px solid var(--kungalgame-blue-4);
}
}
}
}
/* Container for storing search history tags */
.history-container {
display: flex;
flex-direction: column;
/* Font for individual search records */
font-size: 13px;
/* Blank space on the left and right sides of the search record */
margin: 10px;
}
.single-history {
width: 100%;
display: flex;
justify-content: space between;
padding: 7px 3px;
margin: 2px 0;
&:hover {
color: var(--kungalgame-blue-4);
.delete {
display: flex;
}
}
span:nth-child(1) {
cursor: default;
position: relative;
display: flex;
overflow: hidden;
}
span:nth-child(2) {
width: 17px;
}
}
/* Delete button */
.delete {
width: 30px;
right: 5px;
font-size: 17px;
position: absolute;
justify-content: center;
align-items: center;
cursor: pointer;
color: var(--kungalgame-font-color-0);
background-color: var(--kungalgame-white);
display: none;
}
</style>