mirror of
https://github.com/EstrellaXD/Auto_Bangumi.git
synced 2026-11-02 05:37:14 +08:00
fix(webui): 补修复验报告的低严重度问题,e2e 密码框精确匹配
- 插件市场:键值表标签列宽、分类 Tab 底线通栏、手机列表动画只在从详情返回时播放、
插件不存在时手机页面标题
- 下载器:操作栏出现/消失时高度平滑过渡;640–767px 去掉下载速度列;
桌面按可用宽度依次去掉做种/下载、ETA、上传速度,不再横向滚动
- 日志:手机级别筛选改为两列网格
- 空状态步骤序号与标题左对齐(role="list")
- 向导确认页、设置页标签列表、种子列表选中描边、手机搜索筛选区随结果滚动
- e2e:设置向导的密码框多了显示/隐藏按钮,getByLabel('Password') 改为精确匹配
This commit is contained in:
@@ -25,7 +25,7 @@ export async function loginThroughUI(
|
||||
): Promise<Response> {
|
||||
await page.goto('/#/login');
|
||||
await page.getByLabel('Username').fill(credentials.username);
|
||||
await page.getByLabel('Password').fill(credentials.password);
|
||||
await page.getByLabel('Password', { exact: true }).fill(credentials.password);
|
||||
const responsePromise = page.waitForResponse(
|
||||
(response) =>
|
||||
response.url().endsWith('/api/v1/auth/login') &&
|
||||
@@ -60,7 +60,7 @@ export async function completeFirstRunSetup(
|
||||
|
||||
await page.getByLabel('Host').fill(environment.downloaderURL);
|
||||
await page.getByLabel('Username').fill(environment.downloaderUsername);
|
||||
await page.getByLabel('Password').fill(environment.downloaderPassword);
|
||||
await page.getByLabel('Password', { exact: true }).fill(environment.downloaderPassword);
|
||||
await page.getByLabel('Download Path').fill('/downloads/Bangumi');
|
||||
await page.getByRole('button', { name: 'Next' }).click();
|
||||
|
||||
|
||||
@@ -37,7 +37,10 @@ const selectValue = computed<string | number | null>({
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="setting-item">
|
||||
<div
|
||||
class="setting-item"
|
||||
:class="{ 'setting-item--tags': type === 'dynamic-tags' && data?.length }"
|
||||
>
|
||||
<AbField
|
||||
:label="label"
|
||||
:description="description"
|
||||
@@ -104,6 +107,20 @@ const selectValue = computed<string | number | null>({
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
// 有标签时控件分两行(标签行 + 输入框):标签名对齐首行,不再居中于整块。
|
||||
// 行高取 ab-tag 的高度(24px),使文字与标签行垂直居中
|
||||
.setting-item--tags {
|
||||
@include forTablet {
|
||||
:deep(.ab-field-main) {
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
:deep(.ab-field-label) {
|
||||
line-height: 24px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 与 ab-input 同宽(200px),各行控件左缘对齐;标签在框内换行
|
||||
.dynamic-tags-wrapper {
|
||||
width: 100%;
|
||||
|
||||
@@ -235,6 +235,8 @@ onActivated(load);
|
||||
&--selected:hover {
|
||||
background: color-mix(in srgb, var(--color-primary) 12%, transparent);
|
||||
outline: 1px solid var(--color-primary);
|
||||
// 首行贴着滚动容器顶边,外描边会被裁掉,改为内描边
|
||||
outline-offset: -1px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -168,12 +168,12 @@ function permissionLabel(id: string) {
|
||||
margin: 0;
|
||||
font-size: 13px;
|
||||
|
||||
@include forMobile {
|
||||
grid-template-columns: minmax(64px, 7.5em) 1fr;
|
||||
}
|
||||
|
||||
dt {
|
||||
color: var(--color-text-secondary);
|
||||
|
||||
@include forMobile {
|
||||
max-width: 7.5em;
|
||||
}
|
||||
}
|
||||
|
||||
dd {
|
||||
|
||||
@@ -1030,6 +1030,12 @@ function handleFilterClick(
|
||||
max-height: calc(100vh - 100px);
|
||||
}
|
||||
|
||||
// 触屏:筛选区随结果一起滚动,不再常驻占掉半个结果区;搜索栏吸顶
|
||||
@include forTouch {
|
||||
overflow-y: auto;
|
||||
overscroll-behavior: contain;
|
||||
}
|
||||
|
||||
@include forDesktop {
|
||||
max-height: calc(100dvh - 120px);
|
||||
|
||||
@@ -1049,6 +1055,13 @@ function handleFilterClick(
|
||||
flex-shrink: 0;
|
||||
transition: border-color var(--transition-normal);
|
||||
|
||||
@include forTouch {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 1;
|
||||
background: var(--color-surface);
|
||||
}
|
||||
|
||||
@include forTablet {
|
||||
gap: 12px;
|
||||
padding: 16px;
|
||||
@@ -1464,6 +1477,11 @@ function handleFilterClick(
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 16px;
|
||||
|
||||
@include forTouch {
|
||||
flex: none;
|
||||
overflow-y: visible;
|
||||
}
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
|
||||
@@ -162,7 +162,8 @@ function maskPassword(pwd: string): string {
|
||||
.review-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
// 长值换行时标签对齐值的首行
|
||||
align-items: baseline;
|
||||
gap: 12px;
|
||||
padding: 4px 0;
|
||||
font-size: 12px;
|
||||
|
||||
@@ -179,7 +179,7 @@ function pinLeaving(el: Element) {
|
||||
:description="$t('homepage.empty.subtitle')"
|
||||
>
|
||||
<template #action>
|
||||
<ol class="empty-steps">
|
||||
<ol class="empty-steps" role="list">
|
||||
<li v-for="n in 3" :key="n">
|
||||
<span class="empty-steps-title">{{
|
||||
$t(`homepage.empty.step${n}_title`)
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
} from 'naive-ui';
|
||||
import AbProgress from '@/components/basic/ab-progress.vue';
|
||||
import AbTag from '@/components/basic/ab-tag.vue';
|
||||
import { useElementSize, useMediaQuery } from '@vueuse/core';
|
||||
import { useConfirm } from '@/hooks/useConfirm';
|
||||
import type { QbTorrentInfo, TorrentGroup } from '#/downloader';
|
||||
import { useDownloaderInstances } from '@/hooks/useDownloaderInstances';
|
||||
@@ -183,6 +184,8 @@ function onCheckedChange(keys: DataTableRowKey[]) {
|
||||
const downloaders = useDownloaderInstances();
|
||||
|
||||
const { isMobile, isTablet } = useBreakpointQuery();
|
||||
// 平板窄档(640–767px)放不下下载速度列
|
||||
const isNarrowTablet = useMediaQuery('(max-width: 767px)');
|
||||
|
||||
function progressCell(row: QbTorrentInfo) {
|
||||
return h(AbProgress, {
|
||||
@@ -215,6 +218,20 @@ function mobileNameCell(row: QbTorrentInfo) {
|
||||
]);
|
||||
}
|
||||
|
||||
// 各列定宽 + 名称列下限
|
||||
function columnsWidth(cols: DataTableColumns<Row>) {
|
||||
return cols.reduce((sum, col) => {
|
||||
if (col.type === 'selection') return sum + 40;
|
||||
const c = col as { width?: number; minWidth?: number };
|
||||
return sum + (c.width ?? c.minWidth ?? 0);
|
||||
}, 0);
|
||||
}
|
||||
|
||||
// 桌面窄档(1024–1279px,或侧栏展开)放不下全部列:按可用宽度依次去掉
|
||||
// 做种/下载、ETA、上传速度,不出现没有提示的横向滚动
|
||||
const contentRef = ref<HTMLElement | null>(null);
|
||||
const { width: contentWidth } = useElementSize(contentRef);
|
||||
|
||||
const tableColumnsValue = computed<DataTableColumns<Row>>(() => {
|
||||
const nameCol: DataTableColumns<Row>[number] = {
|
||||
title: t('downloader.torrent.name'),
|
||||
@@ -222,7 +239,8 @@ const tableColumnsValue = computed<DataTableColumns<Row>>(() => {
|
||||
// CSS 省略号 + title:不用逐行测量宽度的 tooltip
|
||||
ellipsis: !isMobile.value,
|
||||
className: 'name-col',
|
||||
minWidth: 200,
|
||||
// 平板收紧名称与进度列,各列总宽不超过内容区(768px 时 672px)
|
||||
minWidth: isTablet.value ? 150 : 200,
|
||||
colSpan: (row: Row): number =>
|
||||
isGroupRow(row) ? tableColumnsValue.value.length - 1 : 1,
|
||||
cellProps: (row) => ({
|
||||
@@ -239,13 +257,13 @@ const tableColumnsValue = computed<DataTableColumns<Row>>(() => {
|
||||
};
|
||||
if (isMobile.value) return [{ type: 'selection' }, nameCol];
|
||||
|
||||
return [
|
||||
let cols: DataTableColumns<Row> = [
|
||||
{ type: 'selection' },
|
||||
nameCol,
|
||||
{
|
||||
title: t('downloader.torrent.progress'),
|
||||
key: 'progress',
|
||||
width: 140,
|
||||
width: isTablet.value ? 120 : 140,
|
||||
render: torrentCell(progressCell),
|
||||
},
|
||||
{
|
||||
@@ -261,13 +279,17 @@ const tableColumnsValue = computed<DataTableColumns<Row>>(() => {
|
||||
...numCol,
|
||||
render: torrentCell((row) => formatSize(row.size)),
|
||||
},
|
||||
{
|
||||
title: t('downloader.torrent.dlspeed'),
|
||||
key: 'dlspeed',
|
||||
width: 110,
|
||||
...numCol,
|
||||
render: torrentCell((row) => formatSpeed(row.dlspeed)),
|
||||
},
|
||||
...(isNarrowTablet.value
|
||||
? []
|
||||
: [
|
||||
{
|
||||
title: t('downloader.torrent.dlspeed'),
|
||||
key: 'dlspeed',
|
||||
width: 110,
|
||||
...numCol,
|
||||
render: torrentCell((row) => formatSpeed(row.dlspeed)),
|
||||
},
|
||||
]),
|
||||
// 平板宽度不够:去掉上传速度、ETA、做种/下载
|
||||
...(isTablet.value
|
||||
? []
|
||||
@@ -309,17 +331,16 @@ const tableColumnsValue = computed<DataTableColumns<Row>>(() => {
|
||||
]
|
||||
: []),
|
||||
];
|
||||
for (const key of ['peers', 'eta', 'upspeed']) {
|
||||
if (!contentWidth.value || columnsWidth(cols) <= contentWidth.value) break;
|
||||
cols = cols.filter((col) => (col as { key?: string }).key !== key);
|
||||
}
|
||||
return cols;
|
||||
});
|
||||
|
||||
// 表格最小宽度 = 各列定宽 + 名称列下限:WebKit 在 table-layout: fixed 下忽略
|
||||
// <col> 的 min-width,不设 scroll-x 时名称列会被算成 0
|
||||
const tableMinWidth = computed(() =>
|
||||
tableColumnsValue.value.reduce((sum, col) => {
|
||||
if (col.type === 'selection') return sum + 40;
|
||||
const c = col as { width?: number; minWidth?: number };
|
||||
return sum + (c.width ?? c.minWidth ?? 0);
|
||||
}, 0)
|
||||
);
|
||||
// 表格最小宽度:WebKit 在 table-layout: fixed 下忽略 <col> 的 min-width,
|
||||
// 不设 scroll-x 时名称列会被算成 0
|
||||
const tableMinWidth = computed(() => columnsWidth(tableColumnsValue.value));
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -330,7 +351,7 @@ const tableMinWidth = computed(() =>
|
||||
:description="$t('downloader.empty.subtitle')"
|
||||
>
|
||||
<template #action>
|
||||
<ol class="empty-steps">
|
||||
<ol class="empty-steps" role="list">
|
||||
<li v-for="n in 3" :key="n">
|
||||
<span class="empty-steps-title">{{
|
||||
$t(`downloader.empty.step${n}_title`)
|
||||
@@ -345,7 +366,7 @@ const tableMinWidth = computed(() =>
|
||||
</ab-empty>
|
||||
</div>
|
||||
|
||||
<div v-else class="downloader-content">
|
||||
<div v-else ref="contentRef" class="downloader-content">
|
||||
<div v-if="groups.length === 0 && !loading" class="downloader-empty">
|
||||
{{ $t('downloader.empty_torrents') }}
|
||||
</div>
|
||||
@@ -370,28 +391,31 @@ const tableMinWidth = computed(() =>
|
||||
@update:expanded-row-keys="onExpandedChange"
|
||||
/>
|
||||
|
||||
<!-- 操作栏在内容列底部占位(不 fixed):不遮表格、不压底栏,无需预留 padding -->
|
||||
<!-- 操作栏在内容列底部占位(不 fixed):不遮表格、不压底栏,无需预留 padding;
|
||||
外层 grid 行高 0fr↔1fr 过渡,表格高度随之平滑变化 -->
|
||||
<Transition name="action-bar">
|
||||
<div v-if="selectedKeys.size > 0" class="action-bar">
|
||||
<span class="action-bar-count">
|
||||
{{ selectedKeys.size }} {{ $t('downloader.selected') }}
|
||||
</span>
|
||||
<div class="action-bar-buttons">
|
||||
<ab-button variant="primary" size="sm" @click="resumeSelected">
|
||||
{{
|
||||
$t('downloader.action.resume')
|
||||
}}
|
||||
</ab-button>
|
||||
<ab-button variant="secondary" size="sm" @click="pauseSelected">
|
||||
{{
|
||||
$t('downloader.action.pause')
|
||||
}}
|
||||
</ab-button>
|
||||
<ab-button variant="danger" size="sm" @click="onDeleteSelected">
|
||||
{{
|
||||
$t('downloader.action.delete')
|
||||
}}
|
||||
</ab-button>
|
||||
<div v-if="selectedKeys.size > 0" class="action-bar-wrap">
|
||||
<div class="action-bar-clip">
|
||||
<div class="action-bar">
|
||||
<span class="action-bar-count">
|
||||
{{ selectedKeys.size }} {{ $t('downloader.selected') }}
|
||||
</span>
|
||||
<div class="action-bar-buttons">
|
||||
<ab-button variant="primary" size="sm" @click="resumeSelected">
|
||||
{{ $t('downloader.action.resume') }}
|
||||
</ab-button>
|
||||
<ab-button variant="secondary" size="sm" @click="pauseSelected">
|
||||
{{ $t('downloader.action.pause') }}
|
||||
</ab-button>
|
||||
<ab-button
|
||||
variant="danger"
|
||||
size="sm"
|
||||
@click="onDeleteSelected"
|
||||
>
|
||||
{{ $t('downloader.action.delete') }}
|
||||
</ab-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Transition>
|
||||
@@ -413,7 +437,6 @@ const tableMinWidth = computed(() =>
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
// flex-height:表格填满页面剩余高度,虚拟滚动在表体内进行
|
||||
@@ -497,9 +520,22 @@ const tableMinWidth = computed(() =>
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.action-bar {
|
||||
.action-bar-wrap {
|
||||
flex-shrink: 0;
|
||||
display: grid;
|
||||
grid-template-rows: 1fr;
|
||||
}
|
||||
|
||||
.action-bar-clip {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.action-bar {
|
||||
align-self: center;
|
||||
// 与表格的间距用 margin 放进可收起的格子里,出现/收起时一并过渡
|
||||
margin-top: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
@@ -539,13 +575,18 @@ const tableMinWidth = computed(() =>
|
||||
|
||||
.action-bar-enter-active,
|
||||
.action-bar-leave-active {
|
||||
transition: opacity var(--transition-normal),
|
||||
transform var(--transition-normal);
|
||||
transition: grid-template-rows var(--transition-normal),
|
||||
opacity var(--transition-normal);
|
||||
|
||||
// 只在过渡中裁切,静止时不裁掉操作栏阴影
|
||||
.action-bar-clip {
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
.action-bar-enter-from,
|
||||
.action-bar-leave-to {
|
||||
grid-template-rows: 0fr;
|
||||
opacity: 0;
|
||||
transform: translateY(4px);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -319,12 +319,20 @@ onDeactivated(() => {
|
||||
}
|
||||
|
||||
// 筛选项不换行:放不下时整组单独占一行并横向滚动,
|
||||
// 搜索框与操作按钮落到下一行,不出现 3+1 的零散换行
|
||||
// 搜索框与操作按钮落到下一行,不出现 3+1 的零散换行;
|
||||
// 手机上 4 个筛选项排成 2×2,全部可见,不靠横向滚动
|
||||
.filter-chips {
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
overflow-x: auto;
|
||||
scrollbar-width: none;
|
||||
|
||||
@media (max-width: 639px) {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
flex-basis: 100%;
|
||||
overflow: visible;
|
||||
}
|
||||
}
|
||||
|
||||
.filter-chip {
|
||||
|
||||
@@ -367,43 +367,47 @@ function permissionLabel(id: string) {
|
||||
</template>
|
||||
|
||||
<div v-else-if="catalog" class="market__panes">
|
||||
<nav
|
||||
v-if="!showDetail"
|
||||
class="market__list"
|
||||
:aria-label="$t('market.list_label')"
|
||||
>
|
||||
<RouterLink
|
||||
v-for="entry in visible"
|
||||
:key="entry.id"
|
||||
:to="`/market/${encodeURIComponent(entry.id)}`"
|
||||
:replace="!isMobile"
|
||||
class="row"
|
||||
:class="{ 'row--on': !isMobile && selected?.id === entry.id }"
|
||||
:aria-current="
|
||||
!isMobile && selected?.id === entry.id ? 'true' : undefined
|
||||
"
|
||||
<!-- 手机上从详情返回时列表按 v-if 重新挂载:与进入详情同样的淡入上移;
|
||||
不加 appear,从别的页面进来时只走路由过渡 -->
|
||||
<Transition name="market-pane">
|
||||
<nav
|
||||
v-if="!showDetail"
|
||||
class="market__list"
|
||||
:aria-label="$t('market.list_label')"
|
||||
>
|
||||
<span class="row__name">{{ entry.name || entry.id }}</span>
|
||||
<span class="row__version" :title="entry.version">{{ entry.version }}</span>
|
||||
<span class="row__state">
|
||||
<span
|
||||
class="mark"
|
||||
:class="`mark--${entryState(entry)}`"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
{{ stateLabel(entry) }}
|
||||
</span>
|
||||
<span class="row__author">{{ entry.authors.join(', ') }}</span>
|
||||
</RouterLink>
|
||||
<ab-empty
|
||||
v-if="!visible.length"
|
||||
:title="
|
||||
query.trim()
|
||||
? $t('market.no_match', { query: query.trim() })
|
||||
: $t('market.no_filter_match')
|
||||
"
|
||||
/>
|
||||
</nav>
|
||||
<RouterLink
|
||||
v-for="entry in visible"
|
||||
:key="entry.id"
|
||||
:to="`/market/${encodeURIComponent(entry.id)}`"
|
||||
:replace="!isMobile"
|
||||
class="row"
|
||||
:class="{ 'row--on': !isMobile && selected?.id === entry.id }"
|
||||
:aria-current="
|
||||
!isMobile && selected?.id === entry.id ? 'true' : undefined
|
||||
"
|
||||
>
|
||||
<span class="row__name">{{ entry.name || entry.id }}</span>
|
||||
<span class="row__version" :title="entry.version">{{ entry.version }}</span>
|
||||
<span class="row__state">
|
||||
<span
|
||||
class="mark"
|
||||
:class="`mark--${entryState(entry)}`"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
{{ stateLabel(entry) }}
|
||||
</span>
|
||||
<span class="row__author">{{ entry.authors.join(', ') }}</span>
|
||||
</RouterLink>
|
||||
<ab-empty
|
||||
v-if="!visible.length"
|
||||
:title="
|
||||
query.trim()
|
||||
? $t('market.no_match', { query: query.trim() })
|
||||
: $t('market.no_filter_match')
|
||||
"
|
||||
/>
|
||||
</nav>
|
||||
</Transition>
|
||||
|
||||
<section
|
||||
v-if="selected && (!isMobile || showDetail)"
|
||||
@@ -573,6 +577,12 @@ function permissionLabel(id: string) {
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
|
||||
// 平板以上底线由 .market__top::after 画满整行;这里只留透明边框占位
|
||||
@include forTablet {
|
||||
border-bottom-color: transparent;
|
||||
background: none;
|
||||
}
|
||||
}
|
||||
|
||||
// 手机:标题 | 目录状态 / 搜索 | 状态 / 分类;
|
||||
@@ -593,6 +603,17 @@ function permissionLabel(id: string) {
|
||||
'tabs meta meta';
|
||||
grid-template-columns: minmax(0, 1fr) auto 140px;
|
||||
gap: 8px 12px;
|
||||
|
||||
// 分类底线横跨整行(含目录状态),不随目录状态宽度伸缩;
|
||||
// order 让它先画,选中下划线盖在上面
|
||||
&:has(.market__tabs)::after {
|
||||
content: '';
|
||||
grid-column: 1 / -1;
|
||||
grid-row: 2;
|
||||
align-self: end;
|
||||
order: -1;
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -601,9 +622,9 @@ function permissionLabel(id: string) {
|
||||
box-sizing: content-box;
|
||||
height: 27px;
|
||||
|
||||
// 桌面画 ab-segmented 的底线;手机/平板已由 .market__tabs 的背景线画出
|
||||
// 桌面与 ab-segmented 一样留 1px 底边;线由 .market__top::after 画
|
||||
@include forDesktop {
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
border-bottom: 1px solid transparent;
|
||||
}
|
||||
|
||||
@include forTouch {
|
||||
@@ -717,11 +738,11 @@ function permissionLabel(id: string) {
|
||||
@include forTablet {
|
||||
border-right: 1px solid var(--color-border);
|
||||
}
|
||||
}
|
||||
|
||||
// 手机上从详情返回时列表按 v-if 重新挂载:与进入详情同样的淡入上移
|
||||
@include forMobile {
|
||||
animation: detail-enter var(--transition-normal) both;
|
||||
}
|
||||
// 只在手机上从详情返回列表时触发(见模板里的 Transition)
|
||||
.market-pane-enter-active {
|
||||
animation: detail-enter var(--transition-normal) both;
|
||||
}
|
||||
|
||||
// 右列最多占 45%,名称至少保留一半以上
|
||||
@@ -868,12 +889,12 @@ function permissionLabel(id: string) {
|
||||
margin: 0;
|
||||
font-size: 13px;
|
||||
|
||||
@include forMobile {
|
||||
grid-template-columns: minmax(64px, 7.5em) 1fr;
|
||||
}
|
||||
|
||||
dt {
|
||||
color: var(--color-text-secondary);
|
||||
|
||||
@include forMobile {
|
||||
max-width: 7.5em;
|
||||
}
|
||||
}
|
||||
|
||||
dd {
|
||||
|
||||
@@ -14,7 +14,7 @@ const { url } = storeToRefs(usePlayerStore());
|
||||
:description="$t('player.empty.subtitle')"
|
||||
>
|
||||
<template #action>
|
||||
<ol class="empty-steps">
|
||||
<ol class="empty-steps" role="list">
|
||||
<li v-for="n in 3" :key="n">
|
||||
<span class="empty-steps-title">{{
|
||||
$t(`player.empty.step${n}_title`)
|
||||
|
||||
@@ -20,13 +20,18 @@ const ui = computed<PluginUiSlot | undefined>((prev) =>
|
||||
(s) => s.slot === 'page' && s.plugin_id === route.params.id
|
||||
)
|
||||
);
|
||||
// 同上:离场时保留上一个 id,标题不在淡出中变化
|
||||
const pluginId = computed<string>((prev) =>
|
||||
route.name !== 'Plugin' ? (prev ?? '') : route.params.id
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="page-plugin">
|
||||
<!-- 手机上 ab-page-title 隐藏,页面自己给标题 -->
|
||||
<h1 v-if="isMobile && ui" class="page-plugin__title">
|
||||
{{ slotTitle(ui, lang) }}
|
||||
<!-- 与 index.vue 的 pageTitle 一致:插件不存在时用 id 作标题 -->
|
||||
<h1 v-if="isMobile && (ui || loaded || loadFailed)" class="page-plugin__title">
|
||||
{{ ui ? slotTitle(ui, lang) : pluginId }}
|
||||
</h1>
|
||||
<PluginSlot v-if="ui" :key="`${ui.plugin_id}:${ui.element}`" :ui="ui" />
|
||||
<ab-empty
|
||||
|
||||
@@ -95,17 +95,28 @@ input::-webkit-inner-spin-button {
|
||||
.empty-steps {
|
||||
max-width: 52ch;
|
||||
margin: 0 0 14px;
|
||||
padding-left: 1.5em;
|
||||
list-style: decimal;
|
||||
padding-left: 0;
|
||||
list-style: none;
|
||||
counter-reset: empty-step;
|
||||
font-size: 13px;
|
||||
line-height: 1.5;
|
||||
color: var(--color-text-secondary);
|
||||
|
||||
li {
|
||||
position: relative;
|
||||
padding-left: 1.75em;
|
||||
counter-increment: empty-step;
|
||||
}
|
||||
|
||||
li + li {
|
||||
margin-top: 6px;
|
||||
}
|
||||
|
||||
li::marker {
|
||||
// 自绘序号:左缘与上方标题对齐(::marker 落在内容区外,各引擎位置不一)
|
||||
li::before {
|
||||
content: counter(empty-step) '.';
|
||||
position: absolute;
|
||||
left: 0;
|
||||
font-family: var(--font-mono);
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user