feat: add the entity component as a list item (halo-dev/console#609)

#### What type of PR is this?

/kind feature
/milestone 2.0

#### What this PR does / why we need it:

添加 Entity 和 EntityField 组件,作为列表项使用。

#### Which issue(s) this PR fixes:

#### Screenshots:

#### Special notes for your reviewer:

测试方式:检查后台各个页面的列表样式和功能是否正常。

/cc @halo-dev/sig-halo-admin 

#### Does this PR introduce a user-facing change?

```release-note
None
```
pull/3445/head
Ryan Wang 2022-09-13 12:08:12 +08:00 committed by GitHub
parent 0a4d31fa33
commit bcfe7a52ee
13 changed files with 732 additions and 909 deletions

View File

@ -0,0 +1,55 @@
<script lang="ts" setup>
import { IconMore, VSpace } from "@halo-dev/components";
withDefaults(
defineProps<{
isSelected?: boolean;
}>(),
{
isSelected: false,
}
);
</script>
<template>
<div
:class="{
'bg-gray-100': isSelected,
}"
class="group relative block cursor-pointer px-4 py-3 transition-all hover:bg-gray-50"
>
<div
v-show="isSelected"
class="absolute inset-y-0 left-0 w-0.5 bg-primary"
></div>
<slot name="prepend" />
<div class="relative flex flex-row items-center">
<div v-if="$slots.checkbox" class="mr-4 hidden items-center sm:flex">
<slot name="checkbox" />
</div>
<div class="flex flex-1 items-center gap-4">
<slot name="start" />
</div>
<div
class="flex flex-col items-end gap-4 sm:flex-row sm:items-center sm:gap-6"
>
<slot name="end" />
</div>
<div v-if="$slots.menuItems" class="ml-4 inline-flex items-center">
<FloatingDropdown>
<div
class="cursor-pointer rounded p-1 transition-all hover:text-blue-600 group-hover:bg-gray-100"
>
<IconMore />
</div>
<template #popper>
<div class="w-48 p-2">
<VSpace class="w-full" direction="column">
<slot name="menuItems"></slot>
</VSpace>
</div>
</template>
</FloatingDropdown>
</div>
</div>
</div>
</template>

View File

@ -0,0 +1,54 @@
<script lang="ts" setup>
import type { RouteLocationRaw } from "vue-router";
withDefaults(
defineProps<{
title?: string;
description?: string;
route?: RouteLocationRaw;
}>(),
{
title: undefined,
description: undefined,
route: undefined,
}
);
const emit = defineEmits<{
(event: "click"): void;
}>();
</script>
<template>
<div class="inline-flex flex-col gap-1">
<div
v-if="title || $slots.title"
class="inline-flex flex-col items-center sm:flex-row"
>
<slot name="title">
<div
class="mr-0 truncate text-sm font-medium text-gray-900 sm:mr-2"
@click="emit('click')"
>
<RouterLink v-if="route" :to="route">
{{ title }}
</RouterLink>
<span v-else>
{{ title }}
</span>
</div>
<slot name="extra" />
</slot>
</div>
<div
v-if="description || $slots.description"
class="inline-flex items-center"
>
<slot name="description">
<span v-if="description" class="text-xs text-gray-500">
{{ description }}
</span>
</slot>
</div>
</div>
</template>

View File

