pagetype列表页工具栏拆分为独立的组件GenericListPageToolBar.vue
This commit is contained in:
parent
ec822037f8
commit
7aa2b10cdd
@ -29,50 +29,19 @@
|
|||||||
t
|
t
|
||||||
}"
|
}"
|
||||||
/>
|
/>
|
||||||
<template v-else>
|
<GenericListPageToolBar
|
||||||
<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
|
|
||||||
v-else
|
v-else
|
||||||
class="delete-btn"
|
:entity="entity"
|
||||||
@click="handleDeleteSelected"
|
:search-query="searchQuery"
|
||||||
:disabled="loading"
|
:view-mode="viewMode"
|
||||||
>
|
:selected-keys="selectedKeys"
|
||||||
<i class="fa fa-trash"></i>
|
:loading="loading"
|
||||||
{{ t('Delete Selected') }} ({{ selectedKeys.length }})
|
@update:search-query="searchQuery = $event"
|
||||||
</button>
|
@update:view-mode="viewMode = $event"
|
||||||
</template>
|
@reload="reload"
|
||||||
|
@create="createRecordHandler"
|
||||||
|
@delete-selected="handleDeleteSelected"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -303,7 +272,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { onMounted, ref, computed, watch, shallowRef, markRaw } from 'vue'
|
import { onMounted, ref, computed, watch, shallowRef, markRaw } from 'vue'
|
||||||
import { useRoute, useRouter } from 'vue-router'
|
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 { Icon } from '@iconify/vue'
|
||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
import { t } from '@/shared/i18n'
|
import { t } from '@/shared/i18n'
|
||||||
@ -312,6 +281,7 @@ import { usePageTypeSlug } from '@/shared/utils/slug'
|
|||||||
import { isSinglePageType } from '@/shared/utils/pagetype'
|
import { isSinglePageType } from '@/shared/utils/pagetype'
|
||||||
import SinglePageDetail from './SinglePageDetail.vue'
|
import SinglePageDetail from './SinglePageDetail.vue'
|
||||||
import FilterBar from '@/core/components/FilterBar.vue'
|
import FilterBar from '@/core/components/FilterBar.vue'
|
||||||
|
import GenericListPageToolBar from './GenericListPageToolBar.vue'
|
||||||
import {
|
import {
|
||||||
resolvePagetypeListOverride,
|
resolvePagetypeListOverride,
|
||||||
resolvePagetypeListToolbarOverride,
|
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-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; }
|
.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; }
|
.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;
|
color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 工具栏样式(对齐 AgentList) */
|
/* 工具栏容器样式(保留用于布局) */
|
||||||
.header-right { display: flex; align-items: center; gap: 12px; }
|
.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 完全一致 */
|
/* 表格操作列样式 - 与 AgentList 完全一致 */
|
||||||
.col-actions {
|
.col-actions {
|
||||||
|
|||||||
@ -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>
|
||||||
Loading…
x
Reference in New Issue
Block a user