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, required: false,
order: 100, order: 100,
helper: "PFX、jks格式证书是否加密\njks必须设置密码不传则默认123456", helper: "PFX、jks格式证书是否加密\njks必须设置密码不传则默认123456\npfx不传则为空密码",
}) })
pfxPassword!: string; pfxPassword!: string;

View File

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

View File

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

View File

@ -13,7 +13,7 @@ const slots = defineSlots();
<div class="tutorial-button pointer" @click="open"> <div class="tutorial-button pointer" @click="open">
<template v-if="!slots.default"> <template v-if="!slots.default">
<fs-icon icon="ant-design:question-circle-outlined"></fs-icon> <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> </template>
<slot></slot> <slot></slot>
<a-modal v-model:open="openedRef" class="tutorial-modal" width="90%"> <a-modal v-model:open="openedRef" class="tutorial-modal" width="90%">

View File

@ -3,10 +3,10 @@
<contextHolder /> <contextHolder />
<fs-icon icon="mingcute:vip-1-line" :title="text.title" /> <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> <a-tooltip>
<template #title> {{ text.title }}</template> <template #title> {{ text.title }}</template>
<span>{{ text.name }}</span> <span class="">{{ text.name }}</span>
</a-tooltip> </a-tooltip>
</div> </div>
</div> </div>
@ -390,7 +390,6 @@ onMounted(() => {
} }
.text { .text {
margin-left: 5px;
} }
} }

View File

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

View File

