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,
|
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,47 +556,35 @@ 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),
|
<input
|
||||||
}"
|
:checked="selectedAttachments.has(attachment)"
|
||||||
class="relative block cursor-pointer px-4 py-3 transition-all hover:bg-gray-50"
|
class="h-4 w-4 rounded border-gray-300 text-indigo-600"
|
||||||
>
|
type="checkbox"
|
||||||
<div
|
@click="handleSelect(attachment)"
|
||||||
v-show="isChecked(attachment)"
|
/>
|
||||||
class="absolute inset-y-0 left-0 w-0.5 bg-primary"
|
</template>
|
||||||
></div>
|
<template #start>
|
||||||
<div class="relative flex flex-row items-center">
|
<EntityField>
|
||||||
<div class="mr-4 hidden items-center sm:flex">
|
<template #description>
|
||||||
<input
|
<div
|
||||||
:checked="selectedAttachments.has(attachment)"
|
class="h-10 w-10 rounded border bg-white p-1 hover:shadow-sm"
|
||||||
class="h-4 w-4 rounded border-gray-300 text-indigo-600"
|
|
||||||
type="checkbox"
|
|
||||||
@click="handleSelect(attachment)"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div class="mr-4">
|
|
||||||
<div
|
|
||||||
class="h-12 w-12 rounded border bg-white p-1 hover:shadow-sm"
|
|
||||||
>
|
|
||||||
<AttachmentFileTypeIcon
|
|
||||||
:display-ext="false"
|
|
||||||
:file-name="attachment.spec.displayName"
|
|
||||||
:width="8"
|
|
||||||
: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"
|
|
||||||
@click="handleClickItem(attachment)"
|
|
||||||
>
|
>
|
||||||
{{ attachment.spec.displayName }}
|
<AttachmentFileTypeIcon
|
||||||
</span>
|
:display-ext="false"
|
||||||
</div>
|
:file-name="attachment.spec.displayName"
|
||||||
<div class="mt-1 flex">
|
:width="8"
|
||||||
|
:height="8"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</EntityField>
|
||||||
|
<EntityField
|
||||||
|
:title="attachment.spec.displayName"
|
||||||
|
@click="handleClickItem(attachment)"
|
||||||
|
>
|
||||||
|
<template #description>
|
||||||
<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
|
||||||
|
v-tooltip="`删除中`"
|
||||||
|
class="inline-flex h-1.5 w-1.5 rounded-full bg-red-600"
|
||||||
>
|
>
|
||||||
<div
|
<span
|
||||||
class="inline-flex h-1.5 w-1.5 rounded-full bg-red-600"
|
class="inline-block h-1.5 w-1.5 animate-ping rounded-full bg-red-600"
|
||||||
>
|
></span>
|
||||||
<span
|
</div>
|
||||||
class="inline-block h-1.5 w-1.5 animate-ping rounded-full bg-red-600"
|
</template>
|
||||||
></span>
|
</EntityField>
|
||||||
</div>
|
<EntityField
|
||||||
<template #popper> 删除中</template>
|
:description="
|
||||||
</FloatingTooltip>
|
formatDatetime(attachment.metadata.creationTimestamp)
|
||||||
<time class="text-sm text-gray-500">
|
"
|
||||||
{{
|
/>
|
||||||
formatDatetime(
|
</template>
|
||||||
attachment.metadata.creationTimestamp
|
<template #menuItems>
|
||||||
)
|
<VButton v-close-popper block type="danger"> 删除 </VButton>
|
||||||
}}
|
</template>
|
||||||
</time>
|
</Entity>
|
||||||
<span class="cursor-pointer">
|
|
||||||
<IconSettings
|
|
||||||
@click.stop="handleClickItem(attachment)"
|
|
||||||
/>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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,68 +333,47 @@ 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,
|
<input
|
||||||
}"
|
v-model="checkAll"
|
||||||
class="relative block cursor-pointer px-4 py-3 transition-all hover:bg-gray-50"
|
class="h-4 w-4 rounded border-gray-300 text-indigo-600"
|
||||||
>
|
type="checkbox"
|
||||||
<div
|
/>
|
||||||
v-show="checkAll"
|
</template>
|
||||||
class="absolute inset-y-0 left-0 w-0.5 bg-primary"
|
<template #start>
|
||||||
></div>
|
<EntityField
|
||||||
<div class="relative flex flex-row items-center">
|
:title="singlePage.page.spec.title"
|
||||||
<div class="mr-4 hidden items-center sm:flex">
|
:description="singlePage.page.status?.permalink"
|
||||||
<input
|
:route="{
|
||||||
v-model="checkAll"
|
name: 'SinglePageEditor',
|
||||||
class="h-4 w-4 rounded border-gray-300 text-indigo-600"
|
query: { name: singlePage.page.metadata.name },
|
||||||
type="checkbox"
|
}"
|
||||||
/>
|
>
|
||||||
</div>
|
<template #extra>
|
||||||
<div class="flex-1">
|
|
||||||
<div class="flex flex-row items-center">
|
|
||||||
<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 },
|
||||||
}"
|
}"
|
||||||
|
class="flex items-center"
|
||||||
>
|
>
|
||||||
<span class="truncate text-sm font-medium text-gray-900">
|
<div
|
||||||
{{ singlePage.page.spec.title }}
|
class="inline-flex h-1.5 w-1.5 rounded-full bg-orange-600"
|
||||||
</span>
|
|
||||||
</RouterLink>
|
|
||||||
<FloatingTooltip
|
|
||||||
v-if="singlePage.page.status?.inProgress"
|
|
||||||
class="hidden items-center sm:flex"
|
|
||||||
>
|
|
||||||
<RouterLink
|
|
||||||
:to="{
|
|
||||||
name: 'SinglePageEditor',
|
|
||||||
query: { name: singlePage.page.metadata.name },
|
|
||||||
}"
|
|
||||||
class="flex items-center"
|
|
||||||
>
|
>
|
||||||
<div
|
<span
|
||||||
class="inline-flex h-1.5 w-1.5 rounded-full bg-orange-600"
|
class="inline-block h-1.5 w-1.5 animate-ping rounded-full bg-orange-600"
|
||||||
>
|
></span>
|
||||||
<span
|
</div>
|
||||||
class="inline-block h-1.5 w-1.5 animate-ping rounded-full bg-orange-600"
|
</RouterLink>
|
||||||
></span>
|
</template>
|
||||||
</div>
|
</EntityField>
|
||||||
</RouterLink>
|
</template>
|
||||||
<template #popper> 当前有内容已保存,但还未发布。 </template>
|
<template #end>
|
||||||
</FloatingTooltip>
|
<EntityField>
|
||||||
</div>
|
<template #description>
|
||||||
<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"
|
|
||||||
>
|
|
||||||
<RouterLink
|
<RouterLink
|
||||||
v-for="(
|
v-for="(
|
||||||
contributor, contributorIndex
|
contributor, contributorIndex
|
||||||
|
@ -413,64 +393,53 @@ 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>
|
||||||
<IconEye
|
<template #description>
|
||||||
v-if="singlePage.page.spec.visible === 'PUBLIC'"
|
<IconEye
|
||||||
v-tooltip="`公开访问`"
|
v-if="singlePage.page.spec.visible === 'PUBLIC'"
|
||||||
class="cursor-pointer text-sm transition-all hover:text-blue-600"
|
v-tooltip="`公开访问`"
|
||||||
/>
|
class="cursor-pointer text-sm transition-all hover:text-blue-600"
|
||||||
<IconEyeOff
|
/>
|
||||||
v-if="singlePage.page.spec.visible === 'PRIVATE'"
|
<IconEyeOff
|
||||||
v-tooltip="`私有访问`"
|
v-if="singlePage.page.spec.visible === 'PRIVATE'"
|
||||||
class="cursor-pointer text-sm transition-all hover:text-blue-600"
|
v-tooltip="`私有访问`"
|
||||||
/>
|
class="cursor-pointer text-sm transition-all hover:text-blue-600"
|
||||||
<IconTeam
|
/>
|
||||||
v-if="singlePage.page.spec.visible === 'INTERNAL'"
|
<IconTeam
|
||||||
v-tooltip="`内部成员可访问`"
|
v-if="singlePage.page.spec.visible === 'INTERNAL'"
|
||||||
class="cursor-pointer text-sm transition-all hover:text-blue-600"
|
v-tooltip="`内部成员可访问`"
|
||||||
/>
|
class="cursor-pointer text-sm transition-all hover:text-blue-600"
|
||||||
</span>
|
/>
|
||||||
<time class="text-sm text-gray-500">
|
</template>
|
||||||
{{
|
</EntityField>
|
||||||
formatDatetime(singlePage.page.metadata.creationTimestamp)
|
<EntityField
|
||||||
}}
|
:description="
|
||||||
</time>
|
formatDatetime(singlePage.page.metadata.creationTimestamp)
|
||||||
<span>
|
"
|
||||||
<FloatingDropdown>
|
/>
|
||||||
<IconSettings
|
</template>
|
||||||
class="cursor-pointer transition-all hover:text-blue-600"
|
<template #menuItems>
|
||||||
/>
|
<VButton
|
||||||
<template #popper>
|
v-close-popper
|
||||||
<div class="w-48 p-2">
|
block
|
||||||
<VSpace class="w-full" direction="column">
|
type="secondary"
|
||||||
<VButton
|
@click="handleOpenSettingModal(singlePage.page)"
|
||||||
v-close-popper
|
>
|
||||||
block
|
设置
|
||||||
type="secondary"
|
</VButton>
|
||||||
@click="handleOpenSettingModal(singlePage.page)"
|
<VButton
|
||||||
>
|
v-close-popper
|
||||||
设置
|
block
|
||||||
</VButton>
|
type="danger"
|
||||||
<VButton
|
@click="handleDelete(singlePage.page)"
|
||||||
v-close-popper
|
>
|
||||||
block
|
删除
|
||||||
type="danger"
|
</VButton>
|
||||||
@click="handleDelete(singlePage.page)"
|
</template>
|
||||||
>
|
</Entity>
|
||||||
删除
|
|
||||||
</VButton>
|
|
||||||
</VSpace>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</FloatingDropdown>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
|
|
|
@ -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,64 +739,43 @@ 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),
|
<input
|
||||||
}"
|
v-model="selectedPostNames"
|
||||||
class="relative block cursor-pointer px-4 py-3 transition-all hover:bg-gray-50"
|
:value="post.post.metadata.name"
|
||||||
>
|
class="h-4 w-4 rounded border-gray-300 text-indigo-600"
|
||||||
<div
|
name="post-checkbox"
|
||||||
v-show="checkSelection(post.post)"
|
type="checkbox"
|
||||||
class="absolute inset-y-0 left-0 w-0.5 bg-primary"
|
/>
|
||||||
></div>
|
</template>
|
||||||
<div class="relative flex flex-row items-center">
|
<template #start>
|
||||||
<div class="mr-4 hidden items-center sm:flex">
|
<EntityField
|
||||||
<input
|
:title="post.post.spec.title"
|
||||||
v-model="selectedPostNames"
|
:route="{
|
||||||
:value="post.post.metadata.name"
|
name: 'PostEditor',
|
||||||
class="h-4 w-4 rounded border-gray-300 text-indigo-600"
|
query: { name: post.post.metadata.name },
|
||||||
name="post-checkbox"
|
}"
|
||||||
type="checkbox"
|
>
|
||||||
/>
|
<template #extra>
|
||||||
</div>
|
|
||||||
<div class="flex-1">
|
|
||||||
<div class="flex flex-col items-center sm:flex-row">
|
|
||||||
<RouterLink
|
|
||||||
:to="{
|
|
||||||
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>
|
|
||||||
<VSpace class="mt-1 sm:mt-0">
|
<VSpace class="mt-1 sm:mt-0">
|
||||||
<FloatingTooltip
|
<RouterLink
|
||||||
v-if="post.post.status?.inProgress"
|
v-if="post.post.status?.inProgress"
|
||||||
class="hidden items-center sm:flex"
|
v-tooltip="`当前有内容已保存,但还未发布。`"
|
||||||
|
:to="{
|
||||||
|
name: 'PostEditor',
|
||||||
|
query: { name: post.post.metadata.name },
|
||||||
|
}"
|
||||||
|
class="flex items-center"
|
||||||
>
|
>
|
||||||
<RouterLink
|
<div
|
||||||
:to="{
|
class="inline-flex h-1.5 w-1.5 rounded-full bg-orange-600"
|
||||||
name: 'PostEditor',
|
|
||||||
query: { name: post.post.metadata.name },
|
|
||||||
}"
|
|
||||||
class="flex items-center"
|
|
||||||
>
|
>
|
||||||
<div
|
<span
|
||||||
class="inline-flex h-1.5 w-1.5 rounded-full bg-orange-600"
|
class="inline-block h-1.5 w-1.5 animate-ping rounded-full bg-orange-600"
|
||||||
>
|
></span>
|
||||||
<span
|
</div>
|
||||||
class="inline-block h-1.5 w-1.5 animate-ping rounded-full bg-orange-600"
|
</RouterLink>
|
||||||
></span>
|
|
||||||
</div>
|
|
||||||
</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,62 +824,53 @@ 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>
|
||||||
<IconEye
|
<template #description>
|
||||||
v-if="post.post.spec.visible === 'PUBLIC'"
|
<IconEye
|
||||||
v-tooltip="`公开访问`"
|
v-if="post.post.spec.visible === 'PUBLIC'"
|
||||||
class="cursor-pointer text-sm transition-all hover:text-blue-600"
|
v-tooltip="`公开访问`"
|
||||||
/>
|
class="cursor-pointer text-sm transition-all hover:text-blue-600"
|
||||||
<IconEyeOff
|
/>
|
||||||
v-if="post.post.spec.visible === 'PRIVATE'"
|
<IconEyeOff
|
||||||
v-tooltip="`私有访问`"
|
v-if="post.post.spec.visible === 'PRIVATE'"
|
||||||
class="cursor-pointer text-sm transition-all hover:text-blue-600"
|
v-tooltip="`私有访问`"
|
||||||
/>
|
class="cursor-pointer text-sm transition-all hover:text-blue-600"
|
||||||
<IconTeam
|
/>
|
||||||
v-if="post.post.spec.visible === 'INTERNAL'"
|
<IconTeam
|
||||||
v-tooltip="`内部成员可访问`"
|
v-if="post.post.spec.visible === 'INTERNAL'"
|
||||||
class="cursor-pointer text-sm transition-all hover:text-blue-600"
|
v-tooltip="`内部成员可访问`"
|
||||||
/>
|
class="cursor-pointer text-sm transition-all hover:text-blue-600"
|
||||||
</span>
|
/>
|
||||||
<time class="text-sm text-gray-500">
|
</template>
|
||||||
{{ formatDatetime(post.post.metadata.creationTimestamp) }}
|
</EntityField>
|
||||||
</time>
|
<EntityField
|
||||||
<span>
|
:description="
|
||||||
<FloatingDropdown>
|
formatDatetime(post.post.metadata.creationTimestamp)
|
||||||
<IconSettings
|
"
|
||||||
class="cursor-pointer transition-all hover:text-blue-600"
|
/>
|
||||||
/>
|
</template>
|
||||||
<template #popper>
|
<template #menuItems>
|
||||||
<div class="w-48 p-2">
|
<VButton
|
||||||
<VSpace class="w-full" direction="column">
|
v-close-popper
|
||||||
<VButton
|
block
|
||||||
v-close-popper
|
type="secondary"
|
||||||
block
|
@click="handleOpenSettingModal(post.post)"
|
||||||
type="secondary"
|
>
|
||||||
@click="handleOpenSettingModal(post.post)"
|
设置
|
||||||
>
|
</VButton>
|
||||||
设置
|
<VButton
|
||||||
</VButton>
|
v-close-popper
|
||||||
<VButton
|
block
|
||||||
v-close-popper
|
type="danger"
|
||||||
block
|
@click="handleDelete(post.post)"
|
||||||
type="danger"
|
>
|
||||||
@click="handleDelete(post.post)"
|
删除
|
||||||
>
|
</VButton>
|
||||||
删除
|
</template>
|
||||||
</VButton>
|
</Entity>
|
||||||
</VSpace>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</FloatingDropdown>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
|
|
|
@ -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,86 +51,59 @@ 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 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>
|
||||||
<div class="flex">
|
</template>
|
||||||
<div
|
<template #start>
|
||||||
class="inline-flex flex-col items-end gap-4 sm:flex-row sm:items-center sm:gap-6"
|
<EntityField
|
||||||
>
|
:title="category.spec.displayName"
|
||||||
<FloatingTooltip
|
:description="category.status.permalink"
|
||||||
v-if="category.metadata.deletionTimestamp"
|
/>
|
||||||
class="mr-4 hidden items-center sm:flex"
|
</template>
|
||||||
>
|
<template #end>
|
||||||
<div class="inline-flex h-1.5 w-1.5 rounded-full bg-red-600">
|
<EntityField v-if="category.metadata.deletionTimestamp">
|
||||||
<span
|
<template #description>
|
||||||
class="inline-block h-1.5 w-1.5 animate-ping rounded-full bg-red-600"
|
|
||||||
></span>
|
|
||||||
</div>
|
|
||||||
<template #popper> 删除中</template>
|
|
||||||
</FloatingTooltip>
|
|
||||||
<div
|
<div
|
||||||
class="cursor-pointer text-sm text-gray-500 hover:text-gray-900"
|
v-tooltip="`删除中`"
|
||||||
|
class="inline-flex h-1.5 w-1.5 rounded-full bg-red-600"
|
||||||
>
|
>
|
||||||
{{ category.status?.posts?.length || 0 }} 篇文章
|
<span
|
||||||
|
class="inline-block h-1.5 w-1.5 animate-ping rounded-full bg-red-600"
|
||||||
|
></span>
|
||||||
</div>
|
</div>
|
||||||
<time class="text-sm text-gray-500">
|
</template>
|
||||||
{{ formatDatetime(category.metadata.creationTimestamp) }}
|
</EntityField>
|
||||||
</time>
|
<EntityField
|
||||||
<span class="self-center">
|
:description="`${category.status?.posts?.length || 0} 篇文章`"
|
||||||
<FloatingDropdown>
|
/>
|
||||||
<IconSettings
|
<EntityField
|
||||||
class="cursor-pointer transition-all hover:text-blue-600"
|
:description="formatDatetime(category.metadata.creationTimestamp)"
|
||||||
/>
|
/>
|
||||||
<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
|
type="secondary"
|
||||||
type="secondary"
|
@click="onOpenEditingModal(category)"
|
||||||
@click="onOpenEditingModal(category)"
|
>
|
||||||
>
|
修改
|
||||||
修改
|
</VButton>
|
||||||
</VButton>
|
<VButton
|
||||||
<VButton
|
v-close-popper
|
||||||
v-close-popper
|
block
|
||||||
block
|
type="danger"
|
||||||
type="danger"
|
@click="onDelete(category)"
|
||||||
@click="onDelete(category)"
|
>
|
||||||
>
|
删除
|
||||||
删除
|
</VButton>
|
||||||
</VButton>
|
</template>
|
||||||
</VSpace>
|
</Entity>
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</FloatingDropdown>
|
|
||||||
</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"
|
||||||
|
|
|
@ -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,85 +174,55 @@ 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
|
|
||||||
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
|
<div
|
||||||
class="cursor-pointer text-sm text-gray-500 hover:text-gray-900"
|
v-tooltip="`删除中`"
|
||||||
|
class="inline-flex h-1.5 w-1.5 rounded-full bg-red-600"
|
||||||
>
|
>
|
||||||
{{ tag.status?.posts?.length || 0 }} 篇文章
|
<span
|
||||||
|
class="inline-block h-1.5 w-1.5 animate-ping rounded-full bg-red-600"
|
||||||
|
></span>
|
||||||
</div>
|
</div>
|
||||||
<time class="text-sm text-gray-500">
|
</template>
|
||||||
{{ formatDatetime(tag.metadata.creationTimestamp) }}
|
</EntityField>
|
||||||
</time>
|
<EntityField
|
||||||
<span class="self-center">
|
:description="`${tag.status?.posts?.length || 0} 篇文章`"
|
||||||
<FloatingDropdown>
|
/>
|
||||||
<IconSettings
|
<EntityField
|
||||||
class="cursor-pointer transition-all hover:text-blue-600"
|
:description="formatDatetime(tag.metadata.creationTimestamp)"
|
||||||
/>
|
/>
|
||||||
<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
|
type="secondary"
|
||||||
type="secondary"
|
@click="handleOpenEditingModal(tag)"
|
||||||
@click="handleOpenEditingModal(tag)"
|
>
|
||||||
>
|
修改
|
||||||
修改
|
</VButton>
|
||||||
</VButton>
|
<VButton
|
||||||
<VButton
|
v-close-popper
|
||||||
v-close-popper
|
block
|
||||||
block
|
type="danger"
|
||||||
type="danger"
|
@click="handleDelete(tag)"
|
||||||
@click="handleDelete(tag)"
|
>
|
||||||
>
|
删除
|
||||||
删除
|
</VButton>
|
||||||
</VButton>
|
</template>
|
||||||
</VSpace>
|
</Entity>
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</FloatingDropdown>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
|
|
|
@ -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,80 +66,59 @@ 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>
|
</template>
|
||||||
<div class="relative flex flex-row items-center">
|
<template #start>
|
||||||
<div class="flex-1">
|
<EntityField
|
||||||
<div class="flex flex-row items-center gap-2">
|
:title="menuItem.status.displayName"
|
||||||
<span class="truncate text-sm font-medium text-gray-900">
|
:description="menuItem.status.href"
|
||||||
{{ menuItem.status.displayName }}
|
>
|
||||||
</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>
|
<span
|
||||||
</VSpace>
|
class="inline-block h-1.5 w-1.5 animate-ping rounded-full bg-red-600"
|
||||||
</div>
|
></span>
|
||||||
</div>
|
</div>
|
||||||
<FloatingTooltip
|
</template>
|
||||||
v-if="menuItem.metadata.deletionTimestamp"
|
</EntityField>
|
||||||
class="mr-4 hidden items-center sm:flex"
|
</template>
|
||||||
|
<template #menuItems>
|
||||||
|
<VButton
|
||||||
|
v-close-popper
|
||||||
|
block
|
||||||
|
type="secondary"
|
||||||
|
@click="onOpenEditingModal(menuItem)"
|
||||||
>
|
>
|
||||||
<div class="inline-flex h-1.5 w-1.5 rounded-full bg-red-600">
|
修改
|
||||||
<span
|
</VButton>
|
||||||
class="inline-block h-1.5 w-1.5 animate-ping rounded-full bg-red-600"
|
<VButton
|
||||||
></span>
|
v-close-popper
|
||||||
</div>
|
block
|
||||||
<template #popper> 删除中</template>
|
type="danger"
|
||||||
</FloatingTooltip>
|
@click="onDelete(menuItem)"
|
||||||
<div class="self-center">
|
>
|
||||||
<FloatingDropdown>
|
删除
|
||||||
<IconSettings
|
</VButton>
|
||||||
class="cursor-pointer transition-all hover:text-blue-600"
|
</template>
|
||||||
/>
|
</Entity>
|
||||||
<template #popper>
|
|
||||||
<div class="w-48 p-2">
|
|
||||||
<VSpace class="w-full" direction="column">
|
|
||||||
<VButton
|
|
||||||
v-close-popper
|
|
||||||
block
|
|
||||||
type="secondary"
|
|
||||||
@click="onOpenEditingModal(menuItem)"
|
|
||||||
>
|
|
||||||
修改
|
|
||||||
</VButton>
|
|
||||||
<VButton
|
|
||||||
v-close-popper
|
|
||||||
block
|
|
||||||
type="danger"
|
|
||||||
@click="onDelete(menuItem)"
|
|
||||||
>
|
|
||||||
删除
|
|
||||||
</VButton>
|
|
||||||
</VSpace>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</FloatingDropdown>
|
|
||||||
</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"
|
||||||
|
|
|
@ -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,71 +138,56 @@ 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>
|
|
||||||
<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"
|
|
||||||
>
|
>
|
||||||
<div class="inline-flex h-1.5 w-1.5 rounded-full bg-red-600">
|
<template #start>
|
||||||
<span
|
<EntityField
|
||||||
class="inline-block h-1.5 w-1.5 animate-ping rounded-full bg-red-600"
|
:title="menu.spec?.displayName"
|
||||||
></span>
|
:description="`${menu.spec.menuItems?.length || 0} 个菜单项`"
|
||||||
</div>
|
></EntityField>
|
||||||
<template #popper> 删除中</template>
|
</template>
|
||||||
</FloatingTooltip>
|
<template #end>
|
||||||
<div class="self-center">
|
<EntityField v-if="menu.metadata.deletionTimestamp">
|
||||||
<FloatingDropdown>
|
<template #description>
|
||||||
<IconSettings
|
<div
|
||||||
class="cursor-pointer transition-all hover:text-blue-600"
|
v-tooltip="`删除中`"
|
||||||
/>
|
class="inline-flex h-1.5 w-1.5 rounded-full bg-red-600"
|
||||||
<template #popper>
|
>
|
||||||
<div class="w-48 p-2">
|
<span
|
||||||
<VSpace class="w-full" direction="column">
|
class="inline-block h-1.5 w-1.5 animate-ping rounded-full bg-red-600"
|
||||||
<VButton
|
></span>
|
||||||
v-close-popper
|
</div>
|
||||||
block
|
</template>
|
||||||
type="secondary"
|
</EntityField>
|
||||||
@click="handleOpenEditingModal(menu)"
|
</template>
|
||||||
>
|
<template #menuItems>
|
||||||
修改
|
<VButton
|
||||||
</VButton>
|
v-close-popper
|
||||||
<VButton
|
block
|
||||||
v-close-popper
|
type="secondary"
|
||||||
block
|
@click="handleOpenEditingModal(menu)"
|
||||||
type="danger"
|
>
|
||||||
@click="handleDeleteMenu(menu)"
|
修改
|
||||||
>
|
</VButton>
|
||||||
删除
|
<VButton
|
||||||
</VButton>
|
v-close-popper
|
||||||
</VSpace>
|
block
|
||||||
</div>
|
type="danger"
|
||||||
</template>
|
@click="handleDeleteMenu(menu)"
|
||||||
</FloatingDropdown>
|
>
|
||||||
</div>
|
删除
|
||||||
</div>
|
</VButton>
|
||||||
</div>
|
</template>
|
||||||
|
</Entity>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<VButton block type="secondary" @click="handleOpenEditingModal(null)">
|
<VButton block type="secondary" @click="handleOpenEditingModal(null)">
|
||||||
新增
|
新增
|
||||||
|
|
|
@ -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">
|
<div
|
||||||
@{{ plugin?.spec.author }} {{ plugin?.spec.version }}
|
v-tooltip="`${plugin?.status?.reason}:${plugin?.status?.message}`"
|
||||||
</span>
|
class="inline-flex h-1.5 w-1.5 rounded-full bg-red-600"
|
||||||
</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"
|
|
||||||
>
|
|
||||||
<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>
|
||||||
<template #popper>
|
</EntityField>
|
||||||
{{ plugin?.status?.reason }}:
|
<EntityField>
|
||||||
{{ plugin?.status?.message }}
|
<template #description>
|
||||||
</template>
|
|
||||||
</FloatingTooltip>
|
|
||||||
<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
|
</VButton>
|
||||||
v-close-popper
|
</template>
|
||||||
block
|
</Entity>
|
||||||
type="danger"
|
|
||||||
@click="uninstall"
|
|
||||||
>
|
|
||||||
卸载
|
|
||||||
</VButton>
|
|
||||||
</VSpace>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</FloatingDropdown>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -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,114 +200,75 @@ 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="`包含
|
||||||
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(
|
JSON.parse(
|
||||||
role.metadata.annotations?.[
|
role.metadata.annotations?.[
|
||||||
rbacAnnotations.DEPENDENCIES
|
rbacAnnotations.DEPENDENCIES
|
||||||
] || "[]"
|
] || '[]'
|
||||||
).length
|
).length
|
||||||
}}
|
}
|
||||||
个权限
|
个权限`"
|
||||||
</span>
|
:route="{
|
||||||
</div>
|
name: 'RoleDetail',
|
||||||
</div>
|
params: {
|
||||||
<div class="flex">
|
name: role.metadata.name,
|
||||||
<div
|
},
|
||||||
class="inline-flex flex-col items-end gap-4 sm:flex-row sm:items-center sm:gap-6"
|
}"
|
||||||
>
|
></EntityField>
|
||||||
<FloatingTooltip
|
</template>
|
||||||
v-if="role.metadata.deletionTimestamp"
|
<template #end>
|
||||||
class="hidden items-center sm:flex"
|
<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"
|
||||||
>
|
>
|
||||||
<div
|
<span
|
||||||
class="inline-flex h-1.5 w-1.5 rounded-full bg-red-600"
|
class="inline-block h-1.5 w-1.5 animate-ping rounded-full bg-red-600"
|
||||||
>
|
></span>
|
||||||
<span
|
</div>
|
||||||
class="inline-block h-1.5 w-1.5 animate-ping rounded-full bg-red-600"
|
</template>
|
||||||
></span>
|
</EntityField>
|
||||||
</div>
|
<EntityField description="0 个用户" />
|
||||||
<template #popper> 删除中</template>
|
<EntityField>
|
||||||
</FloatingTooltip>
|
<template #description>
|
||||||
|
|
||||||
<a
|
|
||||||
class="hidden text-sm text-gray-500 hover:text-gray-900 sm:block"
|
|
||||||
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>
|
<VButton
|
||||||
<IconSettings />
|
v-close-popper
|
||||||
<template #popper>
|
block
|
||||||
<div class="w-48 p-2">
|
type="secondary"
|
||||||
<VSpace class="w-full" direction="column">
|
@click="handleOpenEditingModal(role)"
|
||||||
<VButton
|
>
|
||||||
v-close-popper
|
编辑
|
||||||
block
|
</VButton>
|
||||||
type="secondary"
|
<VButton
|
||||||
@click="handleOpenEditingModal(role)"
|
v-close-popper
|
||||||
>
|
block
|
||||||
编辑
|
type="danger"
|
||||||
</VButton>
|
@click="handleDelete(role)"
|
||||||
<VButton
|
>
|
||||||
v-close-popper
|
删除
|
||||||
block
|
</VButton>
|
||||||
type="danger"
|
<VButton v-close-popper block @click="handleCloneRole(role)">
|
||||||
@click="handleDelete(role)"
|
基于此角色创建
|
||||||
>
|
</VButton>
|
||||||
删除
|
</template>
|
||||||
</VButton>
|
</Entity>
|
||||||
<VButton
|
|
||||||
v-close-popper
|
|
||||||
block
|
|
||||||
@click="handleCloneRole(role)"
|
|
||||||
>
|
|
||||||
基于此角色创建
|
|
||||||
</VButton>
|
|
||||||
</VSpace>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</FloatingDropdown>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
|
|
|
@ -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,104 +276,70 @@ 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,
|
<input
|
||||||
}"
|
v-model="checkAll"
|
||||||
class="relative block cursor-pointer px-4 py-3 transition-all hover:bg-gray-50"
|
v-permission="['system:users:manage']"
|
||||||
>
|
class="h-4 w-4 rounded border-gray-300 text-indigo-600"
|
||||||
<div
|
type="checkbox"
|
||||||
v-show="checkAll"
|
/>
|
||||||
class="absolute inset-y-0 left-0 w-0.5 bg-primary"
|
</template>
|
||||||
></div>
|
<template #start>
|
||||||
<div class="relative flex flex-row items-center">
|
<EntityField>
|
||||||
<div class="mr-4 hidden items-center sm:flex">
|
<template #description>
|
||||||
<input
|
<VAvatar
|
||||||
v-model="checkAll"
|
:alt="user.spec.displayName"
|
||||||
v-permission="['system:users:manage']"
|
:src="user.spec.avatar"
|
||||||
class="h-4 w-4 rounded border-gray-300 text-indigo-600"
|
size="md"
|
||||||
type="checkbox"
|
></VAvatar>
|
||||||
/>
|
</template>
|
||||||
</div>
|
</EntityField>
|
||||||
<div v-if="user.spec.avatar" class="mr-4 flex items-center">
|
<EntityField
|
||||||
<VAvatar
|
:title="user.spec.displayName"
|
||||||
:alt="user.spec.displayName"
|
:description="user.metadata.name"
|
||||||
:src="user.spec.avatar"
|
:route="{
|
||||||
size="md"
|
name: 'UserDetail',
|
||||||
></VAvatar>
|
params: { name: user.metadata.name },
|
||||||
</div>
|
}"
|
||||||
<div class="flex-1">
|
/>
|
||||||
<div class="flex flex-row items-center">
|
</template>
|
||||||
<span
|
<template #end>
|
||||||
class="mr-2 truncate text-sm font-medium text-gray-900"
|
<EntityField>
|
||||||
@click="
|
<template #description>
|
||||||
$router.push({
|
|
||||||
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"
|
|
||||||
>
|
|
||||||
<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>
|
<VButton
|
||||||
<IconSettings />
|
v-close-popper
|
||||||
<template #popper>
|
block
|
||||||
<div class="w-48 p-2">
|
type="secondary"
|
||||||
<VSpace class="w-full" direction="column">
|
@click="handleOpenCreateModal(user)"
|
||||||
<VButton
|
>
|
||||||
v-close-popper
|
修改资料
|
||||||
block
|
</VButton>
|
||||||
type="secondary"
|
<VButton
|
||||||
@click="handleOpenCreateModal(user)"
|
v-close-popper
|
||||||
>
|
block
|
||||||
修改资料
|
@click="handleOpenPasswordChangeModal(user)"
|
||||||
</VButton>
|
>
|
||||||
<VButton
|
修改密码
|
||||||
v-close-popper
|
</VButton>
|
||||||
block
|
</template>
|
||||||
@click="handleOpenPasswordChangeModal(user)"
|
</Entity>
|
||||||
>
|
|
||||||
修改密码
|
|
||||||
</VButton>
|
|
||||||
</VSpace>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</FloatingDropdown>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue