summaryrefslogtreecommitdiff
path: root/src/components/layout
diff options
context:
space:
mode:
Diffstat (limited to 'src/components/layout')
-rw-r--r--src/components/layout/avatar.js3
-rw-r--r--src/components/layout/index.vue38
-rw-r--r--src/components/layout/layoutHeader.vue333
-rw-r--r--src/components/layout/leftMenu.vue160
4 files changed, 534 insertions, 0 deletions
diff --git a/src/components/layout/avatar.js b/src/components/layout/avatar.js
new file mode 100644
index 0000000..490950c
--- /dev/null
+++ b/src/components/layout/avatar.js
@@ -0,0 +1,3 @@
+const avatarUrl = "data:image/png;base64,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"
+
+export default avatarUrl \ No newline at end of file
diff --git a/src/components/layout/index.vue b/src/components/layout/index.vue
new file mode 100644
index 0000000..62858ac
--- /dev/null
+++ b/src/components/layout/index.vue
@@ -0,0 +1,38 @@
+<template>
+ <div id="layout">
+ <layout-header></layout-header>
+ <div id="layout-container">
+ <left-menu></left-menu>
+ <div class="pageContent">
+ <router-view></router-view>
+ </div>
+ </div>
+ </div>
+</template>
+
+<script setup>
+import layoutHeader from '@/components/layout/layoutHeader.vue';
+import leftMenu from '@/components/layout/leftMenu.vue';
+</script>
+
+<style lang="scss" scoped>
+#layout {
+ width: 100%;
+ height: 100%;
+ #layout-container {
+ height: calc(100vh - 70px);
+ position: relative;
+ .pageContent {
+ padding: 18px 45px 32px 107px;
+ width: 100%;
+ height: 100%;
+ display: flex;
+ flex-direction: column;
+ gap: 18px 0;
+ overflow-x: hidden;
+ overflow-y: auto;
+ position: relative;
+ }
+ }
+}
+</style>
diff --git a/src/components/layout/layoutHeader.vue b/src/components/layout/layoutHeader.vue
new file mode 100644
index 0000000..b2bf673
--- /dev/null
+++ b/src/components/layout/layoutHeader.vue
@@ -0,0 +1,333 @@
+<template>
+ <div id="layout-header">
+ <!-- left -->
+ <div class="header-left">
+ <div class="header-logo">
+ <el-icon :size="45">
+ <Loading />
+ </el-icon>
+ <p>
+ <span>Appsketch</span>
+ <span>Works</span>
+ </p>
+ </div>
+ <el-dropdown class="workspace-dropdown" placement="bottom-start">
+ <div class="workspace-dropdown-button">
+ Community
+ <el-icon class="el-icon--right"><arrow-down /></el-icon>
+ </div>
+ <template #dropdown>
+ <el-dropdown-menu>
+ <el-dropdown-item>Community</el-dropdown-item>
+ <el-dropdown-item>
+ <el-icon class="primary-color" :size="16">
+ <CirclePlus />
+ </el-icon>
+ <span>Add a Private Workspace</span>
+ </el-dropdown-item>
+ </el-dropdown-menu>
+ </template>
+ </el-dropdown>
+ <!-- search -->
+ <div id="header-search">
+ <el-input v-model="keyword" size="large" placeholder="Search">
+ <template #prefix>
+ <el-icon><Search /></el-icon>
+ </template>
+ </el-input>
+ </div>
+ </div>
+ <!-- right -->
+ <div class="header-right">
+ <el-switch
+ class="theme-switch"
+ :width="56"
+ size="large"
+ v-model="isLight"
+ inline-prompt
+ :active-icon="Sunny"
+ :inactive-icon="Moon"
+ @change="themeChange"
+ >
+ </el-switch>
+ <el-dropdown
+ class="avatar-dropdown"
+ placement="bottom-end"
+ popper-class="avatar-popper"
+ >
+ <div class="avatar-dropdown-img">
+ <img :src="avatarUrl" alt="" />
+ <el-icon class="el-icon--right"><arrow-down /></el-icon>
+ </div>
+ <template #dropdown>
+ <el-dropdown-menu>
+ <el-dropdown-item>
+ <div>
+ <p class="profile-text">Profile</p>
+ <p class="profile-info">[email protected]</p>
+ </div>
+ </el-dropdown-item>
+ <el-dropdown-item>
+ <el-icon :size="16">
+ <CircleClose />
+ </el-icon>
+ <span class="profile-text">Sign Out</span>
+ </el-dropdown-item>
+ </el-dropdown-menu>
+ </template>
+ </el-dropdown>
+
+ <el-dropdown
+ class="headerMore-dropdown"
+ trigger="click"
+ placement="bottom-end"
+ popper-class="headerMore-popper"
+ >
+ <button class="headerMore-dropdown-button">
+ <el-icon><Grid /></el-icon>
+ </button>
+ <template #dropdown>
+ <el-dropdown-menu>
+ <ul class="headerMore-list">
+ <li class="headerMore-item active">
+ <el-icon><Setting /></el-icon>
+ <span>PCAPs</span>
+ </li>
+ <li class="headerMore-item">
+ <el-icon><Setting /></el-icon>
+ <span>About</span>
+ </li>
+ <li class="headerMore-item">
+ <el-icon><Setting /></el-icon>
+ <span>company</span>
+ </li>
+ <li class="headerMore-item">
+ <el-icon><Setting /></el-icon>
+ <span>FAQ</span>
+ </li>
+ <li class="headerMore-item">
+ <el-icon><Setting /></el-icon>
+ <span>Docs</span>
+ </li>
+ <li class="headerMore-item">
+ <el-icon><Setting /></el-icon>
+ <span>Resources</span>
+ </li>
+ <li class="headerMore-item">
+ <el-icon><Setting /></el-icon>
+ <span>Contact</span>
+ </li>
+ <li class="headerMore-item">
+ <el-icon><Setting /></el-icon>
+ <span>Pricing</span>
+ </li>
+ </ul>
+ </el-dropdown-menu>
+ </template>
+ </el-dropdown>
+ </div>
+ </div>
+</template>
+
+<script setup>
+import avatarUrl from './avatar.js';
+import { Sunny, Moon } from '@element-plus/icons-vue';
+import { toRefs, ref } from 'vue';
+import { useMainStore } from '@/store/index';
+import { useTheme } from '@/hooks/useTheme';
+
+const props = defineProps({
+ test: {
+ type: String,
+ },
+});
+
+const mainStore = useMainStore();
+
+const keyword = ref('');
+
+const { themeSet } = useTheme();
+const isLight = ref(mainStore.theme === 'light');
+const themeChange = (val) => {
+ themeSet(val ? 'light' : 'dark');
+};
+</script>
+
+<style lang="scss" scoped>
+#layout-header {
+ width: 100%;
+ height: 70px;
+ background-color: var(--background_secondary);
+ padding: 14px 32px;
+ box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.25);
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ .header-left {
+ align-items: center;
+ display: flex;
+ .header-logo {
+ display: flex;
+ align-items: center;
+ gap: 15px;
+ .el-icon {
+ color: var(--primary);
+ }
+ p {
+ span {
+ font-size: 22px;
+ font-weight: 500;
+ }
+ span:last-of-type {
+ margin-left: 10px;
+ color: var(--primary);
+ }
+ }
+ }
+ .workspace-dropdown {
+ margin-left: 20px;
+ .workspace-dropdown-button {
+ outline: none;
+ cursor: pointer;
+ display: inline-flex;
+ justify-content: center;
+ align-items: center;
+ gap: 10px;
+ background: #e0eaff;
+ border: 1px solid var(--primary);
+ border-radius: 4px;
+ color: var(--primary);
+ height: 27px;
+ padding: 0 8px;
+ }
+ }
+ #header-search {
+ margin-left: 60px;
+ .el-input {
+ width: 320px;
+ }
+ }
+ }
+
+ .header-right {
+ display: flex;
+ align-items: center;
+ gap: 20px;
+ :deep(.theme-switch) {
+ .el-icon {
+ font-size: 20px !important;
+ color: #fac302 !important;
+ }
+ .el-switch__core {
+ border: none;
+ background: #636363;
+ }
+ .el-icon {
+ margin-left: 4px;
+ }
+ &.is-checked {
+ .el-switch__core {
+ background: #e8e8e8;
+ }
+ .el-icon {
+ margin-left: 0px;
+ }
+ }
+ }
+ .avatar-dropdown {
+ .avatar-dropdown-img {
+ outline: none;
+ cursor: default;
+ align-items: center;
+ display: flex;
+ gap: 0 5px;
+ img {
+ width: 36px;
+ height: 36px;
+ }
+ }
+ }
+ .headerMore-dropdown {
+ .headerMore-dropdown-button {
+ background: transparent;
+ border: none;
+ border-radius: 4px;
+ box-shadow: none;
+ cursor: pointer;
+ outline: none;
+ color: var(--text);
+ font-size: 22px;
+ width: 28px;
+ height: 28px;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ &:hover {
+ color: var(--primary);
+ }
+ &[aria-expanded='true'] {
+ background: #f0f0f0;
+ color: var(--primary);
+ }
+ }
+ }
+ }
+}
+</style>
+<style lang="scss">
+.avatar-popper {
+ .profile-text, .el-icon {
+ color: var(--text);
+ }
+ .profile-info{
+ color: var(--text_secondary);
+
+ }
+ .el-dropdown-menu__item {
+ padding: 10px 16px;
+ &:last-of-type {
+ border-top: 1px solid #ceced2;
+ }
+ }
+}
+
+.headerMore-popper {
+ .el-dropdown-menu {
+ padding: 0;
+ .headerMore-list {
+ padding: 8px;
+ display: flex;
+ gap: 0 8px;
+ justify-content: center;
+ max-width: 224px;
+ flex-wrap: wrap;
+ .headerMore-item {
+ width: 64px;
+ height: 64px;
+ align-items: center;
+ border-radius: 4px;
+ color: var(--text);
+ display: flex;
+ flex-direction: column;
+ gap: 4px 0;
+ justify-content: center;
+ transition: all 0.2s ease-in;
+ cursor: pointer;
+ .el-icon {
+ font-size: 20px;
+ }
+ span {
+ font-size: 14px;
+ }
+ &:hover {
+ color: var(--primary);
+ }
+ &.active {
+ background: #f0f0f0;
+ color: var(--primary);
+ }
+ }
+ }
+ }
+}
+</style>
diff --git a/src/components/layout/leftMenu.vue b/src/components/layout/leftMenu.vue
new file mode 100644
index 0000000..8360919
--- /dev/null
+++ b/src/components/layout/leftMenu.vue
@@ -0,0 +1,160 @@
+<template>
+ <div id="leftMenu">
+ <ul class="leftMenu-list">
+ <li
+ class="leftMenu-item"
+ :class="{
+ active: `${currentRoute}` == item.route,
+ }"
+ v-for="item in menuList"
+ :key="item.route"
+ @click="jummp(item.route)"
+ >
+ <div class="leftMenu-icon">
+ <el-icon><Notebook /></el-icon>
+ </div>
+ <div class="leftMenu-text">{{ item.name }}</div>
+ </li>
+ </ul>
+ </div>
+</template>
+
+<script setup>
+import { useRouter } from 'vue-router';
+import { toRefs, ref, watch } from 'vue';
+import { useI18n } from "vue-i18n"
+
+const props = defineProps({
+ test: {
+ type: String,
+ },
+});
+
+const { t } = useI18n()
+
+const router = useRouter();
+
+const currentRoute = ref('');
+watch(
+ () => router.currentRoute.value,
+ (value) => {
+ currentRoute.value = value.path;
+ },
+ { immediate: true }
+);
+
+const jummp = (path) => {
+ router.push({
+ path: path,
+ });
+};
+
+const menuList = ref([
+ {
+ name: 'Workbooks',
+ route: '/workbooks',
+ },
+ {
+ name: 'Applications',
+ route: '/applications',
+ },
+ {
+ name: 'Signatures',
+ route: '/signatures',
+ },
+ {
+ name: 'Pcaps',
+ route: '/pcaps',
+ },
+ {
+ name: 'Packages',
+ route: '/packages',
+ },
+ {
+ name: 'Jobs',
+ route: '/jobs',
+ },
+ {
+ name: 'Playbooks',
+ route: '/playbooks',
+ },
+ {
+ name: 'Runners',
+ route: '/runners',
+ },
+]);
+</script>
+
+<style lang="scss" scoped>
+#leftMenu {
+ z-index: 10;
+ position: absolute;
+ left: 0;
+ top: 0;
+ width: 64px;
+ height: calc(100vh - 70px);
+ padding: 24px 0;
+ transition: width 0.2s ease-in;
+ background-color: var(--background_secondary);
+ &:hover {
+ width: 240px;
+ .leftMenu-list .leftMenu-item .leftMenu-text {
+ display: block;
+ width: 100%;
+ }
+ }
+ .leftMenu-list {
+ display: flex;
+ flex-direction: column;
+ gap: 12px 0;
+ width: 100%;
+ .leftMenu-item {
+ width: 100%;
+ height: 56px;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ cursor: pointer;
+ &:hover {
+ .leftMenu-icon {
+ color: var(--primary);
+ }
+ .leftMenu-text {
+ color: var(--primary);
+ }
+ }
+ &.active {
+ background-color: var(--primary_inactive);
+ .leftMenu-icon {
+ color: var(--primary);
+ }
+ .leftMenu-text {
+ color: var(--primary);
+ }
+ }
+ .leftMenu-icon {
+ flex-shrink: 0;
+ width: 64px;
+ height: 56px;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ font-size: 32px;
+ color: var(--text_secondary);
+ }
+ .leftMenu-text {
+ display: none;
+ width: 0;
+ transition: width 0.2s ease-in;
+ padding: 0 5px;
+ font-size: 18px;
+ font-weight: 600;
+ max-height: 56px;
+ overflow: hidden;
+ white-space: nowrap;
+ color: var(--text_secondary);
+ }
+ }
+ }
+}
+</style>