mirror of
https://github.com/fanchenio/DawnLauncher.git
synced 2025-07-14 21:32:10 +08:00
639 lines
18 KiB
Vue
639 lines
18 KiB
Vue
<template>
|
||
<div
|
||
class="overflow-x-hidden max-h-[525px]"
|
||
style="text-shadow: none"
|
||
:style="{
|
||
color: store.setting.appearance.theme.mainFontColor,
|
||
backgroundColor: store.setting.appearance.theme.mainBackgroundColor,
|
||
}"
|
||
>
|
||
<div
|
||
class="flex items-center h-[44px]"
|
||
:class="`${resultList && resultList.length > 0 ? 'border-b-[1px]' : ''}`"
|
||
:style="{
|
||
borderColor: store.setting.appearance.theme.borderColor,
|
||
borderBottomStyle:
|
||
resultList && resultList.length > 0 ? 'solid' : undefined,
|
||
}"
|
||
>
|
||
<div class="mx-2 whitespace-nowrap flex items-center">
|
||
<svg
|
||
class="w-[24px] h-[24px] app-region-drag"
|
||
viewBox="0 96 960 960"
|
||
v-if="!webSearch"
|
||
>
|
||
<path
|
||
fill="currentColor"
|
||
d="M779.385 902.154 528.923 651.693q-30 25.538-69 39.538-39 14-78.385 14-96.1 0-162.665-66.529-66.566-66.529-66.566-162.577t66.529-162.702q66.529-66.654 162.577-66.654 96.049 0 162.702 66.565Q610.769 379.899 610.769 476q0 41.692-14.769 80.692-14.769 39-38.769 66.693l250.462 250.461-28.308 28.308ZM381.538 665.231q79.616 0 134.423-54.808Q570.769 555.615 570.769 476q0-79.615-54.808-134.423-54.807-54.808-134.423-54.808-79.615 0-134.423 54.808Q192.308 396.385 192.308 476q0 79.615 54.807 134.423 54.808 54.808 134.423 54.808Z"
|
||
/>
|
||
</svg>
|
||
<span
|
||
class="text-2xl block app-region-drag"
|
||
v-else-if="webSearch && webSearchSource"
|
||
>{{ webSearchSource.name }}</span
|
||
>
|
||
</div>
|
||
<input
|
||
ref="searchInput"
|
||
type="text"
|
||
v-model="value"
|
||
class="w-full resize-none text-2xl font-light hover:outline-0 focus-visible:outline-0"
|
||
:style="{
|
||
backgroundColor: store.setting.appearance.theme.mainBackgroundColor,
|
||
color: store.setting.appearance.theme.mainFontColor,
|
||
}"
|
||
placeholder="Dawn Launcher"
|
||
/>
|
||
</div>
|
||
<ul
|
||
id="search-result-list"
|
||
class="overflow-x-hidden max-h-[480px]"
|
||
@contextmenu="contextmenu"
|
||
@mouseover="mouseover"
|
||
@mouseout="mouseout"
|
||
@click="parentRunItem"
|
||
@dragover="dragover"
|
||
@drop="drop"
|
||
data-simplebar
|
||
v-cloak
|
||
>
|
||
<li
|
||
v-for="(item, index) of resultList"
|
||
class="item flex items-center px-2 h-[48px]"
|
||
:key="'item-' + item.id + '-' + index"
|
||
:id="'item-' + index"
|
||
:item-id="item.id"
|
||
:style="{
|
||
backgroundColor:
|
||
selected === index ||
|
||
store.quickSearchItemRightMenuItemId === item.id
|
||
? hexToRGBA(
|
||
store.setting.appearance.theme.secondBackgroundColor,
|
||
0.3
|
||
)
|
||
: undefined,
|
||
}"
|
||
:title="getItemTitle(item as Item)"
|
||
:index="index"
|
||
>
|
||
<CustomItemIcon :item="(item as Item)" :icon-size="32"></CustomItemIcon>
|
||
<span
|
||
class="text-sm ml-2 overflow-hidden text-ellipsis whitespace-nowrap h-[20px] flex-1 pr-[10px]"
|
||
>{{ getName(item.name)
|
||
}}<span class="text-xs ml-2">{{
|
||
getSearchItemClassificationName((item as Item).classificationId)
|
||
}}</span></span
|
||
>
|
||
<span
|
||
class="ml-auto text-[12px]"
|
||
v-if="
|
||
store.setting.quickSearch.openShortcutKey !== 'none' && index <= 9
|
||
"
|
||
:style="{
|
||
color: hexToRGBA(store.setting.appearance.theme.mainFontColor, 0.7),
|
||
}"
|
||
>{{
|
||
showHistory
|
||
? "Alt + "
|
||
: store.setting.quickSearch.openShortcutKey === "numberKey"
|
||
? store.language.numberKey
|
||
: store.setting.quickSearch.openShortcutKey === "ctrlNumberKey"
|
||
? "Ctrl + "
|
||
: "Alt + "
|
||
}}{{ index + 1 === 10 ? 0 : index + 1 }}</span
|
||
>
|
||
</li>
|
||
</ul>
|
||
</div>
|
||
</template>
|
||
<script setup lang="ts">
|
||
import { ref, onMounted, onUnmounted, nextTick, watch } from "vue";
|
||
import { convertClassificationList } from "../classification/js";
|
||
import {
|
||
convertItemList,
|
||
getItemById,
|
||
getItemSearchMap,
|
||
getName,
|
||
itemHoverStyle,
|
||
itemRemoveStyle,
|
||
run,
|
||
searchItem,
|
||
getItemTitle,
|
||
filterExcludeSearchItemList,
|
||
sort,
|
||
} from "../item/js";
|
||
import { findElement, unlistens } from "../../utils/common";
|
||
import { WebSearchSource } from "../../../types/setting";
|
||
import { CommonItem, Item } from "../../../types/item";
|
||
import { getSearchItemClassificationName } from "../classification/js/index";
|
||
import "simplebar";
|
||
import "simplebar/dist/simplebar.css";
|
||
import CustomItemIcon from "../../components/CustomItemIcon.vue";
|
||
import { getClassElement, hexToRGBA } from "../../utils/style";
|
||
import { convert } from "../../../commons/utils/common";
|
||
import { itemAllRemoveStyle, searchResultDivMoveScroll } from "./js/index";
|
||
import { useMainStore } from "../../store";
|
||
// pinia
|
||
const store = useMainStore();
|
||
// 显示历史记录
|
||
let showHistory = ref(false);
|
||
// 搜索框
|
||
let searchInput = ref<any>(null);
|
||
// 搜索内容
|
||
let value = ref<string | null>(null);
|
||
// 搜索模式
|
||
let webSearch = ref(false);
|
||
// 搜索模式对应的实体
|
||
let webSearchSource = ref<WebSearchSource | null>(null);
|
||
// 选中的项
|
||
let selected = ref<number>(0);
|
||
// 结果列表
|
||
let resultList = ref<Array<CommonItem | Item>>([]);
|
||
// 搜索Map
|
||
let searchMap = new Map();
|
||
// 监听值
|
||
watch(
|
||
() => value.value,
|
||
() => {
|
||
search();
|
||
}
|
||
);
|
||
// 搜索
|
||
function search() {
|
||
if (!webSearch.value) {
|
||
if (value.value) {
|
||
showHistory.value = false;
|
||
resultList.value = searchItem(value.value, searchMap, 50);
|
||
if (resultList.value && resultList.value.length > 0) {
|
||
if (
|
||
store.setting.quickSearch.openNow &&
|
||
resultList.value.length === 1
|
||
) {
|
||
// 仅剩一项时立即打开
|
||
runItem(resultList.value[0] as Item);
|
||
return;
|
||
}
|
||
}
|
||
} else {
|
||
// 获取历史记录
|
||
getHistory();
|
||
}
|
||
// 重置滚动条
|
||
resetScroll();
|
||
// 刷新DOM完毕执行
|
||
nextTick(() => {
|
||
// 设置窗口高度
|
||
let height =
|
||
!resultList.value || resultList.value.length === 0
|
||
? 0
|
||
: resultList.value.length > 9
|
||
? 10 * 48 + 1
|
||
: resultList.value.length * 48 + 1;
|
||
window.quickSearch.setWindowHeight(height + 44);
|
||
});
|
||
}
|
||
}
|
||
/**
|
||
* 重置滚动条
|
||
*/
|
||
function resetScroll() {
|
||
selected.value = 0;
|
||
// 刷新DOM完毕执行
|
||
nextTick(() => {
|
||
let listEL = document.getElementById("search-result-list");
|
||
if (listEL) {
|
||
let sim = listEL.getElementsByClassName("simplebar-content-wrapper")[0];
|
||
if (sim) {
|
||
sim.scrollTop = 0;
|
||
}
|
||
}
|
||
});
|
||
}
|
||
// 查询数据
|
||
function getData() {
|
||
// 分类
|
||
store.classificationList = convertClassificationList(
|
||
window.classification.list()
|
||
);
|
||
// 项目
|
||
store.itemMap = convertItemList(window.item.list());
|
||
// 过滤排除搜索
|
||
let itemList: Array<Item> = filterExcludeSearchItemList();
|
||
// 搜索Map
|
||
searchMap = getItemSearchMap(
|
||
itemList,
|
||
store.setting.quickSearch.matchConditionsRemark
|
||
);
|
||
// 历史记录
|
||
getHistory();
|
||
}
|
||
// 获取历史记录
|
||
function getHistory() {
|
||
if (store.setting.quickSearch.showHistory) {
|
||
// 过滤排除搜索
|
||
let itemList: Array<Item> = filterExcludeSearchItemList().filter((item) => {
|
||
if (
|
||
store.setting.quickSearch.showHistorySort === "openNumber" &&
|
||
item.data.quickSearchOpenNumber > 0
|
||
) {
|
||
return true;
|
||
} else if (
|
||
store.setting.quickSearch.showHistorySort === "lastOpen" &&
|
||
item.data.quickSearchLastOpen > 0
|
||
) {
|
||
return true;
|
||
}
|
||
return false;
|
||
});
|
||
// 按照指定顺序排序
|
||
let sortList = sort(
|
||
itemList,
|
||
store.setting.quickSearch.showHistorySort === "openNumber"
|
||
? "quickSearchOpenNumber"
|
||
: "quickSearchLastOpen"
|
||
);
|
||
if (sortList && sortList.length > 0) {
|
||
// 截取数据
|
||
if (sortList.length > 10) {
|
||
resultList.value = sortList.slice(0, 10);
|
||
} else {
|
||
resultList.value = sortList;
|
||
}
|
||
showHistory.value = true;
|
||
} else {
|
||
resultList.value = [];
|
||
showHistory.value = false;
|
||
}
|
||
} else {
|
||
resultList.value = [];
|
||
showHistory.value = false;
|
||
}
|
||
// 重置滚动条
|
||
resetScroll();
|
||
// 刷新DOM完毕执行
|
||
nextTick(() => {
|
||
// 设置窗口高度
|
||
let height =
|
||
!resultList.value || resultList.value.length === 0
|
||
? 0
|
||
: resultList.value.length > 9
|
||
? 10 * 48 + 1
|
||
: resultList.value.length * 48 + 1;
|
||
window.quickSearch.setWindowHeight(height + 44);
|
||
});
|
||
}
|
||
// 父级运行项目
|
||
function parentRunItem(e: any) {
|
||
// 找到item
|
||
let itemElement = getClassElement(e, "item");
|
||
if (itemElement) {
|
||
// 项目ID
|
||
let itemId = parseInt(itemElement.getAttribute("item-id"));
|
||
// 查询项目
|
||
let item = getItemById(itemId);
|
||
if (item && item.data) {
|
||
runItem(item);
|
||
}
|
||
}
|
||
}
|
||
// 运行项目
|
||
function runItem(item: Item) {
|
||
if (store.setting.quickSearch.openAfterHideQuickSearchWindow) {
|
||
hide();
|
||
}
|
||
run("quickSearch", "open", item);
|
||
}
|
||
// 隐藏窗口
|
||
function hide() {
|
||
window.quickSearch.hideWindow();
|
||
}
|
||
// 监听右键
|
||
function contextmenu(e: any) {
|
||
e.preventDefault();
|
||
e.stopPropagation();
|
||
// 当前项目
|
||
let item: Item | null = null;
|
||
// 判断是在哪个区域右键
|
||
if (getClassElement(e, "item")) {
|
||
// 项目右键
|
||
// 获取项目ID
|
||
let element = getClassElement(e, "item");
|
||
// 项目ID
|
||
let id = parseInt(element.getAttribute("item-id"));
|
||
// 获取项目
|
||
item = convert(getItemById(id));
|
||
}
|
||
if (item) {
|
||
// 弹出菜单
|
||
store.quickSearchItemRightMenuItemId = item.id;
|
||
window.item.showRightMenu({
|
||
item,
|
||
x: e.screenX,
|
||
y: e.screenY,
|
||
type: "quickSearch",
|
||
});
|
||
}
|
||
}
|
||
// dragover
|
||
function dragover(e: any) {
|
||
// 从程序外拖动文件到项目图标上时用此项目打开文件
|
||
if (store.setting.quickSearch.useItemOpen) {
|
||
// 选中效果
|
||
let target = findElement(e.target, "item");
|
||
// 取消选中效果
|
||
let itemList = document.getElementsByClassName("item");
|
||
for (let i = 0; i < itemList.length; i++) {
|
||
const element = itemList[i] as HTMLElement;
|
||
let index = element.getAttribute("index") as any;
|
||
if (
|
||
element.style.backgroundColor &&
|
||
element !== target &&
|
||
selected.value !== parseInt(index)
|
||
) {
|
||
itemRemoveStyle(element, "item");
|
||
}
|
||
}
|
||
// 选中效果
|
||
if (target) {
|
||
itemHoverStyle(target, "item");
|
||
}
|
||
}
|
||
e.preventDefault();
|
||
e.stopPropagation();
|
||
}
|
||
// drop
|
||
function drop(e: any) {
|
||
// 尝试获取项目
|
||
let item: Item | null = null;
|
||
let itemElement = findElement(e.target, "item");
|
||
if (itemElement) {
|
||
let id = itemElement.getAttribute("item-id");
|
||
if (id) {
|
||
item = getItemById(parseInt(id));
|
||
}
|
||
}
|
||
// 获取文件列表
|
||
let pathList = [];
|
||
for (const file of e.dataTransfer.files) {
|
||
pathList.push(file.path);
|
||
}
|
||
// 从程序外拖动文件到项目图标上时用此项目打开文件
|
||
if (store.setting.quickSearch.useItemOpen && item && pathList.length > 0) {
|
||
// 如果相同路径则不打开
|
||
if (pathList.length === 1 && pathList[0] === item.data.target) {
|
||
return;
|
||
}
|
||
let params = "";
|
||
for (let i = 0; i < pathList.length; i++) {
|
||
if (i > 0) {
|
||
params += " ";
|
||
}
|
||
params += '"' + pathList[i] + '"';
|
||
}
|
||
if (item.data.params) {
|
||
params += " " + item.data.params;
|
||
}
|
||
let copyItem: Item = convert(item);
|
||
copyItem.data.params = params;
|
||
// 运行
|
||
runItem(copyItem);
|
||
}
|
||
e.preventDefault();
|
||
e.stopPropagation();
|
||
}
|
||
// keydown
|
||
function keydown(e: any) {
|
||
// esc
|
||
if (e.keyCode == 27) {
|
||
hide();
|
||
return;
|
||
}
|
||
// 上下按键 38上 40下
|
||
if (e.keyCode === 38 || e.keyCode === 40) {
|
||
e.preventDefault();
|
||
if (resultList.value && resultList.value.length > 0) {
|
||
if (e.keyCode === 38 && selected.value > 0) {
|
||
selected.value = selected.value - 1;
|
||
searchResultDivMoveScroll("item", 44, "up", selected.value);
|
||
e.stopPropagation();
|
||
e.preventDefault();
|
||
return;
|
||
}
|
||
if (e.keyCode === 40 && selected.value < resultList.value.length - 1) {
|
||
selected.value = selected.value + 1;
|
||
searchResultDivMoveScroll("item", 44, "down", selected.value);
|
||
e.stopPropagation();
|
||
e.preventDefault();
|
||
return;
|
||
}
|
||
}
|
||
}
|
||
// 空格
|
||
if (e.keyCode === 32) {
|
||
if (!webSearch.value) {
|
||
// 判断是否是搜索引擎
|
||
if (value.value && value.value.trim() !== "") {
|
||
let flag = false;
|
||
if (store.setting.webSearch.mode === 0) {
|
||
if (value.value.substring(0, 1) === ":") {
|
||
flag = true;
|
||
}
|
||
} else if (store.setting.webSearch.mode === 1) {
|
||
flag = true;
|
||
}
|
||
if (flag) {
|
||
let keyword =
|
||
store.setting.webSearch.mode == 0
|
||
? value.value.substring(1)
|
||
: value.value;
|
||
for (let searchSource of store.setting.webSearch.searchSourceList) {
|
||
if (keyword === searchSource.keyword) {
|
||
webSearch.value = true;
|
||
webSearchSource.value = searchSource;
|
||
value.value = null;
|
||
resultList.value = [];
|
||
selected.value = 0;
|
||
window.quickSearch.setWindowHeight(44);
|
||
e.stopPropagation();
|
||
e.preventDefault();
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
// enter
|
||
if (e.keyCode === 13) {
|
||
if (webSearch.value && webSearchSource.value) {
|
||
let url = webSearchSource.value.url.replace("{w}", value.value ?? "");
|
||
window.api.openURL(url);
|
||
if (store.setting.quickSearch.openAfterHideQuickSearchWindow) {
|
||
hide();
|
||
}
|
||
} else if (
|
||
!webSearch.value &&
|
||
resultList.value.length - 1 >= selected.value
|
||
) {
|
||
runItem(resultList.value[selected.value] as Item);
|
||
}
|
||
e.stopPropagation();
|
||
e.preventDefault();
|
||
}
|
||
// 退格键
|
||
if (e.keyCode === 8) {
|
||
if (webSearch.value) {
|
||
if (!value.value || value.value.trim() === "") {
|
||
webSearch.value = false;
|
||
webSearchSource.value = null;
|
||
window.quickSearch.setWindowHeight(44);
|
||
}
|
||
}
|
||
}
|
||
if (showHistory.value) {
|
||
// 历史记录使用alt+数字键
|
||
if (
|
||
e.altKey &&
|
||
e.keyCode !== 18 &&
|
||
((e.keyCode >= 48 && e.keyCode <= 57) ||
|
||
(e.keyCode >= 96 && e.keyCode <= 105))
|
||
) {
|
||
let index;
|
||
if (e.key === 0) {
|
||
index = 9;
|
||
} else {
|
||
index = e.key - 1;
|
||
}
|
||
if (resultList.value && index < resultList.value.length) {
|
||
runItem(resultList.value[index] as Item);
|
||
e.stopPropagation();
|
||
e.preventDefault();
|
||
}
|
||
}
|
||
} else {
|
||
if (store.setting.quickSearch.openShortcutKey !== "none") {
|
||
let flag = false;
|
||
if (store.setting.quickSearch.openShortcutKey === "numberKey") {
|
||
flag = true;
|
||
} else if (
|
||
store.setting.quickSearch.openShortcutKey === "ctrlNumberKey" &&
|
||
e.ctrlKey &&
|
||
e.keyCode !== 17
|
||
) {
|
||
flag = true;
|
||
} else if (
|
||
store.setting.quickSearch.openShortcutKey === "altNumberKey" &&
|
||
e.altKey &&
|
||
e.keyCode !== 18
|
||
) {
|
||
flag = true;
|
||
}
|
||
if (
|
||
flag &&
|
||
((e.keyCode >= 48 && e.keyCode <= 57) ||
|
||
(e.keyCode >= 96 && e.keyCode <= 105))
|
||
) {
|
||
// 如果是单纯的数字键需要先搜索一遍数据,如果搜索结果大于等于1条的话,就继续查询,否则的话是立即执行
|
||
if (store.setting.quickSearch.openShortcutKey === "numberKey") {
|
||
let resultList = searchItem(value.value + e.key, searchMap);
|
||
if (resultList && resultList.length > 0) {
|
||
flag = false;
|
||
}
|
||
}
|
||
if (flag) {
|
||
let index;
|
||
if (e.key === 0) {
|
||
index = 9;
|
||
} else {
|
||
index = e.key - 1;
|
||
}
|
||
if (resultList.value && index < resultList.value.length) {
|
||
runItem(resultList.value[index] as Item);
|
||
e.stopPropagation();
|
||
e.preventDefault();
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
// 加载完dom后,设置初始化完毕
|
||
nextTick(() => {
|
||
window.quickSearch.initFinished();
|
||
});
|
||
// mouseover
|
||
function mouseover(e: any) {
|
||
// 鼠标经过添加样式
|
||
if (getClassElement(e, "item") && !store.quickSearchItemRightMenuItemId) {
|
||
itemHoverStyle(e, "item");
|
||
}
|
||
}
|
||
// mouseout
|
||
function mouseout(e: any) {
|
||
// 鼠标移走删除样式
|
||
if (getClassElement(e, "item") && !store.quickSearchItemRightMenuItemId) {
|
||
let element = getClassElement(e, "item");
|
||
let index = element.getAttribute("index");
|
||
if (selected.value !== parseInt(index)) {
|
||
itemRemoveStyle(e, "item");
|
||
}
|
||
}
|
||
}
|
||
// 监听
|
||
let listens: Array<Function> = [];
|
||
// moutned
|
||
onMounted(() => {
|
||
window.addEventListener("keydown", keydown, true);
|
||
// 监听显示窗口之前
|
||
listens.push(
|
||
window.quickSearch.onShowWindowBefore((data) => {
|
||
// 查询数据
|
||
getData();
|
||
// 聚焦文本框
|
||
searchInput.value.focus();
|
||
// 刷新DOM完毕执行
|
||
nextTick(() => {
|
||
// 显示窗口
|
||
window.quickSearch.showWindow();
|
||
});
|
||
})
|
||
);
|
||
// 清空数据
|
||
listens.push(
|
||
window.quickSearch.onClearData((data) => {
|
||
// 清空数据
|
||
value.value = null;
|
||
resultList.value = [];
|
||
selected.value = 0;
|
||
webSearch.value = false;
|
||
webSearchSource.value = null;
|
||
})
|
||
);
|
||
// 监听项目右键菜单关闭
|
||
listens.push(
|
||
window.item.onRightMenuClose((data) => {
|
||
store.quickSearchItemRightMenuItemId = null;
|
||
itemAllRemoveStyle("item", selected.value);
|
||
})
|
||
);
|
||
// 监听项目资源管理器菜单
|
||
listens.push(
|
||
window.item.onExplorerMenu((data) => {
|
||
if (data.type === "quickSearch") {
|
||
store.quickSearchItemRightMenuItemId = data.id;
|
||
}
|
||
})
|
||
);
|
||
});
|
||
// unmounted
|
||
onUnmounted(() => {
|
||
window.removeEventListener("keydown", keydown, true);
|
||
// 删除监听
|
||
unlistens(listens);
|
||
});
|
||
</script>
|
||
<style scoped>
|
||
[v-cloak] {
|
||
display: none !important;
|
||
}
|
||
</style>
|