refactor: theme page ui

Signed-off-by: Ryan Wang <i@ryanc.cc>
pull/581/head
Ryan Wang 2022-05-30 16:36:34 +08:00
parent 558b8a3778
commit 76ed305ae5
11 changed files with 491 additions and 139 deletions

View File

@ -1,4 +1,5 @@
<script lang="ts" setup>
import type { PropType } from "vue";
import { computed, ref } from "vue";
import { IconClose } from "@/core/icons";
@ -18,6 +19,9 @@ const props = defineProps({
type: Boolean,
default: false,
},
bodyClass: {
type: Object as PropType<string[]>,
},
});
const emit = defineEmits(["update:visible", "close"]);
@ -86,7 +90,7 @@ function handleClose() {
</div>
</div>
</div>
<div class="modal-body">
<div :class="bodyClass" class="modal-body">
<slot />
</div>
<div v-if="$slots.footer" class="modal-footer">

View File

@ -67,6 +67,10 @@ import IconPhone from "~icons/ri/smartphone-line";
import IconTablet from "~icons/ri/tablet-line";
// @ts-ignore
import IconUserFollow from "~icons/ri/user-follow-line";
// @ts-ignore
import IconExchange from "~icons/ri/exchange-line";
// @ts-ignore
import IconGitHub from "~icons/ri/github-fill";
export {
IconDashboard,
@ -103,4 +107,6 @@ export {
IconPhone,
IconTablet,
IconUserFollow,
IconExchange,
IconGitHub,
};

View File

@ -1,7 +1,6 @@
import {
IconBookRead,
IconDashboard,
IconEye,
IconFolder,
IconListSettings,
IconMessage,
@ -66,7 +65,7 @@ export const menus: MenuGroupType[] = [
items: [
{
name: "主题",
path: "/themes",
path: "/theme",
icon: IconPalette,
},
{
@ -74,11 +73,6 @@ export const menus: MenuGroupType[] = [
path: "/menus",
icon: IconListSettings,
},
{
name: "可视化",
path: "/visual",
icon: IconEye,
},
],
},
{

View File

@ -11,13 +11,14 @@ import TagList from "../views/contents/posts/tags/TagList.vue";
import CommentList from "../views/contents/comments/CommentList.vue";
import AttachmentList from "../views/contents/attachments/AttachmentList.vue";
import ThemeList from "../views/interface/themes/ThemeList.vue";
import ThemeDetail from "../views/interface/themes/ThemeDetail.vue";
import MenuList from "../views/interface/menus/MenuList.vue";
import Visual from "../views/interface/visual/Visual.vue";
import PluginList from "../views/system/plugins/PluginList.vue";
import PluginDetail from "../views/system/plugins/PluginDetail.vue";
import UserList from "../views/system/users/UserList.vue";
import RoleList from "../views/system/roles/RoleList.vue";
import Profile from "../views/system/users/Profile.vue";
import GeneralSettings from "../views/system/settings/GeneralSettings.vue";
import NotificationSettings from "../views/system/settings/NotificationSettings.vue";
@ -107,13 +108,24 @@ export const routes: Array<RouteRecordRaw> = [
],
},
{
path: "/themes",
path: "/theme",
component: BasicLayout,
children: [
{
path: "",
name: "Themes",
component: ThemeList,
name: "Theme",
component: ThemeDetail,
},
],
},
{
path: "/theme/visual",
component: BlankLayout,
children: [
{
path: "",
name: "ThemeVisual",
component: Visual,
},
],
},
@ -128,17 +140,6 @@ export const routes: Array<RouteRecordRaw> = [
},
],
},
{
path: "/visual",
component: BlankLayout,
children: [
{
path: "",
name: "Visual",
component: Visual,
},
],
},
{
path: "/plugins",
component: BasicLayout,
@ -175,6 +176,11 @@ export const routes: Array<RouteRecordRaw> = [
name: "Profile",
component: Profile,
},
{
path: "roles",
name: "Roles",
component: RoleList,
},
],
},
{

View File

@ -0,0 +1,316 @@
<script lang="ts" setup>
import { VPageHeader } from "@/components/base/header";
import { VButton } from "@/components/base/button";
import { VSpace } from "@/components/base/space";
import { VCard } from "@/components/base/card";
import { VModal } from "@/components/base/modal";
import { VInput } from "@/components/base/input";
import { VTag } from "@/components/base/tag";
import { VTextarea } from "@/components/base/textarea";
import { VTabbar } from "@/components/base/tabs";
import {
IconArrowRight,
IconExchange,
IconEye,
IconGitHub,
IconPalette,
} from "@/core/icons";
import { ref } from "vue";
import { themes } from "@/views/interface/themes/themes-mock";
const currentTheme = ref(themes[0]);
const changeTheme = ref(false);
const themeActiveId = ref("detail");
// eslint-disable-next-line
const handleChangeTheme = (theme: any) => {
currentTheme.value = theme;
changeTheme.value = false;
};
</script>
<template>
<VModal
v-model:visible="changeTheme"
:body-class="['!p-0']"
:width="888"
title="切换主题"
>
<ul class="flex flex-col divide-y divide-gray-100" role="list">
<li
v-for="(theme, index) in themes"
:key="index"
:class="{ 'bg-gray-50': theme.activated }"
class="relative cursor-pointer py-4 transition-all hover:bg-gray-100"
@click="handleChangeTheme(theme)"
>
<div class="flex items-center">
<div
v-show="theme.activated"
class="absolute inset-y-0 left-0 w-0.5 bg-themeable-primary"
></div>
<div class="w-40 px-4">
<div
class="group aspect-w-4 aspect-h-3 block w-full overflow-hidden rounded border bg-gray-100"
>
<img
:src="theme.screenshots"
alt=""
class="pointer-events-none object-cover group-hover:opacity-75"
/>
</div>
</div>
<div class="flex-1">
<VSpace align="start" direction="column" spacing="xs">
<div class="flex items-center gap-2">
<span class="text-lg font-medium text-gray-900">
{{ theme.name }}
</span>
<VTag v-if="theme.activated"></VTag>
</div>
<div>
<span class="text-sm text-gray-400">{{ theme.version }}</span>
</div>
</VSpace>
</div>
<div class="px-4">
<VSpace spacing="lg">
<div>
<span class="text-sm text-gray-400 hover:text-blue-600">
{{ theme.author.name }}
</span>
</div>
<div v-if="theme.repo">
<a
:href="theme.repo"
class="text-gray-900 hover:text-blue-600"
target="_blank"
>
<IconGitHub />
</a>
</div>
<div>
<IconArrowRight class="text-gray-900" />
</div>
</VSpace>
</div>
</div>
</li>
</ul>
<template #footer>
<VButton @click="changeTheme = false">关闭</VButton>
</template>
</VModal>
<VPageHeader :title="currentTheme.name">
<template #icon>
<IconPalette class="mr-2 self-center" />
</template>
<template #actions>
<VSpace>
<VButton size="sm" type="default" @click="changeTheme = true">
<template #icon>
<IconExchange class="h-full w-full" />
</template>
切换主题
</VButton>
<VButton v-if="!currentTheme.activated" size="sm" type="primary">
启用
</VButton>
<VButton :route="{ name: 'ThemeVisual' }" type="secondary">
<template #icon>
<IconEye class="h-full w-full" />
</template>
可视化编辑
</VButton>
</VSpace>
</template>
</VPageHeader>
<div class="m-0 md:m-4">
<VCard :body-class="['!p-0']">
<template #header>
<VTabbar
v-model:active-id="themeActiveId"
:items="[
{ id: 'detail', label: '详情' },
{ id: 'settings', label: '基础设置' },
]"
class="w-full"
type="outline"
></VTabbar>
</template>
<div v-if="themeActiveId === 'detail'">
<div class="px-4 py-4 sm:px-6">
<div class="flex flex-row gap-3">
<div v-if="currentTheme.logo">
<div
class="h-12 w-12 overflow-hidden rounded border bg-white hover:shadow-sm"
>
<img
:alt="currentTheme.name"
:src="currentTheme.logo"
class="h-full w-full"
/>
</div>
</div>
<div>
<h3 class="text-lg font-medium leading-6 text-gray-900">
{{ currentTheme.name }}
</h3>
<p class="mt-1 flex max-w-2xl items-center gap-2">
<span class="text-sm text-gray-500">
{{ currentTheme.version }}
</span>
<VTag>
{{ currentTheme.activated ? "当前启用" : "未启用" }}
</VTag>
</p>
</div>
</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-3 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-2 sm:mt-0">
{{ currentTheme.id }}
</dd>
</div>
<div
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">作者</dt>
<dd class="mt-1 text-sm text-gray-900 sm:col-span-2 sm:mt-0">
{{ currentTheme.author.name }}
</dd>
</div>
<div
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">网站</dt>
<dd class="mt-1 text-sm text-gray-900 sm:col-span-2 sm:mt-0">
<a :href="currentTheme.website" target="_blank">
{{ currentTheme.website }}
</a>
</dd>
</div>
<div
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">源码仓库</dt>
<dd class="mt-1 text-sm text-gray-900 sm:col-span-2 sm:mt-0">
<a :href="currentTheme.repo" target="_blank">
{{ currentTheme.repo }}
</a>
</dd>
</div>
<div
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">当前版本</dt>
<dd class="mt-1 text-sm text-gray-900 sm:col-span-2 sm:mt-0">
{{ currentTheme.version }}
</dd>
</div>
<div
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">Halo 版本要求</dt>
<dd class="mt-1 text-sm text-gray-900 sm:col-span-2 sm:mt-0">
{{ currentTheme.require }}
</dd>
</div>
<div
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">存储位置</dt>
<dd class="mt-1 text-sm text-gray-900 sm:col-span-2 sm:mt-0">
{{ currentTheme.themePath }}
</dd>
</div>
<div
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">插件依赖</dt>
<dd class="mt-1 text-sm text-gray-900 sm:col-span-2 sm:mt-0">
</dd>
</div>
</dl>
</div>
</div>
<div v-if="themeActiveId === 'settings'">
<form>
<div class="space-y-6 divide-y divide-gray-100 sm:space-y-5">
<div
class="px-4 sm:grid sm:grid-cols-3 sm:items-start sm:gap-4 sm:pt-5"
>
<label
class="block text-sm font-medium text-gray-700 sm:mt-px sm:pt-2"
>
侧边栏宽度
</label>
<div class="mt-1 sm:col-span-2 sm:mt-0">
<div class="flex max-w-lg shadow-sm">
<VInput />
</div>
</div>
</div>
<div
class="px-4 sm:grid sm:grid-cols-3 sm:items-start sm:gap-4 sm:pt-5"
>
<label
class="block text-sm font-medium text-gray-700 sm:mt-px sm:pt-2"
>
侧边栏背景图
</label>
<div class="mt-1 sm:col-span-2 sm:mt-0">
<div class="flex max-w-lg shadow-sm">
<VInput />
</div>
</div>
</div>
<div
class="px-4 sm:grid sm:grid-cols-3 sm:items-start sm:gap-4 sm:pt-5"
>
<label
class="block text-sm font-medium text-gray-700 sm:mt-px sm:pt-2"
>
右上角图标
</label>
<div class="mt-1 sm:col-span-2 sm:mt-0">
<div class="flex max-w-lg shadow-sm">
<VInput />
</div>
</div>
</div>
<div
class="px-4 sm:grid sm:grid-cols-3 sm:items-start sm:gap-4 sm:pt-5"
>
<label
class="block text-sm font-medium text-gray-700 sm:mt-px sm:pt-2"
>
文章代码高亮语言
</label>
<div class="mt-1 sm:col-span-2 sm:mt-0">
<div class="flex max-w-lg shadow-sm">
<VTextarea modelValue="Halo" />
</div>
</div>
</div>
</div>
<div class="pt-5">
<div class="flex justify-start p-4">
<VButton type="secondary"> 保存</VButton>
</div>
</div>
</form>
</div>
</VCard>
</div>
</template>

View File

@ -1,43 +0,0 @@
<script lang="ts" setup>
import { VPageHeader } from "@/components/base/header";
import { VButton } from "@/components/base/button";
import { VCard } from "@/components/base/card";
import { IconPalette } from "@/core/icons";
import { themes } from "@/views/interface/themes/themes-mock";
</script>
<template>
<VPageHeader title="主题">
<template #icon>
<IconPalette class="mr-2 self-center" />
</template>
<template #actions>
<VButton type="secondary">安装</VButton>
</template>
</VPageHeader>
<div class="m-4">
<ul
class="grid grid-cols-1 gap-x-2 gap-y-3 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-6"
role="list"
>
<li v-for="(theme, index) in themes" :key="index" class="relative">
<VCard :body-class="['!p-0']">
<div
class="group aspect-w-10 aspect-h-7 block w-full cursor-pointer overflow-hidden bg-gray-100"
>
<img
:src="theme.screenshots"
alt=""
class="pointer-events-none object-cover group-hover:opacity-75"
/>
</div>
<p
class="pointer-events-none block truncate px-2 py-1 text-sm font-medium text-gray-700"
>
{{ theme.name }}
</p>
</VCard>
</li>
</ul>
</div>
</template>

View File

@ -1,4 +1,52 @@
export const themes = [
{
id: "caicai_anatole",
name: "Anatole",
website: "https://github.com/halo-dev/halo-theme-anatole",
branch: "master",
repo: "https://github.com/halo-dev/halo-theme-anatole",
updateStrategy: "RELEASE",
description: "A other Halo theme",
logo: "https://avatars1.githubusercontent.com/u/1811819?s=460&v=4",
version: "1.2.0",
require: "1.3.0",
author: {
name: "Caicai",
website: "https://www.caicai.me",
avatar: null,
},
themePath: "/root/.halo/templates/themes/caicai_anatole",
folderName: "caicai_anatole",
hasOptions: true,
screenshots: "https://demo.halo.run/themes/caicai_anatole/screenshot.png",
postMetaField: null,
sheetMetaField: null,
activated: true,
},
{
id: "flex-block",
name: "flex-block",
website: "https://github.com/guiyunweb/halo-theme-flex-block",
branch: "master",
repo: "https://github.com/guiyunweb/halo-theme-flex-block",
updateStrategy: "RELEASE",
description: "flex-block主题修改",
logo: "https://image.guiyunweb.com/2021/84306692_p0_master1200_1608708448480_1625562784578_1625565770497.jpg",
version: "2.0",
require: null,
author: {
name: "Guiyunweb",
website: "https://guiyunweb.com",
avatar: null,
},
themePath: "/root/.halo/templates/themes/flex-block",
folderName: "flex-block",
hasOptions: true,
screenshots: "https://demo.halo.run/themes/flex-block/screenshot.png",
postMetaField: null,
sheetMetaField: null,
activated: false,
},
{
id: "cetr_sagiri",
name: "Sagiri",
@ -47,30 +95,6 @@ export const themes = [
sheetMetaField: null,
activated: false,
},
{
id: "caicai_anatole",
name: "Anatole",
website: "https://github.com/halo-dev/halo-theme-anatole",
branch: "master",
repo: "https://github.com/halo-dev/halo-theme-anatole",
updateStrategy: "RELEASE",
description: "A other Halo theme",
logo: "https://avatars1.githubusercontent.com/u/1811819?s=460&v=4",
version: "1.2.0",
require: "1.3.0",
author: {
name: "Caicai",
website: "https://www.caicai.me",
avatar: null,
},
themePath: "/root/.halo/templates/themes/caicai_anatole",
folderName: "caicai_anatole",
hasOptions: true,
screenshots: "https://demo.halo.run/themes/caicai_anatole/screenshot.png",
postMetaField: null,
sheetMetaField: null,
activated: false,
},
{
id: "louie_senpai_siren",
name: "Siren",
@ -459,30 +483,6 @@ export const themes = [
sheetMetaField: null,
activated: false,
},
{
id: "flex-block",
name: "flex-block",
website: "https://github.com/guiyunweb/halo-theme-flex-block",
branch: "master",
repo: "https://github.com/guiyunweb/halo-theme-flex-block",
updateStrategy: "RELEASE",
description: "flex-block主题修改",
logo: "https://image.guiyunweb.com/2021/84306692_p0_master1200_1608708448480_1625562784578_1625565770497.jpg",
version: "2.0",
require: null,
author: {
name: "Guiyunweb",
website: "https://guiyunweb.com",
avatar: null,
},
themePath: "/root/.halo/templates/themes/flex-block",
folderName: "flex-block",
hasOptions: true,
screenshots: "https://demo.halo.run/themes/flex-block/screenshot.png",
postMetaField: null,
sheetMetaField: null,
activated: true,
},
{
id: "imzeuk_zozo",
name: "Zozo",

View File

@ -2,13 +2,30 @@
import { VButton } from "@/components/base/button";
import { VInput } from "@/components/base/input";
import { VOption, VSelect } from "@/components/base/select";
import { VModal } from "@/components/base/modal";
import { VTextarea } from "@/components/base/textarea";
import { VCard } from "@/components/base/card";
import { VTabbar, VTabItem, VTabs } from "@/components/base/tabs";
import { computed, ref } from "vue";
import { computed, onMounted, ref } from "vue";
import { IconComputer, IconPhone, IconTablet } from "@/core/icons";
const activeId = ref("general");
const deviceActiveId = ref("desktop");
const attachmentSelectVisible = ref(false);
const devices = ref([
{
id: "desktop",
icon: IconComputer,
},
{
id: "tablet",
icon: IconTablet,
},
{
id: "phone",
icon: IconPhone,
},
]);
const iframeClasses = computed(() => {
if (deviceActiveId.value === "desktop") {
@ -20,11 +37,74 @@ const iframeClasses = computed(() => {
// phone
return "w-96 h-[50rem]";
});
const attachments = Array.from(new Array(50), (_, index) => index).map(
(index) => {
return {
id: index,
name: `attachment-${index}`,
url: `https://picsum.photos/1000/700?random=${index}`,
size: "1.2MB",
type: "image/png",
strategy: "本地存储",
};
}
);
onMounted(() => {
window.addEventListener(
"message",
function receiveMessageFromIframePage(event) {
if (event.data === "select_image") {
attachmentSelectVisible.value = true;
}
},
false
);
});
</script>
<template>
<VModal
v-model:visible="attachmentSelectVisible"
:width="1240"
title="选择附件"
>
<div class="w-full">
<ul
class="grid grid-cols-2 gap-x-2 gap-y-3 sm:grid-cols-3 md:grid-cols-2 xl:grid-cols-8 2xl:grid-cols-8"
role="list"
>
<li
v-for="(attachment, index) in attachments"
:key="index"
class="relative"
>
<VCard :body-class="['!p-0']">
<div
class="group aspect-w-10 aspect-h-7 block w-full cursor-pointer overflow-hidden bg-gray-100"
>
<img
:src="attachment.url"
alt=""
class="pointer-events-none object-cover group-hover:opacity-75"
/>
</div>
<p
class="pointer-events-none block truncate px-2 py-1 text-sm font-medium text-gray-700"
>
{{ attachment.name }}
</p>
</VCard>
</li>
</ul>
</div>
<template #footer>
<VButton type="secondary">确定</VButton>
</template>
</VModal>
<div class="flex h-screen">
<div class="h-full w-96 overflow-y-auto bg-white drop-shadow-sm">
<VTabs v-model:active-id="activeId">
<VTabs v-model:active-id="activeId" type="outline">
<VTabItem id="general" class="p-3" label="基础设置">
<form>
<div class="space-y-8 divide-y divide-gray-200 sm:space-y-5">
@ -108,20 +188,7 @@ const iframeClasses = computed(() => {
<div>
<VTabbar
v-model:active-id="deviceActiveId"
:items="[
{
id: 'desktop',
icon: IconComputer,
},
{
id: 'tablet',
icon: IconTablet,
},
{
id: 'phone',
icon: IconPhone,
},
]"
:items="devices"
type="outline"
></VTabbar>
</div>

View File

@ -37,9 +37,9 @@ console.log(plugin);
</p>
</div>
<div class="border-t border-gray-200">
<dl>
<dl class="divide-y divide-gray-100">
<div
class="bg-white px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6"
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">名称</dt>
<dd class="mt-1 text-sm text-gray-900 sm:col-span-2 sm:mt-0">
@ -47,7 +47,7 @@ console.log(plugin);
</dd>
</div>
<div
class="bg-gray-50 px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6"
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">插件类别</dt>
<dd class="mt-1 text-sm text-gray-900 sm:col-span-2 sm:mt-0">
@ -59,7 +59,7 @@ console.log(plugin);
</dd>
</div>
<div
class="bg-white px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6"
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">版本</dt>
<dd class="mt-1 text-sm text-gray-900 sm:col-span-2 sm:mt-0">
@ -67,7 +67,7 @@ console.log(plugin);
</dd>
</div>
<div
class="bg-gray-50 px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6"
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">Halo 版本要求</dt>
<dd class="mt-1 text-sm text-gray-900 sm:col-span-2 sm:mt-0">
@ -75,7 +75,7 @@ console.log(plugin);
</dd>
</div>
<div
class="bg-white px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6"
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">提供方</dt>
<dd class="mt-1 text-sm text-gray-900 sm:col-span-2 sm:mt-0">
@ -85,7 +85,7 @@ console.log(plugin);
</dd>
</div>
<div
class="bg-gray-50 px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6"
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">协议</dt>
<dd class="mt-1 text-sm text-gray-900 sm:col-span-2 sm:mt-0">
@ -93,7 +93,7 @@ console.log(plugin);
</dd>
</div>
<div
class="bg-white px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6"
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">模型定义</dt>
<dd class="mt-1 text-sm text-gray-900 sm:col-span-2 sm:mt-0"></dd>

View File

@ -0,0 +1,2 @@
<script lang="ts" setup></script>
<template>Role</template>

View File

@ -31,7 +31,7 @@ const handleRouteToDetail = (username: string) => {
</template>
<template #actions>
<VSpace>
<VButton size="sm" type="default">
<VButton :route="{ name: 'Roles' }" size="sm" type="default">
<template #icon>
<IconUserFollow class="h-full w-full" />
</template>