fix(ui): 手机端权限切换菜单防溢出,溢出时改为屏幕水平居中

锚点在输入栏左下而菜单较宽(250/350px),right:0 右对齐导致菜单左缘算出负数跑出屏幕外。打开时测量锚点位置与菜单宽度:右对齐不溢出屏幕左缘则保持现状;溢出则换算水平偏移让菜单相对屏幕居中,垂直方向保持锚点上方不变。仅 ≤768px 断点生效,监听 resize 重算
This commit is contained in:
JOJO 2026-08-30 23:36:08 +08:00
parent b55f4a811d
commit c4f5ef8c56

View File

@ -499,7 +499,7 @@
> >
<span>{{ $t('input.versionControl') }}</span> <span>{{ $t('input.versionControl') }}</span>
</button> </button>
<div class="permission-switcher__block"> <div ref="permissionBlockEl" class="permission-switcher__block">
<button <button
type="button" type="button"
class="permission-switcher__btn" class="permission-switcher__btn"
@ -535,8 +535,13 @@
</button> </button>
<div <div
v-if="permissionMenuOpen" v-if="permissionMenuOpen"
ref="permissionMenuEl"
class="permission-switcher__menu" class="permission-switcher__menu"
:class="{ 'permission-switcher__menu--split': executionModeEnabled }" :class="{
'permission-switcher__menu--split': executionModeEnabled,
'permission-switcher__menu--centered': permissionMenuCentered,
}"
:style="permissionMenuCentered ? { left: permissionMenuCenteredLeft, right: 'auto' } : undefined"
> >
<div <div
class="permission-switcher__group" class="permission-switcher__group"
@ -3391,6 +3396,45 @@ const executionModeLocked = computed(
() => permissionLockedByPlan.value || executionLockedByRestricted.value () => permissionLockedByPlan.value || executionLockedByRestricted.value
); );
/**
* 手机端权限菜单防溢出定位优先右对齐锚点right: 0若菜单左缘会溢出屏幕
* 锚点在输入栏左下而菜单较宽时典型发生则改为相对屏幕水平居中垂直方向
* 保持锚点上方不变inline left 为相对锚点容器的偏移换算 768px 断点启用
*/
const permissionBlockEl = ref<HTMLElement | null>(null);
const permissionMenuEl = ref<HTMLElement | null>(null);
const permissionMenuCentered = ref(false);
const permissionMenuCenteredLeft = ref('0px');
async function updatePermissionMenuPosition() {
permissionMenuCentered.value = false;
if (!props.permissionMenuOpen) return;
if (!window.matchMedia('(max-width: 768px)').matches) return;
await nextTick();
const block = permissionBlockEl.value;
const menu = permissionMenuEl.value;
if (!block || !menu) return;
const anchorRect = block.getBoundingClientRect();
const menuWidth = menu.offsetWidth;
const safeMargin = 8;
if (anchorRect.right - menuWidth < safeMargin) {
const centeredLeft = (window.innerWidth - menuWidth) / 2 - anchorRect.left;
permissionMenuCenteredLeft.value = `${Math.round(centeredLeft)}px`;
permissionMenuCentered.value = true;
}
}
function onWindowResizeRepositionMenu() {
if (props.permissionMenuOpen) void updatePermissionMenuPosition();
}
watch(
() => props.permissionMenuOpen,
(open) => {
if (open) void updatePermissionMenuPosition();
}
);
const currentExecutionShortLabel = computed(() => { const currentExecutionShortLabel = computed(() => {
void currentLocale.value; void currentLocale.value;
const mode = String(props.currentExecutionMode || ''); const mode = String(props.currentExecutionMode || '');
@ -3588,6 +3632,7 @@ watch(
onMounted(() => { onMounted(() => {
document.addEventListener('click', closeProjectGitMenu); document.addEventListener('click', closeProjectGitMenu);
window.addEventListener('resize', onWindowResizeRepositionMenu);
adjustTextareaSize(); adjustTextareaSize();
nextTick(() => { nextTick(() => {
emitComposerHeight(); emitComposerHeight();
@ -3611,6 +3656,7 @@ onMounted(() => {
onBeforeUnmount(() => { onBeforeUnmount(() => {
stopVoiceRecording(); stopVoiceRecording();
document.removeEventListener('click', closeProjectGitMenu); document.removeEventListener('click', closeProjectGitMenu);
window.removeEventListener('resize', onWindowResizeRepositionMenu);
editor.value?.destroy(); editor.value?.destroy();
runtimeQueueItemRefs.forEach((el) => { runtimeQueueItemRefs.forEach((el) => {
if (el) { if (el) {