jcloud/dashboard/src2/pages/DetailPage.vue
2025-04-12 17:39:38 +08:00

198 lines
4.9 KiB
Vue

<template>
<Header class="sticky top-0 z-10 bg-white">
<div class="w-full sm:flex sm:items-center sm:justify-between">
<div class="flex items-center space-x-2">
<FBreadcrumbs :items="breadcrumbs" />
<Badge
class="hidden sm:inline-flex"
v-if="$resources.document?.pg && badge"
v-bind="badge"
/>
</div>
<div
class="mt-1 flex items-center justify-between space-x-2 sm:mt-0"
v-if="$resources.document?.pg"
>
<div class="sm:hidden">
<Badge v-if="$resources.document?.pg && badge" v-bind="badge" />
</div>
<div class="space-x-2">
<ActionButton
v-for="button in actions"
v-bind="button"
:key="button.label"
/>
</div>
</div>
</div>
</Header>
<div>
<TabsWithRouter
v-if="!$resources.document.get.error && $resources.document.get.fetched"
:document="$resources.document?.pg"
:tabs="tabs"
>
<template #tab-content="{ tab }">
<!-- 这个 div 是必需的 -->
<div></div>
<router-view
v-if="$resources.document?.pg"
:tab="tab"
:document="$resources.document"
/>
</template>
</TabsWithRouter>
<div
v-else-if="$resources.document.get.error"
class="mx-auto mt-60 w-fit rounded border border-dashed px-12 py-8 text-center text-gray-600"
>
<i-lucide-alert-triangle class="mx-auto mb-4 h-6 w-6 text-red-600" />
<ErrorMessage :message="$resources.document.get.error" />
</div>
</div>
</template>
<script>
import Header from '../components/Header.vue';
import ActionButton from '../components/ActionButton.vue';
import { Breadcrumbs } from 'jingrow-ui';
import { getObject } from '../objects';
import TabsWithRouter from '../components/TabsWithRouter.vue';
let subscribed = {};
export default {
name: 'DetailPage',
props: {
id: String,
objectType: {
type: String,
required: true
},
name: {
type: String,
required: true
}
},
components: {
Header,
ActionButton,
TabsWithRouter,
FBreadcrumbs: Breadcrumbs
},
resources: {
document() {
return {
type: 'document',
pagetype: this.object.pagetype,
name: this.name,
whitelistedMethods: this.object.whitelistedMethods || {},
onError(error) {
for (let message of error?.messages || []) {
if (message.redirect) {
window.location.href = message.redirect;
return;
}
}
}
};
}
},
mounted() {
if (!subscribed[`${this.object.pagetype}:${this.name}`]) {
this.$socket.emit('pg_subscribe', this.object.pagetype, this.name);
subscribed[`${this.object.pagetype}:${this.name}`] = true;
}
this.$socket.on('pg_update', data => {
if (data.pagetype === this.object.pagetype && data.name === this.name) {
this.$resources.document.reload();
}
});
},
beforeUnmount() {
let pagetype = this.object.pagetype;
if (subscribed[`${pagetype}:${this.name}`]) {
this.$socket.emit('pg_unsubscribe', pagetype, this.name);
subscribed[`${pagetype}:${this.name}`] = false;
}
},
computed: {
object() {
return getObject(this.objectType);
},
tabs() {
return this.object.detail.tabs.filter(tab => {
if (tab.condition) {
return tab.condition({
documentResource: this.$resources.document
});
}
return true;
});
},
title() {
let pg = this.$resources.document?.pg;
return pg ? pg[this.object.detail.titleField || 'name'] : this.name;
},
badge() {
if (this.object.detail.statusBadge) {
return this.object.detail.statusBadge({
documentResource: this.$resources.document
});
}
return null;
},
actions() {
if (this.object.detail.actions && this.$resources.document?.pg) {
let actions = this.object.detail.actions({
documentResource: this.$resources.document
});
return actions.filter(action => {
if (action.condition) {
return action.condition({
documentResource: this.$resources.document
});
}
return true;
});
}
return [];
},
breadcrumbs() {
let items = [
{ label: this.object.list.title, route: this.object.list.route },
{
label: this.title,
route: {
name: `${this.object.pagetype} 详情`,
params: { name: this.name }
}
}
];
if (this.object.detail.breadcrumbs && this.$resources.document?.pg) {
let result = this.object.detail.breadcrumbs({
documentResource: this.$resources.document,
items
});
if (Array.isArray(result)) {
items = result;
}
}
// 如果面包屑过长则添加省略号
for (let i = 0; i < items.length; i++) {
if (items[i].label.length > 30 && i !== items.length - 1) {
items[i].label = items[i].label.slice(0, 30) + '...';
}
}
return items;
}
}
};
</script>
<style scoped>
:deep(button[role='tab']) {
white-space: nowrap;
}
</style>