pagetype列表页工具栏拆分为独立的组件GenericListPageToolBar.vue

This commit is contained in:
jingrow 2025-11-02 14:55:41 +08:00
parent ec822037f8
commit 7aa2b10cdd
2 changed files with 301 additions and 163 deletions

View File

@ -29,50 +29,19 @@
t
}"
/>
<template v-else>
<div class="filters">
<n-input v-model:value="searchQuery" :placeholder="t('Search')" clearable style="width: 200px" />
</div>
<div class="view-toggle">
<button
class="toggle-btn"
:class="{ active: viewMode === 'list' }"
@click="viewMode = 'list'"
:title="t('List View')"
>
<i class="fa fa-list"></i>
</button>
<button
class="toggle-btn"
:class="{ active: viewMode === 'card' }"
@click="viewMode = 'card'"
:title="t('Card View')"
>
<i class="fa fa-th-large"></i>
</button>
</div>
<button class="refresh-btn" @click="reload" :disabled="loading">
<i :class="loading ? 'fa fa-spinner fa-spin' : 'fa fa-refresh'"></i>
</button>
<button
v-if="selectedKeys.length === 0"
class="create-btn"
@click="createRecordHandler"
:disabled="loading"
>
<i class="fa fa-plus"></i>
{{ t('Create') }}
</button>
<button
<GenericListPageToolBar
v-else
class="delete-btn"
@click="handleDeleteSelected"
:disabled="loading"
>
<i class="fa fa-trash"></i>
{{ t('Delete Selected') }} ({{ selectedKeys.length }})
</button>
</template>
:entity="entity"
:search-query="searchQuery"
:view-mode="viewMode"
:selected-keys="selectedKeys"
:loading="loading"
@update:search-query="searchQuery = $event"
@update:view-mode="viewMode = $event"
@reload="reload"
@create="createRecordHandler"
@delete-selected="handleDeleteSelected"
/>
</div>
</div>
@ -303,7 +272,7 @@
<script setup lang="ts">
import { onMounted, ref, computed, watch, shallowRef, markRaw } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { NInput, NPagination, useMessage } from 'naive-ui'
import { NPagination, useMessage } from 'naive-ui'
import { Icon } from '@iconify/vue'
import axios from 'axios'
import { t } from '@/shared/i18n'
@ -312,6 +281,7 @@ import { usePageTypeSlug } from '@/shared/utils/slug'
import { isSinglePageType } from '@/shared/utils/pagetype'
import SinglePageDetail from './SinglePageDetail.vue'
import FilterBar from '@/core/components/FilterBar.vue'
import GenericListPageToolBar from './GenericListPageToolBar.vue'
import {
resolvePagetypeListOverride,
resolvePagetypeListToolbarOverride,
@ -1229,14 +1199,6 @@ function formatDisplayValue(value: any, fieldName: string) {
.header-left h2 { font-size: 28px; font-weight: 700; color: #1f2937; margin: 0; }
.header-right { display: flex; align-items: center; gap: 12px; }
.filters { display: flex; gap: 8px; align-items: center; }
.refresh-btn { width: 36px; height: 36px; border: none; border-radius: 8px; background: #f8fafc; color: #64748b; cursor: pointer; display: flex; align-items: center; justify-content: center; font-size: 14px; transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1); position: relative; overflow: hidden; }
.refresh-btn:hover { background: #e2e8f0; color: #475569; transform: translateY(-1px); box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); }
.refresh-btn:active { transform: translateY(0); box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1); }
.refresh-btn:disabled { opacity: 0.6; cursor: not-allowed; transform: none; box-shadow: none; }
.refresh-btn:disabled:hover { background: #f8fafc; color: #64748b; transform: none; box-shadow: none; }
.agent-list { background: white; border: 1px solid #e5e7eb; border-radius: 12px; overflow: hidden; }
.list-pagination { padding: 16px 20px; border-top: 1px solid #e5e7eb; background: #f9fafb; display: flex; justify-content: center; }
@ -1493,118 +1455,8 @@ function formatDisplayValue(value: any, fieldName: string) {
color: white;
}
/* 工具栏样式(对齐 AgentList */
/* 工具栏容器样式(保留用于布局 */
.header-right { display: flex; align-items: center; gap: 12px; }
.filters { display: flex; gap: 8px; align-items: center; }
/* 视图切换按钮 */
.view-toggle {
display: flex;
background: #f8fafc;
border-radius: 8px;
padding: 2px;
border: 1px solid #e2e8f0;
}
/* 切换按钮 - 使用灰色系 */
.toggle-btn {
width: 32px;
height: 32px;
border: none;
background: transparent;
color: #6b7280;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
border-radius: 6px;
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
}
.toggle-btn:hover {
background: #f1f5f9;
color: #475569;
}
.toggle-btn.active {
background: #e2e8f0;
color: #1e293b;
}
/* 刷新按钮 */
.refresh-btn {
width: 36px; height: 36px; border: none; border-radius: 8px; background: #f8fafc;
color: #64748b; cursor: pointer; display: flex; align-items: center;
justify-content: center; font-size: 14px; transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
position: relative; overflow: hidden;
}
.refresh-btn:hover { background: #e2e8f0; color: #475569; transform: translateY(-1px); box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); }
.refresh-btn:active { transform: translateY(0); box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1); }
.refresh-btn:disabled { opacity: 0.6; cursor: not-allowed; transform: none; box-shadow: none; }
.refresh-btn:disabled:hover { background: #f8fafc; color: #64748b; transform: none; box-shadow: none; }
/* 新建按钮 - 使用柔和的品牌色系,与整体风格协调 */
.create-btn {
height: 36px;
padding: 0 16px;
border: 1px solid #1fc76f;
border-radius: 8px;
background: #e6f8f0;
color: #0d684b;
cursor: pointer;
display: flex;
align-items: center;
gap: 6px;
font-size: 14px;
font-weight: 500;
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
position: relative;
overflow: hidden;
}
.create-btn:hover {
background: #dcfce7;
border-color: #1fc76f;
color: #166534;
transform: translateY(-1px);
box-shadow: 0 2px 8px rgba(31, 199, 111, 0.15);
}
.create-btn:active {
background: #1fc76f;
border-color: #1fc76f;
color: white;
transform: translateY(0);
box-shadow: 0 1px 4px rgba(31, 199, 111, 0.2);
}
.create-btn:disabled {
background: #f1f5f9;
border-color: #e2e8f0;
color: #94a3b8;
opacity: 0.6;
cursor: not-allowed;
transform: none;
box-shadow: none;
}
.create-btn:disabled:hover {
background: #f1f5f9;
border-color: #e2e8f0;
color: #94a3b8;
transform: none;
box-shadow: none;
}
.create-btn i {
font-size: 12px;
}
/* 删除选中按钮 */
.delete-btn {
background: #ef4444; color: white; border: none; padding: 8px 16px; border-radius: 6px;
cursor: pointer; font-size: 14px; font-weight: 500; display: flex; align-items: center;
gap: 6px; transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1); position: relative; overflow: hidden;
}
.delete-btn:hover { background: #dc2626; transform: translateY(-1px); box-shadow: 0 4px 12px rgba(239, 68, 68, 0.3); }
.delete-btn:active { transform: translateY(0); box-shadow: 0 2px 6px rgba(239, 68, 68, 0.3); }
.delete-btn:disabled { opacity: 0.5; cursor: not-allowed; }
.delete-btn i { font-size: 12px; }
/* 表格操作列样式 - 与 AgentList 完全一致 */
.col-actions {

View File

@ -0,0 +1,286 @@
<template>
<div class="header-right">
<div class="filters">
<n-input
v-model:value="searchQueryModel"
:placeholder="t('Search')"
clearable
style="width: 200px"
/>
</div>
<div class="view-toggle">
<button
class="toggle-btn"
:class="{ active: viewModeModel === 'list' }"
@click="viewModeModel = 'list'"
:title="t('List View')"
>
<i class="fa fa-list"></i>
</button>
<button
class="toggle-btn"
:class="{ active: viewModeModel === 'card' }"
@click="viewModeModel = 'card'"
:title="t('Card View')"
>
<i class="fa fa-th-large"></i>
</button>
</div>
<button class="refresh-btn" @click="reload" :disabled="loading">
<i :class="loading ? 'fa fa-spinner fa-spin' : 'fa fa-refresh'"></i>
</button>
<button
v-if="selectedKeys.length === 0"
class="create-btn"
@click="createRecordHandler"
:disabled="loading"
>
<i class="fa fa-plus"></i>
{{ t('Create') }}
</button>
<button
v-else
class="delete-btn"
@click="handleDeleteSelected"
:disabled="loading"
>
<i class="fa fa-trash"></i>
{{ t('Delete Selected') }} ({{ selectedKeys.length }})
</button>
</div>
</template>
<script setup lang="ts">
import { computed } from 'vue'
import { NInput } from 'naive-ui'
import { t } from '@/shared/i18n'
interface Props {
entity: string
searchQuery: string
viewMode: 'card' | 'list'
selectedKeys: string[]
loading: boolean
}
interface Emits {
(e: 'update:searchQuery', value: string): void
(e: 'update:viewMode', value: 'card' | 'list'): void
(e: 'reload'): void
(e: 'create'): void
(e: 'delete-selected'): void
}
const props = defineProps<Props>()
const emit = defineEmits<Emits>()
// 使 computed
const searchQueryModel = computed({
get: () => props.searchQuery,
set: (value) => emit('update:searchQuery', value)
})
const viewModeModel = computed({
get: () => props.viewMode,
set: (value) => emit('update:viewMode', value)
})
function reload() {
emit('reload')
}
function createRecordHandler() {
emit('create')
}
function handleDeleteSelected() {
emit('delete-selected')
}
</script>
<style scoped>
.header-right {
display: flex;
align-items: center;
gap: 12px;
}
.filters {
display: flex;
gap: 8px;
align-items: center;
}
/* 视图切换按钮 */
.view-toggle {
display: flex;
background: #f8fafc;
border-radius: 8px;
padding: 2px;
border: 1px solid #e2e8f0;
}
/* 切换按钮 - 使用灰色系 */
.toggle-btn {
width: 32px;
height: 32px;
border: none;
background: transparent;
color: #6b7280;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
border-radius: 6px;
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
}
.toggle-btn:hover {
background: #f1f5f9;
color: #475569;
}
.toggle-btn.active {
background: #e2e8f0;
color: #1e293b;
}
/* 刷新按钮 */
.refresh-btn {
width: 36px;
height: 36px;
border: none;
border-radius: 8px;
background: #f8fafc;
color: #64748b;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
font-size: 14px;
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
position: relative;
overflow: hidden;
}
.refresh-btn:hover {
background: #e2e8f0;
color: #475569;
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}
.refresh-btn:active {
transform: translateY(0);
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1);
}
.refresh-btn:disabled {
opacity: 0.6;
cursor: not-allowed;
transform: none;
box-shadow: none;
}
.refresh-btn:disabled:hover {
background: #f8fafc;
color: #64748b;
transform: none;
box-shadow: none;
}
/* 新建按钮 - 使用柔和的品牌色系,与整体风格协调 */
.create-btn {
height: 36px;
padding: 0 16px;
border: 1px solid #1fc76f;
border-radius: 8px;
background: #e6f8f0;
color: #0d684b;
cursor: pointer;
display: flex;
align-items: center;
gap: 6px;
font-size: 14px;
font-weight: 500;
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
position: relative;
overflow: hidden;
}
.create-btn:hover {
background: #dcfce7;
border-color: #1fc76f;
color: #166534;
transform: translateY(-1px);
box-shadow: 0 2px 8px rgba(31, 199, 111, 0.15);
}
.create-btn:active {
background: #1fc76f;
border-color: #1fc76f;
color: white;
transform: translateY(0);
box-shadow: 0 1px 4px rgba(31, 199, 111, 0.2);
}
.create-btn:disabled {
background: #f1f5f9;
border-color: #e2e8f0;
color: #94a3b8;
opacity: 0.6;
cursor: not-allowed;
transform: none;
box-shadow: none;
}
.create-btn:disabled:hover {
background: #f1f5f9;
border-color: #e2e8f0;
color: #94a3b8;
transform: none;
box-shadow: none;
}
.create-btn i {
font-size: 12px;
}
/* 删除选中按钮 */
.delete-btn {
background: #ef4444;
color: white;
border: none;
padding: 8px 16px;
border-radius: 6px;
cursor: pointer;
font-size: 14px;
font-weight: 500;
display: flex;
align-items: center;
gap: 6px;
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
position: relative;
overflow: hidden;
}
.delete-btn:hover {
background: #dc2626;
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(239, 68, 68, 0.3);
}
.delete-btn:active {
transform: translateY(0);
box-shadow: 0 2px 6px rgba(239, 68, 68, 0.3);
}
.delete-btn:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.delete-btn i {
font-size: 12px;
}
</style>