task-276(验收反馈): 分页组件全站统一(jumper+按钮高亮样式)+MenusPage补分页+最低价筛选同排+视频筛选加固+移除侧边栏收起按钮+gitignore战役脚本规则
This commit is contained in:
@@ -126,3 +126,9 @@ progress_*.json
|
||||
|
||||
# ===== 本地预览工具(不入库)=====
|
||||
backend/_preview_mock.py
|
||||
|
||||
# ===== 战役辅助脚本与本地数据(不入库)=====
|
||||
scripts/test_task_*.py
|
||||
scripts/*_scan.py
|
||||
scripts/*_migration.py
|
||||
data/logs/
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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="最低价 ≥"'), '不再有独立的最低价 ≥ 筛选项')
|
||||
})
|
||||
|
||||
@@ -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\)/)
|
||||
})
|
||||
|
||||
@@ -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', () => {
|
||||
|
||||
Reference in New Issue
Block a user