task-276(验收反馈): 分页组件全站统一(jumper+按钮高亮样式)+MenusPage补分页+最低价筛选同排+视频筛选加固+移除侧边栏收起按钮+gitignore战役脚本规则

This commit is contained in:
2026-09-06 02:16:47 +08:00
parent 6e2e4bf8e7
commit 3b5adff2e3
14 changed files with 87 additions and 64 deletions
+6
View File
@@ -126,3 +126,9 @@ progress_*.json
# ===== 本地预览工具(不入库)=====
backend/_preview_mock.py
# ===== 战役辅助脚本与本地数据(不入库)=====
scripts/test_task_*.py
scripts/*_scan.py
scripts/*_migration.py
data/logs/
+4 -31
View File
@@ -1,5 +1,5 @@
<script setup lang="ts">
import { computed, ref, watch } from 'vue'
import { computed, watch } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { ElMessage, ElMessageBox } from 'element-plus'
import { useAdminSessionStore } from '@/stores/admin-session'
@@ -12,7 +12,6 @@ import {
shouldShowEmptyMenu,
} from '@/layout/empty-state'
import GlobalErrorContainer from '@/layout/GlobalErrorContainer.vue'
import { initialCollapsed, toggleCollapsed, writeCollapsePreference } from '@/layout/sidebar-collapse'
import { crumbsForActiveRoute, resolveDocumentTitle, updateDocumentTitle } from '@/layout/title-breadcrumb'
import {
LOGOUT_CONFIRM_CANCEL,
@@ -25,22 +24,6 @@ import {
const route = useRoute()
const router = useRouter()
const session = useAdminSessionStore()
const collapsed = ref(readInitialCollapse())
function readInitialCollapse(): boolean {
const storage = typeof window !== 'undefined' ? window.localStorage : null
const width = typeof window !== 'undefined' ? window.innerWidth : 0
return initialCollapsed(width, storage)
}
function setCollapsed(value: boolean): void {
collapsed.value = value
if (typeof window !== 'undefined') writeCollapsePreference(window.localStorage, value)
}
function toggleSidebar(): void {
setCollapsed(toggleCollapsed(collapsed.value))
}
const activePath = computed(() => route.path)
const menuEntries = computed(() => toSidebarEntries(session.menuTree))
@@ -78,19 +61,19 @@ async function signOut() {
</script>
<template>
<div class="admin-shell" :class="{ 'is-collapsed': collapsed }">
<div class="admin-shell">
<a class="skip-link" href="#admin-content">跳到主内容</a>
<aside class="admin-sidebar" role="navigation" aria-label="侧边栏菜单">
<div class="admin-brand">
<img :src="logoUrl" alt="数富AI" class="admin-brand-logo" />
<div v-if="!collapsed" class="admin-brand-copy">
<div class="admin-brand-copy">
<strong>数富AI</strong>
<span>电商运营管理后台</span>
</div>
</div>
<el-scrollbar class="admin-menu-scroll">
<el-menu :default-active="activePath" :default-openeds="openedKeys" :collapse="collapsed" router class="admin-menu">
<el-menu :default-active="activePath" :default-openeds="openedKeys" router class="admin-menu">
<template v-for="entry in menuEntries" :key="entry.key">
<el-sub-menu v-if="entry.kind === 'group'" :index="entry.key">
<template #title>
@@ -107,16 +90,6 @@ async function signOut() {
</el-menu>
<el-empty v-if="emptyMenu" :description="EMPTY_MENU_TITLE" :image-size="72" />
</el-scrollbar>
<button
class="sidebar-collapse"
type="button"
:aria-expanded="!collapsed"
aria-label="切换侧边栏"
@click="toggleSidebar"
>
{{ collapsed ? '展开菜单' : '收起菜单' }}
</button>
</aside>
<section class="admin-main">
@@ -123,7 +123,7 @@ onMounted(loadGroups)
</el-table-column>
</el-table>
<div class="table-footer">
<el-pagination background layout="prev, pager, next" :total="total" :page-size="pageSize" :current-page="page" @current-change="changePage" />
<el-pagination background layout="prev, pager, next, jumper" :total="total" :page-size="pageSize" :current-page="page" @current-change="changePage" />
</div>
</el-card>
<GroupEditorDialog
@@ -22,6 +22,15 @@ import {
const loading = ref(false)
const rows = ref<MenuManageNode[]>([])
// 客户端分页:列表展示统一带分页组件。
const page = ref(1)
const pageSize = 10
const total = computed(() => rows.value.length)
const pagedRows = computed(() => rows.value.slice((page.value - 1) * pageSize, page.value * pageSize))
function changePage(p: number) {
page.value = p
}
const createVisible = ref(false)
const saving = ref(false)
const editingNode = ref<MenuManageNode | null>(null)
@@ -181,7 +190,7 @@ onMounted(loadMenus)
</div>
<el-card shadow="never">
<p class="menu-drag-tip">拖动每行左侧的手柄可调整同级菜单的显示顺序松开后自动保存</p>
<el-table v-loading="loading" :data="rows" row-key="id" default-expand-all stripe>
<el-table v-loading="loading" :data="pagedRows" row-key="id" default-expand-all stripe>
<el-table-column label="排序" min-width="64">
<template #default="{ row }">
<span
@@ -218,6 +227,16 @@ onMounted(loadMenus)
</template>
</el-table-column>
</el-table>
<div class="table-footer">
<el-pagination
background
layout="prev, pager, next, jumper"
:total="total"
:page-size="pageSize"
:current-page="page"
@current-change="changePage"
/>
</div>
</el-card>
<el-dialog v-model="createVisible" :title="dialogTitle" width="520px">
@@ -352,7 +352,7 @@ onMounted(loadTree)
</el-table-column>
</el-table>
<div class="table-footer">
<el-pagination background layout="prev, pager, next" :total="searchTotal" :page-size="PRODUCT_CATEGORY_DEFAULT_PAGE_SIZE" :current-page="searchPage" @current-change="searchChangePage" />
<el-pagination background layout="prev, pager, next, jumper" :total="searchTotal" :page-size="PRODUCT_CATEGORY_DEFAULT_PAGE_SIZE" :current-page="searchPage" @current-change="searchChangePage" />
</div>
</el-card>
@@ -390,13 +390,13 @@ onMounted(() => {
<el-option v-for="code in COUNTRIES" :key="code" :label="asinCountryLabel(code)" :value="code" />
</el-select>
</div>
<div class="f-item">
<label>最低价 </label>
<el-input v-model="filter.minimumPriceFrom" placeholder="数值" clearable @keyup.enter="apply" />
</div>
<div class="f-item">
<label>最低价 </label>
<el-input v-model="filter.minimumPriceTo" placeholder="数值" clearable @keyup.enter="apply" />
<div class="f-item price-range">
<label>最低价</label>
<div class="price-range-row">
<el-input v-model="filter.minimumPriceFrom" placeholder="≥ 最低值" clearable @keyup.enter="apply" />
<span class="range-sep"></span>
<el-input v-model="filter.minimumPriceTo" placeholder="≤ 最高值" clearable @keyup.enter="apply" />
</div>
</div>
<div class="f-item btn-row">
<el-button type="primary" @click="apply">查询</el-button>
@@ -541,6 +541,11 @@ onMounted(() => {
.f-item { display: flex; flex-direction: column; gap: 6px; width: 190px; }
.f-item label { color: var(--el-text-color-secondary); font-size: 12px; }
.f-item.btn-row { flex-direction: row; align-items: flex-end; gap: 8px; width: auto; }
/* 最低价范围筛选:≥ 与 ≤ 并排同一行。 */
.f-item.price-range { width: 300px; }
.price-range-row { display: flex; align-items: center; gap: 8px; width: 100%; }
.price-range-row .el-input { flex: 1; }
.range-sep { color: var(--el-text-color-secondary); font-size: 12px; flex: none; }
.table-footer { display: flex; justify-content: space-between; align-items: center; padding-top: 14px; }
.table-footer span { color: var(--el-text-color-secondary); font-size: 12.5px; }
.asin-cell { font-family: ui-monospace, SFMono-Regular, Consolas, monospace; font-size: 12px; }
@@ -118,7 +118,7 @@ onMounted(load)
</el-table-column>
</el-table>
<div class="table-footer">
<el-pagination background layout="prev, pager, next" :total="total" :page-size="pageSize" :current-page="page" @current-change="changePage" />
<el-pagination background layout="prev, pager, next, jumper" :total="total" :page-size="pageSize" :current-page="page" @current-change="changePage" />
</div>
</el-card>
@@ -270,7 +270,6 @@ onMounted(load)
<el-pagination
background
layout="prev, pager, next, jumper"
small
:total="totalTasks"
:page-size="pageSize"
:current-page="page"
@@ -379,7 +378,7 @@ onMounted(load)
.f-item { display: flex; flex-direction: column; gap: 6px; width: 220px; }
.f-item label { color: var(--el-text-color-secondary); font-size: 12px; }
.f-item.wide { width: 340px; }
.f-item.btn-row { flex-direction: row; align-items: flex-end; gap: 8px; width: auto; }
.f-item.btn-row { flex-direction: row; align-items: flex-end; gap: 8px; width: auto; margin-left: auto; flex: none; }
.video-summary-row { display: flex; justify-content: space-between; align-items: center; color: var(--el-text-color-secondary); font-size: 13px; }
.task-card-grid { display: flex; flex-direction: column; gap: 14px; }
.task-card { border: 1px solid var(--el-border-color); border-radius: 12px; background: #fff; box-shadow: 0 1px 2px rgba(39, 67, 94, 0.04), 0 14px 30px -24px rgba(39, 67, 94, 0.28); overflow: hidden; }
@@ -280,7 +280,6 @@ onMounted(load)
<el-pagination
background
layout="prev, pager, next, jumper"
small
:total="totalShops"
:page-size="pageSize"
:current-page="page"
@@ -348,7 +347,7 @@ onMounted(load)
<div v-if="allResultRows.length > cardPageSize" class="table-footer">
<el-pagination
background
layout="prev, pager, next"
layout="prev, pager, next, jumper"
:total="allResultRows.length"
:page-size="cardPageSize"
:current-page="cardPage"
+25 -9
View File
@@ -85,14 +85,6 @@ button, input, textarea, select { font: inherit; }
.menu-group-title { font-size: 15px; font-weight: 600; letter-spacing: .4px; color: var(--admin-sidebar-text); }
.menu-group-title:hover { color: var(--admin-primary-strong); }
.sidebar-collapse {
margin: 12px; padding: 9px 10px;
border: 1px solid #cbd9e6; border-radius: 8px;
color: var(--admin-sidebar-text); background: #fff; cursor: pointer;
}
.sidebar-collapse:hover { color: var(--admin-primary-strong); border-color: #95b1cb; background: #edf5fb; }
.sidebar-collapse:focus-visible { outline: 2px solid var(--admin-primary); outline-offset: 2px; }
.skip-link { position: absolute; left: -9999px; width: 1px; height: 1px; overflow: hidden; }
.skip-link:focus {
position: fixed; left: 16px; top: 16px; z-index: 2000; width: auto; height: auto;
@@ -157,7 +149,31 @@ button, input, textarea, select { font: inherit; }
.el-input__wrapper, .el-textarea__inner { border-radius: 9px; }
.el-input__wrapper.is-focus,
.el-select__wrapper.is-focused { box-shadow: 0 0 0 3px rgba(95, 133, 173, 0.16); }
.el-pagination { --el-pagination-button-color: var(--admin-text); }
.el-pagination { --el-pagination-button-color: var(--admin-text); --el-pagination-hover-color: var(--admin-primary-strong); }
/* 分页按钮样式加强:background 模式下每粒按钮带边框、当前页深蓝高亮,浅色主题下清晰可见。 */
.el-pagination.is-background .btn-prev,
.el-pagination.is-background .btn-next,
.el-pagination.is-background .el-pager li {
background: #fff;
border: 1px solid var(--admin-border);
border-radius: 7px;
min-width: 30px;
height: 30px;
line-height: 28px;
color: var(--admin-text);
font-weight: 600;
}
.el-pagination.is-background .el-pager li.is-active {
background: var(--admin-primary-strong);
border-color: var(--admin-primary-strong);
color: #fff;
}
.el-pagination.is-background .btn-prev:hover,
.el-pagination.is-background .btn-next:hover,
.el-pagination.is-background .el-pager li:not(.is-active):hover {
border-color: var(--admin-primary);
color: var(--admin-primary-strong);
}
.el-dialog { border-radius: 14px; }
.el-dialog__header { padding-bottom: 12px; }
.el-dialog__title { font-size: 16px; font-weight: 600; color: var(--admin-text); }
@@ -23,7 +23,7 @@ test('align_shop_data_grid_boundary_empty_input', () => {
const src = readSource(PAGE)
const count = (src.match(/el-pagination/g) || []).length
assert.ok(count >= 2, '页面应含店铺分页 + 卡片分页两处 el-pagination')
assert.match(src, /layout="prev, pager, next"/, '卡片分页含上一页/下一页')
assert.match(src, /layout="prev, pager, next, jumper"/, '卡片分页含上一页/下一页')
})
test('align_shop_data_grid_boundary_single_item', () => {
@@ -88,3 +88,12 @@ test('align_skip_price_page_layout_wiring', () => {
assert.match(page, /最低价格式不正确/, '最低价格式校验对齐')
assert.match(page, /请先选择分组/, '店铺下拉未选分组时占位对齐')
})
test('align_skip_price_filter_price_range_same_row', () => {
// 验收反馈:筛选条件「最低价」的 ≥/≤ 输入并排同一排(price-range-row),不换行拆开。
const page = readSource('src/pages/asin/SkipPricePage.vue')
assert.match(page, /price-range-row/, '最低价范围筛选并排容器存在')
assert.match(page, /placeholder="≥ 最低值"/, '下限占位文案')
assert.match(page, /placeholder="≤ 最高值"/, '上限占位文案')
assert.ok(!page.includes('label="最低价 ≥"'), '不再有独立的最低价 ≥ 筛选项')
})
+3 -5
View File
@@ -82,12 +82,10 @@ test('test_task_014_sidebar_responsive_behavior_invalid_input_rejected', () => {
})
test('test_task_014_sidebar_responsive_behavior_dependency_failure_returns_actionable_message', () => {
// 依赖失败:壳层接入折叠逻辑;CSS 有 is-collapsed 与窄屏媒体适配。
// 依赖失败:验收反馈移除收起菜单按钮;折叠逻辑改为恒定展开,窄屏媒体适配保留
const layout = readSource('src/layout/AdminLayout.vue')
assert.match(layout, /initialCollapsed/)
assert.match(layout, /writeCollapsePreference/)
assert.match(layout, /toggleSidebar/)
assert.ok(!layout.includes('toggleSidebar'), '收起按钮点击逻辑已移除')
assert.ok(!layout.includes('sidebar-collapse'), '收起按钮已从布局移除')
const css = readSource('src/styles/main.css')
assert.match(css, /is-collapsed/)
assert.match(css, /@media \(max-width: 820px\)/)
})
+2 -3
View File
@@ -29,12 +29,11 @@ test('test_task_243_shell_normal_repeated_operation_is_idempotent', () => {
})
test('test_task_243_shell_boundary_empty_input', () => {
// 边界:保留折叠与窄屏适配;collapse 按钮仍在布局中
// 边界:验收反馈已移除收起菜单按钮;窄屏适配保留
const css = readSource('src/styles/main.css')
assert.match(css, /is-collapsed/)
assert.match(css, /@media \(max-width:\s*820px\)/)
const layout = readSource('src/layout/AdminLayout.vue')
assert.match(layout, /sidebar-collapse/)
assert.ok(!layout.includes('sidebar-collapse'), '收起按钮已移除')
})
test('test_task_243_shell_boundary_single_item', () => {