init
This commit is contained in:
6
.env.development
Normal file
6
.env.development
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
# 开发环境基地址
|
||||||
|
|
||||||
|
VITE_BASE_URL='/api'
|
||||||
|
# VITE_BASE_URL='http://192.168.101.7:8000'
|
||||||
|
|
||||||
|
VITE_BASE_WSURL='ws://web-tunnel.feashow.com/api'
|
||||||
7
.env.production
Normal file
7
.env.production
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
# 生产环境基地址
|
||||||
|
|
||||||
|
VITE_TITLE='fateverse'
|
||||||
|
|
||||||
|
VITE_BASE_URL='/api'
|
||||||
|
|
||||||
|
VITE_BASE_WSURL='ws://web-tunnel.feashow.com/api'
|
||||||
33
.gitignore
vendored
33
.gitignore
vendored
@@ -1,11 +1,28 @@
|
|||||||
# ---> Vue
|
# Logs
|
||||||
# gitignore template for Vue.js projects
|
logs
|
||||||
#
|
*.log
|
||||||
# Recommended template: Node.gitignore
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
|
||||||
# TODO: where does this rule come from?
|
node_modules
|
||||||
docs/_book
|
.DS_Store
|
||||||
|
dist
|
||||||
|
dist-ssr
|
||||||
|
coverage
|
||||||
|
*.local
|
||||||
|
|
||||||
# TODO: where does this rule come from?
|
/cypress/videos/
|
||||||
test/
|
/cypress/screenshots/
|
||||||
|
|
||||||
|
# Editor directories and files
|
||||||
|
.vscode
|
||||||
|
!.vscode/extensions.json
|
||||||
|
.idea
|
||||||
|
*.suo
|
||||||
|
*.ntvs*
|
||||||
|
*.njsproj
|
||||||
|
*.sln
|
||||||
|
*.sw?
|
||||||
|
|||||||
13
index.html
Normal file
13
index.html
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>Tunnel Cloud</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="app"></div>
|
||||||
|
<script type="module" src="/src/main.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
9202
package-lock.json
generated
Normal file
9202
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
31
package.json
Normal file
31
package.json
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
{
|
||||||
|
"name": "tunnel-cloud-web",
|
||||||
|
"private": true,
|
||||||
|
"version": "0.0.0",
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite",
|
||||||
|
"build": "vite build",
|
||||||
|
"preview": "vite preview"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"axios": "^1.4.0",
|
||||||
|
"echarts": "^5.4.2",
|
||||||
|
"element-plus": "^2.3.5",
|
||||||
|
"nprogress": "^0.2.0",
|
||||||
|
"pinia": "^2.0.35",
|
||||||
|
"sass": "^1.62.1",
|
||||||
|
"scss": "^0.2.4",
|
||||||
|
"js-cookie": "^3.0.5",
|
||||||
|
"unplugin-icons": "^0.16.1",
|
||||||
|
"vite-plugin-inspect": "^0.7.26",
|
||||||
|
"vue": "^3.2.47",
|
||||||
|
"vue-router": "^4.1.6"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@vitejs/plugin-vue": "^4.2.1",
|
||||||
|
"unplugin-auto-import": "^0.15.3",
|
||||||
|
"unplugin-vue-components": "^0.24.1",
|
||||||
|
"vite": "^4.3.4"
|
||||||
|
}
|
||||||
|
}
|
||||||
1
public/vite.svg
Normal file
1
public/vite.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>
|
||||||
|
After Width: | Height: | Size: 1.5 KiB |
7
src/App.vue
Normal file
7
src/App.vue
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<template>
|
||||||
|
<RouterView />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
|
||||||
|
</script>
|
||||||
227
src/assets/styles/index.scss
Normal file
227
src/assets/styles/index.scss
Normal file
@@ -0,0 +1,227 @@
|
|||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
list-style: none;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
text-decoration: none;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
html, body, #app, .el-container, .el-aside, .el-main {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo {
|
||||||
|
height: 106px;
|
||||||
|
color: #EDC49A;
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 23px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-menu {
|
||||||
|
border: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle {
|
||||||
|
line-height: 65px;
|
||||||
|
padding: 0 15px;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: #79bbff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
vertical-align: middle;
|
||||||
|
transform: translateY(-2px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-main {
|
||||||
|
padding: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-aside {
|
||||||
|
box-shadow: 4px 0 2px 1px rgb(171, 167, 167);
|
||||||
|
transition: width 0.3s;
|
||||||
|
overflow: hidden;
|
||||||
|
height: 100vh;
|
||||||
|
background-color: #211F31;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-popover.el-popper {
|
||||||
|
padding: 10px !important;
|
||||||
|
min-width: 60px !important;
|
||||||
|
ul{
|
||||||
|
li{
|
||||||
|
height: 25px;
|
||||||
|
line-height: 25px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar {
|
||||||
|
height: 65px;
|
||||||
|
border-bottom: 1px solid #999999;
|
||||||
|
padding: 0 15px 0 0;
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-start;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.right-bar {
|
||||||
|
margin-left: auto;
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-start;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
img {
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
border-radius: 50%;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-dialog {
|
||||||
|
border-radius: 12px !important;
|
||||||
|
border: none;
|
||||||
|
|
||||||
|
.el-dialog__header {
|
||||||
|
border-top-left-radius: 12px;
|
||||||
|
border-top-right-radius: 12px;
|
||||||
|
// background-color: #262626;
|
||||||
|
margin: 0;
|
||||||
|
// .el-dialog__title{
|
||||||
|
// color: white;
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.table-header-btn {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-start;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.query-form {
|
||||||
|
.el-form-item__label {
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-button {
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.query-btn {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
|
||||||
|
.el-button {
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.table {
|
||||||
|
thead .el-table-column--selection .cell {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-form {
|
||||||
|
.el-form-item {
|
||||||
|
flex-direction: column;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
|
||||||
|
.el-form-item__content {
|
||||||
|
.el-select {
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-form-item__label {
|
||||||
|
justify-content: flex-start;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-dialog {
|
||||||
|
:deep .el-dialog__header {
|
||||||
|
padding: 10px 20px;
|
||||||
|
|
||||||
|
.el-dialog__title {
|
||||||
|
font-size: 17px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-dialog__headerbtn {
|
||||||
|
top: 15px;
|
||||||
|
|
||||||
|
.i {
|
||||||
|
font-size: large;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep .el-dialog__footer {
|
||||||
|
padding: 10px 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.border {
|
||||||
|
:deep .el-dialog__header {
|
||||||
|
border-bottom: 1px solid #e8e8e8;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep .el-dialog__footer {
|
||||||
|
border-top: 1px solid #e8e8e8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.layout {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-around;
|
||||||
|
|
||||||
|
.layout-left {
|
||||||
|
width: 30%;
|
||||||
|
border: 1px solid #ebeef5;
|
||||||
|
padding: 10px;
|
||||||
|
|
||||||
|
|
||||||
|
.dict-tree {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
//:deep .el-button{
|
||||||
|
// //border: 1px solid;
|
||||||
|
// background-color: #fff;
|
||||||
|
//}
|
||||||
|
.left-type {
|
||||||
|
margin-right: 20px;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.layout-right {
|
||||||
|
width: 70%;
|
||||||
|
border: 1px solid #ebeef5;
|
||||||
|
margin-left: 10px;
|
||||||
|
padding: 10px
|
||||||
|
}
|
||||||
|
}
|
||||||
0
src/assets/styles/sidebar.scss
Normal file
0
src/assets/styles/sidebar.scss
Normal file
1
src/assets/vue.svg
Normal file
1
src/assets/vue.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="37.07" height="36" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 198"><path fill="#41B883" d="M204.8 0H256L128 220.8L0 0h97.92L128 51.2L157.44 0h47.36Z"></path><path fill="#41B883" d="m0 0l128 220.8L256 0h-51.2L128 132.48L50.56 0H0Z"></path><path fill="#35495E" d="M50.56 0L128 133.12L204.8 0h-47.36L128 51.2L97.92 0H50.56Z"></path></svg>
|
||||||
|
After Width: | Height: | Size: 496 B |
40
src/components/HelloWorld.vue
Normal file
40
src/components/HelloWorld.vue
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
<script setup>
|
||||||
|
import { ref } from 'vue'
|
||||||
|
|
||||||
|
defineProps({
|
||||||
|
msg: String,
|
||||||
|
})
|
||||||
|
|
||||||
|
const count = ref(0)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<h1>{{ msg }}</h1>
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
<button type="button" @click="count++">count is {{ count }}</button>
|
||||||
|
<p>
|
||||||
|
Edit
|
||||||
|
<code>components/HelloWorld.vue</code> to test HMR
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Check out
|
||||||
|
<a href="https://vuejs.org/guide/quick-start.html#local" target="_blank"
|
||||||
|
>create-vue</a
|
||||||
|
>, the official Vue + Vite starter
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Install
|
||||||
|
<a href="https://github.com/vuejs/language-tools" target="_blank">Volar</a>
|
||||||
|
in your IDE for a better DX
|
||||||
|
</p>
|
||||||
|
<p class="read-the-docs">Click on the Vite and Vue logos to learn more</p>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.read-the-docs {
|
||||||
|
color: #888;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
44
src/layout/appmain/AppMain.vue
Normal file
44
src/layout/appmain/AppMain.vue
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
<template>
|
||||||
|
<div class="app-main-container">
|
||||||
|
<router-view v-slot="{ Component, route }">
|
||||||
|
<transition name="fade-transform" type="transition" appear mode="out-in">
|
||||||
|
<div>
|
||||||
|
<component
|
||||||
|
:is="Component"
|
||||||
|
:key="route.fullPath"
|
||||||
|
></component>
|
||||||
|
</div>
|
||||||
|
</transition>
|
||||||
|
</router-view>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.app-main-container {
|
||||||
|
padding: 15px;
|
||||||
|
max-height: calc(100vh - 96px);
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* fade-transform */
|
||||||
|
.fade-transform-leave-active,
|
||||||
|
.fade-transform-enter-active {
|
||||||
|
transition: all 0.5s;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 可能为enter失效,拆分为 enter-from和enter-to */
|
||||||
|
.fade-transform-enter-from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateX(-30px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.fade-transform-enter-to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateX(0px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.fade-transform-leave-to {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateX(30px);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
42
src/layout/index.vue
Normal file
42
src/layout/index.vue
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
<template>
|
||||||
|
<el-container>
|
||||||
|
<el-aside
|
||||||
|
:width="!siderbarStore.getSiderBarStatus() ? '200px' : 'fit-content'"
|
||||||
|
:class="!siderbarStore.getSiderBarStatus() ? 'expand' : ''"
|
||||||
|
>
|
||||||
|
<SiderBar></SiderBar>
|
||||||
|
</el-aside>
|
||||||
|
<el-main>
|
||||||
|
<NavBar></NavBar>
|
||||||
|
<TagsView></TagsView>
|
||||||
|
<AppMain></AppMain>
|
||||||
|
</el-main>
|
||||||
|
</el-container>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import SiderBar from './siderbar/index.vue'
|
||||||
|
import NavBar from './navbar/index.vue'
|
||||||
|
import TagsView from './tagsview/index.vue'
|
||||||
|
import AppMain from './appmain/AppMain.vue';
|
||||||
|
import {useSiderBar} from '../store/siderbar';
|
||||||
|
|
||||||
|
const siderbarStore = useSiderBar()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.expand {
|
||||||
|
animation: Expand 0.3s ease forwards;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes Expand {
|
||||||
|
from {
|
||||||
|
width: 64px;
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
width: 200px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
47
src/layout/navbar/Breadcrumb.vue
Normal file
47
src/layout/navbar/Breadcrumb.vue
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
<template>
|
||||||
|
<el-breadcrumb separator=">">
|
||||||
|
<el-breadcrumb-item v-for="(item, index) in breadcrumbList" :key="item.path">
|
||||||
|
<span v-if="item.meta.noRedirect || index === breadcrumbList.length-1" class="no-redirect">
|
||||||
|
{{ item.meta.title }}
|
||||||
|
</span>
|
||||||
|
<router-link v-else :to="item.redirect || item.path">
|
||||||
|
{{ item.meta.title }}
|
||||||
|
</router-link>
|
||||||
|
</el-breadcrumb-item>
|
||||||
|
</el-breadcrumb>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
const route = useRoute()
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
|
watch(route, () => {
|
||||||
|
getBreadcrumb()
|
||||||
|
})
|
||||||
|
|
||||||
|
//面包屑导航数据
|
||||||
|
const breadcrumbList = ref([])
|
||||||
|
|
||||||
|
//获取面包屑导航数据
|
||||||
|
const getBreadcrumb = () => {
|
||||||
|
let matched = route.matched.filter(item => item.meta && item.meta.title)
|
||||||
|
const first = matched[0]
|
||||||
|
if (!isDashboard(first)) {
|
||||||
|
matched = [{path: '/home', meta: {title: '首页'}}].concat(matched)
|
||||||
|
}
|
||||||
|
breadcrumbList.value.length = 0;
|
||||||
|
const reBreadcrumbList = matched.filter(item => item.meta && item.meta.title && item.meta.breadcrumb !== false)
|
||||||
|
breadcrumbList.value.push(...reBreadcrumbList)
|
||||||
|
}
|
||||||
|
|
||||||
|
const isDashboard = (meta) => {
|
||||||
|
const name = meta && meta.name
|
||||||
|
if (!name) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return name.trim().toLocaleLowerCase() === 'Home'.toLocaleLowerCase()
|
||||||
|
}
|
||||||
|
|
||||||
|
getBreadcrumb()
|
||||||
|
|
||||||
|
</script>
|
||||||
14
src/layout/navbar/Hamburger.vue
Normal file
14
src/layout/navbar/Hamburger.vue
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
<template>
|
||||||
|
<div class="toggle" @click="toggleClick">
|
||||||
|
<component :is="siderbarStore.getSiderBarStatus() ? 'Fold' : 'Expand'" class="icon"></component>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import {useSiderBar} from '@/store/siderbar.js'
|
||||||
|
|
||||||
|
const siderbarStore = useSiderBar()
|
||||||
|
const toggleClick = () => {
|
||||||
|
siderbarStore.setSiderBarStatus()
|
||||||
|
}
|
||||||
|
</script>
|
||||||
37
src/layout/navbar/index.vue
Normal file
37
src/layout/navbar/index.vue
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
<template>
|
||||||
|
<div class="navbar">
|
||||||
|
<Hamburger></Hamburger>
|
||||||
|
<Breadcrumb></Breadcrumb>
|
||||||
|
<div class="right-bar">
|
||||||
|
<el-popover
|
||||||
|
placement="bottom"
|
||||||
|
:width="80"
|
||||||
|
trigger="click"
|
||||||
|
>
|
||||||
|
<template #reference>
|
||||||
|
<img src="@/assets/vue.svg" alt="">
|
||||||
|
</template>
|
||||||
|
<template #default>
|
||||||
|
<ul>
|
||||||
|
<li @click="handleToAuth">个人中心</li>
|
||||||
|
<li @click="handleLogout">退出登录</li>
|
||||||
|
</ul>
|
||||||
|
</template>
|
||||||
|
</el-popover>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import Breadcrumb from './Breadcrumb.vue';
|
||||||
|
import Hamburger from './Hamburger.vue';
|
||||||
|
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
|
const handleToAuth = () => {
|
||||||
|
router.push('/auth')
|
||||||
|
}
|
||||||
|
const handleLogout = () => {
|
||||||
|
router.push('/login')
|
||||||
|
}
|
||||||
|
</script>
|
||||||
25
src/layout/siderbar/index.vue
Normal file
25
src/layout/siderbar/index.vue
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
<template>
|
||||||
|
<div class="logo" ref="logo">
|
||||||
|
<span v-if="!siderbarStore.getSiderBarStatus()">TUNNEL-CLOUD</span>
|
||||||
|
</div>
|
||||||
|
<el-menu
|
||||||
|
router
|
||||||
|
:default-active="activeMenu"
|
||||||
|
:unique-opened="true"
|
||||||
|
:collapse="siderbarStore.getSiderBarStatus()"
|
||||||
|
active-text-color="#EDC49A"
|
||||||
|
background-color='#211F31'
|
||||||
|
text-color="#fff"
|
||||||
|
>
|
||||||
|
<el-menu-item index="/">
|
||||||
|
<el-icon><User /></el-icon>
|
||||||
|
<template #title>首页</template>
|
||||||
|
</el-menu-item>
|
||||||
|
</el-menu>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import {useSiderBar} from '@/store/siderbar.js'
|
||||||
|
const siderbarStore = useSiderBar()
|
||||||
|
</script>
|
||||||
|
|
||||||
92
src/layout/tagsview/index.vue
Normal file
92
src/layout/tagsview/index.vue
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
<template>
|
||||||
|
<div class="link-box">
|
||||||
|
<el-scrollbar noresize>
|
||||||
|
<div>
|
||||||
|
<router-link
|
||||||
|
v-for="item in tagsViewStore.visitedViews"
|
||||||
|
:key="item.path" :to="{ path: item.path }" class="tag"
|
||||||
|
:class="isActive(item) ? 'active' : ''"
|
||||||
|
@click.prevent
|
||||||
|
@contextmenu.prevent.native="openMenu(item, $event)">
|
||||||
|
<span>{{ item.meta.title }}</span>
|
||||||
|
<component is="CircleClose" class="close" @click.prevent="closeTagView(item.path)"></component>
|
||||||
|
</router-link>
|
||||||
|
</div>
|
||||||
|
</el-scrollbar>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { useTagsView } from '@/store/tagsview.js'
|
||||||
|
|
||||||
|
const route = useRoute()
|
||||||
|
const tagsViewStore = useTagsView()
|
||||||
|
const visible = ref(false)
|
||||||
|
const left = ref()
|
||||||
|
const top = ref()
|
||||||
|
|
||||||
|
watch(route, () => {
|
||||||
|
init()
|
||||||
|
})
|
||||||
|
const init = () => {
|
||||||
|
tagsViewStore.addVisitedViews(route)
|
||||||
|
}
|
||||||
|
const closeTagView = (path) => {
|
||||||
|
tagsViewStore.delVisitedViews(path)
|
||||||
|
}
|
||||||
|
const isActive = (tag) => {
|
||||||
|
return tag.path === route.path
|
||||||
|
}
|
||||||
|
const openMenu = (tag, e) => {
|
||||||
|
left.value = e.x + 10
|
||||||
|
top.value = e.y + 10
|
||||||
|
visible.value = true
|
||||||
|
}
|
||||||
|
init()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.close {
|
||||||
|
width: 12px;
|
||||||
|
height: 12px;
|
||||||
|
margin-left: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.link-box {
|
||||||
|
padding: 0 15px;
|
||||||
|
border-bottom: 1px solid #222;
|
||||||
|
line-height: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tag {
|
||||||
|
padding: 3px 6px;
|
||||||
|
border: 1px solid #666;
|
||||||
|
font-size: 14px;
|
||||||
|
margin-right: 5px;
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.active {
|
||||||
|
background-color: #211F31;
|
||||||
|
color: #EDC49A;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.contextmenu {
|
||||||
|
position: absolute;
|
||||||
|
z-index: 3000;
|
||||||
|
background: #fff;
|
||||||
|
box-shadow: 2px 2px 3px 0 rgba(0, 0, 0, .3);
|
||||||
|
|
||||||
|
li {
|
||||||
|
margin: 0;
|
||||||
|
padding: 7px 16px;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: #eee;
|
||||||
|
color: #EDC49A;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
24
src/main.js
Normal file
24
src/main.js
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import { createApp } from 'vue'
|
||||||
|
import { createPinia } from 'pinia'
|
||||||
|
|
||||||
|
import App from './App.vue'
|
||||||
|
import router from './router'
|
||||||
|
|
||||||
|
import ElementPlus from 'element-plus'
|
||||||
|
import zhCn from 'element-plus/es/locale/lang/zh-cn'
|
||||||
|
//导入图标组件
|
||||||
|
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
|
||||||
|
import 'element-plus/dist/index.css'
|
||||||
|
|
||||||
|
import '@/assets/styles/index.scss'
|
||||||
|
import '@/assets/styles/sidebar.scss'
|
||||||
|
|
||||||
|
const app = createApp(App)
|
||||||
|
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
|
||||||
|
app.component(key, component)
|
||||||
|
}
|
||||||
|
app.use(ElementPlus,{locale: zhCn})
|
||||||
|
app.use(createPinia())
|
||||||
|
app.use(router)
|
||||||
|
|
||||||
|
app.mount('#app')
|
||||||
78
src/router/index.js
Normal file
78
src/router/index.js
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
import { createRouter, createWebHashHistory } from 'vue-router';
|
||||||
|
import NProgress from 'nprogress'//进度条
|
||||||
|
import 'nprogress/nprogress.css'
|
||||||
|
import Layout from '@/layout/index.vue'
|
||||||
|
// import { getToken } from '../utils/auth'
|
||||||
|
// import { usePermissionStore } from '../store/permisstion.js'
|
||||||
|
// import { useAuthStore } from '../store/userstore.js'
|
||||||
|
NProgress.configure({ showSpinner: false })
|
||||||
|
|
||||||
|
const routes = [
|
||||||
|
{
|
||||||
|
path: '/login',
|
||||||
|
name: 'login',
|
||||||
|
component: ()=>import('@/views/login/index.vue'),
|
||||||
|
meta: {
|
||||||
|
hidden: true,
|
||||||
|
title: '登录'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/',
|
||||||
|
name: 'layout',
|
||||||
|
component: Layout,
|
||||||
|
redirect: '/home',
|
||||||
|
meta: {
|
||||||
|
hidden: false
|
||||||
|
},
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: '/home',
|
||||||
|
name: 'home',
|
||||||
|
component: () => import('@/views/home/index.vue'),
|
||||||
|
meta: {
|
||||||
|
title: '首页',
|
||||||
|
breadcrumb: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const router = createRouter({
|
||||||
|
history: createWebHashHistory(),
|
||||||
|
routes,
|
||||||
|
});
|
||||||
|
// router.beforeEach(async (to,form,next)=>{
|
||||||
|
// const permissionStore = usePermissionStore()
|
||||||
|
// const authStore = useAuthStore()
|
||||||
|
// NProgress.start()
|
||||||
|
// if(!getToken()) {
|
||||||
|
// if(to.path === '/login') {
|
||||||
|
// next()
|
||||||
|
// NProgress.done()
|
||||||
|
// }else {
|
||||||
|
// next({path: '/login'})
|
||||||
|
// }
|
||||||
|
// }else {
|
||||||
|
// if(to.path === '/login') {
|
||||||
|
// next('/')
|
||||||
|
// NProgress.done()
|
||||||
|
// }else {
|
||||||
|
// permissionStore.setIsLoadRoutes(true)
|
||||||
|
// if(permissionStore.isLoadRoutes && permissionStore.asyncRouters.length==0){
|
||||||
|
// // await authStore.setUserInfo()
|
||||||
|
// next({...to, replace: true})
|
||||||
|
// } else {
|
||||||
|
// next()
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// }
|
||||||
|
// })
|
||||||
|
router.afterEach(()=>{
|
||||||
|
NProgress.done()
|
||||||
|
})
|
||||||
|
|
||||||
|
export default router;
|
||||||
|
|
||||||
19
src/store/siderbar.js
Normal file
19
src/store/siderbar.js
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import { defineStore } from "pinia";
|
||||||
|
export const useSiderBar = defineStore('siderbar',()=>{
|
||||||
|
//定义控制侧边栏是否展开变量默认展开
|
||||||
|
const isCollapse = ref(false)
|
||||||
|
//获取侧边栏状态
|
||||||
|
const getSiderBarStatus = () => {
|
||||||
|
return isCollapse.value
|
||||||
|
}
|
||||||
|
//设置侧边栏状态
|
||||||
|
const setSiderBarStatus = () => {
|
||||||
|
const status = getSiderBarStatus()
|
||||||
|
return isCollapse.value = !status
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
getSiderBarStatus,
|
||||||
|
setSiderBarStatus,
|
||||||
|
}
|
||||||
|
})
|
||||||
56
src/store/tagsview.js
Normal file
56
src/store/tagsview.js
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
import { defineStore } from "pinia";
|
||||||
|
import { ref } from "vue";
|
||||||
|
import { useRouter } from "vue-router";
|
||||||
|
|
||||||
|
export const useTagsView = defineStore('tagsView',()=>{
|
||||||
|
const router = useRouter()
|
||||||
|
//已显示的标签页list
|
||||||
|
const visitedViews = ref([])
|
||||||
|
|
||||||
|
//添加标签页面
|
||||||
|
const addVisitedViews = ({path,meta}) => {
|
||||||
|
if(visitedViews.value.length == 0) {
|
||||||
|
visitedViews.value.push({path,meta})
|
||||||
|
}else {
|
||||||
|
const paths = visitedViews.value.map(item => item.path)
|
||||||
|
if(paths.includes(path) == false) {
|
||||||
|
visitedViews.value.push({path,meta})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//删除标签页
|
||||||
|
const delVisitedViews = (path) => {
|
||||||
|
console.log('进入删除');
|
||||||
|
if(visitedViews.value.length - 1 == 0) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
visitedViews.value.forEach((item,index)=>{
|
||||||
|
if(item.path == path) {
|
||||||
|
visitedViews.value.splice(index,1)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
toLastTagView(visitedViews)
|
||||||
|
}
|
||||||
|
|
||||||
|
//删除其他标签页
|
||||||
|
const delOtherVisitedViews = ({path,meta}) => {
|
||||||
|
visitedViews.value = []
|
||||||
|
visitedViews.value.push({path,meta})
|
||||||
|
toLastTagView(visitedViews)
|
||||||
|
}
|
||||||
|
|
||||||
|
//路由到末尾标签页
|
||||||
|
const toLastTagView = (view) => {
|
||||||
|
console.log(view,'进入跳转末页');
|
||||||
|
const lastTagView = view.value.slice(-1)[0]
|
||||||
|
router.push(lastTagView.path)
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
visitedViews,
|
||||||
|
addVisitedViews,
|
||||||
|
delVisitedViews,
|
||||||
|
delOtherVisitedViews,
|
||||||
|
}
|
||||||
|
})
|
||||||
14
src/views/home/index.vue
Normal file
14
src/views/home/index.vue
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
home
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
// import { useCacheStore } from '@/store/cache.js'
|
||||||
|
// const cacheStore = useCacheStore()
|
||||||
|
// cacheStore.setCacheKey(['normal_disable','show_hide'])
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
</style>
|
||||||
92
src/views/login/index.vue
Normal file
92
src/views/login/index.vue
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
<template>
|
||||||
|
<div class="login-box">
|
||||||
|
<el-form
|
||||||
|
:model="loginForm"
|
||||||
|
ref="formInstance"
|
||||||
|
:rules="rules"
|
||||||
|
label-width="65px"
|
||||||
|
>
|
||||||
|
<h3>Rib-Account-ADMIN</h3>
|
||||||
|
<el-form-item prop="username" label="账号">
|
||||||
|
<el-input v-model="loginForm.username" :prefix-icon="User"></el-input>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item prop="password" label="密码">
|
||||||
|
<el-input v-model="loginForm.password" type="password" :prefix-icon="Lock"></el-input>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item>
|
||||||
|
<el-button @click="handleLogin(formInstance)" type="primary">登录</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import {onBeforeUnmount, reactive,} from 'vue'
|
||||||
|
// import {useAuthStore} from '@/store/userstore'
|
||||||
|
import {ElLoading} from 'element-plus'
|
||||||
|
import {User, Lock, Key} from '@element-plus/icons-vue'
|
||||||
|
|
||||||
|
const router = useRouter()
|
||||||
|
// const authStore = useAuthStore()
|
||||||
|
const loginForm = reactive({
|
||||||
|
username: 'admin',
|
||||||
|
password: '123456',
|
||||||
|
uuid: ''
|
||||||
|
})
|
||||||
|
const formInstance = ref()
|
||||||
|
const rules = reactive({
|
||||||
|
username: [
|
||||||
|
{required: true, message: '请输入账户名', trigger: 'blur'},
|
||||||
|
],
|
||||||
|
password: [
|
||||||
|
{required: true, message: '请输入密码', trigger: 'blur'},
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
const handleLogin = (instance) => {
|
||||||
|
if (!instance) return
|
||||||
|
instance.validate(async (valid) => {
|
||||||
|
if (!valid) return
|
||||||
|
// 发送请求
|
||||||
|
// await authStore.userLogin(loginForm).then(res=>{
|
||||||
|
// if(res) {
|
||||||
|
ElLoading.service({text: '正在加载系统资源', background: '#409eff', lock: true})
|
||||||
|
await router.push('/')
|
||||||
|
// }
|
||||||
|
// })
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
ElLoading.service({text: '正在加载系统资源', background: '#409eff', lock: true}).close()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.login-box {
|
||||||
|
height: 100%;
|
||||||
|
background-color: #4158D0;
|
||||||
|
background-image: linear-gradient(43deg, #4158D0 0%, #C850C0 46%, #FFCC70 100%);
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.el-form {
|
||||||
|
padding: 12px 15px;
|
||||||
|
border-radius: 12px;
|
||||||
|
width: 25%;
|
||||||
|
background-color: #fff;
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
width: 100%;
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-button {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
102
vite.config.js
Normal file
102
vite.config.js
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
import { fileURLToPath, URL } from 'node:url'
|
||||||
|
import { defineConfig } from 'vite'
|
||||||
|
import AutoImport from 'unplugin-auto-import/vite'
|
||||||
|
import Components from 'unplugin-vue-components/vite'
|
||||||
|
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
|
||||||
|
import Icons from 'unplugin-icons/vite'
|
||||||
|
import IconsResolver from 'unplugin-icons/resolver'
|
||||||
|
import vue from '@vitejs/plugin-vue'
|
||||||
|
import Inspect from 'vite-plugin-inspect'
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [
|
||||||
|
vue(),
|
||||||
|
AutoImport({
|
||||||
|
//自动导入vue相关函数
|
||||||
|
imports: ['vue','vue-router'],
|
||||||
|
|
||||||
|
resolvers: [
|
||||||
|
ElementPlusResolver(),
|
||||||
|
//自动导入图标组件
|
||||||
|
IconsResolver({
|
||||||
|
prefix: 'Icon',
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
Components({
|
||||||
|
resolvers: [
|
||||||
|
// 自动注册图标组件
|
||||||
|
IconsResolver({
|
||||||
|
enabledCollections: ['ep'],
|
||||||
|
}),
|
||||||
|
//自动导入组件
|
||||||
|
ElementPlusResolver()
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
Icons({
|
||||||
|
autoInstall: true,
|
||||||
|
}),
|
||||||
|
Inspect(),
|
||||||
|
],
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
'@': fileURLToPath(new URL('./src', import.meta.url))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
build: {
|
||||||
|
minify: 'esbuild',
|
||||||
|
terserOptions: {
|
||||||
|
compress: {
|
||||||
|
drop_console: false, // 生产环境移除log
|
||||||
|
drop_debugger: false // 生产环境禁用debugger
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
server: {
|
||||||
|
host: '0.0.0.0',
|
||||||
|
port: 8888,
|
||||||
|
strictPort: false,
|
||||||
|
open: true,
|
||||||
|
proxy: {
|
||||||
|
// '/api/custom': {
|
||||||
|
// // target: 'http://web-tunnel.feashow.com/api',
|
||||||
|
// target: 'http://192.168.31.175:8000',
|
||||||
|
// changeOrigin: true,
|
||||||
|
// rewrite: (path) => path.replace(/^\/api/, ''),
|
||||||
|
// },
|
||||||
|
// '/api/admin': {
|
||||||
|
// target: 'http://web-tunnel.feashow.com/api',
|
||||||
|
// // target: 'http://192.168.31.175:8000',
|
||||||
|
// changeOrigin: true,
|
||||||
|
// rewrite: (path) => path.replace(/^\/api/, ''),
|
||||||
|
// },
|
||||||
|
// '/api/auth': {
|
||||||
|
// target: 'http://web-tunnel.feashow.com/api',
|
||||||
|
// // target: 'http://192.168.31.175:8000',
|
||||||
|
// changeOrigin: true,
|
||||||
|
// rewrite: (path) => path.replace(/^\/api/, ''),
|
||||||
|
// },
|
||||||
|
// '/api/log': {
|
||||||
|
// // target: 'http://web-tunnel.feashow.com/api',
|
||||||
|
// target: 'http://192.168.31.175:8000',
|
||||||
|
// changeOrigin: true,
|
||||||
|
// rewrite: (path) => path.replace(/^\/api/, ''),
|
||||||
|
// },
|
||||||
|
// '/api/code-gen': {
|
||||||
|
// // target: 'http://web-tunnel.feashow.com/api',
|
||||||
|
// target: 'http://192.168.31.175:8000',
|
||||||
|
// changeOrigin: true,
|
||||||
|
// rewrite: (path) => path.replace(/^\/api/, ''),
|
||||||
|
// },
|
||||||
|
'/api': {
|
||||||
|
target: 'http://web-tunnel.feashow.com/api',
|
||||||
|
// target: 'http://192.168.31.175:8000',
|
||||||
|
changeOrigin: true,
|
||||||
|
rewrite: (path) => path.replace(/^\/api/, ''),
|
||||||
|
},
|
||||||
|
'/socket': {
|
||||||
|
target: 'ws://web-tunnel.feashow.com/api/notice-ws/notice',
|
||||||
|
ws: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
Reference in New Issue
Block a user