mirror of https://github.com/halo-dev/halo
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
parent
0a4d31fa33
commit
bcfe7a52ee
|
@ -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>
|
|
@ -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>
|
|
@ -7,7 +7,6 @@ import {
|
|||
IconDatabase2Line,
|
||||
IconGrid,
|
||||
IconList,
|
||||
IconSettings,
|
||||
IconUpload,
|
||||
VButton,
|
||||
VCard,
|
||||
|
@ -36,6 +35,8 @@ import cloneDeep from "lodash.clonedeep";
|
|||
import { isImage } from "@/utils/image";
|
||||
import { useRouteQuery } from "@vueuse/router";
|
||||
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 uploadVisible = ref(false);
|
||||
|
@ -555,28 +556,20 @@ onMounted(() => {
|
|||
role="list"
|
||||
>
|
||||
<li v-for="(attachment, index) in attachments.items" :key="index">
|
||||
<div
|
||||
:class="{
|
||||
'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">
|
||||
<Entity :is-selected="isChecked(attachment)">
|
||||
<template #checkbox>
|
||||
<input
|
||||
:checked="selectedAttachments.has(attachment)"
|
||||
class="h-4 w-4 rounded border-gray-300 text-indigo-600"
|
||||
type="checkbox"
|
||||
@click="handleSelect(attachment)"
|
||||
/>
|
||||
</div>
|
||||
<div class="mr-4">
|
||||
</template>
|
||||
<template #start>
|
||||
<EntityField>
|
||||
<template #description>
|
||||
<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
|
||||
:display-ext="false"
|
||||
|
@ -585,17 +578,13 @@ onMounted(() => {
|
|||
:height="8"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<div class="flex flex-col sm:flex-row">
|
||||
<span
|
||||
class="mr-0 truncate text-sm font-medium text-gray-900 sm:mr-2"
|
||||
</template>
|
||||
</EntityField>
|
||||
<EntityField
|
||||
:title="attachment.spec.displayName"
|
||||
@click="handleClickItem(attachment)"
|
||||
>
|
||||
{{ attachment.spec.displayName }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="mt-1 flex">
|
||||
<template #description>
|
||||
<VSpace>
|
||||
<span class="text-xs text-gray-500">
|
||||
{{ attachment.spec.mediaType }}
|
||||
|
@ -604,54 +593,50 @@ onMounted(() => {
|
|||
{{ prettyBytes(attachment.spec.size || 0) }}
|
||||
</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"
|
||||
>
|
||||
<span class="text-sm text-gray-500">
|
||||
{{ getPolicyName(attachment.spec.policyRef?.name) }}
|
||||
</span>
|
||||
</template>
|
||||
</EntityField>
|
||||
</template>
|
||||
<template #end>
|
||||
<EntityField
|
||||
:description="
|
||||
getPolicyName(attachment.spec.policyRef?.name)
|
||||
"
|
||||
/>
|
||||
<EntityField>
|
||||
<template #description>
|
||||
<RouterLink
|
||||
:to="{
|
||||
name: 'UserDetail',
|
||||
params: { name: attachment.spec.uploadedBy?.name },
|
||||
}"
|
||||
class="text-xs text-gray-500"
|
||||
>
|
||||
<span class="text-sm text-gray-500">
|
||||
{{ attachment.spec.uploadedBy?.name }}
|
||||
</span>
|
||||
</RouterLink>
|
||||
<FloatingTooltip
|
||||
v-if="attachment.metadata.deletionTimestamp"
|
||||
class="hidden items-center sm:flex"
|
||||
>
|
||||
</template>
|
||||
</EntityField>
|
||||
<EntityField v-if="attachment.metadata.deletionTimestamp">
|
||||
<template #description>
|
||||
<div
|
||||
v-tooltip="`删除中`"
|
||||
class="inline-flex h-1.5 w-1.5 rounded-full bg-red-600"
|
||||
>
|
||||
<span
|
||||
class="inline-block h-1.5 w-1.5 animate-ping rounded-full bg-red-600"
|
||||
></span>
|
||||
</div>
|
||||
<template #popper> 删除中</template>
|
||||
</FloatingTooltip>
|
||||
<time class="text-sm text-gray-500">
|
||||
{{
|
||||
formatDatetime(
|
||||
attachment.metadata.creationTimestamp
|
||||
)
|
||||
}}
|
||||
</time>
|
||||
<span class="cursor-pointer">
|
||||
<IconSettings
|
||||
@click.stop="handleClickItem(attachment)"
|
||||
</template>
|
||||
</EntityField>
|
||||
<EntityField
|
||||
:description="
|
||||
formatDatetime(attachment.metadata.creationTimestamp)
|
||||
"
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template #menuItems>
|
||||
<VButton v-close-popper block type="danger"> 删除 </VButton>
|
||||
</template>
|
||||
</Entity>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
|
|
@ -3,6 +3,8 @@ import { ref } from "vue";
|
|||
import { VEmpty, VSpace, VButton, IconAddCircle } from "@halo-dev/components";
|
||||
import type { PagesPublicState } from "@halo-dev/admin-shared";
|
||||
import { useExtensionPointsState } from "@/composables/usePlugins";
|
||||
import Entity from "@/components/entity/Entity.vue";
|
||||
import EntityField from "@/components/entity/EntityField.vue";
|
||||
|
||||
const pagesPublicState = ref<PagesPublicState>({
|
||||
functionalPages: [],
|
||||
|
@ -38,27 +40,15 @@ useExtensionPointsState("PAGES", pagesPublicState);
|
|||
:key="index"
|
||||
v-permission="page.permissions"
|
||||
>
|
||||
<div
|
||||
class="relative block cursor-pointer px-4 py-3 transition-all hover:bg-gray-50"
|
||||
>
|
||||
<div class="relative flex flex-row items-center">
|
||||
<div class="flex-1">
|
||||
<div class="flex">
|
||||
<RouterLink
|
||||
:to="page.path"
|
||||
class="truncate text-sm font-medium text-gray-900"
|
||||
>
|
||||
{{ page.name }}
|
||||
</RouterLink>
|
||||
</div>
|
||||
<div class="mt-1 flex">
|
||||
<span class="text-xs text-gray-500">
|
||||
{{ page.url }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Entity>
|
||||
<template #start>
|
||||
<EntityField
|
||||
:title="page.name"
|
||||
:route="page.path"
|
||||
:description="page.url"
|
||||
></EntityField>
|
||||
</template>
|
||||
</Entity>
|
||||
</li>
|
||||
</ul>
|
||||
</template>
|
||||
|
|
|
@ -3,7 +3,6 @@ import {
|
|||
IconArrowDown,
|
||||
IconArrowLeft,
|
||||
IconArrowRight,
|
||||
IconSettings,
|
||||
IconEye,
|
||||
IconEyeOff,
|
||||
IconTeam,
|
||||
|
@ -30,6 +29,8 @@ import { apiClient } from "@halo-dev/admin-shared";
|
|||
import { formatDatetime } from "@/utils/date";
|
||||
import { RouterLink } from "vue-router";
|
||||
import cloneDeep from "lodash.clonedeep";
|
||||
import Entity from "../../../components/entity/Entity.vue";
|
||||
import EntityField from "../../../components/entity/EntityField.vue";
|
||||
|
||||
enum SinglePagePhase {
|
||||
DRAFT = "未发布",
|
||||
|
@ -332,41 +333,27 @@ const handleSelectUser = (user?: User) => {
|
|||
role="list"
|
||||
>
|
||||
<li v-for="(singlePage, index) in singlePages.items" :key="index">
|
||||
<div
|
||||
:class="{
|
||||
'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">
|
||||
<Entity :is-selected="checkAll">
|
||||
<template #checkbox>
|
||||
<input
|
||||
v-model="checkAll"
|
||||
class="h-4 w-4 rounded border-gray-300 text-indigo-600"
|
||||
type="checkbox"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<div class="flex flex-row items-center">
|
||||
<RouterLink
|
||||
:to="{
|
||||
</template>
|
||||
<template #start>
|
||||
<EntityField
|
||||
:title="singlePage.page.spec.title"
|
||||
:description="singlePage.page.status?.permalink"
|
||||
:route="{
|
||||
name: 'SinglePageEditor',
|
||||
query: { name: singlePage.page.metadata.name },
|
||||
}"
|
||||
>
|
||||
<span class="truncate text-sm font-medium text-gray-900">
|
||||
{{ singlePage.page.spec.title }}
|
||||
</span>
|
||||
</RouterLink>
|
||||
<FloatingTooltip
|
||||
v-if="singlePage.page.status?.inProgress"
|
||||
class="hidden items-center sm:flex"
|
||||
>
|
||||
<template #extra>
|
||||
<RouterLink
|
||||
v-if="singlePage.page.status?.inProgress"
|
||||
v-tooltip="`当前有内容已保存,但还未发布。`"
|
||||
:to="{
|
||||
name: 'SinglePageEditor',
|
||||
query: { name: singlePage.page.metadata.name },
|
||||
|
@ -381,19 +368,12 @@ const handleSelectUser = (user?: User) => {
|
|||
></span>
|
||||
</div>
|
||||
</RouterLink>
|
||||
<template #popper> 当前有内容已保存,但还未发布。 </template>
|
||||
</FloatingTooltip>
|
||||
</div>
|
||||
<div class="mt-1 flex">
|
||||
<span class="text-xs text-gray-500">
|
||||
{{ singlePage.page.status?.permalink }}
|
||||
</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"
|
||||
>
|
||||
</template>
|
||||
</EntityField>
|
||||
</template>
|
||||
<template #end>
|
||||
<EntityField>
|
||||
<template #description>
|
||||
<RouterLink
|
||||
v-for="(
|
||||
contributor, contributorIndex
|
||||
|
@ -413,10 +393,11 @@ const handleSelectUser = (user?: User) => {
|
|||
circle
|
||||
></VAvatar>
|
||||
</RouterLink>
|
||||
<span class="text-sm text-gray-500">
|
||||
{{ finalStatus(singlePage.page) }}
|
||||
</span>
|
||||
<span>
|
||||
</template>
|
||||
</EntityField>
|
||||
<EntityField :description="finalStatus(singlePage.page)" />
|
||||
<EntityField>
|
||||
<template #description>
|
||||
<IconEye
|
||||
v-if="singlePage.page.spec.visible === 'PUBLIC'"
|
||||
v-tooltip="`公开访问`"
|
||||
|
@ -432,20 +413,15 @@ const handleSelectUser = (user?: User) => {
|
|||
v-tooltip="`内部成员可访问`"
|
||||
class="cursor-pointer text-sm transition-all hover:text-blue-600"
|
||||
/>
|
||||
</span>
|
||||
<time class="text-sm text-gray-500">
|
||||
{{
|
||||
</template>
|
||||
</EntityField>
|
||||
<EntityField
|
||||
:description="
|
||||
formatDatetime(singlePage.page.metadata.creationTimestamp)
|
||||
}}
|
||||
</time>
|
||||
<span>
|
||||
<FloatingDropdown>
|
||||
<IconSettings
|
||||
class="cursor-pointer transition-all hover:text-blue-600"
|
||||
"
|
||||
/>
|
||||
<template #popper>
|
||||
<div class="w-48 p-2">
|
||||
<VSpace class="w-full" direction="column">
|
||||
</template>
|
||||
<template #menuItems>
|
||||
<VButton
|
||||
v-close-popper
|
||||
block
|
||||
|
@ -462,15 +438,8 @@ const handleSelectUser = (user?: User) => {
|
|||
>
|
||||
删除
|
||||
</VButton>
|
||||
</VSpace>
|
||||
</div>
|
||||
</template>
|
||||
</FloatingDropdown>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Entity>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
|
|
|
@ -7,7 +7,6 @@ import {
|
|||
IconBookRead,
|
||||
IconEye,
|
||||
IconEyeOff,
|
||||
IconSettings,
|
||||
IconTeam,
|
||||
IconCloseCircle,
|
||||
useDialog,
|
||||
|
@ -21,6 +20,8 @@ import {
|
|||
} from "@halo-dev/components";
|
||||
import UserDropdownSelector from "@/components/dropdown-selector/UserDropdownSelector.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 { onMounted, ref, watch, watchEffect } from "vue";
|
||||
import type {
|
||||
|
@ -738,18 +739,8 @@ function handleContributorFilterItemChange(user?: User) {
|
|||
role="list"
|
||||
>
|
||||
<li v-for="(post, index) in posts.items" :key="index">
|
||||
<div
|
||||
:class="{
|
||||
'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">
|
||||
<Entity :is-selected="checkSelection(post.post)">
|
||||
<template #checkbox>
|
||||
<input
|
||||
v-model="selectedPostNames"
|
||||
:value="post.post.metadata.name"
|
||||
|
@ -757,27 +748,20 @@ function handleContributorFilterItemChange(user?: User) {
|
|||
name="post-checkbox"
|
||||
type="checkbox"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<div class="flex flex-col items-center sm:flex-row">
|
||||
<RouterLink
|
||||
:to="{
|
||||
</template>
|
||||
<template #start>
|
||||
<EntityField
|
||||
:title="post.post.spec.title"
|
||||
:route="{
|
||||
name: 'PostEditor',
|
||||
query: { name: post.post.metadata.name },
|
||||
}"
|
||||
>
|
||||
<span
|
||||
class="mr-0 truncate text-sm font-medium text-gray-900 sm:mr-2"
|
||||
>
|
||||
{{ post.post.spec.title }}
|
||||
</span>
|
||||
</RouterLink>
|
||||
<template #extra>
|
||||
<VSpace class="mt-1 sm:mt-0">
|
||||
<FloatingTooltip
|
||||
v-if="post.post.status?.inProgress"
|
||||
class="hidden items-center sm:flex"
|
||||
>
|
||||
<RouterLink
|
||||
v-if="post.post.status?.inProgress"
|
||||
v-tooltip="`当前有内容已保存,但还未发布。`"
|
||||
:to="{
|
||||
name: 'PostEditor',
|
||||
query: { name: post.post.metadata.name },
|
||||
|
@ -792,10 +776,6 @@ function handleContributorFilterItemChange(user?: User) {
|
|||
></span>
|
||||
</div>
|
||||
</RouterLink>
|
||||
<template #popper>
|
||||
当前有内容已保存,但还未发布。
|
||||
</template>
|
||||
</FloatingTooltip>
|
||||
<PostTag
|
||||
v-for="(tag, tagIndex) in post.tags"
|
||||
:key="tagIndex"
|
||||
|
@ -803,8 +783,8 @@ function handleContributorFilterItemChange(user?: User) {
|
|||
route
|
||||
></PostTag>
|
||||
</VSpace>
|
||||
</div>
|
||||
<div class="mt-1 flex">
|
||||
</template>
|
||||
<template #description>
|
||||
<VSpace>
|
||||
<p
|
||||
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>
|
||||
</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"
|
||||
>
|
||||
</template>
|
||||
</EntityField>
|
||||
</template>
|
||||
<template #end>
|
||||
<EntityField>
|
||||
<template #description>
|
||||
<RouterLink
|
||||
v-for="(contributor, contributorIndex) in post.contributors"
|
||||
:key="contributorIndex"
|
||||
|
@ -844,10 +824,11 @@ function handleContributorFilterItemChange(user?: User) {
|
|||
circle
|
||||
></VAvatar>
|
||||
</RouterLink>
|
||||
<span class="text-sm text-gray-500">
|
||||
{{ finalStatus(post.post) }}
|
||||
</span>
|
||||
<span>
|
||||
</template>
|
||||
</EntityField>
|
||||
<EntityField :description="finalStatus(post.post)"></EntityField>
|
||||
<EntityField>
|
||||
<template #description>
|
||||
<IconEye
|
||||
v-if="post.post.spec.visible === 'PUBLIC'"
|
||||
v-tooltip="`公开访问`"
|
||||
|
@ -863,18 +844,15 @@ function handleContributorFilterItemChange(user?: User) {
|
|||
v-tooltip="`内部成员可访问`"
|
||||
class="cursor-pointer text-sm transition-all hover:text-blue-600"
|
||||
/>
|
||||
</span>
|
||||
<time class="text-sm text-gray-500">
|
||||
{{ formatDatetime(post.post.metadata.creationTimestamp) }}
|
||||
</time>
|
||||
<span>
|
||||
<FloatingDropdown>
|
||||
<IconSettings
|
||||
class="cursor-pointer transition-all hover:text-blue-600"
|
||||
</template>
|
||||
</EntityField>
|
||||
<EntityField
|
||||
:description="
|
||||
formatDatetime(post.post.metadata.creationTimestamp)
|
||||
"
|
||||
/>
|
||||
<template #popper>
|
||||
<div class="w-48 p-2">
|
||||
<VSpace class="w-full" direction="column">
|
||||
</template>
|
||||
<template #menuItems>
|
||||
<VButton
|
||||
v-close-popper
|
||||
block
|
||||
|
@ -891,15 +869,8 @@ function handleContributorFilterItemChange(user?: User) {
|
|||
>
|
||||
删除
|
||||
</VButton>
|
||||
</VSpace>
|
||||
</div>
|
||||
</template>
|
||||
</FloatingDropdown>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Entity>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
<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 type { CategoryTree } from "../utils";
|
||||
import { ref } from "vue";
|
||||
import { formatDatetime } from "@/utils/date";
|
||||
import Entity from "@/components/entity/Entity.vue";
|
||||
import EntityField from "@/components/entity/EntityField.vue";
|
||||
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
|
@ -49,61 +51,41 @@ function onDelete(category: CategoryTree) {
|
|||
>
|
||||
<template #item="{ element: category }">
|
||||
<li>
|
||||
<div
|
||||
class="group relative block cursor-pointer px-4 py-3 transition-all hover:bg-gray-50"
|
||||
>
|
||||
<Entity>
|
||||
<template #prepend>
|
||||
<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"
|
||||
>
|
||||
<IconList class="h-3.5 w-3.5" />
|
||||
</div>
|
||||
<div class="relative flex flex-row items-center">
|
||||
<div class="flex-1">
|
||||
<div class="flex flex-col sm:flex-row">
|
||||
<span
|
||||
class="mr-0 truncate text-sm font-medium text-gray-900 sm:mr-2"
|
||||
>
|
||||
{{ category.spec.displayName }}
|
||||
</span>
|
||||
<VSpace class="mt-1 sm:mt-0"></VSpace>
|
||||
</div>
|
||||
<div class="mt-1 flex">
|
||||
<span class="text-xs text-gray-500">
|
||||
{{ category.status.permalink }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex">
|
||||
</template>
|
||||
<template #start>
|
||||
<EntityField
|
||||
:title="category.spec.displayName"
|
||||
:description="category.status.permalink"
|
||||
/>
|
||||
</template>
|
||||
<template #end>
|
||||
<EntityField v-if="category.metadata.deletionTimestamp">
|
||||
<template #description>
|
||||
<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
|
||||
class="inline-block h-1.5 w-1.5 animate-ping rounded-full bg-red-600"
|
||||
></span>
|
||||
</div>
|
||||
<template #popper> 删除中</template>
|
||||
</FloatingTooltip>
|
||||
<div
|
||||
class="cursor-pointer text-sm text-gray-500 hover:text-gray-900"
|
||||
>
|
||||
{{ 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>
|
||||
</EntityField>
|
||||
<EntityField
|
||||
:description="`${category.status?.posts?.length || 0} 篇文章`"
|
||||
/>
|
||||
<template #popper>
|
||||
<div class="w-48 p-2">
|
||||
<VSpace class="w-full" direction="column">
|
||||
<EntityField
|
||||
:description="formatDatetime(category.metadata.creationTimestamp)"
|
||||
/>
|
||||
</template>
|
||||
<template #menuItems>
|
||||
<VButton
|
||||
v-close-popper
|
||||
block
|
||||
|
@ -120,15 +102,8 @@ function onDelete(category: CategoryTree) {
|
|||
>
|
||||
删除
|
||||
</VButton>
|
||||
</VSpace>
|
||||
</div>
|
||||
</template>
|
||||
</FloatingDropdown>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Entity>
|
||||
<CategoryListItem
|
||||
:categories="category.spec.children"
|
||||
class="pl-10 transition-all duration-300"
|
||||
|
|
|
@ -8,7 +8,6 @@ import {
|
|||
IconBookRead,
|
||||
IconGrid,
|
||||
IconList,
|
||||
IconSettings,
|
||||
VButton,
|
||||
VCard,
|
||||
VEmpty,
|
||||
|
@ -17,6 +16,8 @@ import {
|
|||
} from "@halo-dev/components";
|
||||
import TagEditingModal from "./components/TagEditingModal.vue";
|
||||
import PostTag from "./components/PostTag.vue";
|
||||
import Entity from "@/components/entity/Entity.vue";
|
||||
import EntityField from "@/components/entity/EntityField.vue";
|
||||
|
||||
// types
|
||||
import type { Tag } from "@halo-dev/api-client";
|
||||
|
@ -173,60 +174,37 @@ onMounted(async () => {
|
|||
role="list"
|
||||
>
|
||||
<li v-for="(tag, index) in tags" :key="index">
|
||||
<div
|
||||
:class="{
|
||||
'bg-gray-100': selectedTag?.metadata.name === tag.metadata.name,
|
||||
}"
|
||||
class="relative block cursor-pointer px-4 py-3 transition-all hover:bg-gray-50"
|
||||
<Entity
|
||||
:is-selected="selectedTag?.metadata.name === tag.metadata.name"
|
||||
>
|
||||
<div
|
||||
v-show="selectedTag?.metadata.name === tag.metadata.name"
|
||||
class="absolute inset-y-0 left-0 w-0.5 bg-primary"
|
||||
></div>
|
||||
<div class="relative flex flex-row items-center">
|
||||
<div class="flex-1">
|
||||
<div class="flex flex-col sm:flex-row">
|
||||
<template #start>
|
||||
<EntityField :description="tag.status?.permalink">
|
||||
<template #title>
|
||||
<PostTag :tag="tag" />
|
||||
</div>
|
||||
<div class="mt-1 flex">
|
||||
<span class="text-xs text-gray-500">
|
||||
{{ tag.status?.permalink }}
|
||||
</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="tag.metadata.deletionTimestamp"
|
||||
class="mr-4 hidden items-center sm:flex"
|
||||
>
|
||||
</template>
|
||||
</EntityField>
|
||||
</template>
|
||||
<template #end>
|
||||
<EntityField v-if="tag.metadata.deletionTimestamp">
|
||||
<template #description>
|
||||
<div
|
||||
v-tooltip="`删除中`"
|
||||
class="inline-flex h-1.5 w-1.5 rounded-full bg-red-600"
|
||||
>
|
||||
<span
|
||||
class="inline-block h-1.5 w-1.5 animate-ping rounded-full bg-red-600"
|
||||
></span>
|
||||
</div>
|
||||
<template #popper> 删除中</template>
|
||||
</FloatingTooltip>
|
||||
<div
|
||||
class="cursor-pointer text-sm text-gray-500 hover:text-gray-900"
|
||||
>
|
||||
{{ 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>
|
||||
</EntityField>
|
||||
<EntityField
|
||||
:description="`${tag.status?.posts?.length || 0} 篇文章`"
|
||||
/>
|
||||
<template #popper>
|
||||
<div class="w-48 p-2">
|
||||
<VSpace class="w-full" direction="column">
|
||||
<EntityField
|
||||
:description="formatDatetime(tag.metadata.creationTimestamp)"
|
||||
/>
|
||||
</template>
|
||||
<template #menuItems>
|
||||
<VButton
|
||||
v-close-popper
|
||||
block
|
||||
|
@ -243,15 +221,8 @@ onMounted(async () => {
|
|||
>
|
||||
删除
|
||||
</VButton>
|
||||
</VSpace>
|
||||
</div>
|
||||
</template>
|
||||
</FloatingDropdown>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Entity>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
|
|
|
@ -1,14 +1,10 @@
|
|||
<script lang="ts" setup>
|
||||
import {
|
||||
IconList,
|
||||
IconSettings,
|
||||
VButton,
|
||||
VSpace,
|
||||
VTag,
|
||||
} from "@halo-dev/components";
|
||||
import { IconList, VButton, VTag } from "@halo-dev/components";
|
||||
import Draggable from "vuedraggable";
|
||||
import { ref } from "vue";
|
||||
import type { MenuTreeItem } from "@/modules/interface/menus/utils";
|
||||
import Entity from "@/components/entity/Entity.vue";
|
||||
import EntityField from "@/components/entity/EntityField.vue";
|
||||
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
|
@ -70,56 +66,41 @@ function getMenuItemRefDisplayName(menuItem: MenuTreeItem) {
|
|||
>
|
||||
<template #item="{ element: menuItem }">
|
||||
<li>
|
||||
<div
|
||||
class="group relative block cursor-pointer px-4 py-3 transition-all hover:bg-gray-50"
|
||||
>
|
||||
<Entity>
|
||||
<template #prepend>
|
||||
<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"
|
||||
>
|
||||
<IconList class="h-3.5 w-3.5" />
|
||||
</div>
|
||||
<div class="relative flex flex-row items-center">
|
||||
<div class="flex-1">
|
||||
<div class="flex flex-row items-center gap-2">
|
||||
<span class="truncate text-sm font-medium text-gray-900">
|
||||
{{ menuItem.status.displayName }}
|
||||
</span>
|
||||
</template>
|
||||
<template #start>
|
||||
<EntityField
|
||||
:title="menuItem.status.displayName"
|
||||
:description="menuItem.status.href"
|
||||
>
|
||||
<template #extra>
|
||||
<VTag v-if="getMenuItemRefDisplayName(menuItem)">
|
||||
{{ getMenuItemRefDisplayName(menuItem) }}
|
||||
</VTag>
|
||||
</div>
|
||||
|
||||
<div class="mt-1 flex">
|
||||
<VSpace align="start" direction="column" spacing="xs">
|
||||
<a
|
||||
:href="menuItem.status.href"
|
||||
class="text-xs text-gray-500 hover:text-gray-900"
|
||||
target="_blank"
|
||||
</template>
|
||||
</EntityField>
|
||||
</template>
|
||||
<template #end>
|
||||
<EntityField v-if="menuItem.metadata.deletionTimestamp">
|
||||
<template #description>
|
||||
<div
|
||||
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
|
||||
class="inline-block h-1.5 w-1.5 animate-ping rounded-full bg-red-600"
|
||||
></span>
|
||||
</div>
|
||||
<template #popper> 删除中</template>
|
||||
</FloatingTooltip>
|
||||
<div class="self-center">
|
||||
<FloatingDropdown>
|
||||
<IconSettings
|
||||
class="cursor-pointer transition-all hover:text-blue-600"
|
||||
/>
|
||||
<template #popper>
|
||||
<div class="w-48 p-2">
|
||||
<VSpace class="w-full" direction="column">
|
||||
</template>
|
||||
</EntityField>
|
||||
</template>
|
||||
<template #menuItems>
|
||||
<VButton
|
||||
v-close-popper
|
||||
block
|
||||
|
@ -136,14 +117,8 @@ function getMenuItemRefDisplayName(menuItem: MenuTreeItem) {
|
|||
>
|
||||
删除
|
||||
</VButton>
|
||||
</VSpace>
|
||||
</div>
|
||||
</template>
|
||||
</FloatingDropdown>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</Entity>
|
||||
<MenuItemListItem
|
||||
:menu-tree-items="menuItem.spec.children"
|
||||
class="pl-10 transition-all duration-300"
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
<script lang="ts" setup>
|
||||
import {
|
||||
IconSettings,
|
||||
useDialog,
|
||||
VButton,
|
||||
VCard,
|
||||
|
@ -12,6 +11,8 @@ import { defineExpose, onMounted, ref } from "vue";
|
|||
import type { Menu } from "@halo-dev/api-client";
|
||||
import { apiClient } from "@halo-dev/admin-shared";
|
||||
import { useRouteQuery } from "@vueuse/router";
|
||||
import Entity from "@/components/entity/Entity.vue";
|
||||
import EntityField from "@/components/entity/EntityField.vue";
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
|
@ -137,48 +138,36 @@ defineExpose({
|
|||
</VSpace>
|
||||
</template>
|
||||
</VEmpty>
|
||||
<div class="divide-y divide-gray-100 bg-white">
|
||||
<div
|
||||
<ul class="box-border h-full w-full divide-y divide-gray-100" role="list">
|
||||
<li
|
||||
v-for="(menu, index) in menus"
|
||||
:key="index"
|
||||
:class="{
|
||||
'bg-gray-50': selectedMenu?.metadata.name === menu.metadata.name,
|
||||
}"
|
||||
class="relative flex items-center p-4"
|
||||
@click="handleSelect(menu)"
|
||||
>
|
||||
<div
|
||||
v-if="selectedMenu?.metadata.name === menu.metadata.name"
|
||||
class="absolute inset-y-0 left-0 w-0.5 bg-primary"
|
||||
></div>
|
||||
<span class="flex flex-1 cursor-pointer flex-col gap-y-1">
|
||||
<span class="block text-sm font-medium">
|
||||
{{ menu.spec?.displayName }}
|
||||
</span>
|
||||
<span class="block text-xs text-gray-400">
|
||||
{{ menu.spec.menuItems?.length || 0 }}
|
||||
个菜单项
|
||||
</span>
|
||||
</span>
|
||||
<FloatingTooltip
|
||||
v-if="menu.metadata.deletionTimestamp"
|
||||
class="mr-4 hidden items-center sm:flex"
|
||||
<Entity
|
||||
:is-selected="selectedMenu?.metadata.name === menu.metadata.name"
|
||||
>
|
||||
<template #start>
|
||||
<EntityField
|
||||
:title="menu.spec?.displayName"
|
||||
:description="`${menu.spec.menuItems?.length || 0} 个菜单项`"
|
||||
></EntityField>
|
||||
</template>
|
||||
<template #end>
|
||||
<EntityField v-if="menu.metadata.deletionTimestamp">
|
||||
<template #description>
|
||||
<div
|
||||
v-tooltip="`删除中`"
|
||||
class="inline-flex h-1.5 w-1.5 rounded-full bg-red-600"
|
||||
>
|
||||
<div class="inline-flex h-1.5 w-1.5 rounded-full bg-red-600">
|
||||
<span
|
||||
class="inline-block h-1.5 w-1.5 animate-ping rounded-full bg-red-600"
|
||||
></span>
|
||||
</div>
|
||||
<template #popper> 删除中</template>
|
||||
</FloatingTooltip>
|
||||
<div class="self-center">
|
||||
<FloatingDropdown>
|
||||
<IconSettings
|
||||
class="cursor-pointer transition-all hover:text-blue-600"
|
||||
/>
|
||||
<template #popper>
|
||||
<div class="w-48 p-2">
|
||||
<VSpace class="w-full" direction="column">
|
||||
</template>
|
||||
</EntityField>
|
||||
</template>
|
||||
<template #menuItems>
|
||||
<VButton
|
||||
v-close-popper
|
||||
block
|
||||
|
@ -195,13 +184,10 @@ defineExpose({
|
|||
>
|
||||
删除
|
||||
</VButton>
|
||||
</VSpace>
|
||||
</div>
|
||||
</template>
|
||||
</FloatingDropdown>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Entity>
|
||||
</li>
|
||||
</ul>
|
||||
<template #footer>
|
||||
<VButton block type="secondary" @click="handleOpenEditingModal(null)">
|
||||
新增
|
||||
|
|
|
@ -1,11 +1,7 @@
|
|||
<script lang="ts" setup>
|
||||
import {
|
||||
IconSettings,
|
||||
VButton,
|
||||
VSpace,
|
||||
VSwitch,
|
||||
VTag,
|
||||
} from "@halo-dev/components";
|
||||
import { VButton, VSpace, VSwitch, VTag } from "@halo-dev/components";
|
||||
import Entity from "@/components/entity/Entity.vue";
|
||||
import EntityField from "@/components/entity/EntityField.vue";
|
||||
import { toRefs } from "vue";
|
||||
import { usePluginLifeCycle } from "../composables/use-plugin";
|
||||
import type { Plugin } from "@halo-dev/api-client";
|
||||
|
@ -25,17 +21,10 @@ const { plugin } = toRefs(props);
|
|||
const { isStarted, changeStatus, uninstall } = usePluginLifeCycle(plugin);
|
||||
</script>
|
||||
<template>
|
||||
<div
|
||||
class="relative block cursor-pointer px-4 py-3 transition-all hover:bg-gray-50"
|
||||
>
|
||||
<div class="relative flex flex-row items-center">
|
||||
<div v-if="plugin?.spec.logo" class="mr-4">
|
||||
<RouterLink
|
||||
:to="{
|
||||
name: 'PluginDetail',
|
||||
params: { name: plugin?.metadata.name },
|
||||
}"
|
||||
>
|
||||
<Entity>
|
||||
<template #start>
|
||||
<EntityField>
|
||||
<template #description>
|
||||
<div class="h-12 w-12 rounded border bg-white p-1 hover:shadow-sm">
|
||||
<img
|
||||
:alt="plugin?.metadata.name"
|
||||
|
@ -43,55 +32,40 @@ const { isStarted, changeStatus, uninstall } = usePluginLifeCycle(plugin);
|
|||
class="h-full w-full"
|
||||
/>
|
||||
</div>
|
||||
</RouterLink>
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<div class="flex flex-row items-center">
|
||||
<RouterLink
|
||||
:to="{
|
||||
</template>
|
||||
</EntityField>
|
||||
<EntityField
|
||||
:title="plugin?.spec.displayName"
|
||||
:description="plugin?.spec.description"
|
||||
:route="{
|
||||
name: 'PluginDetail',
|
||||
params: { name: plugin?.metadata.name },
|
||||
}"
|
||||
>
|
||||
<span class="mr-2 truncate text-sm font-medium text-gray-900">
|
||||
{{ plugin?.spec.displayName }}
|
||||
</span>
|
||||
</RouterLink>
|
||||
<template #extra>
|
||||
<VSpace>
|
||||
<VTag>
|
||||
{{ isStarted ? "已启用" : "未启用" }}
|
||||
</VTag>
|
||||
</VSpace>
|
||||
</div>
|
||||
<div class="mt-2 flex">
|
||||
<VSpace align="start" direction="column" spacing="xs">
|
||||
<span class="text-xs text-gray-500">
|
||||
{{ plugin?.spec.description }}
|
||||
</span>
|
||||
<span class="text-xs text-gray-500 sm:hidden">
|
||||
@{{ plugin?.spec.author }} {{ plugin?.spec.version }}
|
||||
</span>
|
||||
</VSpace>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex">
|
||||
</template>
|
||||
</EntityField>
|
||||
</template>
|
||||
<template #end>
|
||||
<EntityField v-if="plugin?.status?.phase === 'FAILED'">
|
||||
<template #description>
|
||||
<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
|
||||
class="inline-block h-1.5 w-1.5 animate-ping rounded-full bg-red-600"
|
||||
></span>
|
||||
</div>
|
||||
<template #popper>
|
||||
{{ plugin?.status?.reason }}:
|
||||
{{ plugin?.status?.message }}
|
||||
</template>
|
||||
</FloatingTooltip>
|
||||
</EntityField>
|
||||
<EntityField>
|
||||
<template #description>
|
||||
<a
|
||||
:href="plugin?.spec.homepage"
|
||||
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 }}
|
||||
</a>
|
||||
<span class="hidden text-sm text-gray-500 sm:block">
|
||||
{{ plugin?.spec.version }}
|
||||
</span>
|
||||
<time class="hidden text-sm text-gray-500 sm:block">
|
||||
{{ formatDatetime(plugin?.metadata.creationTimestamp) }}
|
||||
</time>
|
||||
</template>
|
||||
</EntityField>
|
||||
<EntityField :description="plugin?.spec.version" />
|
||||
<EntityField
|
||||
v-if="plugin?.metadata.creationTimestamp"
|
||||
:description="formatDatetime(plugin?.metadata.creationTimestamp)"
|
||||
/>
|
||||
<EntityField>
|
||||
<template #description>
|
||||
<div
|
||||
v-permission="['system:plugins:manage']"
|
||||
class="flex items-center"
|
||||
|
@ -114,27 +91,13 @@ const { isStarted, changeStatus, uninstall } = usePluginLifeCycle(plugin);
|
|||
@click="changeStatus"
|
||||
/>
|
||||
</div>
|
||||
<span v-permission="['system:plugins:manage']" class="cursor-pointer">
|
||||
<FloatingDropdown>
|
||||
<IconSettings />
|
||||
<template #popper>
|
||||
<div class="w-48 p-2">
|
||||
<VSpace class="w-full" direction="column">
|
||||
<VButton
|
||||
v-close-popper
|
||||
block
|
||||
type="danger"
|
||||
@click="uninstall"
|
||||
>
|
||||
</template>
|
||||
</EntityField>
|
||||
</template>
|
||||
<template #menuItems>
|
||||
<VButton v-close-popper block type="danger" @click="uninstall">
|
||||
卸载
|
||||
</VButton>
|
||||
</VSpace>
|
||||
</div>
|
||||
</template>
|
||||
</FloatingDropdown>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Entity>
|
||||
</template>
|
||||
|
|
|
@ -7,7 +7,6 @@ import type { Role } from "@halo-dev/api-client";
|
|||
import {
|
||||
IconAddCircle,
|
||||
IconArrowDown,
|
||||
IconSettings,
|
||||
IconShieldUser,
|
||||
useDialog,
|
||||
VButton,
|
||||
|
@ -18,6 +17,8 @@ import {
|
|||
VTag,
|
||||
} from "@halo-dev/components";
|
||||
import RoleEditingModal from "./components/RoleEditingModal.vue";
|
||||
import Entity from "@/components/entity/Entity.vue";
|
||||
import EntityField from "@/components/entity/EntityField.vue";
|
||||
|
||||
// constants
|
||||
import { rbacAnnotations } from "@/constants/annotations";
|
||||
|
@ -199,82 +200,54 @@ const handleDelete = async (role: Role) => {
|
|||
</template>
|
||||
<ul class="box-border h-full w-full divide-y divide-gray-100" role="list">
|
||||
<li v-for="(role, index) in roles" :key="index">
|
||||
<div
|
||||
class="relative block cursor-pointer px-4 py-3 transition-all hover:bg-gray-50"
|
||||
>
|
||||
<div class="relative flex flex-row items-center">
|
||||
<div class="flex-1">
|
||||
<div class="flex flex-row items-center">
|
||||
<RouterLink
|
||||
:to="{
|
||||
<Entity>
|
||||
<template #start>
|
||||
<EntityField
|
||||
:title="
|
||||
role.metadata.annotations?.[rbacAnnotations.DISPLAY_NAME] ||
|
||||
role.metadata.name
|
||||
"
|
||||
:description="`包含
|
||||
${
|
||||
JSON.parse(
|
||||
role.metadata.annotations?.[
|
||||
rbacAnnotations.DEPENDENCIES
|
||||
] || '[]'
|
||||
).length
|
||||
}
|
||||
个权限`"
|
||||
:route="{
|
||||
name: 'RoleDetail',
|
||||
params: {
|
||||
name: role.metadata.name,
|
||||
},
|
||||
}"
|
||||
>
|
||||
<span
|
||||
class="mr-2 truncate text-sm font-medium text-gray-900"
|
||||
>
|
||||
{{
|
||||
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"
|
||||
>
|
||||
></EntityField>
|
||||
</template>
|
||||
<template #end>
|
||||
<EntityField v-if="role.metadata.deletionTimestamp">
|
||||
<template #description>
|
||||
<div
|
||||
v-tooltip="`删除中`"
|
||||
class="inline-flex h-1.5 w-1.5 rounded-full bg-red-600"
|
||||
>
|
||||
<span
|
||||
class="inline-block h-1.5 w-1.5 animate-ping rounded-full bg-red-600"
|
||||
></span>
|
||||
</div>
|
||||
<template #popper> 删除中</template>
|
||||
</FloatingTooltip>
|
||||
|
||||
<a
|
||||
class="hidden text-sm text-gray-500 hover:text-gray-900 sm:block"
|
||||
target="_blank"
|
||||
>
|
||||
0 个用户
|
||||
</a>
|
||||
</template>
|
||||
</EntityField>
|
||||
<EntityField description="0 个用户" />
|
||||
<EntityField>
|
||||
<template #description>
|
||||
<VTag> 系统保留</VTag>
|
||||
<time class="text-sm text-gray-500">
|
||||
{{ formatDatetime(role.metadata.creationTimestamp) }}
|
||||
</time>
|
||||
<span
|
||||
v-permission="['system:roles:manage']"
|
||||
class="cursor-pointer"
|
||||
>
|
||||
<FloatingDropdown>
|
||||
<IconSettings />
|
||||
<template #popper>
|
||||
<div class="w-48 p-2">
|
||||
<VSpace class="w-full" direction="column">
|
||||
</template>
|
||||
</EntityField>
|
||||
<EntityField
|
||||
:description="formatDatetime(role.metadata.creationTimestamp)"
|
||||
/>
|
||||
</template>
|
||||
<template #menuItems>
|
||||
<VButton
|
||||
v-close-popper
|
||||
block
|
||||
|
@ -291,22 +264,11 @@ const handleDelete = async (role: Role) => {
|
|||
>
|
||||
删除
|
||||
</VButton>
|
||||
<VButton
|
||||
v-close-popper
|
||||
block
|
||||
@click="handleCloneRole(role)"
|
||||
>
|
||||
<VButton v-close-popper block @click="handleCloneRole(role)">
|
||||
基于此角色创建
|
||||
</VButton>
|
||||
</VSpace>
|
||||
</div>
|
||||
</template>
|
||||
</FloatingDropdown>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Entity>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
import {
|
||||
IconAddCircle,
|
||||
IconArrowDown,
|
||||
IconSettings,
|
||||
IconUserFollow,
|
||||
IconUserSettings,
|
||||
VButton,
|
||||
|
@ -21,6 +20,8 @@ import type { User, UserList } from "@halo-dev/api-client";
|
|||
import { rbacAnnotations } from "@/constants/annotations";
|
||||
import { formatDatetime } from "@/utils/date";
|
||||
import { useRouteQuery } from "@vueuse/router";
|
||||
import Entity from "@/components/entity/Entity.vue";
|
||||
import EntityField from "@/components/entity/EntityField.vue";
|
||||
|
||||
const checkAll = ref(false);
|
||||
const editingModal = ref<boolean>(false);
|
||||
|
@ -275,80 +276,53 @@ onMounted(() => {
|
|||
</template>
|
||||
<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">
|
||||
<div
|
||||
:class="{
|
||||
'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">
|
||||
<Entity :is-selected="checkAll">
|
||||
<template #checkbox>
|
||||
<input
|
||||
v-model="checkAll"
|
||||
v-permission="['system:users:manage']"
|
||||
class="h-4 w-4 rounded border-gray-300 text-indigo-600"
|
||||
type="checkbox"
|
||||
/>
|
||||
</div>
|
||||
<div v-if="user.spec.avatar" class="mr-4 flex items-center">
|
||||
</template>
|
||||
<template #start>
|
||||
<EntityField>
|
||||
<template #description>
|
||||
<VAvatar
|
||||
:alt="user.spec.displayName"
|
||||
:src="user.spec.avatar"
|
||||
size="md"
|
||||
></VAvatar>
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<div class="flex flex-row items-center">
|
||||
<span
|
||||
class="mr-2 truncate text-sm font-medium text-gray-900"
|
||||
@click="
|
||||
$router.push({
|
||||
</template>
|
||||
</EntityField>
|
||||
<EntityField
|
||||
:title="user.spec.displayName"
|
||||
:description="user.metadata.name"
|
||||
:route="{
|
||||
name: 'UserDetail',
|
||||
params: { name: user.metadata.name },
|
||||
})
|
||||
"
|
||||
>
|
||||
{{ user.spec.displayName }}
|
||||
</span>
|
||||
<VTag class="sm:hidden">{{ user.metadata.name }}</VTag>
|
||||
</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"
|
||||
>
|
||||
}"
|
||||
/>
|
||||
</template>
|
||||
<template #end>
|
||||
<EntityField>
|
||||
<template #description>
|
||||
<div
|
||||
v-for="(role, roleIndex) in getRoles(user)"
|
||||
:key="roleIndex"
|
||||
class="hidden items-center sm:flex"
|
||||
class="flex items-center"
|
||||
>
|
||||
<VTag>
|
||||
{{ role }}
|
||||
</VTag>
|
||||
</div>
|
||||
<time class="text-sm text-gray-500">
|
||||
{{ formatDatetime(user.metadata.creationTimestamp) }}
|
||||
</time>
|
||||
<span
|
||||
v-permission="['system:users:manage']"
|
||||
class="cursor-pointer"
|
||||
>
|
||||
<FloatingDropdown>
|
||||
<IconSettings />
|
||||
<template #popper>
|
||||
<div class="w-48 p-2">
|
||||
<VSpace class="w-full" direction="column">
|
||||
</template>
|
||||
</EntityField>
|
||||
<EntityField
|
||||
:description="formatDatetime(user.metadata.creationTimestamp)"
|
||||
/>
|
||||
</template>
|
||||
<template #menuItems>
|
||||
<VButton
|
||||
v-close-popper
|
||||
block
|
||||
|
@ -364,15 +338,8 @@ onMounted(() => {
|
|||
>
|
||||
修改密码
|
||||
</VButton>
|
||||
</VSpace>
|
||||
</div>
|
||||
</template>
|
||||
</FloatingDropdown>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Entity>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
|
|
Loading…
Reference in New Issue