优化pagetype默认详情页,更新为支持直接复制详情页工具栏到新的pagetype里面进行修改覆盖默认模板

This commit is contained in:
jingrow 2025-11-02 19:57:16 +08:00
parent 2760d25b70
commit 67119d3265
2 changed files with 264 additions and 31 deletions

View File

@ -17,9 +17,25 @@
canEdit,
loading,
save: handleSave,
refresh: handleRefresh,
delete: handleDelete,
goBack: goBack,
toggleSidebarPosition: toggleSidebarPosition,
sidebarPosition,
router,
t
}"
:entity="entity"
:id="id"
:record="record"
:can-edit="canEdit"
:loading="loading"
:sidebar-position="sidebarPosition"
@toggle-sidebar-position="toggleSidebarPosition"
@refresh="handleRefresh"
@delete="handleDelete"
@go-back="goBack"
@save="handleSave"
/>
<DefaultToolbar
v-else

View File

@ -1,5 +1,53 @@
<template>
<n-space :size="8">
<n-space align="center">
<!-- 侧边栏位置切换按钮 -->
<n-button
type="default"
size="medium"
@click="$emit('toggle-sidebar-position')"
:title="sidebarPosition === 'left' ? '切换到右侧' : '切换到左侧'"
class="header-action-btn"
>
<template #icon>
<n-icon>
<Icon :icon="sidebarPosition === 'left' ? 'fluent:panel-right-16-regular' : 'fluent:panel-left-16-regular'" />
</n-icon>
</template>
</n-button>
<!-- 刷新按钮 -->
<n-button
type="default"
size="medium"
@click="$emit('refresh')"
:disabled="loading || isNew"
:title="t('Refresh')"
class="header-action-btn"
>
<template #icon>
<n-icon>
<Icon icon="tabler:refresh" />
</n-icon>
</template>
</n-button>
<!-- 删除按钮 -->
<n-button
type="default"
size="medium"
@click="$emit('delete')"
:disabled="loading || isNew"
:title="t('Delete')"
class="header-action-btn delete-btn"
>
<template #icon>
<n-icon>
<Icon icon="tabler:trash" />
</n-icon>
</template>
</n-button>
<!-- Edit Schema 按钮 -->
<n-button
type="default"
size="medium"
@ -22,33 +70,79 @@
:on-save="handleSchemaSave"
@close="showSchemaEditor = false"
/>
<!-- 返回按钮 -->
<n-button type="default" size="medium" @click="$emit('go-back')" :disabled="loading">
<template #icon>
<n-icon><Icon icon="tabler:arrow-left" /></n-icon>
</template>
{{ t('Back') }}
</n-button>
<!-- 保存按钮 -->
<n-button
type="primary"
size="medium"
:disabled="loading"
@click="$emit('save')"
v-if="canEdit"
class="save-btn-brand"
>
<template #icon>
<n-icon>
<Icon icon="tabler:check" />
</n-icon>
</template>
{{ t('Save') }}
</n-button>
</n-space>
</template>
<script setup lang="ts">
import { ref, computed } from 'vue'
import { NSpace, NButton, NIcon, useMessage } from 'naive-ui'
import { ref, computed, watch, onMounted } from 'vue'
import { NButton, NSpace, NIcon, useMessage } from 'naive-ui'
import { Icon } from '@iconify/vue'
import { t } from '@/shared/i18n'
import SchemaEditorModal from '@/core/components/SchemaEditorModal.vue'
import axios from 'axios'
import { get_session_api_headers } from '@/shared/api/auth'
const props = defineProps<{ context: {
entity: any
id: any
record: any
canEdit: any
loading: any
save: () => Promise<void> | void
router: any
t: (k: string) => string
} }>()
interface Props {
entity: string
id: string
record?: any
canEdit: boolean
loading: boolean
sidebarPosition?: 'left' | 'right'
}
interface Emits {
(e: 'toggle-sidebar-position'): void
(e: 'refresh'): void
(e: 'delete'): void
(e: 'go-back'): void
(e: 'save'): void
}
const props = withDefaults(defineProps<Props>(), {
sidebarPosition: 'left',
record: () => ({})
})
const emit = defineEmits<Emits>()
const message = useMessage()
const t = props.context.t
const isNew = computed(() => String(props.context.id?.value || props.context.id) === 'new')
const showSchemaEditor = ref(false)
const nodeName = computed(() => String(props.context.id?.value || props.context.id))
const nodeRecord = computed(() => props.context.record?.value || props.context.record || {})
//
const isNew = computed(() => {
const idValue = props.id
return idValue === 'new' || idValue.startsWith('new-')
})
// Schema
const showSchemaEditor = ref(false)
const nodeName = computed(() => String(props.id))
const nodeRecord = computed(() => props.record || {})
function openSchemaEditor() {
if (isNew.value) return
@ -63,28 +157,150 @@ function openSchemaEditor() {
async function handleSchemaSave(schemaData: any) {
try {
//
if (props.context.record?.value) {
props.context.record.value.node_schema = schemaData
} else if (props.context.record) {
props.context.record.node_schema = schemaData
}
// Schema
const response = await axios.put(
`/api/data/${encodeURIComponent(props.entity)}/${encodeURIComponent(nodeName.value)}`,
{
node_schema: schemaData
},
{
headers: get_session_api_headers(),
withCredentials: true
}
)
//
if (props.context.save) {
await props.context.save()
if (response.data?.success !== false) {
//
if (props.record) {
props.record.node_schema = schemaData
}
message.success(t('Schema saved successfully'))
showSchemaEditor.value = false
} else {
throw new Error('No save method provided')
throw new Error(response.data?.message || t('Save failed'))
}
} catch (e) {
throw e //
} catch (error: any) {
console.error('保存 Schema 失败:', error)
message.error(error?.response?.data?.message || error?.message || t('Save failed, please check permission and server logs'))
throw error //
}
}
// localStorage
const sidebarPosition = ref<'left' | 'right'>(props.sidebarPosition || 'left')
// props
watch(() => props.sidebarPosition, (newPos) => {
if (newPos) {
sidebarPosition.value = newPos
}
}, { immediate: true })
onMounted(() => {
// sidebarPosition localStorage
if (!props.sidebarPosition) {
const savedPosition = localStorage.getItem('jingrow-sidebar-position')
if (savedPosition === 'left' || savedPosition === 'right') {
sidebarPosition.value = savedPosition
}
}
})
</script>
<style scoped>
/* 头部操作按钮统一样式 */
.header-action-btn {
display: inline-flex;
align-items: center;
justify-content: center;
}
/* 删除按钮悬浮时使用红色 */
.header-action-btn.delete-btn:hover:not(:disabled) {
background: #ef4444 !important;
border-color: #ef4444 !important;
color: white !important;
}
.header-action-btn.delete-btn:hover:not(:disabled) :deep(.n-button__border),
.header-action-btn.delete-btn:hover:not(:disabled) :deep(.n-button__state-border) {
border-color: #ef4444 !important;
}
/* 保存按钮 - 使用柔和的品牌色系,与列表页创建按钮一致 */
.save-btn-brand {
background: #e6f8f0 !important;
border: 1px solid #1fc76f !important;
color: #0d684b !important;
}
.save-btn-brand :deep(.n-button__border) {
border: none !important;
border-color: transparent !important;
}
.save-btn-brand :deep(.n-button__state-border) {
border: none !important;
border-color: transparent !important;
}
.save-btn-brand:hover {
background: #dcfce7 !important;
border-color: #1fc76f !important;
border: 1px solid #1fc76f !important;
color: #166534 !important;
box-shadow: 0 2px 8px rgba(31, 199, 111, 0.15) !important;
}
.save-btn-brand:hover :deep(.n-button__border),
.save-btn-brand:hover :deep(.n-button__state-border) {
border: none !important;
border-color: transparent !important;
}
.save-btn-brand:focus {
background: #dcfce7 !important;
border-color: #1fc76f !important;
border: 1px solid #1fc76f !important;
color: #166534 !important;
box-shadow: 0 0 0 2px rgba(31, 199, 111, 0.2) !important;
}
.save-btn-brand:focus :deep(.n-button__border),
.save-btn-brand:focus :deep(.n-button__state-border) {
border: none !important;
border-color: transparent !important;
}
.save-btn-brand:active {
background: #1fc76f !important;
border-color: #1fc76f !important;
border: 1px solid #1fc76f !important;
color: white !important;
box-shadow: 0 1px 4px rgba(31, 199, 111, 0.2) !important;
}
.save-btn-brand:active :deep(.n-button__border),
.save-btn-brand:active :deep(.n-button__state-border) {
border: none !important;
border-color: transparent !important;
}
.save-btn-brand:disabled {
background: #f1f5f9 !important;
border: 1px solid #e2e8f0 !important;
border-color: #e2e8f0 !important;
color: #94a3b8 !important;
opacity: 0.6 !important;
cursor: not-allowed !important;
}
.save-btn-brand:disabled :deep(.n-button__border),
.save-btn-brand:disabled :deep(.n-button__state-border) {
border: none !important;
border-color: transparent !important;
}
/* 工具栏按钮基础样式 - 与返回按钮一致使用badge配色方案 */
.toolbar-btn {
background: #f3f4f6 !important;
@ -136,3 +352,4 @@ async function handleSchemaSave(schemaData: any) {
border-color: transparent !important;
}
</style>