chore: 集成vben

pull/361/head
xiaojunnuo 2025-03-07 18:01:51 +08:00
parent 8fcabc5e9f
commit 9557fc799e
26 changed files with 232 additions and 169 deletions

View File

@ -58,7 +58,7 @@ export abstract class CertApplyBasePlugin extends AbstractTaskPlugin {
},
required: false,
order: 100,
helper: "PFX、jks格式证书是否加密\njks必须设置密码不传则默认123456",
helper: "PFX、jks格式证书是否加密\njks必须设置密码不传则默认123456\npfx不传则为空密码",
})
pfxPassword!: string;

View File

@ -109,8 +109,8 @@ function createService() {
error.message += `: ${error.response?.config?.url}`;
errorLog(error, error?.response?.config?.showErrorNotify);
if (status === 401) {
const userStore = useUserStore();
userStore.logout();
// const userStore = useUserStore();
// userStore.logout();
}
if (error?.config?.onError) {

View File

@ -6,7 +6,7 @@
<td style="width: 100px; text-align: center">记录类型</td>
<td style="width: 250px">请设置CNAME记录验证成功以后不要删除</td>
<td style="width: 120px" class="center">状态</td>
<td style="width: 80px" class="center">操作</td>
<td style="width: 90px" class="center">操作</td>
</tr>
</thead>
<template v-for="key in domains" :key="key">

View File

@ -13,7 +13,7 @@ const slots = defineSlots();
<div class="tutorial-button pointer" @click="open">
<template v-if="!slots.default">
<fs-icon icon="ant-design:question-circle-outlined"></fs-icon>
<div class="ml-5">使用教程</div>
<div class="hidden md:block ml-0.5">使用教程</div>
</template>
<slot></slot>
<a-modal v-model:open="openedRef" class="tutorial-modal" width="90%">

View File

@ -3,10 +3,10 @@
<contextHolder />
<fs-icon icon="mingcute:vip-1-line" :title="text.title" />
<div v-if="mode !== 'icon'" class="text">
<div v-if="mode !== 'icon'" class="text hidden md:block ml-0.5">
<a-tooltip>
<template #title> {{ text.title }}</template>
<span>{{ text.name }}</span>
<span class="">{{ text.name }}</span>
</a-tooltip>
</div>
</div>
@ -390,7 +390,6 @@ onMounted(() => {
}
.text {
margin-left: 5px;
}
}

View File

@ -1,20 +1,24 @@
import { createI18n } from "vue-i18n";
import en from "./locale/en";
import zh from "./locale/zh_CN";
const messages = {
en: {
import { SupportedLanguagesType } from "/@/vben/locales";
export const messages = {
"en-US": {
label: "English",
...en
},
"zh-cn": {
"zh-CN": {
label: "简体中文",
...zh
}
};
export default createI18n({
legacy: false,
locale: "zh-cn",
fallbackLocale: "zh-cn",
messages
});
// export default createI18n({
// legacy: false,
// locale: "zh-cn",
// fallbackLocale: "zh-cn",
// messages
// });
export async function loadMessages(lang: SupportedLanguagesType) {
return messages[lang];
}

View File

@ -1,6 +1,6 @@
<template>
<div class="flex flex-between full-w">
<div class="flex">
<div class="flex flex-between w-full text-sm p-5 bg-neutral-100 dark:bg-neutral-900">
<div class="flex items-center">
<span v-if="!settingStore.isComm">
<span>Powered by</span>
<a> handsfree.work </a>
@ -26,7 +26,7 @@ import { computed, onMounted, ref } from "vue";
import { useSettingStore } from "/@/store/modules/settings";
defineOptions({
name: "Footer"
name: "PageFooter"
});
const version = ref(import.meta.env.VITE_APP_VERSION);

View File

@ -8,28 +8,29 @@ import { useUserStore } from "/@/store/modules/user";
import VipButton from "/@/components/vip-button/index.vue";
import TutorialButton from "/@/components/tutorial/index.vue";
import { useSettingStore } from "/@/store/modules/settings";
import Footer from "./components/footer/index.vue";
import PageFooter from "./components/footer/index.vue";
import { useRouter } from "vue-router";
const userStore = useUserStore();
const accessStore = useAccessStore();
const router = useRouter();
const menus = computed(() => [
// {
// handler: () => {
// openWindow(VBEN_DOC_URL, {
// target: "_blank"
// });
// },
// icon: BookOpenText,
// text: $t("ui.widgets.document")
// }
{
handler: () => {
router.push("/certd/mine/user-profile");
},
icon: "fa-solid:book",
text: "账号信息"
}
]);
const avatar = computed(() => {
return userStore.userInfo?.avatar ?? preferences.app.defaultAvatar;
const avt = userStore.getUserInfo?.avatar;
return avt ? `/api/basic/file/download?key=${avt}` : "";
});
async function handleLogout() {
await userStore.logout(true);
userStore.logout(true);
}
const settingStore = useSettingStore();
@ -56,21 +57,21 @@ onMounted(async () => {
<template>
<BasicLayout @clear-preferences-and-logout="handleLogout">
<template #user-dropdown>
<UserDropdown :avatar :menus :text="userStore.userInfo?.nickName" description="development@handsfree.work" tag-text="Pro" @logout="handleLogout" />
<UserDropdown :avatar="avatar" :menus="menus" :text="userStore.userInfo?.nickName || userStore.userInfo?.username" description="" tag-text="" @logout="handleLogout" />
</template>
<template #lock-screen>
<LockScreen :avatar @to-login="handleLogout" />
</template>
<template #header-right-0>
<div class="hover:bg-accent ml-1 mr-2 cursor-pointer rounded-full p-1.5 pl-3 pr-3">
<div class="hover:bg-accent ml-1 mr-2 cursor-pointer rounded-full hidden md:block">
<tutorial-button v-if="!settingStore.isComm" class="flex-center header-btn" />
</div>
<div class="hover:bg-accent ml-1 mr-2 cursor-pointer rounded-full p-1.5 pl-3 pr-3">
<div class="hover:bg-accent ml-1 mr-2 cursor-pointer rounded-full">
<vip-button class="flex-center header-btn" mode="nav" />
</div>
</template>
<template #footer>
<Footer></Footer>
<PageFooter></PageFooter>
</template>
</BasicLayout>
</template>
@ -78,5 +79,6 @@ onMounted(async () => {
<style lang="less">
.header-btn {
font-size: 14px;
padding: 5px;
}
</style>

View File

@ -3,7 +3,8 @@ import App from "./App.vue";
// import Antd from "ant-design-vue";
import Antd from "./plugin/antdv-async/index";
import "./style/common.less";
import i18n from "./i18n";
import { loadMessages } from "./i18n";
import { i18n } from "/@/vben/locales";
import components from "./components";
import router from "./router";
import plugin from "./plugin/";
@ -17,9 +18,9 @@ async function bootstrap() {
const app = createApp(App);
// app.use(Antd);
app.use(Antd);
await setupVben(app);
await setupVben(app, { loadMessages });
app.use(router);
app.use(i18n);
// app.use(i18n);
// app.use(store);
app.use(components);
app.use(plugin, { i18n });

View File

@ -7,15 +7,17 @@ import Empty from "ant-design-vue/es/empty";
import Avatar from "ant-design-vue/es/avatar";
import Steps from "ant-design-vue/es/steps";
import Select from "ant-design-vue/es/select";
import PageHeader from "ant-design-vue/es/page-header";
export default {
install(app: any) {
app.use(Input);
app.use(Button);
app.component("ADivider", Divider);
app.component("ABadge", Badge);
app.component("AEmpty", Empty);
app.component("AAvatar", Avatar);
app.use(Divider);
app.use(Badge);
app.use(Empty);
app.use(Avatar);
app.use(PageHeader);
app.use(Steps);
app.use(Select);
@ -165,8 +167,8 @@ export default {
defineAsyncComponent(() => import("ant-design-vue/es/tree-select"))
);
app.component(
"AToar",
defineAsyncComponent(() => import("ant-design-vue/es/tree-select"))
"ATour",
defineAsyncComponent(() => import("ant-design-vue/es/tour"))
);
app.component(
@ -186,5 +188,40 @@ export default {
"AProgress",
defineAsyncComponent(() => import("ant-design-vue/es/progress"))
);
app.component(
"ATimelineItem",
defineAsyncComponent(() => import("ant-design-vue/es/timeline/TimelineItem"))
);
app.component(
"ATimeline",
defineAsyncComponent(() => import("ant-design-vue/es/timeline/Timeline"))
);
app.component(
"APageHeader",
defineAsyncComponent(() => import("ant-design-vue/es/page-header/index"))
);
app.component(
"APopover",
defineAsyncComponent(() => import("ant-design-vue/es/popover"))
);
app.component(
"APopconfirm",
defineAsyncComponent(() => import("ant-design-vue/es/popconfirm"))
);
app.component(
"ACollapse",
defineAsyncComponent(() => import("ant-design-vue/es/collapse"))
);
app.component(
"ADescriptions",
defineAsyncComponent(() => import("ant-design-vue/es/descriptions"))
);
app.component(
"ADescriptionsItem",
defineAsyncComponent(async () => {
const m = await import("ant-design-vue/es/descriptions/");
return m.DescriptionsItem;
})
);
}
};

View File

@ -28,7 +28,6 @@ export function registerRouterHook() {
if (!token || token === "undefined") {
return true;
}
// 初始化权限列表
try {
console.log("permission is enabled");

View File

@ -4,17 +4,25 @@ import { getPermissions } from "./api";
import { mitter } from "/@/utils/util.mitt";
import { env } from "/@/utils/util.env";
import { useAccessStore } from "/@/vben/stores";
import { eachTree } from "/@/utils/util.tree";
import util from "/@/plugin/permission/util.permission";
//监听注销事件
mitter.on("app.logout", () => {
const permissionStore = usePermissionStore();
permissionStore.clear();
const accessStore = useAccessStore();
accessStore.setIsAccessChecked(false);
});
mitter.on("app.login", () => {
const permissionStore = useResourceStore();
const accessStore = useAccessStore();
accessStore.setIsAccessChecked(false);
const permissionStore = usePermissionStore();
permissionStore.clear();
permissionStore.init();
// const accessStore = useAccessStore();
// accessStore.setAccessCode([]);
// permissionStore.init();
});
interface PermissionState {
@ -28,7 +36,7 @@ interface PermissionState {
* @param permissionList
* @returns {*}
*/
function formatPermissions(menuTree: Array<any>, permissionList: any[] = []) {
export function formatPermissions(menuTree: Array<any>, permissionList: any[] = []) {
if (menuTree == null) {
menuTree = [];
}

View File

@ -6,7 +6,32 @@ import { useAccessStore } from "/@/vben/stores";
import { generateMenus, startProgress, stopProgress } from "/@/vben/utils";
import { frameworkRoutes } from "/@/router/resolve";
import { useSettingStore } from "/@/store/modules/settings";
import { usePermissionStore } from "/@/plugin/permission/store.permission";
import util from "/@/plugin/permission/util.permission";
import { useUserStore } from "/@/store/modules/user";
function buildAccessedMenus(menus: any) {
if (menus == null) {
return;
}
const list: any = [];
for (const sub of menus) {
if (sub.meta?.permission != null) {
if (!util.hasPermissions(sub.meta.permission)) {
continue;
}
}
const item: any = {
...sub
};
list.push(item);
if (sub.children && sub.children.length > 0) {
item.children = buildAccessedMenus(sub.children);
}
}
return list;
}
/**
*
* @param router
@ -70,7 +95,15 @@ function setupAccessGuard(router: Router) {
// 是否已经生成过动态路由
if (!accessStore.isAccessChecked) {
const accessibleMenus = await generateMenus(frameworkRoutes[0].children, router);
if (accessStore.accessToken) {
const permissionStore = usePermissionStore();
await permissionStore.loadFromRemote();
const userStore = useUserStore();
await userStore.getUserInfoAction();
}
const allMenus = await generateMenus(frameworkRoutes[0].children, router);
const accessibleMenus = buildAccessedMenus(allMenus);
accessStore.setAccessRoutes(frameworkRoutes);
accessStore.setAccessMenus(accessibleMenus);
accessStore.setIsAccessChecked(true);

View File

@ -66,7 +66,8 @@ export const certdResources = [
{
title: "设置",
name: "MineSetting",
path: "/certd/mine",
path: "/certd/setting",
redirect: "/certd/cname/record",
meta: {
icon: "ion:settings-outline",
auth: true,

View File

@ -9,7 +9,7 @@ import { HeaderMenus, PlusInfo, SiteEnv, SiteInfo, SuiteSetting, SysInstallInfo,
import { useUserStore } from "/@/store/modules/user";
import { mitter } from "/@/utils/util.mitt";
import { env } from "/@/utils/util.env";
import { preferences } from "/@/vben/preferences";
import { updatePreferences } from "/@/vben/preferences";
export interface SettingState {
sysPublic?: SysPublicSetting;
@ -140,7 +140,11 @@ export const useSettingStore = defineStore({
this.siteInfo = _.merge({}, defaultSiteInfo, siteInfo);
if (this.siteInfo.logo) {
preferences.logo.source = this.siteInfo.logo;
updatePreferences({
logo: {
source: this.siteInfo.logo
}
});
}
},
async checkUrlBound() {

View File

@ -13,6 +13,8 @@ import { useI18n } from "vue-i18n";
import { mitter } from "/src/utils/util.mitt";
import { resetAllStores, useAccessStore } from "/@/vben/stores";
import { useUserStore as vbenUserStore } from "/@/vben/stores/modules/user";
interface UserState {
userInfo: Nullable<UserInfoRes>;
token?: string;
@ -48,6 +50,8 @@ export const useUserStore = defineStore({
},
setUserInfo(info: UserInfoRes) {
this.userInfo = info;
const userStore = vbenUserStore();
userStore.setUserInfo(info);
LocalStorage.set(USER_INFO_KEY, info);
},
resetState() {
@ -81,6 +85,7 @@ export const useUserStore = defineStore({
// get user info
return await this.onLoginSuccess(loginRes);
} catch (error) {
console.error(error);
return null;
}
},

View File

@ -11,8 +11,8 @@ import "./styles/antd/index.css";
import { useTitle } from "@vueuse/core";
import { setupI18n } from "/@/vben/locales";
export async function setupVben(app: any) {
await setupI18n(app);
export async function setupVben(app: any, { loadMessages }: any) {
await setupI18n(app, { loadMessages });
const store = await initStores(app, { namespace: "fs" });
return { store };

View File

@ -78,7 +78,7 @@ function transformComponent(component: VNode, route: RouteLocationNormalizedLoad
</script>
<template>
<div class="relative h-full">
<div class="relative h-full bg-white dark:bg-black">
<IFrameRouterView />
<RouterView v-slot="{ Component, route }">
<Transition :name="getTransitionName(route)" appear mode="out-in">

View File

@ -73,7 +73,7 @@ function useMixedMenu() {
if (!needSplit.value) {
return menus.value;
}
return menus.value.map((item) => {
return menus.value.map((item: any) => {
return {
...item,
children: []
@ -85,6 +85,10 @@ function useMixedMenu() {
*
*/
const sidebarMenus = computed(() => {
if (preferences.app.isMobile) {
return [...holdMenus.value, ...menus.value];
}
const sideMenus = needSplit.value ? splitSideMenus.value : menus.value;
return [...holdMenus.value, ...sideMenus];
});
@ -116,7 +120,7 @@ function useMixedMenu() {
}
if (!splitSideMenus.value || splitSideMenus.value.length === 0) {
//仍然为空,从所有菜单中查找
const hasChildren = allMenus.value.find((item) => {
const hasChildren = allMenus.value.find((item: any) => {
return item.children && item.children.length > 0;
});
if (hasChildren) {
@ -136,7 +140,7 @@ function useMixedMenu() {
return;
}
const rootMenu = menus.value.find((item) => item.path === key);
const rootMenu = menus.value.find((item: any) => item.path === key);
rootMenuPath.value = rootMenu?.path ?? "";
splitSideMenus.value = rootMenu?.children ?? [];
saveLastSplitSideMenus();
@ -165,7 +169,7 @@ function useMixedMenu() {
function calcSideMenus(path: string = route.path) {
let { rootMenu } = findRootMenuByPath(menus.value, path);
if (!rootMenu) {
rootMenu = menus.value.find((item) => item.path === path);
rootMenu = menus.value.find((item: any) => item.path === path);
}
const result = findRootMenuByPath(rootMenu?.children || [], path, 1);
mixedRootMenuPath.value = result.rootMenuPath ?? "";

View File

@ -9,9 +9,9 @@ const defaultPreferences: Preferences = {
colorWeakMode: false,
compact: false,
contentCompact: "wide",
defaultAvatar: "https://unpkg.com/@vbenjs/static-source@0.1.7/source/avatar-v1.webp",
defaultAvatar: "./static/images/logo/logo.svg",
dynamicTitle: true,
enableCheckUpdates: true,
enableCheckUpdates: false,
enablePreferences: true,
enableRefreshToken: false,
isMobile: false,
@ -65,7 +65,7 @@ const defaultPreferences: Preferences = {
globalSearch: true
},
sidebar: {
autoActivateChild: false,
autoActivateChild: true,
collapsed: false,
collapsedShowTitle: false,
enable: true,
@ -108,9 +108,9 @@ const defaultPreferences: Preferences = {
widget: {
fullscreen: true,
globalSearch: true,
languageToggle: true,
languageToggle: false,
lockScreen: true,
notification: true,
notification: false,
refresh: true,
sidebarToggle: true,
themeToggle: true

View File

@ -1,40 +1,32 @@
<script setup lang="ts">
import type { TabsEmits, TabsProps } from './types';
import type { TabsEmits, TabsProps } from "./types";
import { useForwardPropsEmits } from '/@/vben/composables';
import { ChevronLeft, ChevronRight } from '/@/vben/icons';
import { VbenScrollbar } from '/@/vben/shadcn-ui';
import { useForwardPropsEmits } from "/@/vben/composables";
import { ChevronLeft, ChevronRight } from "/@/vben/icons";
import { VbenScrollbar } from "/@/vben/shadcn-ui";
import { Tabs, TabsChrome } from './components';
import { useTabsDrag } from './use-tabs-drag';
import { useTabsViewScroll } from './use-tabs-view-scroll';
import { Tabs, TabsChrome } from "./components";
import { useTabsDrag } from "./use-tabs-drag";
import { useTabsViewScroll } from "./use-tabs-view-scroll";
interface Props extends TabsProps {}
defineOptions({
name: 'TabsView',
name: "TabsView"
});
const props = withDefaults(defineProps<Props>(), {
contentClass: 'vben-tabs-content',
contentClass: "vben-tabs-content",
draggable: true,
styleType: 'chrome',
wheelable: true,
styleType: "chrome",
wheelable: true
});
const emit = defineEmits<TabsEmits>();
const forward = useForwardPropsEmits(props, emit);
const {
handleScrollAt,
handleWheel,
scrollbarRef,
scrollDirection,
scrollIsAtLeft,
scrollIsAtRight,
showScrollButton,
} = useTabsViewScroll(props);
const { handleScrollAt, handleWheel, scrollbarRef, scrollDirection, scrollIsAtLeft, scrollIsAtRight, showScrollButton } = useTabsViewScroll(props);
function onWheel(e: WheelEvent) {
if (props.wheelable) {
@ -48,13 +40,13 @@ useTabsDrag(props, emit);
</script>
<template>
<div class="flex h-full flex-1 overflow-hidden">
<div class="flex h-full flex-1 overflow-hidden bg-gray-100 dark:bg-black">
<!-- 左侧滚动按钮 -->
<span
v-show="showScrollButton"
:class="{
'hover:bg-muted text-muted-foreground cursor-pointer': !scrollIsAtLeft,
'pointer-events-none opacity-30': scrollIsAtLeft,
'pointer-events-none opacity-30': scrollIsAtLeft
}"
class="border-r px-2"
@click="scrollDirection('left')"
@ -64,27 +56,12 @@ useTabsDrag(props, emit);
<div
:class="{
'pt-[3px]': styleType === 'chrome',
'pt-[3px]': styleType === 'chrome'
}"
class="size-full flex-1 overflow-hidden"
>
<VbenScrollbar
ref="scrollbarRef"
:shadow-bottom="false"
:shadow-top="false"
class="h-full"
horizontal
scroll-bar-class="z-10 hidden "
shadow
shadow-left
shadow-right
@scroll-at="handleScrollAt"
@wheel="onWheel"
>
<TabsChrome
v-if="styleType === 'chrome'"
v-bind="{ ...forward, ...$attrs, ...$props }"
/>
<VbenScrollbar ref="scrollbarRef" :shadow-bottom="false" :shadow-top="false" class="h-full" horizontal scroll-bar-class="z-10 hidden " shadow shadow-left shadow-right @scroll-at="handleScrollAt" @wheel="onWheel">
<TabsChrome v-if="styleType === 'chrome'" v-bind="{ ...forward, ...$attrs, ...$props }" />
<Tabs v-else v-bind="{ ...forward, ...$attrs, ...$props }" />
</VbenScrollbar>
@ -95,7 +72,7 @@ useTabsDrag(props, emit);
v-show="showScrollButton"
:class="{
'hover:bg-muted text-muted-foreground cursor-pointer': !scrollIsAtRight,
'pointer-events-none opacity-30': scrollIsAtRight,
'pointer-events-none opacity-30': scrollIsAtRight
}"
class="hover:bg-muted text-muted-foreground cursor-pointer border-l px-2"
@click="scrollDirection('right')"

View File

@ -18,7 +18,7 @@
<div class="layout">
<div class="layout-left">
<div class="pipeline-container">
<div class="pipeline-container bg-neutral-100 dark:bg-black">
<div class="pipeline">
<v-draggable v-model="pipeline.stages" class="stages" item-key="id" handle=".stage-move-handle" :disabled="!settingStore.isPlus">
<template #header>
@ -95,19 +95,10 @@
<!-- :open="true"-->
<template #content>
<div v-for="(item, index) of task.steps" :key="item.id" class="flex-o w-100">
<span class="ellipsis flex-1 step-title" :class="{ disabled: item.disabled, deleted: item.disabled }">
{{ index + 1 }}. {{ item.title }}
</span>
<span class="ellipsis flex-1 step-title" :class="{ disabled: item.disabled, deleted: item.disabled }"> {{ index + 1 }}. {{ item.title }} </span>
<pi-status-show v-if="!editMode" :status="item.status?.result"></pi-status-show>
<a-tooltip title="强制重新执行此步骤">
<fs-icon
v-if="!editMode"
class="pointer color-blue ml-2"
style="font-size: 16px"
title="强制重新执行此步骤"
icon="icon-park-outline:replay-music"
@click="run(item.id)"
></fs-icon>
<fs-icon v-if="!editMode" class="pointer color-blue ml-2" style="font-size: 16px" title="强制重新执行此步骤" icon="icon-park-outline:replay-music" @click="run(item.id)"></fs-icon>
</a-tooltip>
</div>
</template>
@ -231,7 +222,7 @@
</div>
<div class="layout-right">
<a-page-header title="运行历史" sub-title="" class="logs-block">
<a-page-header title="运行历史" sub-title="" class="logs-block" :ghost="false">
<a-timeline class="mt-10">
<template v-for="item of histories" :key="item.id">
<pi-history-timeline-item
@ -813,7 +804,6 @@ export default defineComponent({
width: 100%;
height: 100%;
position: relative;
background-color: #f0f0f0;
overflow: auto;
}
.pipeline {
@ -821,7 +811,6 @@ export default defineComponent({
left: 0;
top: 0;
height: 100%;
background-color: #f0f0f0;
.stages {
display: flex;
overflow: auto;

View File

@ -1,36 +1,39 @@
<template>
<div class="dashboard-user">
<div class="header-profile">
<div class="avatar">
<a-avatar v-if="userInfo.avatar" size="large" :src="'/api/basic/file/download?&key=' + userInfo.avatar" style="background-color: #eee"> </a-avatar>
<a-avatar v-else size="large" style="background-color: #00b4f5">
{{ userInfo.username }}
</a-avatar>
</div>
<div class="text">
<div class="left">
<div>
<span>您好{{ userInfo.nickName || userInfo.username }} 欢迎使用 {{ siteInfo.title }}</span>
</div>
<div class="flex-o">
<a-tag color="green" class="flex-inline pointer m-0"> <fs-icon icon="ion:time-outline"></fs-icon> {{ now }}</a-tag>
<template v-if="userStore.isAdmin">
<a-divider type="vertical" />
<a-badge :dot="hasNewVersion">
<a-tag color="blue" class="flex-inline pointer m-0" :title="'最新版本:' + latestVersion" @click="openUpgradeUrl()">
<fs-icon icon="ion:rocket-outline" class="mr-5"></fs-icon>
v{{ version }}
</a-tag>
</a-badge>
</template>
<template v-if="settingsStore.isComm">
<a-divider type="vertical" />
<suite-card class="m-0"></suite-card>
</template>
<div class="header-profile flex-wrap bg-white dark:bg-black">
<div class="flex flex-1">
<div class="avatar">
<a-avatar v-if="userInfo.avatar" size="large" :src="'/api/basic/file/download?&key=' + userInfo.avatar" style="background-color: #eee"> </a-avatar>
<a-avatar v-else size="large" style="background-color: #00b4f5">
{{ userInfo.username }}
</a-avatar>
</div>
<div class="text">
<div class="left">
<div>
<span>您好{{ userInfo.nickName || userInfo.username }} 欢迎使用 {{ siteInfo.title }}</span>
</div>
<div class="flex-o">
<a-tag color="green" class="flex-inline pointer m-0"> <fs-icon icon="ion:time-outline"></fs-icon> {{ now }}</a-tag>
<template v-if="userStore.isAdmin">
<a-divider type="vertical" />
<a-badge :dot="hasNewVersion">
<a-tag color="blue" class="flex-inline pointer m-0" :title="'最新版本:' + latestVersion" @click="openUpgradeUrl()">
<fs-icon icon="ion:rocket-outline" class="mr-5"></fs-icon>
v{{ version }}
</a-tag>
</a-badge>
</template>
<template v-if="settingsStore.isComm">
<a-divider type="vertical" />
<suite-card class="m-0"></suite-card>
</template>
</div>
</div>
</div>
</div>
<div class="suggest">
<div class="suggest hidden md:block">
<tutorial-button class="flex-center mt-2">
<a-tooltip title="点击查看详细教程">
<a-tag color="blue" class="flex-center">
@ -53,8 +56,8 @@
</div>
<div class="statistic-data m-20">
<a-row :gutter="20">
<a-col :span="6">
<a-row :gutter="20" class="flex-wrap">
<a-col :md="6" :xs="24">
<statistic-card title="证书流水线数量" :count="count.pipelineCount">
<template v-if="count.pipelineCount === 0" #default>
<div class="flex-center flex-1 flex-col">
@ -67,17 +70,17 @@
</template>
</statistic-card>
</a-col>
<a-col :span="6">
<a-col :md="6" :xs="24">
<statistic-card title="流水线状态" :footer="false">
<pie-count v-if="count.pipelineStatusCount" :data="count.pipelineStatusCount"></pie-count>
</statistic-card>
</a-col>
<a-col :span="6">
<a-col :md="6" :xs="24">
<statistic-card title="最近运行统计" :footer="false">
<day-count v-if="count.historyCountPerDay" :data="count.historyCountPerDay" title="运行次数"></day-count>
</statistic-card>
</a-col>
<a-col :span="6">
<a-col :md="6" :xs="24">
<statistic-card title="最快到期证书">
<expiring-list v-if="count.expiringList" :data="count.expiringList"></expiring-list>
</statistic-card>
@ -91,9 +94,9 @@
已支持的部署任务总览 <a-tag color="green">{{ pluginGroups.groups.all.plugins.length }}</a-tag>
</template>
<a-row :gutter="10">
<a-col v-for="item of pluginGroups.groups.all.plugins" :key="item.name" class="plugin-item-col" :span="4">
<a-col v-for="item of pluginGroups.groups.all.plugins" :key="item.name" class="plugin-item-col" :xl="4" :md="6" :xs="24">
<a-card>
<a-tooltip :title="item.desc" class="flex-between">
<a-tooltip :title="item.desc" class="flex-between overflow-hidden">
<div class="plugin-item pointer">
<div class="icon">
<fs-icon :icon="item.icon" class="font-size-16 color-blue" />
@ -239,7 +242,6 @@ function openUpgradeUrl() {
display: flex;
align-items: center;
padding: 20px;
background-color: #fff;
.avatar {
margin-right: 10px;

View File

@ -1,5 +1,5 @@
<template>
<fs-page class="home—index">
<fs-page class="home—index bg-neutral-100 dark:bg-black">
<!-- <page-content />-->
<dashboard-user />
<change-password-button ref="changePasswordButtonRef" :show-button="false"></change-password-button>
@ -32,6 +32,5 @@ onMounted(() => {
</script>
<style lang="less">
.homeindex {
background-color: #eee;
}
</style>

View File

@ -1,18 +1,17 @@
<template>
<div class="sys-settings-form sys-settings-register">
<a-form :model="formState" name="register" :label-col="{ span: 8 }" :wrapper-col="{ span: 16 }" autocomplete="off" @finish="onFinish">
<a-form-item label="管理其他用户流水线" :name="['public', 'managerOtherUserPipeline']">
<a-switch v-model:checked="formState.public.managerOtherUserPipeline" />
</a-form-item>
<a-form-item label="限制用户流水线数量" :name="['public', 'limitUserPipelineCount']">
<a-input-number v-model:value="formState.public.limitUserPipelineCount" />
<div class="helper">0为不限制</div>
</a-form-item>
<a-form-item label="开启自助注册" :name="['public', 'registerEnabled']">
<a-switch v-model:checked="formState.public.registerEnabled" />
</a-form-item>
<template v-if="formState.public.registerEnabled">
<a-form-item label="限制用户流水线数量" :name="['public', 'limitUserPipelineCount']">
<a-input-number v-model:value="formState.public.limitUserPipelineCount" />
<div class="helper">0为不限制</div>
</a-form-item>
<a-form-item label="管理其他用户流水线" :name="['public', 'managerOtherUserPipeline']">
<a-switch v-model:checked="formState.public.managerOtherUserPipeline" />
</a-form-item>
<a-form-item label="开启用户名注册" :name="['public', 'usernameRegisterEnabled']">
<a-switch v-model:checked="formState.public.usernameRegisterEnabled" />
</a-form-item>

View File

@ -97,7 +97,7 @@ export default ({ command, mode }) => {
},
server: {
host: "0.0.0.0",
port: 3002,
port: 3008,
fs: devServerFs,
proxy: {
// with options