fix(ui): improve edit rule dialog layout and button consistency

- Increase dialog width from 380px to 460px to fit all content
- Unify button heights across ab-button and ab-button-multi components
  - small: 32px, normal: 36px, big: 44px
- Restructure ab-rule form layout with consistent gap spacing
- Fix Episode Offset row to keep input and button on same line (mobile)
- Add proper z-index layering for ab-bottom-sheet (backdrop: 100, container: 101, panel: 102)
- Add scoped styles for ab-setting dynamic-tags wrapper
- Add action button separator line in edit dialog

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Estrella Pan
2026-01-25 09:46:21 +01:00
parent ecd7914f22
commit c9724179fb
7 changed files with 96 additions and 52 deletions

View File

@@ -35,6 +35,7 @@
- 规则编辑弹窗新增归档/取消归档按钮
- 规则编辑器新增剧集偏移字段和「自动检测」按钮
- 新增 i18n 翻译(中文/英文)
- 优化规则编辑弹窗布局:统一表单字段对齐、统一按钮高度、修复移动端底部弹窗 z-index 层级问题
---

View File

@@ -79,7 +79,7 @@ const boxSize = computed(() => {
if (rule.value.deleted) {
return 'w-300';
} else {
return 'w-380';
return 'w-460';
}
});
</script>
@@ -105,10 +105,10 @@ const boxSize = computed(() => {
</div>
</div>
<div v-else space-y-12>
<div v-else class="edit-rule-content">
<ab-rule v-model:rule="rule"></ab-rule>
<div fx-cer justify-end gap-x-10>
<div class="edit-rule-actions">
<ab-button
v-if="rule.archived"
size="small"
@@ -153,3 +153,22 @@ const boxSize = computed(() => {
</ab-popup>
</ab-popup>
</template>
<style lang="scss" scoped>
.edit-rule-content {
display: flex;
flex-direction: column;
gap: 20px;
}
.edit-rule-actions {
display: flex;
align-items: center;
justify-content: flex-end;
gap: 10px;
padding-top: 4px;
border-top: 1px solid var(--color-border);
padding-top: 16px;
flex-wrap: wrap;
}
</style>

View File

@@ -52,19 +52,17 @@ const items: SettingItem<BangumiRule>[] = [
prop: {
type: 'number',
},
bottomLine: true,
},
{
configKey: 'filter',
label: () => t('homepage.rule.exclude'),
type: 'dynamic-tags',
bottomLine: true,
},
];
</script>
<template>
<div space-y-12>
<div class="rule-form">
<ab-setting
v-for="i in items"
:key="i.configKey"
@@ -73,12 +71,12 @@ const items: SettingItem<BangumiRule>[] = [
></ab-setting>
<!-- Offset field with auto-detect button -->
<div class="offset-row">
<div class="offset-label">{{ $t('homepage.rule.offset') }}</div>
<ab-label :label="() => $t('homepage.rule.offset')">
<div class="offset-controls">
<input
v-model.number="rule.offset"
type="number"
ab-input
class="offset-input"
/>
<ab-button
@@ -89,47 +87,50 @@ const items: SettingItem<BangumiRule>[] = [
{{ $t('homepage.rule.auto_detect') }}
</ab-button>
</div>
<div v-if="offsetReason" class="offset-reason">{{ offsetReason }}</div>
</div>
</ab-label>
<div v-if="offsetReason" class="offset-reason">{{ offsetReason }}</div>
</div>
</template>
<style lang="scss" scoped>
.offset-row {
.rule-form {
display: flex;
flex-direction: column;
gap: 6px;
}
.offset-label {
font-size: 14px;
color: var(--color-text);
gap: 16px;
}
.offset-controls {
display: flex;
align-items: center;
gap: 8px;
gap: 10px;
width: 100%;
@include forTablet {
width: auto;
min-width: 220px;
}
:deep(.ab-button) {
flex-shrink: 0;
white-space: nowrap;
}
}
.offset-input {
width: 72px;
padding: 6px 10px;
border: 1px solid var(--color-border);
border-radius: var(--radius-sm);
background: var(--color-surface);
color: var(--color-text);
font-size: 14px;
outline: none;
transition: border-color var(--transition-fast);
width: 80px;
flex-shrink: 0;
&:focus {
border-color: var(--color-primary);
@include forMobile {
flex: 1;
min-width: 60px;
}
}
.offset-reason {
font-size: 12px;
color: var(--color-text-secondary);
padding-left: 2px;
margin-top: -8px;
}
</style>

View File

@@ -7,12 +7,12 @@ withDefaults(defineProps<AbSettingProps>(), {
bottomLine: false,
});
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const data = defineModel<any>('data');
</script>
<template>
<div>
<div class="setting-item">
<ab-label :label="label">
<AbSwitch
v-if="type === 'switch'"
@@ -36,11 +36,33 @@ const data = defineModel<any>('data');
v-bind="prop"
/>
<div v-else-if="type === 'dynamic-tags'" w-full sm:max-w-200 overflow-auto pb-1>
<div v-else-if="type === 'dynamic-tags'" class="dynamic-tags-wrapper">
<NDynamicTags v-model:value="data" size="small"></NDynamicTags>
</div>
</ab-label>
<div v-if="bottomLine" line my-6></div>
<div v-if="bottomLine" class="setting-divider"></div>
</div>
</template>
<style lang="scss" scoped>
.setting-item {
width: 100%;
}
.setting-divider {
height: 1px;
background: var(--color-border);
margin-top: 12px;
}
.dynamic-tags-wrapper {
width: 100%;
overflow-x: auto;
padding-bottom: 2px;
@include forTablet {
max-width: 220px;
}
}
</style>

View File

@@ -1,11 +1,11 @@
<script lang="ts" setup>
import { ref, watch, computed } from 'vue';
import { computed, ref } from 'vue';
import { usePointerSwipe } from '@vueuse/core';
import {
TransitionRoot,
TransitionChild,
Dialog,
DialogPanel,
TransitionChild,
TransitionRoot,
} from '@headlessui/vue';
const props = withDefaults(
@@ -69,7 +69,7 @@ function close() {
<template>
<TransitionRoot :show="show" as="template">
<Dialog @close="close" class="ab-bottom-sheet">
<Dialog class="ab-bottom-sheet" @close="close">
<!-- Backdrop -->
<TransitionChild
enter="overlay-enter-active"
@@ -125,6 +125,7 @@ function close() {
&__backdrop {
position: fixed;
inset: 0;
z-index: 100;
background: rgba(0, 0, 0, 0.4);
backdrop-filter: blur(4px);
}
@@ -132,6 +133,7 @@ function close() {
&__container {
position: fixed;
inset: 0;
z-index: 101;
display: flex;
align-items: flex-end;
justify-content: center;
@@ -140,6 +142,7 @@ function close() {
&__panel {
position: relative;
z-index: 102;
width: 100%;
max-width: 640px;
background: var(--color-surface);

View File

@@ -70,22 +70,22 @@ const showSelections = ref<boolean>(false);
&--big {
border-radius: var(--radius-md);
width: 276px;
height: 55px;
font-size: 24px;
height: 44px;
font-size: 16px;
}
&--normal {
border-radius: var(--radius-sm);
width: 170px;
min-width: 100px;
height: 36px;
font-size: 14px;
}
&--small {
border-radius: var(--radius-sm);
width: 86px;
height: 28px;
font-size: 12px;
min-width: 80px;
height: 32px;
font-size: 13px;
}
&--primary {

View File

@@ -80,13 +80,13 @@ const buttonSize = computed(() => {
cursor: not-allowed;
}
// Sizes - all meet 44px minimum touch target
// Sizes
&--big {
border-radius: var(--radius-md);
font-size: 18px;
font-size: 16px;
width: 100%;
max-width: 276px;
height: 55px;
height: 44px;
}
&--normal {
@@ -94,17 +94,15 @@ const buttonSize = computed(() => {
font-size: 14px;
width: 100%;
max-width: 170px;
min-height: var(--touch-target);
height: 44px;
height: 36px;
}
&--small {
border-radius: var(--radius-sm);
font-size: 13px;
min-width: 86px;
min-height: var(--touch-target);
height: 44px;
padding: 0 16px;
min-width: 80px;
height: 32px;
padding: 0 14px;
gap: 6px;
white-space: nowrap;
}