mirror of https://github.com/certd/certd
chore: 集成vben
parent
8fcabc5e9f
commit
9557fc799e
|
@ -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;
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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%">
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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];
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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 });
|
||||
|
|
|
@ -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;
|
||||
})
|
||||
);
|
||||
}
|
||||
};
|
||||
|
|
|
@ -28,7 +28,6 @@ export function registerRouterHook() {
|
|||
if (!token || token === "undefined") {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 初始化权限列表
|
||||
try {
|
||||
console.log("permission is enabled");
|
||||
|
|
|
@ -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 = [];
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
},
|
||||
|
|
|
@ -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 };
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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 ?? "";
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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')"
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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">
|
||||
.home—index {
|
||||
background-color: #eee;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -97,7 +97,7 @@ export default ({ command, mode }) => {
|
|||
},
|
||||
server: {
|
||||
host: "0.0.0.0",
|
||||
port: 3002,
|
||||
port: 3008,
|
||||
fs: devServerFs,
|
||||
proxy: {
|
||||
// with options
|
||||
|
|
Loading…
Reference in New Issue