@ -1,6 +1,6 @@
<template> <template>
<div class="flex flex-between full-w"> <div class="flex flex-between w-full text-sm p-5 bg-neutral-100 dark:bg-neutral-900">
<div class="flex"> <div class="flex items-center">
<span v-if="!settingStore.isComm"> <span v-if="!settingStore.isComm">
<span>Powered by</span> <span>Powered by</span>
<a> handsfree.work </a> <a> handsfree.work </a>
@ -26,7 +26,7 @@ import { computed, onMounted, ref } from "vue";
import { useSettingStore } from "/@/store/modules/settings"; import { useSettingStore } from "/@/store/modules/settings";
defineOptions({ defineOptions({
name: "Footer" name: "PageFooter"
}); });
const version = ref(import.meta.env.VITE_APP_VERSION); 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 VipButton from "/@/components/vip-button/index.vue";
import TutorialButton from "/@/components/tutorial/index.vue"; import TutorialButton from "/@/components/tutorial/index.vue";
import { useSettingStore } from "/@/store/modules/settings"; 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 userStore = useUserStore();
const accessStore = useAccessStore(); const accessStore = useAccessStore();
const router = useRouter();
const menus = computed(() => [ const menus = computed(() => [
// { {
// handler: () => { handler: () => {
// openWindow(VBEN_DOC_URL, { router.push("/certd/mine/user-profile");
// target: "_blank" },
// }); icon: "fa-solid:book",
// }, text: "账号信息"
// icon: BookOpenText, }
// text: $t("ui.widgets.document")
// }
]); ]);
const avatar = computed(() => { 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() { async function handleLogout() {
await userStore.logout(true); userStore.logout(true);
} }
const settingStore = useSettingStore(); const settingStore = useSettingStore();
@ -56,21 +57,21 @@ onMounted(async () => {
<template> <template>
<BasicLayout @clear-preferences-and-logout="handleLogout"> <BasicLayout @clear-preferences-and-logout="handleLogout">
<template #user-dropdown> <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>
<template #lock-screen> <template #lock-screen>
<LockScreen :avatar @to-login="handleLogout" /> <LockScreen :avatar @to-login="handleLogout" />
</template> </template>
<template #header-right-0> <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" /> <tutorial-button v-if="!settingStore.isComm" class="flex-center header-btn" />
</div> </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" /> <vip-button class="flex-center header-btn" mode="nav" />
</div> </div>
</template> </template>
<template #footer> <template #footer>
<Footer></Footer> <PageFooter></PageFooter>
</template> </template>
</BasicLayout> </BasicLayout>
</template> </template>
@ -78,5 +79,6 @@ onMounted(async () => {
<style lang="less"> <style lang="less">
.header-btn { .header-btn {
font-size: 14px; font-size: 14px;
padding: 5px;
} }
</style> </style>

View File

@ -3,7 +3,8 @@ import App from "./App.vue";
// import Antd from "ant-design-vue"; // import Antd from "ant-design-vue";
import Antd from "./plugin/antdv-async/index"; import Antd from "./plugin/antdv-async/index";
import "./style/common.less"; import "./style/common.less";
import i18n from "./i18n"; import { loadMessages } from "./i18n";
import { i18n } from "/@/vben/locales";
import components from "./components"; import components from "./components";
import router from "./router"; import router from "./router";
import plugin from "./plugin/"; import plugin from "./plugin/";
@ -17,9 +18,9 @@ async function bootstrap() {
const app = createApp(App); const app = createApp(App);
// app.use(Antd); // app.use(Antd);
app.use(Antd); app.use(Antd);
await setupVben(app); await setupVben(app, { loadMessages });
app.use(router); app.use(router);
app.use(i18n); // app.use(i18n);
// app.use(store); // app.use(store);
app.use(components); app.use(components);
app.use(plugin, { i18n }); 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 Avatar from "ant-design-vue/es/avatar";
import Steps from "ant-design-vue/es/steps"; import Steps from "ant-design-vue/es/steps";
import Select from "ant-design-vue/es/select"; import Select from "ant-design-vue/es/select";
import PageHeader from "ant-design-vue/es/page-header";
export default { export default {
install(app: any) { install(app: any) {
app.use(Input); app.use(Input);
app.use(Button); app.use(Button);
app.component("ADivider", Divider); app.use(Divider);
app.component("ABadge", Badge); app.use(Badge);
app.component("AEmpty", Empty); app.use(Empty);
app.component("AAvatar", Avatar); app.use(Avatar);
app.use(PageHeader);
app.use(Steps); app.use(Steps);
app.use(Select); app.use(Select);
@ -165,8 +167,8 @@ export default {
defineAsyncComponent(() => import("ant-design-vue/es/tree-select")) defineAsyncComponent(() => import("ant-design-vue/es/tree-select"))
); );
app.component( app.component(
"AToar", "ATour",
defineAsyncComponent(() => import("ant-design-vue/es/tree-select")) defineAsyncComponent(() => import("ant-design-vue/es/tour"))
); );
app.component( app.component(
@ -186,5 +188,40 @@ export default {
"AProgress", "AProgress",
defineAsyncComponent(() => import("ant-design-vue/es/progress")) 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") { if (!token || token === "undefined") {
return true; return true;
} }
// 初始化权限列表 // 初始化权限列表
try { try {
console.log("permission is enabled"); console.log("permission is enabled");

View File

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

View File

@ -6,7 +6,32 @@ import { useAccessStore } from "/@/vben/stores";
import { generateMenus, startProgress, stopProgress } from "/@/vben/utils"; import { generateMenus, startProgress, stopProgress } from "/@/vben/utils";
import { frameworkRoutes } from "/@/router/resolve"; import { frameworkRoutes } from "/@/router/resolve";
import { useSettingStore } from "/@/store/modules/settings"; 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 * @param router
@ -70,7 +95,15 @@ function setupAccessGuard(router: Router) {
// 是否已经生成过动态路由 // 是否已经生成过动态路由
if (!accessStore.isAccessChecked) { 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.setAccessRoutes(frameworkRoutes);
accessStore.setAccessMenus(accessibleMenus); accessStore.setAccessMenus(accessibleMenus);
accessStore.setIsAccessChecked(true); accessStore.setIsAccessChecked(true);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -18,7 +18,7 @@
<div class="layout"> <div class="layout">
<div class="layout-left"> <div class="layout-left">
<div class="pipeline-container"> <div class="pipeline-container bg-neutral-100 dark:bg-black">
<div class="pipeline"> <div class="pipeline">
<v-draggable v-model="pipeline.stages" class="stages" item-key="id" handle=".stage-move-handle" :disabled="!settingStore.isPlus"> <v-draggable v-model="pipeline.stages" class="stages" item-key="id" handle=".stage-move-handle" :disabled="!settingStore.isPlus">
<template #header> <template #header>
@ -95,19 +95,10 @@
<!-- :open="true"--> <!-- :open="true"-->
<template #content> <template #content>
<div v-for="(item, index) of task.steps" :key="item.id" class="flex-o w-100"> <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 }"> <span class="ellipsis flex-1 step-title" :class="{ disabled: item.disabled, deleted: item.disabled }"> {{ index + 1 }}. {{ item.title }} </span>
{{ index + 1 }}. {{ item.title }}
</span>
<pi-status-show v-if="!editMode" :status="item.status?.result"></pi-status-show> <pi-status-show v-if="!editMode" :status="item.status?.result"></pi-status-show>
<a-tooltip title="强制重新执行此步骤"> <a-tooltip title="强制重新执行此步骤">
<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>
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> </a-tooltip>
</div> </div>
</template> </template>
@ -231,7 +222,7 @@
</div> </div>
<div class="layout-right"> <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"> <a-timeline class="mt-10">
<template v-for="item of histories" :key="item.id"> <template v-for="item of histories" :key="item.id">
<pi-history-timeline-item <pi-history-timeline-item
@ -813,7 +804,6 @@ export default defineComponent({
width: 100%; width: 100%;
height: 100%; height: 100%;
position: relative; position: relative;
background-color: #f0f0f0;
overflow: auto; overflow: auto;
} }
.pipeline { .pipeline {
@ -821,7 +811,6 @@ export default defineComponent({
left: 0; left: 0;
top: 0; top: 0;
height: 100%; height: 100%;
background-color: #f0f0f0;
.stages { .stages {
display: flex; display: flex;
overflow: auto; overflow: auto;

View File

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

View File

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

View File

@ -1,18 +1,17 @@
<template> <template>
<div class="sys-settings-form sys-settings-register"> <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 :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-form-item label="开启自助注册" :name="['public', 'registerEnabled']">
<a-switch v-model:checked="formState.public.registerEnabled" /> <a-switch v-model:checked="formState.public.registerEnabled" />
</a-form-item> </a-form-item>
<template v-if="formState.public.registerEnabled"> <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-form-item label="开启用户名注册" :name="['public', 'usernameRegisterEnabled']">
<a-switch v-model:checked="formState.public.usernameRegisterEnabled" /> <a-switch v-model:checked="formState.public.usernameRegisterEnabled" />
</a-form-item> </a-form-item>

View File

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