@ -7,7 +7,6 @@ import {
IconDatabase2Line, IconDatabase2Line,
IconGrid, IconGrid,
IconList, IconList,
IconSettings,
IconUpload, IconUpload,
VButton, VButton,
VCard, VCard,
@ -36,6 +35,8 @@ import cloneDeep from "lodash.clonedeep";
import { isImage } from "@/utils/image"; import { isImage } from "@/utils/image";
import { useRouteQuery } from "@vueuse/router"; import { useRouteQuery } from "@vueuse/router";
import { useFetchAttachmentGroup } from "./composables/use-attachment-group"; import { useFetchAttachmentGroup } from "./composables/use-attachment-group";
import Entity from "@/components/entity/Entity.vue";
import EntityField from "@/components/entity/EntityField.vue";
const policyVisible = ref(false); const policyVisible = ref(false);
const uploadVisible = ref(false); const uploadVisible = ref(false);
@ -555,28 +556,20 @@ onMounted(() => {
role="list" role="list"
> >
<li v-for="(attachment, index) in attachments.items" :key="index"> <li v-for="(attachment, index) in attachments.items" :key="index">
<div <Entity :is-selected="isChecked(attachment)">
:class="{ <template #checkbox>
'bg-gray-100': isChecked(attachment),
}"
class="relative block cursor-pointer px-4 py-3 transition-all hover:bg-gray-50"
>
<div
v-show="isChecked(attachment)"
class="absolute inset-y-0 left-0 w-0.5 bg-primary"
></div>
<div class="relative flex flex-row items-center">
<div class="mr-4 hidden items-center sm:flex">
<input <input
:checked="selectedAttachments.has(attachment)" :checked="selectedAttachments.has(attachment)"
class="h-4 w-4 rounded border-gray-300 text-indigo-600" class="h-4 w-4 rounded border-gray-300 text-indigo-600"
type="checkbox" type="checkbox"
@click="handleSelect(attachment)" @click="handleSelect(attachment)"
/> />
</div> </template>
<div class="mr-4"> <template #start>
<EntityField>
<template #description>
<div <div
class="h-12 w-12 rounded border bg-white p-1 hover:shadow-sm" class="h-10 w-10 rounded border bg-white p-1 hover:shadow-sm"
> >
<AttachmentFileTypeIcon <AttachmentFileTypeIcon
:display-ext="false" :display-ext="false"
@ -585,17 +578,13 @@ onMounted(() => {
:height="8" :height="8"
/> />
</div> </div>
</div> </template>
<div class="flex-1"> </EntityField>
<div class="flex flex-col sm:flex-row"> <EntityField
<span :title="attachment.spec.displayName"
class="mr-0 truncate text-sm font-medium text-gray-900 sm:mr-2"
@click="handleClickItem(attachment)" @click="handleClickItem(attachment)"
> >
{{ attachment.spec.displayName }} <template #description>
</span>
</div>
<div class="mt-1 flex">
<VSpace> <VSpace>
<span class="text-xs text-gray-500"> <span class="text-xs text-gray-500">
{{ attachment.spec.mediaType }} {{ attachment.spec.mediaType }}
@ -604,54 +593,50 @@ onMounted(() => {
{{ prettyBytes(attachment.spec.size || 0) }} {{ prettyBytes(attachment.spec.size || 0) }}
</span> </span>
</VSpace> </VSpace>
</div> </template>
</div> </EntityField>
<div class="flex"> </template>
<div <template #end>
class="inline-flex flex-col items-end gap-4 sm:flex-row sm:items-center sm:gap-6" <EntityField
> :description="
<span class="text-sm text-gray-500"> getPolicyName(attachment.spec.policyRef?.name)
{{ getPolicyName(attachment.spec.policyRef?.name) }} "
</span> />
<EntityField>
<template #description>
<RouterLink <RouterLink
:to="{ :to="{
name: 'UserDetail', name: 'UserDetail',
params: { name: attachment.spec.uploadedBy?.name }, params: { name: attachment.spec.uploadedBy?.name },
}" }"
class="text-xs text-gray-500"
> >
<span class="text-sm text-gray-500">
{{ attachment.spec.uploadedBy?.name }} {{ attachment.spec.uploadedBy?.name }}
</span>
</RouterLink> </RouterLink>
<FloatingTooltip </template>
v-if="attachment.metadata.deletionTimestamp" </EntityField>
class="hidden items-center sm:flex" <EntityField v-if="attachment.metadata.deletionTimestamp">
> <template #description>
<div <div
v-tooltip="`删除中`"
class="inline-flex h-1.5 w-1.5 rounded-full bg-red-600" class="inline-flex h-1.5 w-1.5 rounded-full bg-red-600"
> >
<span <span
class="inline-block h-1.5 w-1.5 animate-ping rounded-full bg-red-600" class="inline-block h-1.5 w-1.5 animate-ping rounded-full bg-red-600"
></span> ></span>
</div> </div>
<template #popper> 删除中</template> </template>
</FloatingTooltip> </EntityField>
<time class="text-sm text-gray-500"> <EntityField
{{ :description="
formatDatetime( formatDatetime(attachment.metadata.creationTimestamp)
attachment.metadata.creationTimestamp "
)
}}
</time>
<span class="cursor-pointer">
<IconSettings
@click.stop="handleClickItem(attachment)"
/> />
</span> </template>
</div> <template #menuItems>
</div> <VButton v-close-popper block type="danger"> 删除 </VButton>
</div> </template>
</div> </Entity>
</li> </li>
</ul> </ul>
</div> </div>

View File

@ -3,6 +3,8 @@ import { ref } from "vue";
import { VEmpty, VSpace, VButton, IconAddCircle } from "@halo-dev/components"; import { VEmpty, VSpace, VButton, IconAddCircle } from "@halo-dev/components";
import type { PagesPublicState } from "@halo-dev/admin-shared"; import type { PagesPublicState } from "@halo-dev/admin-shared";
import { useExtensionPointsState } from "@/composables/usePlugins"; import { useExtensionPointsState } from "@/composables/usePlugins";
import Entity from "@/components/entity/Entity.vue";
import EntityField from "@/components/entity/EntityField.vue";
const pagesPublicState = ref<PagesPublicState>({ const pagesPublicState = ref<PagesPublicState>({
functionalPages: [], functionalPages: [],
@ -38,27 +40,15 @@ useExtensionPointsState("PAGES", pagesPublicState);
:key="index" :key="index"
v-permission="page.permissions" v-permission="page.permissions"
> >
<div <Entity>
class="relative block cursor-pointer px-4 py-3 transition-all hover:bg-gray-50" <template #start>
> <EntityField
<div class="relative flex flex-row items-center"> :title="page.name"
<div class="flex-1"> :route="page.path"
<div class="flex"> :description="page.url"
<RouterLink ></EntityField>
:to="page.path" </template>
class="truncate text-sm font-medium text-gray-900" </Entity>
>
{{ page.name }}
</RouterLink>
</div>
<div class="mt-1 flex">
<span class="text-xs text-gray-500">
{{ page.url }}
</span>
</div>
</div>
</div>
</div>
</li> </li>
</ul> </ul>
</template> </template>

View File

@ -3,7 +3,6 @@ import {
IconArrowDown, IconArrowDown,
IconArrowLeft, IconArrowLeft,
IconArrowRight, IconArrowRight,
IconSettings,
IconEye, IconEye,
IconEyeOff, IconEyeOff,
IconTeam, IconTeam,
@ -30,6 +29,8 @@ import { apiClient } from "@halo-dev/admin-shared";
import { formatDatetime } from "@/utils/date"; import { formatDatetime } from "@/utils/date";
import { RouterLink } from "vue-router"; import { RouterLink } from "vue-router";
import cloneDeep from "lodash.clonedeep"; import cloneDeep from "lodash.clonedeep";
import Entity from "../../../components/entity/Entity.vue";
import EntityField from "../../../components/entity/EntityField.vue";
enum SinglePagePhase { enum SinglePagePhase {
DRAFT = "未发布", DRAFT = "未发布",
@ -332,41 +333,27 @@ const handleSelectUser = (user?: User) => {
role="list" role="list"
> >
<li v-for="(singlePage, index) in singlePages.items" :key="index"> <li v-for="(singlePage, index) in singlePages.items" :key="index">
<div <Entity :is-selected="checkAll">
:class="{ <template #checkbox>
'bg-gray-100': checkAll,
}"
class="relative block cursor-pointer px-4 py-3 transition-all hover:bg-gray-50"
>
<div
v-show="checkAll"
class="absolute inset-y-0 left-0 w-0.5 bg-primary"
></div>
<div class="relative flex flex-row items-center">
<div class="mr-4 hidden items-center sm:flex">
<input <input
v-model="checkAll" v-model="checkAll"
class="h-4 w-4 rounded border-gray-300 text-indigo-600" class="h-4 w-4 rounded border-gray-300 text-indigo-600"
type="checkbox" type="checkbox"
/> />
</div> </template>
<div class="flex-1"> <template #start>
<div class="flex flex-row items-center"> <EntityField
<RouterLink :title="singlePage.page.spec.title"
:to="{ :description="singlePage.page.status?.permalink"
:route="{
name: 'SinglePageEditor', name: 'SinglePageEditor',
query: { name: singlePage.page.metadata.name }, query: { name: singlePage.page.metadata.name },
}" }"
> >
<span class="truncate text-sm font-medium text-gray-900"> <template #extra>
{{ singlePage.page.spec.title }}
</span>
</RouterLink>
<FloatingTooltip
v-if="singlePage.page.status?.inProgress"
class="hidden items-center sm:flex"
>
<RouterLink <RouterLink
v-if="singlePage.page.status?.inProgress"
v-tooltip="`当前有内容已保存,但还未发布。`"
:to="{ :to="{
name: 'SinglePageEditor', name: 'SinglePageEditor',
query: { name: singlePage.page.metadata.name }, query: { name: singlePage.page.metadata.name },
@ -381,19 +368,12 @@ const handleSelectUser = (user?: User) => {
></span> ></span>
</div> </div>
</RouterLink> </RouterLink>
<template #popper> 当前有内容已保存但还未发布 </template> </template>
</FloatingTooltip> </EntityField>
</div> </template>
<div class="mt-1 flex"> <template #end>
<span class="text-xs text-gray-500"> <EntityField>
{{ singlePage.page.status?.permalink }} <template #description>
</span>
</div>
</div>
<div class="flex">
<div
class="inline-flex flex-col items-end gap-4 sm:flex-row sm:items-center sm:gap-6"
>
<RouterLink <RouterLink
v-for="( v-for="(
contributor, contributorIndex contributor, contributorIndex
@ -413,10 +393,11 @@ const handleSelectUser = (user?: User) => {
circle circle
></VAvatar> ></VAvatar>
</RouterLink> </RouterLink>
<span class="text-sm text-gray-500"> </template>
{{ finalStatus(singlePage.page) }} </EntityField>
</span> <EntityField :description="finalStatus(singlePage.page)" />
<span> <EntityField>
<template #description>
<IconEye <IconEye
v-if="singlePage.page.spec.visible === 'PUBLIC'" v-if="singlePage.page.spec.visible === 'PUBLIC'"
v-tooltip="`公开访问`" v-tooltip="`公开访问`"
@ -432,20 +413,15 @@ const handleSelectUser = (user?: User) => {
v-tooltip="`内部成员可访问`" v-tooltip="`内部成员可访问`"
class="cursor-pointer text-sm transition-all hover:text-blue-600" class="cursor-pointer text-sm transition-all hover:text-blue-600"
/> />
</span> </template>
<time class="text-sm text-gray-500"> </EntityField>
{{ <EntityField
:description="
formatDatetime(singlePage.page.metadata.creationTimestamp) formatDatetime(singlePage.page.metadata.creationTimestamp)
}} "
</time>
<span>
<FloatingDropdown>
<IconSettings
class="cursor-pointer transition-all hover:text-blue-600"
/> />
<template #popper> </template>
<div class="w-48 p-2"> <template #menuItems>
<VSpace class="w-full" direction="column">
<VButton <VButton
v-close-popper v-close-popper
block block
@ -462,15 +438,8 @@ const handleSelectUser = (user?: User) => {
> >
删除 删除
</VButton> </VButton>
</VSpace>
</div>
</template> </template>
</FloatingDropdown> </Entity>
</span>
</div>
</div>
</div>
</div>
</li> </li>
</ul> </ul>

View File

@ -7,7 +7,6 @@ import {
IconBookRead, IconBookRead,
IconEye, IconEye,
IconEyeOff, IconEyeOff,
IconSettings,
IconTeam, IconTeam,
IconCloseCircle, IconCloseCircle,
useDialog, useDialog,
@ -21,6 +20,8 @@ import {
} from "@halo-dev/components"; } from "@halo-dev/components";
import UserDropdownSelector from "@/components/dropdown-selector/UserDropdownSelector.vue"; import UserDropdownSelector from "@/components/dropdown-selector/UserDropdownSelector.vue";
import PostSettingModal from "./components/PostSettingModal.vue"; import PostSettingModal from "./components/PostSettingModal.vue";
import Entity from "@/components/entity/Entity.vue";
import EntityField from "@/components/entity/EntityField.vue";
import PostTag from "../posts/tags/components/PostTag.vue"; import PostTag from "../posts/tags/components/PostTag.vue";
import { onMounted, ref, watch, watchEffect } from "vue"; import { onMounted, ref, watch, watchEffect } from "vue";
import type { import type {
@ -738,18 +739,8 @@ function handleContributorFilterItemChange(user?: User) {
role="list" role="list"
> >
<li v-for="(post, index) in posts.items" :key="index"> <li v-for="(post, index) in posts.items" :key="index">
<div <Entity :is-selected="checkSelection(post.post)">
:class="{ <template #checkbox>
'bg-gray-100': checkSelection(post.post),
}"
class="relative block cursor-pointer px-4 py-3 transition-all hover:bg-gray-50"
>
<div
v-show="checkSelection(post.post)"
class="absolute inset-y-0 left-0 w-0.5 bg-primary"
></div>
<div class="relative flex flex-row items-center">
<div class="mr-4 hidden items-center sm:flex">
<input <input
v-model="selectedPostNames" v-model="selectedPostNames"
:value="post.post.metadata.name" :value="post.post.metadata.name"
@ -757,27 +748,20 @@ function handleContributorFilterItemChange(user?: User) {
name="post-checkbox" name="post-checkbox"
type="checkbox" type="checkbox"
/> />
</div> </template>
<div class="flex-1"> <template #start>
<div class="flex flex-col items-center sm:flex-row"> <EntityField
<RouterLink :title="post.post.spec.title"
:to="{ :route="{
name: 'PostEditor', name: 'PostEditor',
query: { name: post.post.metadata.name }, query: { name: post.post.metadata.name },
}" }"
> >
<span <template #extra>
class="mr-0 truncate text-sm font-medium text-gray-900 sm:mr-2"
>
{{ post.post.spec.title }}
</span>
</RouterLink>
<VSpace class="mt-1 sm:mt-0"> <VSpace class="mt-1 sm:mt-0">
<FloatingTooltip
v-if="post.post.status?.inProgress"
class="hidden items-center sm:flex"
>
<RouterLink <RouterLink
v-if="post.post.status?.inProgress"
v-tooltip="`当前有内容已保存,但还未发布。`"
:to="{ :to="{
name: 'PostEditor', name: 'PostEditor',
query: { name: post.post.metadata.name }, query: { name: post.post.metadata.name },
@ -792,10 +776,6 @@ function handleContributorFilterItemChange(user?: User) {
></span> ></span>
</div> </div>
</RouterLink> </RouterLink>
<template #popper>
当前有内容已保存但还未发布
</template>
</FloatingTooltip>
<PostTag <PostTag
v-for="(tag, tagIndex) in post.tags" v-for="(tag, tagIndex) in post.tags"
:key="tagIndex" :key="tagIndex"
@ -803,8 +783,8 @@ function handleContributorFilterItemChange(user?: User) {
route route
></PostTag> ></PostTag>
</VSpace> </VSpace>
</div> </template>
<div class="mt-1 flex"> <template #description>
<VSpace> <VSpace>
<p <p
v-if="post.categories.length" v-if="post.categories.length"
@ -821,12 +801,12 @@ function handleContributorFilterItemChange(user?: User) {
<span class="text-xs text-gray-500">访问量 0</span> <span class="text-xs text-gray-500">访问量 0</span>
<span class="text-xs text-gray-500"> 评论 0 </span> <span class="text-xs text-gray-500"> 评论 0 </span>
</VSpace> </VSpace>
</div> </template>
</div> </EntityField>
<div class="flex"> </template>
<div <template #end>
class="inline-flex flex-col items-end gap-4 sm:flex-row sm:items-center sm:gap-6" <EntityField>
> <template #description>
<RouterLink <RouterLink
v-for="(contributor, contributorIndex) in post.contributors" v-for="(contributor, contributorIndex) in post.contributors"
:key="contributorIndex" :key="contributorIndex"
@ -844,10 +824,11 @@ function handleContributorFilterItemChange(user?: User) {
circle circle
></VAvatar> ></VAvatar>
</RouterLink> </RouterLink>
<span class="text-sm text-gray-500"> </template>
{{ finalStatus(post.post) }} </EntityField>
</span> <EntityField :description="finalStatus(post.post)"></EntityField>
<span> <EntityField>
<template #description>
<IconEye <IconEye
v-if="post.post.spec.visible === 'PUBLIC'" v-if="post.post.spec.visible === 'PUBLIC'"
v-tooltip="`公开访问`" v-tooltip="`公开访问`"
@ -863,18 +844,15 @@ function handleContributorFilterItemChange(user?: User) {
v-tooltip="`内部成员可访问`" v-tooltip="`内部成员可访问`"
class="cursor-pointer text-sm transition-all hover:text-blue-600" class="cursor-pointer text-sm transition-all hover:text-blue-600"
/> />
</span> </template>
<time class="text-sm text-gray-500"> </EntityField>
{{ formatDatetime(post.post.metadata.creationTimestamp) }} <EntityField
</time> :description="
<span> formatDatetime(post.post.metadata.creationTimestamp)
<FloatingDropdown> "
<IconSettings
class="cursor-pointer transition-all hover:text-blue-600"
/> />
<template #popper> </template>
<div class="w-48 p-2"> <template #menuItems>
<VSpace class="w-full" direction="column">
<VButton <VButton
v-close-popper v-close-popper
block block
@ -891,15 +869,8 @@ function handleContributorFilterItemChange(user?: User) {
> >
删除 删除
</VButton> </VButton>
</VSpace>
</div>
</template> </template>
</FloatingDropdown> </Entity>
</span>
</div>
</div>
</div>
</div>
</li> </li>
</ul> </ul>

View File

@ -1,9 +1,11 @@
<script lang="ts" setup> <script lang="ts" setup>
import { IconList, IconSettings, VButton, VSpace } from "@halo-dev/components"; import { IconList, VButton } from "@halo-dev/components";
import Draggable from "vuedraggable"; import Draggable from "vuedraggable";
import type { CategoryTree } from "../utils"; import type { CategoryTree } from "../utils";
import { ref } from "vue"; import { ref } from "vue";
import { formatDatetime } from "@/utils/date"; import { formatDatetime } from "@/utils/date";
import Entity from "@/components/entity/Entity.vue";
import EntityField from "@/components/entity/EntityField.vue";
withDefaults( withDefaults(
defineProps<{ defineProps<{
@ -49,61 +51,41 @@ function onDelete(category: CategoryTree) {
> >
<template #item="{ element: category }"> <template #item="{ element: category }">
<li> <li>
<div <Entity>
class="group relative block cursor-pointer px-4 py-3 transition-all hover:bg-gray-50" <template #prepend>
>
<div <div
class="drag-element absolute inset-y-0 left-0 hidden w-3.5 cursor-move items-center bg-gray-100 transition-all hover:bg-gray-200 group-hover:flex" class="drag-element absolute inset-y-0 left-0 hidden w-3.5 cursor-move items-center bg-gray-100 transition-all hover:bg-gray-200 group-hover:flex"
> >
<IconList class="h-3.5 w-3.5" /> <IconList class="h-3.5 w-3.5" />
</div> </div>
<div class="relative flex flex-row items-center"> </template>
<div class="flex-1"> <template #start>
<div class="flex flex-col sm:flex-row"> <EntityField
<span :title="category.spec.displayName"
class="mr-0 truncate text-sm font-medium text-gray-900 sm:mr-2" :description="category.status.permalink"
> />
{{ category.spec.displayName }} </template>
</span> <template #end>
<VSpace class="mt-1 sm:mt-0"></VSpace> <EntityField v-if="category.metadata.deletionTimestamp">
</div> <template #description>
<div class="mt-1 flex">
<span class="text-xs text-gray-500">
{{ category.status.permalink }}
</span>
</div>
</div>
<div class="flex">
<div <div
class="inline-flex flex-col items-end gap-4 sm:flex-row sm:items-center sm:gap-6" v-tooltip="`删除中`"
class="inline-flex h-1.5 w-1.5 rounded-full bg-red-600"
> >
<FloatingTooltip
v-if="category.metadata.deletionTimestamp"
class="mr-4 hidden items-center sm:flex"
>
<div class="inline-flex h-1.5 w-1.5 rounded-full bg-red-600">
<span <span
class="inline-block h-1.5 w-1.5 animate-ping rounded-full bg-red-600" class="inline-block h-1.5 w-1.5 animate-ping rounded-full bg-red-600"
></span> ></span>
</div> </div>
<template #popper> 删除中</template> </template>
</FloatingTooltip> </EntityField>
<div <EntityField
class="cursor-pointer text-sm text-gray-500 hover:text-gray-900" :description="`${category.status?.posts?.length || 0} 篇文章`"
>
{{ category.status?.posts?.length || 0 }} 篇文章
</div>
<time class="text-sm text-gray-500">
{{ formatDatetime(category.metadata.creationTimestamp) }}
</time>
<span class="self-center">
<FloatingDropdown>
<IconSettings
class="cursor-pointer transition-all hover:text-blue-600"
/> />
<template #popper> <EntityField
<div class="w-48 p-2"> :description="formatDatetime(category.metadata.creationTimestamp)"
<VSpace class="w-full" direction="column"> />
</template>
<template #menuItems>
<VButton <VButton
v-close-popper v-close-popper
block block
@ -120,15 +102,8 @@ function onDelete(category: CategoryTree) {
> >
删除 删除
</VButton> </VButton>
</VSpace>
</div>
</template> </template>
</FloatingDropdown> </Entity>
</span>
</div>
</div>
</div>
</div>
<CategoryListItem <CategoryListItem
:categories="category.spec.children" :categories="category.spec.children"
class="pl-10 transition-all duration-300" class="pl-10 transition-all duration-300"

View File

@ -8,7 +8,6 @@ import {
IconBookRead, IconBookRead,
IconGrid, IconGrid,
IconList, IconList,
IconSettings,
VButton, VButton,
VCard, VCard,
VEmpty, VEmpty,
@ -17,6 +16,8 @@ import {
} from "@halo-dev/components"; } from "@halo-dev/components";
import TagEditingModal from "./components/TagEditingModal.vue"; import TagEditingModal from "./components/TagEditingModal.vue";
import PostTag from "./components/PostTag.vue"; import PostTag from "./components/PostTag.vue";
import Entity from "@/components/entity/Entity.vue";
import EntityField from "@/components/entity/EntityField.vue";
// types // types
import type { Tag } from "@halo-dev/api-client"; import type { Tag } from "@halo-dev/api-client";
@ -173,60 +174,37 @@ onMounted(async () => {
role="list" role="list"
> >
<li v-for="(tag, index) in tags" :key="index"> <li v-for="(tag, index) in tags" :key="index">
<div <Entity
:class="{ :is-selected="selectedTag?.metadata.name === tag.metadata.name"
'bg-gray-100': selectedTag?.metadata.name === tag.metadata.name,
}"
class="relative block cursor-pointer px-4 py-3 transition-all hover:bg-gray-50"
> >
<div <template #start>
v-show="selectedTag?.metadata.name === tag.metadata.name" <EntityField :description="tag.status?.permalink">
class="absolute inset-y-0 left-0 w-0.5 bg-primary" <template #title>
></div>
<div class="relative flex flex-row items-center">
<div class="flex-1">
<div class="flex flex-col sm:flex-row">
<PostTag :tag="tag" /> <PostTag :tag="tag" />
</div> </template>
<div class="mt-1 flex"> </EntityField>
<span class="text-xs text-gray-500"> </template>
{{ tag.status?.permalink }} <template #end>
</span> <EntityField v-if="tag.metadata.deletionTimestamp">
</div> <template #description>
</div>
<div class="flex">
<div
class="inline-flex flex-col items-end gap-4 sm:flex-row sm:items-center sm:gap-6"
>
<FloatingTooltip
v-if="tag.metadata.deletionTimestamp"
class="mr-4 hidden items-center sm:flex"
>
<div <div
v-tooltip="`删除中`"
class="inline-flex h-1.5 w-1.5 rounded-full bg-red-600" class="inline-flex h-1.5 w-1.5 rounded-full bg-red-600"
> >
<span <span
class="inline-block h-1.5 w-1.5 animate-ping rounded-full bg-red-600" class="inline-block h-1.5 w-1.5 animate-ping rounded-full bg-red-600"
></span> ></span>
</div> </div>
<template #popper> 删除中</template> </template>
</FloatingTooltip> </EntityField>
<div <EntityField
class="cursor-pointer text-sm text-gray-500 hover:text-gray-900" :description="`${tag.status?.posts?.length || 0} 篇文章`"
>
{{ tag.status?.posts?.length || 0 }} 篇文章
</div>
<time class="text-sm text-gray-500">
{{ formatDatetime(tag.metadata.creationTimestamp) }}
</time>
<span class="self-center">
<FloatingDropdown>
<IconSettings
class="cursor-pointer transition-all hover:text-blue-600"
/> />
<template #popper> <EntityField
<div class="w-48 p-2"> :description="formatDatetime(tag.metadata.creationTimestamp)"
<VSpace class="w-full" direction="column"> />
</template>
<template #menuItems>
<VButton <VButton
v-close-popper v-close-popper
block block
@ -243,15 +221,8 @@ onMounted(async () => {
> >
删除 删除
</VButton> </VButton>
</VSpace>
</div>
</template> </template>
</FloatingDropdown> </Entity>
</span>
</div>
</div>
</div>
</div>
</li> </li>
</ul> </ul>

View File

@ -1,14 +1,10 @@
<script lang="ts" setup> <script lang="ts" setup>
import { import { IconList, VButton, VTag } from "@halo-dev/components";
IconList,
IconSettings,
VButton,
VSpace,
VTag,
} from "@halo-dev/components";
import Draggable from "vuedraggable"; import Draggable from "vuedraggable";
import { ref } from "vue"; import { ref } from "vue";
import type { MenuTreeItem } from "@/modules/interface/menus/utils"; import type { MenuTreeItem } from "@/modules/interface/menus/utils";
import Entity from "@/components/entity/Entity.vue";
import EntityField from "@/components/entity/EntityField.vue";
withDefaults( withDefaults(
defineProps<{ defineProps<{
@ -70,56 +66,41 @@ function getMenuItemRefDisplayName(menuItem: MenuTreeItem) {
> >
<template #item="{ element: menuItem }"> <template #item="{ element: menuItem }">
<li> <li>
<div <Entity>
class="group relative block cursor-pointer px-4 py-3 transition-all hover:bg-gray-50" <template #prepend>
>
<div <div
class="drag-element absolute inset-y-0 left-0 hidden w-3.5 cursor-move items-center bg-gray-100 transition-all hover:bg-gray-200 group-hover:flex" class="drag-element absolute inset-y-0 left-0 hidden w-3.5 cursor-move items-center bg-gray-100 transition-all hover:bg-gray-200 group-hover:flex"
> >
<IconList class="h-3.5 w-3.5" /> <IconList class="h-3.5 w-3.5" />
</div> </div>
<div class="relative flex flex-row items-center"> </template>
<div class="flex-1"> <template #start>
<div class="flex flex-row items-center gap-2"> <EntityField
<span class="truncate text-sm font-medium text-gray-900"> :title="menuItem.status.displayName"
{{ menuItem.status.displayName }} :description="menuItem.status.href"
</span> >
<template #extra>
<VTag v-if="getMenuItemRefDisplayName(menuItem)"> <VTag v-if="getMenuItemRefDisplayName(menuItem)">
{{ getMenuItemRefDisplayName(menuItem) }} {{ getMenuItemRefDisplayName(menuItem) }}
</VTag> </VTag>
</div> </template>
</EntityField>
<div class="mt-1 flex"> </template>
<VSpace align="start" direction="column" spacing="xs"> <template #end>
<a <EntityField v-if="menuItem.metadata.deletionTimestamp">
:href="menuItem.status.href" <template #description>
class="text-xs text-gray-500 hover:text-gray-900" <div
target="_blank" v-tooltip="`删除中`"
class="inline-flex h-1.5 w-1.5 rounded-full bg-red-600"
> >
{{ menuItem.status.href }}
</a>
</VSpace>
</div>
</div>
<FloatingTooltip
v-if="menuItem.metadata.deletionTimestamp"
class="mr-4 hidden items-center sm:flex"
>
<div class="inline-flex h-1.5 w-1.5 rounded-full bg-red-600">
<span <span
class="inline-block h-1.5 w-1.5 animate-ping rounded-full bg-red-600" class="inline-block h-1.5 w-1.5 animate-ping rounded-full bg-red-600"
></span> ></span>
</div> </div>
<template #popper> 删除中</template> </template>
</FloatingTooltip> </EntityField>
<div class="self-center"> </template>
<FloatingDropdown> <template #menuItems>
<IconSettings
class="cursor-pointer transition-all hover:text-blue-600"
/>
<template #popper>
<div class="w-48 p-2">
<VSpace class="w-full" direction="column">
<VButton <VButton
v-close-popper v-close-popper
block block
@ -136,14 +117,8 @@ function getMenuItemRefDisplayName(menuItem: MenuTreeItem) {
> >
删除 删除
</VButton> </VButton>
</VSpace>
</div>
</template> </template>
</FloatingDropdown> </Entity>
</div>
</div>
</div>
<MenuItemListItem <MenuItemListItem
:menu-tree-items="menuItem.spec.children" :menu-tree-items="menuItem.spec.children"
class="pl-10 transition-all duration-300" class="pl-10 transition-all duration-300"

View File

@ -1,6 +1,5 @@
<script lang="ts" setup> <script lang="ts" setup>
import { import {
IconSettings,
useDialog, useDialog,
VButton, VButton,
VCard, VCard,
@ -12,6 +11,8 @@ import { defineExpose, onMounted, ref } from "vue";
import type { Menu } from "@halo-dev/api-client"; import type { Menu } from "@halo-dev/api-client";
import { apiClient } from "@halo-dev/admin-shared"; import { apiClient } from "@halo-dev/admin-shared";
import { useRouteQuery } from "@vueuse/router"; import { useRouteQuery } from "@vueuse/router";
import Entity from "@/components/entity/Entity.vue";
import EntityField from "@/components/entity/EntityField.vue";
const props = withDefaults( const props = withDefaults(
defineProps<{ defineProps<{
@ -137,48 +138,36 @@ defineExpose({
</VSpace> </VSpace>
</template> </template>
</VEmpty> </VEmpty>
<div class="divide-y divide-gray-100 bg-white"> <ul class="box-border h-full w-full divide-y divide-gray-100" role="list">
<div <li
v-for="(menu, index) in menus" v-for="(menu, index) in menus"
:key="index" :key="index"
:class="{
'bg-gray-50': selectedMenu?.metadata.name === menu.metadata.name,
}"
class="relative flex items-center p-4"
@click="handleSelect(menu)" @click="handleSelect(menu)"
> >
<div <Entity
v-if="selectedMenu?.metadata.name === menu.metadata.name" :is-selected="selectedMenu?.metadata.name === menu.metadata.name"
class="absolute inset-y-0 left-0 w-0.5 bg-primary" >
></div> <template #start>
<span class="flex flex-1 cursor-pointer flex-col gap-y-1"> <EntityField
<span class="block text-sm font-medium"> :title="menu.spec?.displayName"
{{ menu.spec?.displayName }} :description="`${menu.spec.menuItems?.length || 0} 个菜单项`"
</span> ></EntityField>
<span class="block text-xs text-gray-400"> </template>
{{ menu.spec.menuItems?.length || 0 }} <template #end>
个菜单项 <EntityField v-if="menu.metadata.deletionTimestamp">
</span> <template #description>
</span> <div
<FloatingTooltip v-tooltip="`删除中`"
v-if="menu.metadata.deletionTimestamp" class="inline-flex h-1.5 w-1.5 rounded-full bg-red-600"
class="mr-4 hidden items-center sm:flex"
> >
<div class="inline-flex h-1.5 w-1.5 rounded-full bg-red-600">
<span <span
class="inline-block h-1.5 w-1.5 animate-ping rounded-full bg-red-600" class="inline-block h-1.5 w-1.5 animate-ping rounded-full bg-red-600"
></span> ></span>
</div> </div>
<template #popper> 删除中</template> </template>
</FloatingTooltip> </EntityField>
<div class="self-center"> </template>
<FloatingDropdown> <template #menuItems>
<IconSettings
class="cursor-pointer transition-all hover:text-blue-600"
/>
<template #popper>
<div class="w-48 p-2">
<VSpace class="w-full" direction="column">
<VButton <VButton
v-close-popper v-close-popper
block block
@ -195,13 +184,10 @@ defineExpose({
> >
删除 删除
</VButton> </VButton>
</VSpace>
</div>
</template> </template>
</FloatingDropdown> </Entity>
</div> </li>
</div> </ul>
</div>
<template #footer> <template #footer>
<VButton block type="secondary" @click="handleOpenEditingModal(null)"> <VButton block type="secondary" @click="handleOpenEditingModal(null)">
新增 新增

View File

@ -1,11 +1,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import { import { VButton, VSpace, VSwitch, VTag } from "@halo-dev/components";
IconSettings, import Entity from "@/components/entity/Entity.vue";
VButton, import EntityField from "@/components/entity/EntityField.vue";
VSpace,
VSwitch,
VTag,
} from "@halo-dev/components";
import { toRefs } from "vue"; import { toRefs } from "vue";
import { usePluginLifeCycle } from "../composables/use-plugin"; import { usePluginLifeCycle } from "../composables/use-plugin";
import type { Plugin } from "@halo-dev/api-client"; import type { Plugin } from "@halo-dev/api-client";
@ -25,17 +21,10 @@ const { plugin } = toRefs(props);
const { isStarted, changeStatus, uninstall } = usePluginLifeCycle(plugin); const { isStarted, changeStatus, uninstall } = usePluginLifeCycle(plugin);
</script> </script>
<template> <template>
<div <Entity>
class="relative block cursor-pointer px-4 py-3 transition-all hover:bg-gray-50" <template #start>
> <EntityField>
<div class="relative flex flex-row items-center"> <template #description>
<div v-if="plugin?.spec.logo" class="mr-4">
<RouterLink
:to="{
name: 'PluginDetail',
params: { name: plugin?.metadata.name },
}"
>
<div class="h-12 w-12 rounded border bg-white p-1 hover:shadow-sm"> <div class="h-12 w-12 rounded border bg-white p-1 hover:shadow-sm">
<img <img
:alt="plugin?.metadata.name" :alt="plugin?.metadata.name"
@ -43,55 +32,40 @@ const { isStarted, changeStatus, uninstall } = usePluginLifeCycle(plugin);
class="h-full w-full" class="h-full w-full"
/> />
</div> </div>
</RouterLink> </template>
</div> </EntityField>
<div class="flex-1"> <EntityField
<div class="flex flex-row items-center"> :title="plugin?.spec.displayName"
<RouterLink :description="plugin?.spec.description"
:to="{ :route="{
name: 'PluginDetail', name: 'PluginDetail',
params: { name: plugin?.metadata.name }, params: { name: plugin?.metadata.name },
}" }"
> >
<span class="mr-2 truncate text-sm font-medium text-gray-900"> <template #extra>
{{ plugin?.spec.displayName }}
</span>
</RouterLink>
<VSpace> <VSpace>
<VTag> <VTag>
{{ isStarted ? "已启用" : "未启用" }} {{ isStarted ? "已启用" : "未启用" }}
</VTag> </VTag>
</VSpace> </VSpace>
</div> </template>
<div class="mt-2 flex"> </EntityField>
<VSpace align="start" direction="column" spacing="xs"> </template>
<span class="text-xs text-gray-500"> <template #end>
{{ plugin?.spec.description }} <EntityField v-if="plugin?.status?.phase === 'FAILED'">
</span> <template #description>
<span class="text-xs text-gray-500 sm:hidden">
@{{ plugin?.spec.author }} {{ plugin?.spec.version }}
</span>
</VSpace>
</div>
</div>
<div class="flex">
<div <div
class="inline-flex flex-col items-end gap-4 sm:flex-row sm:items-center sm:gap-6" v-tooltip="`${plugin?.status?.reason}:${plugin?.status?.message}`"
class="inline-flex h-1.5 w-1.5 rounded-full bg-red-600"
> >
<FloatingTooltip
v-if="plugin?.status?.phase === 'FAILED'"
class="hidden items-center sm:flex"
>
<div class="inline-flex h-1.5 w-1.5 rounded-full bg-red-600">
<span <span
class="inline-block h-1.5 w-1.5 animate-ping rounded-full bg-red-600" class="inline-block h-1.5 w-1.5 animate-ping rounded-full bg-red-600"
></span> ></span>
</div> </div>
<template #popper>
{{ plugin?.status?.reason }}:
{{ plugin?.status?.message }}
</template> </template>
</FloatingTooltip> </EntityField>
<EntityField>
<template #description>
<a <a
:href="plugin?.spec.homepage" :href="plugin?.spec.homepage"
class="hidden text-sm text-gray-500 hover:text-gray-900 sm:block" class="hidden text-sm text-gray-500 hover:text-gray-900 sm:block"
@ -99,12 +73,15 @@ const { isStarted, changeStatus, uninstall } = usePluginLifeCycle(plugin);
> >
@{{ plugin?.spec.author }} @{{ plugin?.spec.author }}
</a> </a>
<span class="hidden text-sm text-gray-500 sm:block"> </template>
{{ plugin?.spec.version }} </EntityField>
</span> <EntityField :description="plugin?.spec.version" />
<time class="hidden text-sm text-gray-500 sm:block"> <EntityField
{{ formatDatetime(plugin?.metadata.creationTimestamp) }} v-if="plugin?.metadata.creationTimestamp"
</time> :description="formatDatetime(plugin?.metadata.creationTimestamp)"
/>
<EntityField>
<template #description>
<div <div
v-permission="['system:plugins:manage']" v-permission="['system:plugins:manage']"
class="flex items-center" class="flex items-center"
@ -114,27 +91,13 @@ const { isStarted, changeStatus, uninstall } = usePluginLifeCycle(plugin);
@click="changeStatus" @click="changeStatus"
/> />
</div> </div>
<span v-permission="['system:plugins:manage']" class="cursor-pointer"> </template>
<FloatingDropdown> </EntityField>
<IconSettings /> </template>
<template #popper> <template #menuItems>
<div class="w-48 p-2"> <VButton v-close-popper block type="danger" @click="uninstall">
<VSpace class="w-full" direction="column">
<VButton
v-close-popper
block
type="danger"
@click="uninstall"
>
卸载 卸载
</VButton> </VButton>
</VSpace>
</div>
</template> </template>
</FloatingDropdown> </Entity>
</span>
</div>
</div>
</div>
</div>
</template> </template>

View File

@ -7,7 +7,6 @@ import type { Role } from "@halo-dev/api-client";
import { import {
IconAddCircle, IconAddCircle,
IconArrowDown, IconArrowDown,
IconSettings,
IconShieldUser, IconShieldUser,
useDialog, useDialog,
VButton, VButton,
@ -18,6 +17,8 @@ import {
VTag, VTag,
} from "@halo-dev/components"; } from "@halo-dev/components";
import RoleEditingModal from "./components/RoleEditingModal.vue"; import RoleEditingModal from "./components/RoleEditingModal.vue";
import Entity from "@/components/entity/Entity.vue";
import EntityField from "@/components/entity/EntityField.vue";
// constants // constants
import { rbacAnnotations } from "@/constants/annotations"; import { rbacAnnotations } from "@/constants/annotations";
@ -199,82 +200,54 @@ const handleDelete = async (role: Role) => {
</template> </template>
<ul class="box-border h-full w-full divide-y divide-gray-100" role="list"> <ul class="box-border h-full w-full divide-y divide-gray-100" role="list">
<li v-for="(role, index) in roles" :key="index"> <li v-for="(role, index) in roles" :key="index">
<div <Entity>
class="relative block cursor-pointer px-4 py-3 transition-all hover:bg-gray-50" <template #start>
> <EntityField
<div class="relative flex flex-row items-center"> :title="
<div class="flex-1"> role.metadata.annotations?.[rbacAnnotations.DISPLAY_NAME] ||
<div class="flex flex-row items-center"> role.metadata.name
<RouterLink "
:to="{ :description="`包含
${
JSON.parse(
role.metadata.annotations?.[
rbacAnnotations.DEPENDENCIES
] || '[]'
).length
}
个权限`"
:route="{
name: 'RoleDetail', name: 'RoleDetail',
params: { params: {
name: role.metadata.name, name: role.metadata.name,
}, },
}" }"
> ></EntityField>
<span </template>
class="mr-2 truncate text-sm font-medium text-gray-900" <template #end>
> <EntityField v-if="role.metadata.deletionTimestamp">
{{ <template #description>
role.metadata.annotations?.[
rbacAnnotations.DISPLAY_NAME
] || role.metadata.name
}}
</span>
</RouterLink>
</div>
<div class="mt-2 flex">
<span class="text-xs text-gray-500">
包含
{{
JSON.parse(
role.metadata.annotations?.[
rbacAnnotations.DEPENDENCIES
] || "[]"
).length
}}
个权限
</span>
</div>
</div>
<div class="flex">
<div
class="inline-flex flex-col items-end gap-4 sm:flex-row sm:items-center sm:gap-6"
>
<FloatingTooltip
v-if="role.metadata.deletionTimestamp"
class="hidden items-center sm:flex"
>
<div <div
v-tooltip="`删除中`"
class="inline-flex h-1.5 w-1.5 rounded-full bg-red-600" class="inline-flex h-1.5 w-1.5 rounded-full bg-red-600"
> >
<span <span
class="inline-block h-1.5 w-1.5 animate-ping rounded-full bg-red-600" class="inline-block h-1.5 w-1.5 animate-ping rounded-full bg-red-600"
></span> ></span>
</div> </div>
<template #popper> 删除中</template> </template>
</FloatingTooltip> </EntityField>
<EntityField description="0 个用户" />
<a <EntityField>
class="hidden text-sm text-gray-500 hover:text-gray-900 sm:block" <template #description>
target="_blank"
>
0 个用户
</a>
<VTag> 系统保留</VTag> <VTag> 系统保留</VTag>
<time class="text-sm text-gray-500"> </template>
{{ formatDatetime(role.metadata.creationTimestamp) }} </EntityField>
</time> <EntityField
<span :description="formatDatetime(role.metadata.creationTimestamp)"
v-permission="['system:roles:manage']" />
class="cursor-pointer" </template>
> <template #menuItems>
<FloatingDropdown>
<IconSettings />
<template #popper>
<div class="w-48 p-2">
<VSpace class="w-full" direction="column">
<VButton <VButton
v-close-popper v-close-popper
block block
@ -291,22 +264,11 @@ const handleDelete = async (role: Role) => {
> >
删除 删除
</VButton> </VButton>
<VButton <VButton v-close-popper block @click="handleCloneRole(role)">
v-close-popper
block
@click="handleCloneRole(role)"
>
基于此角色创建 基于此角色创建
</VButton> </VButton>
</VSpace>
</div>
</template> </template>
</FloatingDropdown> </Entity>
</span>
</div>
</div>
</div>
</div>
</li> </li>
</ul> </ul>

View File

@ -2,7 +2,6 @@
import { import {
IconAddCircle, IconAddCircle,
IconArrowDown, IconArrowDown,
IconSettings,
IconUserFollow, IconUserFollow,
IconUserSettings, IconUserSettings,
VButton, VButton,
@ -21,6 +20,8 @@ import type { User, UserList } from "@halo-dev/api-client";
import { rbacAnnotations } from "@/constants/annotations"; import { rbacAnnotations } from "@/constants/annotations";
import { formatDatetime } from "@/utils/date"; import { formatDatetime } from "@/utils/date";
import { useRouteQuery } from "@vueuse/router"; import { useRouteQuery } from "@vueuse/router";
import Entity from "@/components/entity/Entity.vue";
import EntityField from "@/components/entity/EntityField.vue";
const checkAll = ref(false); const checkAll = ref(false);
const editingModal = ref<boolean>(false); const editingModal = ref<boolean>(false);
@ -275,80 +276,53 @@ onMounted(() => {
</template> </template>
<ul class="box-border h-full w-full divide-y divide-gray-100" role="list"> <ul class="box-border h-full w-full divide-y divide-gray-100" role="list">
<li v-for="(user, index) in users.items" :key="index"> <li v-for="(user, index) in users.items" :key="index">
<div <Entity :is-selected="checkAll">
:class="{ <template #checkbox>
'bg-gray-100': checkAll,
}"
class="relative block cursor-pointer px-4 py-3 transition-all hover:bg-gray-50"
>
<div
v-show="checkAll"
class="absolute inset-y-0 left-0 w-0.5 bg-primary"
></div>
<div class="relative flex flex-row items-center">
<div class="mr-4 hidden items-center sm:flex">
<input <input
v-model="checkAll" v-model="checkAll"
v-permission="['system:users:manage']" v-permission="['system:users:manage']"
class="h-4 w-4 rounded border-gray-300 text-indigo-600" class="h-4 w-4 rounded border-gray-300 text-indigo-600"
type="checkbox" type="checkbox"
/> />
</div> </template>
<div v-if="user.spec.avatar" class="mr-4 flex items-center"> <template #start>
<EntityField>
<template #description>
<VAvatar <VAvatar
:alt="user.spec.displayName" :alt="user.spec.displayName"
:src="user.spec.avatar" :src="user.spec.avatar"
size="md" size="md"
></VAvatar> ></VAvatar>
</div> </template>
<div class="flex-1"> </EntityField>
<div class="flex flex-row items-center"> <EntityField
<span :title="user.spec.displayName"
class="mr-2 truncate text-sm font-medium text-gray-900" :description="user.metadata.name"
@click=" :route="{
$router.push({
name: 'UserDetail', name: 'UserDetail',
params: { name: user.metadata.name }, params: { name: user.metadata.name },
}) }"
" />
> </template>
{{ user.spec.displayName }} <template #end>
</span> <EntityField>
<VTag class="sm:hidden">{{ user.metadata.name }}</VTag> <template #description>
</div>
<div class="mt-1 flex">
<VSpace align="start" direction="column" spacing="xs">
<span class="text-xs text-gray-500">
{{ user.metadata.name }}
</span>
</VSpace>
</div>
</div>
<div class="flex">
<div
class="inline-flex flex-col items-end gap-4 sm:flex-row sm:items-center sm:gap-6"
>
<div <div
v-for="(role, roleIndex) in getRoles(user)" v-for="(role, roleIndex) in getRoles(user)"
:key="roleIndex" :key="roleIndex"
class="hidden items-center sm:flex" class="flex items-center"
> >
<VTag> <VTag>
{{ role }} {{ role }}
</VTag> </VTag>
</div> </div>
<time class="text-sm text-gray-500"> </template>
{{ formatDatetime(user.metadata.creationTimestamp) }} </EntityField>
</time> <EntityField
<span :description="formatDatetime(user.metadata.creationTimestamp)"
v-permission="['system:users:manage']" />
class="cursor-pointer" </template>
> <template #menuItems>
<FloatingDropdown>
<IconSettings />
<template #popper>
<div class="w-48 p-2">
<VSpace class="w-full" direction="column">
<VButton <VButton
v-close-popper v-close-popper
block block
@ -364,15 +338,8 @@ onMounted(() => {
> >
修改密码 修改密码
</VButton> </VButton>
</VSpace>
</div>
</template> </template>
</FloatingDropdown> </Entity>
</span>
</div>
</div>
</div>
</div>
</li> </li>
</ul> </ul>