mirror of https://github.com/halo-dev/halo
refactor: add loading states for data list (halo-dev/console#703)
#### What type of PR is this? /kind improvement /milestone 2.0 #### What this PR does / why we need it: 优化列表的加载,添加加载动画,解决加载完成之后界面闪动的问题。 #### Which issue(s) this PR fixes: Ref https://github.com/halo-dev/halo/issues/2370 #### Special notes for your reviewer: /cc @halo-dev/sig-halo-console #### Does this PR introduce a user-facing change? ```release-note 优化列表的加载,添加加载动画,解决加载完成之后界面闪动的问题。 ```pull/3445/head
parent
c0877e2a9c
commit
3f3147ce34
|
@ -21,3 +21,4 @@ export * from "./components/empty";
|
|||
export * from "./components/status";
|
||||
export * from "./components/entity";
|
||||
export * from "./components/toast";
|
||||
export * from "./components/loading";
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
<script lang="ts" setup></script>
|
||||
|
||||
<template>
|
||||
<div class="flex items-center justify-center py-4">
|
||||
<svg
|
||||
class="h-5 w-5 animate-spin"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<circle
|
||||
class="opacity-25"
|
||||
cx="12"
|
||||
cy="12"
|
||||
r="10"
|
||||
stroke="currentColor"
|
||||
stroke-width="4"
|
||||
></circle>
|
||||
<path
|
||||
class="opacity-75"
|
||||
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
|
||||
fill="currentColor"
|
||||
></path>
|
||||
</svg>
|
||||
</div>
|
||||
</template>
|
|
@ -0,0 +1 @@
|
|||
export { default as VLoading } from "./Loading.vue";
|
|
@ -19,6 +19,7 @@ import {
|
|||
VStatusDot,
|
||||
VEntity,
|
||||
VEntityField,
|
||||
VLoading,
|
||||
} from "@halo-dev/components";
|
||||
import LazyImage from "@/components/image/LazyImage.vue";
|
||||
import UserDropdownSelector from "@/components/dropdown-selector/UserDropdownSelector.vue";
|
||||
|
@ -563,30 +564,33 @@ onMounted(() => {
|
|||
/>
|
||||
</div>
|
||||
|
||||
<VEmpty
|
||||
v-if="!attachments.total && !loading"
|
||||
message="当前分组没有附件,你可以尝试刷新或者上传附件"
|
||||
title="当前分组没有附件"
|
||||
>
|
||||
<template #actions>
|
||||
<VSpace>
|
||||
<VButton @click="handleFetchAttachments">刷新</VButton>
|
||||
<VButton
|
||||
v-permission="['system:attachments:manage']"
|
||||
type="secondary"
|
||||
@click="uploadVisible = true"
|
||||
>
|
||||
<template #icon>
|
||||
<IconUpload class="h-full w-full" />
|
||||
</template>
|
||||
上传附件
|
||||
</VButton>
|
||||
</VSpace>
|
||||
</template>
|
||||
</VEmpty>
|
||||
<VLoading v-if="loading" />
|
||||
|
||||
<Transition v-else-if="!attachments.total" appear name="fade">
|
||||
<VEmpty
|
||||
message="当前分组没有附件,你可以尝试刷新或者上传附件"
|
||||
title="当前分组没有附件"
|
||||
>
|
||||
<template #actions>
|
||||
<VSpace>
|
||||
<VButton @click="handleFetchAttachments">刷新</VButton>
|
||||
<VButton
|
||||
v-permission="['system:attachments:manage']"
|
||||
type="secondary"
|
||||
@click="uploadVisible = true"
|
||||
>
|
||||
<template #icon>
|
||||
<IconUpload class="h-full w-full" />
|
||||
</template>
|
||||
上传附件
|
||||
</VButton>
|
||||
</VSpace>
|
||||
</template>
|
||||
</VEmpty>
|
||||
</Transition>
|
||||
|
||||
<div v-else>
|
||||
<div v-if="viewType === 'grid'">
|
||||
<Transition v-if="viewType === 'grid'" appear name="fade">
|
||||
<div
|
||||
class="mt-2 grid grid-cols-3 gap-x-2 gap-y-3 sm:grid-cols-3 md:grid-cols-6 xl:grid-cols-8 2xl:grid-cols-12"
|
||||
role="list"
|
||||
|
@ -666,119 +670,126 @@ onMounted(() => {
|
|||
</div>
|
||||
</VCard>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ul
|
||||
v-if="viewType === 'list'"
|
||||
class="box-border h-full w-full divide-y divide-gray-100"
|
||||
role="list"
|
||||
>
|
||||
<li v-for="(attachment, index) in attachments.items" :key="index">
|
||||
<VEntity :is-selected="isChecked(attachment)">
|
||||
<template
|
||||
v-if="
|
||||
currentUserHasPermission(['system:attachments:manage'])
|
||||
"
|
||||
#checkbox
|
||||
>
|
||||
<input
|
||||
:checked="selectedAttachments.has(attachment)"
|
||||
class="h-4 w-4 rounded border-gray-300 text-indigo-600"
|
||||
type="checkbox"
|
||||
@click="handleSelect(attachment)"
|
||||
/>
|
||||
</template>
|
||||
<template #start>
|
||||
<VEntityField>
|
||||
<template #description>
|
||||
<div
|
||||
class="h-10 w-10 rounded border bg-white p-1 hover:shadow-sm"
|
||||
>
|
||||
<AttachmentFileTypeIcon
|
||||
:display-ext="false"
|
||||
:file-name="attachment.spec.displayName"
|
||||
:width="8"
|
||||
:height="8"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</VEntityField>
|
||||
<VEntityField
|
||||
:title="attachment.spec.displayName"
|
||||
@click="handleClickItem(attachment)"
|
||||
>
|
||||
<template #description>
|
||||
<VSpace>
|
||||
<span class="text-xs text-gray-500">
|
||||
{{ attachment.spec.mediaType }}
|
||||
</span>
|
||||
<span class="text-xs text-gray-500">
|
||||
{{ prettyBytes(attachment.spec.size || 0) }}
|
||||
</span>
|
||||
</VSpace>
|
||||
</template>
|
||||
</VEntityField>
|
||||
</template>
|
||||
<template #end>
|
||||
<VEntityField
|
||||
:description="
|
||||
getPolicyName(attachment.spec.policyRef?.name)
|
||||
</Transition>
|
||||
<Transition v-if="viewType === 'list'" appear name="fade">
|
||||
<ul
|
||||
class="box-border h-full w-full divide-y divide-gray-100"
|
||||
role="list"
|
||||
>
|
||||
<li
|
||||
v-for="(attachment, index) in attachments.items"
|
||||
:key="index"
|
||||
>
|
||||
<VEntity :is-selected="isChecked(attachment)">
|
||||
<template
|
||||
v-if="
|
||||
currentUserHasPermission(['system:attachments:manage'])
|
||||
"
|
||||
/>
|
||||
<VEntityField>
|
||||
<template #description>
|
||||
<RouterLink
|
||||
:to="{
|
||||
name: 'UserDetail',
|
||||
params: { name: attachment.spec.uploadedBy?.name },
|
||||
}"
|
||||
class="text-xs text-gray-500"
|
||||
>
|
||||
{{ attachment.spec.uploadedBy?.name }}
|
||||
</RouterLink>
|
||||
</template>
|
||||
</VEntityField>
|
||||
<VEntityField v-if="attachment.metadata.deletionTimestamp">
|
||||
<template #description>
|
||||
<VStatusDot
|
||||
v-tooltip="`删除中`"
|
||||
state="warning"
|
||||
animate
|
||||
/>
|
||||
</template>
|
||||
</VEntityField>
|
||||
<VEntityField>
|
||||
<template #description>
|
||||
<span
|
||||
class="truncate text-xs tabular-nums text-gray-500"
|
||||
>
|
||||
{{
|
||||
formatDatetime(
|
||||
attachment.metadata.creationTimestamp
|
||||
)
|
||||
}}
|
||||
</span>
|
||||
</template>
|
||||
</VEntityField>
|
||||
</template>
|
||||
<template
|
||||
v-if="
|
||||
currentUserHasPermission(['system:attachments:manage'])
|
||||
"
|
||||
#dropdownItems
|
||||
>
|
||||
<VButton
|
||||
v-close-popper
|
||||
block
|
||||
type="danger"
|
||||
@click="handleDelete(attachment)"
|
||||
#checkbox
|
||||
>
|
||||
删除
|
||||
</VButton>
|
||||
</template>
|
||||
</VEntity>
|
||||
</li>
|
||||
</ul>
|
||||
<input
|
||||
:checked="selectedAttachments.has(attachment)"
|
||||
class="h-4 w-4 rounded border-gray-300 text-indigo-600"
|
||||
type="checkbox"
|
||||
@click="handleSelect(attachment)"
|
||||
/>
|
||||
</template>
|
||||
<template #start>
|
||||
<VEntityField>
|
||||
<template #description>
|
||||
<div
|
||||
class="h-10 w-10 rounded border bg-white p-1 hover:shadow-sm"
|
||||
>
|
||||
<AttachmentFileTypeIcon
|
||||
:display-ext="false"
|
||||
:file-name="attachment.spec.displayName"
|
||||
:width="8"
|
||||
:height="8"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</VEntityField>
|
||||
<VEntityField
|
||||
:title="attachment.spec.displayName"
|
||||
@click="handleClickItem(attachment)"
|
||||
>
|
||||
<template #description>
|
||||
<VSpace>
|
||||
<span class="text-xs text-gray-500">
|
||||
{{ attachment.spec.mediaType }}
|
||||
</span>
|
||||
<span class="text-xs text-gray-500">
|
||||
{{ prettyBytes(attachment.spec.size || 0) }}
|
||||
</span>
|
||||
</VSpace>
|
||||
</template>
|
||||
</VEntityField>
|
||||
</template>
|
||||
<template #end>
|
||||
<VEntityField
|
||||
:description="
|
||||
getPolicyName(attachment.spec.policyRef?.name)
|
||||
"
|
||||
/>
|
||||
<VEntityField>
|
||||
<template #description>
|
||||
<RouterLink
|
||||
:to="{
|
||||
name: 'UserDetail',
|
||||
params: {
|
||||
name: attachment.spec.uploadedBy?.name,
|
||||
},
|
||||
}"
|
||||
class="text-xs text-gray-500"
|
||||
>
|
||||
{{ attachment.spec.uploadedBy?.name }}
|
||||
</RouterLink>
|
||||
</template>
|
||||
</VEntityField>
|
||||
<VEntityField
|
||||
v-if="attachment.metadata.deletionTimestamp"
|
||||
>
|
||||
<template #description>
|
||||
<VStatusDot
|
||||
v-tooltip="`删除中`"
|
||||
state="warning"
|
||||
animate
|
||||
/>
|
||||
</template>
|
||||
</VEntityField>
|
||||
<VEntityField>
|
||||
<template #description>
|
||||
<span
|
||||
class="truncate text-xs tabular-nums text-gray-500"
|
||||
>
|
||||
{{
|
||||
formatDatetime(
|
||||
attachment.metadata.creationTimestamp
|
||||
)
|
||||
}}
|
||||
</span>
|
||||
</template>
|
||||
</VEntityField>
|
||||
</template>
|
||||
<template
|
||||
v-if="
|
||||
currentUserHasPermission(['system:attachments:manage'])
|
||||
"
|
||||
#dropdownItems
|
||||
>
|
||||
<VButton
|
||||
v-close-popper
|
||||
block
|
||||
type="danger"
|
||||
@click="handleDelete(attachment)"
|
||||
>
|
||||
删除
|
||||
</VButton>
|
||||
</template>
|
||||
</VEntity>
|
||||
</li>
|
||||
</ul>
|
||||
</Transition>
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
|
|
|
@ -10,6 +10,7 @@ import {
|
|||
IconRefreshLine,
|
||||
VEmpty,
|
||||
Dialog,
|
||||
VLoading,
|
||||
} from "@halo-dev/components";
|
||||
import CommentListItem from "./components/CommentListItem.vue";
|
||||
import UserDropdownSelector from "@/components/dropdown-selector/UserDropdownSelector.vue";
|
||||
|
@ -459,36 +460,40 @@ function handleClearFilters() {
|
|||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<VEmpty
|
||||
v-if="!comments.items.length && !loading"
|
||||
message="你可以尝试刷新或者修改筛选条件"
|
||||
title="当前没有评论"
|
||||
>
|
||||
<template #actions>
|
||||
<VSpace>
|
||||
<VButton @click="handleFetchComments">刷新</VButton>
|
||||
</VSpace>
|
||||
</template>
|
||||
</VEmpty>
|
||||
<ul class="box-border h-full w-full divide-y divide-gray-100" role="list">
|
||||
<li v-for="(comment, index) in comments.items" :key="index">
|
||||
<CommentListItem
|
||||
:comment="comment"
|
||||
:is-selected="checkSelection(comment)"
|
||||
@reload="handleFetchComments"
|
||||
>
|
||||
<template #checkbox>
|
||||
<input
|
||||
v-model="selectedCommentNames"
|
||||
:value="comment?.comment?.metadata.name"
|
||||
class="h-4 w-4 rounded border-gray-300 text-indigo-600"
|
||||
name="comment-checkbox"
|
||||
type="checkbox"
|
||||
/>
|
||||
</template>
|
||||
</CommentListItem>
|
||||
</li>
|
||||
</ul>
|
||||
<VLoading v-if="loading" />
|
||||
<Transition v-else-if="!comments.items.length" appear name="fade">
|
||||
<VEmpty message="你可以尝试刷新或者修改筛选条件" title="当前没有评论">
|
||||
<template #actions>
|
||||
<VSpace>
|
||||
<VButton @click="handleFetchComments">刷新</VButton>
|
||||
</VSpace>
|
||||
</template>
|
||||
</VEmpty>
|
||||
</Transition>
|
||||
<Transition v-else appear name="fade">
|
||||
<ul
|
||||
class="box-border h-full w-full divide-y divide-gray-100"
|
||||
role="list"
|
||||
>
|
||||
<li v-for="(comment, index) in comments.items" :key="index">
|
||||
<CommentListItem
|
||||
:comment="comment"
|
||||
:is-selected="checkSelection(comment)"
|
||||
@reload="handleFetchComments"
|
||||
>
|
||||
<template #checkbox>
|
||||
<input
|
||||
v-model="selectedCommentNames"
|
||||
:value="comment?.comment?.metadata.name"
|
||||
class="h-4 w-4 rounded border-gray-300 text-indigo-600"
|
||||
name="comment-checkbox"
|
||||
type="checkbox"
|
||||
/>
|
||||
</template>
|
||||
</CommentListItem>
|
||||
</li>
|
||||
</ul>
|
||||
</Transition>
|
||||
|
||||
<template #footer>
|
||||
<div class="bg-white sm:flex sm:items-center sm:justify-end">
|
||||
|
|
|
@ -14,6 +14,7 @@ import {
|
|||
VEntityField,
|
||||
VPageHeader,
|
||||
VStatusDot,
|
||||
VLoading,
|
||||
} from "@halo-dev/components";
|
||||
import { onMounted, ref, watch } from "vue";
|
||||
import type { ListedSinglePageList, SinglePage } from "@halo-dev/api-client";
|
||||
|
@ -305,125 +306,130 @@ function handleClearKeyword() {
|
|||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<VEmpty
|
||||
v-if="!singlePages.items.length && !loading"
|
||||
message="你可以尝试刷新或者返回自定义页面管理"
|
||||
title="没有自定义页面被放入回收站"
|
||||
>
|
||||
<template #actions>
|
||||
<VSpace>
|
||||
<VButton @click="handleFetchSinglePages">刷新</VButton>
|
||||
<VButton
|
||||
v-permission="['system:singlepages:view']"
|
||||
:route="{ name: 'SinglePages' }"
|
||||
type="primary"
|
||||
>
|
||||
返回
|
||||
</VButton>
|
||||
</VSpace>
|
||||
</template>
|
||||
</VEmpty>
|
||||
<ul
|
||||
v-else
|
||||
class="box-border h-full w-full divide-y divide-gray-100"
|
||||
role="list"
|
||||
>
|
||||
<li v-for="(singlePage, index) in singlePages.items" :key="index">
|
||||
<VEntity :is-selected="checkSelection(singlePage.page)">
|
||||
<template
|
||||
v-if="currentUserHasPermission(['system:singlepages:manage'])"
|
||||
#checkbox
|
||||
>
|
||||
<input
|
||||
v-model="selectedPageNames"
|
||||
:value="singlePage.page.metadata.name"
|
||||
class="h-4 w-4 rounded border-gray-300 text-indigo-600"
|
||||
type="checkbox"
|
||||
/>
|
||||
</template>
|
||||
<template #start>
|
||||
<VEntityField :title="singlePage.page.spec.title">
|
||||
<template #description>
|
||||
<VSpace>
|
||||
<span class="text-xs text-gray-500">
|
||||
{{ singlePage.page.status?.permalink }}
|
||||
</span>
|
||||
<span class="text-xs text-gray-500">
|
||||
访问量 {{ singlePage.stats.visit || 0 }}
|
||||
</span>
|
||||
<span class="text-xs text-gray-500">
|
||||
评论 {{ singlePage.stats.totalComment || 0 }}
|
||||
</span>
|
||||
</VSpace>
|
||||
</template>
|
||||
</VEntityField>
|
||||
</template>
|
||||
<template #end>
|
||||
<VEntityField>
|
||||
<template #description>
|
||||
<RouterLink
|
||||
v-for="(
|
||||
contributor, contributorIndex
|
||||
) in singlePage.contributors"
|
||||
:key="contributorIndex"
|
||||
:to="{
|
||||
name: 'UserDetail',
|
||||
params: { name: contributor.name },
|
||||
}"
|
||||
class="flex items-center"
|
||||
>
|
||||
<VAvatar
|
||||
v-tooltip="contributor.displayName"
|
||||
size="xs"
|
||||
:src="contributor.avatar"
|
||||
:alt="contributor.displayName"
|
||||
circle
|
||||
></VAvatar>
|
||||
</RouterLink>
|
||||
</template>
|
||||
</VEntityField>
|
||||
<VEntityField v-if="!singlePage?.page?.spec.deleted">
|
||||
<template #description>
|
||||
<VStatusDot v-tooltip="`恢复中`" state="success" animate />
|
||||
</template>
|
||||
</VEntityField>
|
||||
<VEntityField v-if="singlePage?.page?.metadata.deletionTimestamp">
|
||||
<template #description>
|
||||
<VStatusDot v-tooltip="`删除中`" state="warning" animate />
|
||||
</template>
|
||||
</VEntityField>
|
||||
<VEntityField>
|
||||
<template #description>
|
||||
<span class="truncate text-xs tabular-nums text-gray-500">
|
||||
{{ formatDatetime(singlePage.page.spec.publishTime) }}
|
||||
</span>
|
||||
</template>
|
||||
</VEntityField>
|
||||
</template>
|
||||
<template
|
||||
v-if="currentUserHasPermission(['system:singlepages:manage'])"
|
||||
#dropdownItems
|
||||
>
|
||||
<VLoading v-if="loading" />
|
||||
<Transition v-else-if="!singlePages.items.length" appear name="fade">
|
||||
<VEmpty
|
||||
message="你可以尝试刷新或者返回自定义页面管理"
|
||||
title="没有自定义页面被放入回收站"
|
||||
>
|
||||
<template #actions>
|
||||
<VSpace>
|
||||
<VButton @click="handleFetchSinglePages">刷新</VButton>
|
||||
<VButton
|
||||
v-close-popper
|
||||
block
|
||||
type="danger"
|
||||
@click="handleDeletePermanently(singlePage.page)"
|
||||
v-permission="['system:singlepages:view']"
|
||||
:route="{ name: 'SinglePages' }"
|
||||
type="primary"
|
||||
>
|
||||
永久删除
|
||||
返回
|
||||
</VButton>
|
||||
<VButton
|
||||
v-close-popper
|
||||
block
|
||||
type="default"
|
||||
@click="handleRecovery(singlePage.page)"
|
||||
</VSpace>
|
||||
</template>
|
||||
</VEmpty>
|
||||
</Transition>
|
||||
<Transition v-else appear name="fade">
|
||||
<ul
|
||||
class="box-border h-full w-full divide-y divide-gray-100"
|
||||
role="list"
|
||||
>
|
||||
<li v-for="(singlePage, index) in singlePages.items" :key="index">
|
||||
<VEntity :is-selected="checkSelection(singlePage.page)">
|
||||
<template
|
||||
v-if="currentUserHasPermission(['system:singlepages:manage'])"
|
||||
#checkbox
|
||||
>
|
||||
恢复
|
||||
</VButton>
|
||||
</template>
|
||||
</VEntity>
|
||||
</li>
|
||||
</ul>
|
||||
<input
|
||||
v-model="selectedPageNames"
|
||||
:value="singlePage.page.metadata.name"
|
||||
class="h-4 w-4 rounded border-gray-300 text-indigo-600"
|
||||
type="checkbox"
|
||||
/>
|
||||
</template>
|
||||
<template #start>
|
||||
<VEntityField :title="singlePage.page.spec.title">
|
||||
<template #description>
|
||||
<VSpace>
|
||||
<span class="text-xs text-gray-500">
|
||||
{{ singlePage.page.status?.permalink }}
|
||||
</span>
|
||||
<span class="text-xs text-gray-500">
|
||||
访问量 {{ singlePage.stats.visit || 0 }}
|
||||
</span>
|
||||
<span class="text-xs text-gray-500">
|
||||
评论 {{ singlePage.stats.totalComment || 0 }}
|
||||
</span>
|
||||
</VSpace>
|
||||
</template>
|
||||
</VEntityField>
|
||||
</template>
|
||||
<template #end>
|
||||
<VEntityField>
|
||||
<template #description>
|
||||
<RouterLink
|
||||
v-for="(
|
||||
contributor, contributorIndex
|
||||
) in singlePage.contributors"
|
||||
:key="contributorIndex"
|
||||
:to="{
|
||||
name: 'UserDetail',
|
||||
params: { name: contributor.name },
|
||||
}"
|
||||
class="flex items-center"
|
||||
>
|
||||
<VAvatar
|
||||
v-tooltip="contributor.displayName"
|
||||
size="xs"
|
||||
:src="contributor.avatar"
|
||||
:alt="contributor.displayName"
|
||||
circle
|
||||
></VAvatar>
|
||||
</RouterLink>
|
||||
</template>
|
||||
</VEntityField>
|
||||
<VEntityField v-if="!singlePage?.page?.spec.deleted">
|
||||
<template #description>
|
||||
<VStatusDot v-tooltip="`恢复中`" state="success" animate />
|
||||
</template>
|
||||
</VEntityField>
|
||||
<VEntityField
|
||||
v-if="singlePage?.page?.metadata.deletionTimestamp"
|
||||
>
|
||||
<template #description>
|
||||
<VStatusDot v-tooltip="`删除中`" state="warning" animate />
|
||||
</template>
|
||||
</VEntityField>
|
||||
<VEntityField>
|
||||
<template #description>
|
||||
<span class="truncate text-xs tabular-nums text-gray-500">
|
||||
{{ formatDatetime(singlePage.page.spec.publishTime) }}
|
||||
</span>
|
||||
</template>
|
||||
</VEntityField>
|
||||
</template>
|
||||
<template
|
||||
v-if="currentUserHasPermission(['system:singlepages:manage'])"
|
||||
#dropdownItems
|
||||
>
|
||||
<VButton
|
||||
v-close-popper
|
||||
block
|
||||
type="danger"
|
||||
@click="handleDeletePermanently(singlePage.page)"
|
||||
>
|
||||
永久删除
|
||||
</VButton>
|
||||
<VButton
|
||||
v-close-popper
|
||||
block
|
||||
type="default"
|
||||
@click="handleRecovery(singlePage.page)"
|
||||
>
|
||||
恢复
|
||||
</VButton>
|
||||
</template>
|
||||
</VEntity>
|
||||
</li>
|
||||
</ul>
|
||||
</Transition>
|
||||
|
||||
<template #footer>
|
||||
<div class="bg-white sm:flex sm:items-center sm:justify-end">
|
||||
|
|
|
@ -18,6 +18,7 @@ import {
|
|||
VStatusDot,
|
||||
VEntity,
|
||||
VEntityField,
|
||||
VLoading,
|
||||
} from "@halo-dev/components";
|
||||
import SinglePageSettingModal from "./components/SinglePageSettingModal.vue";
|
||||
import UserDropdownSelector from "@/components/dropdown-selector/UserDropdownSelector.vue";
|
||||
|
@ -613,170 +614,167 @@ function handleClearFilters() {
|
|||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<VEmpty
|
||||
v-if="!singlePages.items.length && !loading"
|
||||
message="你可以尝试刷新或者新建页面"
|
||||
title="当前没有页面"
|
||||
>
|
||||
<template #actions>
|
||||
<VSpace>
|
||||
<VButton @click="handleFetchSinglePages">刷新</VButton>
|
||||
<VButton
|
||||
v-permission="['system:singlepages:manage']"
|
||||
:route="{ name: 'SinglePageEditor' }"
|
||||
type="primary"
|
||||
>
|
||||
<template #icon>
|
||||
<IconAddCircle class="h-full w-full" />
|
||||
<VLoading v-if="loading" />
|
||||
<Transition v-else-if="!singlePages.items.length" appear name="fade">
|
||||
<VEmpty message="你可以尝试刷新或者新建页面" title="当前没有页面">
|
||||
<template #actions>
|
||||
<VSpace>
|
||||
<VButton @click="handleFetchSinglePages">刷新</VButton>
|
||||
<VButton
|
||||
v-permission="['system:singlepages:manage']"
|
||||
:route="{ name: 'SinglePageEditor' }"
|
||||
type="primary"
|
||||
>
|
||||
<template #icon>
|
||||
<IconAddCircle class="h-full w-full" />
|
||||
</template>
|
||||
新建页面
|
||||
</VButton>
|
||||
</VSpace>
|
||||
</template>
|
||||
</VEmpty>
|
||||
</Transition>
|
||||
<Transition v-else appear name="fade">
|
||||
<ul class="box-border h-full w-full divide-y divide-gray-100" role="list">
|
||||
<li v-for="(singlePage, index) in singlePages.items" :key="index">
|
||||
<VEntity :is-selected="checkSelection(singlePage.page)">
|
||||
<template
|
||||
v-if="currentUserHasPermission(['system:singlepages:manage'])"
|
||||
#checkbox
|
||||
>
|
||||
<input
|
||||
v-model="selectedPageNames"
|
||||
:value="singlePage.page.metadata.name"
|
||||
class="h-4 w-4 rounded border-gray-300 text-indigo-600"
|
||||
type="checkbox"
|
||||
/>
|
||||
</template>
|
||||
新建页面
|
||||
</VButton>
|
||||
</VSpace>
|
||||
</template>
|
||||
</VEmpty>
|
||||
<ul
|
||||
v-else
|
||||
class="box-border h-full w-full divide-y divide-gray-100"
|
||||
role="list"
|
||||
>
|
||||
<li v-for="(singlePage, index) in singlePages.items" :key="index">
|
||||
<VEntity :is-selected="checkSelection(singlePage.page)">
|
||||
<template
|
||||
v-if="currentUserHasPermission(['system:singlepages:manage'])"
|
||||
#checkbox
|
||||
>
|
||||
<input
|
||||
v-model="selectedPageNames"
|
||||
:value="singlePage.page.metadata.name"
|
||||
class="h-4 w-4 rounded border-gray-300 text-indigo-600"
|
||||
type="checkbox"
|
||||
/>
|
||||
</template>
|
||||
<template #start>
|
||||
<VEntityField
|
||||
:title="singlePage.page.spec.title"
|
||||
:route="{
|
||||
name: 'SinglePageEditor',
|
||||
query: { name: singlePage.page.metadata.name },
|
||||
}"
|
||||
<template #start>
|
||||
<VEntityField
|
||||
:title="singlePage.page.spec.title"
|
||||
:route="{
|
||||
name: 'SinglePageEditor',
|
||||
query: { name: singlePage.page.metadata.name },
|
||||
}"
|
||||
>
|
||||
<template #extra>
|
||||
<RouterLink
|
||||
v-if="singlePage.page.status?.inProgress"
|
||||
v-tooltip="`当前有内容已保存,但还未发布。`"
|
||||
:to="{
|
||||
name: 'SinglePageEditor',
|
||||
query: { name: singlePage.page.metadata.name },
|
||||
}"
|
||||
class="flex items-center"
|
||||
>
|
||||
<VStatusDot state="success" animate />
|
||||
</RouterLink>
|
||||
</template>
|
||||
<template #description>
|
||||
<div class="flex w-full flex-col gap-1">
|
||||
<VSpace class="w-full">
|
||||
<span class="text-xs text-gray-500">
|
||||
访问量 {{ singlePage.stats.visit || 0 }}
|
||||
</span>
|
||||
<span class="text-xs text-gray-500">
|
||||
评论 {{ singlePage.stats.totalComment || 0 }}
|
||||
</span>
|
||||
</VSpace>
|
||||
<VSpace class="w-full">
|
||||
<span class="truncate text-xs text-gray-500">
|
||||
{{ singlePage.page.status?.permalink }}
|
||||
</span>
|
||||
</VSpace>
|
||||
</div>
|
||||
</template>
|
||||
</VEntityField>
|
||||
</template>
|
||||
<template #end>
|
||||
<VEntityField>
|
||||
<template #description>
|
||||
<RouterLink
|
||||
v-for="(
|
||||
contributor, contributorIndex
|
||||
) in singlePage.contributors"
|
||||
:key="contributorIndex"
|
||||
:to="{
|
||||
name: 'UserDetail',
|
||||
params: { name: contributor.name },
|
||||
}"
|
||||
class="flex items-center"
|
||||
>
|
||||
<VAvatar
|
||||
v-tooltip="contributor.displayName"
|
||||
size="xs"
|
||||
:src="contributor.avatar"
|
||||
:alt="contributor.displayName"
|
||||
circle
|
||||
></VAvatar>
|
||||
</RouterLink>
|
||||
</template>
|
||||
</VEntityField>
|
||||
<VEntityField :description="getPublishStatus(singlePage.page)">
|
||||
<template v-if="isPublishing(singlePage.page)" #description>
|
||||
<VStatusDot text="发布中" animate />
|
||||
</template>
|
||||
</VEntityField>
|
||||
<VEntityField>
|
||||
<template #description>
|
||||
<IconEye
|
||||
v-if="singlePage.page.spec.visible === 'PUBLIC'"
|
||||
v-tooltip="`公开访问`"
|
||||
class="cursor-pointer text-sm transition-all hover:text-blue-600"
|
||||
/>
|
||||
<IconEyeOff
|
||||
v-if="singlePage.page.spec.visible === 'PRIVATE'"
|
||||
v-tooltip="`私有访问`"
|
||||
class="cursor-pointer text-sm transition-all hover:text-blue-600"
|
||||
/>
|
||||
<IconTeam
|
||||
v-if="singlePage.page.spec.visible === 'INTERNAL'"
|
||||
v-tooltip="`内部成员可访问`"
|
||||
class="cursor-pointer text-sm transition-all hover:text-blue-600"
|
||||
/>
|
||||
</template>
|
||||
</VEntityField>
|
||||
<VEntityField v-if="singlePage?.page?.spec.deleted">
|
||||
<template #description>
|
||||
<VStatusDot v-tooltip="`删除中`" state="warning" animate />
|
||||
</template>
|
||||
</VEntityField>
|
||||
<VEntityField>
|
||||
<template #description>
|
||||
<span class="truncate text-xs tabular-nums text-gray-500">
|
||||
{{ formatDatetime(singlePage.page.spec.publishTime) }}
|
||||
</span>
|
||||
</template>
|
||||
</VEntityField>
|
||||
</template>
|
||||
<template
|
||||
v-if="currentUserHasPermission(['system:singlepages:manage'])"
|
||||
#dropdownItems
|
||||
>
|
||||
<template #extra>
|
||||
<RouterLink
|
||||
v-if="singlePage.page.status?.inProgress"
|
||||
v-tooltip="`当前有内容已保存,但还未发布。`"
|
||||
:to="{
|
||||
name: 'SinglePageEditor',
|
||||
query: { name: singlePage.page.metadata.name },
|
||||
}"
|
||||
class="flex items-center"
|
||||
>
|
||||
<VStatusDot state="success" animate />
|
||||
</RouterLink>
|
||||
</template>
|
||||
<template #description>
|
||||
<div class="flex w-full flex-col gap-1">
|
||||
<VSpace class="w-full">
|
||||
<span class="text-xs text-gray-500">
|
||||
访问量 {{ singlePage.stats.visit || 0 }}
|
||||
</span>
|
||||
<span class="text-xs text-gray-500">
|
||||
评论 {{ singlePage.stats.totalComment || 0 }}
|
||||
</span>
|
||||
</VSpace>
|
||||
<VSpace class="w-full">
|
||||
<span class="truncate text-xs text-gray-500">
|
||||
{{ singlePage.page.status?.permalink }}
|
||||
</span>
|
||||
</VSpace>
|
||||
</div>
|
||||
</template>
|
||||
</VEntityField>
|
||||
</template>
|
||||
<template #end>
|
||||
<VEntityField>
|
||||
<template #description>
|
||||
<RouterLink
|
||||
v-for="(
|
||||
contributor, contributorIndex
|
||||
) in singlePage.contributors"
|
||||
:key="contributorIndex"
|
||||
:to="{
|
||||
name: 'UserDetail',
|
||||
params: { name: contributor.name },
|
||||
}"
|
||||
class="flex items-center"
|
||||
>
|
||||
<VAvatar
|
||||
v-tooltip="contributor.displayName"
|
||||
size="xs"
|
||||
:src="contributor.avatar"
|
||||
:alt="contributor.displayName"
|
||||
circle
|
||||
></VAvatar>
|
||||
</RouterLink>
|
||||
</template>
|
||||
</VEntityField>
|
||||
<VEntityField :description="getPublishStatus(singlePage.page)">
|
||||
<template v-if="isPublishing(singlePage.page)" #description>
|
||||
<VStatusDot text="发布中" animate />
|
||||
</template>
|
||||
</VEntityField>
|
||||
<VEntityField>
|
||||
<template #description>
|
||||
<IconEye
|
||||
v-if="singlePage.page.spec.visible === 'PUBLIC'"
|
||||
v-tooltip="`公开访问`"
|
||||
class="cursor-pointer text-sm transition-all hover:text-blue-600"
|
||||
/>
|
||||
<IconEyeOff
|
||||
v-if="singlePage.page.spec.visible === 'PRIVATE'"
|
||||
v-tooltip="`私有访问`"
|
||||
class="cursor-pointer text-sm transition-all hover:text-blue-600"
|
||||
/>
|
||||
<IconTeam
|
||||
v-if="singlePage.page.spec.visible === 'INTERNAL'"
|
||||
v-tooltip="`内部成员可访问`"
|
||||
class="cursor-pointer text-sm transition-all hover:text-blue-600"
|
||||
/>
|
||||
</template>
|
||||
</VEntityField>
|
||||
<VEntityField v-if="singlePage?.page?.spec.deleted">
|
||||
<template #description>
|
||||
<VStatusDot v-tooltip="`删除中`" state="warning" animate />
|
||||
</template>
|
||||
</VEntityField>
|
||||
<VEntityField>
|
||||
<template #description>
|
||||
<span class="truncate text-xs tabular-nums text-gray-500">
|
||||
{{ formatDatetime(singlePage.page.spec.publishTime) }}
|
||||
</span>
|
||||
</template>
|
||||
</VEntityField>
|
||||
</template>
|
||||
<template
|
||||
v-if="currentUserHasPermission(['system:singlepages:manage'])"
|
||||
#dropdownItems
|
||||
>
|
||||
<VButton
|
||||
v-close-popper
|
||||
block
|
||||
type="secondary"
|
||||
@click="handleOpenSettingModal(singlePage.page)"
|
||||
>
|
||||
设置
|
||||
</VButton>
|
||||
<VButton
|
||||
v-close-popper
|
||||
block
|
||||
type="danger"
|
||||
@click="handleDelete(singlePage.page)"
|
||||
>
|
||||
删除
|
||||
</VButton>
|
||||
</template>
|
||||
</VEntity>
|
||||
</li>
|
||||
</ul>
|
||||
<VButton
|
||||
v-close-popper
|
||||
block
|
||||
type="secondary"
|
||||
@click="handleOpenSettingModal(singlePage.page)"
|
||||
>
|
||||
设置
|
||||
</VButton>
|
||||
<VButton
|
||||
v-close-popper
|
||||
block
|
||||
type="danger"
|
||||
@click="handleDelete(singlePage.page)"
|
||||
>
|
||||
删除
|
||||
</VButton>
|
||||
</template>
|
||||
</VEntity>
|
||||
</li>
|
||||
</ul>
|
||||
</Transition>
|
||||
|
||||
<template #footer>
|
||||
<div class="bg-white sm:flex sm:items-center sm:justify-end">
|
||||
|
|
|
@ -14,6 +14,7 @@ import {
|
|||
VStatusDot,
|
||||
VEntity,
|
||||
VEntityField,
|
||||
VLoading,
|
||||
} from "@halo-dev/components";
|
||||
import PostTag from "./tags/components/PostTag.vue";
|
||||
import { onMounted, ref, watch } from "vue";
|
||||
|
@ -298,138 +299,146 @@ function handleClearKeyword() {
|
|||
</div>
|
||||
</template>
|
||||
|
||||
<VEmpty
|
||||
v-if="!posts.items.length && !loading"
|
||||
message="你可以尝试刷新或者返回文章管理"
|
||||
title="没有文章被放入回收站"
|
||||
>
|
||||
<template #actions>
|
||||
<VSpace>
|
||||
<VButton @click="handleFetchPosts">刷新</VButton>
|
||||
<VButton :route="{ name: 'Posts' }" type="primary"> 返回 </VButton>
|
||||
</VSpace>
|
||||
</template>
|
||||
</VEmpty>
|
||||
<VLoading v-if="loading" />
|
||||
|
||||
<ul
|
||||
v-else
|
||||
class="box-border h-full w-full divide-y divide-gray-100"
|
||||
role="list"
|
||||
>
|
||||
<li v-for="(post, index) in posts.items" :key="index">
|
||||
<VEntity :is-selected="checkSelection(post.post)">
|
||||
<template
|
||||
v-if="currentUserHasPermission(['system:posts:manage'])"
|
||||
#checkbox
|
||||
>
|
||||
<input
|
||||
v-model="selectedPostNames"
|
||||
:value="post.post.metadata.name"
|
||||
class="h-4 w-4 rounded border-gray-300 text-indigo-600"
|
||||
name="post-checkbox"
|
||||
type="checkbox"
|
||||
/>
|
||||
</template>
|
||||
<template #start>
|
||||
<VEntityField :title="post.post.spec.title">
|
||||
<template #extra>
|
||||
<VSpace class="mt-1 sm:mt-0">
|
||||
<PostTag
|
||||
v-for="(tag, tagIndex) in post.tags"
|
||||
:key="tagIndex"
|
||||
:tag="tag"
|
||||
route
|
||||
></PostTag>
|
||||
</VSpace>
|
||||
</template>
|
||||
<template #description>
|
||||
<VSpace>
|
||||
<p
|
||||
v-if="post.categories.length"
|
||||
class="inline-flex flex-wrap gap-1 text-xs text-gray-500"
|
||||
>
|
||||
分类:<span
|
||||
v-for="(category, categoryIndex) in post.categories"
|
||||
:key="categoryIndex"
|
||||
class="cursor-pointer hover:text-gray-900"
|
||||
<Transition v-else-if="!posts.items.length" appear name="fade">
|
||||
<VEmpty
|
||||
message="你可以尝试刷新或者返回文章管理"
|
||||
title="没有文章被放入回收站"
|
||||
>
|
||||
<template #actions>
|
||||
<VSpace>
|
||||
<VButton @click="handleFetchPosts">刷新</VButton>
|
||||
<VButton :route="{ name: 'Posts' }" type="primary">
|
||||
返回
|
||||
</VButton>
|
||||
</VSpace>
|
||||
</template>
|
||||
</VEmpty>
|
||||
</Transition>
|
||||
|
||||
<Transition v-else appear name="fade">
|
||||
<ul
|
||||
class="box-border h-full w-full divide-y divide-gray-100"
|
||||
role="list"
|
||||
>
|
||||
<li v-for="(post, index) in posts.items" :key="index">
|
||||
<VEntity :is-selected="checkSelection(post.post)">
|
||||
<template
|
||||
v-if="currentUserHasPermission(['system:posts:manage'])"
|
||||
#checkbox
|
||||
>
|
||||
<input
|
||||
v-model="selectedPostNames"
|
||||
:value="post.post.metadata.name"
|
||||
class="h-4 w-4 rounded border-gray-300 text-indigo-600"
|
||||
name="post-checkbox"
|
||||
type="checkbox"
|
||||
/>
|
||||
</template>
|
||||
<template #start>
|
||||
<VEntityField :title="post.post.spec.title">
|
||||
<template #extra>
|
||||
<VSpace class="mt-1 sm:mt-0">
|
||||
<PostTag
|
||||
v-for="(tag, tagIndex) in post.tags"
|
||||
:key="tagIndex"
|
||||
:tag="tag"
|
||||
route
|
||||
></PostTag>
|
||||
</VSpace>
|
||||
</template>
|
||||
<template #description>
|
||||
<VSpace>
|
||||
<p
|
||||
v-if="post.categories.length"
|
||||
class="inline-flex flex-wrap gap-1 text-xs text-gray-500"
|
||||
>
|
||||
{{ category.spec.displayName }}
|
||||
分类:<span
|
||||
v-for="(category, categoryIndex) in post.categories"
|
||||
:key="categoryIndex"
|
||||
class="cursor-pointer hover:text-gray-900"
|
||||
>
|
||||
{{ category.spec.displayName }}
|
||||
</span>
|
||||
</p>
|
||||
<span class="text-xs text-gray-500">
|
||||
访问量 {{ post.stats.visit || 0 }}
|
||||
</span>
|
||||
</p>
|
||||
<span class="text-xs text-gray-500">
|
||||
访问量 {{ post.stats.visit || 0 }}
|
||||
<span class="text-xs text-gray-500">
|
||||
评论 {{ post.stats.totalComment || 0 }}
|
||||
</span>
|
||||
</VSpace>
|
||||
</template>
|
||||
</VEntityField>
|
||||
</template>
|
||||
<template #end>
|
||||
<VEntityField>
|
||||
<template #description>
|
||||
<RouterLink
|
||||
v-for="(
|
||||
contributor, contributorIndex
|
||||
) in post.contributors"
|
||||
:key="contributorIndex"
|
||||
:to="{
|
||||
name: 'UserDetail',
|
||||
params: { name: contributor.name },
|
||||
}"
|
||||
class="flex items-center"
|
||||
>
|
||||
<VAvatar
|
||||
v-tooltip="contributor.displayName"
|
||||
size="xs"
|
||||
:src="contributor.avatar"
|
||||
:alt="contributor.displayName"
|
||||
circle
|
||||
></VAvatar>
|
||||
</RouterLink>
|
||||
</template>
|
||||
</VEntityField>
|
||||
<VEntityField v-if="!post?.post?.spec.deleted">
|
||||
<template #description>
|
||||
<VStatusDot v-tooltip="`恢复中`" state="success" animate />
|
||||
</template>
|
||||
</VEntityField>
|
||||
<VEntityField v-if="post?.post?.metadata.deletionTimestamp">
|
||||
<template #description>
|
||||
<VStatusDot v-tooltip="`删除中`" state="warning" animate />
|
||||
</template>
|
||||
</VEntityField>
|
||||
<VEntityField>
|
||||
<template #description>
|
||||
<span class="truncate text-xs tabular-nums text-gray-500">
|
||||
{{ formatDatetime(post.post.spec.publishTime) }}
|
||||
</span>
|
||||
<span class="text-xs text-gray-500">
|
||||
评论 {{ post.stats.totalComment || 0 }}
|
||||
</span>
|
||||
</VSpace>
|
||||
</template>
|
||||
</VEntityField>
|
||||
</template>
|
||||
<template #end>
|
||||
<VEntityField>
|
||||
<template #description>
|
||||
<RouterLink
|
||||
v-for="(contributor, contributorIndex) in post.contributors"
|
||||
:key="contributorIndex"
|
||||
:to="{
|
||||
name: 'UserDetail',
|
||||
params: { name: contributor.name },
|
||||
}"
|
||||
class="flex items-center"
|
||||
>
|
||||
<VAvatar
|
||||
v-tooltip="contributor.displayName"
|
||||
size="xs"
|
||||
:src="contributor.avatar"
|
||||
:alt="contributor.displayName"
|
||||
circle
|
||||
></VAvatar>
|
||||
</RouterLink>
|
||||
</template>
|
||||
</VEntityField>
|
||||
<VEntityField v-if="!post?.post?.spec.deleted">
|
||||
<template #description>
|
||||
<VStatusDot v-tooltip="`恢复中`" state="success" animate />
|
||||
</template>
|
||||
</VEntityField>
|
||||
<VEntityField v-if="post?.post?.metadata.deletionTimestamp">
|
||||
<template #description>
|
||||
<VStatusDot v-tooltip="`删除中`" state="warning" animate />
|
||||
</template>
|
||||
</VEntityField>
|
||||
<VEntityField>
|
||||
<template #description>
|
||||
<span class="truncate text-xs tabular-nums text-gray-500">
|
||||
{{ formatDatetime(post.post.spec.publishTime) }}
|
||||
</span>
|
||||
</template>
|
||||
</VEntityField>
|
||||
</template>
|
||||
<template
|
||||
v-if="currentUserHasPermission(['system:posts:manage'])"
|
||||
#dropdownItems
|
||||
>
|
||||
<VButton
|
||||
v-close-popper
|
||||
block
|
||||
type="danger"
|
||||
@click="handleDeletePermanently(post.post)"
|
||||
</template>
|
||||
</VEntityField>
|
||||
</template>
|
||||
<template
|
||||
v-if="currentUserHasPermission(['system:posts:manage'])"
|
||||
#dropdownItems
|
||||
>
|
||||
永久删除
|
||||
</VButton>
|
||||
<VButton
|
||||
v-close-popper
|
||||
block
|
||||
type="default"
|
||||
@click="handleRecovery(post.post)"
|
||||
>
|
||||
恢复
|
||||
</VButton>
|
||||
</template>
|
||||
</VEntity>
|
||||
</li>
|
||||
</ul>
|
||||
<VButton
|
||||
v-close-popper
|
||||
block
|
||||
type="danger"
|
||||
@click="handleDeletePermanently(post.post)"
|
||||
>
|
||||
永久删除
|
||||
</VButton>
|
||||
<VButton
|
||||
v-close-popper
|
||||
block
|
||||
type="default"
|
||||
@click="handleRecovery(post.post)"
|
||||
>
|
||||
恢复
|
||||
</VButton>
|
||||
</template>
|
||||
</VEntity>
|
||||
</li>
|
||||
</ul>
|
||||
</Transition>
|
||||
|
||||
<template #footer>
|
||||
<div class="bg-white sm:flex sm:items-center sm:justify-end">
|
||||
|
|
|
@ -20,6 +20,7 @@ import {
|
|||
VStatusDot,
|
||||
VEntity,
|
||||
VEntityField,
|
||||
VLoading,
|
||||
} from "@halo-dev/components";
|
||||
import UserDropdownSelector from "@/components/dropdown-selector/UserDropdownSelector.vue";
|
||||
import PostSettingModal from "./components/PostSettingModal.vue";
|
||||
|
@ -823,183 +824,184 @@ const hasFilters = computed(() => {
|
|||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<VEmpty
|
||||
v-if="!posts.items.length && !loading"
|
||||
message="你可以尝试刷新或者新建文章"
|
||||
title="当前没有文章"
|
||||
>
|
||||
<template #actions>
|
||||
<VSpace>
|
||||
<VButton @click="handleFetchPosts">刷新</VButton>
|
||||
<VButton
|
||||
v-permission="['system:posts:manage']"
|
||||
:route="{ name: 'PostEditor' }"
|
||||
type="primary"
|
||||
>
|
||||
<template #icon>
|
||||
<IconAddCircle class="h-full w-full" />
|
||||
</template>
|
||||
新建文章
|
||||
</VButton>
|
||||
</VSpace>
|
||||
</template>
|
||||
</VEmpty>
|
||||
<ul
|
||||
v-else
|
||||
class="box-border h-full w-full divide-y divide-gray-100"
|
||||
role="list"
|
||||
>
|
||||
<li v-for="(post, index) in posts.items" :key="index">
|
||||
<VEntity :is-selected="checkSelection(post.post)">
|
||||
<template
|
||||
v-if="currentUserHasPermission(['system:posts:manage'])"
|
||||
#checkbox
|
||||
>
|
||||
<input
|
||||
v-model="selectedPostNames"
|
||||
:value="post.post.metadata.name"
|
||||
class="h-4 w-4 rounded border-gray-300 text-indigo-600"
|
||||
name="post-checkbox"
|
||||
type="checkbox"
|
||||
/>
|
||||
</template>
|
||||
<template #start>
|
||||
<VEntityField
|
||||
:title="post.post.spec.title"
|
||||
:route="{
|
||||
name: 'PostEditor',
|
||||
query: { name: post.post.metadata.name },
|
||||
}"
|
||||
<VLoading v-if="loading" />
|
||||
<Transition v-else-if="!posts.items.length" appear name="fade">
|
||||
<VEmpty message="你可以尝试刷新或者新建文章" title="当前没有文章">
|
||||
<template #actions>
|
||||
<VSpace>
|
||||
<VButton @click="handleFetchPosts">刷新</VButton>
|
||||
<VButton
|
||||
v-permission="['system:posts:manage']"
|
||||
:route="{ name: 'PostEditor' }"
|
||||
type="primary"
|
||||
>
|
||||
<template #extra>
|
||||
<VSpace class="mt-1 sm:mt-0">
|
||||
<template #icon>
|
||||
<IconAddCircle class="h-full w-full" />
|
||||
</template>
|
||||
新建文章
|
||||
</VButton>
|
||||
</VSpace>
|
||||
</template>
|
||||
</VEmpty>
|
||||
</Transition>
|
||||
<Transition v-else appear name="fade">
|
||||
<ul
|
||||
class="box-border h-full w-full divide-y divide-gray-100"
|
||||
role="list"
|
||||
>
|
||||
<li v-for="(post, index) in posts.items" :key="index">
|
||||
<VEntity :is-selected="checkSelection(post.post)">
|
||||
<template
|
||||
v-if="currentUserHasPermission(['system:posts:manage'])"
|
||||
#checkbox
|
||||
>
|
||||
<input
|
||||
v-model="selectedPostNames"
|
||||
:value="post.post.metadata.name"
|
||||
class="h-4 w-4 rounded border-gray-300 text-indigo-600"
|
||||
name="post-checkbox"
|
||||
type="checkbox"
|
||||
/>
|
||||
</template>
|
||||
<template #start>
|
||||
<VEntityField
|
||||
:title="post.post.spec.title"
|
||||
:route="{
|
||||
name: 'PostEditor',
|
||||
query: { name: post.post.metadata.name },
|
||||
}"
|
||||
>
|
||||
<template #extra>
|
||||
<VSpace class="mt-1 sm:mt-0">
|
||||
<RouterLink
|
||||
v-if="post.post.status?.inProgress"
|
||||
v-tooltip="`当前有内容已保存,但还未发布。`"
|
||||
:to="{
|
||||
name: 'PostEditor',
|
||||
query: { name: post.post.metadata.name },
|
||||
}"
|
||||
class="flex items-center"
|
||||
>
|
||||
<VStatusDot state="success" animate />
|
||||
</RouterLink>
|
||||
<PostTag
|
||||
v-for="(tag, tagIndex) in post.tags"
|
||||
:key="tagIndex"
|
||||
:tag="tag"
|
||||
route
|
||||
></PostTag>
|
||||
</VSpace>
|
||||
</template>
|
||||
<template #description>
|
||||
<VSpace>
|
||||
<p
|
||||
v-if="post.categories.length"
|
||||
class="inline-flex flex-wrap gap-1 text-xs text-gray-500"
|
||||
>
|
||||
分类:<span
|
||||
v-for="(category, categoryIndex) in post.categories"
|
||||
:key="categoryIndex"
|
||||
class="cursor-pointer hover:text-gray-900"
|
||||
>
|
||||
{{ category.spec.displayName }}
|
||||
</span>
|
||||
</p>
|
||||
<span class="text-xs text-gray-500">
|
||||
访问量 {{ post.stats.visit || 0 }}
|
||||
</span>
|
||||
<span class="text-xs text-gray-500">
|
||||
评论 {{ post.stats.totalComment || 0 }}
|
||||
</span>
|
||||
</VSpace>
|
||||
</template>
|
||||
</VEntityField>
|
||||
</template>
|
||||
<template #end>
|
||||
<VEntityField>
|
||||
<template #description>
|
||||
<RouterLink
|
||||
v-if="post.post.status?.inProgress"
|
||||
v-tooltip="`当前有内容已保存,但还未发布。`"
|
||||
v-for="(
|
||||
contributor, contributorIndex
|
||||
) in post.contributors"
|
||||
:key="contributorIndex"
|
||||
:to="{
|
||||
name: 'PostEditor',
|
||||
query: { name: post.post.metadata.name },
|
||||
name: 'UserDetail',
|
||||
params: { name: contributor.name },
|
||||
}"
|
||||
class="flex items-center"
|
||||
>
|
||||
<VStatusDot state="success" animate />
|
||||
<VAvatar
|
||||
v-tooltip="contributor.displayName"
|
||||
size="xs"
|
||||
:src="contributor.avatar"
|
||||
:alt="contributor.displayName"
|
||||
circle
|
||||
></VAvatar>
|
||||
</RouterLink>
|
||||
<PostTag
|
||||
v-for="(tag, tagIndex) in post.tags"
|
||||
:key="tagIndex"
|
||||
:tag="tag"
|
||||
route
|
||||
></PostTag>
|
||||
</VSpace>
|
||||
</template>
|
||||
<template #description>
|
||||
<VSpace>
|
||||
<p
|
||||
v-if="post.categories.length"
|
||||
class="inline-flex flex-wrap gap-1 text-xs text-gray-500"
|
||||
>
|
||||
分类:<span
|
||||
v-for="(category, categoryIndex) in post.categories"
|
||||
:key="categoryIndex"
|
||||
class="cursor-pointer hover:text-gray-900"
|
||||
>
|
||||
{{ category.spec.displayName }}
|
||||
</span>
|
||||
</p>
|
||||
<span class="text-xs text-gray-500">
|
||||
访问量 {{ post.stats.visit || 0 }}
|
||||
</template>
|
||||
</VEntityField>
|
||||
<VEntityField :description="getPublishStatus(post.post)">
|
||||
<template v-if="isPublishing(post.post)" #description>
|
||||
<VStatusDot text="发布中" animate />
|
||||
</template>
|
||||
</VEntityField>
|
||||
<VEntityField>
|
||||
<template #description>
|
||||
<IconEye
|
||||
v-if="post.post.spec.visible === 'PUBLIC'"
|
||||
v-tooltip="`公开访问`"
|
||||
class="cursor-pointer text-sm transition-all hover:text-blue-600"
|
||||
/>
|
||||
<IconEyeOff
|
||||
v-if="post.post.spec.visible === 'PRIVATE'"
|
||||
v-tooltip="`私有访问`"
|
||||
class="cursor-pointer text-sm transition-all hover:text-blue-600"
|
||||
/>
|
||||
<IconTeam
|
||||
v-if="post.post.spec.visible === 'INTERNAL'"
|
||||
v-tooltip="`内部成员可访问`"
|
||||
class="cursor-pointer text-sm transition-all hover:text-blue-600"
|
||||
/>
|
||||
</template>
|
||||
</VEntityField>
|
||||
<VEntityField v-if="post?.post?.spec.deleted">
|
||||
<template #description>
|
||||
<VStatusDot v-tooltip="`删除中`" state="warning" animate />
|
||||
</template>
|
||||
</VEntityField>
|
||||
<VEntityField>
|
||||
<template #description>
|
||||
<span class="truncate text-xs tabular-nums text-gray-500">
|
||||
{{ formatDatetime(post.post.spec.publishTime) }}
|
||||
</span>
|
||||
<span class="text-xs text-gray-500">
|
||||
评论 {{ post.stats.totalComment || 0 }}
|
||||
</span>
|
||||
</VSpace>
|
||||
</template>
|
||||
</VEntityField>
|
||||
</template>
|
||||
<template #end>
|
||||
<VEntityField>
|
||||
<template #description>
|
||||
<RouterLink
|
||||
v-for="(contributor, contributorIndex) in post.contributors"
|
||||
:key="contributorIndex"
|
||||
:to="{
|
||||
name: 'UserDetail',
|
||||
params: { name: contributor.name },
|
||||
}"
|
||||
class="flex items-center"
|
||||
>
|
||||
<VAvatar
|
||||
v-tooltip="contributor.displayName"
|
||||
size="xs"
|
||||
:src="contributor.avatar"
|
||||
:alt="contributor.displayName"
|
||||
circle
|
||||
></VAvatar>
|
||||
</RouterLink>
|
||||
</template>
|
||||
</VEntityField>
|
||||
<VEntityField :description="getPublishStatus(post.post)">
|
||||
<template v-if="isPublishing(post.post)" #description>
|
||||
<VStatusDot text="发布中" animate />
|
||||
</template>
|
||||
</VEntityField>
|
||||
<VEntityField>
|
||||
<template #description>
|
||||
<IconEye
|
||||
v-if="post.post.spec.visible === 'PUBLIC'"
|
||||
v-tooltip="`公开访问`"
|
||||
class="cursor-pointer text-sm transition-all hover:text-blue-600"
|
||||
/>
|
||||
<IconEyeOff
|
||||
v-if="post.post.spec.visible === 'PRIVATE'"
|
||||
v-tooltip="`私有访问`"
|
||||
class="cursor-pointer text-sm transition-all hover:text-blue-600"
|
||||
/>
|
||||
<IconTeam
|
||||
v-if="post.post.spec.visible === 'INTERNAL'"
|
||||
v-tooltip="`内部成员可访问`"
|
||||
class="cursor-pointer text-sm transition-all hover:text-blue-600"
|
||||
/>
|
||||
</template>
|
||||
</VEntityField>
|
||||
<VEntityField v-if="post?.post?.spec.deleted">
|
||||
<template #description>
|
||||
<VStatusDot v-tooltip="`删除中`" state="warning" animate />
|
||||
</template>
|
||||
</VEntityField>
|
||||
<VEntityField>
|
||||
<template #description>
|
||||
<span class="truncate text-xs tabular-nums text-gray-500">
|
||||
{{ formatDatetime(post.post.spec.publishTime) }}
|
||||
</span>
|
||||
</template>
|
||||
</VEntityField>
|
||||
</template>
|
||||
<template
|
||||
v-if="currentUserHasPermission(['system:posts:manage'])"
|
||||
#dropdownItems
|
||||
>
|
||||
<VButton
|
||||
v-close-popper
|
||||
block
|
||||
type="secondary"
|
||||
@click="handleOpenSettingModal(post.post)"
|
||||
</template>
|
||||
</VEntityField>
|
||||
</template>
|
||||
<template
|
||||
v-if="currentUserHasPermission(['system:posts:manage'])"
|
||||
#dropdownItems
|
||||
>
|
||||
设置
|
||||
</VButton>
|
||||
<VButton
|
||||
v-close-popper
|
||||
block
|
||||
type="danger"
|
||||
@click="handleDelete(post.post)"
|
||||
>
|
||||
删除
|
||||
</VButton>
|
||||
</template>
|
||||
</VEntity>
|
||||
</li>
|
||||
</ul>
|
||||
<VButton
|
||||
v-close-popper
|
||||
block
|
||||
type="secondary"
|
||||
@click="handleOpenSettingModal(post.post)"
|
||||
>
|
||||
设置
|
||||
</VButton>
|
||||
<VButton
|
||||
v-close-popper
|
||||
block
|
||||
type="danger"
|
||||
@click="handleDelete(post.post)"
|
||||
>
|
||||
删除
|
||||
</VButton>
|
||||
</template>
|
||||
</VEntity>
|
||||
</li>
|
||||
</ul>
|
||||
</Transition>
|
||||
|
||||
<template #footer>
|
||||
<div class="bg-white sm:flex sm:items-center sm:justify-end">
|
||||
|
|
|
@ -12,6 +12,7 @@ import {
|
|||
VEmpty,
|
||||
VPageHeader,
|
||||
VSpace,
|
||||
VLoading,
|
||||
} from "@halo-dev/components";
|
||||
import CategoryEditingModal from "./components/CategoryEditingModal.vue";
|
||||
import CategoryListItem from "./components/CategoryListItem.vue";
|
||||
|
@ -111,34 +112,34 @@ const onEditingModalClose = () => {
|
|||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<VEmpty
|
||||
v-if="!categories.length && !loading"
|
||||
message="你可以尝试刷新或者新建分类"
|
||||
title="当前没有分类"
|
||||
>
|
||||
<template #actions>
|
||||
<VSpace>
|
||||
<VButton @click="handleFetchCategories">刷新</VButton>
|
||||
<VButton
|
||||
v-permission="['system:posts:manage']"
|
||||
type="primary"
|
||||
@click="editingModal = true"
|
||||
>
|
||||
<template #icon>
|
||||
<IconAddCircle class="h-full w-full" />
|
||||
</template>
|
||||
新建分类
|
||||
</VButton>
|
||||
</VSpace>
|
||||
</template>
|
||||
</VEmpty>
|
||||
<CategoryListItem
|
||||
v-else
|
||||
:categories="categoriesTree"
|
||||
@change="handleUpdateInBatch"
|
||||
@delete="handleDelete"
|
||||
@open-editing="handleOpenEditingModal"
|
||||
/>
|
||||
<VLoading v-if="loading" />
|
||||
<Transition v-else-if="!categories.length" appear name="fade">
|
||||
<VEmpty message="你可以尝试刷新或者新建分类" title="当前没有分类">
|
||||
<template #actions>
|
||||
<VSpace>
|
||||
<VButton @click="handleFetchCategories">刷新</VButton>
|
||||
<VButton
|
||||
v-permission="['system:posts:manage']"
|
||||
type="primary"
|
||||
@click="editingModal = true"
|
||||
>
|
||||
<template #icon>
|
||||
<IconAddCircle class="h-full w-full" />
|
||||
</template>
|
||||
新建分类
|
||||
</VButton>
|
||||
</VSpace>
|
||||
</template>
|
||||
</VEmpty>
|
||||
</Transition>
|
||||
<Transition v-else appear name="fade">
|
||||
<CategoryListItem
|
||||
:categories="categoriesTree"
|
||||
@change="handleUpdateInBatch"
|
||||
@delete="handleDelete"
|
||||
@open-editing="handleOpenEditingModal"
|
||||
/>
|
||||
</Transition>
|
||||
</VCard>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -16,6 +16,7 @@ import {
|
|||
VStatusDot,
|
||||
VEntity,
|
||||
VEntityField,
|
||||
VLoading,
|
||||
} from "@halo-dev/components";
|
||||
import TagEditingModal from "./components/TagEditingModal.vue";
|
||||
import PostTag from "./components/PostTag.vue";
|
||||
|
@ -158,92 +159,99 @@ onMounted(async () => {
|
|||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<VEmpty
|
||||
v-if="!tags.length && !loading"
|
||||
message="你可以尝试刷新或者新建标签"
|
||||
title="当前没有标签"
|
||||
>
|
||||
<template #actions>
|
||||
<VSpace>
|
||||
<VButton @click="handleFetchTags">刷新</VButton>
|
||||
<VButton type="primary" @click="editingModal = true">
|
||||
<template #icon>
|
||||
<IconAddCircle class="h-full w-full" />
|
||||
</template>
|
||||
新建标签
|
||||
</VButton>
|
||||
</VSpace>
|
||||
</template>
|
||||
</VEmpty>
|
||||
<div v-else>
|
||||
<ul
|
||||
v-if="viewType === 'list'"
|
||||
class="box-border h-full w-full divide-y divide-gray-100"
|
||||
role="list"
|
||||
>
|
||||
<li v-for="(tag, index) in tags" :key="index">
|
||||
<VEntity
|
||||
:is-selected="selectedTag?.metadata.name === tag.metadata.name"
|
||||
>
|
||||
<template #start>
|
||||
<VEntityField :description="tag.status?.permalink">
|
||||
<template #title>
|
||||
<PostTag :tag="tag" />
|
||||
</template>
|
||||
</VEntityField>
|
||||
</template>
|
||||
<template #end>
|
||||
<VEntityField v-if="tag.metadata.deletionTimestamp">
|
||||
<template #description>
|
||||
<VStatusDot v-tooltip="`删除中`" state="warning" animate />
|
||||
</template>
|
||||
</VEntityField>
|
||||
<VEntityField
|
||||
:description="`${tag.status?.postCount || 0} 篇文章`"
|
||||
/>
|
||||
<VEntityField>
|
||||
<template #description>
|
||||
<span class="truncate text-xs tabular-nums text-gray-500">
|
||||
{{ formatDatetime(tag.metadata.creationTimestamp) }}
|
||||
</span>
|
||||
</template>
|
||||
</VEntityField>
|
||||
</template>
|
||||
<template
|
||||
v-if="currentUserHasPermission(['system:posts:manage'])"
|
||||
#dropdownItems
|
||||
>
|
||||
<VButton
|
||||
v-permission="['system:posts:manage']"
|
||||
v-close-popper
|
||||
block
|
||||
type="secondary"
|
||||
@click="handleOpenEditingModal(tag)"
|
||||
>
|
||||
修改
|
||||
</VButton>
|
||||
<VButton
|
||||
v-permission="['system:posts:manage']"
|
||||
v-close-popper
|
||||
block
|
||||
type="danger"
|
||||
@click="handleDelete(tag)"
|
||||
>
|
||||
删除
|
||||
</VButton>
|
||||
</template>
|
||||
</VEntity>
|
||||
</li>
|
||||
</ul>
|
||||
<VLoading v-if="loading" />
|
||||
<Transition v-else-if="!tags.length" appear name="fade">
|
||||
<VEmpty message="你可以尝试刷新或者新建标签" title="当前没有标签">
|
||||
<template #actions>
|
||||
<VSpace>
|
||||
<VButton @click="handleFetchTags">刷新</VButton>
|
||||
<VButton type="primary" @click="editingModal = true">
|
||||
<template #icon>
|
||||
<IconAddCircle class="h-full w-full" />
|
||||
</template>
|
||||
新建标签
|
||||
</VButton>
|
||||
</VSpace>
|
||||
</template>
|
||||
</VEmpty>
|
||||
</Transition>
|
||||
|
||||
<div v-else class="flex flex-wrap gap-3 p-4" role="list">
|
||||
<PostTag
|
||||
v-for="(tag, index) in tags"
|
||||
:key="index"
|
||||
:tag="tag"
|
||||
@click="handleOpenEditingModal(tag)"
|
||||
/>
|
||||
</div>
|
||||
<div v-else>
|
||||
<Transition v-if="viewType === 'list'" appear name="fade">
|
||||
<ul
|
||||
class="box-border h-full w-full divide-y divide-gray-100"
|
||||
role="list"
|
||||
>
|
||||
<li v-for="(tag, index) in tags" :key="index">
|
||||
<VEntity
|
||||
:is-selected="selectedTag?.metadata.name === tag.metadata.name"
|
||||
>
|
||||
<template #start>
|
||||
<VEntityField :description="tag.status?.permalink">
|
||||
<template #title>
|
||||
<PostTag :tag="tag" />
|
||||
</template>
|
||||
</VEntityField>
|
||||
</template>
|
||||
<template #end>
|
||||
<VEntityField v-if="tag.metadata.deletionTimestamp">
|
||||
<template #description>
|
||||
<VStatusDot
|
||||
v-tooltip="`删除中`"
|
||||
state="warning"
|
||||
animate
|
||||
/>
|
||||
</template>
|
||||
</VEntityField>
|
||||
<VEntityField
|
||||
:description="`${tag.status?.postCount || 0} 篇文章`"
|
||||
/>
|
||||
<VEntityField>
|
||||
<template #description>
|
||||
<span class="truncate text-xs tabular-nums text-gray-500">
|
||||
{{ formatDatetime(tag.metadata.creationTimestamp) }}
|
||||
</span>
|
||||
</template>
|
||||
</VEntityField>
|
||||
</template>
|
||||
<template
|
||||
v-if="currentUserHasPermission(['system:posts:manage'])"
|
||||
#dropdownItems
|
||||
>
|
||||
<VButton
|
||||
v-permission="['system:posts:manage']"
|
||||
v-close-popper
|
||||
block
|
||||
type="secondary"
|
||||
@click="handleOpenEditingModal(tag)"
|
||||
>
|
||||
修改
|
||||
</VButton>
|
||||
<VButton
|
||||
v-permission="['system:posts:manage']"
|
||||
v-close-popper
|
||||
block
|
||||
type="danger"
|
||||
@click="handleDelete(tag)"
|
||||
>
|
||||
删除
|
||||
</VButton>
|
||||
</template>
|
||||
</VEntity>
|
||||
</li>
|
||||
</ul>
|
||||
</Transition>
|
||||
|
||||
<Transition v-else appear name="fade">
|
||||
<div class="flex flex-wrap gap-3 p-4" role="list">
|
||||
<PostTag
|
||||
v-for="(tag, index) in tags"
|
||||
:key="index"
|
||||
:tag="tag"
|
||||
@click="handleOpenEditingModal(tag)"
|
||||
/>
|
||||
</div>
|
||||
</Transition>
|
||||
</div>
|
||||
</VCard>
|
||||
</div>
|
||||
|
|
|
@ -8,6 +8,7 @@ import {
|
|||
VEmpty,
|
||||
VPageHeader,
|
||||
VSpace,
|
||||
VLoading,
|
||||
} from "@halo-dev/components";
|
||||
import MenuItemEditingModal from "./components/MenuItemEditingModal.vue";
|
||||
import MenuItemListItem from "./components/MenuItemListItem.vue";
|
||||
|
@ -220,35 +221,38 @@ const handleDelete = async (menuItem: MenuTreeItem) => {
|
|||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<VEmpty
|
||||
v-if="!menuItems.length && !loading"
|
||||
message="你可以尝试刷新或者新建菜单项"
|
||||
title="当前没有菜单项"
|
||||
>
|
||||
<template #actions>
|
||||
<VSpace>
|
||||
<VButton @click="handleFetchMenuItems"> 刷新</VButton>
|
||||
<VButton
|
||||
v-permission="['system:menus:manage']"
|
||||
type="primary"
|
||||
@click="menuItemEditingModal = true"
|
||||
>
|
||||
<template #icon>
|
||||
<IconAddCircle class="h-full w-full" />
|
||||
</template>
|
||||
新增菜单项
|
||||
</VButton>
|
||||
</VSpace>
|
||||
</template>
|
||||
</VEmpty>
|
||||
<MenuItemListItem
|
||||
v-else
|
||||
:menu-tree-items="menuTreeItems"
|
||||
@change="handleUpdateInBatch"
|
||||
@delete="handleDelete"
|
||||
@open-editing="handleOpenEditingModal"
|
||||
@open-create-by-parent="handleOpenCreateByParentModal"
|
||||
/>
|
||||
<VLoading v-if="loading" />
|
||||
<Transition v-else-if="!menuItems.length" appear name="fade">
|
||||
<VEmpty
|
||||
message="你可以尝试刷新或者新建菜单项"
|
||||
title="当前没有菜单项"
|
||||
>
|
||||
<template #actions>
|
||||
<VSpace>
|
||||
<VButton @click="handleFetchMenuItems"> 刷新</VButton>
|
||||
<VButton
|
||||
v-permission="['system:menus:manage']"
|
||||
type="primary"
|
||||
@click="menuItemEditingModal = true"
|
||||
>
|
||||
<template #icon>
|
||||
<IconAddCircle class="h-full w-full" />
|
||||
</template>
|
||||
新增菜单项
|
||||
</VButton>
|
||||
</VSpace>
|
||||
</template>
|
||||
</VEmpty>
|
||||
</Transition>
|
||||
<Transition v-else appear name="fade">
|
||||
<MenuItemListItem
|
||||
:menu-tree-items="menuTreeItems"
|
||||
@change="handleUpdateInBatch"
|
||||
@delete="handleDelete"
|
||||
@open-editing="handleOpenEditingModal"
|
||||
@open-create-by-parent="handleOpenCreateByParentModal"
|
||||
/>
|
||||
</Transition>
|
||||
</VCard>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -9,6 +9,7 @@ import {
|
|||
VEntity,
|
||||
VEntityField,
|
||||
VTag,
|
||||
VLoading,
|
||||
} from "@halo-dev/components";
|
||||
import MenuEditingModal from "./MenuEditingModal.vue";
|
||||
import { onMounted, onUnmounted, ref } from "vue";
|
||||
|
@ -190,75 +191,76 @@ onMounted(handleFetchPrimaryMenuName);
|
|||
@created="handleSelect"
|
||||
/>
|
||||
<VCard :body-class="['!p-0']" title="菜单">
|
||||
<VEmpty
|
||||
v-if="!menus.length && !loading"
|
||||
message="你可以尝试刷新或者新建菜单"
|
||||
title="当前没有菜单"
|
||||
>
|
||||
<template #actions>
|
||||
<VSpace>
|
||||
<VButton size="sm" @click="handleFetchMenus"> 刷新</VButton>
|
||||
</VSpace>
|
||||
</template>
|
||||
</VEmpty>
|
||||
<ul class="box-border h-full w-full divide-y divide-gray-100" role="list">
|
||||
<li
|
||||
v-for="(menu, index) in menus"
|
||||
:key="index"
|
||||
@click="handleSelect(menu)"
|
||||
>
|
||||
<VEntity
|
||||
:is-selected="selectedMenu?.metadata.name === menu.metadata.name"
|
||||
<VLoading v-if="loading" />
|
||||
<Transition v-else-if="!menus.length" appear name="fade">
|
||||
<VEmpty message="你可以尝试刷新或者新建菜单" title="当前没有菜单">
|
||||
<template #actions>
|
||||
<VSpace>
|
||||
<VButton size="sm" @click="handleFetchMenus"> 刷新</VButton>
|
||||
</VSpace>
|
||||
</template>
|
||||
</VEmpty>
|
||||
</Transition>
|
||||
<Transition v-else appear name="fade">
|
||||
<ul class="box-border h-full w-full divide-y divide-gray-100" role="list">
|
||||
<li
|
||||
v-for="(menu, index) in menus"
|
||||
:key="index"
|
||||
@click="handleSelect(menu)"
|
||||
>
|
||||
<template #start>
|
||||
<VEntityField
|
||||
:title="menu.spec?.displayName"
|
||||
:description="`${menu.spec.menuItems?.length || 0} 个菜单项`"
|
||||
>
|
||||
<template v-if="menu.metadata.name === primaryMenuName" #extra>
|
||||
<VTag>主菜单</VTag>
|
||||
</template>
|
||||
</VEntityField>
|
||||
</template>
|
||||
<template #end>
|
||||
<VEntityField v-if="menu.metadata.deletionTimestamp">
|
||||
<template #description>
|
||||
<VStatusDot v-tooltip="`删除中`" state="warning" animate />
|
||||
</template>
|
||||
</VEntityField>
|
||||
</template>
|
||||
<template
|
||||
v-if="currentUserHasPermission(['system:menus:manage'])"
|
||||
#dropdownItems
|
||||
<VEntity
|
||||
:is-selected="selectedMenu?.metadata.name === menu.metadata.name"
|
||||
>
|
||||
<VButton
|
||||
v-close-popper
|
||||
block
|
||||
type="secondary"
|
||||
@click="handleSetPrimaryMenu(menu)"
|
||||
<template #start>
|
||||
<VEntityField
|
||||
:title="menu.spec?.displayName"
|
||||
:description="`${menu.spec.menuItems?.length || 0} 个菜单项`"
|
||||
>
|
||||
<template v-if="menu.metadata.name === primaryMenuName" #extra>
|
||||
<VTag>主菜单</VTag>
|
||||
</template>
|
||||
</VEntityField>
|
||||
</template>
|
||||
<template #end>
|
||||
<VEntityField v-if="menu.metadata.deletionTimestamp">
|
||||
<template #description>
|
||||
<VStatusDot v-tooltip="`删除中`" state="warning" animate />
|
||||
</template>
|
||||
</VEntityField>
|
||||
</template>
|
||||
<template
|
||||
v-if="currentUserHasPermission(['system:menus:manage'])"
|
||||
#dropdownItems
|
||||
>
|
||||
设置为主菜单
|
||||
</VButton>
|
||||
<VButton
|
||||
v-close-popper
|
||||
block
|
||||
type="default"
|
||||
@click="handleOpenEditingModal(menu)"
|
||||
>
|
||||
修改
|
||||
</VButton>
|
||||
<VButton
|
||||
v-close-popper
|
||||
block
|
||||
type="danger"
|
||||
@click="handleDeleteMenu(menu)"
|
||||
>
|
||||
删除
|
||||
</VButton>
|
||||
</template>
|
||||
</VEntity>
|
||||
</li>
|
||||
</ul>
|
||||
<VButton
|
||||
v-close-popper
|
||||
block
|
||||
type="secondary"
|
||||
@click="handleSetPrimaryMenu(menu)"
|
||||
>
|
||||
设置为主菜单
|
||||
</VButton>
|
||||
<VButton
|
||||
v-close-popper
|
||||
block
|
||||
type="default"
|
||||
@click="handleOpenEditingModal(menu)"
|
||||
>
|
||||
修改
|
||||
</VButton>
|
||||
<VButton
|
||||
v-close-popper
|
||||
block
|
||||
type="danger"
|
||||
@click="handleDeleteMenu(menu)"
|
||||
>
|
||||
删除
|
||||
</VButton>
|
||||
</template>
|
||||
</VEntity>
|
||||
</li>
|
||||
</ul>
|
||||
</Transition>
|
||||
<template v-if="currentUserHasPermission(['system:menus:manage'])" #footer>
|
||||
<VButton block type="secondary" @click="handleOpenEditingModal()">
|
||||
新增
|
||||
|
|
|
@ -55,170 +55,177 @@ const onUpgradeModalClose = () => {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<div class="bg-white px-4 py-4 sm:px-6">
|
||||
<div class="group flex items-center justify-between">
|
||||
<div class="flex flex-row items-center gap-3">
|
||||
<VAvatar
|
||||
:key="selectedTheme?.metadata.name"
|
||||
:alt="selectedTheme?.spec.displayName"
|
||||
:src="selectedTheme?.spec.logo"
|
||||
size="lg"
|
||||
/>
|
||||
<div>
|
||||
<h3 class="text-lg font-medium leading-6 text-gray-900">
|
||||
{{ selectedTheme?.spec.displayName }}
|
||||
</h3>
|
||||
<p class="mt-1 flex max-w-2xl items-center gap-2">
|
||||
<span class="text-sm text-gray-500">
|
||||
{{ selectedTheme?.spec.version }}
|
||||
</span>
|
||||
<VTag>
|
||||
{{ isActivated ? "当前启用" : "未启用" }}
|
||||
</VTag>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<FloatingDropdown v-permission="['system:themes:manage']">
|
||||
<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">
|
||||
<VButton
|
||||
v-close-popper
|
||||
block
|
||||
type="secondary"
|
||||
@click="upgradeModal = true"
|
||||
>
|
||||
更新
|
||||
</VButton>
|
||||
<VButton
|
||||
v-close-popper
|
||||
block
|
||||
type="default"
|
||||
@click="handleReloadThemeSetting"
|
||||
>
|
||||
刷新设置表单
|
||||
</VButton>
|
||||
</VSpace>
|
||||
</div>
|
||||
</template>
|
||||
</FloatingDropdown>
|
||||
</div>
|
||||
</div>
|
||||
<div class="border-t border-gray-200">
|
||||
<dl class="divide-y divide-gray-100">
|
||||
<div
|
||||
class="bg-white px-4 py-5 hover:bg-gray-50 sm:grid sm:grid-cols-6 sm:gap-4 sm:px-6"
|
||||
>
|
||||
<dt class="text-sm font-medium text-gray-900">ID</dt>
|
||||
<dd class="mt-1 text-sm text-gray-900 sm:col-span-3 sm:mt-0">
|
||||
{{ selectedTheme?.metadata.name }}
|
||||
</dd>
|
||||
</div>
|
||||
<div
|
||||
class="bg-white px-4 py-5 hover:bg-gray-50 sm:grid sm:grid-cols-6 sm:gap-4 sm:px-6"
|
||||
>
|
||||
<dt class="text-sm font-medium text-gray-900">作者</dt>
|
||||
<dd class="mt-1 text-sm text-gray-900 sm:col-span-3 sm:mt-0">
|
||||
{{ selectedTheme?.spec.author.name }}
|
||||
</dd>
|
||||
</div>
|
||||
<div
|
||||
class="bg-white px-4 py-5 hover:bg-gray-50 sm:grid sm:grid-cols-6 sm:gap-4 sm:px-6"
|
||||
>
|
||||
<dt class="text-sm font-medium text-gray-900">网站</dt>
|
||||
<dd class="mt-1 text-sm text-gray-900 sm:col-span-3 sm:mt-0">
|
||||
<a :href="selectedTheme?.spec.website" target="_blank">
|
||||
{{ selectedTheme?.spec.website }}
|
||||
</a>
|
||||
</dd>
|
||||
</div>
|
||||
<div
|
||||
class="bg-white px-4 py-5 hover:bg-gray-50 sm:grid sm:grid-cols-6 sm:gap-4 sm:px-6"
|
||||
>
|
||||
<dt class="text-sm font-medium text-gray-900">源码仓库</dt>
|
||||
<dd class="mt-1 text-sm text-gray-900 sm:col-span-3 sm:mt-0">
|
||||
<a :href="selectedTheme?.spec.repo" target="_blank">
|
||||
{{ selectedTheme?.spec.repo }}
|
||||
</a>
|
||||
</dd>
|
||||
</div>
|
||||
<div
|
||||
class="bg-white px-4 py-5 hover:bg-gray-50 sm:grid sm:grid-cols-6 sm:gap-4 sm:px-6"
|
||||
>
|
||||
<dt class="text-sm font-medium text-gray-900">当前版本</dt>
|
||||
<dd class="mt-1 text-sm text-gray-900 sm:col-span-3 sm:mt-0">
|
||||
{{ selectedTheme?.spec.version }}
|
||||
</dd>
|
||||
</div>
|
||||
<div
|
||||
class="bg-white px-4 py-5 hover:bg-gray-50 sm:grid sm:grid-cols-6 sm:gap-4 sm:px-6"
|
||||
>
|
||||
<dt class="text-sm font-medium text-gray-900">Halo 版本要求</dt>
|
||||
<dd class="mt-1 text-sm text-gray-900 sm:col-span-3 sm:mt-0">
|
||||
{{ selectedTheme?.spec.require }}
|
||||
</dd>
|
||||
</div>
|
||||
<div
|
||||
class="bg-white px-4 py-5 hover:bg-gray-50 sm:grid sm:grid-cols-6 sm:gap-4 sm:px-6"
|
||||
>
|
||||
<dt class="text-sm font-medium text-gray-900">存储位置</dt>
|
||||
<dd class="mt-1 text-sm text-gray-900 sm:col-span-3 sm:mt-0">
|
||||
{{ selectedTheme?.status?.location }}
|
||||
</dd>
|
||||
</div>
|
||||
<!-- TODO: add display required plugins support -->
|
||||
<div
|
||||
v-if="false"
|
||||
class="bg-white px-4 py-5 hover:bg-gray-50 sm:grid sm:grid-cols-6 sm:gap-4 sm:px-6"
|
||||
>
|
||||
<dt class="text-sm font-medium text-gray-900">插件依赖</dt>
|
||||
<dd class="mt-1 text-sm sm:col-span-3 sm:mt-0">
|
||||
<VAlert description="当前有 1 个插件还未安装" title="提示"></VAlert>
|
||||
<ul class="mt-2 space-y-2">
|
||||
<li>
|
||||
<div
|
||||
class="inline-flex w-96 cursor-pointer flex-col gap-y-3 rounded border p-5 hover:border-primary"
|
||||
>
|
||||
<RouterLink
|
||||
:to="{
|
||||
name: 'PluginDetail',
|
||||
params: { name: 'PluginLinks' },
|
||||
}"
|
||||
class="font-medium text-gray-900 hover:text-blue-400"
|
||||
>
|
||||
run.halo.plugins.links
|
||||
</RouterLink>
|
||||
<div class="text-xs">
|
||||
<VSpace>
|
||||
<VTag> 已安装</VTag>
|
||||
</VSpace>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<div
|
||||
class="inline-flex w-96 cursor-pointer flex-col gap-y-3 rounded border p-5 hover:border-primary"
|
||||
>
|
||||
<span class="font-medium hover:text-blue-400">
|
||||
run.halo.plugins.photos
|
||||
<Transition appear mode="out-in" name="fade">
|
||||
<div>
|
||||
<div class="bg-white px-4 py-4 sm:px-6">
|
||||
<div class="group flex items-center justify-between">
|
||||
<div class="flex flex-row items-center gap-3">
|
||||
<VAvatar
|
||||
:key="selectedTheme?.metadata.name"
|
||||
:alt="selectedTheme?.spec.displayName"
|
||||
:src="selectedTheme?.spec.logo"
|
||||
size="lg"
|
||||
/>
|
||||
<div>
|
||||
<h3 class="text-lg font-medium leading-6 text-gray-900">
|
||||
{{ selectedTheme?.spec.displayName }}
|
||||
</h3>
|
||||
<p class="mt-1 flex max-w-2xl items-center gap-2">
|
||||
<span class="text-sm text-gray-500">
|
||||
{{ selectedTheme?.spec.version }}
|
||||
</span>
|
||||
<div class="text-xs">
|
||||
<VSpace>
|
||||
<VTag>未安装</VTag>
|
||||
</VSpace>
|
||||
</div>
|
||||
<VTag>
|
||||
{{ isActivated ? "当前启用" : "未启用" }}
|
||||
</VTag>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<FloatingDropdown v-permission="['system:themes:manage']">
|
||||
<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">
|
||||
<VButton
|
||||
v-close-popper
|
||||
block
|
||||
type="secondary"
|
||||
@click="upgradeModal = true"
|
||||
>
|
||||
更新
|
||||
</VButton>
|
||||
<VButton
|
||||
v-close-popper
|
||||
block
|
||||
type="default"
|
||||
@click="handleReloadThemeSetting"
|
||||
>
|
||||
刷新设置表单
|
||||
</VButton>
|
||||
</VSpace>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</dd>
|
||||
</template>
|
||||
</FloatingDropdown>
|
||||
</div>
|
||||
</div>
|
||||
</dl>
|
||||
</div>
|
||||
<div class="border-t border-gray-200">
|
||||
<dl class="divide-y divide-gray-100">
|
||||
<div
|
||||
class="bg-white px-4 py-5 hover:bg-gray-50 sm:grid sm:grid-cols-6 sm:gap-4 sm:px-6"
|
||||
>
|
||||
<dt class="text-sm font-medium text-gray-900">ID</dt>
|
||||
<dd class="mt-1 text-sm text-gray-900 sm:col-span-3 sm:mt-0">
|
||||
{{ selectedTheme?.metadata.name }}
|
||||
</dd>
|
||||
</div>
|
||||
<div
|
||||
class="bg-white px-4 py-5 hover:bg-gray-50 sm:grid sm:grid-cols-6 sm:gap-4 sm:px-6"
|
||||
>
|
||||
<dt class="text-sm font-medium text-gray-900">作者</dt>
|
||||
<dd class="mt-1 text-sm text-gray-900 sm:col-span-3 sm:mt-0">
|
||||
{{ selectedTheme?.spec.author.name }}
|
||||
</dd>
|
||||
</div>
|
||||
<div
|
||||
class="bg-white px-4 py-5 hover:bg-gray-50 sm:grid sm:grid-cols-6 sm:gap-4 sm:px-6"
|
||||
>
|
||||
<dt class="text-sm font-medium text-gray-900">网站</dt>
|
||||
<dd class="mt-1 text-sm text-gray-900 sm:col-span-3 sm:mt-0">
|
||||
<a :href="selectedTheme?.spec.website" target="_blank">
|
||||
{{ selectedTheme?.spec.website }}
|
||||
</a>
|
||||
</dd>
|
||||
</div>
|
||||
<div
|
||||
class="bg-white px-4 py-5 hover:bg-gray-50 sm:grid sm:grid-cols-6 sm:gap-4 sm:px-6"
|
||||
>
|
||||
<dt class="text-sm font-medium text-gray-900">源码仓库</dt>
|
||||
<dd class="mt-1 text-sm text-gray-900 sm:col-span-3 sm:mt-0">
|
||||
<a :href="selectedTheme?.spec.repo" target="_blank">
|
||||
{{ selectedTheme?.spec.repo }}
|
||||
</a>
|
||||
</dd>
|
||||
</div>
|
||||
<div
|
||||
class="bg-white px-4 py-5 hover:bg-gray-50 sm:grid sm:grid-cols-6 sm:gap-4 sm:px-6"
|
||||
>
|
||||
<dt class="text-sm font-medium text-gray-900">当前版本</dt>
|
||||
<dd class="mt-1 text-sm text-gray-900 sm:col-span-3 sm:mt-0">
|
||||
{{ selectedTheme?.spec.version }}
|
||||
</dd>
|
||||
</div>
|
||||
<div
|
||||
class="bg-white px-4 py-5 hover:bg-gray-50 sm:grid sm:grid-cols-6 sm:gap-4 sm:px-6"
|
||||
>
|
||||
<dt class="text-sm font-medium text-gray-900">Halo 版本要求</dt>
|
||||
<dd class="mt-1 text-sm text-gray-900 sm:col-span-3 sm:mt-0">
|
||||
{{ selectedTheme?.spec.require }}
|
||||
</dd>
|
||||
</div>
|
||||
<div
|
||||
class="bg-white px-4 py-5 hover:bg-gray-50 sm:grid sm:grid-cols-6 sm:gap-4 sm:px-6"
|
||||
>
|
||||
<dt class="text-sm font-medium text-gray-900">存储位置</dt>
|
||||
<dd class="mt-1 text-sm text-gray-900 sm:col-span-3 sm:mt-0">
|
||||
{{ selectedTheme?.status?.location }}
|
||||
</dd>
|
||||
</div>
|
||||
<!-- TODO: add display required plugins support -->
|
||||
<div
|
||||
v-if="false"
|
||||
class="bg-white px-4 py-5 hover:bg-gray-50 sm:grid sm:grid-cols-6 sm:gap-4 sm:px-6"
|
||||
>
|
||||
<dt class="text-sm font-medium text-gray-900">插件依赖</dt>
|
||||
<dd class="mt-1 text-sm sm:col-span-3 sm:mt-0">
|
||||
<VAlert
|
||||
description="当前有 1 个插件还未安装"
|
||||
title="提示"
|
||||
></VAlert>
|
||||
<ul class="mt-2 space-y-2">
|
||||
<li>
|
||||
<div
|
||||
class="inline-flex w-96 cursor-pointer flex-col gap-y-3 rounded border p-5 hover:border-primary"
|
||||
>
|
||||
<RouterLink
|
||||
:to="{
|
||||
name: 'PluginDetail',
|
||||
params: { name: 'PluginLinks' },
|
||||
}"
|
||||
class="font-medium text-gray-900 hover:text-blue-400"
|
||||
>
|
||||
run.halo.plugins.links
|
||||
</RouterLink>
|
||||
<div class="text-xs">
|
||||
<VSpace>
|
||||
<VTag> 已安装</VTag>
|
||||
</VSpace>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<div
|
||||
class="inline-flex w-96 cursor-pointer flex-col gap-y-3 rounded border p-5 hover:border-primary"
|
||||
>
|
||||
<span class="font-medium hover:text-blue-400">
|
||||
run.halo.plugins.photos
|
||||
</span>
|
||||
<div class="text-xs">
|
||||
<VSpace>
|
||||
<VTag>未安装</VTag>
|
||||
</VSpace>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
</Transition>
|
||||
<ThemeUploadModal
|
||||
v-model:visible="upgradeModal"
|
||||
:upgrade-theme="selectedTheme"
|
||||
|
|
|
@ -53,31 +53,36 @@ watch(
|
|||
);
|
||||
</script>
|
||||
<template>
|
||||
<div class="bg-white p-4">
|
||||
<div>
|
||||
<FormKit
|
||||
v-if="group && formSchema && configMapFormData"
|
||||
:id="group"
|
||||
v-model="configMapFormData[group]"
|
||||
:name="group"
|
||||
:actions="false"
|
||||
:preserve="true"
|
||||
type="form"
|
||||
@submit="handleSaveConfigMap"
|
||||
>
|
||||
<FormKitSchema :schema="formSchema" :data="configMapFormData[group]" />
|
||||
</FormKit>
|
||||
</div>
|
||||
<div v-permission="['system:configmaps:manage']" class="pt-5">
|
||||
<div class="flex justify-start">
|
||||
<VButton
|
||||
:loading="saving"
|
||||
type="secondary"
|
||||
@click="$formkit.submit(group || '')"
|
||||
<Transition appear mode="out-in" name="fade">
|
||||
<div class="bg-white p-4">
|
||||
<div>
|
||||
<FormKit
|
||||
v-if="group && formSchema && configMapFormData"
|
||||
:id="group"
|
||||
v-model="configMapFormData[group]"
|
||||
:name="group"
|
||||
:actions="false"
|
||||
:preserve="true"
|
||||
type="form"
|
||||
@submit="handleSaveConfigMap"
|
||||
>
|
||||
保存
|
||||
</VButton>
|
||||
<FormKitSchema
|
||||
:schema="formSchema"
|
||||
:data="configMapFormData[group]"
|
||||
/>
|
||||
</FormKit>
|
||||
</div>
|
||||
<div v-permission="['system:configmaps:manage']" class="pt-5">
|
||||
<div class="flex justify-start">
|
||||
<VButton
|
||||
:loading="saving"
|
||||
type="secondary"
|
||||
@click="$formkit.submit(group || '')"
|
||||
>
|
||||
保存
|
||||
</VButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Transition>
|
||||
</template>
|
||||
|
|
|
@ -13,6 +13,7 @@ import {
|
|||
VStatusDot,
|
||||
VTabItem,
|
||||
VTabs,
|
||||
VLoading,
|
||||
} from "@halo-dev/components";
|
||||
import LazyImage from "@/components/image/LazyImage.vue";
|
||||
import ThemePreviewModal from "./preview/ThemePreviewModal.vue";
|
||||
|
@ -214,257 +215,270 @@ const handleOpenPreview = (theme: Theme) => {
|
|||
class="my-[12px] mx-[16px]"
|
||||
>
|
||||
<VTabItem id="installed" label="已安装" class="-mx-[16px]">
|
||||
<VEmpty
|
||||
v-if="!themes.length && !loading"
|
||||
message="当前没有已安装的主题,你可以尝试刷新或者安装新主题"
|
||||
title="当前没有已安装的主题"
|
||||
>
|
||||
<template #actions>
|
||||
<VSpace>
|
||||
<VButton :loading="loading" @click="handleFetchThemes">
|
||||
刷新
|
||||
</VButton>
|
||||
<VButton
|
||||
v-permission="['system:themes:manage']"
|
||||
type="primary"
|
||||
@click="themeInstall = true"
|
||||
>
|
||||
<template #icon>
|
||||
<IconAddCircle class="h-full w-full" />
|
||||
</template>
|
||||
安装主题
|
||||
</VButton>
|
||||
</VSpace>
|
||||
</template>
|
||||
</VEmpty>
|
||||
|
||||
<ul
|
||||
v-else
|
||||
class="box-border h-full w-full divide-y divide-gray-100"
|
||||
role="list"
|
||||
>
|
||||
<li
|
||||
v-for="(theme, index) in themes"
|
||||
:key="index"
|
||||
@click="handleSelectTheme(theme)"
|
||||
<VLoading v-if="loading" />
|
||||
<Transition v-else-if="!themes.length" appear name="fade">
|
||||
<VEmpty
|
||||
message="当前没有已安装的主题,你可以尝试刷新或者安装新主题"
|
||||
title="当前没有已安装的主题"
|
||||
>
|
||||
<VEntity
|
||||
:is-selected="
|
||||
theme.metadata.name === selectedTheme?.metadata?.name
|
||||
"
|
||||
<template #actions>
|
||||
<VSpace>
|
||||
<VButton :loading="loading" @click="handleFetchThemes">
|
||||
刷新
|
||||
</VButton>
|
||||
<VButton
|
||||
v-permission="['system:themes:manage']"
|
||||
type="primary"
|
||||
@click="themeInstall = true"
|
||||
>
|
||||
<template #icon>
|
||||
<IconAddCircle class="h-full w-full" />
|
||||
</template>
|
||||
安装主题
|
||||
</VButton>
|
||||
</VSpace>
|
||||
</template>
|
||||
</VEmpty>
|
||||
</Transition>
|
||||
<Transition v-else appear name="fade">
|
||||
<ul
|
||||
class="box-border h-full w-full divide-y divide-gray-100"
|
||||
role="list"
|
||||
>
|
||||
<li
|
||||
v-for="(theme, index) in themes"
|
||||
:key="index"
|
||||
@click="handleSelectTheme(theme)"
|
||||
>
|
||||
<template #start>
|
||||
<VEntityField>
|
||||
<template #description>
|
||||
<div class="w-32">
|
||||
<div
|
||||
class="group aspect-w-4 aspect-h-3 block w-full overflow-hidden rounded border bg-gray-100"
|
||||
>
|
||||
<LazyImage
|
||||
:key="theme.metadata.name"
|
||||
:src="theme.spec.logo"
|
||||
:alt="theme.spec.displayName"
|
||||
classes="pointer-events-none object-cover group-hover:opacity-75"
|
||||
>
|
||||
<template #loading>
|
||||
<div
|
||||
class="flex h-full items-center justify-center object-cover"
|
||||
>
|
||||
<span class="text-xs text-gray-400"
|
||||
>加载中...</span
|
||||
>
|
||||
</div>
|
||||
</template>
|
||||
<template #error>
|
||||
<div
|
||||
class="flex h-full items-center justify-center object-cover"
|
||||
>
|
||||
<span class="text-xs text-red-400">加载异常</span>
|
||||
</div>
|
||||
</template>
|
||||
</LazyImage>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</VEntityField>
|
||||
<VEntityField
|
||||
:title="theme.spec.displayName"
|
||||
:description="theme.spec.version"
|
||||
>
|
||||
<template #extra>
|
||||
<VTag
|
||||
v-if="
|
||||
theme.metadata.name === activatedTheme?.metadata?.name
|
||||
"
|
||||
>
|
||||
当前启用
|
||||
</VTag>
|
||||
</template>
|
||||
</VEntityField>
|
||||
</template>
|
||||
<template #end>
|
||||
<VEntityField v-if="theme.metadata.deletionTimestamp">
|
||||
<template #description>
|
||||
<VStatusDot v-tooltip="`删除中`" state="warning" animate />
|
||||
</template>
|
||||
</VEntityField>
|
||||
<VEntityField>
|
||||
<template #description>
|
||||
<a
|
||||
class="text-sm text-gray-400 hover:text-blue-600"
|
||||
:href="theme.spec.author.website"
|
||||
target="_blank"
|
||||
@click.stop
|
||||
>
|
||||
{{ theme.spec.author.name }}
|
||||
</a>
|
||||
</template>
|
||||
</VEntityField>
|
||||
<VEntityField>
|
||||
<template #description>
|
||||
<a
|
||||
:href="theme.spec.repo"
|
||||
class="text-gray-900 hover:text-blue-600"
|
||||
target="_blank"
|
||||
>
|
||||
<IconGitHub />
|
||||
</a>
|
||||
</template>
|
||||
</VEntityField>
|
||||
</template>
|
||||
<template
|
||||
v-if="currentUserHasPermission(['system:themes:manage'])"
|
||||
#dropdownItems
|
||||
<VEntity
|
||||
:is-selected="
|
||||
theme.metadata.name === selectedTheme?.metadata?.name
|
||||
"
|
||||
>
|
||||
<VButton
|
||||
v-close-popper
|
||||
block
|
||||
type="secondary"
|
||||
@click="handleOpenPreview(theme)"
|
||||
<template #start>
|
||||
<VEntityField>
|
||||
<template #description>
|
||||
<div class="w-32">
|
||||
<div
|
||||
class="group aspect-w-4 aspect-h-3 block w-full overflow-hidden rounded border bg-gray-100"
|
||||
>
|
||||
<LazyImage
|
||||
:key="theme.metadata.name"
|
||||
:src="theme.spec.logo"
|
||||
:alt="theme.spec.displayName"
|
||||
classes="pointer-events-none object-cover group-hover:opacity-75"
|
||||
>
|
||||
<template #loading>
|
||||
<div
|
||||
class="flex h-full items-center justify-center object-cover"
|
||||
>
|
||||
<span class="text-xs text-gray-400"
|
||||
>加载中...</span
|
||||
>
|
||||
</div>
|
||||
</template>
|
||||
<template #error>
|
||||
<div
|
||||
class="flex h-full items-center justify-center object-cover"
|
||||
>
|
||||
<span class="text-xs text-red-400"
|
||||
>加载异常</span
|
||||
>
|
||||
</div>
|
||||
</template>
|
||||
</LazyImage>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</VEntityField>
|
||||
<VEntityField
|
||||
:title="theme.spec.displayName"
|
||||
:description="theme.spec.version"
|
||||
>
|
||||
<template #extra>
|
||||
<VTag
|
||||
v-if="
|
||||
theme.metadata.name === activatedTheme?.metadata?.name
|
||||
"
|
||||
>
|
||||
当前启用
|
||||
</VTag>
|
||||
</template>
|
||||
</VEntityField>
|
||||
</template>
|
||||
<template #end>
|
||||
<VEntityField v-if="theme.metadata.deletionTimestamp">
|
||||
<template #description>
|
||||
<VStatusDot
|
||||
v-tooltip="`删除中`"
|
||||
state="warning"
|
||||
animate
|
||||
/>
|
||||
</template>
|
||||
</VEntityField>
|
||||
<VEntityField>
|
||||
<template #description>
|
||||
<a
|
||||
class="text-sm text-gray-400 hover:text-blue-600"
|
||||
:href="theme.spec.author.website"
|
||||
target="_blank"
|
||||
@click.stop
|
||||
>
|
||||
{{ theme.spec.author.name }}
|
||||
</a>
|
||||
</template>
|
||||
</VEntityField>
|
||||
<VEntityField>
|
||||
<template #description>
|
||||
<a
|
||||
:href="theme.spec.repo"
|
||||
class="text-gray-900 hover:text-blue-600"
|
||||
target="_blank"
|
||||
>
|
||||
<IconGitHub />
|
||||
</a>
|
||||
</template>
|
||||
</VEntityField>
|
||||
</template>
|
||||
<template
|
||||
v-if="currentUserHasPermission(['system:themes:manage'])"
|
||||
#dropdownItems
|
||||
>
|
||||
预览
|
||||
</VButton>
|
||||
<VButton
|
||||
v-close-popper
|
||||
block
|
||||
type="danger"
|
||||
@click="handleUninstall(theme)"
|
||||
>
|
||||
卸载
|
||||
</VButton>
|
||||
<VButton
|
||||
v-close-popper
|
||||
:disabled="
|
||||
theme.metadata.name === activatedTheme?.metadata?.name
|
||||
"
|
||||
block
|
||||
type="danger"
|
||||
@click="handleUninstall(theme, true)"
|
||||
>
|
||||
卸载并删除配置
|
||||
</VButton>
|
||||
</template>
|
||||
</VEntity>
|
||||
</li>
|
||||
</ul>
|
||||
<VButton
|
||||
v-close-popper
|
||||
block
|
||||
type="secondary"
|
||||
@click="handleOpenPreview(theme)"
|
||||
>
|
||||
预览
|
||||
</VButton>
|
||||
<VButton
|
||||
v-close-popper
|
||||
block
|
||||
type="danger"
|
||||
@click="handleUninstall(theme)"
|
||||
>
|
||||
卸载
|
||||
</VButton>
|
||||
<VButton
|
||||
v-close-popper
|
||||
:disabled="
|
||||
theme.metadata.name === activatedTheme?.metadata?.name
|
||||
"
|
||||
block
|
||||
type="danger"
|
||||
@click="handleUninstall(theme, true)"
|
||||
>
|
||||
卸载并删除配置
|
||||
</VButton>
|
||||
</template>
|
||||
</VEntity>
|
||||
</li>
|
||||
</ul>
|
||||
</Transition>
|
||||
</VTabItem>
|
||||
<VTabItem id="uninstalled" label="未安装" class="-mx-[16px]">
|
||||
<VEmpty v-if="!themes.length && !loading" title="当前没有未安装的主题">
|
||||
<template #actions>
|
||||
<VSpace>
|
||||
<VButton :loading="loading" @click="handleFetchThemes">
|
||||
刷新
|
||||
</VButton>
|
||||
</VSpace>
|
||||
</template>
|
||||
</VEmpty>
|
||||
|
||||
<ul
|
||||
v-else
|
||||
class="box-border h-full w-full divide-y divide-gray-100"
|
||||
role="list"
|
||||
>
|
||||
<li v-for="(theme, index) in themes" :key="index">
|
||||
<VEntity>
|
||||
<template #start>
|
||||
<VEntityField>
|
||||
<template #description>
|
||||
<div class="w-32">
|
||||
<div
|
||||
class="group aspect-w-4 aspect-h-3 block w-full overflow-hidden rounded border bg-gray-100"
|
||||
>
|
||||
<LazyImage
|
||||
:key="theme.metadata.name"
|
||||
:src="theme.spec.logo"
|
||||
:alt="theme.spec.displayName"
|
||||
classes="pointer-events-none object-cover group-hover:opacity-75"
|
||||
<VLoading v-if="loading" />
|
||||
<Transition v-else-if="!themes.length" appear name="fade">
|
||||
<VEmpty title="当前没有未安装的主题">
|
||||
<template #actions>
|
||||
<VSpace>
|
||||
<VButton :loading="loading" @click="handleFetchThemes">
|
||||
刷新
|
||||
</VButton>
|
||||
</VSpace>
|
||||
</template>
|
||||
</VEmpty>
|
||||
</Transition>
|
||||
<Transition v-else appear name="fade">
|
||||
<ul
|
||||
class="box-border h-full w-full divide-y divide-gray-100"
|
||||
role="list"
|
||||
>
|
||||
<li v-for="(theme, index) in themes" :key="index">
|
||||
<VEntity>
|
||||
<template #start>
|
||||
<VEntityField>
|
||||
<template #description>
|
||||
<div class="w-32">
|
||||
<div
|
||||
class="group aspect-w-4 aspect-h-3 block w-full overflow-hidden rounded border bg-gray-100"
|
||||
>
|
||||
<template #loading>
|
||||
<div
|
||||
class="flex h-full items-center justify-center object-cover"
|
||||
>
|
||||
<span class="text-xs text-gray-400"
|
||||
>加载中...</span
|
||||
<LazyImage
|
||||
:key="theme.metadata.name"
|
||||
:src="theme.spec.logo"
|
||||
:alt="theme.spec.displayName"
|
||||
classes="pointer-events-none object-cover group-hover:opacity-75"
|
||||
>
|
||||
<template #loading>
|
||||
<div
|
||||
class="flex h-full items-center justify-center object-cover"
|
||||
>
|
||||
</div>
|
||||
</template>
|
||||
<template #error>
|
||||
<div
|
||||
class="flex h-full items-center justify-center object-cover"
|
||||
>
|
||||
<span class="text-xs text-red-400">加载异常</span>
|
||||
</div>
|
||||
</template>
|
||||
</LazyImage>
|
||||
<span class="text-xs text-gray-400"
|
||||
>加载中...</span
|
||||
>
|
||||
</div>
|
||||
</template>
|
||||
<template #error>
|
||||
<div
|
||||
class="flex h-full items-center justify-center object-cover"
|
||||
>
|
||||
<span class="text-xs text-red-400"
|
||||
>加载异常</span
|
||||
>
|
||||
</div>
|
||||
</template>
|
||||
</LazyImage>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</VEntityField>
|
||||
<VEntityField
|
||||
:title="theme.spec.displayName"
|
||||
:description="theme.spec.version"
|
||||
>
|
||||
</VEntityField>
|
||||
</template>
|
||||
<template #end>
|
||||
<VEntityField>
|
||||
<template #description>
|
||||
<a
|
||||
class="text-sm text-gray-400 hover:text-blue-600"
|
||||
:href="theme.spec.author.website"
|
||||
target="_blank"
|
||||
@click.stop
|
||||
>
|
||||
{{ theme.spec.author.name }}
|
||||
</a>
|
||||
</template>
|
||||
</VEntityField>
|
||||
<VEntityField>
|
||||
<template #description>
|
||||
<a
|
||||
:href="theme.spec.repo"
|
||||
class="text-gray-900 hover:text-blue-600"
|
||||
target="_blank"
|
||||
>
|
||||
<IconGitHub />
|
||||
</a>
|
||||
</template>
|
||||
</VEntityField>
|
||||
<VEntityField v-permission="['system:themes:manage']">
|
||||
<template #description>
|
||||
<VButton
|
||||
size="sm"
|
||||
:disabled="creating"
|
||||
@click="handleCreateTheme(theme)"
|
||||
>
|
||||
安装
|
||||
</VButton>
|
||||
</template>
|
||||
</VEntityField>
|
||||
</template>
|
||||
</VEntity>
|
||||
</li>
|
||||
</ul>
|
||||
</template>
|
||||
</VEntityField>
|
||||
<VEntityField
|
||||
:title="theme.spec.displayName"
|
||||
:description="theme.spec.version"
|
||||
>
|
||||
</VEntityField>
|
||||
</template>
|
||||
<template #end>
|
||||
<VEntityField>
|
||||
<template #description>
|
||||
<a
|
||||
class="text-sm text-gray-400 hover:text-blue-600"
|
||||
:href="theme.spec.author.website"
|
||||
target="_blank"
|
||||
@click.stop
|
||||
>
|
||||
{{ theme.spec.author.name }}
|
||||
</a>
|
||||
</template>
|
||||
</VEntityField>
|
||||
<VEntityField>
|
||||
<template #description>
|
||||
<a
|
||||
:href="theme.spec.repo"
|
||||
class="text-gray-900 hover:text-blue-600"
|
||||
target="_blank"
|
||||
>
|
||||
<IconGitHub />
|
||||
</a>
|
||||
</template>
|
||||
</VEntityField>
|
||||
<VEntityField v-permission="['system:themes:manage']">
|
||||
<template #description>
|
||||
<VButton
|
||||
size="sm"
|
||||
:disabled="creating"
|
||||
@click="handleCreateTheme(theme)"
|
||||
>
|
||||
安装
|
||||
</VButton>
|
||||
</template>
|
||||
</VEntityField>
|
||||
</template>
|
||||
</VEntity>
|
||||
</li>
|
||||
</ul>
|
||||
</Transition>
|
||||
</VTabItem>
|
||||
</VTabs>
|
||||
|
||||
|
|
|
@ -24,6 +24,7 @@ import {
|
|||
VPageHeader,
|
||||
VSpace,
|
||||
VTabbar,
|
||||
VLoading,
|
||||
} from "@halo-dev/components";
|
||||
import ThemeListModal from "../components/ThemeListModal.vue";
|
||||
import ThemePreviewModal from "../components/preview/ThemePreviewModal.vue";
|
||||
|
@ -224,15 +225,13 @@ onMounted(() => {
|
|||
></VTabbar>
|
||||
</template>
|
||||
</VCard>
|
||||
<div>
|
||||
<div class="bg-white">
|
||||
<RouterView :key="activeTab" v-slot="{ Component }">
|
||||
<template v-if="Component">
|
||||
<Suspense>
|
||||
<component :is="Component"></component>
|
||||
<template #fallback>
|
||||
<div class="flex h-32 w-full justify-center bg-white">
|
||||
<span class="text-sm text-gray-600">加载中...</span>
|
||||
</div>
|
||||
<VLoading />
|
||||
</template>
|
||||
</Suspense>
|
||||
</template>
|
||||
|
|
|
@ -59,166 +59,175 @@ watchEffect(() => {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex items-center justify-between bg-white px-4 py-4 sm:px-6">
|
||||
<Transition appear mode="out-in" name="fade">
|
||||
<div>
|
||||
<h3 class="text-lg font-medium leading-6 text-gray-900">插件信息</h3>
|
||||
<p class="mt-1 flex max-w-2xl items-center gap-2">
|
||||
<span class="text-sm text-gray-500">{{ plugin?.spec.version }}</span>
|
||||
<VTag>
|
||||
{{ isStarted ? "已启用" : "未启用" }}
|
||||
</VTag>
|
||||
</p>
|
||||
</div>
|
||||
<div v-permission="['system:plugins:manage']">
|
||||
<VSwitch :model-value="isStarted" @change="changeStatus" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="border-t border-gray-200">
|
||||
<dl class="divide-y divide-gray-100">
|
||||
<div
|
||||
class="bg-white px-4 py-5 hover:bg-gray-50 sm:grid sm:grid-cols-6 sm:gap-4 sm:px-6"
|
||||
>
|
||||
<dt class="text-sm font-medium text-gray-900">名称</dt>
|
||||
<dd class="mt-1 text-sm text-gray-900 sm:col-span-2 sm:mt-0">
|
||||
{{ plugin?.spec.displayName }}
|
||||
</dd>
|
||||
<div class="flex items-center justify-between bg-white px-4 py-4 sm:px-6">
|
||||
<div>
|
||||
<h3 class="text-lg font-medium leading-6 text-gray-900">插件信息</h3>
|
||||
<p class="mt-1 flex max-w-2xl items-center gap-2">
|
||||
<span class="text-sm text-gray-500">{{
|
||||
plugin?.spec.version
|
||||
}}</span>
|
||||
<VTag>
|
||||
{{ isStarted ? "已启用" : "未启用" }}
|
||||
</VTag>
|
||||
</p>
|
||||
</div>
|
||||
<div v-permission="['system:plugins:manage']">
|
||||
<VSwitch :model-value="isStarted" @change="changeStatus" />
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="bg-white px-4 py-5 hover:bg-gray-50 sm:grid sm:grid-cols-6 sm:gap-4 sm:px-6"
|
||||
>
|
||||
<dt class="text-sm font-medium text-gray-900">描述</dt>
|
||||
<dd class="mt-1 text-sm text-gray-900 sm:col-span-2 sm:mt-0">
|
||||
{{ plugin?.spec.description }}
|
||||
</dd>
|
||||
</div>
|
||||
<div
|
||||
class="bg-white px-4 py-5 hover:bg-gray-50 sm:grid sm:grid-cols-6 sm:gap-4 sm:px-6"
|
||||
>
|
||||
<dt class="text-sm font-medium text-gray-900">版本</dt>
|
||||
<dd class="mt-1 text-sm text-gray-900 sm:col-span-2 sm:mt-0">
|
||||
{{ plugin?.spec.version }}
|
||||
</dd>
|
||||
</div>
|
||||
<div
|
||||
class="bg-white px-4 py-5 hover:bg-gray-50 sm:grid sm:grid-cols-6 sm:gap-4 sm:px-6"
|
||||
>
|
||||
<dt class="text-sm font-medium text-gray-900">Halo 版本要求</dt>
|
||||
<dd class="mt-1 text-sm text-gray-900 sm:col-span-2 sm:mt-0">
|
||||
{{ plugin?.spec.requires }}
|
||||
</dd>
|
||||
</div>
|
||||
<div
|
||||
class="bg-white px-4 py-5 hover:bg-gray-50 sm:grid sm:grid-cols-6 sm:gap-4 sm:px-6"
|
||||
>
|
||||
<dt class="text-sm font-medium text-gray-900">提供方</dt>
|
||||
<dd class="mt-1 text-sm text-gray-900 sm:col-span-2 sm:mt-0">
|
||||
<a :href="plugin?.spec.homepage" target="_blank">
|
||||
{{ plugin?.spec.author }}
|
||||
</a>
|
||||
</dd>
|
||||
</div>
|
||||
<div
|
||||
class="bg-white px-4 py-5 hover:bg-gray-50 sm:grid sm:grid-cols-6 sm:gap-4 sm:px-6"
|
||||
>
|
||||
<dt class="text-sm font-medium text-gray-900">协议</dt>
|
||||
<dd class="mt-1 text-sm text-gray-900 sm:col-span-2 sm:mt-0">
|
||||
<ul
|
||||
v-if="plugin?.spec.license && plugin?.spec.license.length"
|
||||
class="list-inside"
|
||||
:class="{ 'list-disc': plugin?.spec.license.length > 1 }"
|
||||
<div class="border-t border-gray-200">
|
||||
<dl class="divide-y divide-gray-100">
|
||||
<div
|
||||
class="bg-white px-4 py-5 hover:bg-gray-50 sm:grid sm:grid-cols-6 sm:gap-4 sm:px-6"
|
||||
>
|
||||
<li v-for="(license, index) in plugin.spec.license" :key="index">
|
||||
<a v-if="license.url" :href="license.url" target="_blank">
|
||||
{{ license.name }}
|
||||
<dt class="text-sm font-medium text-gray-900">名称</dt>
|
||||
<dd class="mt-1 text-sm text-gray-900 sm:col-span-2 sm:mt-0">
|
||||
{{ plugin?.spec.displayName }}
|
||||
</dd>
|
||||
</div>
|
||||
<div
|
||||
class="bg-white px-4 py-5 hover:bg-gray-50 sm:grid sm:grid-cols-6 sm:gap-4 sm:px-6"
|
||||
>
|
||||
<dt class="text-sm font-medium text-gray-900">描述</dt>
|
||||
<dd class="mt-1 text-sm text-gray-900 sm:col-span-2 sm:mt-0">
|
||||
{{ plugin?.spec.description }}
|
||||
</dd>
|
||||
</div>
|
||||
<div
|
||||
class="bg-white px-4 py-5 hover:bg-gray-50 sm:grid sm:grid-cols-6 sm:gap-4 sm:px-6"
|
||||
>
|
||||
<dt class="text-sm font-medium text-gray-900">版本</dt>
|
||||
<dd class="mt-1 text-sm text-gray-900 sm:col-span-2 sm:mt-0">
|
||||
{{ plugin?.spec.version }}
|
||||
</dd>
|
||||
</div>
|
||||
<div
|
||||
class="bg-white px-4 py-5 hover:bg-gray-50 sm:grid sm:grid-cols-6 sm:gap-4 sm:px-6"
|
||||
>
|
||||
<dt class="text-sm font-medium text-gray-900">Halo 版本要求</dt>
|
||||
<dd class="mt-1 text-sm text-gray-900 sm:col-span-2 sm:mt-0">
|
||||
{{ plugin?.spec.requires }}
|
||||
</dd>
|
||||
</div>
|
||||
<div
|
||||
class="bg-white px-4 py-5 hover:bg-gray-50 sm:grid sm:grid-cols-6 sm:gap-4 sm:px-6"
|
||||
>
|
||||
<dt class="text-sm font-medium text-gray-900">提供方</dt>
|
||||
<dd class="mt-1 text-sm text-gray-900 sm:col-span-2 sm:mt-0">
|
||||
<a :href="plugin?.spec.homepage" target="_blank">
|
||||
{{ plugin?.spec.author }}
|
||||
</a>
|
||||
<span>
|
||||
{{ license.name }}
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
</dd>
|
||||
</div>
|
||||
<!-- TODO add display extensions support -->
|
||||
<div
|
||||
v-if="false"
|
||||
class="bg-white px-4 py-5 hover:bg-gray-50 sm:grid sm:grid-cols-6 sm:gap-4 sm:px-6"
|
||||
>
|
||||
<dt class="text-sm font-medium text-gray-900">模型定义</dt>
|
||||
<dd class="mt-1 text-sm text-gray-900 sm:col-span-2 sm:mt-0">
|
||||
<span>无</span>
|
||||
</dd>
|
||||
</div>
|
||||
<div
|
||||
:class="`${
|
||||
pluginRoleTemplateGroups.length ? 'bg-gray-50' : 'bg-white'
|
||||
}`"
|
||||
class="px-4 py-5 hover:bg-gray-50 sm:grid sm:grid-cols-6 sm:gap-4 sm:px-6"
|
||||
>
|
||||
<dt class="text-sm font-medium text-gray-900">权限模板</dt>
|
||||
<dd class="mt-1 text-sm text-gray-900 sm:col-span-5 sm:mt-0">
|
||||
<dl
|
||||
v-if="pluginRoleTemplateGroups.length"
|
||||
class="divide-y divide-gray-100"
|
||||
</dd>
|
||||
</div>
|
||||
<div
|
||||
class="bg-white px-4 py-5 hover:bg-gray-50 sm:grid sm:grid-cols-6 sm:gap-4 sm:px-6"
|
||||
>
|
||||
<div
|
||||
v-for="(group, groupIndex) in pluginRoleTemplateGroups"
|
||||
:key="groupIndex"
|
||||
class="bg-white px-4 py-5 hover:bg-gray-50 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6"
|
||||
>
|
||||
<dt class="text-sm font-medium text-gray-900">
|
||||
{{ group.module }}
|
||||
</dt>
|
||||
<dd class="mt-1 text-sm text-gray-900 sm:col-span-2 sm:mt-0">
|
||||
<ul class="space-y-2">
|
||||
<li v-for="(role, index) in group.roles" :key="index">
|
||||
<div
|
||||
class="inline-flex w-72 cursor-pointer flex-row items-center gap-4 rounded border p-5 hover:border-primary"
|
||||
>
|
||||
<div class="inline-flex flex-col gap-y-3">
|
||||
<span class="font-medium text-gray-900">
|
||||
{{
|
||||
role.metadata.annotations?.[
|
||||
rbacAnnotations.DISPLAY_NAME
|
||||
]
|
||||
}}
|
||||
</span>
|
||||
<span
|
||||
v-if="
|
||||
role.metadata.annotations?.[
|
||||
rbacAnnotations.DEPENDENCIES
|
||||
]
|
||||
"
|
||||
class="text-xs text-gray-400"
|
||||
<dt class="text-sm font-medium text-gray-900">协议</dt>
|
||||
<dd class="mt-1 text-sm text-gray-900 sm:col-span-2 sm:mt-0">
|
||||
<ul
|
||||
v-if="plugin?.spec.license && plugin?.spec.license.length"
|
||||
class="list-inside"
|
||||
:class="{ 'list-disc': plugin?.spec.license.length > 1 }"
|
||||
>
|
||||
<li
|
||||
v-for="(license, index) in plugin.spec.license"
|
||||
:key="index"
|
||||
>
|
||||
<a v-if="license.url" :href="license.url" target="_blank">
|
||||
{{ license.name }}
|
||||
</a>
|
||||
<span>
|
||||
{{ license.name }}
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
</dd>
|
||||
</div>
|
||||
<!-- TODO add display extensions support -->
|
||||
<div
|
||||
v-if="false"
|
||||
class="bg-white px-4 py-5 hover:bg-gray-50 sm:grid sm:grid-cols-6 sm:gap-4 sm:px-6"
|
||||
>
|
||||
<dt class="text-sm font-medium text-gray-900">模型定义</dt>
|
||||
<dd class="mt-1 text-sm text-gray-900 sm:col-span-2 sm:mt-0">
|
||||
<span>无</span>
|
||||
</dd>
|
||||
</div>
|
||||
<div
|
||||
:class="`${
|
||||
pluginRoleTemplateGroups.length ? 'bg-gray-50' : 'bg-white'
|
||||
}`"
|
||||
class="px-4 py-5 hover:bg-gray-50 sm:grid sm:grid-cols-6 sm:gap-4 sm:px-6"
|
||||
>
|
||||
<dt class="text-sm font-medium text-gray-900">权限模板</dt>
|
||||
<dd class="mt-1 text-sm text-gray-900 sm:col-span-5 sm:mt-0">
|
||||
<dl
|
||||
v-if="pluginRoleTemplateGroups.length"
|
||||
class="divide-y divide-gray-100"
|
||||
>
|
||||
<div
|
||||
v-for="(group, groupIndex) in pluginRoleTemplateGroups"
|
||||
:key="groupIndex"
|
||||
class="bg-white px-4 py-5 hover:bg-gray-50 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6"
|
||||
>
|
||||
<dt class="text-sm font-medium text-gray-900">
|
||||
{{ group.module }}
|
||||
</dt>
|
||||
<dd class="mt-1 text-sm text-gray-900 sm:col-span-2 sm:mt-0">
|
||||
<ul class="space-y-2">
|
||||
<li v-for="(role, index) in group.roles" :key="index">
|
||||
<div
|
||||
class="inline-flex w-72 cursor-pointer flex-row items-center gap-4 rounded border p-5 hover:border-primary"
|
||||
>
|
||||
依赖于
|
||||
{{
|
||||
JSON.parse(
|
||||
role.metadata.annotations?.[
|
||||
rbacAnnotations.DEPENDENCIES
|
||||
]
|
||||
).join(", ")
|
||||
}}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</dd>
|
||||
</div>
|
||||
</dl>
|
||||
<span v-else>无</span>
|
||||
</dd>
|
||||
<div class="inline-flex flex-col gap-y-3">
|
||||
<span class="font-medium text-gray-900">
|
||||
{{
|
||||
role.metadata.annotations?.[
|
||||
rbacAnnotations.DISPLAY_NAME
|
||||
]
|
||||
}}
|
||||
</span>
|
||||
<span
|
||||
v-if="
|
||||
role.metadata.annotations?.[
|
||||
rbacAnnotations.DEPENDENCIES
|
||||
]
|
||||
"
|
||||
class="text-xs text-gray-400"
|
||||
>
|
||||
依赖于
|
||||
{{
|
||||
JSON.parse(
|
||||
role.metadata.annotations?.[
|
||||
rbacAnnotations.DEPENDENCIES
|
||||
]
|
||||
).join(", ")
|
||||
}}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</dd>
|
||||
</div>
|
||||
</dl>
|
||||
<span v-else>无</span>
|
||||
</dd>
|
||||
</div>
|
||||
<div
|
||||
class="bg-white px-4 py-5 hover:bg-gray-50 sm:grid sm:grid-cols-6 sm:gap-4 sm:px-6"
|
||||
>
|
||||
<dt class="text-sm font-medium text-gray-900">最近一次启动</dt>
|
||||
<dd
|
||||
class="mt-1 text-sm tabular-nums text-gray-900 sm:col-span-2 sm:mt-0"
|
||||
>
|
||||
{{ formatDatetime(plugin?.status?.lastStartTime) }}
|
||||
</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</div>
|
||||
<div
|
||||
class="bg-white px-4 py-5 hover:bg-gray-50 sm:grid sm:grid-cols-6 sm:gap-4 sm:px-6"
|
||||
>
|
||||
<dt class="text-sm font-medium text-gray-900">最近一次启动</dt>
|
||||
<dd
|
||||
class="mt-1 text-sm tabular-nums text-gray-900 sm:col-span-2 sm:mt-0"
|
||||
>
|
||||
{{ formatDatetime(plugin?.status?.lastStartTime) }}
|
||||
</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
</Transition>
|
||||
</template>
|
||||
|
|
|
@ -10,6 +10,7 @@ import {
|
|||
VPageHeader,
|
||||
VPagination,
|
||||
VSpace,
|
||||
VLoading,
|
||||
} from "@halo-dev/components";
|
||||
import PluginListItem from "./components/PluginListItem.vue";
|
||||
import PluginUploadModal from "./components/PluginUploadModal.vue";
|
||||
|
@ -310,37 +311,41 @@ function handleClearFilters() {
|
|||
</div>
|
||||
</template>
|
||||
|
||||
<VEmpty
|
||||
v-if="!plugins.total && !loading"
|
||||
message="当前没有已安装的插件,你可以尝试刷新或者安装新插件"
|
||||
title="当前没有已安装的插件"
|
||||
>
|
||||
<template #actions>
|
||||
<VSpace>
|
||||
<VButton @click="handleFetchPlugins">刷新</VButton>
|
||||
<VButton
|
||||
v-permission="['system:plugins:manage']"
|
||||
type="secondary"
|
||||
@click="pluginInstall = true"
|
||||
>
|
||||
<template #icon>
|
||||
<IconAddCircle class="h-full w-full" />
|
||||
</template>
|
||||
安装插件
|
||||
</VButton>
|
||||
</VSpace>
|
||||
</template>
|
||||
</VEmpty>
|
||||
<VLoading v-if="loading" />
|
||||
|
||||
<ul
|
||||
v-else
|
||||
class="box-border h-full w-full divide-y divide-gray-100"
|
||||
role="list"
|
||||
>
|
||||
<li v-for="(plugin, index) in plugins.items" :key="index">
|
||||
<PluginListItem :plugin="plugin" @reload="handleFetchPlugins" />
|
||||
</li>
|
||||
</ul>
|
||||
<Transition v-else-if="!plugins.total" appear name="fade">
|
||||
<VEmpty
|
||||
message="当前没有已安装的插件,你可以尝试刷新或者安装新插件"
|
||||
title="当前没有已安装的插件"
|
||||
>
|
||||
<template #actions>
|
||||
<VSpace>
|
||||
<VButton @click="handleFetchPlugins">刷新</VButton>
|
||||
<VButton
|
||||
v-permission="['system:plugins:manage']"
|
||||
type="secondary"
|
||||
@click="pluginInstall = true"
|
||||
>
|
||||
<template #icon>
|
||||
<IconAddCircle class="h-full w-full" />
|
||||
</template>
|
||||
安装插件
|
||||
</VButton>
|
||||
</VSpace>
|
||||
</template>
|
||||
</VEmpty>
|
||||
</Transition>
|
||||
|
||||
<Transition v-else appear name="fade">
|
||||
<ul
|
||||
class="box-border h-full w-full divide-y divide-gray-100"
|
||||
role="list"
|
||||
>
|
||||
<li v-for="(plugin, index) in plugins.items" :key="index">
|
||||
<PluginListItem :plugin="plugin" @reload="handleFetchPlugins" />
|
||||
</li>
|
||||
</ul>
|
||||
</Transition>
|
||||
|
||||
<template #footer>
|
||||
<div class="bg-white sm:flex sm:items-center sm:justify-end">
|
||||
|
|
|
@ -62,31 +62,36 @@ const handleFetchPlugin = async () => {
|
|||
await handleFetchPlugin();
|
||||
</script>
|
||||
<template>
|
||||
<div class="bg-white p-4">
|
||||
<div>
|
||||
<FormKit
|
||||
v-if="group && formSchema && configMapFormData"
|
||||
:id="group"
|
||||
v-model="configMapFormData[group]"
|
||||
:name="group"
|
||||
:actions="false"
|
||||
:preserve="true"
|
||||
type="form"
|
||||
@submit="handleSaveConfigMap"
|
||||
>
|
||||
<FormKitSchema :schema="formSchema" :data="configMapFormData[group]" />
|
||||
</FormKit>
|
||||
</div>
|
||||
<div v-permission="['system:configmaps:manage']" class="pt-5">
|
||||
<div class="flex justify-start">
|
||||
<VButton
|
||||
:loading="saving"
|
||||
type="secondary"
|
||||
@click="$formkit.submit(group || '')"
|
||||
<Transition appear mode="out-in" name="fade">
|
||||
<div class="bg-white p-4">
|
||||
<div>
|
||||
<FormKit
|
||||
v-if="group && formSchema && configMapFormData"
|
||||
:id="group"
|
||||
v-model="configMapFormData[group]"
|
||||
:name="group"
|
||||
:actions="false"
|
||||
:preserve="true"
|
||||
type="form"
|
||||
@submit="handleSaveConfigMap"
|
||||
>
|
||||
保存
|
||||
</VButton>
|
||||
<FormKitSchema
|
||||
:schema="formSchema"
|
||||
:data="configMapFormData[group]"
|
||||
/>
|
||||
</FormKit>
|
||||
</div>
|
||||
<div v-permission="['system:configmaps:manage']" class="pt-5">
|
||||
<div class="flex justify-start">
|
||||
<VButton
|
||||
:loading="saving"
|
||||
type="secondary"
|
||||
@click="$formkit.submit(group || '')"
|
||||
>
|
||||
保存
|
||||
</VButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Transition>
|
||||
</template>
|
||||
|
|
|
@ -17,6 +17,7 @@ import {
|
|||
VPageHeader,
|
||||
VTabbar,
|
||||
VAvatar,
|
||||
VLoading,
|
||||
} from "@halo-dev/components";
|
||||
import BasicLayout from "@/layouts/BasicLayout.vue";
|
||||
|
||||
|
@ -172,15 +173,13 @@ watch([() => route.name, () => route.params], () => {
|
|||
></VTabbar>
|
||||
</template>
|
||||
</VCard>
|
||||
<div>
|
||||
<div class="bg-white">
|
||||
<RouterView :key="activeTab" v-slot="{ Component }">
|
||||
<template v-if="Component">
|
||||
<Suspense>
|
||||
<component :is="Component"></component>
|
||||
<template #fallback>
|
||||
<div class="flex h-32 w-full justify-center bg-white">
|
||||
<span class="text-sm text-gray-600">加载中...</span>
|
||||
</div>
|
||||
<VLoading />
|
||||
</template>
|
||||
</Suspense>
|
||||
</template>
|
||||
|
|
|
@ -12,12 +12,12 @@ import {
|
|||
VButton,
|
||||
VCard,
|
||||
VPageHeader,
|
||||
VPagination,
|
||||
VSpace,
|
||||
VTag,
|
||||
VStatusDot,
|
||||
VEntity,
|
||||
VEntityField,
|
||||
VLoading,
|
||||
} from "@halo-dev/components";
|
||||
import RoleEditingModal from "./components/RoleEditingModal.vue";
|
||||
|
||||
|
@ -39,7 +39,7 @@ const { currentUserHasPermission } = usePermission();
|
|||
const editingModal = ref<boolean>(false);
|
||||
const selectedRole = ref<Role>();
|
||||
|
||||
const { roles, handleFetchRoles } = useFetchRole();
|
||||
const { roles, handleFetchRoles, loading } = useFetchRole();
|
||||
|
||||
let fuse: Fuse<Role> | undefined = undefined;
|
||||
|
||||
|
@ -228,16 +228,21 @@ const handleDelete = async (role: Role) => {
|
|||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<ul class="box-border h-full w-full divide-y divide-gray-100" role="list">
|
||||
<li v-for="(role, index) in searchResults" :key="index">
|
||||
<VEntity>
|
||||
<template #start>
|
||||
<VEntityField
|
||||
:title="
|
||||
role.metadata.annotations?.[rbacAnnotations.DISPLAY_NAME] ||
|
||||
role.metadata.name
|
||||
"
|
||||
:description="`包含
|
||||
<VLoading v-if="loading" />
|
||||
<Transition v-else appear name="fade">
|
||||
<ul
|
||||
class="box-border h-full w-full divide-y divide-gray-100"
|
||||
role="list"
|
||||
>
|
||||
<li v-for="(role, index) in searchResults" :key="index">
|
||||
<VEntity>
|
||||
<template #start>
|
||||
<VEntityField
|
||||
:title="
|
||||
role.metadata.annotations?.[rbacAnnotations.DISPLAY_NAME] ||
|
||||
role.metadata.name
|
||||
"
|
||||
:description="`包含
|
||||
${
|
||||
JSON.parse(
|
||||
role.metadata.annotations?.[
|
||||
|
@ -246,67 +251,62 @@ const handleDelete = async (role: Role) => {
|
|||
).length
|
||||
}
|
||||
个权限`"
|
||||
:route="{
|
||||
name: 'RoleDetail',
|
||||
params: {
|
||||
name: role.metadata.name,
|
||||
},
|
||||
}"
|
||||
></VEntityField>
|
||||
</template>
|
||||
<template #end>
|
||||
<VEntityField v-if="role.metadata.deletionTimestamp">
|
||||
<template #description>
|
||||
<VStatusDot v-tooltip="`删除中`" state="warning" animate />
|
||||
</template>
|
||||
</VEntityField>
|
||||
<VEntityField description="0 个用户" />
|
||||
<VEntityField>
|
||||
<template #description>
|
||||
<VTag> 系统保留</VTag>
|
||||
</template>
|
||||
</VEntityField>
|
||||
<VEntityField>
|
||||
<template #description>
|
||||
<span class="truncate text-xs tabular-nums text-gray-500">
|
||||
{{ formatDatetime(role.metadata.creationTimestamp) }}
|
||||
</span>
|
||||
</template>
|
||||
</VEntityField>
|
||||
</template>
|
||||
<template
|
||||
v-if="currentUserHasPermission(['system:roles:manage'])"
|
||||
#dropdownItems
|
||||
>
|
||||
<VButton
|
||||
v-close-popper
|
||||
block
|
||||
type="secondary"
|
||||
@click="handleOpenEditingModal(role)"
|
||||
:route="{
|
||||
name: 'RoleDetail',
|
||||
params: {
|
||||
name: role.metadata.name,
|
||||
},
|
||||
}"
|
||||
></VEntityField>
|
||||
</template>
|
||||
<template #end>
|
||||
<VEntityField v-if="role.metadata.deletionTimestamp">
|
||||
<template #description>
|
||||
<VStatusDot v-tooltip="`删除中`" state="warning" animate />
|
||||
</template>
|
||||
</VEntityField>
|
||||
<VEntityField description="0 个用户" />
|
||||
<VEntityField>
|
||||
<template #description>
|
||||
<VTag> 系统保留</VTag>
|
||||
</template>
|
||||
</VEntityField>
|
||||
<VEntityField>
|
||||
<template #description>
|
||||
<span class="truncate text-xs tabular-nums text-gray-500">
|
||||
{{ formatDatetime(role.metadata.creationTimestamp) }}
|
||||
</span>
|
||||
</template>
|
||||
</VEntityField>
|
||||
</template>
|
||||
<template
|
||||
v-if="currentUserHasPermission(['system:roles:manage'])"
|
||||
#dropdownItems
|
||||
>
|
||||
编辑
|
||||
</VButton>
|
||||
<VButton
|
||||
v-close-popper
|
||||
block
|
||||
type="danger"
|
||||
@click="handleDelete(role)"
|
||||
>
|
||||
删除
|
||||
</VButton>
|
||||
<VButton v-close-popper block @click="handleCloneRole(role)">
|
||||
基于此角色创建
|
||||
</VButton>
|
||||
</template>
|
||||
</VEntity>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<template #footer>
|
||||
<div class="bg-white sm:flex sm:items-center sm:justify-end">
|
||||
<VPagination :page="1" :size="10" :total="20" />
|
||||
</div>
|
||||
</template>
|
||||
<VButton
|
||||
v-close-popper
|
||||
block
|
||||
type="secondary"
|
||||
@click="handleOpenEditingModal(role)"
|
||||
>
|
||||
编辑
|
||||
</VButton>
|
||||
<VButton
|
||||
v-close-popper
|
||||
block
|
||||
type="danger"
|
||||
@click="handleDelete(role)"
|
||||
>
|
||||
删除
|
||||
</VButton>
|
||||
<VButton v-close-popper block @click="handleCloneRole(role)">
|
||||
基于此角色创建
|
||||
</VButton>
|
||||
</template>
|
||||
</VEntity>
|
||||
</li>
|
||||
</ul>
|
||||
</Transition>
|
||||
</VCard>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -33,31 +33,36 @@ await handleFetchSettings();
|
|||
await handleFetchConfigMap();
|
||||
</script>
|
||||
<template>
|
||||
<div class="bg-white p-4">
|
||||
<div>
|
||||
<FormKit
|
||||
v-if="group && formSchema && configMapFormData"
|
||||
:id="group"
|
||||
v-model="configMapFormData[group]"
|
||||
:name="group"
|
||||
:actions="false"
|
||||
:preserve="true"
|
||||
type="form"
|
||||
@submit="handleSaveConfigMap"
|
||||
>
|
||||
<FormKitSchema :schema="formSchema" :data="configMapFormData[group]" />
|
||||
</FormKit>
|
||||
</div>
|
||||
<div v-permission="['system:configmaps:manage']" class="pt-5">
|
||||
<div class="flex justify-start">
|
||||
<VButton
|
||||
:loading="saving"
|
||||
type="secondary"
|
||||
@click="$formkit.submit(group || '')"
|
||||
<Transition appear mode="out-in" name="fade">
|
||||
<div class="bg-white p-4">
|
||||
<div>
|
||||
<FormKit
|
||||
v-if="group && formSchema && configMapFormData"
|
||||
:id="group"
|
||||
v-model="configMapFormData[group]"
|
||||
:name="group"
|
||||
:actions="false"
|
||||
:preserve="true"
|
||||
type="form"
|
||||
@submit="handleSaveConfigMap"
|
||||
>
|
||||
保存
|
||||
</VButton>
|
||||
<FormKitSchema
|
||||
:schema="formSchema"
|
||||
:data="configMapFormData[group]"
|
||||
/>
|
||||
</FormKit>
|
||||
</div>
|
||||
<div v-permission="['system:configmaps:manage']" class="pt-5">
|
||||
<div class="flex justify-start">
|
||||
<VButton
|
||||
:loading="saving"
|
||||
type="secondary"
|
||||
@click="$formkit.submit(group || '')"
|
||||
>
|
||||
保存
|
||||
</VButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Transition>
|
||||
</template>
|
||||
|
|
|
@ -14,6 +14,7 @@ import {
|
|||
VPageHeader,
|
||||
VTabbar,
|
||||
IconSettings,
|
||||
VLoading,
|
||||
} from "@halo-dev/components";
|
||||
import type { SettingForm } from "@halo-dev/api-client";
|
||||
|
||||
|
@ -109,15 +110,13 @@ watch([() => route.name, () => route.params], async () => {
|
|||
></VTabbar>
|
||||
</template>
|
||||
</VCard>
|
||||
<div>
|
||||
<div class="bg-white">
|
||||
<RouterView :key="activeTab" v-slot="{ Component }">
|
||||
<template v-if="Component">
|
||||
<Suspense>
|
||||
<component :is="Component"></component>
|
||||
<template #fallback>
|
||||
<div class="flex h-32 w-full justify-center bg-white">
|
||||
<span class="text-sm text-gray-600">加载中...</span>
|
||||
</div>
|
||||
<VLoading />
|
||||
</template>
|
||||
</Suspense>
|
||||
</template>
|
||||
|
|
|
@ -15,6 +15,7 @@ import {
|
|||
VEntityField,
|
||||
Dialog,
|
||||
VStatusDot,
|
||||
VLoading,
|
||||
} from "@halo-dev/components";
|
||||
import UserEditingModal from "./components/UserEditingModal.vue";
|
||||
import UserPasswordChangeModal from "./components/UserPasswordChangeModal.vue";
|
||||
|
@ -47,6 +48,7 @@ const users = ref<UserList>({
|
|||
hasPrevious: false,
|
||||
totalPages: 0,
|
||||
});
|
||||
const loading = ref(false);
|
||||
const selectedUserNames = ref<string[]>([]);
|
||||
const selectedUser = ref<User>();
|
||||
|
||||
|
@ -56,6 +58,8 @@ let fuse: Fuse<User> | undefined = undefined;
|
|||
|
||||
const handleFetchUsers = async () => {
|
||||
try {
|
||||
loading.value = true;
|
||||
|
||||
const { data } = await apiClient.extension.user.listv1alpha1User({
|
||||
page: users.value.page,
|
||||
size: users.value.size,
|
||||
|
@ -70,6 +74,7 @@ const handleFetchUsers = async () => {
|
|||
console.error("Failed to fetch users", e);
|
||||
} finally {
|
||||
selectedUser.value = undefined;
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -385,111 +390,117 @@ onMounted(() => {
|
|||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<ul class="box-border h-full w-full divide-y divide-gray-100" role="list">
|
||||
<li v-for="(user, index) in searchResults" :key="index">
|
||||
<VEntity :is-selected="checkSelection(user)">
|
||||
<template
|
||||
v-if="currentUserHasPermission(['system:users:manage'])"
|
||||
#checkbox
|
||||
>
|
||||
<input
|
||||
v-model="selectedUserNames"
|
||||
:value="user.metadata.name"
|
||||
class="h-4 w-4 rounded border-gray-300 text-indigo-600"
|
||||
name="post-checkbox"
|
||||
type="checkbox"
|
||||
/>
|
||||
</template>
|
||||
<template #start>
|
||||
<VEntityField>
|
||||
<template #description>
|
||||
<VAvatar
|
||||
:alt="user.spec.displayName"
|
||||
:src="user.spec.avatar"
|
||||
size="md"
|
||||
></VAvatar>
|
||||
</template>
|
||||
</VEntityField>
|
||||
<VEntityField
|
||||
:title="user.spec.displayName"
|
||||
:description="user.metadata.name"
|
||||
:route="{
|
||||
name: 'UserDetail',
|
||||
params: { name: user.metadata.name },
|
||||
}"
|
||||
/>
|
||||
</template>
|
||||
<template #end>
|
||||
<VEntityField>
|
||||
<template #description>
|
||||
<div
|
||||
v-for="(role, roleIndex) in getRoles(user)"
|
||||
:key="roleIndex"
|
||||
class="flex items-center"
|
||||
>
|
||||
<VTag>
|
||||
{{ role }}
|
||||
</VTag>
|
||||
</div>
|
||||
</template>
|
||||
</VEntityField>
|
||||
<VEntityField v-if="user.metadata.deletionTimestamp">
|
||||
<template #description>
|
||||
<VStatusDot v-tooltip="`删除中`" state="warning" animate />
|
||||
</template>
|
||||
</VEntityField>
|
||||
<VEntityField>
|
||||
<template #description>
|
||||
<span class="truncate text-xs tabular-nums text-gray-500">
|
||||
{{ formatDatetime(user.metadata.creationTimestamp) }}
|
||||
</span>
|
||||
</template>
|
||||
</VEntityField>
|
||||
</template>
|
||||
<template
|
||||
v-if="currentUserHasPermission(['system:users:manage'])"
|
||||
#dropdownItems
|
||||
>
|
||||
<VButton
|
||||
v-close-popper
|
||||
block
|
||||
type="secondary"
|
||||
@click="handleOpenCreateModal(user)"
|
||||
<VLoading v-if="loading" />
|
||||
<Transition v-else appear name="fade">
|
||||
<ul
|
||||
class="box-border h-full w-full divide-y divide-gray-100"
|
||||
role="list"
|
||||
>
|
||||
<li v-for="(user, index) in searchResults" :key="index">
|
||||
<VEntity :is-selected="checkSelection(user)">
|
||||
<template
|
||||
v-if="currentUserHasPermission(['system:users:manage'])"
|
||||
#checkbox
|
||||
>
|
||||
修改资料
|
||||
</VButton>
|
||||
<VButton
|
||||
v-close-popper
|
||||
block
|
||||
@click="handleOpenPasswordChangeModal(user)"
|
||||
<input
|
||||
v-model="selectedUserNames"
|
||||
:value="user.metadata.name"
|
||||
class="h-4 w-4 rounded border-gray-300 text-indigo-600"
|
||||
name="post-checkbox"
|
||||
type="checkbox"
|
||||
/>
|
||||
</template>
|
||||
<template #start>
|
||||
<VEntityField>
|
||||
<template #description>
|
||||
<VAvatar
|
||||
:alt="user.spec.displayName"
|
||||
:src="user.spec.avatar"
|
||||
size="md"
|
||||
></VAvatar>
|
||||
</template>
|
||||
</VEntityField>
|
||||
<VEntityField
|
||||
:title="user.spec.displayName"
|
||||
:description="user.metadata.name"
|
||||
:route="{
|
||||
name: 'UserDetail',
|
||||
params: { name: user.metadata.name },
|
||||
}"
|
||||
/>
|
||||
</template>
|
||||
<template #end>
|
||||
<VEntityField>
|
||||
<template #description>
|
||||
<div
|
||||
v-for="(role, roleIndex) in getRoles(user)"
|
||||
:key="roleIndex"
|
||||
class="flex items-center"
|
||||
>
|
||||
<VTag>
|
||||
{{ role }}
|
||||
</VTag>
|
||||
</div>
|
||||
</template>
|
||||
</VEntityField>
|
||||
<VEntityField v-if="user.metadata.deletionTimestamp">
|
||||
<template #description>
|
||||
<VStatusDot v-tooltip="`删除中`" state="warning" animate />
|
||||
</template>
|
||||
</VEntityField>
|
||||
<VEntityField>
|
||||
<template #description>
|
||||
<span class="truncate text-xs tabular-nums text-gray-500">
|
||||
{{ formatDatetime(user.metadata.creationTimestamp) }}
|
||||
</span>
|
||||
</template>
|
||||
</VEntityField>
|
||||
</template>
|
||||
<template
|
||||
v-if="currentUserHasPermission(['system:users:manage'])"
|
||||
#dropdownItems
|
||||
>
|
||||
修改密码
|
||||
</VButton>
|
||||
<VButton
|
||||
v-if="
|
||||
userStore.currentUser?.metadata.name !== user.metadata.name
|
||||
"
|
||||
v-close-popper
|
||||
block
|
||||
@click="handleOpenGrantPermissionModal(user)"
|
||||
>
|
||||
分配角色
|
||||
</VButton>
|
||||
<VButton
|
||||
v-if="
|
||||
userStore.currentUser?.metadata.name !== user.metadata.name
|
||||
"
|
||||
v-close-popper
|
||||
block
|
||||
type="danger"
|
||||
@click="handleDelete(user)"
|
||||
>
|
||||
删除
|
||||
</VButton>
|
||||
</template>
|
||||
</VEntity>
|
||||
</li>
|
||||
</ul>
|
||||
<VButton
|
||||
v-close-popper
|
||||
block
|
||||
type="secondary"
|
||||
@click="handleOpenCreateModal(user)"
|
||||
>
|
||||
修改资料
|
||||
</VButton>
|
||||
<VButton
|
||||
v-close-popper
|
||||
block
|
||||
@click="handleOpenPasswordChangeModal(user)"
|
||||
>
|
||||
修改密码
|
||||
</VButton>
|
||||
<VButton
|
||||
v-if="
|
||||
userStore.currentUser?.metadata.name !== user.metadata.name
|
||||
"
|
||||
v-close-popper
|
||||
block
|
||||
@click="handleOpenGrantPermissionModal(user)"
|
||||
>
|
||||
分配角色
|
||||
</VButton>
|
||||
<VButton
|
||||
v-if="
|
||||
userStore.currentUser?.metadata.name !== user.metadata.name
|
||||
"
|
||||
v-close-popper
|
||||
block
|
||||
type="danger"
|
||||
@click="handleDelete(user)"
|
||||
>
|
||||
删除
|
||||
</VButton>
|
||||
</template>
|
||||
</VEntity>
|
||||
</li>
|
||||
</ul>
|
||||
</Transition>
|
||||
|
||||
<template #footer>
|
||||
<div class="bg-white sm:flex sm:items-center sm:justify-end">
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import "@halo-dev/richtext-editor/dist/style.css";
|
||||
import "@halo-dev/components/dist/style.css";
|
||||
import "@/styles/tailwind.css";
|
||||
import "@/styles/index.css";
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
.fade-enter-active {
|
||||
@apply duration-200 ease-out;
|
||||
}
|
||||
.fade-enter-from {
|
||||
@apply opacity-0;
|
||||
}
|
||||
.fade-enter-to {
|
||||
@apply opacity-100;
|
||||
}
|
||||
.fade-leave-active {
|
||||
@apply duration-200 ease-in;
|
||||
}
|
||||
.fade-leave-from {
|
||||
@apply opacity-100;
|
||||
}
|
||||
.fade-leave-to {
|
||||
@apply opacity-0;
|
||||
}
|
Loading…
Reference in New Issue