408 lines
16 KiB
Vue
408 lines
16 KiB
Vue
<script>
|
|
// 全局搜索同时展示实例与商品结果,负责输入防抖、过期请求失效和结果跳转。
|
|
import "~/assets/styles/GlobalSearch.BQqObidy.css";
|
|
import { toDisplayString } from "vue";
|
|
import { renderList } from "vue";
|
|
import { onMounted } from "vue";
|
|
import { useId } from "vue";
|
|
import { onUnmounted } from "vue";
|
|
import { ref } from "vue";
|
|
import { createCommentVNode } from "vue";
|
|
import { computed } from "vue";
|
|
import { isRef } from "vue";
|
|
import { createElementBlock } from "vue";
|
|
import { Fragment } from "vue";
|
|
import { withDirectives } from "vue";
|
|
import { createElementVNode } from "vue";
|
|
import { createTextVNode } from "vue";
|
|
import { unref } from "vue";
|
|
import { defineComponent } from "vue";
|
|
import { openBlock } from "vue";
|
|
import { vModelText } from "vue";
|
|
import { withModifiers } from "vue";
|
|
import { withKeys } from "vue";
|
|
import { useRouter } from "#app";
|
|
import { useApi } from "~/lib/auth.js";
|
|
import { t as _plugin_vue_export_helper_t } from "~/lib/_plugin-vue_export-helper.js";
|
|
import { n as product_name_n } from "~/lib/product-name.js";
|
|
var w = {
|
|
class: `gs-input`,
|
|
},
|
|
T = [`id`, `aria-controls`],
|
|
E = [`aria-busy`],
|
|
D = {
|
|
key: 0,
|
|
class: `gs-state`,
|
|
role: `status`,
|
|
},
|
|
O = {
|
|
key: 1,
|
|
class: `gs-state gs-error`,
|
|
role: `alert`,
|
|
},
|
|
k = {
|
|
key: 0,
|
|
class: `gs-group`,
|
|
},
|
|
A = [`onClick`],
|
|
j = {
|
|
class: `gs-item-name`,
|
|
},
|
|
ee = {
|
|
class: `gs-item-sub`,
|
|
},
|
|
te = {
|
|
key: 1,
|
|
class: `gs-group`,
|
|
},
|
|
ne = [`onClick`],
|
|
M = {
|
|
class: `gs-item-name`,
|
|
},
|
|
N = {
|
|
class: `gs-item-sub`,
|
|
},
|
|
P = {
|
|
key: 2,
|
|
class: `gs-state`,
|
|
role: `status`,
|
|
},
|
|
F = Object.assign(
|
|
_plugin_vue_export_helper_t(
|
|
defineComponent({
|
|
__name: `GlobalSearch`,
|
|
setup(h) {
|
|
let S = useApi(),
|
|
F = useRouter(),
|
|
I = ref(``),
|
|
L = ref(!1),
|
|
R = ref(!1),
|
|
z = ref(``),
|
|
B = ref(null),
|
|
V = ref(null),
|
|
H = ref(null),
|
|
U = `global-search-${useId()}`,
|
|
W = null,
|
|
G = 0,
|
|
K = !1;
|
|
// 取消待执行的防抖任务并递增代次,已发出的旧请求也不能更新当前结果。
|
|
function q() {
|
|
(W !== null && clearTimeout(W),
|
|
(W = null),
|
|
(G += 1),
|
|
(R.value = !1));
|
|
}
|
|
function J(e = !1) {
|
|
(q(), e && H.value?.focus(), (L.value = !1));
|
|
}
|
|
// 搜索使用去空白关键词;只有当前代次且组件未卸载时才写入结果或错误。
|
|
async function Y() {
|
|
if ((q(), K)) return;
|
|
let e = G,
|
|
t = I.value.trim();
|
|
if (((z.value = ``), (B.value = null), !t)) {
|
|
L.value = !1;
|
|
return;
|
|
}
|
|
((R.value = !0), (L.value = !0));
|
|
try {
|
|
let n = await S.get(`/global_search`, {
|
|
keywords: t,
|
|
});
|
|
if (e !== G || K) return;
|
|
B.value = n;
|
|
} catch (t) {
|
|
if (e !== G || K) return;
|
|
((B.value = null),
|
|
(z.value =
|
|
t instanceof Error ? t.message : `搜索失败,请重试`));
|
|
} finally {
|
|
e === G && !K && (R.value = !1);
|
|
}
|
|
}
|
|
// 每次输入先关闭旧结果,停顿 300ms 后再查询,避免逐字符请求。
|
|
function X() {
|
|
(J(),
|
|
(B.value = null),
|
|
(z.value = ``),
|
|
I.value.trim() && (W = setTimeout(Y, 300)));
|
|
}
|
|
let Z = computed(() => B.value?.hosts ?? []),
|
|
Q = computed(() => B.value?.products ?? []),
|
|
re = computed(
|
|
() =>
|
|
!R.value &&
|
|
B.value != null &&
|
|
!Z.value.length &&
|
|
!Q.value.length,
|
|
);
|
|
function ie(e) {
|
|
(J(),
|
|
(I.value = ``),
|
|
(B.value = null),
|
|
F.push(`/servers/server?id=${e}`));
|
|
}
|
|
function ae(e) {
|
|
(J(), (I.value = ``), (B.value = null), F.push(`/products/${e}`));
|
|
}
|
|
function $(e) {
|
|
V.value && !V.value.contains(e.target) && J();
|
|
}
|
|
return (
|
|
onMounted(() => document.addEventListener(`click`, $)),
|
|
onUnmounted(() => {
|
|
((K = !0), document.removeEventListener(`click`, $), q());
|
|
}),
|
|
(n, r) => (
|
|
openBlock(),
|
|
createElementBlock(
|
|
`div`,
|
|
{
|
|
ref_key: `box`,
|
|
ref: V,
|
|
class: `gs`,
|
|
onKeydown: (r[2] ||= withKeys(
|
|
withModifiers((e) => J(!0), [`stop`, `prevent`]),
|
|
[`esc`],
|
|
)),
|
|
},
|
|
[
|
|
createElementVNode(`span`, w, [
|
|
(r[3] ||= createElementVNode(
|
|
`i`,
|
|
{
|
|
class: `pi pi-search`,
|
|
"aria-hidden": `true`,
|
|
},
|
|
null,
|
|
-1,
|
|
)),
|
|
withDirectives(
|
|
createElementVNode(
|
|
`input`,
|
|
{
|
|
id: `${U}-input`,
|
|
name: `keywords`,
|
|
ref_key: `input`,
|
|
ref: H,
|
|
"onUpdate:modelValue": (r[0] ||= (e) =>
|
|
isRef(I) ? (I.value = e) : null),
|
|
type: `search`,
|
|
"aria-label": `搜索产品标识或商品名`,
|
|
"aria-controls": unref(L) ? U : void 0,
|
|
autocomplete: `off`,
|
|
placeholder: `搜索产品标识 / 商品名`,
|
|
onInput: X,
|
|
onKeyup: withKeys(Y, [`enter`]),
|
|
onFocus: (r[1] ||= (e) =>
|
|
unref(I).trim() &&
|
|
(unref(B) || unref(z)) &&
|
|
(L.value = !0)),
|
|
},
|
|
null,
|
|
40,
|
|
T,
|
|
),
|
|
[[vModelText, unref(I)]],
|
|
),
|
|
]),
|
|
unref(L)
|
|
? (openBlock(),
|
|
createElementBlock(
|
|
`div`,
|
|
{
|
|
key: 0,
|
|
id: U,
|
|
class: `gs-panel`,
|
|
role: `region`,
|
|
"aria-label": `搜索结果`,
|
|
"aria-busy": unref(R),
|
|
},
|
|
[
|
|
unref(R)
|
|
? (openBlock(),
|
|
createElementBlock(`div`, D, [
|
|
...(r[4] ||= [
|
|
createElementVNode(
|
|
`i`,
|
|
{
|
|
class: `pi pi-spin pi-spinner`,
|
|
"aria-hidden": `true`,
|
|
},
|
|
null,
|
|
-1,
|
|
),
|
|
createTextVNode(` 搜索中…`, -1),
|
|
]),
|
|
]))
|
|
: unref(z)
|
|
? (openBlock(),
|
|
createElementBlock(`div`, O, [
|
|
createElementVNode(
|
|
`span`,
|
|
null,
|
|
toDisplayString(unref(z)),
|
|
1,
|
|
),
|
|
createElementVNode(
|
|
`button`,
|
|
{
|
|
type: `button`,
|
|
class: `gs-retry`,
|
|
onClick: Y,
|
|
},
|
|
`重试搜索`,
|
|
),
|
|
]))
|
|
: (openBlock(),
|
|
createElementBlock(
|
|
Fragment,
|
|
{
|
|
key: 2,
|
|
},
|
|
[
|
|
unref(Z).length
|
|
? (openBlock(),
|
|
createElementBlock(`div`, k, [
|
|
(r[5] ||= createElementVNode(
|
|
`div`,
|
|
{
|
|
class: `gs-group-title`,
|
|
},
|
|
`产品`,
|
|
-1,
|
|
)),
|
|
(openBlock(!0),
|
|
createElementBlock(
|
|
Fragment,
|
|
null,
|
|
renderList(
|
|
unref(Z),
|
|
(t) => (
|
|
openBlock(),
|
|
createElementBlock(
|
|
`button`,
|
|
{
|
|
key: t.id,
|
|
type: `button`,
|
|
class: `gs-item`,
|
|
onClick: (e) => ie(t.id),
|
|
},
|
|
[
|
|
createElementVNode(
|
|
`span`,
|
|
j,
|
|
toDisplayString(
|
|
unref(product_name_n)(
|
|
t.product_name,
|
|
),
|
|
),
|
|
1,
|
|
),
|
|
createElementVNode(
|
|
`span`,
|
|
ee,
|
|
`#` +
|
|
toDisplayString(t.id) +
|
|
` · ` +
|
|
toDisplayString(t.name),
|
|
1,
|
|
),
|
|
],
|
|
8,
|
|
A,
|
|
)
|
|
),
|
|
),
|
|
128,
|
|
)),
|
|
]))
|
|
: createCommentVNode(``, !0),
|
|
unref(Q).length
|
|
? (openBlock(),
|
|
createElementBlock(`div`, te, [
|
|
(r[6] ||= createElementVNode(
|
|
`div`,
|
|
{
|
|
class: `gs-group-title`,
|
|
},
|
|
`商品`,
|
|
-1,
|
|
)),
|
|
(openBlock(!0),
|
|
createElementBlock(
|
|
Fragment,
|
|
null,
|
|
renderList(
|
|
unref(Q),
|
|
(t) => (
|
|
openBlock(),
|
|
createElementBlock(
|
|
`button`,
|
|
{
|
|
key: t.id,
|
|
type: `button`,
|
|
class: `gs-item`,
|
|
onClick: (e) => ae(t.id),
|
|
},
|
|
[
|
|
createElementVNode(
|
|
`span`,
|
|
M,
|
|
toDisplayString(
|
|
unref(product_name_n)(
|
|
t.name,
|
|
),
|
|
),
|
|
1,
|
|
),
|
|
createElementVNode(
|
|
`span`,
|
|
N,
|
|
`#` +
|
|
toDisplayString(t.id),
|
|
1,
|
|
),
|
|
],
|
|
8,
|
|
ne,
|
|
)
|
|
),
|
|
),
|
|
128,
|
|
)),
|
|
]))
|
|
: createCommentVNode(``, !0),
|
|
unref(re)
|
|
? (openBlock(),
|
|
createElementBlock(
|
|
`div`,
|
|
P,
|
|
`没有匹配的结果`,
|
|
))
|
|
: createCommentVNode(``, !0),
|
|
],
|
|
64,
|
|
)),
|
|
],
|
|
8,
|
|
E,
|
|
))
|
|
: createCommentVNode(``, !0),
|
|
],
|
|
544,
|
|
)
|
|
)
|
|
);
|
|
},
|
|
}),
|
|
[[`__scopeId`, `data-v-bc4d898a`]],
|
|
),
|
|
{
|
|
__name: `GlobalSearch`,
|
|
},
|
|
);
|
|
export {};
|
|
export default F;
|
|
</script>
|