mirror of
https://github.com/fofolee/uTools-quickcommand.git
synced 2025-06-07 05:25:39 +08:00
239 lines
6.4 KiB
Vue
239 lines
6.4 KiB
Vue
<template>
|
|
<q-card @mousewheel="mouseHandler">
|
|
<q-virtual-scroll
|
|
ref="scrollBar"
|
|
@scroll="scrollHandler"
|
|
:style="{ maxHeight: listMaxHeight + 'px', height: '100vh' }"
|
|
:virtual-scroll-slice-size="lazyItemSize"
|
|
:virtual-scroll-item-size="itemHeight"
|
|
:items="matchedItems"
|
|
>
|
|
<template v-slot="{ item, index }">
|
|
<div
|
|
:key="index"
|
|
v-ripple
|
|
@mousemove="currentIndex = index"
|
|
@click="clickOK"
|
|
:class="{
|
|
'item-selected': index === currentIndex,
|
|
}"
|
|
:style="{
|
|
height: itemHeight + 'px',
|
|
paddingRight: shortCutWidth - 16 + 'px',
|
|
cursor: 'pointer',
|
|
}"
|
|
>
|
|
<!-- 纯文本 -->
|
|
<q-item v-if="is.text">
|
|
<q-item-section>
|
|
<q-item-label lines="1">{{ item }}</q-item-label>
|
|
</q-item-section>
|
|
</q-item>
|
|
<!-- json -->
|
|
<q-item v-else-if="is.json">
|
|
<q-item-section avatar v-if="item.icon">
|
|
<q-avatar size="34px">
|
|
<q-img :src="item.icon" />
|
|
</q-avatar>
|
|
</q-item-section>
|
|
<q-item-section>
|
|
<q-item-label lines="1">{{ item.title }}</q-item-label>
|
|
<q-item-label lines="1" caption>{{
|
|
item.description
|
|
}}</q-item-label>
|
|
</q-item-section>
|
|
</q-item>
|
|
<!-- html -->
|
|
<q-item v-else-if="is.html">
|
|
<q-item-section>
|
|
<div v-html="item" class="full-width"></div>
|
|
</q-item-section>
|
|
</q-item>
|
|
</div>
|
|
</template>
|
|
</q-virtual-scroll>
|
|
<div
|
|
:style="{ top: 0, bottom: 0, width: shortCutWidth + 'px' }"
|
|
class="fixed-right shortcut"
|
|
>
|
|
<div
|
|
:style="{ height: itemHeight + 'px' }"
|
|
class="flex content-center justify-start q-pa-xs"
|
|
v-for="count in countsPerPage"
|
|
:key="count"
|
|
>
|
|
{{ $q.platform.is.mac ? "⌘" : "Alt" }}+{{ count % countsPerPage }}
|
|
</div>
|
|
</div>
|
|
<q-btn
|
|
v-if="options.options.showCancelButton"
|
|
class="absolute-bottom-right q-ma-xs"
|
|
round
|
|
color="primary"
|
|
icon="close"
|
|
@click="hide"
|
|
/>
|
|
</q-card>
|
|
</template>
|
|
|
|
<script>
|
|
import pinyinMatch from "pinyin-match";
|
|
|
|
export default {
|
|
data() {
|
|
return {
|
|
items: this.options.initItems,
|
|
listMaxHeight: 500,
|
|
currentIndex: 0,
|
|
itemHeight: 50,
|
|
lazyItemSize: 100,
|
|
shortCutWidth: 50,
|
|
searchWords: "",
|
|
topIndex: 0,
|
|
timeoutId: null,
|
|
};
|
|
},
|
|
mounted() {
|
|
this.options.options.enableSearch && this.setSubInput();
|
|
this.addListeners();
|
|
this.setUtoolsHeight(this.itemHeight * this.matchedItemsSize);
|
|
},
|
|
unmounted() {
|
|
this.clear();
|
|
},
|
|
computed: {
|
|
matchedItems() {
|
|
let matchedItems = this.searchWords
|
|
? this.items.filter((x) =>
|
|
pinyinMatch.match(x.title ? x.title : x, this.searchWords)
|
|
)
|
|
: this.items;
|
|
this.setUtoolsHeight(this.itemHeight * matchedItems.length);
|
|
return matchedItems;
|
|
},
|
|
matchedItemsSize() {
|
|
return this.matchedItems.length;
|
|
},
|
|
countsPerPage() {
|
|
return this.listMaxHeight / this.itemHeight;
|
|
},
|
|
bottomIndex() {
|
|
return this.topIndex + this.countsPerPage - 1;
|
|
},
|
|
is() {
|
|
return {
|
|
json: this.options.options.optionType === "json",
|
|
html: this.options.options.optionType === "html",
|
|
text: this.options.options.optionType === "plaintext",
|
|
};
|
|
},
|
|
},
|
|
props: {
|
|
options: Object,
|
|
},
|
|
methods: {
|
|
hide() {
|
|
this.clear();
|
|
this.$emit("hide");
|
|
},
|
|
|
|
clickOK() {
|
|
let selected = this.is.json
|
|
? this.matchedItems[this.currentIndex]
|
|
: {
|
|
id: this.currentIndex,
|
|
text: this.matchedItems[this.currentIndex],
|
|
};
|
|
this.is.json && selected.clickFn ? selected.clickFn(this.matchedItems[this.currentIndex].id) : this.$emit("clickOK", selected);
|
|
this.options.options.closeOnSelect && this.hide();
|
|
},
|
|
|
|
keyHandler(e) {
|
|
e.preventDefault();
|
|
if (this.$q.platform.is.mac ? e.metaKey : e.altKey && !isNaN(e.key)) {
|
|
let index = e.key === "0" ? 10 : parseInt(e.key);
|
|
this.currentIndex = this.topIndex + index - 1;
|
|
return this.clickOK();
|
|
}
|
|
switch (e.keyCode) {
|
|
case 38: // 上
|
|
this.currentIndex = Math.max(0, this.currentIndex - 1);
|
|
break;
|
|
case 40: // 下
|
|
this.currentIndex = Math.min(
|
|
this.matchedItemsSize - 1,
|
|
this.currentIndex + 1
|
|
);
|
|
break;
|
|
case 13: // 回车
|
|
return this.clickOK();
|
|
}
|
|
this.$nextTick(() => {
|
|
document.querySelector(".item-selected").scrollIntoViewIfNeeded(0);
|
|
});
|
|
},
|
|
|
|
scrollHandler(e) {
|
|
this.topIndex = parseInt(e.target.scrollTop / this.itemHeight);
|
|
clearTimeout(this.timeoutId);
|
|
this.timeoutId = setTimeout(() => {
|
|
if (this.currentIndex < this.topIndex)
|
|
this.currentIndex = this.topIndex;
|
|
if (this.currentIndex > this.bottomIndex)
|
|
this.currentIndex = this.bottomIndex;
|
|
this.timeoutId = null;
|
|
}, 200);
|
|
},
|
|
|
|
mouseHandler(e) {
|
|
e.preventDefault();
|
|
this.$refs.scrollBar.$el.scrollBy(
|
|
0,
|
|
(e.deltaY / this.itemHeight).toFixed() * this.itemHeight
|
|
);
|
|
},
|
|
|
|
setSubInput() {
|
|
utools.setSubInput(({ text }) => {
|
|
this.searchWords = text;
|
|
if (this.matchedItems.length < this.currentIndex + 1) this.currentIndex = 0
|
|
}, this.options.options.placeholder);
|
|
},
|
|
|
|
setUtoolsHeight(height) {
|
|
utools.setExpendHeight(Math.min(height, this.listMaxHeight));
|
|
},
|
|
|
|
clear() {
|
|
utools.removeSubInput();
|
|
document.removeEventListener("keydown", this.keyHandler, true);
|
|
this.setUtoolsHeight(this.listMaxHeight);
|
|
},
|
|
|
|
addListeners() {
|
|
// 如果之前启用了子输入框,先移除监听
|
|
if (this.$root.subInputEvent)
|
|
document.removeEventListener(...this.$root.subInputEvent);
|
|
document.addEventListener("keydown", this.keyHandler, true);
|
|
},
|
|
},
|
|
};
|
|
</script>
|
|
|
|
<style scoped>
|
|
.q-card--dark {
|
|
background: var(--q-dark-page);
|
|
}
|
|
.q-item,
|
|
.shortcut {
|
|
user-select: none;
|
|
transition: 0s;
|
|
}
|
|
.item-selected {
|
|
background: #dfe2e6;
|
|
}
|
|
.body--dark .item-selected {
|
|
background: #515151;
|
|
}
|
|
</style>
|