DawnLauncher/src/renderer/views/search/Index.vue

357 lines
12 KiB
Vue

<template>
<div
id="search"
class="top-[34px] fixed z-10 w-4/5 drop-shadow-[0_0_6px_rgba(0,0,0,0.2)]"
style="text-shadow: none"
:style="{
color: $store.state.setting.appearance.theme.fontBasic,
backgroundColor: $store.state.setting.appearance.theme.mainBackground,
borderRadius: $store.state.setting.appearance.backgroundTransparency < 1 && $store.state.setting.appearance.windowRoundedCorners ? '8px' : null,
}"
>
<div
class="flex items-center h-[34px]"
:class="`${!arrayIsEmpty(resultList) ? 'border-b-[1px] !h-[35px]' : ''}`"
:style="{ borderColor: $store.state.setting.appearance.theme.border }"
>
<div class="mx-2 whitespace-nowrap">
<svg class="w-[18px] h-[18px]" :style="{ color: $store.state.setting.appearance.theme.fontBasic }" viewBox="0 0 24 24" v-if="!sourceSearch">
<path
fill="currentColor"
d="M9.5,3A6.5,6.5 0 0,1 16,9.5C16,11.11 15.41,12.59 14.44,13.73L14.71,14H15.5L20.5,19L19,20.5L14,15.5V14.71L13.73,14.44C12.59,15.41 11.11,16 9.5,16A6.5,6.5 0 0,1 3,9.5A6.5,6.5 0 0,1 9.5,3M9.5,5C7,5 5,7 5,9.5C5,12 7,14 9.5,14C12,14 14,12 14,9.5C14,7 12,5 9.5,5Z"
/>
</svg>
<span class="text-sm block" v-else>{{ sourceSearch.name }}</span>
</div>
<input
type="text"
v-model="name"
ref="searchInput"
class="w-full resize-none text-sm py-1 hover:outline-0 focus-visible:outline-0"
:style="{
color: $store.state.setting.appearance.theme.fontBasic,
backgroundColor: $store.state.setting.appearance.theme.mainBackground,
borderColor: $store.state.setting.appearance.theme.border,
}"
/>
<Close @click="close" :key="'close-' + $store.state.setting.appearance.theme.name"></Close>
</div>
<ul
id="search-result-list"
style="overflow-x: hidden"
@click="parentItemRun($event)"
@mouseover="parentMouseover($event)"
@mouseout="parentMouseout($event)"
data-simplebar
>
<li
v-for="(item, index) of resultList"
class="flex items-center p-2 h-[48px] search-result-item"
:key="'search-item-' + item.id + '-' + index"
:id="'result-' + index"
:item-id="item.id"
:classification-parent-id="String(item.classificationIds).split('-').length == 2 ? item.classificationIds.split('-')[0] : item.classificationId"
:classification-child-id="String(item.classificationIds).split('-').length == 2 ? item.classificationIds.split('-')[1] : null"
:item-child="String(item.classificationIds).split('-').length == 2"
:title="getItemTitle(item)"
:index="index"
:style="{ backgroundColor: selected == index ? $hexToRGBA($store.state.setting.appearance.theme.minorBackground, 0.3) : null }"
>
<template v-if="item.type != 5 || item.useAppxBackgroundColor == null || !item.useAppxBackgroundColor">
<div v-if="item.htmlIcon != null" class="w-8 h-8 min-w-[32px] min-h-[32px]" v-html="sanitize(item.htmlIcon)"></div>
<img v-else :src="getIconByClassification(item)" class="w-8 h-8 min-w-[32px] min-h-[32px]" />
</template>
<div v-else class="flex items-center justify-center w-8 h-8 min-w-[32px] min-h-[32px]" style="background-color: rgb(0, 120, 215)">
<img :src="getIconByClassification(item)" :style="[{ width: 32 - 8 + 'px' }, { height: 32 - 8 + 'px' }]" />
</div>
<span class="text-sm ml-2 overflow-hidden text-ellipsis whitespace-nowrap h-[20px]"
>{{ getItemName(item.name) }}<span class="text-xs ml-2">({{ item.classificationName }})</span></span
>
</li>
</ul>
</div>
</template>
<script>
import "simplebar";
import "simplebar/dist/simplebar.css";
import ItemJS from "@/views/item/js/index.js";
import CommonJS from "@/common";
import Close from "@/components/Close";
import IndexJS from "./js/index.js";
import ClassificationJS from "@/views/classification/js/index.js";
const { ipcRenderer } = window.require("electron");
export default {
name: "SearchIndex",
components: { Close },
props: {
show: {
type: Boolean,
},
},
data() {
return {
// 名称
name: null,
// map
itemMap: null,
// 显示筛选后的结果
resultList: null,
// 选中的结果
selected: null,
// 搜索模式
webSearch: false,
sourceSearch: null,
};
},
created() {
this.itemMap = IndexJS.convertToMap(this.$store.state.list);
},
mounted() {
this.$nextTick(() => {
// 输入框默认获取焦点
this.$refs.searchInput.focus();
});
this.resize();
window.addEventListener("resize", this.resize, true);
window.addEventListener("keydown", this.keydown, true);
},
unmounted() {
window.removeEventListener("resize", this.resize, true);
window.removeEventListener("keydown", this.keydown, true);
},
watch: {
name() {
this.search();
},
},
methods: {
/**
* 判断数组是否等于空
*/
arrayIsEmpty: CommonJS.arrayIsEmpty,
/**
* 判断字符串是否为空
*/
strIsEmpty: CommonJS.strIsEmpty,
/**
* 过滤XSS
*/
sanitize: CommonJS.DOMPurify.sanitize,
/**
* 获取图标根据分类
*/
getIconByClassification: CommonJS.getIconByClassification,
/**
* 获取项目名称
*/
getItemName: ItemJS.getName,
/**
* 重新设置宽高
*/
resize() {
// body
let body = document.querySelector("body");
// search
let search = document.getElementById("search");
// result
let result = document.getElementById("search-result-list");
// 设置整个搜索模块居中
search.style.left = (body.clientWidth - search.clientWidth) / 2 + "px";
// 设置搜索结果高度
if (result != null) {
// 高度
result.style.maxHeight = body.clientHeight - 70 * 2 + "px";
}
},
/**
* 搜索
*/
search() {
if (!this.webSearch) {
this.resultList = IndexJS.search(this.name, this.itemMap);
if (!this.arrayIsEmpty(this.resultList)) {
this.selected = 0;
this.$nextTick(() => {
let listEL = document.getElementById("search-result-list");
if (listEL != null) {
let sim = listEL.getElementsByClassName("simplebar-content-wrapper")[0];
if (sim != null) {
sim.scrollTop = 0;
}
}
});
}
}
},
/**
* 父级点击运行
* @param e
*/
parentItemRun(e) {
// 找到search-result-item
let target = this.$getClassElement(e, "search-result-item");
if (target != null) {
// 获取分类ID
let classificationParentId = target.getAttribute("classification-parent-id");
let classificationChildId = target.getAttribute("classification-child-id");
// 项目ID
let itemId = target.getAttribute("item-id");
// 查询项目
if (classificationParentId != null && itemId != null) {
let classification = ClassificationJS.getClassificationById(classificationParentId, classificationChildId);
if (classification != null && !this.arrayIsEmpty(classification.itemList)) {
let item = ItemJS.getItemById(classification, itemId);
if (item != null) {
this.itemRun(item);
}
}
}
}
},
/**
* 运行项目
*/
itemRun(item) {
ItemJS.itemRun(item, null);
this.close();
},
/**
* 父级鼠标悬浮
*/
parentMouseover(e) {
this.$styleMouseover(e, "search-result-item", ["background-color"], [this.$hexToRGBA(this.$store.state.setting.appearance.theme.minorBackground, 0.3)]);
},
/**
* 父级鼠标离开
*/
parentMouseout(e) {
// 获取元素
let target = this.$getClassElement(e, "search-result-item");
if (target != null) {
let index = target.getAttribute("index");
if (index != null) {
if (Number(index) != this.selected) {
this.$styleMouseout(e, "search-result-item", ["background-color"]);
}
}
}
},
/**
* 关闭
*/
close() {
this.webSearch = false;
this.sourceSearch = null;
this.$emit("update:show", false);
},
keydown(e) {
// 提取快捷键
let shortcutKey = CommonJS.setShortcutKey(e, null, false);
// 隐藏搜索框
if (this.$store.state.setting != null) {
if (this.$store.state.setting.item != null) {
if (this.$store.state.setting.item.searchShortcutKey != null && shortcutKey == this.$store.state.setting.item.searchShortcutKey) {
this.close();
e.preventDefault();
return;
}
}
}
// 上下按键 38上 40下
if (e.keyCode == 38 || e.keyCode == 40) {
e.preventDefault();
if (!this.arrayIsEmpty(this.resultList)) {
if (e.keyCode == 38 && this.selected > 0) {
this.selected--;
this.searchResultDivMoveScroll(0);
return;
}
if (e.keyCode == 40 && this.selected < this.resultList.length - 1) {
this.selected++;
this.searchResultDivMoveScroll(1);
return;
}
}
}
// 空格
if (e.keyCode == 32) {
if (!this.webSearch) {
// 判断是否是搜索引擎
if (!this.strIsEmpty(this.name)) {
let flag = false;
if (this.$store.state.setting.webSearch.mode == 0) {
if (this.name.substring(0, 1) == ":") {
flag = true;
}
} else if (this.$store.state.setting.webSearch.mode == 1) {
flag = true;
}
if (flag) {
let keyword = this.$store.state.setting.webSearch.mode == 0 ? this.name.substring(1) : this.name;
for (let searchSource of this.$store.state.setting.webSearch.searchSourceList) {
if (keyword == searchSource.keyword) {
this.webSearch = true;
this.sourceSearch = searchSource;
this.name = null;
this.resultList = null;
this.selected = null;
e.preventDefault();
}
}
}
}
}
}
// enter
if (e.keyCode == 13) {
e.preventDefault();
if (this.webSearch) {
let URL = this.sourceSearch.URL.replace("{w}", this.name == null ? "" : this.name);
ipcRenderer.send("openUrl", URL);
this.close();
} else {
if (this.selected != null && this.resultList.length - 1 >= this.selected) {
this.itemRun(this.resultList[this.selected]);
}
}
}
// 退格键
if (e.keyCode == 8) {
if (this.webSearch) {
if (this.name == null || this.name == "") {
this.webSearch = false;
this.sourceSearch = null;
}
}
}
},
/**
* 移动滚动条
* @param type 0上 1下
*/
searchResultDivMoveScroll(type) {
let itemEl = document.getElementById("result-" + this.selected);
let itemRect = itemEl.getBoundingClientRect();
let listEL = document.getElementById("search-result-list");
let realY = itemRect.y - 34 - 34 - 1 + 48;
if (type == 0) {
if (realY < 48) {
let sim = listEL.getElementsByClassName("simplebar-content-wrapper")[0];
sim.scrollTop = sim.scrollTop - (48 - realY);
}
} else {
if (realY > listEL.clientHeight) {
let sim = listEL.getElementsByClassName("simplebar-content-wrapper")[0];
sim.scrollTop = sim.scrollTop + (realY - listEL.clientHeight);
}
}
},
getItemTitle: ItemJS.getItemTitle,
},
};
</script>
<style scoped></style>