Compare commits
16 Commits
cc4e07f5ca
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| bbd2885b49 | |||
| 6512e9da8b | |||
| 93f7c25ce6 | |||
| 5f9b684604 | |||
| 37760f685c | |||
| a2b9255814 | |||
| d28e4e194c | |||
| 4bee473d98 | |||
| e9b51cc337 | |||
| c5cfce2ea4 | |||
| 970e734dda | |||
| 3559f2bf1e | |||
| 9d27ed1484 | |||
| 52bfe51aa4 | |||
| 58dc3fe3d0 | |||
| 9f21fa1f4f |
@@ -1,13 +1,17 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="">
|
<html lang="">
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<link rel="icon" href="/favicon.ico">
|
<link rel="icon" href="/favicon.ico">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>Vite App</title>
|
<title>DataBuddy WebScreen</title>
|
||||||
|
<script src="/config.js"></script>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<div id="app"></div>
|
<div id="app"></div>
|
||||||
<script type="module" src="/src/main.js"></script>
|
<script type="module" src="/src/main.js"></script>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
@@ -13,6 +13,10 @@
|
|||||||
"format": "prettier --write --experimental-cli src/"
|
"format": "prettier --write --experimental-cli src/"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@element-plus/icons-vue": "^2.3.2",
|
||||||
|
"@fortawesome/fontawesome-free": "^7.1.0",
|
||||||
|
"element-plus": "^2.12.0",
|
||||||
|
"nprogress": "^0.2.0",
|
||||||
"vue": "^3.5.25",
|
"vue": "^3.5.25",
|
||||||
"vue-router": "^4.6.3"
|
"vue-router": "^4.6.3"
|
||||||
},
|
},
|
||||||
|
|||||||
12
public/config.js
Normal file
12
public/config.js
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
window.$website = {
|
||||||
|
isDemo: false,
|
||||||
|
isDemoTip: '这是一个演示大屏',
|
||||||
|
title: 'DataBuddy 数据大屏',
|
||||||
|
name: 'DataBuddy 数据大屏',
|
||||||
|
subName: '你的数据可视化引擎',
|
||||||
|
apiUrl: 'https://databuddy.nyamiao.com/api/databuddy',
|
||||||
|
cdnUrl: '',
|
||||||
|
backgroundImg: '/img/background.webp',
|
||||||
|
autoSave: false,
|
||||||
|
autoSaveTime: 60000,
|
||||||
|
}
|
||||||
BIN
public/img/background.webp
Normal file
BIN
public/img/background.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 18 KiB |
82
src/App.vue
82
src/App.vue
@@ -1,85 +1,3 @@
|
|||||||
<script setup>
|
|
||||||
import { RouterLink, RouterView } from 'vue-router'
|
|
||||||
import HelloWorld from './components/HelloWorld.vue'
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<header>
|
|
||||||
<img alt="Vue logo" class="logo" src="@/assets/logo.svg" width="125" height="125" />
|
|
||||||
|
|
||||||
<div class="wrapper">
|
|
||||||
<HelloWorld msg="You did it!" />
|
|
||||||
|
|
||||||
<nav>
|
|
||||||
<RouterLink to="/">Home</RouterLink>
|
|
||||||
<RouterLink to="/about">About</RouterLink>
|
|
||||||
</nav>
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<RouterView />
|
<RouterView />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
header {
|
|
||||||
line-height: 1.5;
|
|
||||||
max-height: 100vh;
|
|
||||||
}
|
|
||||||
|
|
||||||
.logo {
|
|
||||||
display: block;
|
|
||||||
margin: 0 auto 2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
nav {
|
|
||||||
width: 100%;
|
|
||||||
font-size: 12px;
|
|
||||||
text-align: center;
|
|
||||||
margin-top: 2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
nav a.router-link-exact-active {
|
|
||||||
color: var(--color-text);
|
|
||||||
}
|
|
||||||
|
|
||||||
nav a.router-link-exact-active:hover {
|
|
||||||
background-color: transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
nav a {
|
|
||||||
display: inline-block;
|
|
||||||
padding: 0 1rem;
|
|
||||||
border-left: 1px solid var(--color-border);
|
|
||||||
}
|
|
||||||
|
|
||||||
nav a:first-of-type {
|
|
||||||
border: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: 1024px) {
|
|
||||||
header {
|
|
||||||
display: flex;
|
|
||||||
place-items: center;
|
|
||||||
padding-right: calc(var(--section-gap) / 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
.logo {
|
|
||||||
margin: 0 2rem 0 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
header .wrapper {
|
|
||||||
display: flex;
|
|
||||||
place-items: flex-start;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
nav {
|
|
||||||
text-align: left;
|
|
||||||
margin-left: -1rem;
|
|
||||||
font-size: 1rem;
|
|
||||||
|
|
||||||
padding: 1rem 0;
|
|
||||||
margin-top: 1rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|||||||
@@ -1,86 +0,0 @@
|
|||||||
/* color palette from <https://github.com/vuejs/theme> */
|
|
||||||
:root {
|
|
||||||
--vt-c-white: #ffffff;
|
|
||||||
--vt-c-white-soft: #f8f8f8;
|
|
||||||
--vt-c-white-mute: #f2f2f2;
|
|
||||||
|
|
||||||
--vt-c-black: #181818;
|
|
||||||
--vt-c-black-soft: #222222;
|
|
||||||
--vt-c-black-mute: #282828;
|
|
||||||
|
|
||||||
--vt-c-indigo: #2c3e50;
|
|
||||||
|
|
||||||
--vt-c-divider-light-1: rgba(60, 60, 60, 0.29);
|
|
||||||
--vt-c-divider-light-2: rgba(60, 60, 60, 0.12);
|
|
||||||
--vt-c-divider-dark-1: rgba(84, 84, 84, 0.65);
|
|
||||||
--vt-c-divider-dark-2: rgba(84, 84, 84, 0.48);
|
|
||||||
|
|
||||||
--vt-c-text-light-1: var(--vt-c-indigo);
|
|
||||||
--vt-c-text-light-2: rgba(60, 60, 60, 0.66);
|
|
||||||
--vt-c-text-dark-1: var(--vt-c-white);
|
|
||||||
--vt-c-text-dark-2: rgba(235, 235, 235, 0.64);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* semantic color variables for this project */
|
|
||||||
:root {
|
|
||||||
--color-background: var(--vt-c-white);
|
|
||||||
--color-background-soft: var(--vt-c-white-soft);
|
|
||||||
--color-background-mute: var(--vt-c-white-mute);
|
|
||||||
|
|
||||||
--color-border: var(--vt-c-divider-light-2);
|
|
||||||
--color-border-hover: var(--vt-c-divider-light-1);
|
|
||||||
|
|
||||||
--color-heading: var(--vt-c-text-light-1);
|
|
||||||
--color-text: var(--vt-c-text-light-1);
|
|
||||||
|
|
||||||
--section-gap: 160px;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (prefers-color-scheme: dark) {
|
|
||||||
:root {
|
|
||||||
--color-background: var(--vt-c-black);
|
|
||||||
--color-background-soft: var(--vt-c-black-soft);
|
|
||||||
--color-background-mute: var(--vt-c-black-mute);
|
|
||||||
|
|
||||||
--color-border: var(--vt-c-divider-dark-2);
|
|
||||||
--color-border-hover: var(--vt-c-divider-dark-1);
|
|
||||||
|
|
||||||
--color-heading: var(--vt-c-text-dark-1);
|
|
||||||
--color-text: var(--vt-c-text-dark-2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
*,
|
|
||||||
*::before,
|
|
||||||
*::after {
|
|
||||||
box-sizing: border-box;
|
|
||||||
margin: 0;
|
|
||||||
font-weight: normal;
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
|
||||||
min-height: 100vh;
|
|
||||||
color: var(--color-text);
|
|
||||||
background: var(--color-background);
|
|
||||||
transition:
|
|
||||||
color 0.5s,
|
|
||||||
background-color 0.5s;
|
|
||||||
line-height: 1.6;
|
|
||||||
font-family:
|
|
||||||
Inter,
|
|
||||||
-apple-system,
|
|
||||||
BlinkMacSystemFont,
|
|
||||||
'Segoe UI',
|
|
||||||
Roboto,
|
|
||||||
Oxygen,
|
|
||||||
Ubuntu,
|
|
||||||
Cantarell,
|
|
||||||
'Fira Sans',
|
|
||||||
'Droid Sans',
|
|
||||||
'Helvetica Neue',
|
|
||||||
sans-serif;
|
|
||||||
font-size: 15px;
|
|
||||||
text-rendering: optimizeLegibility;
|
|
||||||
-webkit-font-smoothing: antialiased;
|
|
||||||
-moz-osx-font-smoothing: grayscale;
|
|
||||||
}
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
@import './base.css';
|
|
||||||
|
|
||||||
#app {
|
|
||||||
max-width: 1280px;
|
|
||||||
margin: 0 auto;
|
|
||||||
padding: 2rem;
|
|
||||||
font-weight: normal;
|
|
||||||
}
|
|
||||||
|
|
||||||
a,
|
|
||||||
.green {
|
|
||||||
text-decoration: none;
|
|
||||||
color: hsla(160, 100%, 37%, 1);
|
|
||||||
transition: 0.4s;
|
|
||||||
padding: 3px;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (hover: hover) {
|
|
||||||
a:hover {
|
|
||||||
background-color: hsla(160, 100%, 37%, 0.2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: 1024px) {
|
|
||||||
body {
|
|
||||||
display: flex;
|
|
||||||
place-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
#app {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 1fr 1fr;
|
|
||||||
padding: 0 2rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
<script setup>
|
|
||||||
defineProps({
|
|
||||||
msg: {
|
|
||||||
type: String,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div class="greetings">
|
|
||||||
<h1 class="green">{{ msg }}</h1>
|
|
||||||
<h3>
|
|
||||||
You’ve successfully created a project with
|
|
||||||
<a href="https://vite.dev/" target="_blank" rel="noopener">Vite</a> +
|
|
||||||
<a href="https://vuejs.org/" target="_blank" rel="noopener">Vue 3</a>.
|
|
||||||
</h3>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
h1 {
|
|
||||||
font-weight: 500;
|
|
||||||
font-size: 2.6rem;
|
|
||||||
position: relative;
|
|
||||||
top: -10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
h3 {
|
|
||||||
font-size: 1.2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.greetings h1,
|
|
||||||
.greetings h3 {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: 1024px) {
|
|
||||||
.greetings h1,
|
|
||||||
.greetings h3 {
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,95 +0,0 @@
|
|||||||
<script setup>
|
|
||||||
import WelcomeItem from './WelcomeItem.vue'
|
|
||||||
import DocumentationIcon from './icons/IconDocumentation.vue'
|
|
||||||
import ToolingIcon from './icons/IconTooling.vue'
|
|
||||||
import EcosystemIcon from './icons/IconEcosystem.vue'
|
|
||||||
import CommunityIcon from './icons/IconCommunity.vue'
|
|
||||||
import SupportIcon from './icons/IconSupport.vue'
|
|
||||||
|
|
||||||
const openReadmeInEditor = () => fetch('/__open-in-editor?file=README.md')
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<WelcomeItem>
|
|
||||||
<template #icon>
|
|
||||||
<DocumentationIcon />
|
|
||||||
</template>
|
|
||||||
<template #heading>Documentation</template>
|
|
||||||
|
|
||||||
Vue’s
|
|
||||||
<a href="https://vuejs.org/" target="_blank" rel="noopener">official documentation</a>
|
|
||||||
provides you with all information you need to get started.
|
|
||||||
</WelcomeItem>
|
|
||||||
|
|
||||||
<WelcomeItem>
|
|
||||||
<template #icon>
|
|
||||||
<ToolingIcon />
|
|
||||||
</template>
|
|
||||||
<template #heading>Tooling</template>
|
|
||||||
|
|
||||||
This project is served and bundled with
|
|
||||||
<a href="https://vite.dev/guide/features.html" target="_blank" rel="noopener">Vite</a>. The
|
|
||||||
recommended IDE setup is
|
|
||||||
<a href="https://code.visualstudio.com/" target="_blank" rel="noopener">VSCode</a>
|
|
||||||
+
|
|
||||||
<a href="https://github.com/vuejs/language-tools" target="_blank" rel="noopener"
|
|
||||||
>Vue - Official</a
|
|
||||||
>. If you need to test your components and web pages, check out
|
|
||||||
<a href="https://vitest.dev/" target="_blank" rel="noopener">Vitest</a>
|
|
||||||
and
|
|
||||||
<a href="https://www.cypress.io/" target="_blank" rel="noopener">Cypress</a>
|
|
||||||
/
|
|
||||||
<a href="https://playwright.dev/" target="_blank" rel="noopener">Playwright</a>.
|
|
||||||
|
|
||||||
<br />
|
|
||||||
|
|
||||||
More instructions are available in
|
|
||||||
<a href="javascript:void(0)" @click="openReadmeInEditor"><code>README.md</code></a
|
|
||||||
>.
|
|
||||||
</WelcomeItem>
|
|
||||||
|
|
||||||
<WelcomeItem>
|
|
||||||
<template #icon>
|
|
||||||
<EcosystemIcon />
|
|
||||||
</template>
|
|
||||||
<template #heading>Ecosystem</template>
|
|
||||||
|
|
||||||
Get official tools and libraries for your project:
|
|
||||||
<a href="https://pinia.vuejs.org/" target="_blank" rel="noopener">Pinia</a>,
|
|
||||||
<a href="https://router.vuejs.org/" target="_blank" rel="noopener">Vue Router</a>,
|
|
||||||
<a href="https://test-utils.vuejs.org/" target="_blank" rel="noopener">Vue Test Utils</a>, and
|
|
||||||
<a href="https://github.com/vuejs/devtools" target="_blank" rel="noopener">Vue Dev Tools</a>. If
|
|
||||||
you need more resources, we suggest paying
|
|
||||||
<a href="https://github.com/vuejs/awesome-vue" target="_blank" rel="noopener">Awesome Vue</a>
|
|
||||||
a visit.
|
|
||||||
</WelcomeItem>
|
|
||||||
|
|
||||||
<WelcomeItem>
|
|
||||||
<template #icon>
|
|
||||||
<CommunityIcon />
|
|
||||||
</template>
|
|
||||||
<template #heading>Community</template>
|
|
||||||
|
|
||||||
Got stuck? Ask your question on
|
|
||||||
<a href="https://chat.vuejs.org" target="_blank" rel="noopener">Vue Land</a>
|
|
||||||
(our official Discord server), or
|
|
||||||
<a href="https://stackoverflow.com/questions/tagged/vue.js" target="_blank" rel="noopener"
|
|
||||||
>StackOverflow</a
|
|
||||||
>. You should also follow the official
|
|
||||||
<a href="https://bsky.app/profile/vuejs.org" target="_blank" rel="noopener">@vuejs.org</a>
|
|
||||||
Bluesky account or the
|
|
||||||
<a href="https://x.com/vuejs" target="_blank" rel="noopener">@vuejs</a>
|
|
||||||
X account for latest news in the Vue world.
|
|
||||||
</WelcomeItem>
|
|
||||||
|
|
||||||
<WelcomeItem>
|
|
||||||
<template #icon>
|
|
||||||
<SupportIcon />
|
|
||||||
</template>
|
|
||||||
<template #heading>Support Vue</template>
|
|
||||||
|
|
||||||
As an independent project, Vue relies on community backing for its sustainability. You can help
|
|
||||||
us by
|
|
||||||
<a href="https://vuejs.org/sponsor/" target="_blank" rel="noopener">becoming a sponsor</a>.
|
|
||||||
</WelcomeItem>
|
|
||||||
</template>
|
|
||||||
@@ -1,86 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="item">
|
|
||||||
<i>
|
|
||||||
<slot name="icon"></slot>
|
|
||||||
</i>
|
|
||||||
<div class="details">
|
|
||||||
<h3>
|
|
||||||
<slot name="heading"></slot>
|
|
||||||
</h3>
|
|
||||||
<slot></slot>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.item {
|
|
||||||
margin-top: 2rem;
|
|
||||||
display: flex;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.details {
|
|
||||||
flex: 1;
|
|
||||||
margin-left: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
i {
|
|
||||||
display: flex;
|
|
||||||
place-items: center;
|
|
||||||
place-content: center;
|
|
||||||
width: 32px;
|
|
||||||
height: 32px;
|
|
||||||
color: var(--color-text);
|
|
||||||
}
|
|
||||||
|
|
||||||
h3 {
|
|
||||||
font-size: 1.2rem;
|
|
||||||
font-weight: 500;
|
|
||||||
margin-bottom: 0.4rem;
|
|
||||||
color: var(--color-heading);
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: 1024px) {
|
|
||||||
.item {
|
|
||||||
margin-top: 0;
|
|
||||||
padding: 0.4rem 0 1rem calc(var(--section-gap) / 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
i {
|
|
||||||
top: calc(50% - 25px);
|
|
||||||
left: -26px;
|
|
||||||
position: absolute;
|
|
||||||
border: 1px solid var(--color-border);
|
|
||||||
background: var(--color-background);
|
|
||||||
border-radius: 8px;
|
|
||||||
width: 50px;
|
|
||||||
height: 50px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.item:before {
|
|
||||||
content: ' ';
|
|
||||||
border-left: 1px solid var(--color-border);
|
|
||||||
position: absolute;
|
|
||||||
left: 0;
|
|
||||||
bottom: calc(50% + 25px);
|
|
||||||
height: calc(50% - 25px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.item:after {
|
|
||||||
content: ' ';
|
|
||||||
border-left: 1px solid var(--color-border);
|
|
||||||
position: absolute;
|
|
||||||
left: 0;
|
|
||||||
top: calc(50% + 25px);
|
|
||||||
height: calc(50% - 25px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.item:first-of-type:before {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.item:last-of-type:after {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
<template>
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor">
|
|
||||||
<path
|
|
||||||
d="M15 4a1 1 0 1 0 0 2V4zm0 11v-1a1 1 0 0 0-1 1h1zm0 4l-.707.707A1 1 0 0 0 16 19h-1zm-4-4l.707-.707A1 1 0 0 0 11 14v1zm-4.707-1.293a1 1 0 0 0-1.414 1.414l1.414-1.414zm-.707.707l-.707-.707.707.707zM9 11v-1a1 1 0 0 0-.707.293L9 11zm-4 0h1a1 1 0 0 0-1-1v1zm0 4H4a1 1 0 0 0 1.707.707L5 15zm10-9h2V4h-2v2zm2 0a1 1 0 0 1 1 1h2a3 3 0 0 0-3-3v2zm1 1v6h2V7h-2zm0 6a1 1 0 0 1-1 1v2a3 3 0 0 0 3-3h-2zm-1 1h-2v2h2v-2zm-3 1v4h2v-4h-2zm1.707 3.293l-4-4-1.414 1.414 4 4 1.414-1.414zM11 14H7v2h4v-2zm-4 0c-.276 0-.525-.111-.707-.293l-1.414 1.414C5.42 15.663 6.172 16 7 16v-2zm-.707 1.121l3.414-3.414-1.414-1.414-3.414 3.414 1.414 1.414zM9 12h4v-2H9v2zm4 0a3 3 0 0 0 3-3h-2a1 1 0 0 1-1 1v2zm3-3V3h-2v6h2zm0-6a3 3 0 0 0-3-3v2a1 1 0 0 1 1 1h2zm-3-3H3v2h10V0zM3 0a3 3 0 0 0-3 3h2a1 1 0 0 1 1-1V0zM0 3v6h2V3H0zm0 6a3 3 0 0 0 3 3v-2a1 1 0 0 1-1-1H0zm3 3h2v-2H3v2zm1-1v4h2v-4H4zm1.707 4.707l.586-.586-1.414-1.414-.586.586 1.414 1.414z"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</template>
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
<template>
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="17" fill="currentColor">
|
|
||||||
<path
|
|
||||||
d="M11 2.253a1 1 0 1 0-2 0h2zm-2 13a1 1 0 1 0 2 0H9zm.447-12.167a1 1 0 1 0 1.107-1.666L9.447 3.086zM1 2.253L.447 1.42A1 1 0 0 0 0 2.253h1zm0 13H0a1 1 0 0 0 1.553.833L1 15.253zm8.447.833a1 1 0 1 0 1.107-1.666l-1.107 1.666zm0-14.666a1 1 0 1 0 1.107 1.666L9.447 1.42zM19 2.253h1a1 1 0 0 0-.447-.833L19 2.253zm0 13l-.553.833A1 1 0 0 0 20 15.253h-1zm-9.553-.833a1 1 0 1 0 1.107 1.666L9.447 14.42zM9 2.253v13h2v-13H9zm1.553-.833C9.203.523 7.42 0 5.5 0v2c1.572 0 2.961.431 3.947 1.086l1.107-1.666zM5.5 0C3.58 0 1.797.523.447 1.42l1.107 1.666C2.539 2.431 3.928 2 5.5 2V0zM0 2.253v13h2v-13H0zm1.553 13.833C2.539 15.431 3.928 15 5.5 15v-2c-1.92 0-3.703.523-5.053 1.42l1.107 1.666zM5.5 15c1.572 0 2.961.431 3.947 1.086l1.107-1.666C9.203 13.523 7.42 13 5.5 13v2zm5.053-11.914C11.539 2.431 12.928 2 14.5 2V0c-1.92 0-3.703.523-5.053 1.42l1.107 1.666zM14.5 2c1.573 0 2.961.431 3.947 1.086l1.107-1.666C18.203.523 16.421 0 14.5 0v2zm3.5.253v13h2v-13h-2zm1.553 12.167C18.203 13.523 16.421 13 14.5 13v2c1.573 0 2.961.431 3.947 1.086l1.107-1.666zM14.5 13c-1.92 0-3.703.523-5.053 1.42l1.107 1.666C11.539 15.431 12.928 15 14.5 15v-2z"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</template>
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
<template>
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="20" fill="currentColor">
|
|
||||||
<path
|
|
||||||
d="M11.447 8.894a1 1 0 1 0-.894-1.789l.894 1.789zm-2.894-.789a1 1 0 1 0 .894 1.789l-.894-1.789zm0 1.789a1 1 0 1 0 .894-1.789l-.894 1.789zM7.447 7.106a1 1 0 1 0-.894 1.789l.894-1.789zM10 9a1 1 0 1 0-2 0h2zm-2 2.5a1 1 0 1 0 2 0H8zm9.447-5.606a1 1 0 1 0-.894-1.789l.894 1.789zm-2.894-.789a1 1 0 1 0 .894 1.789l-.894-1.789zm2 .789a1 1 0 1 0 .894-1.789l-.894 1.789zm-1.106-2.789a1 1 0 1 0-.894 1.789l.894-1.789zM18 5a1 1 0 1 0-2 0h2zm-2 2.5a1 1 0 1 0 2 0h-2zm-5.447-4.606a1 1 0 1 0 .894-1.789l-.894 1.789zM9 1l.447-.894a1 1 0 0 0-.894 0L9 1zm-2.447.106a1 1 0 1 0 .894 1.789l-.894-1.789zm-6 3a1 1 0 1 0 .894 1.789L.553 4.106zm2.894.789a1 1 0 1 0-.894-1.789l.894 1.789zm-2-.789a1 1 0 1 0-.894 1.789l.894-1.789zm1.106 2.789a1 1 0 1 0 .894-1.789l-.894 1.789zM2 5a1 1 0 1 0-2 0h2zM0 7.5a1 1 0 1 0 2 0H0zm8.553 12.394a1 1 0 1 0 .894-1.789l-.894 1.789zm-1.106-2.789a1 1 0 1 0-.894 1.789l.894-1.789zm1.106 1a1 1 0 1 0 .894 1.789l-.894-1.789zm2.894.789a1 1 0 1 0-.894-1.789l.894 1.789zM8 19a1 1 0 1 0 2 0H8zm2-2.5a1 1 0 1 0-2 0h2zm-7.447.394a1 1 0 1 0 .894-1.789l-.894 1.789zM1 15H0a1 1 0 0 0 .553.894L1 15zm1-2.5a1 1 0 1 0-2 0h2zm12.553 2.606a1 1 0 1 0 .894 1.789l-.894-1.789zM17 15l.447.894A1 1 0 0 0 18 15h-1zm1-2.5a1 1 0 1 0-2 0h2zm-7.447-5.394l-2 1 .894 1.789 2-1-.894-1.789zm-1.106 1l-2-1-.894 1.789 2 1 .894-1.789zM8 9v2.5h2V9H8zm8.553-4.894l-2 1 .894 1.789 2-1-.894-1.789zm.894 0l-2-1-.894 1.789 2 1 .894-1.789zM16 5v2.5h2V5h-2zm-4.553-3.894l-2-1-.894 1.789 2 1 .894-1.789zm-2.894-1l-2 1 .894 1.789 2-1L8.553.106zM1.447 5.894l2-1-.894-1.789-2 1 .894 1.789zm-.894 0l2 1 .894-1.789-2-1-.894 1.789zM0 5v2.5h2V5H0zm9.447 13.106l-2-1-.894 1.789 2 1 .894-1.789zm0 1.789l2-1-.894-1.789-2 1 .894 1.789zM10 19v-2.5H8V19h2zm-6.553-3.894l-2-1-.894 1.789 2 1 .894-1.789zM2 15v-2.5H0V15h2zm13.447 1.894l2-1-.894-1.789-2 1 .894 1.789zM18 15v-2.5h-2V15h2z"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</template>
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
<template>
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor">
|
|
||||||
<path
|
|
||||||
d="M10 3.22l-.61-.6a5.5 5.5 0 0 0-7.666.105 5.5 5.5 0 0 0-.114 7.665L10 18.78l8.39-8.4a5.5 5.5 0 0 0-.114-7.665 5.5 5.5 0 0 0-7.666-.105l-.61.61z"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</template>
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
<!-- This icon is from <https://github.com/Templarian/MaterialDesign>, distributed under Apache 2.0 (https://www.apache.org/licenses/LICENSE-2.0) license-->
|
|
||||||
<template>
|
|
||||||
<svg
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
|
||||||
aria-hidden="true"
|
|
||||||
role="img"
|
|
||||||
class="iconify iconify--mdi"
|
|
||||||
width="24"
|
|
||||||
height="24"
|
|
||||||
preserveAspectRatio="xMidYMid meet"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
d="M20 18v-4h-3v1h-2v-1H9v1H7v-1H4v4h16M6.33 8l-1.74 4H7v-1h2v1h6v-1h2v1h2.41l-1.74-4H6.33M9 5v1h6V5H9m12.84 7.61c.1.22.16.48.16.8V18c0 .53-.21 1-.6 1.41c-.4.4-.85.59-1.4.59H4c-.55 0-1-.19-1.4-.59C2.21 19 2 18.53 2 18v-4.59c0-.32.06-.58.16-.8L4.5 7.22C4.84 6.41 5.45 6 6.33 6H7V5c0-.55.18-1 .57-1.41C7.96 3.2 8.44 3 9 3h6c.56 0 1.04.2 1.43.59c.39.41.57.86.57 1.41v1h.67c.88 0 1.49.41 1.83 1.22l2.34 5.39z"
|
|
||||||
fill="currentColor"
|
|
||||||
></path>
|
|
||||||
</svg>
|
|
||||||
</template>
|
|
||||||
45
src/components/index/Category.vue
Normal file
45
src/components/index/Category.vue
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
<script setup>
|
||||||
|
import { defineAsyncComponent } from 'vue'
|
||||||
|
const Nodata = defineAsyncComponent(() => import('@/components/index/Nodata.vue'))
|
||||||
|
const Create = defineAsyncComponent(() => import('@/components/index/modal/category/Create.vue'))
|
||||||
|
|
||||||
|
const emit = defineEmits(['show-modal', 'show-tip'])
|
||||||
|
|
||||||
|
const handleCreate = () => {
|
||||||
|
emit('show-modal', {
|
||||||
|
title: '创建分类',
|
||||||
|
content: Create,
|
||||||
|
theme: 'light',
|
||||||
|
width: '300px',
|
||||||
|
height: '30%',
|
||||||
|
events: {
|
||||||
|
'show-tip': (payload) => emit('show-tip', payload)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<header class="header">
|
||||||
|
<div class="header-left">
|
||||||
|
<button class="header-button create-category" @click="handleCreate"><i class="fa-solid fa-folder-plus"></i> 创建分类</button>
|
||||||
|
</div>
|
||||||
|
<div class="header-right">
|
||||||
|
<input type="text" placeholder="请输入名称" class="header-search"></input>
|
||||||
|
<button class="header-button search-button"><i class="fa-solid fa-magnifying-glass"></i></button>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
<Nodata />
|
||||||
|
</template>
|
||||||
|
<style scoped>
|
||||||
|
.create-category {
|
||||||
|
color: #fff;
|
||||||
|
background-color: #00b7ff;
|
||||||
|
transition: background-color 0.2s ease;
|
||||||
|
}
|
||||||
|
.create-category:hover {
|
||||||
|
background-color: #00a2e2;
|
||||||
|
}
|
||||||
|
.create-category:active {
|
||||||
|
background-color: #0088c3;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
93
src/components/index/Components.vue
Normal file
93
src/components/index/Components.vue
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
<script setup>
|
||||||
|
import { defineAsyncComponent, ref } from 'vue'
|
||||||
|
const Nodata = defineAsyncComponent(() => import('@/components/index/Nodata.vue'))
|
||||||
|
const Create = defineAsyncComponent(() => import('@/components/index/modal/components/Create.vue'))
|
||||||
|
|
||||||
|
const activeTab = ref('all')
|
||||||
|
|
||||||
|
const handleTabClick = (tabName) => {
|
||||||
|
activeTab.value = tabName
|
||||||
|
|
||||||
|
// 这里可以添加实际业务逻辑,例如:
|
||||||
|
// if (tabName === 'vue') {
|
||||||
|
// loadVueComponents()
|
||||||
|
// } else if (tabName === 'echarts') {
|
||||||
|
// loadEchartsComponents()
|
||||||
|
// } else {
|
||||||
|
// loadHtmlComponents()
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
const emit = defineEmits(['show-modal', 'show-tip'])
|
||||||
|
const handleCreate = () => {
|
||||||
|
emit('show-modal', {
|
||||||
|
title: '创建组件',
|
||||||
|
content: Create,
|
||||||
|
theme: 'light',
|
||||||
|
height: '80%',
|
||||||
|
width: '60%',
|
||||||
|
events: {
|
||||||
|
'show-tip': (payload) => emit('show-tip', payload)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<header class="header">
|
||||||
|
<div class="header-left">
|
||||||
|
<button class="header-button create-component" @click="handleCreate"><i class="fa-solid fa-square-plus"></i> 创建组件</button>
|
||||||
|
</div>
|
||||||
|
<div class="header-right">
|
||||||
|
<input type="text" placeholder="请输入名称" class="header-search"></input>
|
||||||
|
<button class="header-button search-button"><i class="fa-solid fa-magnifying-glass"></i></button>
|
||||||
|
</div>
|
||||||
|
<div class="header-components-list">
|
||||||
|
<button class="header-components-item header-button" :class="{ 'components-item-actived': activeTab === 'all' }" @click="handleTabClick('all')">全部组件</button>
|
||||||
|
<button class="header-components-item header-button" :class="{ 'components-item-actived': activeTab === 'vue' }" @click="handleTabClick('vue')">VUE 组件</button>
|
||||||
|
<button class="header-components-item header-button" :class="{ 'components-item-actived': activeTab === 'echarts' }" @click="handleTabClick('echarts')">ECharts 组件</button>
|
||||||
|
<button class="header-components-item header-button" :class="{ 'components-item-actived': activeTab === 'html' }" @click="handleTabClick('html')">HTML 组件</button>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
<Nodata />
|
||||||
|
</template>
|
||||||
|
<style scoped>
|
||||||
|
.create-component {
|
||||||
|
color: #fff;
|
||||||
|
background-color: #00b7ff;
|
||||||
|
transition: background-color 0.2s ease;
|
||||||
|
}
|
||||||
|
.create-component:hover {
|
||||||
|
background-color: #00a2e2;
|
||||||
|
}
|
||||||
|
.create-component:active {
|
||||||
|
background-color: #0088c3;
|
||||||
|
}
|
||||||
|
.header-components-list {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 8px 0;
|
||||||
|
margin-top: 58px;
|
||||||
|
padding-left: 15px;
|
||||||
|
width: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
.header-components-item {
|
||||||
|
background-color: rgba(255,255,255,0.05);
|
||||||
|
backdrop-filter: blur(10px) saturate(180%);
|
||||||
|
-webkit-backdrop-filter: blur(10px) saturate(180%);
|
||||||
|
border: 1px solid rgba(255,255,255,0.4);
|
||||||
|
box-shadow:
|
||||||
|
0 8px 32px rgba(0,0,0,0.2),
|
||||||
|
inset 0 4px 20px rgba(255,255,255,0.3);
|
||||||
|
color: #333;
|
||||||
|
text-shadow: 1px 1px 4px rgba(255,255,255,0.4);
|
||||||
|
transition: background-color 0.2s ease;
|
||||||
|
}
|
||||||
|
.header-components-item:hover {
|
||||||
|
background-color: rgba(255,255,255,0.25);
|
||||||
|
}
|
||||||
|
.components-item-actived,
|
||||||
|
.header-components-item:active {
|
||||||
|
background-color: rgba(255,255,255,0.6);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
64
src/components/index/Datascreen.vue
Normal file
64
src/components/index/Datascreen.vue
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
<script setup>
|
||||||
|
import { defineAsyncComponent } from 'vue'
|
||||||
|
const Nodata = defineAsyncComponent(() => import('@/components/index/Nodata.vue'))
|
||||||
|
const Create = defineAsyncComponent(() => import('@/components/index/modal/datascreen/Create.vue'))
|
||||||
|
const Import = defineAsyncComponent(() => import('@/components/index/modal/datascreen/Import.vue'))
|
||||||
|
|
||||||
|
const emit = defineEmits(['show-modal', 'show-tip'])
|
||||||
|
const handleCreate = () => {
|
||||||
|
emit('show-modal', {
|
||||||
|
title: '创建大屏',
|
||||||
|
content: Create,
|
||||||
|
theme: 'light',
|
||||||
|
width: '80%',
|
||||||
|
height: '80%',
|
||||||
|
events: {
|
||||||
|
'show-tip': (payload) => emit('show-tip', payload)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
const handleImport = () => {
|
||||||
|
emit('show-modal', {
|
||||||
|
title: '导入大屏',
|
||||||
|
content: Import,
|
||||||
|
theme: 'light'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<header class="header">
|
||||||
|
<div class="header-left">
|
||||||
|
<button class="header-button create-datascreen" @click="handleCreate"><i class="fa-solid fa-plus"></i> 创建大屏</button>
|
||||||
|
<button class="header-button import-datascreen" @click="handleImport"><i class="fa-solid fa-file-import"></i> 导入大屏</button>
|
||||||
|
</div>
|
||||||
|
<div class="header-right">
|
||||||
|
<input type="text" placeholder="请输入名称" class="header-search"></input>
|
||||||
|
<button class="header-button search-button"><i class="fa-solid fa-magnifying-glass"></i></button>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
<Nodata />
|
||||||
|
</template>
|
||||||
|
<style scoped>
|
||||||
|
.create-datascreen {
|
||||||
|
color: #fff;
|
||||||
|
background-color: #00b7ff;
|
||||||
|
transition: background-color 0.2s ease;
|
||||||
|
}
|
||||||
|
.create-datascreen:hover {
|
||||||
|
background-color: #00a2e2;
|
||||||
|
}
|
||||||
|
.create-datascreen:active {
|
||||||
|
background-color: #0088c3;
|
||||||
|
}
|
||||||
|
.import-datascreen {
|
||||||
|
color: #fff;
|
||||||
|
background-color: #f472b6;
|
||||||
|
transition: background-color 0.2s ease;
|
||||||
|
}
|
||||||
|
.import-datascreen:hover {
|
||||||
|
background-color: #e267a7;
|
||||||
|
}
|
||||||
|
.import-datascreen:active {
|
||||||
|
background-color: #c35b99;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
45
src/components/index/Datasource.vue
Normal file
45
src/components/index/Datasource.vue
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
<script setup>
|
||||||
|
import { defineAsyncComponent } from 'vue'
|
||||||
|
const Nodata = defineAsyncComponent(() => import('@/components/index/Nodata.vue'))
|
||||||
|
const Create = defineAsyncComponent(() => import('@/components/index/modal/datasource/Create.vue'))
|
||||||
|
|
||||||
|
const emit = defineEmits(['show-modal', 'show-tip'])
|
||||||
|
|
||||||
|
const handleCreate = () => {
|
||||||
|
emit('show-modal', {
|
||||||
|
title: '创建数据源',
|
||||||
|
content: Create,
|
||||||
|
theme: 'light',
|
||||||
|
width: '80%',
|
||||||
|
height: '80%',
|
||||||
|
events: {
|
||||||
|
'show-tip': (payload) => emit('show-tip', payload)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<header class="header">
|
||||||
|
<div class="header-left">
|
||||||
|
<button class="header-button create-datasource" @click="handleCreate"><i class="fa-solid fa-calendar-plus"></i> 创建数据源</button>
|
||||||
|
</div>
|
||||||
|
<div class="header-right">
|
||||||
|
<input type="text" placeholder="请输入名称" class="header-search"></input>
|
||||||
|
<button class="header-button search-button"><i class="fa-solid fa-magnifying-glass"></i></button>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
<Nodata />
|
||||||
|
</template>
|
||||||
|
<style scoped>
|
||||||
|
.create-datasource {
|
||||||
|
color: #fff;
|
||||||
|
background-color: #00b7ff;
|
||||||
|
transition: background-color 0.2s ease;
|
||||||
|
}
|
||||||
|
.create-datasource:hover {
|
||||||
|
background-color: #00a2e2;
|
||||||
|
}
|
||||||
|
.create-datasource:active {
|
||||||
|
background-color: #0088c3;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
39
src/components/index/Files.vue
Normal file
39
src/components/index/Files.vue
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
<script setup>
|
||||||
|
import { defineAsyncComponent } from 'vue'
|
||||||
|
const Nodata = defineAsyncComponent(() => import('@/components/index/Nodata.vue'))
|
||||||
|
const Upload = defineAsyncComponent(() => import('@/components/index/modal/files/Upload.vue'))
|
||||||
|
|
||||||
|
const emit = defineEmits(['show-modal', 'show-tip'])
|
||||||
|
const handleCreate = () => {
|
||||||
|
emit('show-modal', {
|
||||||
|
title: '上传文件',
|
||||||
|
content: Upload,
|
||||||
|
theme: 'light'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<header class="header">
|
||||||
|
<div class="header-left">
|
||||||
|
<button class="header-button create-files" @click="handleCreate"><i class="fa-solid fa-upload"></i> 上传文件</button>
|
||||||
|
</div>
|
||||||
|
<div class="header-right">
|
||||||
|
<input type="text" placeholder="请输入名称" class="header-search"></input>
|
||||||
|
<button class="header-button search-button"><i class="fa-solid fa-magnifying-glass"></i></button>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
<Nodata />
|
||||||
|
</template>
|
||||||
|
<style scoped>
|
||||||
|
.create-files {
|
||||||
|
color: #fff;
|
||||||
|
background-color: #00b7ff;
|
||||||
|
transition: background-color 0.2s ease;
|
||||||
|
}
|
||||||
|
.create-files:hover {
|
||||||
|
background-color: #00a2e2;
|
||||||
|
}
|
||||||
|
.create-files:active {
|
||||||
|
background-color: #0088c3;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
82
src/components/index/Leftbar.vue
Normal file
82
src/components/index/Leftbar.vue
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
<script setup>
|
||||||
|
import { computed } from 'vue'
|
||||||
|
import { useRoute, useRouter } from 'vue-router'
|
||||||
|
import { Eleme } from '@element-plus/icons-vue'
|
||||||
|
|
||||||
|
const route = useRoute()
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
|
const handleMenuClick = (path) => {
|
||||||
|
router.push(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
const activeMenu = computed(() => {
|
||||||
|
const path = route.path
|
||||||
|
if (path === '/' || path === '/datascreen') return '/datascreen'
|
||||||
|
if (path.startsWith('/category')) return '/category'
|
||||||
|
if (path.startsWith('/datasource')) return '/datasource'
|
||||||
|
if (path.startsWith('/record')) return '/record'
|
||||||
|
if (path.startsWith('/components')) return '/components'
|
||||||
|
if (path.startsWith('/variables')) return '/variables'
|
||||||
|
if (path.startsWith('/files')) return '/files'
|
||||||
|
if (path.startsWith('/maps')) return '/maps'
|
||||||
|
if (path.startsWith('/tools')) return '/tools'
|
||||||
|
return ''
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<div class="leftbar">
|
||||||
|
<button class="leftbar-item" :class="{ 'activated': activeMenu === '/datascreen' }" @click="handleMenuClick('/datascreen')"><i class="fa-solid fa-table-cells-large"></i> 大屏管理</button>
|
||||||
|
<button class="leftbar-item" :class="{ 'activated': activeMenu === '/category' }" @click="handleMenuClick('/category')"><i class="fa-solid fa-folder-open"></i> 大屏分类</button>
|
||||||
|
<button class="leftbar-item" :class="{ 'activated': activeMenu === '/datasource' }" @click="handleMenuClick('/datasource')"><i class="fa-solid fa-database"></i> 数据源管理</button>
|
||||||
|
<button class="leftbar-item" :class="{ 'activated': activeMenu === '/record' }" @click="handleMenuClick('/record')"><i class="fa-solid fa-suitcase"></i> 数据集管理</button>
|
||||||
|
<button class="leftbar-item" :class="{ 'activated': activeMenu === '/components' }" @click="handleMenuClick('/components')"><el-icon style="font-size:20px;transform: translateY(4px);"><Eleme /></el-icon> 组件库</button>
|
||||||
|
<button class="leftbar-item" :class="{ 'activated': activeMenu === '/variables' }" @click="handleMenuClick('/variables')"><i class="fa-solid fa-lightbulb"></i> 全局变量</button>
|
||||||
|
<button class="leftbar-item" :class="{ 'activated': activeMenu === '/files' }" @click="handleMenuClick('/files')"><i class="fa-solid fa-layer-group"></i> 静态资源</button>
|
||||||
|
<button class="leftbar-item" :class="{ 'activated': activeMenu === '/maps' }" @click="handleMenuClick('/maps')"><i class="fa-solid fa-location-dot"></i> 地图管理</button>
|
||||||
|
<button class="leftbar-item" :class="{ 'activated': activeMenu === '/tools' }" @click="handleMenuClick('/tools')"><i class="fa-solid fa-boxes-stacked"></i> 工具箱</button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<style scoped>
|
||||||
|
.leftbar {
|
||||||
|
position: fixed;
|
||||||
|
top: 38px;
|
||||||
|
left: 8px;
|
||||||
|
border-radius: 16px;
|
||||||
|
width: 200px;
|
||||||
|
height: calc(100% - 48px);
|
||||||
|
background-color: rgba(255,255,255,0.15);
|
||||||
|
backdrop-filter: blur(10px) saturate(180%);
|
||||||
|
-webkit-backdrop-filter: blur(10px) saturate(180%);
|
||||||
|
border: 1px solid rgba(255,255,255,0.4);
|
||||||
|
box-shadow:
|
||||||
|
0 8px 32px rgba(0,0,0,0.2),
|
||||||
|
inset 0 4px 20px rgba(255,255,255,0.3);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
padding-top: 8px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
.leftbar-item {
|
||||||
|
width: 90%;
|
||||||
|
height: 48px;
|
||||||
|
border-radius: 16px;
|
||||||
|
border: 0;
|
||||||
|
background-color: transparent;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 16px;
|
||||||
|
color: #333;
|
||||||
|
text-shadow: 1px 1px 4px rgba(255,255,255,0.4);
|
||||||
|
transition: background-color 0.2s ease;
|
||||||
|
}
|
||||||
|
.leftbar-item:hover {
|
||||||
|
background-color: rgba(255,255,255,0.2);
|
||||||
|
}
|
||||||
|
.leftbar-item:active,
|
||||||
|
.activated {
|
||||||
|
background-color: rgba(255,255,255,0.4);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
129
src/components/index/Main.vue
Normal file
129
src/components/index/Main.vue
Normal file
@@ -0,0 +1,129 @@
|
|||||||
|
<script setup>
|
||||||
|
import { defineAsyncComponent, computed } from 'vue'
|
||||||
|
import { useRoute } from 'vue-router'
|
||||||
|
|
||||||
|
const route = useRoute()
|
||||||
|
const DynamicContent = computed(() => {
|
||||||
|
switch (route.path) {
|
||||||
|
case '/':
|
||||||
|
case '/datascreen':
|
||||||
|
return defineAsyncComponent(() => import('@/components/index/Datascreen.vue'))
|
||||||
|
case '/category':
|
||||||
|
return defineAsyncComponent(() => import('@/components/index/Category.vue'))
|
||||||
|
case '/datasource':
|
||||||
|
return defineAsyncComponent(() => import('@/components/index/Datasource.vue'))
|
||||||
|
case '/record':
|
||||||
|
return defineAsyncComponent(() => import('@/components/index/Record.vue'))
|
||||||
|
case '/components':
|
||||||
|
return defineAsyncComponent(() => import('@/components/index/Components.vue'))
|
||||||
|
case '/variables':
|
||||||
|
return defineAsyncComponent(() => import('@/components/index/Variables.vue'))
|
||||||
|
case '/files':
|
||||||
|
return defineAsyncComponent(() => import('@/components/index/Files.vue'))
|
||||||
|
case '/maps':
|
||||||
|
return defineAsyncComponent(() => import('@/components/index/Maps.vue'))
|
||||||
|
case '/tools':
|
||||||
|
return defineAsyncComponent(() => import('@/components/index/Tools.vue'))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const emit = defineEmits(['show-modal', 'show-tip'])
|
||||||
|
const handleShowModal = (payload) => {
|
||||||
|
emit('show-modal', payload)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleShowTip = (payload) => {
|
||||||
|
emit('show-tip', payload)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<div class="container">
|
||||||
|
<component :is="DynamicContent" @show-modal="handleShowModal" @show-tip="handleShowTip" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<style scoped>
|
||||||
|
.container {
|
||||||
|
position: fixed;
|
||||||
|
top: 38px;
|
||||||
|
left: 216px;
|
||||||
|
border-radius: 16px;
|
||||||
|
width: calc(100vw - 224px);
|
||||||
|
height: calc(100vh - 48px);
|
||||||
|
background-color: rgba(255,255,255,0.15);
|
||||||
|
backdrop-filter: blur(10px) saturate(180%);
|
||||||
|
-webkit-backdrop-filter: blur(10px) saturate(180%);
|
||||||
|
border: 1px solid rgba(255,255,255,0.4);
|
||||||
|
box-shadow:
|
||||||
|
0 8px 32px rgba(0,0,0,0.2),
|
||||||
|
inset 0 4px 20px rgba(255,255,255,0.3);
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
:deep(.header-left) {
|
||||||
|
position: absolute;
|
||||||
|
top: 15px;
|
||||||
|
left: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.header-right) {
|
||||||
|
position: absolute;
|
||||||
|
top: 15px;
|
||||||
|
right: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.header-button) {
|
||||||
|
width: 128px;
|
||||||
|
height: 32px;
|
||||||
|
font-size: 15px;
|
||||||
|
border: none;
|
||||||
|
border-radius: 10px;
|
||||||
|
margin: 0 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.header-button:first-child) {
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.header-button:last-child) {
|
||||||
|
margin-right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.header-search) {
|
||||||
|
width: 150px;
|
||||||
|
height: 32px;
|
||||||
|
font-size: 15px;
|
||||||
|
border: none;
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 0 10px;
|
||||||
|
background-color: rgba(255, 255, 255, 0.3);
|
||||||
|
transition: background-color 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.header-search:hover) {
|
||||||
|
background-color: rgba(255, 255, 255, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.header-search:focus) {
|
||||||
|
outline: none;
|
||||||
|
background-color: rgba(255, 255, 255, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.search-button) {
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
background-color: #f472b6;
|
||||||
|
color: #fff;
|
||||||
|
border: none;
|
||||||
|
border-radius: 10px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background-color 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.search-button:hover) {
|
||||||
|
background-color: #e267a7;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.search-button:active) {
|
||||||
|
background-color: #c35b99;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
62
src/components/index/Maps.vue
Normal file
62
src/components/index/Maps.vue
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
<script setup>
|
||||||
|
import { defineAsyncComponent } from 'vue'
|
||||||
|
const Nodata = defineAsyncComponent(() => import('@/components/index/Nodata.vue'))
|
||||||
|
const Create = defineAsyncComponent(() => import('@/components/index/modal/maps/Create.vue'))
|
||||||
|
|
||||||
|
const emit = defineEmits(['show-modal', 'show-tip'])
|
||||||
|
const handleCreate = () => {
|
||||||
|
emit('show-modal', {
|
||||||
|
title: '创建地图',
|
||||||
|
content: Create,
|
||||||
|
theme: 'light',
|
||||||
|
width: '40%',
|
||||||
|
height: '60%',
|
||||||
|
events: {
|
||||||
|
'show-tip': (payload) => emit('show-tip', payload)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
const devTips = () => {
|
||||||
|
emit('show-tip', {
|
||||||
|
content: '功能开发中...',
|
||||||
|
theme: 'info'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<header class="header">
|
||||||
|
<div class="header-left">
|
||||||
|
<button class="header-button create-maps" @click="handleCreate"><i class="fa-solid fa-location-dot"></i> 创建地图</button>
|
||||||
|
<button class="header-button download-maps" @click="devTips"><i class="fa-solid fa-download"></i> 地图下载</button>
|
||||||
|
</div>
|
||||||
|
<div class="header-right">
|
||||||
|
<input type="text" placeholder="请输入名称" class="header-search"></input>
|
||||||
|
<button class="header-button search-button"><i class="fa-solid fa-magnifying-glass"></i></button>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
<Nodata />
|
||||||
|
</template>
|
||||||
|
<style scoped>
|
||||||
|
.create-maps {
|
||||||
|
color: #fff;
|
||||||
|
background-color: #00b7ff;
|
||||||
|
transition: background-color 0.2s ease;
|
||||||
|
}
|
||||||
|
.create-maps:hover {
|
||||||
|
background-color: #00a2e2;
|
||||||
|
}
|
||||||
|
.create-maps:active {
|
||||||
|
background-color: #0088c3;
|
||||||
|
}
|
||||||
|
.download-maps {
|
||||||
|
color: #fff;
|
||||||
|
background-color: #f472b6;
|
||||||
|
transition: background-color 0.2s ease;
|
||||||
|
}
|
||||||
|
.download-maps:hover {
|
||||||
|
background-color: #e267a7;
|
||||||
|
}
|
||||||
|
.download-maps:active {
|
||||||
|
background-color: #c35b99;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
31
src/components/index/Nodata.vue
Normal file
31
src/components/index/Nodata.vue
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
<template>
|
||||||
|
<main class="main">
|
||||||
|
<div class="no-data">
|
||||||
|
<i class="fa-solid fa-circle-xmark"></i>
|
||||||
|
<span>暂无数据</span>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
</template>
|
||||||
|
<style scoped>
|
||||||
|
.main {
|
||||||
|
height: calc(100vh - 62px);
|
||||||
|
padding-top: 62px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
.no-data {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
height: 100%;
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
.no-data i {
|
||||||
|
font-size: 64px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
color: #0095cf;
|
||||||
|
}
|
||||||
|
.no-data span {
|
||||||
|
color: #00354a;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
44
src/components/index/Record.vue
Normal file
44
src/components/index/Record.vue
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
<script setup>
|
||||||
|
import { defineAsyncComponent } from 'vue'
|
||||||
|
const Nodata = defineAsyncComponent(() => import('@/components/index/Nodata.vue'))
|
||||||
|
const Create = defineAsyncComponent(() => import('@/components/index/modal/record/Create.vue'))
|
||||||
|
|
||||||
|
const emit = defineEmits(['show-modal', 'show-tip'])
|
||||||
|
const handleCreate = () => {
|
||||||
|
emit('show-modal', {
|
||||||
|
title: '创建数据集',
|
||||||
|
content: Create,
|
||||||
|
theme: 'light',
|
||||||
|
width: '60%',
|
||||||
|
height: '60%',
|
||||||
|
events: {
|
||||||
|
'show-tip': (payload) => emit('show-tip', payload)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<header class="header">
|
||||||
|
<div class="header-left">
|
||||||
|
<button class="header-button create-record" @click="handleCreate"><i class="fa-solid fa-file-circle-plus"></i> 创建数据集</button>
|
||||||
|
</div>
|
||||||
|
<div class="header-right">
|
||||||
|
<input type="text" placeholder="请输入名称" class="header-search"></input>
|
||||||
|
<button class="header-button search-button"><i class="fa-solid fa-magnifying-glass"></i></button>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
<Nodata />
|
||||||
|
</template>
|
||||||
|
<style scoped>
|
||||||
|
.create-record {
|
||||||
|
color: #fff;
|
||||||
|
background-color: #00b7ff;
|
||||||
|
transition: background-color 0.2s ease;
|
||||||
|
}
|
||||||
|
.create-record:hover {
|
||||||
|
background-color: #00a2e2;
|
||||||
|
}
|
||||||
|
.create-record:active {
|
||||||
|
background-color: #0088c3;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
65
src/components/index/Tools.vue
Normal file
65
src/components/index/Tools.vue
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
<script setup>
|
||||||
|
const emit = defineEmits(['show-modal', 'show-tip'])
|
||||||
|
|
||||||
|
const devTips = () => {
|
||||||
|
emit('show-tip', {
|
||||||
|
content: '功能开发中...',
|
||||||
|
theme: 'info'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<div class="tools-item">
|
||||||
|
<h2>大屏轮播</h2>
|
||||||
|
<input type="text" placeholder="请输入大屏ID多个用','间隔" class="tools-item-input"></input>
|
||||||
|
<button class="tools-item-btn" @click="devTips">预览大屏</button>
|
||||||
|
</div>
|
||||||
|
<div class="tools-item">
|
||||||
|
<h2>HTML 页面</h2>
|
||||||
|
<input type="text" placeholder="请输入大屏ID" class="tools-item-input"></input>
|
||||||
|
<button class="tools-item-btn" @click="devTips">预览大屏</button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<style scoped>
|
||||||
|
.tools-item {
|
||||||
|
margin-left: 20px;
|
||||||
|
color: #333;
|
||||||
|
text-shadow: 1px 1px 4px rgba(255,255,255,0.4);
|
||||||
|
}
|
||||||
|
.tools-item-input {
|
||||||
|
margin-top: -24px;
|
||||||
|
width: calc(100vw - 380px);
|
||||||
|
height: 32px;
|
||||||
|
font-size: 15px;
|
||||||
|
padding: 0 10px;
|
||||||
|
border: none;
|
||||||
|
border-radius: 10px;
|
||||||
|
background-color: rgba(255,255,255,0.3);
|
||||||
|
transition: background-color 0.2s ease;
|
||||||
|
}
|
||||||
|
.tools-item-input:hover {
|
||||||
|
background-color: rgba(255,255,255,0.4);
|
||||||
|
}
|
||||||
|
.tools-item-input:focus {
|
||||||
|
outline: none;
|
||||||
|
background-color: rgba(255,255,255,0.5);
|
||||||
|
}
|
||||||
|
.tools-item-btn {
|
||||||
|
margin-left: 5px;
|
||||||
|
width: 90px;
|
||||||
|
height: 32px;
|
||||||
|
border: none;
|
||||||
|
border-radius: 10px;
|
||||||
|
font-size: 15px;
|
||||||
|
background-color: rgba(255,255,255,0.3);
|
||||||
|
color: #333;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background-color 0.2s ease;
|
||||||
|
}
|
||||||
|
.tools-item-btn:hover {
|
||||||
|
background-color: rgba(255,255,255,0.4);
|
||||||
|
}
|
||||||
|
.tools-item-btn:active {
|
||||||
|
background-color: rgba(255,255,255,0.5);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
82
src/components/index/Topbar.vue
Normal file
82
src/components/index/Topbar.vue
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
<script setup>
|
||||||
|
import { onMounted, onUnmounted } from 'vue'
|
||||||
|
import getConfig from '@/utils/config'
|
||||||
|
import { useRouter } from 'vue-router'
|
||||||
|
|
||||||
|
const config = getConfig()
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
|
let intervalId;
|
||||||
|
|
||||||
|
const updateTime = () => {
|
||||||
|
const now = new Date();
|
||||||
|
const timeString = `${now.getFullYear()}年${now.getMonth() + 1}月${now.getDate()}日 ${['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六'][now.getDay()]} ${now.getHours().toString().padStart(2, '0')}:${now.getMinutes().toString().padStart(2, '0')}:${now.getSeconds().toString().padStart(2, '0')}`;
|
||||||
|
document.getElementById('time').textContent = timeString;
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
updateTime();
|
||||||
|
intervalId = setInterval(updateTime, 1000);
|
||||||
|
})
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
clearInterval(intervalId);
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<div class="topbar">
|
||||||
|
<div class="logo">
|
||||||
|
<img src="@/assets/logo.svg" alt="logo" />
|
||||||
|
</div>
|
||||||
|
<div class="topbar-left">
|
||||||
|
<button>{{ config.title }}</button>
|
||||||
|
</div>
|
||||||
|
<div class="topbar-right">
|
||||||
|
<div id="time"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<style scoped>
|
||||||
|
.topbar {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 32px;
|
||||||
|
background-color: rgba(255,255,255,0.15);
|
||||||
|
backdrop-filter: blur(3px);
|
||||||
|
-webkit-backdrop-filter: blur(3px);
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
.logo {
|
||||||
|
position: fixed;
|
||||||
|
top: 8px;
|
||||||
|
left: 10px;
|
||||||
|
}
|
||||||
|
.logo img {
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
}
|
||||||
|
.topbar-left,
|
||||||
|
.topbar-right {
|
||||||
|
position: fixed;
|
||||||
|
top: 5px;
|
||||||
|
font-size: 16px;
|
||||||
|
color: #fff;
|
||||||
|
text-shadow: 1px 1px 4px rgba(0,0,0,0.4);
|
||||||
|
}
|
||||||
|
.topbar-left {
|
||||||
|
left: 35px;
|
||||||
|
}
|
||||||
|
.topbar-right {
|
||||||
|
right: 16px;
|
||||||
|
}
|
||||||
|
.topbar-left button,
|
||||||
|
.topbar-right button {
|
||||||
|
border: none;
|
||||||
|
background-color: transparent;
|
||||||
|
font-size: 16px;
|
||||||
|
color: #fff;
|
||||||
|
text-shadow: 1px 1px 4px rgba(0,0,0,0.4);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
44
src/components/index/Variables.vue
Normal file
44
src/components/index/Variables.vue
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
<script setup>
|
||||||
|
import { defineAsyncComponent } from 'vue'
|
||||||
|
const Nodata = defineAsyncComponent(() => import('@/components/index/Nodata.vue'))
|
||||||
|
const Create = defineAsyncComponent(() => import('@/components/index/modal/variables/Create.vue'))
|
||||||
|
|
||||||
|
const emit = defineEmits(['show-modal', 'show-tip'])
|
||||||
|
const handleCreate = () => {
|
||||||
|
emit('show-modal', {
|
||||||
|
title: '创建变量',
|
||||||
|
content: Create,
|
||||||
|
theme: 'light',
|
||||||
|
width: '60%',
|
||||||
|
height: '50%',
|
||||||
|
events: {
|
||||||
|
'show-tip': (payload) => emit('show-tip', payload)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<header class="header">
|
||||||
|
<div class="header-left">
|
||||||
|
<button class="header-button create-variables" @click="handleCreate"><i class="fa-solid fa-square-root-variable"></i> 创建变量</button>
|
||||||
|
</div>
|
||||||
|
<div class="header-right">
|
||||||
|
<input type="text" placeholder="请输入名称" class="header-search"></input>
|
||||||
|
<button class="header-button search-button"><i class="fa-solid fa-magnifying-glass"></i></button>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
<Nodata />
|
||||||
|
</template>
|
||||||
|
<style scoped>
|
||||||
|
.create-variables {
|
||||||
|
color: #fff;
|
||||||
|
background-color: #00b7ff;
|
||||||
|
transition: background-color 0.2s ease;
|
||||||
|
}
|
||||||
|
.create-variables:hover {
|
||||||
|
background-color: #00a2e2;
|
||||||
|
}
|
||||||
|
.create-variables:active {
|
||||||
|
background-color: #0088c3;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
106
src/components/index/modal/category/Create.vue
Normal file
106
src/components/index/modal/category/Create.vue
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
<script setup>
|
||||||
|
const emit = defineEmits(['close-modal', 'confirm-create', 'show-tip'])
|
||||||
|
|
||||||
|
const handleClose = () => {
|
||||||
|
emit('close-modal')
|
||||||
|
}
|
||||||
|
|
||||||
|
const devTips = () => {
|
||||||
|
emit('show-tip', {
|
||||||
|
content: '功能开发中...',
|
||||||
|
theme: 'info'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<div class="create-category">
|
||||||
|
<div class="create-category-items">
|
||||||
|
<span><span style="color:red;">*</span>模块名: </span>
|
||||||
|
<input type="text" placeholder="请输入 模块名"></input>
|
||||||
|
</div>
|
||||||
|
<div class="create-category-items">
|
||||||
|
<span><span style="color:red;">*</span>模块值: </span>
|
||||||
|
<input type="number" placeholder="请输入 模块值"></input>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="create-category-btns">
|
||||||
|
<button class="confirm-btn" @click="devTips"><i class="fa-solid fa-check"></i> 保存</button>
|
||||||
|
<button class="cancel-btn" @click="handleClose"><i class="fa-solid fa-xmark"></i> 取消</button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<style scoped>
|
||||||
|
.create-category {
|
||||||
|
position: absolute;
|
||||||
|
top: 45px;
|
||||||
|
left: 15px;
|
||||||
|
max-height: calc(100vh - 150px);
|
||||||
|
width: calc(100% - 32px);
|
||||||
|
overflow-y: auto;
|
||||||
|
overflow-x: hidden;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
.create-category-items {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
min-height: 40px;
|
||||||
|
}
|
||||||
|
.create-category-items span:first-child {
|
||||||
|
width: 80px;
|
||||||
|
text-align: right;
|
||||||
|
margin-right: 15px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
.create-category-items input {
|
||||||
|
flex: 1;
|
||||||
|
padding: 8px 12px;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
border-radius: 10px;
|
||||||
|
}
|
||||||
|
.create-category-items input:hover {
|
||||||
|
border-color: #f472b6;
|
||||||
|
}
|
||||||
|
.create-category-items input:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: #e267a7;
|
||||||
|
}
|
||||||
|
.create-category-btns {
|
||||||
|
position: absolute;
|
||||||
|
align-items: center;
|
||||||
|
bottom: 20px;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 20px;
|
||||||
|
}
|
||||||
|
.confirm-btn,
|
||||||
|
.cancel-btn {
|
||||||
|
height: 32px;
|
||||||
|
width: 85px;
|
||||||
|
border: none;
|
||||||
|
border-radius: 10px;
|
||||||
|
font-size: 14px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.confirm-btn {
|
||||||
|
background-color: #4CAF50;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
.confirm-btn:hover {
|
||||||
|
background-color: #46a049;
|
||||||
|
}
|
||||||
|
.confirm-btn:active {
|
||||||
|
background-color: #38803a;
|
||||||
|
}
|
||||||
|
.cancel-btn {
|
||||||
|
background-color: #f44336;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
.cancel-btn:hover {
|
||||||
|
background-color: #da190b;
|
||||||
|
}
|
||||||
|
.cancel-btn:active {
|
||||||
|
background-color: #b2100a;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
316
src/components/index/modal/components/Create.vue
Normal file
316
src/components/index/modal/components/Create.vue
Normal file
@@ -0,0 +1,316 @@
|
|||||||
|
<script setup>
|
||||||
|
import { ref, onMounted, onUnmounted, nextTick } from 'vue'
|
||||||
|
|
||||||
|
const emit = defineEmits(['close-modal', 'confirm-create', 'show-tip'])
|
||||||
|
|
||||||
|
const lineNumbersRef = ref(null)
|
||||||
|
const dataTextareaRef = ref(null)
|
||||||
|
const resizeObserver = ref(null)
|
||||||
|
const dataValue = ref('')
|
||||||
|
|
||||||
|
const handleClose = () => {
|
||||||
|
emit('close-modal')
|
||||||
|
}
|
||||||
|
|
||||||
|
const devTips = () => {
|
||||||
|
emit('show-tip', {
|
||||||
|
content: '功能开发中...',
|
||||||
|
theme: 'info'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateLineNumbers = () => {
|
||||||
|
const textarea = dataTextareaRef.value
|
||||||
|
|
||||||
|
if (!textarea || !lineNumbersRef.value) return
|
||||||
|
|
||||||
|
const lines = textarea.value.split('\n').length
|
||||||
|
lineNumbersRef.value.innerHTML = Array.from({ length: lines }, (_, i) =>
|
||||||
|
`<span style="line-height: 1.5em;">${i + 1}</span>`
|
||||||
|
).join('')
|
||||||
|
|
||||||
|
nextTick(() => {
|
||||||
|
if (!textarea || !lineNumbersRef.value) return
|
||||||
|
|
||||||
|
const newHeight = Math.max(textarea.scrollHeight, textarea.clientHeight)
|
||||||
|
lineNumbersRef.value.style.height = `${newHeight}px`
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleResize = () => {
|
||||||
|
if (dataTextareaRef.value) {
|
||||||
|
updateLineNumbers()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleScroll = () => {
|
||||||
|
const textarea = dataTextareaRef.value
|
||||||
|
if (lineNumbersRef.value && textarea) {
|
||||||
|
lineNumbersRef.value.style.transform = `translateY(${-textarea.scrollTop}px)`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const setupResizeObserver = () => {
|
||||||
|
if (!dataTextareaRef.value) return
|
||||||
|
|
||||||
|
resizeObserver.value = new ResizeObserver((entries) => {
|
||||||
|
for (const entry of entries) {
|
||||||
|
if (lineNumbersRef.value) {
|
||||||
|
const textarea = dataTextareaRef.value
|
||||||
|
if (textarea) {
|
||||||
|
const newHeight = Math.max(textarea.scrollHeight, textarea.clientHeight)
|
||||||
|
lineNumbersRef.value.style.height = `${newHeight}px`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
resizeObserver.value.observe(dataTextareaRef.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
updateLineNumbers()
|
||||||
|
window.addEventListener('resize', handleResize)
|
||||||
|
setupResizeObserver()
|
||||||
|
})
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
window.removeEventListener('resize', handleResize)
|
||||||
|
if (resizeObserver.value) {
|
||||||
|
resizeObserver.value.disconnect()
|
||||||
|
resizeObserver.value = null
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="create-components">
|
||||||
|
<div class="components-row">
|
||||||
|
<div class="create-components-items name-field">
|
||||||
|
<span><span style="color:red;">*</span>组件名称: </span>
|
||||||
|
<input type="text" placeholder="请输入 组件名称"></input>
|
||||||
|
</div>
|
||||||
|
<div class="create-components-items type-field">
|
||||||
|
<span><span style="color:red;">*</span>组件类型: </span>
|
||||||
|
<select><option>请选择组件类型</option></select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="create-components-items full-width">
|
||||||
|
<span>组件数据: </span>
|
||||||
|
<div class="textarea-container">
|
||||||
|
<div class="line-numbers" ref="lineNumbersRef"></div>
|
||||||
|
<textarea ref="dataTextareaRef" class="data-textarea" @input="updateLineNumbers" @scroll="handleScroll" v-model="dataValue"></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="create-components-items">
|
||||||
|
<span></span>
|
||||||
|
<button class="run-btn" @click="devTips"><i class="fa-solid fa-check"></i> 运行</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="create-components-items full-width">
|
||||||
|
<span>组件预览: </span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="create-components-btns">
|
||||||
|
<button class="confirm-btn" @click="devTips"><i class="fa-solid fa-check"></i> 保存</button>
|
||||||
|
<button class="cancel-btn" @click="handleClose"><i class="fa-solid fa-xmark"></i> 取消</button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.create-components {
|
||||||
|
position: absolute;
|
||||||
|
top: 45px;
|
||||||
|
left: 10px;
|
||||||
|
right: 20px;
|
||||||
|
bottom: 70px;
|
||||||
|
overflow-y: auto;
|
||||||
|
overflow-x: hidden;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
.components-row {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 15px;
|
||||||
|
}
|
||||||
|
.create-components-items {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
min-height: 40px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.create-components-items.full-width {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.create-components-items span:first-child {
|
||||||
|
width: 100px;
|
||||||
|
text-align: right;
|
||||||
|
margin-right: 15px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
align-self: flex-start;
|
||||||
|
padding-top: 8px;
|
||||||
|
}
|
||||||
|
.create-components-items input,
|
||||||
|
.create-components-items select {
|
||||||
|
flex: 1;
|
||||||
|
padding: 8px 12px;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
border-radius: 10px;
|
||||||
|
width: 20px;
|
||||||
|
}
|
||||||
|
.create-components-items input:hover,
|
||||||
|
.create-components-items select:hover {
|
||||||
|
border-color: #f472b6;
|
||||||
|
}
|
||||||
|
.create-components-items input:focus,
|
||||||
|
.create-components-items select:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: #e267a7;
|
||||||
|
box-shadow: 0 0 0 2px rgba(226, 103, 167, 0.2);
|
||||||
|
}
|
||||||
|
.create-components-items select {
|
||||||
|
background-color: #fff;
|
||||||
|
}
|
||||||
|
.textarea-container {
|
||||||
|
flex: 1;
|
||||||
|
position: relative;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
border-radius: 10px;
|
||||||
|
overflow: hidden;
|
||||||
|
min-height: 100px;
|
||||||
|
background-color: #fff;
|
||||||
|
}
|
||||||
|
.line-numbers {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
width: 35px;
|
||||||
|
padding: 8px 0;
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
border-right: 1px solid #ddd;
|
||||||
|
text-align: center;
|
||||||
|
font-family: monospace;
|
||||||
|
font-size: 13px;
|
||||||
|
user-select: none;
|
||||||
|
color: #999;
|
||||||
|
z-index: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
will-change: transform;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
.data-textarea {
|
||||||
|
position: relative;
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
min-height: 256px;
|
||||||
|
padding: 8px 12px 8px 45px;
|
||||||
|
border: none;
|
||||||
|
resize: vertical;
|
||||||
|
background: transparent;
|
||||||
|
font-family: monospace;
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 1.5em;
|
||||||
|
outline: none;
|
||||||
|
color: inherit;
|
||||||
|
overflow: auto;
|
||||||
|
box-sizing: border-box;
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
.data-textarea:hover {
|
||||||
|
border-color: #f472b6;
|
||||||
|
}
|
||||||
|
.run-btn {
|
||||||
|
border: none;
|
||||||
|
border-radius: 10px;
|
||||||
|
width: 80px;
|
||||||
|
height: 32px;
|
||||||
|
background-color: #f472b6;
|
||||||
|
color: #fff;
|
||||||
|
font-size: 14px;
|
||||||
|
cursor: pointer;
|
||||||
|
gap: 5px;
|
||||||
|
}
|
||||||
|
.run-btn:hover {
|
||||||
|
background-color: #da609b;
|
||||||
|
}
|
||||||
|
.run-btn:active {
|
||||||
|
background-color: #b24a80;
|
||||||
|
}
|
||||||
|
.create-components::-webkit-scrollbar,
|
||||||
|
.data-textarea::-webkit-scrollbar {
|
||||||
|
width: 6px;
|
||||||
|
}
|
||||||
|
.create-components::-webkit-scrollbar-track,
|
||||||
|
.data-textarea::-webkit-scrollbar-track {
|
||||||
|
background: #f1f1f1;
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
.create-components::-webkit-scrollbar-thumb,
|
||||||
|
.data-textarea::-webkit-scrollbar-thumb {
|
||||||
|
background: #c1c1c1;
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
.create-components::-webkit-scrollbar-thumb:hover,
|
||||||
|
.data-textarea::-webkit-scrollbar-thumb:hover {
|
||||||
|
background: #a8a8a8;
|
||||||
|
}
|
||||||
|
.data-textarea:focus {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
.create-components-btns {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 20px;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 20px;
|
||||||
|
}
|
||||||
|
.confirm-btn,
|
||||||
|
.cancel-btn {
|
||||||
|
height: 32px;
|
||||||
|
width: 85px;
|
||||||
|
border: none;
|
||||||
|
border-radius: 10px;
|
||||||
|
font-size: 14px;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 5px;
|
||||||
|
}
|
||||||
|
.confirm-btn {
|
||||||
|
background-color: #4CAF50;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
.confirm-btn:hover {
|
||||||
|
background-color: #46a049;
|
||||||
|
}
|
||||||
|
.confirm-btn:active {
|
||||||
|
background-color: #38803a;
|
||||||
|
}
|
||||||
|
.cancel-btn {
|
||||||
|
background-color: #f44336;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
.cancel-btn:hover {
|
||||||
|
background-color: #da190b;
|
||||||
|
}
|
||||||
|
.cancel-btn:active {
|
||||||
|
background-color: #b2100a;
|
||||||
|
}
|
||||||
|
@media (min-width: 768px) {
|
||||||
|
.components-row {
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
}
|
||||||
|
.name-field, .type-field {
|
||||||
|
width: calc(50% - 7.5px);
|
||||||
|
}
|
||||||
|
.name-field {
|
||||||
|
margin-right: 15px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
155
src/components/index/modal/datascreen/Create.vue
Normal file
155
src/components/index/modal/datascreen/Create.vue
Normal file
@@ -0,0 +1,155 @@
|
|||||||
|
<script setup>
|
||||||
|
const emit = defineEmits(['close-modal', 'confirm-create', 'show-tip'])
|
||||||
|
|
||||||
|
const handleClose = () => {
|
||||||
|
emit('close-modal')
|
||||||
|
}
|
||||||
|
|
||||||
|
const devTips = () => {
|
||||||
|
emit('show-tip', {
|
||||||
|
content: '功能开发中...',
|
||||||
|
theme: 'info'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<div class="create-datascreen">
|
||||||
|
<div class="create-datascreen-items">
|
||||||
|
<span><span style="color:red;">*</span>分组: </span>
|
||||||
|
<select><option>请选择分组</option></select>
|
||||||
|
</div>
|
||||||
|
<div class="create-datascreen-items">
|
||||||
|
<span><span style="color:red;">*</span>大屏名称: </span>
|
||||||
|
<input type="text" placeholder="请输入 大屏名称"></input>
|
||||||
|
</div>
|
||||||
|
<div class="create-datascreen-items">
|
||||||
|
<span>密码: </span>
|
||||||
|
<input type="password" placeholder="请输入 密码"></input>
|
||||||
|
</div>
|
||||||
|
<div class="create-datascreen-items">
|
||||||
|
<span><span style="color:red;">*</span>大屏尺寸: </span>
|
||||||
|
<div class="size-inputs">
|
||||||
|
<div class="size-inputs">
|
||||||
|
<input type="text" placeholder="请输入 宽度" value="1920">
|
||||||
|
<span class="size-separator"> <i class="fa-solid fa-xmark"></i> </span>
|
||||||
|
<input type="text" placeholder="请输入 高度" value="1080">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="create-datascreen-items">
|
||||||
|
<span>缩略图: </span>
|
||||||
|
<button @click="devTips"><i class="fa-solid fa-upload"></i> 点击上传</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="create-datascreen-btns">
|
||||||
|
<button class="confirm-btn" @click="devTips"><i class="fa-solid fa-check"></i> 确认创建</button>
|
||||||
|
<button class="cancel-btn" @click="handleClose"><i class="fa-solid fa-xmark"></i> 关闭</button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<style scoped>
|
||||||
|
.create-datascreen {
|
||||||
|
position: absolute;
|
||||||
|
top: 40px;
|
||||||
|
left: 20px;
|
||||||
|
max-height: calc(100vh - 150px);
|
||||||
|
width: calc(100% - 64px);
|
||||||
|
overflow-y: auto;
|
||||||
|
overflow-x: hidden;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
.create-datascreen-items {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
min-height: 40px;
|
||||||
|
}
|
||||||
|
.create-datascreen-items span:first-child {
|
||||||
|
width: 100px;
|
||||||
|
text-align: right;
|
||||||
|
margin-right: 15px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
.create-datascreen-items input,
|
||||||
|
.create-datascreen-items select,
|
||||||
|
.create-datascreen-items button {
|
||||||
|
flex: 1;
|
||||||
|
padding: 8px 12px;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
border-radius: 10px;
|
||||||
|
}
|
||||||
|
.create-datascreen-items input:hover {
|
||||||
|
border-color: #f472b6;
|
||||||
|
}
|
||||||
|
.create-datascreen-items input:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: #e267a7;
|
||||||
|
}
|
||||||
|
.create-datascreen-items select {
|
||||||
|
background-color: #fff;
|
||||||
|
}
|
||||||
|
.create-datascreen-items select:hover {
|
||||||
|
border-color: #f472b6;
|
||||||
|
}
|
||||||
|
.create-datascreen-items select:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: #e267a7;
|
||||||
|
}
|
||||||
|
.create-datascreen-items button:hover {
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
}
|
||||||
|
.create-datascreen-items button:active {
|
||||||
|
background-color: #ddd;
|
||||||
|
}
|
||||||
|
.size-inputs {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.size-inputs input {
|
||||||
|
width: 40px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.separator {
|
||||||
|
margin: 0 10px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
.create-datascreen-btns {
|
||||||
|
position: absolute;
|
||||||
|
align-items: center;
|
||||||
|
bottom: 20px;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 20px;
|
||||||
|
}
|
||||||
|
.confirm-btn,
|
||||||
|
.cancel-btn {
|
||||||
|
height: 35px;
|
||||||
|
width: 128px;
|
||||||
|
border: none;
|
||||||
|
border-radius: 10px;
|
||||||
|
font-size: 16px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.confirm-btn {
|
||||||
|
background-color: #4CAF50;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
.confirm-btn:hover {
|
||||||
|
background-color: #46a049;
|
||||||
|
}
|
||||||
|
.confirm-btn:active {
|
||||||
|
background-color: #38803a;
|
||||||
|
}
|
||||||
|
.cancel-btn {
|
||||||
|
background-color: #f44336;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
.cancel-btn:hover {
|
||||||
|
background-color: #da190b;
|
||||||
|
}
|
||||||
|
.cancel-btn:active {
|
||||||
|
background-color: #b2100a;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
8
src/components/index/modal/datascreen/Import.vue
Normal file
8
src/components/index/modal/datascreen/Import.vue
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<script setup>
|
||||||
|
const emit = defineEmits(['close-modal', 'confirm-create', 'show-tip'])
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
导入功能只需要点击这里或者将文件拖拽到这里即可导入<br>
|
||||||
|
但是导入功能目前暂不可用<br>
|
||||||
|
因为喵喵们正在开发这个功能!
|
||||||
|
</template>
|
||||||
221
src/components/index/modal/datasource/Create.vue
Normal file
221
src/components/index/modal/datasource/Create.vue
Normal file
@@ -0,0 +1,221 @@
|
|||||||
|
<script setup>
|
||||||
|
const emit = defineEmits(['close-modal', 'confirm-create', 'show-tip'])
|
||||||
|
|
||||||
|
const handleClose = () => {
|
||||||
|
emit('close-modal')
|
||||||
|
}
|
||||||
|
|
||||||
|
const devTips = () => {
|
||||||
|
emit('show-tip', {
|
||||||
|
content: '功能开发中...',
|
||||||
|
theme: 'info'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<div class="create-datasource">
|
||||||
|
<div class="datasource-grid">
|
||||||
|
<div class="grid-item">
|
||||||
|
<span><span style="color:red;">*</span>名称: </span>
|
||||||
|
<input type="text" placeholder="请输入 名称">
|
||||||
|
</div>
|
||||||
|
<div class="grid-item">
|
||||||
|
<span><span style="color:red;">*</span>类型: </span>
|
||||||
|
<select><option>请选择类型</option></select>
|
||||||
|
</div>
|
||||||
|
<div class="grid-item">
|
||||||
|
<span><span style="color:red;">*</span>用户名: </span>
|
||||||
|
<input type="text" placeholder="请输入 用户名">
|
||||||
|
</div>
|
||||||
|
<div class="grid-item">
|
||||||
|
<span><span style="color:red;">*</span>密码: </span>
|
||||||
|
<input type="password" placeholder="请输入 密码">
|
||||||
|
</div>
|
||||||
|
<div class="grid-item">
|
||||||
|
<span><span style="color:red;">*</span>连接地址: </span>
|
||||||
|
<input type="text" placeholder="请输入 连接地址">
|
||||||
|
</div>
|
||||||
|
<div class="grid-item">
|
||||||
|
<span><span style="color:red;">*</span>数据库名: </span>
|
||||||
|
<input type="text" placeholder="请输入 数据库名">
|
||||||
|
</div>
|
||||||
|
<div class="grid-item full-width grid-column remark-item">
|
||||||
|
<span>备注: </span>
|
||||||
|
<textarea placeholder="请输入 备注"></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="create-datasource-btns">
|
||||||
|
<button class="confirm-btn" @click="devTips"><i class="fa-solid fa-check"></i> 保存</button>
|
||||||
|
<button class="cancel-btn" @click="handleClose"><i class="fa-solid fa-xmark"></i> 取消</button>
|
||||||
|
<button class="test-btn" @click="devTips"><i class="fa-solid fa-link"></i> 测试连接</button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<style scoped>
|
||||||
|
.create-datasource {
|
||||||
|
position: absolute;
|
||||||
|
top: 45px;
|
||||||
|
left: 15px;
|
||||||
|
right: 15px;
|
||||||
|
bottom: 70px;
|
||||||
|
overflow-y: auto;
|
||||||
|
overflow-x: hidden;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
.create-datasource::-webkit-scrollbar {
|
||||||
|
width: 6px;
|
||||||
|
}
|
||||||
|
.create-datasource::-webkit-scrollbar-track {
|
||||||
|
background: #f1f1f1;
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
.create-datasource::-webkit-scrollbar-thumb {
|
||||||
|
background: #c1c1c1;
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
.create-datasource::-webkit-scrollbar-thumb:hover {
|
||||||
|
background: #a8a8a8;
|
||||||
|
}
|
||||||
|
.datasource-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(2, 1fr);
|
||||||
|
gap: 15px 20px;
|
||||||
|
padding: 0 10px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.grid-column {
|
||||||
|
grid-column: span 2;
|
||||||
|
}
|
||||||
|
.grid-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
min-height: 40px;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
.grid-item span:first-child {
|
||||||
|
width: 100px;
|
||||||
|
text-align: right;
|
||||||
|
margin-right: 15px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
line-height: 32px;
|
||||||
|
}
|
||||||
|
.grid-item input,
|
||||||
|
.grid-item select,
|
||||||
|
.grid-item textarea {
|
||||||
|
flex: 1;
|
||||||
|
padding: 8px 12px;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
border-radius: 10px;
|
||||||
|
width: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
.grid-item input:hover,
|
||||||
|
.grid-item select:hover,
|
||||||
|
.grid-item textarea:hover {
|
||||||
|
border-color: #f472b6;
|
||||||
|
}
|
||||||
|
.grid-item input:focus,
|
||||||
|
.grid-item select:focus,
|
||||||
|
.grid-item textarea:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: #e267a7;
|
||||||
|
}
|
||||||
|
.remark-item span:first-child {
|
||||||
|
width: 100px;
|
||||||
|
text-align: right;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
margin-right: 15px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
.remark-item textarea {
|
||||||
|
height: 250px;
|
||||||
|
resize: vertical;
|
||||||
|
min-height: 100px;
|
||||||
|
line-height: 1.5;
|
||||||
|
padding: 12px;
|
||||||
|
width: 100% !important;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.datasource-grid {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
.grid-column {
|
||||||
|
grid-column: span 1 !important;
|
||||||
|
}
|
||||||
|
.grid-item {
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: flex-start;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.grid-item span:first-child {
|
||||||
|
width: 100px;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
.grid-item input,
|
||||||
|
.grid-item select,
|
||||||
|
.grid-item textarea {
|
||||||
|
width: calc(100% - 115px);
|
||||||
|
}
|
||||||
|
.remark-item textarea {
|
||||||
|
width: calc(100% - 115px) !important;
|
||||||
|
min-height: 120px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.create-datasource-btns {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 20px;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 20px;
|
||||||
|
}
|
||||||
|
.confirm-btn,
|
||||||
|
.cancel-btn,
|
||||||
|
.test-btn {
|
||||||
|
height: 32px;
|
||||||
|
width: 85px;
|
||||||
|
border: none;
|
||||||
|
border-radius: 10px;
|
||||||
|
font-size: 14px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.confirm-btn {
|
||||||
|
background-color: #4CAF50;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
.confirm-btn:hover {
|
||||||
|
background-color: #46a049;
|
||||||
|
}
|
||||||
|
.confirm-btn:active {
|
||||||
|
background-color: #38803a;
|
||||||
|
}
|
||||||
|
.cancel-btn {
|
||||||
|
background-color: #f44336;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
.cancel-btn:hover {
|
||||||
|
background-color: #da190b;
|
||||||
|
}
|
||||||
|
.cancel-btn:active {
|
||||||
|
background-color: #b2100a;
|
||||||
|
}
|
||||||
|
.test-btn {
|
||||||
|
width: 108px;
|
||||||
|
background-color: #f472b6;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
.test-btn:hover {
|
||||||
|
background-color: #da5a9e;
|
||||||
|
}
|
||||||
|
.test-btn:active {
|
||||||
|
background-color: #b23a80;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
8
src/components/index/modal/files/Upload.vue
Normal file
8
src/components/index/modal/files/Upload.vue
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<script setup>
|
||||||
|
const emit = defineEmits(['close-modal', 'confirm-create', 'show-tip'])
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
上传文件只需要点击这里或者将文件拖拽到这里即可导入<br>
|
||||||
|
但是上次文件功能目前暂不可用<br>
|
||||||
|
因为喵喵们正在开发这个功能!
|
||||||
|
</template>
|
||||||
189
src/components/index/modal/maps/Create.vue
Normal file
189
src/components/index/modal/maps/Create.vue
Normal file
@@ -0,0 +1,189 @@
|
|||||||
|
<script setup>
|
||||||
|
const emit = defineEmits(['close-modal', 'confirm-create', 'show-tip'])
|
||||||
|
|
||||||
|
const handleClose = () => {
|
||||||
|
emit('close-modal')
|
||||||
|
}
|
||||||
|
|
||||||
|
const devTips = () => {
|
||||||
|
emit('show-tip', {
|
||||||
|
content: '功能开发中...',
|
||||||
|
theme: 'info'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<div class="create-maps">
|
||||||
|
<div class="create-maps-items">
|
||||||
|
<span>上一级别: </span>
|
||||||
|
<input type="text" placeholder="请输入 上一级别"></input>
|
||||||
|
</div>
|
||||||
|
<div class="create-maps-items">
|
||||||
|
<span><span style="color:red;">*</span>地图名称: </span>
|
||||||
|
<input type="text" placeholder="请输入 地图名称"></input>
|
||||||
|
</div>
|
||||||
|
<div class="create-maps-items">
|
||||||
|
<span><span style="color:red;">*</span>地图级别: </span>
|
||||||
|
<select><option>请选择 地图级别</option></select>
|
||||||
|
</div>
|
||||||
|
<div class="create-maps-items">
|
||||||
|
<span><span style="color:red;">*</span>地图编号: </span>
|
||||||
|
<input type="text" placeholder="请输入 内容"></input>
|
||||||
|
</div>
|
||||||
|
<div class="create-maps-items full-width">
|
||||||
|
<span>地图数据: </span>
|
||||||
|
<div class="textarea-container">
|
||||||
|
<textarea class="data-textarea" placeholder="地图数据" readonly></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="create-maps-items">
|
||||||
|
<span></span>
|
||||||
|
<button class="upload-btn" @click="devTips"><i class="fa-solid fa-upload"></i> 点击上传</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="create-maps-btns">
|
||||||
|
<button class="confirm-btn" @click="devTips"><i class="fa-solid fa-check"></i> 保存</button>
|
||||||
|
<button class="cancel-btn" @click="handleClose"><i class="fa-solid fa-xmark"></i> 取消</button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<style scoped>
|
||||||
|
.create-maps {
|
||||||
|
position: absolute;
|
||||||
|
top: 45px;
|
||||||
|
left: 15px;
|
||||||
|
right: 20px;
|
||||||
|
bottom: 70px;
|
||||||
|
overflow-y: auto;
|
||||||
|
overflow-x: hidden;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
.create-maps-items {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
min-height: 40px;
|
||||||
|
}
|
||||||
|
.create-maps-items span:first-child {
|
||||||
|
width: 100px;
|
||||||
|
text-align: right;
|
||||||
|
margin-right: 15px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
.create-maps-items input,
|
||||||
|
.create-maps-items select {
|
||||||
|
flex: 1;
|
||||||
|
padding: 8px 12px;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
border-radius: 10px;
|
||||||
|
}
|
||||||
|
.create-maps-items input:hover,
|
||||||
|
.create-maps-items select:hover {
|
||||||
|
border-color: #f472b6;
|
||||||
|
}
|
||||||
|
.create-maps-items input:focus,
|
||||||
|
.create-maps-items select:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: #e267a7;
|
||||||
|
}
|
||||||
|
.textarea-container {
|
||||||
|
flex: 1;
|
||||||
|
position: relative;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
border-radius: 10px;
|
||||||
|
overflow: hidden;
|
||||||
|
min-height: 60px;
|
||||||
|
background-color: #fff;
|
||||||
|
}
|
||||||
|
.data-textarea {
|
||||||
|
position: relative;
|
||||||
|
display: block;
|
||||||
|
width: calc(100% - 24px);
|
||||||
|
padding: 8px 12px;
|
||||||
|
border: none;
|
||||||
|
resize: vertical;
|
||||||
|
min-height: 100px;
|
||||||
|
background-color: #f9f9f9;
|
||||||
|
color: #666;
|
||||||
|
font-family: monospace;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 1.5;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
.create-maps::-webkit-scrollbar,
|
||||||
|
.data-textarea::-webkit-scrollbar {
|
||||||
|
width: 6px;
|
||||||
|
}
|
||||||
|
.create-maps::-webkit-scrollbar-track,
|
||||||
|
.data-textarea::-webkit-scrollbar-track {
|
||||||
|
background: #f1f1f1;
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
.create-maps::-webkit-scrollbar-thumb,
|
||||||
|
.data-textarea::-webkit-scrollbar-thumb {
|
||||||
|
background: #c1c1c1;
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
.create-maps::-webkit-scrollbar-thumb:hover,
|
||||||
|
.data-textarea::-webkit-scrollbar-thumb:hover {
|
||||||
|
background: #a8a8a8;
|
||||||
|
}
|
||||||
|
.data-textarea:focus {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
.upload-btn {
|
||||||
|
border: none;
|
||||||
|
border-radius: 10px;
|
||||||
|
width: 110px;
|
||||||
|
height: 32px;
|
||||||
|
background-color: #f472b6;
|
||||||
|
color: #fff;
|
||||||
|
font-size: 14px;
|
||||||
|
cursor: pointer;
|
||||||
|
gap: 5px;
|
||||||
|
}
|
||||||
|
.upload-btn:hover {
|
||||||
|
background-color: #da609b;
|
||||||
|
}
|
||||||
|
.upload-btn:active {
|
||||||
|
background-color: #b24a80;
|
||||||
|
}
|
||||||
|
.create-maps-btns {
|
||||||
|
position: absolute;
|
||||||
|
align-items: center;
|
||||||
|
bottom: 20px;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 20px;
|
||||||
|
}
|
||||||
|
.confirm-btn,
|
||||||
|
.cancel-btn {
|
||||||
|
height: 32px;
|
||||||
|
width: 85px;
|
||||||
|
border: none;
|
||||||
|
border-radius: 10px;
|
||||||
|
font-size: 14px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.confirm-btn {
|
||||||
|
background-color: #4CAF50;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
.confirm-btn:hover {
|
||||||
|
background-color: #46a049;
|
||||||
|
}
|
||||||
|
.confirm-btn:active {
|
||||||
|
background-color: #38803a;
|
||||||
|
}
|
||||||
|
.cancel-btn {
|
||||||
|
background-color: #f44336;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
.cancel-btn:hover {
|
||||||
|
background-color: #da190b;
|
||||||
|
}
|
||||||
|
.cancel-btn:active {
|
||||||
|
background-color: #b2100a;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
331
src/components/index/modal/record/Create.vue
Normal file
331
src/components/index/modal/record/Create.vue
Normal file
@@ -0,0 +1,331 @@
|
|||||||
|
<script setup>
|
||||||
|
import { ref, onMounted, onUnmounted, nextTick } from 'vue'
|
||||||
|
|
||||||
|
const emit = defineEmits(['close-modal', 'confirm-create', 'show-tip'])
|
||||||
|
|
||||||
|
const lineNumbersRef = ref(null)
|
||||||
|
const filterTextareaRef = ref(null)
|
||||||
|
const resizeObserver = ref(null)
|
||||||
|
const filterValue = ref('(data)=>{\n return {\n data\n }\n}')
|
||||||
|
|
||||||
|
const handleClose = () => {
|
||||||
|
emit('close-modal')
|
||||||
|
}
|
||||||
|
|
||||||
|
const devTips = () => {
|
||||||
|
emit('show-tip', {
|
||||||
|
content: '功能开发中...',
|
||||||
|
theme: 'info'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateLineNumbers = () => {
|
||||||
|
const textarea = filterTextareaRef.value
|
||||||
|
|
||||||
|
if (!textarea || !lineNumbersRef.value) return
|
||||||
|
|
||||||
|
const lines = textarea.value.split('\n').length
|
||||||
|
lineNumbersRef.value.innerHTML = Array.from({ length: lines }, (_, i) =>
|
||||||
|
`<span style="line-height: 1.5em;">${i + 1}</span>`
|
||||||
|
).join('')
|
||||||
|
|
||||||
|
nextTick(() => {
|
||||||
|
if (!textarea || !lineNumbersRef.value) return
|
||||||
|
|
||||||
|
const newHeight = Math.max(textarea.scrollHeight, textarea.clientHeight)
|
||||||
|
lineNumbersRef.value.style.height = `${newHeight}px`
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleResize = () => {
|
||||||
|
if (filterTextareaRef.value) {
|
||||||
|
updateLineNumbers()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleScroll = () => {
|
||||||
|
const textarea = filterTextareaRef.value
|
||||||
|
if (lineNumbersRef.value && textarea) {
|
||||||
|
lineNumbersRef.value.style.transform = `translateY(${-textarea.scrollTop}px)`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const setupResizeObserver = () => {
|
||||||
|
if (!filterTextareaRef.value) return
|
||||||
|
|
||||||
|
resizeObserver.value = new ResizeObserver((entries) => {
|
||||||
|
for (const entry of entries) {
|
||||||
|
if (lineNumbersRef.value) {
|
||||||
|
const textarea = filterTextareaRef.value
|
||||||
|
if (textarea) {
|
||||||
|
const newHeight = Math.max(textarea.scrollHeight, textarea.clientHeight)
|
||||||
|
lineNumbersRef.value.style.height = `${newHeight}px`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
resizeObserver.value.observe(filterTextareaRef.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
updateLineNumbers()
|
||||||
|
window.addEventListener('resize', handleResize)
|
||||||
|
setupResizeObserver()
|
||||||
|
})
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
window.removeEventListener('resize', handleResize)
|
||||||
|
if (resizeObserver.value) {
|
||||||
|
resizeObserver.value.disconnect()
|
||||||
|
resizeObserver.value = null
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="create-record">
|
||||||
|
<div class="record-row">
|
||||||
|
<div class="create-record-items name-field">
|
||||||
|
<span><span style="color:red;">*</span>名称: </span>
|
||||||
|
<input type="text" placeholder="请输入 名称"></input>
|
||||||
|
</div>
|
||||||
|
<div class="create-record-items type-field">
|
||||||
|
<span><span style="color:red;">*</span>类型: </span>
|
||||||
|
<select><option>请选择类型</option></select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="create-record-items full-width">
|
||||||
|
<span>过滤器: </span>
|
||||||
|
<div class="textarea-container">
|
||||||
|
<div class="line-numbers" ref="lineNumbersRef"></div>
|
||||||
|
<textarea ref="filterTextareaRef" class="filter-textarea" @input="updateLineNumbers" @scroll="handleScroll" v-model="filterValue"></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="create-record-items full-width">
|
||||||
|
<span>响应返回: </span>
|
||||||
|
<div class="textarea-container">
|
||||||
|
<textarea class="response-textarea" placeholder="响应返回内容" readonly></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="create-record-btns">
|
||||||
|
<button class="confirm-btn" @click="devTips"><i class="fa-solid fa-check"></i> 保存</button>
|
||||||
|
<button class="cancel-btn" @click="handleClose"><i class="fa-solid fa-xmark"></i> 取消</button>
|
||||||
|
<button class="reload-btn" @click="devTips"><i class="fa-solid fa-arrow-rotate-right"></i> 刷新数据</button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.create-record {
|
||||||
|
position: absolute;
|
||||||
|
top: 45px;
|
||||||
|
left: 10px;
|
||||||
|
right: 20px;
|
||||||
|
bottom: 70px;
|
||||||
|
overflow-y: auto;
|
||||||
|
overflow-x: hidden;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
.record-row {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 15px;
|
||||||
|
}
|
||||||
|
.create-record-items {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
min-height: 40px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.create-record-items.full-width {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.create-record-items span:first-child {
|
||||||
|
width: 80px;
|
||||||
|
text-align: right;
|
||||||
|
margin-right: 15px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
align-self: flex-start;
|
||||||
|
padding-top: 8px;
|
||||||
|
}
|
||||||
|
.create-record-items input,
|
||||||
|
.create-record-items select {
|
||||||
|
flex: 1;
|
||||||
|
padding: 8px 12px;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
border-radius: 10px;
|
||||||
|
width: 20px;
|
||||||
|
}
|
||||||
|
.create-record-items input:hover,
|
||||||
|
.create-record-items select:hover {
|
||||||
|
border-color: #f472b6;
|
||||||
|
}
|
||||||
|
.create-record-items input:focus,
|
||||||
|
.create-record-items select:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: #e267a7;
|
||||||
|
box-shadow: 0 0 0 2px rgba(226, 103, 167, 0.2);
|
||||||
|
}
|
||||||
|
.create-record-items select {
|
||||||
|
background-color: #fff;
|
||||||
|
}
|
||||||
|
.textarea-container {
|
||||||
|
flex: 1;
|
||||||
|
position: relative;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
border-radius: 10px;
|
||||||
|
overflow: hidden;
|
||||||
|
min-height: 100px;
|
||||||
|
background-color: #fff;
|
||||||
|
}
|
||||||
|
.line-numbers {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
width: 35px;
|
||||||
|
padding: 8px 0;
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
border-right: 1px solid #ddd;
|
||||||
|
text-align: center;
|
||||||
|
font-family: monospace;
|
||||||
|
font-size: 13px;
|
||||||
|
user-select: none;
|
||||||
|
color: #999;
|
||||||
|
z-index: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
will-change: transform;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
.filter-textarea {
|
||||||
|
position: relative;
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
min-height: 100px;
|
||||||
|
padding: 8px 12px 8px 45px;
|
||||||
|
border: none;
|
||||||
|
resize: vertical;
|
||||||
|
background: transparent;
|
||||||
|
font-family: monospace;
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 1.5em;
|
||||||
|
outline: none;
|
||||||
|
color: inherit;
|
||||||
|
overflow: auto;
|
||||||
|
box-sizing: border-box;
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
.filter-textarea:hover {
|
||||||
|
border-color: #f472b6;
|
||||||
|
}
|
||||||
|
.response-textarea {
|
||||||
|
position: relative;
|
||||||
|
display: block;
|
||||||
|
width: calc(100% - 24px);
|
||||||
|
padding: 8px 12px;
|
||||||
|
border: none;
|
||||||
|
resize: vertical;
|
||||||
|
min-height: 100px;
|
||||||
|
background-color: #f9f9f9;
|
||||||
|
color: #666;
|
||||||
|
font-family: monospace;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 1.5;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
.create-record::-webkit-scrollbar,
|
||||||
|
.filter-textarea::-webkit-scrollbar,
|
||||||
|
.response-textarea::-webkit-scrollbar {
|
||||||
|
width: 6px;
|
||||||
|
}
|
||||||
|
.create-record::-webkit-scrollbar-track,
|
||||||
|
.filter-textarea::-webkit-scrollbar-track,
|
||||||
|
.response-textarea::-webkit-scrollbar-track {
|
||||||
|
background: #f1f1f1;
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
.create-record::-webkit-scrollbar-thumb,
|
||||||
|
.filter-textarea::-webkit-scrollbar-thumb,
|
||||||
|
.response-textarea::-webkit-scrollbar-thumb {
|
||||||
|
background: #c1c1c1;
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
.create-record::-webkit-scrollbar-thumb:hover,
|
||||||
|
.filter-textarea::-webkit-scrollbar-thumb:hover,
|
||||||
|
.response-textarea::-webkit-scrollbar-thumb:hover {
|
||||||
|
background: #a8a8a8;
|
||||||
|
}
|
||||||
|
.filter-textarea:focus,
|
||||||
|
.response-textarea:focus {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
.create-record-btns {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 20px;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 20px;
|
||||||
|
}
|
||||||
|
.confirm-btn,
|
||||||
|
.cancel-btn,
|
||||||
|
.reload-btn {
|
||||||
|
height: 32px;
|
||||||
|
width: 85px;
|
||||||
|
border: none;
|
||||||
|
border-radius: 10px;
|
||||||
|
font-size: 14px;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 5px;
|
||||||
|
}
|
||||||
|
.confirm-btn {
|
||||||
|
background-color: #4CAF50;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
.confirm-btn:hover {
|
||||||
|
background-color: #46a049;
|
||||||
|
}
|
||||||
|
.confirm-btn:active {
|
||||||
|
background-color: #38803a;
|
||||||
|
}
|
||||||
|
.cancel-btn {
|
||||||
|
background-color: #f44336;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
.cancel-btn:hover {
|
||||||
|
background-color: #da190b;
|
||||||
|
}
|
||||||
|
.cancel-btn:active {
|
||||||
|
background-color: #b2100a;
|
||||||
|
}
|
||||||
|
.reload-btn {
|
||||||
|
width: 108px;
|
||||||
|
background-color: #f472b6;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
.reload-btn:hover {
|
||||||
|
background-color: #da5a9e;
|
||||||
|
}
|
||||||
|
.reload-btn:active {
|
||||||
|
background-color: #b23a80;
|
||||||
|
}
|
||||||
|
@media (min-width: 768px) {
|
||||||
|
.record-row {
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
}
|
||||||
|
.name-field, .type-field {
|
||||||
|
width: calc(50% - 7.5px);
|
||||||
|
}
|
||||||
|
.name-field {
|
||||||
|
margin-right: 15px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
146
src/components/index/modal/variables/Create.vue
Normal file
146
src/components/index/modal/variables/Create.vue
Normal file
@@ -0,0 +1,146 @@
|
|||||||
|
<script setup>
|
||||||
|
const emit = defineEmits(['close-modal', 'confirm-create', 'show-tip'])
|
||||||
|
|
||||||
|
const handleClose = () => {
|
||||||
|
emit('close-modal')
|
||||||
|
}
|
||||||
|
|
||||||
|
const devTips = () => {
|
||||||
|
emit('show-tip', {
|
||||||
|
content: '功能开发中...',
|
||||||
|
theme: 'info'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<div class="create-variables">
|
||||||
|
<div class="create-variables-items">
|
||||||
|
<span><span style="color:red;">*</span>名称: </span>
|
||||||
|
<input type="text" placeholder="请输入 名称"></input>
|
||||||
|
</div>
|
||||||
|
<div class="create-variables-items">
|
||||||
|
<span><span style="color:red;">*</span>变量名: </span>
|
||||||
|
<input type="text" placeholder="请输入 变量名"></input>
|
||||||
|
</div>
|
||||||
|
<div class="create-variables-items">
|
||||||
|
<span><span style="color:red;">*</span>变量值: </span>
|
||||||
|
<div class="textarea-container">
|
||||||
|
<textarea type="text" class="value-textarea" placeholder="请输入 变量值"></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="create-variables-btns">
|
||||||
|
<button class="confirm-btn" @click="devTips"><i class="fa-solid fa-check"></i> 保存</button>
|
||||||
|
<button class="cancel-btn" @click="handleClose"><i class="fa-solid fa-xmark"></i> 取消</button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<style scoped>
|
||||||
|
.create-variables {
|
||||||
|
position: absolute;
|
||||||
|
top: 45px;
|
||||||
|
left: 15px;
|
||||||
|
right: 20px;
|
||||||
|
bottom: 70px;
|
||||||
|
overflow-y: auto;
|
||||||
|
overflow-x: hidden;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
.create-variables::-webkit-scrollbar {
|
||||||
|
width: 6px;
|
||||||
|
}
|
||||||
|
.create-variables::-webkit-scrollbar-track {
|
||||||
|
background: #f1f1f1;
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
.create-variables::-webkit-scrollbar-thumb {
|
||||||
|
background: #c1c1c1;
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
.create-variables::-webkit-scrollbar-thumb:hover {
|
||||||
|
background: #a8a8a8;
|
||||||
|
}
|
||||||
|
.create-variables-items {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
min-height: 40px;
|
||||||
|
}
|
||||||
|
.create-variables-items span:first-child {
|
||||||
|
width: 80px;
|
||||||
|
text-align: right;
|
||||||
|
margin-right: 15px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
.create-variables-items input {
|
||||||
|
flex: 1;
|
||||||
|
padding: 8px 12px;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
border-radius: 10px;
|
||||||
|
}
|
||||||
|
.create-variables-items input:hover {
|
||||||
|
border-color: #f472b6;
|
||||||
|
}
|
||||||
|
.create-variables-items input:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: #e267a7;
|
||||||
|
}
|
||||||
|
.textarea-container {
|
||||||
|
flex: 1;
|
||||||
|
position: relative;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
border-radius: 10px;
|
||||||
|
overflow: hidden;
|
||||||
|
min-height: 90px;
|
||||||
|
background-color: #fff;
|
||||||
|
}
|
||||||
|
.value-textarea {
|
||||||
|
position: relative;
|
||||||
|
display: block;
|
||||||
|
width: calc(100% - 24px);
|
||||||
|
min-height: 90px;
|
||||||
|
padding: 8px 12px;
|
||||||
|
resize: vertical;
|
||||||
|
font-size: 14px;
|
||||||
|
outline: none;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
.create-variables-btns {
|
||||||
|
position: absolute;
|
||||||
|
align-items: center;
|
||||||
|
bottom: 20px;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 20px;
|
||||||
|
}
|
||||||
|
.confirm-btn,
|
||||||
|
.cancel-btn {
|
||||||
|
height: 32px;
|
||||||
|
width: 85px;
|
||||||
|
border: none;
|
||||||
|
border-radius: 10px;
|
||||||
|
font-size: 14px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.confirm-btn {
|
||||||
|
background-color: #4CAF50;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
.confirm-btn:hover {
|
||||||
|
background-color: #46a049;
|
||||||
|
}
|
||||||
|
.confirm-btn:active {
|
||||||
|
background-color: #38803a;
|
||||||
|
}
|
||||||
|
.cancel-btn {
|
||||||
|
background-color: #f44336;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
.cancel-btn:hover {
|
||||||
|
background-color: #da190b;
|
||||||
|
}
|
||||||
|
.cancel-btn:active {
|
||||||
|
background-color: #b2100a;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
80
src/components/layout/Loading.vue
Normal file
80
src/components/layout/Loading.vue
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
<script setup>
|
||||||
|
import { onMounted } from 'vue'
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
document.title = 'Loading...'
|
||||||
|
const loadingScreen = document.querySelector('.loading');
|
||||||
|
setTimeout(() => {
|
||||||
|
loadingScreen.classList.add('active');
|
||||||
|
}, 100);
|
||||||
|
setTimeout(() => {
|
||||||
|
loadingScreen.classList.add('fade-out');
|
||||||
|
setTimeout(() => {
|
||||||
|
loadingScreen.style.display = 'none';
|
||||||
|
}, 500);
|
||||||
|
}, 2400);
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<div class="loading">
|
||||||
|
<div class="title">DataBuddy</div>
|
||||||
|
<div class="loading-bar">
|
||||||
|
<div class="loading-line"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<style>
|
||||||
|
.loading {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
height: 100vh;
|
||||||
|
width: 100vw;
|
||||||
|
background-color: #fff;
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 0.5s ease-in-out;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
z-index: 9999;
|
||||||
|
}
|
||||||
|
.loading.active {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
.loading.fade-out {
|
||||||
|
opacity: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
.title {
|
||||||
|
position: fixed;
|
||||||
|
top: 45%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
font-size: 48px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
.loading-bar {
|
||||||
|
position: fixed;
|
||||||
|
top: 55%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
width: 200px;
|
||||||
|
height: 4px;
|
||||||
|
background-color: #e0e0e0;
|
||||||
|
border-radius: 2px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.loading-line {
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
background: linear-gradient(90deg, transparent, #f472b6, transparent);
|
||||||
|
background-size: 200% 100%;
|
||||||
|
animation: loading 1.5s infinite linear;
|
||||||
|
}
|
||||||
|
@keyframes loading {
|
||||||
|
0% {
|
||||||
|
background-position: 200% 0;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
background-position: -100% 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
201
src/components/layout/Modal.vue
Normal file
201
src/components/layout/Modal.vue
Normal file
@@ -0,0 +1,201 @@
|
|||||||
|
<script setup>
|
||||||
|
import { ref, nextTick } from 'vue'
|
||||||
|
const emit = defineEmits(['close', 'maximize'])
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
theme: {
|
||||||
|
type: String,
|
||||||
|
default: 'light',
|
||||||
|
validator: (value) => ['light', 'dark'].includes(value)
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
validator: (value) => typeof value ==='string' && value.length > 0
|
||||||
|
},
|
||||||
|
width: {
|
||||||
|
type: [String, Number],
|
||||||
|
default: 'auto',
|
||||||
|
validator: (value) => {
|
||||||
|
if (typeof value === 'number') return true;
|
||||||
|
return /^(\d+\.?\d*(px|rem|em|%|vw|vh|cm|mm|in|pt|pc)?|auto)$/.test(value);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
height: {
|
||||||
|
type: [String, Number],
|
||||||
|
default: 'auto',
|
||||||
|
validator: (value) => {
|
||||||
|
if (typeof value === 'number') return true;
|
||||||
|
return /^(\d+\.?\d*(px|rem|em|%|vh|cm|mm|in|pt|pc)?|auto)$/.test(value);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const isMaximized = ref(false)
|
||||||
|
const modalContainer = ref(null)
|
||||||
|
|
||||||
|
const closeModal = () => emit('close')
|
||||||
|
const toggleMaximize = async () => {
|
||||||
|
if (!isMaximized.value && modalContainer.value) {
|
||||||
|
const rect = modalContainer.value.getBoundingClientRect();
|
||||||
|
modalContainer.value.dataset.startX = rect.left;
|
||||||
|
modalContainer.value.dataset.startY = rect.top;
|
||||||
|
modalContainer.value.dataset.startWidth = rect.width;
|
||||||
|
modalContainer.value.dataset.startHeight = rect.height;
|
||||||
|
}
|
||||||
|
|
||||||
|
isMaximized.value = !isMaximized.value
|
||||||
|
emit('maximize', isMaximized.value)
|
||||||
|
|
||||||
|
await nextTick();
|
||||||
|
}
|
||||||
|
const getDimensionValue = (value, dimension) => {
|
||||||
|
if (isMaximized.value) {
|
||||||
|
return '100%'
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof value === 'number') {
|
||||||
|
return `${value}px`;
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<div class="modal-mask">
|
||||||
|
<div ref="modalContainer" class="modal-container" :class="[theme, { 'maximized': isMaximized, 'animating': true }]" :style="{ width: getDimensionValue(width), height: getDimensionValue(height), maxWidth: isMaximized ? '100vw' : 'none', maxHeight: isMaximized ? '100vh' : 'none'}">
|
||||||
|
<header class="modal-header">
|
||||||
|
<div class="modal-header-buttons">
|
||||||
|
<button class="modal-close-btn" @click="closeModal"></button>
|
||||||
|
<button class="modal-maximize-btn" @click="toggleMaximize"></button>
|
||||||
|
</div>
|
||||||
|
<header class="modal-header-title">{{ title }}</header>
|
||||||
|
</header>
|
||||||
|
<div class="modal-main">
|
||||||
|
<slot></slot>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<style scoped>
|
||||||
|
.modal-mask {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100vw;
|
||||||
|
height: 100vh;
|
||||||
|
background-color: rgba(0, 0, 0, 0.2);
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
z-index: 9999;
|
||||||
|
}
|
||||||
|
.modal-container {
|
||||||
|
border-radius: 16px;
|
||||||
|
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
|
||||||
|
min-width: 200px;
|
||||||
|
min-height: 60px;
|
||||||
|
overflow: hidden;
|
||||||
|
position: relative;
|
||||||
|
z-index: 99999;
|
||||||
|
transition: all 0.35s cubic-bezier(0.23, 1, 0.32, 1);
|
||||||
|
transform-origin: center center;
|
||||||
|
}
|
||||||
|
.modal-container.animating {
|
||||||
|
transition: all 0.35s cubic-bezier(0.23, 1, 0.32, 1);
|
||||||
|
}
|
||||||
|
.modal-container.light {
|
||||||
|
background-color: #fff;
|
||||||
|
color: #333333;
|
||||||
|
box-shadow:
|
||||||
|
0 4px 20px rgba(0, 0, 0, 0.15),
|
||||||
|
0 0 40px rgba(0, 0, 0, 0.08);
|
||||||
|
}
|
||||||
|
.modal-container.dark {
|
||||||
|
background-color: #2d2d2d;
|
||||||
|
color: #f0f0f0;
|
||||||
|
box-shadow:
|
||||||
|
0 4px 20px rgba(0, 0, 0, 0.4),
|
||||||
|
inset 0 0 40px rgba(0, 0, 0, 0.2);
|
||||||
|
border: 1px solid #444;
|
||||||
|
}
|
||||||
|
.modal-header {
|
||||||
|
width: 100%;
|
||||||
|
height: 30px;
|
||||||
|
background-color: #e4e4e4;
|
||||||
|
}
|
||||||
|
.modal-container.dark .modal-header {
|
||||||
|
background-color: #3a3a3a;
|
||||||
|
border-bottom: 1px solid #444;
|
||||||
|
}
|
||||||
|
.modal-header-buttons {
|
||||||
|
position: absolute;
|
||||||
|
left: 10px;
|
||||||
|
top: 8px;
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
height: 20px;
|
||||||
|
}
|
||||||
|
.modal-close-btn,
|
||||||
|
.modal-maximize-btn {
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
border-radius: 50%;
|
||||||
|
border: none;
|
||||||
|
cursor: default;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
position: relative;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
.modal-close-btn {
|
||||||
|
background-color: #ff5b5b;
|
||||||
|
}
|
||||||
|
.modal-maximize-btn {
|
||||||
|
background-color: #37b600;
|
||||||
|
}
|
||||||
|
.modal-close-btn::before,
|
||||||
|
.modal-maximize-btn::before {
|
||||||
|
position: absolute;
|
||||||
|
font-family: "Font Awesome 7 Free";
|
||||||
|
font-weight: bolder;
|
||||||
|
font-size: 11px;
|
||||||
|
color: #555;
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 0.2s ease;
|
||||||
|
}
|
||||||
|
.modal-close-btn::before {
|
||||||
|
content: "\f00d";
|
||||||
|
font-size: 11px;
|
||||||
|
}
|
||||||
|
.modal-maximize-btn::before {
|
||||||
|
content: "\f424";
|
||||||
|
font-size: 8px;
|
||||||
|
}
|
||||||
|
.modal-container.maximized .modal-maximize-btn::before {
|
||||||
|
content: "\f422";
|
||||||
|
}
|
||||||
|
.modal-container.maximized {
|
||||||
|
border-radius: 0 !important;
|
||||||
|
}
|
||||||
|
.modal-header-buttons:hover .modal-close-btn::before,
|
||||||
|
.modal-header-buttons:hover .modal-maximize-btn::before {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
.modal-close-btn:active {
|
||||||
|
background-color: #c64848;
|
||||||
|
}
|
||||||
|
.modal-maximize-btn:active {
|
||||||
|
background-color: #288500;
|
||||||
|
}
|
||||||
|
.modal-header-title {
|
||||||
|
position: absolute;
|
||||||
|
top: 4px;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.modal-main {
|
||||||
|
padding: 4px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
220
src/components/layout/Tip.vue
Normal file
220
src/components/layout/Tip.vue
Normal file
@@ -0,0 +1,220 @@
|
|||||||
|
<script setup>
|
||||||
|
import { ref, computed, onMounted, onUnmounted, nextTick, watch } from 'vue'
|
||||||
|
|
||||||
|
const notifications = ref([])
|
||||||
|
const timeouts = ref([])
|
||||||
|
|
||||||
|
const visibleNotifications = computed(() => {
|
||||||
|
return notifications.value
|
||||||
|
.filter(n => n && n.show)
|
||||||
|
.sort((a, b) => a.createdAt - b.createdAt)
|
||||||
|
})
|
||||||
|
|
||||||
|
const clearAllTimeouts = () => {
|
||||||
|
timeouts.value.forEach(timer => clearTimeout(timer))
|
||||||
|
timeouts.value = []
|
||||||
|
}
|
||||||
|
|
||||||
|
const addNotification = (content, theme = 'success', duration = 3000) => {
|
||||||
|
if (!content) return
|
||||||
|
|
||||||
|
const id = Date.now() + Math.random().toString(36).slice(2, 9)
|
||||||
|
const createdAt = Date.now()
|
||||||
|
|
||||||
|
const notification = {
|
||||||
|
id,
|
||||||
|
content,
|
||||||
|
theme,
|
||||||
|
show: true,
|
||||||
|
height: 0,
|
||||||
|
createdAt
|
||||||
|
}
|
||||||
|
|
||||||
|
notifications.value.push(notification)
|
||||||
|
|
||||||
|
nextTick(() => {
|
||||||
|
const stillExists = notifications.value.some(n => n.id === id)
|
||||||
|
if (!stillExists) return
|
||||||
|
|
||||||
|
const el = document.getElementById(`notification-${id}`)
|
||||||
|
if (el) {
|
||||||
|
const styles = window.getComputedStyle(el)
|
||||||
|
const margin = parseFloat(styles.marginBottom) || 15
|
||||||
|
notification.height = el.offsetHeight + margin
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const timer = setTimeout(() => {
|
||||||
|
removeNotification(id)
|
||||||
|
}, duration)
|
||||||
|
timeouts.value.push(timer)
|
||||||
|
|
||||||
|
if (notifications.value.length > 10) {
|
||||||
|
removeNotification(notifications.value[0].id)
|
||||||
|
}
|
||||||
|
|
||||||
|
return id
|
||||||
|
}
|
||||||
|
|
||||||
|
const removeNotification = (id) => {
|
||||||
|
const index = notifications.value.findIndex(n => n.id === id)
|
||||||
|
if (index === -1) return
|
||||||
|
|
||||||
|
const notification = notifications.value[index]
|
||||||
|
notification.show = false
|
||||||
|
|
||||||
|
const visibleNotifs = notifications.value.filter(n => n.show).sort((a, b) => a.position - b.position);
|
||||||
|
visibleNotifs.forEach((notif, i) => {
|
||||||
|
notif.position = i;
|
||||||
|
});
|
||||||
|
|
||||||
|
const timer = setTimeout(() => {
|
||||||
|
const currentIndex = notifications.value.findIndex(n => n.id === id)
|
||||||
|
if (currentIndex === -1) return
|
||||||
|
|
||||||
|
notifications.value.splice(currentIndex, 1)
|
||||||
|
|
||||||
|
for (let i = currentIndex; i < notifications.value.length; i++) {
|
||||||
|
notifications.value[i].position -= 1
|
||||||
|
}
|
||||||
|
}, 350)
|
||||||
|
timeouts.value.push(timer)
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
addNotification,
|
||||||
|
success: (content, duration) => addNotification(content, 'success', duration),
|
||||||
|
error: (content, duration) => addNotification(content, 'error', duration),
|
||||||
|
warning: (content, duration) => addNotification(content, 'warning', duration),
|
||||||
|
info: (content, duration) => addNotification(content, 'info', duration)
|
||||||
|
})
|
||||||
|
|
||||||
|
const getTopPosition = (index) => {
|
||||||
|
let top = 35;
|
||||||
|
for (let i = 0; i < index; i++) {
|
||||||
|
const notification = visibleNotifications.value[i];
|
||||||
|
if (notification && notification.height) {
|
||||||
|
top += notification.height;
|
||||||
|
} else {
|
||||||
|
top += 70;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return `${top}px`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
content: String,
|
||||||
|
theme: {
|
||||||
|
type: String,
|
||||||
|
default: 'success',
|
||||||
|
validator: (value) => ['success', 'error', 'warning', 'info'].includes(value)
|
||||||
|
},
|
||||||
|
duration: {
|
||||||
|
type: Number,
|
||||||
|
default: 3000
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
watch(() => props.content, (newVal) => {
|
||||||
|
if (newVal) {
|
||||||
|
addNotification(newVal, props.theme, props.duration)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
if (props.content) {
|
||||||
|
addNotification(props.content, props.theme, props.duration)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
clearAllTimeouts()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="notifications-container">
|
||||||
|
<transition-group name="slide-fade" tag="div">
|
||||||
|
<div v-for="(notif, index) in visibleNotifications" :key="notif.id" :id="`notification-${notif.id}`" class="tip-container" :class="`theme-${notif.theme}`" :style="{ top: getTopPosition(index) }">
|
||||||
|
{{ notif.content }}
|
||||||
|
</div>
|
||||||
|
</transition-group>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.notifications-container {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
z-index: 9999;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tip-container {
|
||||||
|
position: absolute;
|
||||||
|
right: 16px;
|
||||||
|
z-index: 9999;
|
||||||
|
padding: 20px 12px;
|
||||||
|
border-radius: 15px;
|
||||||
|
min-width: 280px;
|
||||||
|
max-width: 80%;
|
||||||
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||||
|
color: white;
|
||||||
|
text-align: center;
|
||||||
|
pointer-events: auto;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-success {
|
||||||
|
background: linear-gradient(135deg, #4caf50, #43a047);
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-error {
|
||||||
|
background: linear-gradient(135deg, #f44336, #e53935);
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-warning {
|
||||||
|
background: linear-gradient(135deg, #ff9800, #fb8c00);
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-info {
|
||||||
|
background: linear-gradient(135deg, #2196f3, #1e88e5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.slide-fade-enter-active,
|
||||||
|
.slide-fade-leave-active {
|
||||||
|
transition: all 0.35s cubic-bezier(0.23, 1, 0.32, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.slide-fade-enter-from,
|
||||||
|
.slide-fade-leave-to {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translate(20px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.slide-fade-enter-to,
|
||||||
|
.slide-fade-leave-from {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translate(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 480px) {
|
||||||
|
.tip-container {
|
||||||
|
min-width: 70%;
|
||||||
|
max-width: 80%;
|
||||||
|
padding: 10px 15px;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
}
|
||||||
|
.slide-fade-enter-from,
|
||||||
|
.slide-fade-leave-to {
|
||||||
|
transform: translate(-50%, -20px);
|
||||||
|
}
|
||||||
|
.slide-fade-enter-to,
|
||||||
|
.slide-fade-leave-from {
|
||||||
|
transform: translate(-50%, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
15
src/main.js
15
src/main.js
@@ -1,11 +1,18 @@
|
|||||||
import './assets/main.css'
|
|
||||||
|
|
||||||
import { createApp } from 'vue'
|
import { createApp } from 'vue'
|
||||||
import App from './App.vue'
|
import App from './App.vue'
|
||||||
import router from './router'
|
import router from './router.js'
|
||||||
|
import getConfig from './utils/config'
|
||||||
|
import ElementPlus from 'element-plus'
|
||||||
|
import 'element-plus/dist/index.css'
|
||||||
|
import '@fortawesome/fontawesome-free/css/all.min.css'
|
||||||
|
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
|
||||||
|
|
||||||
const app = createApp(App)
|
const app = createApp(App)
|
||||||
|
app.use(ElementPlus)
|
||||||
|
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
|
||||||
|
app.component(key, component)
|
||||||
|
}
|
||||||
|
|
||||||
|
app.config.globalProperties.$config = getConfig()
|
||||||
app.use(router)
|
app.use(router)
|
||||||
|
|
||||||
app.mount('#app')
|
app.mount('#app')
|
||||||
157
src/router.js
Normal file
157
src/router.js
Normal file
@@ -0,0 +1,157 @@
|
|||||||
|
import { createRouter, createWebHistory } from 'vue-router'
|
||||||
|
import { createApp, h } from 'vue'
|
||||||
|
import NProgress from 'nprogress'
|
||||||
|
import 'nprogress/nprogress.css'
|
||||||
|
|
||||||
|
NProgress.configure({
|
||||||
|
easing: 'ease',
|
||||||
|
speed: 500,
|
||||||
|
showSpinner: true,
|
||||||
|
trickleSpeed: 200,
|
||||||
|
minimum: 0.3
|
||||||
|
})
|
||||||
|
|
||||||
|
const style = document.createElement('style')
|
||||||
|
style.innerHTML = `
|
||||||
|
#nprogress {
|
||||||
|
z-index: 9999 !important;
|
||||||
|
position: fixed !important;
|
||||||
|
top: 0 !important;
|
||||||
|
left: 0 !important;
|
||||||
|
right: 0 !important;
|
||||||
|
}
|
||||||
|
#nprogress .bar {
|
||||||
|
background: #f472b6 !important;
|
||||||
|
height: 3px !important;
|
||||||
|
box-shadow: 0 0 5px rgba(244, 114, 182, 0.7) !important;
|
||||||
|
}
|
||||||
|
#nprogress .peg {
|
||||||
|
box-shadow: 0 0 15px #f472b6, 0 0 10px #f472b6 !important;
|
||||||
|
opacity: 1 !important;
|
||||||
|
}
|
||||||
|
#nprogress .spinner-icon {
|
||||||
|
border-top-color: #f472b6 !important;
|
||||||
|
border-left-color: #f472b6 !important;
|
||||||
|
}
|
||||||
|
`
|
||||||
|
document.head.appendChild(style)
|
||||||
|
|
||||||
|
let loadingApp = null;
|
||||||
|
let loadingNode = null;
|
||||||
|
|
||||||
|
const router = createRouter({
|
||||||
|
history: createWebHistory(import.meta.env.BASE_URL),
|
||||||
|
routes: [
|
||||||
|
{
|
||||||
|
path: '/',
|
||||||
|
name: 'Index',
|
||||||
|
component: () => import('./views/Loading.vue'),
|
||||||
|
meta: { requiresIndexLoading: true }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/datascreen',
|
||||||
|
name: 'Datascreen',
|
||||||
|
component: () => import('./views/Index.vue')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/category',
|
||||||
|
name: 'Category',
|
||||||
|
component: () => import('./views/Index.vue')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/datasource',
|
||||||
|
name: 'Datasource',
|
||||||
|
component: () => import('./views/Index.vue')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/record',
|
||||||
|
name: 'Record',
|
||||||
|
component: () => import('./views/Index.vue')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/components',
|
||||||
|
name: 'Components',
|
||||||
|
component: () => import('./views/Index.vue')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/variables',
|
||||||
|
name: 'Variables',
|
||||||
|
component: () => import('./views/Index.vue')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/files',
|
||||||
|
name: 'Files',
|
||||||
|
component: () => import('./views/Index.vue')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/maps',
|
||||||
|
name: 'Maps',
|
||||||
|
component: () => import('./views/Index.vue')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/tools',
|
||||||
|
name: 'Tools',
|
||||||
|
component: () => import('./views/Index.vue')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/build',
|
||||||
|
name: 'Build',
|
||||||
|
component: () => import('./views/Build.vue')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/:pathMatch(.*)*',
|
||||||
|
name: 'NotFound',
|
||||||
|
component: () => import('./views/NotFound.vue')
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
|
||||||
|
router.beforeEach(async (to, from, next) => {
|
||||||
|
if (!to.meta.requiresIndexLoading) {
|
||||||
|
NProgress.start()
|
||||||
|
} else {
|
||||||
|
NProgress.done()
|
||||||
|
NProgress.remove()
|
||||||
|
|
||||||
|
loadingNode = document.createElement('div');
|
||||||
|
loadingNode.id = 'custom-loading';
|
||||||
|
document.body.appendChild(loadingNode);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const LoadingComponent = (await import('@/components/layout/Loading.vue')).default;
|
||||||
|
loadingApp = createApp({
|
||||||
|
render: () => h(LoadingComponent)
|
||||||
|
});
|
||||||
|
loadingApp.mount(loadingNode);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to load custom loading component:', error);
|
||||||
|
NProgress.start();
|
||||||
|
document.body.removeChild(loadingNode);
|
||||||
|
loadingNode = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
next();
|
||||||
|
});
|
||||||
|
|
||||||
|
router.afterEach((to) => {
|
||||||
|
if (to.meta.requiresIndexLoading && loadingApp) {
|
||||||
|
setTimeout(() => {
|
||||||
|
if (loadingApp) {
|
||||||
|
loadingApp.unmount();
|
||||||
|
loadingApp = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (loadingNode && document.body.contains(loadingNode)) {
|
||||||
|
document.body.removeChild(loadingNode);
|
||||||
|
loadingNode = null;
|
||||||
|
}
|
||||||
|
}, 3000);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!to.meta.requiresIndexLoading && NProgress.isStarted()) {
|
||||||
|
NProgress.done();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export default router
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
import { createRouter, createWebHistory } from 'vue-router'
|
|
||||||
import HomeView from '../views/HomeView.vue'
|
|
||||||
|
|
||||||
const router = createRouter({
|
|
||||||
history: createWebHistory(import.meta.env.BASE_URL),
|
|
||||||
routes: [
|
|
||||||
{
|
|
||||||
path: '/',
|
|
||||||
name: 'home',
|
|
||||||
component: HomeView,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/about',
|
|
||||||
name: 'about',
|
|
||||||
// route level code-splitting
|
|
||||||
// this generates a separate chunk (About.[hash].js) for this route
|
|
||||||
// which is lazy-loaded when the route is visited.
|
|
||||||
component: () => import('../views/AboutView.vue'),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
})
|
|
||||||
|
|
||||||
export default router
|
|
||||||
3
src/utils/config.js
Normal file
3
src/utils/config.js
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
export default function getConfig() {
|
||||||
|
return window.$website;
|
||||||
|
}
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="about">
|
|
||||||
<h1>This is an about page</h1>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
@media (min-width: 1024px) {
|
|
||||||
.about {
|
|
||||||
min-height: 100vh;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
20
src/views/Build.vue
Normal file
20
src/views/Build.vue
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
<script setup>
|
||||||
|
import { onMounted, defineAsyncComponent, shallowRef } from 'vue'
|
||||||
|
import getConfig from '@/utils/config'
|
||||||
|
|
||||||
|
const config = getConfig()
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
document.title = "大屏设计器 - " + config.title
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<div class="build"></div>
|
||||||
|
</template>
|
||||||
|
<style scoped>
|
||||||
|
.build {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
<script setup>
|
|
||||||
import TheWelcome from '../components/TheWelcome.vue'
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<main>
|
|
||||||
<TheWelcome />
|
|
||||||
</main>
|
|
||||||
</template>
|
|
||||||
87
src/views/Index.vue
Normal file
87
src/views/Index.vue
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
<script setup>
|
||||||
|
import { onMounted, defineAsyncComponent, shallowRef } from 'vue'
|
||||||
|
import getConfig from '@/utils/config'
|
||||||
|
import Tip from '@/components/layout/Tip.vue'
|
||||||
|
|
||||||
|
const config = getConfig()
|
||||||
|
|
||||||
|
const Topbar = defineAsyncComponent(() => import('@/components/index/Topbar.vue'))
|
||||||
|
const Leftbar = defineAsyncComponent(() => import('@/components/index/Leftbar.vue'))
|
||||||
|
const Main = defineAsyncComponent(() => import('@/components/index/Main.vue'))
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
document.title = config.title
|
||||||
|
document.body.style.background = "url('"+config.backgroundImg+"') no-repeat center center fixed"
|
||||||
|
document.body.style.backgroundSize = "cover"
|
||||||
|
|
||||||
|
//const linkElement = document.createElement('link');
|
||||||
|
//linkElement.rel = 'stylesheet';
|
||||||
|
|
||||||
|
//if (config.cdnUrl && config.cdnUrl !== "") {
|
||||||
|
//linkElement.href = `${config.cdnUrl}/assets/font-awesome/css/all.min.css`;
|
||||||
|
//} else {
|
||||||
|
//linkElement.href = '@/assets/font-awesome/css/all.min.css';
|
||||||
|
//}
|
||||||
|
|
||||||
|
//document.head.appendChild(linkElement);
|
||||||
|
})
|
||||||
|
|
||||||
|
const Modal = defineAsyncComponent(() => import('@/components/layout/Modal.vue'))
|
||||||
|
|
||||||
|
const showModal = shallowRef(false)
|
||||||
|
const modalContent = shallowRef('')
|
||||||
|
const modalTheme = shallowRef('light')
|
||||||
|
const modalTitle = shallowRef('')
|
||||||
|
const modalWidth = shallowRef('auto')
|
||||||
|
const modalHeight = shallowRef('auto')
|
||||||
|
|
||||||
|
const handleShowModal = (payload) => {
|
||||||
|
modalContent.value = payload.content || null
|
||||||
|
modalTheme.value = ['light', 'dark'].includes(payload.theme) ? payload.theme : 'light'
|
||||||
|
modalTitle.value = payload.title || ''
|
||||||
|
showModal.value = true
|
||||||
|
modalWidth.value = payload.width || 'auto'
|
||||||
|
modalHeight.value = payload.height || 'auto'
|
||||||
|
}
|
||||||
|
|
||||||
|
const closeModal = () => {
|
||||||
|
showModal.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
const tip = shallowRef(null)
|
||||||
|
|
||||||
|
const handleShowTip = (payload) => {
|
||||||
|
if (!tip.value) return;
|
||||||
|
|
||||||
|
const { content, theme = 'success', duration = 3000 } = payload;
|
||||||
|
|
||||||
|
switch(theme) {
|
||||||
|
case 'success':
|
||||||
|
tip.value.success(content, duration);
|
||||||
|
break;
|
||||||
|
case 'error':
|
||||||
|
tip.value.error(content, duration);
|
||||||
|
break;
|
||||||
|
case 'warning':
|
||||||
|
tip.value.warning(content, duration);
|
||||||
|
break;
|
||||||
|
case 'info':
|
||||||
|
tip.value.info(content, duration);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
tip.value.success(content, duration);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<Topbar />
|
||||||
|
<Leftbar />
|
||||||
|
<Main @show-modal="handleShowModal" @show-tip="handleShowTip" />
|
||||||
|
|
||||||
|
<Modal v-if="showModal" @close="closeModal" :theme="modalTheme" :title="modalTitle" :width="modalWidth" :height="modalHeight">
|
||||||
|
<component :is="modalContent" v-if="modalContent && typeof modalContent === 'object'" @close-modal="closeModal" @show-tip="handleShowTip" />
|
||||||
|
<template v-else>{{ modalContent || '无内容' }}</template>
|
||||||
|
</Modal>
|
||||||
|
|
||||||
|
<Tip ref="tip" />
|
||||||
|
</template>
|
||||||
24
src/views/Loading.vue
Normal file
24
src/views/Loading.vue
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
<script setup>
|
||||||
|
import { onMounted, onUnmounted } from 'vue'
|
||||||
|
import { useRoute, useRouter } from 'vue-router'
|
||||||
|
|
||||||
|
const route = useRoute()
|
||||||
|
const router = useRouter()
|
||||||
|
let timer = null
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
if (route.path === '/') {
|
||||||
|
timer = setTimeout(() => {
|
||||||
|
router.push('/datascreen')
|
||||||
|
}, 3000)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
if (timer) {
|
||||||
|
clearTimeout(timer)
|
||||||
|
timer = null
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
<template></template>
|
||||||
142
src/views/NotFound.vue
Normal file
142
src/views/NotFound.vue
Normal file
@@ -0,0 +1,142 @@
|
|||||||
|
<script setup>
|
||||||
|
import { onMounted } from 'vue'
|
||||||
|
import { useRoute, useRouter } from 'vue-router'
|
||||||
|
import getConfig from '@/utils/config'
|
||||||
|
|
||||||
|
const route = useRoute()
|
||||||
|
const router = useRouter()
|
||||||
|
const config = getConfig()
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
document.title = '页面未找到 - ' + config.title
|
||||||
|
document.body.style.background = "url('"+config.backgroundImg+"') no-repeat center center fixed"
|
||||||
|
document.body.style.backgroundSize = "cover"
|
||||||
|
|
||||||
|
const linkElement = document.createElement('link');
|
||||||
|
linkElement.rel = 'stylesheet';
|
||||||
|
|
||||||
|
if (config.cdnUrl && config.cdnUrl !== "") {
|
||||||
|
linkElement.href = `${config.cdnUrl}/assets/font-awesome/css/all.min.css`;
|
||||||
|
} else {
|
||||||
|
linkElement.href = '@/assets/font-awesome/css/all.min.css';
|
||||||
|
}
|
||||||
|
|
||||||
|
document.head.appendChild(linkElement);
|
||||||
|
|
||||||
|
if (window.gtag) {
|
||||||
|
window.gtag('event', 'page_view', {
|
||||||
|
page_path: route.path,
|
||||||
|
page_title: '404 - Page Not Found'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<div class="container">
|
||||||
|
<i class="fa-solid fa-ban"></i>
|
||||||
|
<div class="right-container">
|
||||||
|
<h1>页面未找到</h1>
|
||||||
|
<p>抱歉,您正在寻找的页面不存在。</p>
|
||||||
|
<button class="go-home-button" @click="router.push('/')">
|
||||||
|
<i class="fa-solid fa-arrow-left"></i>
|
||||||
|
<span class="go-home-button-text">返回首页</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<style scoped>
|
||||||
|
.container {
|
||||||
|
position: fixed;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
width: 80%;
|
||||||
|
height: 80%;
|
||||||
|
border-radius: 20px;
|
||||||
|
backdrop-filter: blur(3px);
|
||||||
|
background-color: rgba(255, 255, 255, 0.15);
|
||||||
|
backdrop-filter: blur(10px) saturate(180%);
|
||||||
|
-webkit-backdrop-filter: blur(10px) saturate(180%);
|
||||||
|
border: 1px solid rgba(255,255,255,0.4);
|
||||||
|
box-shadow:
|
||||||
|
0 8px 32px rgba(0,0,0,0.2),
|
||||||
|
inset 0 4px 20px rgba(255,255,255,0.3);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.container i {
|
||||||
|
position: absolute;
|
||||||
|
font-size: 256px;
|
||||||
|
color: #000;
|
||||||
|
left: 32px;
|
||||||
|
}
|
||||||
|
.right-container {
|
||||||
|
position: absolute;
|
||||||
|
width: calc(100% - 416px);
|
||||||
|
right: 32px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.right-container h1,
|
||||||
|
.right-container p {
|
||||||
|
padding: 8px;
|
||||||
|
}
|
||||||
|
.go-home-button {
|
||||||
|
height: 48px;
|
||||||
|
width: 210px;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
position: relative;
|
||||||
|
border: 0.1px solid rgba(255, 255, 255, 0.2);
|
||||||
|
border-radius: 16px;
|
||||||
|
backdrop-filter: blur(3px);
|
||||||
|
background-color: rgba(255, 255, 255, 0.2);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.go-home-button i {
|
||||||
|
position: absolute;
|
||||||
|
font-size: 18px;
|
||||||
|
left: 12px;
|
||||||
|
bottom: 14px;
|
||||||
|
}
|
||||||
|
.go-home-button .go-home-button-text {
|
||||||
|
position: absolute;
|
||||||
|
right: 12px;
|
||||||
|
bottom: 11px;
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
.go-home-button:hover {
|
||||||
|
background-color: rgba(255, 255, 255, 0.4);
|
||||||
|
}
|
||||||
|
.go-home-button:active {
|
||||||
|
background-color: rgba(255, 255, 255, 0.6);
|
||||||
|
}
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.container {
|
||||||
|
width: 90%;
|
||||||
|
height: 95%;
|
||||||
|
}
|
||||||
|
.container i {
|
||||||
|
font-size: 128px;
|
||||||
|
left: auto;
|
||||||
|
top: 64px;
|
||||||
|
}
|
||||||
|
.right-container {
|
||||||
|
width: 100%;
|
||||||
|
height: calc(100% - 256px);
|
||||||
|
right: 0;
|
||||||
|
bottom: 32px;
|
||||||
|
}
|
||||||
|
.go-home-button i {
|
||||||
|
font-size: 18px;
|
||||||
|
left: 12px;
|
||||||
|
top: 14px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@media (max-width: 480px) {
|
||||||
|
.container-404 {
|
||||||
|
width: 95%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
Reference in New Issue
Block a user