mirror of https://github.com/halo-dev/halo
fix: dropdown options cannot be clicked in some mobile devices (#4116)
#### What type of PR is this? /area console /kind improvement /milestone 2.7.x #### What this PR does / why we need it: 修复在部分移动端浏览器(比如 iOS Safari)中,下拉框组件(VDropdown)的选项点击无效的问题,即没有触发 click 事件。此问题的原因可能是因为我们用的 floating-vue 组件提供的 `v-close-popper` 指令的兼容问题,最小复现:https://stackblitz.com/edit/vitejs-vite-ncpzhj?file=src%2FApp.vue 此 PR 改写了关闭下拉框的方式,不再使用 v-close-popper 指令,而且对其他使用此组件的地方没有破坏性更新。 #### Which issue(s) this PR fixes: Fixes https://github.com/halo-dev/halo/issues/3689 Ref https://github.com/halo-dev/halo/issues/2699 #### Special notes for your reviewer: 如果有条件可以在移动端测试一下,尤其是 iOS Safari,目前在桌面端 Chrome 的设备模拟中测试正常。 #### Does this PR introduce a user-facing change? ```release-note 修复 Console 端的下拉框组件选项在移动端无法正常点击的问题。 ```pull/4061/head
parent
aaa3548c97
commit
27ef8d3bab
|
@ -1,6 +1,8 @@
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { Dropdown as FloatingDropdown, type Placement } from "floating-vue";
|
import { Dropdown as FloatingDropdown, type Placement } from "floating-vue";
|
||||||
import "floating-vue/dist/style.css";
|
import "floating-vue/dist/style.css";
|
||||||
|
import { provide, ref } from "vue";
|
||||||
|
import { DropdownContextInjectionKey } from "./symbols";
|
||||||
|
|
||||||
withDefaults(
|
withDefaults(
|
||||||
defineProps<{
|
defineProps<{
|
||||||
|
@ -15,13 +17,28 @@ withDefaults(
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const dropdownRef = ref();
|
||||||
|
|
||||||
|
function hide() {
|
||||||
|
dropdownRef.value?.hide();
|
||||||
|
}
|
||||||
|
|
||||||
|
provide(DropdownContextInjectionKey, {
|
||||||
|
hide,
|
||||||
|
});
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(event: "show"): void;
|
(event: "show"): void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
hide,
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<FloatingDropdown
|
<FloatingDropdown
|
||||||
|
ref="dropdownRef"
|
||||||
:placement="placement"
|
:placement="placement"
|
||||||
:triggers="triggers"
|
:triggers="triggers"
|
||||||
@show="emit('show')"
|
@show="emit('show')"
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { VClosePopper } from "floating-vue";
|
import { DropdownContextInjectionKey } from "./symbols";
|
||||||
|
import { inject } from "vue";
|
||||||
|
|
||||||
withDefaults(
|
withDefaults(
|
||||||
defineProps<{
|
defineProps<{
|
||||||
|
@ -11,15 +12,26 @@ withDefaults(
|
||||||
type: "default",
|
type: "default",
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(event: "click", e: MouseEvent): void;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const { hide } = inject(DropdownContextInjectionKey) || {};
|
||||||
|
|
||||||
|
function onClick(e: MouseEvent) {
|
||||||
|
hide?.();
|
||||||
|
emit("click", e);
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
v-close-popper
|
|
||||||
class="dropdown-item-wrapper"
|
class="dropdown-item-wrapper"
|
||||||
:class="[`dropdown-item-wrapper--${type}${selected ? '--selected' : ''}`]"
|
:class="[`dropdown-item-wrapper--${type}${selected ? '--selected' : ''}`]"
|
||||||
role="menuitem"
|
role="menuitem"
|
||||||
tabindex="-1"
|
tabindex="-1"
|
||||||
|
@click="onClick"
|
||||||
>
|
>
|
||||||
<div class="flex items-center gap-3">
|
<div class="flex items-center gap-3">
|
||||||
<slot name="prefix-icon" />
|
<slot name="prefix-icon" />
|
||||||
|
|
|
@ -2,3 +2,4 @@ export { default as VDropdown } from "./Dropdown.vue";
|
||||||
export { default as VDropdownItem } from "./DropdownItem.vue";
|
export { default as VDropdownItem } from "./DropdownItem.vue";
|
||||||
export { default as VDropdownDivider } from "./DropdownDivider.vue";
|
export { default as VDropdownDivider } from "./DropdownDivider.vue";
|
||||||
export { VClosePopper } from "floating-vue";
|
export { VClosePopper } from "floating-vue";
|
||||||
|
export * from "./symbols";
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
import type { InjectionKey } from "vue";
|
||||||
|
|
||||||
|
export const DropdownContextInjectionKey: InjectionKey<{
|
||||||
|
hide: () => void;
|
||||||
|
}> = Symbol("dropdown-context");
|
|
@ -22,6 +22,8 @@ const emit = defineEmits<{
|
||||||
|
|
||||||
const { categories } = usePostCategory();
|
const { categories } = usePostCategory();
|
||||||
|
|
||||||
|
const dropdown = ref();
|
||||||
|
|
||||||
const handleSelect = (category: Category) => {
|
const handleSelect = (category: Category) => {
|
||||||
if (
|
if (
|
||||||
props.selected &&
|
props.selected &&
|
||||||
|
@ -34,6 +36,8 @@ const handleSelect = (category: Category) => {
|
||||||
|
|
||||||
emit("update:selected", category);
|
emit("update:selected", category);
|
||||||
emit("select", category);
|
emit("select", category);
|
||||||
|
|
||||||
|
dropdown.value.hide();
|
||||||
};
|
};
|
||||||
|
|
||||||
function onDropdownShow() {
|
function onDropdownShow() {
|
||||||
|
@ -71,7 +75,7 @@ const searchResults = computed(() => {
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<VDropdown :classes="['!p-0']" @show="onDropdownShow">
|
<VDropdown ref="dropdown" :classes="['!p-0']" @show="onDropdownShow">
|
||||||
<slot />
|
<slot />
|
||||||
<template #popper>
|
<template #popper>
|
||||||
<div class="h-96 w-80">
|
<div class="h-96 w-80">
|
||||||
|
@ -91,7 +95,6 @@ const searchResults = computed(() => {
|
||||||
<li
|
<li
|
||||||
v-for="(category, index) in searchResults"
|
v-for="(category, index) in searchResults"
|
||||||
:key="index"
|
:key="index"
|
||||||
v-close-popper
|
|
||||||
@click="handleSelect(category)"
|
@click="handleSelect(category)"
|
||||||
>
|
>
|
||||||
<VEntity
|
<VEntity
|
||||||
|
|
|
@ -23,6 +23,8 @@ const emit = defineEmits<{
|
||||||
|
|
||||||
const { tags } = usePostTag();
|
const { tags } = usePostTag();
|
||||||
|
|
||||||
|
const dropdown = ref();
|
||||||
|
|
||||||
const handleSelect = (tag: Tag) => {
|
const handleSelect = (tag: Tag) => {
|
||||||
if (props.selected && tag.metadata.name === props.selected.metadata.name) {
|
if (props.selected && tag.metadata.name === props.selected.metadata.name) {
|
||||||
emit("update:selected", undefined);
|
emit("update:selected", undefined);
|
||||||
|
@ -32,6 +34,8 @@ const handleSelect = (tag: Tag) => {
|
||||||
|
|
||||||
emit("update:selected", tag);
|
emit("update:selected", tag);
|
||||||
emit("select", tag);
|
emit("select", tag);
|
||||||
|
|
||||||
|
dropdown.value.hide();
|
||||||
};
|
};
|
||||||
|
|
||||||
function onDropdownShow() {
|
function onDropdownShow() {
|
||||||
|
@ -69,7 +73,7 @@ const searchResults = computed(() => {
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<VDropdown :classes="['!p-0']" @show="onDropdownShow">
|
<VDropdown ref="dropdown" :classes="['!p-0']" @show="onDropdownShow">
|
||||||
<slot />
|
<slot />
|
||||||
<template #popper>
|
<template #popper>
|
||||||
<div class="h-96 w-80">
|
<div class="h-96 w-80">
|
||||||
|
@ -89,7 +93,6 @@ const searchResults = computed(() => {
|
||||||
<li
|
<li
|
||||||
v-for="(tag, index) in searchResults"
|
v-for="(tag, index) in searchResults"
|
||||||
:key="index"
|
:key="index"
|
||||||
v-close-popper
|
|
||||||
@click="handleSelect(tag)"
|
@click="handleSelect(tag)"
|
||||||
>
|
>
|
||||||
<VEntity
|
<VEntity
|
||||||
|
|
|
@ -27,6 +27,8 @@ const emit = defineEmits<{
|
||||||
|
|
||||||
const { users, handleFetchUsers } = useUserFetch();
|
const { users, handleFetchUsers } = useUserFetch();
|
||||||
|
|
||||||
|
const dropdown = ref();
|
||||||
|
|
||||||
const handleSelect = (user: User) => {
|
const handleSelect = (user: User) => {
|
||||||
if (props.selected && user.metadata.name === props.selected.metadata.name) {
|
if (props.selected && user.metadata.name === props.selected.metadata.name) {
|
||||||
emit("update:selected", undefined);
|
emit("update:selected", undefined);
|
||||||
|
@ -36,6 +38,8 @@ const handleSelect = (user: User) => {
|
||||||
|
|
||||||
emit("update:selected", user);
|
emit("update:selected", user);
|
||||||
emit("select", user);
|
emit("select", user);
|
||||||
|
|
||||||
|
dropdown.value.hide();
|
||||||
};
|
};
|
||||||
|
|
||||||
function onDropdownShow() {
|
function onDropdownShow() {
|
||||||
|
@ -71,7 +75,7 @@ const searchResults = computed(() => {
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<VDropdown :classes="['!p-0']" @show="onDropdownShow">
|
<VDropdown ref="dropdown" :classes="['!p-0']" @show="onDropdownShow">
|
||||||
<slot />
|
<slot />
|
||||||
<template #popper>
|
<template #popper>
|
||||||
<div class="h-96 w-80">
|
<div class="h-96 w-80">
|
||||||
|
@ -91,7 +95,6 @@ const searchResults = computed(() => {
|
||||||
<li
|
<li
|
||||||
v-for="(user, index) in searchResults"
|
v-for="(user, index) in searchResults"
|
||||||
:key="index"
|
:key="index"
|
||||||
v-close-popper
|
|
||||||
@click="handleSelect(user)"
|
@click="handleSelect(user)"
|
||||||
>
|
>
|
||||||
<VEntity
|
<VEntity
|
||||||
|
|
|
@ -266,15 +266,10 @@ onMounted(async () => {
|
||||||
{{ $t("core.common.buttons.delete") }}
|
{{ $t("core.common.buttons.delete") }}
|
||||||
</VDropdownItem>
|
</VDropdownItem>
|
||||||
<template #popper>
|
<template #popper>
|
||||||
<VDropdownItem
|
<VDropdownItem type="danger" @click="handleDelete(group)">
|
||||||
v-close-popper.all
|
|
||||||
type="danger"
|
|
||||||
@click="handleDelete(group)"
|
|
||||||
>
|
|
||||||
{{ $t("core.attachment.group_list.operations.delete.button") }}
|
{{ $t("core.attachment.group_list.operations.delete.button") }}
|
||||||
</VDropdownItem>
|
</VDropdownItem>
|
||||||
<VDropdownItem
|
<VDropdownItem
|
||||||
v-close-popper.all
|
|
||||||
type="danger"
|
type="danger"
|
||||||
@click="handleDeleteWithAttachments(group)"
|
@click="handleDeleteWithAttachments(group)"
|
||||||
>
|
>
|
||||||
|
|
|
@ -208,18 +208,10 @@ const handleUninstall = async (theme: Theme, deleteExtensions?: boolean) => {
|
||||||
{{ $t("core.common.buttons.uninstall") }}
|
{{ $t("core.common.buttons.uninstall") }}
|
||||||
</VDropdownItem>
|
</VDropdownItem>
|
||||||
<template #popper>
|
<template #popper>
|
||||||
<VDropdownItem
|
<VDropdownItem type="danger" @click="handleUninstall(theme)">
|
||||||
v-close-popper.all
|
|
||||||
type="danger"
|
|
||||||
@click="handleUninstall(theme)"
|
|
||||||
>
|
|
||||||
{{ $t("core.common.buttons.uninstall") }}
|
{{ $t("core.common.buttons.uninstall") }}
|
||||||
</VDropdownItem>
|
</VDropdownItem>
|
||||||
<VDropdownItem
|
<VDropdownItem type="danger" @click="handleUninstall(theme, true)">
|
||||||
v-close-popper.all
|
|
||||||
type="danger"
|
|
||||||
@click="handleUninstall(theme, true)"
|
|
||||||
>
|
|
||||||
{{ $t("core.theme.operations.uninstall_and_delete_config.button") }}
|
{{ $t("core.theme.operations.uninstall_and_delete_config.button") }}
|
||||||
</VDropdownItem>
|
</VDropdownItem>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -154,14 +154,10 @@ const handleResetSettingConfig = async () => {
|
||||||
{{ $t("core.common.buttons.uninstall") }}
|
{{ $t("core.common.buttons.uninstall") }}
|
||||||
</VDropdownItem>
|
</VDropdownItem>
|
||||||
<template #popper>
|
<template #popper>
|
||||||
<VDropdownItem v-close-popper.all type="danger" @click="uninstall">
|
<VDropdownItem type="danger" @click="uninstall">
|
||||||
{{ $t("core.common.buttons.uninstall") }}
|
{{ $t("core.common.buttons.uninstall") }}
|
||||||
</VDropdownItem>
|
</VDropdownItem>
|
||||||
<VDropdownItem
|
<VDropdownItem type="danger" @click="uninstall(true)">
|
||||||
v-close-popper.all
|
|
||||||
type="danger"
|
|
||||||
@click="uninstall(true)"
|
|
||||||
>
|
|
||||||
{{ $t("core.plugin.list.actions.uninstall_and_delete_config") }}
|
{{ $t("core.plugin.list.actions.uninstall_and_delete_config") }}
|
||||||
</VDropdownItem>
|
</VDropdownItem>
|
||||||
</template>
|
</template>
|
||||||
|
|
Loading…
Reference in New Issue