feat: 初始化项目结构并添加基础配置

添加前后端基础项目结构,包括.gitignore、package.json等配置文件
实现前端基础功能模块,包括路由、状态管理、API请求封装等
添加前端UI组件库和样式体系
配置开发环境Mock系统和构建工具链
This commit is contained in:
2026-03-18 14:03:35 +08:00
parent fc53f5620e
commit 9a387f3eec
504 changed files with 80629 additions and 0 deletions

View File

@@ -0,0 +1,43 @@
<template>
<button>Bubble</button>
</template>
<style scoped>
button {
position: relative;
z-index: 1;
padding: 0.5em 1em;
overflow: hidden;
font-family: inherit;
font-size: inherit;
color: white;
outline: none;
background-color: hsl(236deg 32% 26%);
border: none;
transition: color 0.4s ease-in-out;
}
button::before {
position: absolute;
top: 100%;
right: 100%;
z-index: -1;
width: 1em;
height: 1em;
content: '';
background-color: #1890ff;
border-radius: 50%;
transform: translate3d(50%, -50%, 0) scale3d(0, 0, 0);
transform-origin: center;
transition: transform 0.45s ease-in-out;
}
button:hover {
color: #fff;
cursor: pointer;
}
button:hover::before {
transform: translate3d(50%, -50%, 0) scale3d(15, 15, 15);
}
</style>

View File

@@ -0,0 +1,43 @@
<template>
<button>Bubble</button>
</template>
<style scoped>
button {
position: relative;
z-index: 1;
padding: 0.5em 1em;
overflow: hidden;
font-family: inherit;
font-size: inherit;
color: white;
outline: none;
background-color: hsl(236deg 32% 26%);
border: none;
transition: color 0.4s ease-in-out;
}
button::before {
position: absolute;
top: 100%;
left: 100%;
z-index: -1;
width: 1em;
height: 1em;
content: '';
background-color: #1890ff;
border-radius: 50%;
transform: translate3d(-50%, -50%, 0) scale3d(0, 0, 0);
transform-origin: center;
transition: transform 0.45s ease-in-out;
}
button:hover {
color: #fff;
cursor: pointer;
}
button:hover::before {
transform: translate3d(-50%, -50%, 0) scale3d(15, 15, 15);
}
</style>

View File

@@ -0,0 +1,43 @@
<template>
<button>Bubble</button>
</template>
<style scoped>
button {
position: relative;
z-index: 1;
padding: 0.5em 1em;
overflow: hidden;
font-family: inherit;
font-size: inherit;
color: white;
outline: none;
background-color: hsl(236deg 32% 26%);
border: none;
transition: color 0.4s ease-in-out;
}
button::before {
position: absolute;
right: 100%;
bottom: 100%;
z-index: -1;
width: 1em;
height: 1em;
content: '';
background-color: #1890ff;
border-radius: 50%;
transform: translate3d(50%, 50%, 0) scale3d(0, 0, 0);
transform-origin: center;
transition: transform 0.45s ease-in-out;
}
button:hover {
color: #fff;
cursor: pointer;
}
button:hover::before {
transform: translate3d(50%, 50%, 0) scale3d(15, 15, 15);
}
</style>

View File

@@ -0,0 +1,43 @@
<template>
<button>Bubble</button>
</template>
<style scoped>
button {
position: relative;
z-index: 1;
padding: 0.5em 1em;
overflow: hidden;
font-family: inherit;
font-size: inherit;
color: white;
outline: none;
background-color: hsl(236deg 32% 26%);
border: none;
transition: color 0.4s ease-in-out;
}
button::before {
position: absolute;
bottom: 100%;
left: 100%;
z-index: -1;
width: 1em;
height: 1em;
content: '';
background-color: #1890ff;
border-radius: 50%;
transform: translate3d(-50%, 50%, 0) scale3d(0, 0, 0);
transform-origin: center;
transition: transform 0.45s ease-in-out;
}
button:hover {
color: #fff;
cursor: pointer;
}
button:hover::before {
transform: translate3d(-50%, 50%, 0) scale3d(15, 15, 15);
}
</style>

View File

@@ -0,0 +1,43 @@
<template>
<button>Bubble</button>
</template>
<style scoped>
button {
position: relative;
z-index: 1;
padding: 0.5em 1em;
overflow: hidden;
font-family: inherit;
font-size: inherit;
color: white;
outline: none;
background-color: hsl(236deg 32% 26%);
border: none;
transition: color 0.4s ease-in-out;
}
button::before {
position: absolute;
top: 50%;
left: 50%;
z-index: -1;
width: 1em;
height: 1em;
content: '';
background-color: #1890ff;
border-radius: 50%;
transform: translate3d(-50%, -50%, 0) scale3d(0, 0, 0);
transform-origin: center;
transition: transform 0.45s ease-in-out;
}
button:hover {
color: #fff;
cursor: pointer;
}
button:hover::before {
transform: translate3d(-50%, -50%, 0) scale3d(15, 15, 15);
}
</style>

View File

@@ -0,0 +1,40 @@
<template>
<button>Jelly</button>
</template>
<style scoped>
button {
z-index: 1;
padding: 0.5em 1em;
font-family: inherit;
font-size: inherit;
color: white;
outline: none;
background-color: hsl(236deg 32% 26%);
border: none;
}
button:hover {
cursor: pointer;
animation: jelly 0.5s;
}
@keyframes jelly {
0%,
100% {
transform: scale(1, 1);
}
25% {
transform: scale(0.9, 1.1);
}
50% {
transform: scale(1.1, 0.9);
}
75% {
transform: scale(0.95, 1.05);
}
}
</style>

View File

@@ -0,0 +1,38 @@
<template>
<button>Pulse</button>
</template>
<style scoped>
button {
position: relative;
z-index: 1;
padding: 0.5em 1em;
font-family: inherit;
font-size: inherit;
color: white;
outline: none;
background-color: hsl(236deg 32% 26%);
border: none;
}
button:hover {
cursor: pointer;
}
button::before {
position: absolute;
inset: 0;
z-index: -1;
content: '';
border: 4px solid hsl(236deg 32% 26%);
transform: scale(1);
transform-origin: center;
}
button:hover::before {
opacity: 0;
transform: scale(1.75);
transform-origin: center;
transition: all 0.75s ease-in-out;
}
</style>

View File

@@ -0,0 +1,35 @@
<template>
<button>Shine</button>
</template>
<style scoped>
button {
position: relative;
z-index: 1;
padding: 0.5em 1em;
overflow: hidden;
font-family: inherit;
font-size: inherit;
color: white;
cursor: pointer;
outline: none;
background-color: hsl(236deg 32% 26%);
border: none;
}
button::after {
position: absolute;
top: -50%;
bottom: -50%;
z-index: -1;
width: 1.25em;
content: '';
background-color: hsl(0deg 0% 100% / 20%);
transform: translate3d(-525%, 0, 0) rotate(35deg);
}
button:hover::after {
transform: translate3d(200%, 0, 0) rotate(35deg);
transition: transform 0.45s ease-in-out;
}
</style>

View File

@@ -0,0 +1,37 @@
<template>
<button>Slide</button>
</template>
<style scoped>
button {
position: relative;
z-index: 1;
padding: 0.5em 1em;
font-family: inherit;
font-size: inherit;
color: white;
outline: none;
background-color: hsl(236deg 32% 26%);
border: none;
}
button::before {
position: absolute;
inset: 0;
z-index: -1;
content: '';
background-color: #1890ff;
transform: scaleY(0);
transform-origin: bottom center;
transition: transform 0.25s ease-in-out;
}
button:hover {
cursor: pointer;
}
button:hover::before {
transform: scaleY(1);
transform-origin: center top;
}
</style>

View File

@@ -0,0 +1,37 @@
<template>
<button>Slide</button>
</template>
<style scoped>
button {
position: relative;
z-index: 1;
padding: 0.5em 1em;
font-family: inherit;
font-size: inherit;
color: white;
outline: none;
background-color: hsl(236deg 32% 26%);
border: none;
}
button::before {
position: absolute;
inset: 0;
z-index: -1;
content: '';
background-color: #1890ff;
transform: scaleX(0);
transform-origin: left center;
transition: transform 0.25s ease-in-out;
}
button:hover {
cursor: pointer;
}
button:hover::before {
transform: scaleX(1);
transform-origin: right center;
}
</style>

View File

@@ -0,0 +1,37 @@
<template>
<button>Slide</button>
</template>
<style scoped>
button {
position: relative;
z-index: 1;
padding: 0.5em 1em;
font-family: inherit;
font-size: inherit;
color: white;
outline: none;
background-color: hsl(236deg 32% 26%);
border: none;
}
button::before {
position: absolute;
inset: 0;
z-index: -1;
content: '';
background-color: #1890ff;
transform: scaleX(0);
transform-origin: right center;
transition: transform 0.25s ease-in-out;
}
button:hover {
cursor: pointer;
}
button:hover::before {
transform: scaleX(1);
transform-origin: left center;
}
</style>

View File

@@ -0,0 +1,37 @@
<template>
<button>Slide</button>
</template>
<style scoped>
button {
position: relative;
z-index: 1;
padding: 0.5em 1em;
font-family: inherit;
font-size: inherit;
color: white;
outline: none;
background-color: hsl(236deg 32% 26%);
border: none;
}
button::before {
position: absolute;
inset: 0;
z-index: -1;
content: '';
background-color: #1890ff;
transform: scaleY(0);
transform-origin: top center;
transition: transform 0.25s ease-in-out;
}
button:hover {
cursor: pointer;
}
button:hover::before {
transform: scaleY(1);
transform-origin: center bottom;
}
</style>

View File

@@ -0,0 +1,24 @@
const requireEffect = require.context('./', false, /\.vue$/)
const effectList = requireEffect.keys()
const effects = {}
const components = {}
for (const filename of effectList) {
const name = filename.replace('./', '').replace('.vue', '')
const type = name.slice(0, Math.max(0, name.indexOf('-')))
const raw = require(`!!raw-loader!./${name}`).default
const component = requireEffect(filename).default
const html = /<template>(.*?)<\/template>/g
.exec(JSON.stringify(raw))[0]
.replace(/<\/?template>/g, '')
.replace(/^\\n/, '')
.replace(/\\n/g, '\n')
.replace(/\\"/g, '"')
const css = /<style scoped>(.*?)<\/style>/g
.exec(JSON.stringify(raw))[0]
.replace(/<\/?style(?: scoped)?>/g, '')
.replace(/^\\n/, '')
.replace(/\\n/g, '\n')
effects[name] = { name, type, html, css }
components[name] = component
}
export { effects, components }

View File

@@ -0,0 +1,96 @@
<template>
<div>
<input placeholder="Input Outline" type="text" />
<span class="bottom" />
<span class="right" />
<span class="top" />
<span class="left" />
</div>
</template>
<style scoped>
div {
position: relative;
}
input {
width: 6.5em;
padding: 0.35em 0.45em;
font-family: inherit;
font-size: inherit;
color: white;
background-color: hsl(236deg 32% 26%);
border: 1px solid transparent;
transition: background-color 0.3s ease-in-out;
}
input:focus {
outline: none;
}
input::placeholder {
color: hsl(0deg 0% 100% / 60%);
}
span {
position: absolute;
background-color: #1890ff;
transition: transform 0.5s ease;
}
.bottom,
.top {
right: 0;
left: 0;
height: 1px;
transform: scaleX(0);
}
.left,
.right {
top: 0;
bottom: 0;
width: 1px;
transform: scaleY(0);
}
.bottom {
bottom: 0;
transform-origin: bottom right;
}
input:focus ~ .bottom {
transform: scaleX(1);
transform-origin: bottom left;
}
.right {
right: 0;
transform-origin: top right;
}
input:focus ~ .right {
transform: scaleY(1);
transform-origin: bottom right;
}
.top {
top: 0;
transform-origin: top left;
}
input:focus ~ .top {
transform: scaleX(1);
transform-origin: top right;
}
.left {
left: 0;
transform-origin: bottom left;
}
input:focus ~ .left {
transform: scaleY(1);
transform-origin: top left;
}
</style>

View File

@@ -0,0 +1,83 @@
<template>
<div>
<input placeholder="Input Outline" type="text" />
<span class="bottom" />
<span class="right" />
<span class="top" />
<span class="left" />
</div>
</template>
<style scoped>
div {
position: relative;
}
input {
width: 6.5em;
padding: 0.35em 0.45em;
font-family: inherit;
font-size: inherit;
color: white;
background-color: hsl(236deg 32% 26%);
border: 1px solid transparent;
transition: background-color 0.3s ease-in-out;
}
input:focus {
outline: none;
}
input::placeholder {
color: hsl(0deg 0% 100% / 60%);
}
span {
position: absolute;
background-color: #1890ff;
transform-origin: center;
transition: transform 0.5s ease;
}
.bottom,
.top {
right: 0;
left: 0;
height: 1px;
transform: scaleX(0);
}
.left,
.right {
top: 0;
bottom: 0;
width: 1px;
transform: scaleY(0);
}
.top {
top: 0;
}
.bottom {
bottom: 0;
}
.left {
left: 0;
}
.right {
right: 0;
}
input:focus ~ .top,
input:focus ~ .bottom {
transform: scaleX(1);
}
input:focus ~ .left,
input:focus ~ .right {
transform: scaleY(1);
}
</style>

View File

@@ -0,0 +1,99 @@
<template>
<div>
<input placeholder="Input Trace" type="text" />
<span class="bottom" />
<span class="right" />
<span class="top" />
<span class="left" />
</div>
</template>
<style scoped>
div {
position: relative;
}
input {
width: 6.5em;
padding: 0.35em 0.45em;
font-family: inherit;
font-size: inherit;
color: white;
background-color: hsl(236deg 32% 26%);
border: 1px solid transparent;
transition: background-color 0.3s ease-in-out;
}
input:focus {
outline: none;
}
input::placeholder {
color: hsl(0deg 0% 100% / 60%);
}
span {
position: absolute;
background-color: #1890ff;
transition: transform 0.1s ease;
}
.bottom,
.top {
right: 0;
left: 0;
height: 1px;
transform: scaleX(0);
}
.left,
.right {
top: 0;
bottom: 0;
width: 1px;
transform: scaleY(0);
}
.bottom {
bottom: 0;
transform-origin: bottom right;
}
input:focus ~ .bottom {
transform: scaleX(1);
transform-origin: bottom left;
}
.right {
right: 0;
transform-origin: top right;
transition-delay: 0.05s;
}
input:focus ~ .right {
transform: scaleY(1);
transform-origin: bottom right;
}
.top {
top: 0;
transform-origin: top left;
transition-delay: 0.15s;
}
input:focus ~ .top {
transform: scaleX(1);
transform-origin: top right;
}
.left {
left: 0;
transform-origin: bottom left;
transition-delay: 0.25s;
}
input:focus ~ .left {
transform: scaleY(1);
transform-origin: top left;
}
</style>

View File

@@ -0,0 +1,47 @@
<template>
<div>
<input placeholder="Input Underline" type="text" />
<span />
</div>
</template>
<style scoped>
div {
position: relative;
}
input {
width: 6.5em;
font-family: inherit;
font-size: inherit;
color: white;
background-color: transparent;
border: 1px solid transparent;
border-bottom-color: hsl(185deg 100% 62% / 20%);
}
input:focus {
outline: none;
}
input::placeholder {
color: hsl(0deg 0% 100% / 60%);
}
span {
position: absolute;
right: 0;
bottom: 0;
left: 0;
height: 1px;
background-color: #1890ff;
transform: scaleX(0);
transform-origin: bottom right;
transition: transform 0.5s ease;
}
input:focus ~ span {
transform: scaleX(1);
transform-origin: bottom left;
}
</style>

View File

@@ -0,0 +1,48 @@
<template>
<div>
<input placeholder="Input Underline" type="text" />
<span />
</div>
</template>
<style scoped>
div {
position: relative;
}
input {
width: 6.5em;
font-family: inherit;
font-size: inherit;
color: white;
background-color: transparent;
border: 1px solid transparent;
border-bottom-color: hsl(341deg 97% 59% / 20%);
}
input:focus {
outline: none;
}
input::placeholder {
color: hsl(0deg 0% 100% / 60%);
}
span {
position: absolute;
bottom: 0;
left: 50%;
width: 100%;
height: 1px;
background-color: #1890ff;
opacity: 0;
transform: translate(-50%, 0) scaleX(0);
transform-origin: center;
transition: all 0.3s ease;
}
input:focus ~ span {
opacity: 1;
transform: translate(-50%, 0) scaleX(1);
}
</style>

View File

@@ -0,0 +1,36 @@
<template>
<span>Bars</span>
</template>
<style scoped>
span {
position: relative;
}
span::before,
span::after {
position: absolute;
right: 0;
left: 0;
height: 2px;
content: '';
background-color: #1890ff;
transform: scaleX(0);
transform-origin: right center;
transition: transform 0.5s ease;
}
span::before {
top: 0;
}
span::after {
bottom: 0;
}
span:hover::before,
span:hover::after {
transform: scaleX(1);
transform-origin: left center;
}
</style>

View File

@@ -0,0 +1,41 @@
<template>
<span>Bars</span>
</template>
<style scoped>
span {
position: relative;
}
span::before,
span::after {
position: absolute;
right: 0;
left: 0;
height: 2px;
content: '';
background-color: #1890ff;
transform: scaleX(0);
transition: transform 0.5s ease;
}
span::before {
top: 0;
transform-origin: right center;
}
span:hover::before {
transform: scaleX(1);
transform-origin: left center;
}
span::after {
bottom: 0;
transform-origin: left center;
}
span:hover::after {
transform: scaleX(1);
transform-origin: right center;
}
</style>

View File

@@ -0,0 +1,35 @@
<template>
<span>Bars</span>
</template>
<style scoped>
span {
position: relative;
}
span::before,
span::after {
position: absolute;
left: 50%;
width: 100%;
height: 2px;
content: '';
background-color: #1890ff;
transform: translateX(-50%) scaleX(0);
transform-origin: center;
transition: transform 0.4s ease;
}
span::before {
top: 0;
}
span::after {
bottom: 0;
}
span:hover::before,
span:hover::after {
transform: translateX(-50%) scaleX(1);
}
</style>

View File

@@ -0,0 +1,26 @@
<template>
<span>Highlight</span>
</template>
<style scoped>
span {
position: relative;
z-index: 1;
}
span::before {
position: absolute;
inset: 0 -0.25em;
z-index: -1;
content: '';
background-color: #1890ff;
transform: scaleX(0);
transform-origin: right center;
transition: transform 0.2s ease-in-out;
}
span:hover::before {
transform: scaleX(1);
transform-origin: left center;
}
</style>

View File

@@ -0,0 +1,26 @@
<template>
<span>Highlight</span>
</template>
<style scoped>
span {
position: relative;
z-index: 1;
}
span::before {
position: absolute;
inset: 0 -0.25em;
z-index: -1;
content: '';
background-color: #1890ff;
transform: scaleY(0.1);
transform-origin: bottom center;
transition: all 0.1s ease-in-out;
}
span:hover::before {
background-color: #1890ff;
transform: scaleY(1);
}
</style>

View File

@@ -0,0 +1,27 @@
<template>
<span>Overline</span>
</template>
<style scoped>
span {
position: relative;
}
span::before {
position: absolute;
top: -2px;
right: 0;
left: 0;
height: 2px;
content: '';
background-color: #1890ff;
transform: scaleX(0);
transform-origin: bottom right;
transition: transform 0.5s ease;
}
span:hover::before {
transform: scaleX(1);
transform-origin: bottom left;
}
</style>

View File

@@ -0,0 +1,28 @@
<template>
<span>Overline</span>
</template>
<style scoped>
span {
position: relative;
}
span::before {
position: absolute;
top: -2px;
left: 50%;
width: 100%;
height: 2px;
content: '';
background-color: #1890ff;
opacity: 0;
transform: translate(-50%, 0) scaleX(0);
transform-origin: center;
transition: all 0.3s ease-in-out;
}
span:hover::before {
opacity: 1;
transform: translate(-50%, 0) scaleX(1);
}
</style>

View File

@@ -0,0 +1,36 @@
<template>
<span>Pillars</span>
</template>
<style scoped>
span {
position: relative;
}
span::before,
span::after {
position: absolute;
top: 0;
bottom: 0;
width: 2px;
content: '';
background-color: #1890ff;
transform: scaleY(0);
transform-origin: center top;
transition: transform 0.5s ease;
}
span::before {
left: -8px;
}
span::after {
right: -8px;
}
span:hover::before,
span:hover::after {
transform: scaleY(1);
transform-origin: center bottom;
}
</style>

View File

@@ -0,0 +1,41 @@
<template>
<span>Pillars</span>
</template>
<style scoped>
span {
position: relative;
}
span::before,
span::after {
position: absolute;
top: 0;
bottom: 0;
width: 2px;
content: '';
background-color: #1890ff;
transform: scaleY(0);
transition: transform 0.5s ease;
}
span::before {
left: -8px;
transform-origin: center top;
}
span:hover::before {
transform: scaleY(1);
transform-origin: center bottom;
}
span::after {
right: -8px;
transform-origin: center bottom;
}
span:hover::after {
transform: scaleY(1);
transform-origin: center top;
}
</style>

View File

@@ -0,0 +1,35 @@
<template>
<span>Pillars</span>
</template>
<style scoped>
span {
position: relative;
}
span::before,
span::after {
position: absolute;
top: 0;
bottom: 0;
width: 2px;
content: '';
background-color: #1890ff;
transform: scaleY(0);
transform-origin: center;
transition: transform 0.5s ease;
}
span::before {
left: -8px;
}
span::after {
right: -8px;
}
span:hover::before,
span:hover::after {
transform: scaleY(1);
}
</style>

View File

@@ -0,0 +1,31 @@
<template>
<span>Strikethrough</span>
</template>
<style scoped>
span {
position: relative;
}
span::before {
position: absolute;
top: 50%;
right: 0;
left: 0;
height: 3px;
content: '';
background-color: #1890ff;
transform: scaleX(0) translateY(-50%);
transform-origin: right center;
transition: transform 0.3s ease;
}
span:hover {
color: hsl(0deg 0% 100% / 80%);
}
span:hover::before {
transform: scaleX(1) translateY(-50%);
transform-origin: left center;
}
</style>

View File

@@ -0,0 +1,27 @@
<template>
<span>Underline</span>
</template>
<style scoped>
span {
position: relative;
}
span::before {
position: absolute;
right: 0;
bottom: 0;
left: 0;
height: 2px;
content: '';
background-color: #1890ff;
transform: scaleX(0);
transform-origin: bottom right;
transition: transform 0.5s ease;
}
span:hover::before {
transform: scaleX(1);
transform-origin: bottom left;
}
</style>

View File

@@ -0,0 +1,27 @@
<template>
<span>Underline</span>
</template>
<style scoped>
span {
position: relative;
}
span::before {
position: absolute;
right: 0;
bottom: 0;
left: 0;
height: 2px;
content: '';
background-color: #1890ff;
transform: scaleX(1);
transform-origin: bottom left;
transition: transform 0.3s ease-in-out;
}
span:hover::before {
transform: scaleX(0);
transform-origin: bottom right;
}
</style>

View File

@@ -0,0 +1,26 @@
<template>
<span>Underline</span>
</template>
<style scoped>
span {
position: relative;
}
span::before {
position: absolute;
bottom: 0;
left: 50%;
width: 100%;
height: 2px;
content: '';
background-color: #1890ff;
transform: translate(-50%, 0) scaleX(0);
transform-origin: center;
transition: transform 0.3s ease-in-out;
}
span:hover::before {
transform: translate(-50%, 0) scaleX(1);
}
</style>

View File

@@ -0,0 +1,50 @@
<template>
<div class="cssfx-container">
<el-row :gutter="20">
<el-col
v-for="(item, index) in effects"
:key="item.name"
:lg="4"
:md="4"
:sm="12"
:xl="3"
:xs="12"
>
<div class="cssfx-container-card">
<component :is="index" />
</div>
</el-col>
</el-row>
</div>
</template>
<script>
import { components, effects } from './components'
export default defineComponent({
name: 'Cssfx',
components: { ...components },
setup() {
return {
effects,
}
},
})
</script>
<style lang="scss" scoped>
.cssfx-container {
&-card {
position: relative;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
min-height: 100px;
margin-bottom: $base-margin;
color: var(--el-color-white);
background: #090821;
border-radius: $base-border-radius;
}
}
</style>

View File

@@ -0,0 +1,122 @@
<template>
<div class="card-drag-container">
<vab-query-form>
<vab-query-form-left-panel>
<el-button type="danger" @click="sort">重置顺序</el-button>
</vab-query-form-left-panel>
</vab-query-form>
<el-row :gutter="20">
<vab-draggable
v-model="iconList"
item-key="icon"
v-bind="dragOptions"
>
<template #item="{ element: item }">
<el-col :lg="3" :md="3" :sm="6" :xl="3" :xs="12">
<vab-card class="icon-panel">
<vab-icon
:icon="item.icon"
:style="{ color: item.color }"
/>
<p>按住拖拽</p>
</vab-card>
</el-col>
</template>
</vab-draggable>
</el-row>
</div>
</template>
<script lang="ts" setup>
import _ from 'lodash'
import VabDraggable from 'vuedraggable'
import { getIconList } from '@/api/defaultIcon'
defineOptions({
name: 'CardDrag',
})
const iconList = ref<any>([])
const randomHexColor = () => {
return _.shuffle([
'#1890FF',
'#36CBCB',
'#4ECB73',
'#FBD437',
'#F2637B',
'#975FE5',
])
}
const fetchData = async () => {
const { data } = await getIconList({
pageNo: 1,
pageSize: 89,
})
iconList.value = data.list
.filter((icon: any) => icon.includes('-line'))
.map((icon: any, index: any) => {
return { icon, color: randomHexColor(), order: index + 1 }
})
}
const sort = () => {
iconList.value = iconList.value.sort(
(a: any, b: any) => a.order - b.order
)
}
const dragOptions = computed(() => {
return {
animation: 600,
group: 'description',
disabled: false,
ghostClass: 'ghost',
}
})
onMounted(() => {
fetchData()
})
</script>
<style lang="scss" scoped>
.card-drag-container {
:deep() {
.el-row {
display: block;
> div {
position: relative;
display: flex;
flex-wrap: wrap;
width: 100%;
}
}
}
.icon-panel {
height: 120px;
text-align: center;
cursor: move;
user-select: none;
&:hover {
i {
transform: scale(1.15);
}
}
i {
display: block;
width: 50px;
height: 50px;
margin: auto;
font-size: 40px;
transition: all ease-in-out 0.3s;
}
p {
margin-top: 10px;
}
}
}
</style>

View File

@@ -0,0 +1,37 @@
<template>
<vab-dialog
v-model="dialogVisible"
show-fullscreen
title="温馨提示"
width="600px"
>
昨夜西风凋碧树独上高楼望尽天涯路
<template #footer>
<el-button @click="dialogVisible = false"> </el-button>
<el-button type="primary" @click="dialogVisible = false">
</el-button>
</template>
</vab-dialog>
</template>
<script>
import VabDialog from '@/plugins/VabDialog'
export default defineComponent({
name: 'DialogDemo',
components: {
VabDialog,
},
setup() {
const state = reactive({
dialogVisible: false,
})
return {
...toRefs(state),
}
},
})
</script>
<style></style>

View File

@@ -0,0 +1,29 @@
<template>
<div class="dialog-drag-container">
<el-button type="primary" @click="handleClick">可拖拽弹窗</el-button>
<dialog-demo ref="dialogRef" />
</div>
</template>
<script>
export default defineComponent({
name: 'DialogDrag',
components: {
DialogDemo: defineAsyncComponent(
() => import('./components/DialogDemo')
),
},
setup() {
const state = reactive({
dialogRef: '',
})
const handleClick = () => {
state['dialogRef'].dialogVisible = true
}
return {
...toRefs(state),
handleClick,
}
},
})
</script>

View File

@@ -0,0 +1,40 @@
<template>
<div class="dynamic-anchor-container">
<vab-anchor :floor-list="floorList">
<!--TODO 待优化 这里不会动态循环想做成饿了么那种 el-menu >>> el-menu-item 这种嵌套并且可循环的組件形式 -->
<template #floor0>用户管理</template>
<template #floor1>配置管理</template>
<template #floor2>角色管理</template>
<template #floor3>定时任务补偿</template>
</vab-anchor>
</div>
</template>
<script>
import VabAnchor from '@/plugins/VabAnchor'
export default defineComponent({
name: 'DynamicAnchor',
components: { VabAnchor },
setup() {
const state = reactive({
floorList: [
{ title: '用户管理' },
{ title: '配置管理' },
{ title: '角色管理' },
{ title: '定时任务补偿' },
],
})
return {
...toRefs(state),
}
},
})
</script>
<style lang="scss" scoped>
.dynamic-anchor-container {
// TODO
}
</style>

View File

@@ -0,0 +1,153 @@
<template>
<div class="dynamic-meta-container">
<el-row :gutter="20">
<el-col :lg="8" :md="12" :sm="24" :xl="8" :xs="24">
<vab-card shadow="never">
<template #header>
<span>动态标题</span>
</template>
<el-button
@click="
handleMeta('DynamicMeta', { title: 'vab-demo' })
"
>
标题变更为 vab-demo
</el-button>
<el-button
@click="
handleMeta('DynamicMeta', { title: '动态Meta' })
"
>
还原为默认标题
</el-button>
</vab-card>
</el-col>
<el-col :lg="8" :md="12" :sm="24" :xl="8" :xs="24">
<vab-card shadow="never">
<template #header>
<span>动态徽章</span>
</template>
<el-button @click="handleBadge('DynamicMeta')">
徽章+ 1
</el-button>
<el-button
@click="resetBadge('DynamicMeta', { badge: '0' })"
>
徽章清零
</el-button>
<el-button
@click="resetBadge('DynamicMeta', { badge: false })"
>
移除徽章
</el-button>
</vab-card>
</el-col>
<el-col :lg="8" :md="12" :sm="24" :xl="8" :xs="24">
<vab-card shadow="never">
<template #header>
<span>动态图标</span>
</template>
<el-popover
popper-class="icon-selector-popper"
trigger="hover"
:width="292"
>
<template #reference>
<el-button>
<vab-icon :icon="icon" />
修改图标
<vab-icon icon="arrow-down-s-line" />
</el-button>
</template>
<vab-icon-selector @handle-icon="handleIcon" />
</el-popover>
</vab-card>
</el-col>
<el-col :lg="8" :md="12" :sm="24" :xl="8" :xs="24">
<vab-card shadow="never">
<template #header>
<span>动态高亮菜单</span>
</template>
<el-button @click="handleActiveMenu('/other/notice')">
修改高亮菜单至通知组件
</el-button>
<el-button @click="handleActiveMenu('/other/dynamicMeta')">
还原默认高亮菜单
</el-button>
</vab-card>
</el-col>
</el-row>
</div>
</template>
<script>
import { useTabsStore } from '@/store/modules/tabs'
import { useRoutesStore } from '@/store/modules/routes'
import getPageTitle, { setCustomTitle } from '@/utils/pageTitle'
import VabIconSelector from '@/plugins/VabIconSelector'
export default defineComponent({
name: 'DynamicMeta',
components: { VabIconSelector },
setup() {
const route = useRoute()
const tabsStore = useTabsStore()
const routesStore = useRoutesStore()
const { changeTabsMeta } = tabsStore
const { changeActiveMenu, changeMenuMeta } = routesStore
const state = reactive({
badge: 0,
icon: route.meta.icon,
defaultTitle: route.meta.title,
})
const handleBadge = (name) => {
state.badge = state.badge + 1
changeMenuMeta({
name,
meta: { badge: state.badge },
})
}
const resetBadge = (name, meta) => {
state.badge = 0
changeMenuMeta({ name, meta })
}
const handleMeta = (name, meta) => {
if (meta.title) {
// 设置自定义标题并更新浏览器标题
setCustomTitle(meta.title)
document.title = getPageTitle(meta.title)
}
changeMenuMeta({ name, meta })
changeTabsMeta({ name, meta })
}
const handleIcon = (item) => {
state.icon = item
changeMenuMeta({ name: 'DynamicMeta', meta: { icon: item } })
changeTabsMeta({ name: 'DynamicMeta', meta: { icon: item } })
}
const handleActiveMenu = (activeMenu) => {
changeActiveMenu(activeMenu)
}
return {
...toRefs(state),
handleBadge,
resetBadge,
handleMeta,
handleIcon,
handleActiveMenu,
}
},
})
</script>
<style lang="scss" scoped>
$base: '.dynamic-meta';
#{$base}-container {
padding: 0 !important;
background: $base-color-background !important;
}
</style>

View File

@@ -0,0 +1,60 @@
<template>
<div class="test1-container">
<el-alert :closable="false" show-icon title="params" type="success" />
<vab-json-viewer copyable :expand-depth="5" sort :value="route" />
</div>
</template>
<script>
import VabJsonViewer from 'vue-json-viewer'
import { useTabsStore } from '@/store/modules/tabs'
export default defineComponent({
name: 'Test1',
components: { VabJsonViewer },
setup() {
const route = useRoute()
const tabsStore = useTabsStore()
const { changeTabsMeta } = tabsStore
const state = reactive({
route: {},
})
const handleParams = () => {
nextTick(() => {
changeTabsMeta({
title: 'Query',
meta: {
title: `Query Id=${route.query.id}`,
},
})
const _route = route.matched[0].children.find(
(item) => item.name === 'Test1'
)
const id = route.path.substring(
route.path.lastIndexOf('/') + 1,
route.path.length
)
state.route = {
path: _route.path,
params: {
id,
},
name: _route.name,
meta: _route.meta,
}
})
}
onMounted(() => {
handleParams()
})
return {
...toRefs(state),
}
},
})
</script>

View File

@@ -0,0 +1,49 @@
<template>
<div class="test2-container">
<el-alert :closable="false" show-icon title="query" type="success" />
<vab-json-viewer copyable :expand-depth="5" sort :value="route" />
</div>
</template>
<script>
import VabJsonViewer from 'vue-json-viewer'
import { useTabsStore } from '@/store/modules/tabs'
export default defineComponent({
name: 'Test2',
components: { VabJsonViewer },
setup() {
const route = useRoute()
const tabsStore = useTabsStore()
const { changeTabsMeta } = tabsStore
const state = reactive({
route: {},
})
const handleQuery = () => {
nextTick(() => {
changeTabsMeta({
title: 'Query',
meta: {
title: `Query Id=${route.query.id}`,
},
})
state.route = {
path: route.path,
query: route.query,
name: route.name,
meta: route.meta,
}
})
}
onMounted(() => {
handleQuery()
})
return {
...toRefs(state),
}
},
})
</script>

View File

@@ -0,0 +1,57 @@
<template>
<el-col :lg="8" :md="12" :sm="24" :xl="6" :xs="24">
<vab-card shadow="never">
<template #header>
<span>{{ title }}</span>
</template>
<vab-chart
:init-options="initOptions"
:option="option"
theme="vab-echarts-theme"
/>
</vab-card>
</el-col>
</template>
<script>
import VabChart from '@/plugins/VabChart'
export default defineComponent({
name: 'VabChartBar',
components: {
VabChart,
},
props: {
title: {
type: String,
default: '',
},
},
data() {
return {
initOptions: {
renderer: 'svg',
},
option: {
grid: {
top: 20,
right: 20,
bottom: 40,
left: 40,
},
xAxis: {
type: 'category',
data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'],
},
yAxis: {
type: 'value',
},
series: {
data: [120, 200, 150, 80, 70, 110, 130],
type: 'bar',
},
},
}
},
})
</script>

View File

@@ -0,0 +1,66 @@
<template>
<el-col :lg="8" :md="12" :sm="24" :xl="6" :xs="24">
<vab-card shadow="never">
<template #header>
<span>{{ title }}</span>
</template>
<vab-chart
:init-options="initOptions"
:option="option"
theme="vab-echarts-theme"
/>
</vab-card>
</el-col>
</template>
<script>
import VabChart from '@/plugins/VabChart'
export default defineComponent({
name: 'VabChartCandlestick',
components: {
VabChart,
},
props: {
title: {
type: String,
default: '',
},
},
data() {
return {
initOptions: {
renderer: 'svg',
},
option: {
grid: {
top: 20,
right: 20,
bottom: 40,
left: 40,
},
xAxis: {
data: [
'2017-10-24',
'2017-10-25',
'2017-10-26',
'2017-10-27',
],
},
yAxis: {},
series: [
{
type: 'k',
data: [
[20, 34, 10, 38],
[40, 35, 30, 50],
[31, 38, 33, 44],
[38, 15, 5, 42],
],
},
],
},
}
},
})
</script>

View File

@@ -0,0 +1,83 @@
<template>
<el-col :lg="8" :md="12" :sm="24" :xl="6" :xs="24">
<vab-card shadow="never">
<template #header>
<span>{{ title }}</span>
</template>
<vab-chart
:init-options="initOptions"
:option="option"
theme="vab-echarts-theme"
/>
</vab-card>
</el-col>
</template>
<script>
import VabChart from '@/plugins/VabChart'
export default defineComponent({
name: 'VabChartFunnel',
components: {
VabChart,
},
props: {
title: {
type: String,
default: '',
},
},
data() {
return {
initOptions: {
renderer: 'svg',
},
option: {
grid: {
top: 20,
right: 20,
bottom: 20,
left: 20,
},
tooltip: {
trigger: 'item',
formatter: '{a} <br/>{b} : {c}%',
},
series: {
name: '漏斗图',
type: 'funnel',
left: '20%',
top: 20,
bottom: 20,
width: '60%',
min: 0,
max: 100,
minSize: '0%',
maxSize: '100%',
sort: 'descending',
gap: 2,
labelLine: {
length: 10,
lineStyle: {
width: 1,
type: 'solid',
},
},
emphasis: {
label: {
fontSize: 12,
},
},
data: [
{ value: 60, name: '访问' },
{ value: 40, name: '咨询' },
{ value: 20, name: '订单' },
{ value: 80, name: '点击' },
{ value: 100, name: '展现' },
],
},
},
}
},
})
</script>

View File

@@ -0,0 +1,69 @@
<template>
<el-col :lg="8" :md="12" :sm="24" :xl="6" :xs="24">
<vab-card shadow="never">
<template #header>
<span>{{ title }}</span>
</template>
<vab-chart
:init-options="initOptions"
:option="option"
theme="vab-echarts-theme"
/>
</vab-card>
</el-col>
</template>
<script>
import VabChart from '@/plugins/VabChart'
export default defineComponent({
name: 'VabChartGauge',
components: {
VabChart,
},
props: {
title: {
type: String,
default: '',
},
},
data() {
return {
initOptions: {
renderer: 'svg',
},
option: {
grid: {
top: 20,
right: 20,
bottom: 20,
left: 20,
},
tooltip: {
formatter: '{a} <br/>{b} : {c}%',
},
series: {
name: 'Pressure',
type: 'gauge',
radius: '100%',
progress: {
show: true,
},
detail: {
formatter: '{value}',
valueAnimation: true,
fontSize: 14,
offsetCenter: [0, '70%'],
},
data: [
{
value: 50,
name: 'SCORE',
},
],
},
},
}
},
})
</script>

View File

@@ -0,0 +1,57 @@
<template>
<el-col :lg="8" :md="12" :sm="24" :xl="6" :xs="24">
<vab-card shadow="never">
<template #header>
<span>{{ title }}</span>
</template>
<vab-chart
:init-options="initOptions"
:option="option"
theme="vab-echarts-theme"
/>
</vab-card>
</el-col>
</template>
<script>
import VabChart from '@/plugins/VabChart'
export default defineComponent({
name: 'VabChartLine',
components: {
VabChart,
},
props: {
title: {
type: String,
default: '',
},
},
data() {
return {
initOptions: {
renderer: 'svg',
},
option: {
grid: {
top: 20,
right: 20,
bottom: 40,
left: 40,
},
xAxis: {
type: 'category',
data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'],
},
yAxis: {
type: 'value',
},
series: {
data: [150, 230, 224, 218, 135, 147, 260],
type: 'line',
},
},
}
},
})
</script>

View File

@@ -0,0 +1,82 @@
<template>
<el-col :lg="8" :md="12" :sm="24" :xl="6" :xs="24">
<vab-card shadow="never">
<template #header>
<span>{{ title }}</span>
</template>
<vab-chart
:init-options="initOptions"
:option="option"
theme="vab-echarts-theme"
/>
</vab-card>
</el-col>
</template>
<script>
import VabChart from '@/plugins/VabChart'
export default defineComponent({
name: 'VabChartPie',
components: {
VabChart,
},
props: {
title: {
type: String,
default: '',
},
},
data() {
return {
initOptions: {
renderer: 'svg',
},
option: {
grid: {
top: 20,
right: 20,
bottom: 40,
left: 40,
},
tooltip: {
trigger: 'item',
},
series: [
{
name: '访问来源',
type: 'pie',
radius: ['40%', '80%'],
avoidLabelOverlap: false,
itemStyle: {
borderRadius: 10,
borderColor: '#fff',
borderWidth: 2,
},
label: {
show: false,
position: 'center',
},
emphasis: {
label: {
show: true,
fontSize: '14',
},
},
labelLine: {
show: false,
},
data: [
{ value: 1048, name: '搜索引擎' },
{ value: 735, name: '直接访问' },
{ value: 580, name: '邮件营销' },
{ value: 484, name: '联盟广告' },
{ value: 300, name: '视频广告' },
],
},
],
},
}
},
})
</script>

View File

@@ -0,0 +1,71 @@
<template>
<el-col :lg="8" :md="12" :sm="24" :xl="6" :xs="24">
<vab-card shadow="never">
<template #header>
<span>{{ title }}</span>
</template>
<vab-chart
:init-options="initOptions"
:option="option"
theme="vab-echarts-theme"
/>
</vab-card>
</el-col>
</template>
<script>
import VabChart from '@/plugins/VabChart'
export default defineComponent({
name: 'VabChartRadar',
components: {
VabChart,
},
props: {
title: {
type: String,
default: '',
},
},
data() {
return {
initOptions: {
renderer: 'svg',
},
option: {
grid: {
top: 20,
right: 20,
bottom: 40,
left: 40,
},
radar: {
indicator: [
{ name: '销售' },
{ name: '管理' },
{ name: '信息技术' },
{ name: '客服' },
{ name: '研发' },
],
},
series: [
{
name: '预算 vs 开销',
type: 'radar',
data: [
{
value: [4200, 3000, 20000, 35000, 50000],
name: '预算分配',
},
{
value: [5000, 14000, 28000, 26000, 42000],
name: '实际开销',
},
],
},
],
},
}
},
})
</script>

View File

@@ -0,0 +1,76 @@
<template>
<el-col :lg="8" :md="12" :sm="24" :xl="6" :xs="24">
<vab-card shadow="never">
<template #header>
<span>{{ title }}</span>
</template>
<vab-chart
:init-options="initOptions"
:option="option"
theme="vab-echarts-theme"
/>
</vab-card>
</el-col>
</template>
<script>
import VabChart from '@/plugins/VabChart'
export default defineComponent({
name: 'VabChartScatter',
components: {
VabChart,
},
props: {
title: {
type: String,
default: '',
},
},
data() {
return {
initOptions: {
renderer: 'svg',
},
option: {
grid: {
top: 20,
right: 20,
bottom: 40,
left: 40,
},
xAxis: {},
yAxis: {},
series: {
symbolSize: 10,
data: [
[10, 8.04],
[8.07, 6.95],
[13, 7.58],
[9.05, 8.81],
[11, 8.33],
[14, 7.66],
[13.4, 6.81],
[10, 6.33],
[14, 8.96],
[12.5, 6.82],
[9.15, 7.2],
[11.5, 7.2],
[3.03, 4.23],
[12.2, 7.83],
[2.02, 4.47],
[1.05, 3.33],
[4.05, 4.96],
[6.03, 7.24],
[12, 6.26],
[12, 8.84],
[7.08, 5.82],
[5.02, 5.68],
],
type: 'scatter',
},
},
}
},
})
</script>

View File

@@ -0,0 +1,229 @@
<template>
<el-col :lg="8" :md="12" :sm="24" :xl="6" :xs="24">
<vab-card shadow="never">
<template #header>
<span>{{ title }}</span>
</template>
<vab-chart
:init-options="initOptions"
:option="option"
theme="vab-echarts-theme"
/>
</vab-card>
</el-col>
</template>
<script>
import VabChart from '@/plugins/VabChart'
export default defineComponent({
name: 'VabChartSunburst',
components: {
VabChart,
},
props: {
title: {
type: String,
default: '',
},
},
data() {
return {
initOptions: {
renderer: 'svg',
},
option: {
grid: {
top: 20,
right: 20,
bottom: 20,
left: 20,
},
series: {
type: 'sunburst',
data: [
{
children: [
{
value: 5,
children: [
{
value: 1,
},
{
value: 2,
children: [
{
value: 1,
},
],
},
{
children: [
{
value: 1,
},
],
},
],
},
{
value: 10,
children: [
{
value: 6,
children: [
{
value: 1,
},
{
value: 1,
},
{
value: 1,
},
{
value: 1,
},
],
},
{
value: 2,
children: [
{
value: 1,
},
],
},
{
children: [
{
value: 1,
},
],
},
],
},
],
},
{
value: 9,
children: [
{
value: 4,
children: [
{
value: 2,
},
{
children: [
{
value: 1,
},
],
},
],
},
{
children: [
{
value: 3,
children: [
{
value: 1,
},
{
value: 1,
},
],
},
],
},
],
},
{
value: 7,
children: [
{
children: [
{
value: 1,
},
{
value: 3,
children: [
{
value: 1,
},
{
value: 1,
},
],
},
{
value: 2,
children: [
{
value: 1,
},
{
value: 1,
},
],
},
],
},
],
},
{
children: [
{
value: 6,
children: [
{
value: 1,
},
{
value: 2,
children: [
{
value: 2,
},
],
},
{
value: 1,
},
],
},
{
value: 3,
children: [
{
value: 1,
},
{
children: [
{
value: 1,
},
],
},
{
value: 1,
},
],
},
],
},
],
radius: ['10%', '100%'],
label: {
rotate: 'radial',
},
},
},
}
},
})
</script>

View File

@@ -0,0 +1,215 @@
<template>
<el-col :lg="8" :md="12" :sm="24" :xl="6" :xs="24">
<vab-card shadow="never">
<template #header>
<span>{{ title }}</span>
</template>
<vab-chart
:init-options="initOptions"
:option="option"
theme="vab-echarts-theme"
/>
</vab-card>
</el-col>
</template>
<script>
import VabChart from '@/plugins/VabChart'
export default defineComponent({
name: 'VabChartThemeRiver',
components: {
VabChart,
},
props: {
title: {
type: String,
default: '',
},
},
data() {
return {
initOptions: {
renderer: 'svg',
},
option: {
grid: {
top: 20,
right: 20,
bottom: 40,
left: 40,
},
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'line',
lineStyle: {
color: 'rgba(0,0,0,0.2)',
width: 1,
type: 'solid',
},
},
},
singleAxis: {
top: 20,
bottom: 20,
axisTick: {},
axisLabel: {},
type: 'time',
axisPointer: {
animation: true,
label: {
show: true,
},
},
splitLine: {
show: true,
lineStyle: {
type: 'dashed',
opacity: 0.2,
},
},
},
series: {
type: 'themeRiver',
emphasis: {
itemStyle: {
shadowBlur: 20,
shadowColor: 'rgba(0, 0, 0, 0.8)',
},
},
data: [
['2015/11/08', 10, 'DQ'],
['2015/11/09', 15, 'DQ'],
['2015/11/10', 35, 'DQ'],
['2015/11/11', 38, 'DQ'],
['2015/11/12', 22, 'DQ'],
['2015/11/13', 16, 'DQ'],
['2015/11/14', 7, 'DQ'],
['2015/11/15', 2, 'DQ'],
['2015/11/16', 17, 'DQ'],
['2015/11/17', 33, 'DQ'],
['2015/11/18', 40, 'DQ'],
['2015/11/19', 32, 'DQ'],
['2015/11/20', 26, 'DQ'],
['2015/11/21', 35, 'DQ'],
['2015/11/22', 40, 'DQ'],
['2015/11/23', 32, 'DQ'],
['2015/11/24', 26, 'DQ'],
['2015/11/25', 22, 'DQ'],
['2015/11/26', 16, 'DQ'],
['2015/11/27', 22, 'DQ'],
['2015/11/28', 10, 'DQ'],
['2015/11/08', 35, 'TY'],
['2015/11/09', 36, 'TY'],
['2015/11/10', 37, 'TY'],
['2015/11/11', 22, 'TY'],
['2015/11/12', 24, 'TY'],
['2015/11/13', 26, 'TY'],
['2015/11/14', 34, 'TY'],
['2015/11/15', 21, 'TY'],
['2015/11/16', 18, 'TY'],
['2015/11/17', 45, 'TY'],
['2015/11/18', 32, 'TY'],
['2015/11/19', 35, 'TY'],
['2015/11/20', 30, 'TY'],
['2015/11/21', 28, 'TY'],
['2015/11/22', 27, 'TY'],
['2015/11/23', 26, 'TY'],
['2015/11/24', 15, 'TY'],
['2015/11/25', 30, 'TY'],
['2015/11/26', 35, 'TY'],
['2015/11/27', 42, 'TY'],
['2015/11/28', 42, 'TY'],
['2015/11/08', 21, 'SS'],
['2015/11/09', 25, 'SS'],
['2015/11/10', 27, 'SS'],
['2015/11/11', 23, 'SS'],
['2015/11/12', 24, 'SS'],
['2015/11/13', 21, 'SS'],
['2015/11/14', 35, 'SS'],
['2015/11/15', 39, 'SS'],
['2015/11/16', 40, 'SS'],
['2015/11/17', 36, 'SS'],
['2015/11/18', 33, 'SS'],
['2015/11/19', 43, 'SS'],
['2015/11/20', 40, 'SS'],
['2015/11/21', 34, 'SS'],
['2015/11/22', 28, 'SS'],
['2015/11/23', 26, 'SS'],
['2015/11/24', 37, 'SS'],
['2015/11/25', 41, 'SS'],
['2015/11/26', 46, 'SS'],
['2015/11/27', 47, 'SS'],
['2015/11/28', 41, 'SS'],
['2015/11/08', 10, 'QG'],
['2015/11/09', 15, 'QG'],
['2015/11/10', 35, 'QG'],
['2015/11/11', 38, 'QG'],
['2015/11/12', 22, 'QG'],
['2015/11/13', 16, 'QG'],
['2015/11/14', 7, 'QG'],
['2015/11/15', 2, 'QG'],
['2015/11/16', 17, 'QG'],
['2015/11/17', 33, 'QG'],
['2015/11/18', 40, 'QG'],
['2015/11/19', 32, 'QG'],
['2015/11/20', 26, 'QG'],
['2015/11/21', 35, 'QG'],
['2015/11/22', 40, 'QG'],
['2015/11/23', 32, 'QG'],
['2015/11/24', 26, 'QG'],
['2015/11/25', 22, 'QG'],
['2015/11/26', 16, 'QG'],
['2015/11/27', 22, 'QG'],
['2015/11/28', 10, 'QG'],
['2015/11/08', 10, 'SY'],
['2015/11/09', 15, 'SY'],
['2015/11/10', 35, 'SY'],
['2015/11/11', 38, 'SY'],
['2015/11/12', 22, 'SY'],
['2015/11/13', 16, 'SY'],
['2015/11/14', 7, 'SY'],
['2015/11/15', 2, 'SY'],
['2015/11/16', 17, 'SY'],
['2015/11/17', 33, 'SY'],
['2015/11/18', 40, 'SY'],
['2015/11/19', 32, 'SY'],
['2015/11/20', 26, 'SY'],
['2015/11/21', 35, 'SY'],
['2015/11/22', 4, 'SY'],
['2015/11/23', 32, 'SY'],
['2015/11/24', 26, 'SY'],
['2015/11/25', 22, 'SY'],
['2015/11/26', 16, 'SY'],
['2015/11/27', 22, 'SY'],
['2015/11/28', 10, 'SY'],
['2015/11/08', 10, 'DD'],
['2015/11/09', 15, 'DD'],
['2015/11/10', 35, 'DD'],
['2015/11/11', 38, 'DD'],
['2015/11/12', 22, 'DD'],
['2015/11/13', 16, 'DD'],
['2015/11/14', 7, 'DD'],
['2015/11/15', 2, 'DD'],
['2015/11/16', 17, 'DD'],
['2015/11/17', 33, 'DD'],
['2015/11/18', 4, 'DD'],
['2015/11/19', 32, 'DD'],
['2015/11/20', 26, 'DD'],
['2015/11/21', 35, 'DD'],
['2015/11/22', 40, 'DD'],
['2015/11/23', 32, 'DD'],
['2015/11/24', 26, 'DD'],
['2015/11/25', 22, 'DD'],
['2015/11/26', 16, 'DD'],
['2015/11/27', 22, 'DD'],
['2015/11/28', 10, 'DD'],
],
},
},
}
},
})
</script>

View File

@@ -0,0 +1,83 @@
<template>
<el-col :lg="8" :md="12" :sm="24" :xl="6" :xs="24">
<vab-card shadow="never">
<template #header>
<span>{{ title }}</span>
</template>
<vab-chart
:init-options="initOptions"
:option="option"
theme="vab-echarts-theme"
/>
</vab-card>
</el-col>
</template>
<script>
import VabChart from '@/plugins/VabChart'
export default defineComponent({
name: 'VabChartTreemap',
components: {
VabChart,
},
props: {
title: {
type: String,
default: '',
},
},
data() {
return {
initOptions: {
renderer: 'svg',
},
option: {
grid: {
top: 20,
right: 20,
bottom: 60,
left: 40,
},
series: [
{
type: 'treemap',
data: [
{
name: 'nodeA', // First tree
value: 10,
children: [
{
name: 'nodeAa', // First leaf of first tree
value: 4,
},
{
name: 'nodeAb', // Second leaf of first tree
value: 6,
},
],
},
{
name: 'nodeB', // Second tree
value: 20,
children: [
{
name: 'nodeBa', // Son of first tree
value: 20,
children: [
{
name: 'nodeBa1', // Granson of first tree
value: 20,
},
],
},
],
},
],
},
],
},
}
},
})
</script>

View File

@@ -0,0 +1,70 @@
<template>
<div class="echarts-container">
<el-row :gutter="20">
<vab-chart-line title="折线图" />
<vab-chart-bar title="柱状图" />
<vab-chart-pie title="饼状图" />
<vab-chart-scatter title="散点图" />
<vab-chart-candlestick title="K线图" />
<vab-chart-radar title="雷达图" />
<vab-chart-treemap title="矩形树图" />
<vab-chart-sunburst title="旭日图" />
<vab-chart-funnel title="漏斗图" />
<vab-chart-gauge title="仪表图" />
<vab-chart-theme-river title="河流流向图" />
</el-row>
</div>
</template>
<script>
import VabChartLine from './components/VabChartLine'
import VabChartBar from './components/VabChartBar'
import VabChartPie from './components/VabChartPie'
import VabChartScatter from './components/VabChartScatter'
import VabChartCandlestick from './components/VabChartCandlestick'
import VabChartRadar from './components/VabChartRadar'
import VabChartTreemap from './components/VabChartTreemap'
import VabChartSunburst from './components/VabChartSunburst'
import VabChartFunnel from './components/VabChartFunnel'
import VabChartGauge from './components/VabChartGauge'
import VabChartThemeRiver from './components/VabChartThemeRiver'
export default defineComponent({
name: 'Echarts',
components: {
VabChartLine,
VabChartBar,
VabChartPie,
VabChartScatter,
VabChartCandlestick,
VabChartRadar,
VabChartTreemap,
VabChartSunburst,
VabChartFunnel,
VabChartGauge,
VabChartThemeRiver,
},
data() {
return {}
},
methods: {},
})
</script>
<style lang="scss" scoped>
.echarts-container {
padding: 0 !important;
background: $base-color-background !important;
:deep() {
.vab-card {
height: 300px;
.echarts {
width: 100%;
height: 200px;
}
}
}
}
</style>

View File

@@ -0,0 +1,10 @@
<template>
<!-- js代码错误实时提醒测试 -->
<div>{{ zxwk1998jiayou.zxwk1998jiayou }}</div>
</template>
<script>
export default defineComponent({
name: 'ErrorTest',
})
</script>

View File

@@ -0,0 +1,39 @@
<template>
<div class="errorLog-container">
<el-button type="primary" @click="handleError">
点击模拟一个zxwk1998jiayou的错误
</el-button>
<error-test v-if="show" />
</div>
</template>
<script>
import ErrorTest from './components/ErrorTest'
export default defineComponent({
name: 'ErrorLog',
components: { ErrorTest },
setup() {
const $baseMessage = inject('$baseMessage')
const state = reactive({
show: false,
})
const handleError = () => {
if (process.env.NODE_ENV === 'production') {
$baseMessage(
'\u4E3A\u4E86\u9632\u6B62\u5F15\u8D77\u6B67\u4E49\uFF0C\u6F14\u793A\u73AF\u5883\u4E0D\u5141\u8BB8\u6A21\u62DF\u9519\u8BEF\uFF0C\u8BF7\u4E0B\u8F7D\u6E90\u7801\u540E\u4F53\u9A8C\u6B64\u529F\u80FD\u3002',
'error',
'vab-hey-message-error'
)
}
state.show = true
}
return {
...toRefs(state),
handleError,
}
},
})
</script>

View File

@@ -0,0 +1,127 @@
<template>
<div class="export-excel-container">
<vab-query-form>
<vab-query-form-left-panel :span="24">
<el-form inline label-width="100px" @submit.prevent>
<el-form-item label="文件名">
<el-input
v-model="filename"
placeholder="请输出要导出文件的名称"
/>
</el-form-item>
<el-form-item label="文件类型">
<el-select v-model="bookType">
<el-option
v-for="item in options"
:key="item"
:label="item"
:value="item"
/>
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleDownload">
导出 Excel
</el-button>
</el-form-item>
</el-form>
</vab-query-form-left-panel>
</vab-query-form>
<el-table v-loading="listLoading" border :data="list">
<el-table-column align="center" label="序号" width="55">
<template #default="{ $index }">
{{ $index + 1 }}
</template>
</el-table-column>
<el-table-column align="center" label="标题">
<template #default="{ row }">
{{ row.title }}
</template>
</el-table-column>
<el-table-column align="center" label="作者">
<template #default="{ row }">
<el-tag>{{ row.author }}</el-tag>
</template>
</el-table-column>
<el-table-column align="center" label="访问量">
<template #default="{ row }">
{{ row.pageViews }}
</template>
</el-table-column>
<el-table-column align="center" label="时间">
<template #default="{ row }">
<span>{{ row.datetime }}</span>
</template>
</el-table-column>
</el-table>
</div>
</template>
<script>
import { getList } from '@/api/table'
export default defineComponent({
name: 'ExportExcel',
data() {
return {
list: [],
listLoading: true,
downloadLoading: false,
filename: '',
autoWidth: true,
bookType: 'xlsx',
options: ['xlsx', 'csv', 'txt'],
}
},
created() {
this.fetchData()
},
methods: {
async fetchData() {
this.listLoading = true
const {
data: { list },
} = await getList()
this.list = list
this.listLoading = false
},
handleDownload() {
this.downloadLoading = true
import('@/utils/excel').then((excel) => {
const tHeader = [
'Id',
'Title',
'Author',
'Readings',
'Date',
]
const filterVal = [
'id',
'title',
'author',
'pageViews',
'datetime',
]
const list = this.list
const data = this.formatJson(filterVal, list)
excel.export_json_to_excel({
header: tHeader,
data,
filename: this.filename,
autoWidth: this.autoWidth,
bookType: this.bookType,
})
this.downloadLoading = false
})
},
formatJson(filterVal, jsonData) {
return jsonData.map((v) =>
filterVal.map((j) => {
return v[j]
})
)
},
},
})
</script>

View File

@@ -0,0 +1,116 @@
<template>
<div class="merge-header-container">
<vab-query-form>
<vab-query-form-top-panel :span="24">
<el-button
:loading="downloadLoading"
type="primary"
@click="handleDownload"
>
导出
</el-button>
</vab-query-form-top-panel>
</vab-query-form>
<el-table
ref="multipleTable"
v-loading="listLoading"
border
:data="list"
>
<el-table-column align="center" label="序号" width="55">
<template #default="{ $index }">
{{ $index + 1 }}
</template>
</el-table-column>
<el-table-column align="center" label="Main Information">
<el-table-column label="Title">
<template #default="{ row }">
{{ row.title }}
</template>
</el-table-column>
<el-table-column align="center" label="Author">
<template #default="{ row }">
<el-tag>{{ row.author }}</el-tag>
</template>
</el-table-column>
<el-table-column align="center" label="Readings">
<template #default="{ row }">
{{ row.pageViews }}
</template>
</el-table-column>
</el-table-column>
<el-table-column align="center" label="Date">
<template #default="{ row }">
<span>{{ row.datetime }}</span>
</template>
</el-table-column>
</el-table>
</div>
</template>
<script>
import { getList } from '@/api/table'
import { parseTime } from '@/utils'
export default defineComponent({
name: 'ExportMergeHeaderExcel',
data() {
return {
list: [],
listLoading: true,
downloadLoading: false,
}
},
created() {
this.fetchData()
},
methods: {
async fetchData() {
this.listLoading = true
const {
data: { list },
} = await getList()
this.list = list
this.listLoading = false
},
handleDownload() {
this.downloadLoading = true
import('@/utils/excel').then((excel) => {
const multiHeader = [
['Id', 'Main Information', '', '', 'Date'],
]
const header = ['', 'Title', 'Author', 'Readings', '']
const filterVal = [
'id',
'title',
'author',
'pageViews',
'datetime',
]
const list = this.list
const data = this.formatJson(filterVal, list)
const merges = ['A1:A2', 'B1:D1', 'E1:E2']
excel.export_json_to_excel({
multiHeader,
header,
merges,
data,
})
this.downloadLoading = false
})
},
formatJson(filterVal, jsonData) {
return jsonData.map((v) =>
filterVal.map((j) => {
if (j === 'timestamp') {
return parseTime(v[j])
} else {
return v[j]
}
})
)
},
},
})
</script>

View File

@@ -0,0 +1,139 @@
<template>
<div class="select-excel-container">
<vab-query-form>
<vab-query-form-left-panel>
<el-form inline @submit.prevent>
<el-form-item>
<el-input
v-model="filename"
placeholder="请输出要导出文件的名称"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleDownload">
导出选中行
</el-button>
</el-form-item>
</el-form>
</vab-query-form-left-panel>
</vab-query-form>
<el-table
ref="multipleTableRef"
v-loading="listLoading"
border
:data="list"
@selection-change="handleSelectionChange"
>
<el-table-column align="center" type="selection" />
<el-table-column align="center" label="序号" width="55">
<template #default="{ $index }">
{{ $index + 1 }}
</template>
</el-table-column>
<el-table-column align="center" label="标题">
<template #default="{ row }">
{{ row.title }}
</template>
</el-table-column>
<el-table-column align="center" label="作者">
<template #default="{ row }">
<el-tag>{{ row.author }}</el-tag>
</template>
</el-table-column>
<el-table-column align="center" label="访问量" width="115">
<template #default="{ row }">
{{ row.pageViews }}
</template>
</el-table-column>
<el-table-column align="center" label="时间">
<template #default="{ row }">
<span>{{ row.datetime }}</span>
</template>
</el-table-column>
</el-table>
</div>
</template>
<script>
import { getList } from '@/api/table'
export default defineComponent({
name: 'ExportSelectExcel',
setup() {
const $baseMessage = inject('$baseMessage')
const state = reactive({
multipleTableRef: null,
list: [],
listLoading: true,
multipleSelection: [],
downloadLoading: false,
filename: '',
})
const fetchData = async () => {
state.listLoading = true
const {
data: { list },
} = await getList()
state.list = list
state.listLoading = false
}
const handleSelectionChange = (val) => {
state.multipleSelection = val
}
const handleDownload = () => {
if (state.multipleSelection.length > 0) {
state.downloadLoading = true
import('@/utils/excel').then((excel) => {
const tHeader = [
'Id',
'Title',
'Author',
'Readings',
'Date',
]
const filterVal = [
'id',
'title',
'author',
'pageViews',
'datetime',
]
const list = state.multipleSelection
const data = state.formatJson(filterVal, list)
excel.export_json_to_excel({
header: tHeader,
data,
filename: state.filename,
})
state.multipleTableRef.clearSelection()
state.downloadLoading = false
})
} else {
$baseMessage(
'请至少选择一行',
'error',
'vab-hey-message-error'
)
}
}
const formatJson = (filterVal, jsonData) => {
return jsonData.map((v) => filterVal.map((j) => v[j]))
}
onMounted(() => {
fetchData()
})
return {
...toRefs(state),
fetchData,
handleSelectionChange,
handleDownload,
formatJson,
}
},
})
</script>

View File

@@ -0,0 +1,97 @@
<template>
<div class="iframe-search-container">
<el-row :gutter="20">
<el-col
:lg="{ span: 12, offset: 6 }"
:md="{ span: 20, offset: 2 }"
:sm="{ span: 20, offset: 2 }"
:xl="{ span: 12, offset: 6 }"
:xs="24"
>
<el-form
ref="formRef"
label-position="top"
label-width="100px"
:model="form"
:rules="rules"
@submit.prevent
>
<el-form-item label="请输入跳转url" prop="url">
<el-input v-model="form.url" />
</el-form-item>
<el-form-item
label="请输入跳转后自动改名的Title"
prop="title"
>
<el-input v-model="form.title" />
</el-form-item>
<el-button
:icon="Search"
native-type="submit"
type="primary"
@click="handleClick"
>
查询
</el-button>
</el-form>
</el-col>
</el-row>
</div>
</template>
<script>
import { Search } from '@element-plus/icons-vue'
import { isExternal } from '@/utils/validate'
export default defineComponent({
name: 'IframeSearch',
setup() {
const router = useRouter()
const validateExternal = (rule, value, callback) => {
if (!isExternal(value)) callback(new Error('url输入错误'))
else callback()
}
const state = reactive({
formRef: null,
rules: {
url: [
{
required: true,
message: '请输入跳转url',
trigger: 'blur',
validator: validateExternal,
},
],
title: [
{
required: true,
message: '请输入跳转后自动改名的Title',
trigger: 'blur',
},
],
},
form: {
url: 'https://www.so.com/s?ie=utf-8&fr=none&src=home_suggst_revise&q=vue-admin-better&eci=&nlpv=test_zc_rank1',
title: '360搜索',
},
})
const handleClick = () => {
state['formRef'].validate((valid) => {
if (valid) {
router.push({
path: '/other/iframe/view',
query: state.form,
})
}
})
}
return {
...toRefs(state),
handleClick,
Search,
}
},
})
</script>

View File

@@ -0,0 +1,44 @@
<template>
<div class="iframe-container">
<iframe frameborder="0" :src="url" />
</div>
</template>
<script>
import { useTabsStore } from '@/store/modules/tabs'
export default defineComponent({
name: 'Iframe',
data() {
return { url: '' }
},
watch: {
$route: 'handleIframe',
},
created() {
this.handleIframe()
},
methods: {
...mapActions(useTabsStore, ['changeTabsMeta']),
handleIframe() {
this.url = `https://${this.$route.query.url}`
const meta = { ...this.$route.meta, ...this.$route.query }
this.$nextTick(() => {
this.changeTabsMeta({
title: 'Iframe',
meta,
})
})
},
},
})
</script>
<style lang="scss" scoped>
.iframe-container {
iframe {
width: 100%;
height: $base-keep-alive-height;
}
}
</style>

View File

@@ -0,0 +1,20 @@
<template>
<div class="menu1-1-1-1-container">
<el-alert :closable="false" title="多级路由 1-1-1-1" type="success">
<el-input v-model="value" />
</el-alert>
</div>
</template>
<script>
export default defineComponent({
name: 'Menu1111',
setup() {
const value = ref('')
return {
value,
}
},
})
</script>

View File

@@ -0,0 +1,37 @@
<script lang="ts"></script>
<script lang="ts" setup>
import { handleActivePath } from '@/utils/routes'
import { useTabsStore } from '@/store/modules/tabs'
import type { VabRoute } from '/#/router'
defineOptions({
name: 'NoLayout',
})
const tabsStore = useTabsStore()
const route = useRoute()
const { delVisitedRoute } = tabsStore
const router = useRouter()
const goBack = async () => {
await router.push({ path: '/index' })
await delVisitedRoute(handleActivePath(route as VabRoute, true))
}
</script>
<template>
<div class="no-layout-container">
<el-page-header content="无框" title="返回首页" @back="goBack" />
<el-alert :closable="false" title="无框示例" type="success" />
</div>
</template>
<style lang="scss" scoped>
.no-layout-container {
position: fixed;
inset: 0;
z-index: 9999;
}
</style>

View File

@@ -0,0 +1,277 @@
<template>
<div class="notice-container">
<el-row :gutter="20">
<el-col :lg="6" :md="12" :sm="24" :xl="6" :xs="24">
<vab-card shadow="never">
<template #header>
<span>成功提示</span>
</template>
<el-result
icon="success"
sub-title="请根据提示进行操作"
title="成功提示"
>
<template #extra>
<el-button type="primary">确认</el-button>
</template>
</el-result>
</vab-card>
</el-col>
<el-col :lg="6" :md="12" :sm="24" :xl="6" :xs="24">
<vab-card shadow="never">
<template #header>
<span>警告提示</span>
</template>
<el-result
icon="warning"
sub-title="请根据提示进行操作"
title="警告提示"
>
<template #extra>
<el-button type="primary">确认</el-button>
</template>
</el-result>
</vab-card>
</el-col>
<el-col :lg="6" :md="12" :sm="24" :xl="6" :xs="24">
<vab-card shadow="never">
<template #header>
<span>错误提示</span>
</template>
<el-result
icon="error"
sub-title="请根据提示进行操作"
title="错误提示"
>
<template #extra>
<el-button type="primary">确认</el-button>
</template>
</el-result>
</vab-card>
</el-col>
<el-col :lg="6" :md="12" :sm="24" :xl="6" :xs="24">
<vab-card shadow="never">
<template #header>
<span>信息提示</span>
</template>
<el-result
icon="info"
sub-title="请根据提示进行操作"
title="信息提示"
>
<template #extra>
<el-button type="primary">确认</el-button>
</template>
</el-result>
</vab-card>
</el-col>
<el-col :lg="6" :md="12" :sm="24" :xl="6" :xs="24">
<vab-card shadow="never">
<template #header>
<span>Alert 警告</span>
</template>
<el-alert show-icon title="成功提示的文案" type="success" />
<el-alert show-icon title="消息提示的文案" type="info" />
<el-alert show-icon title="警告提示的文案" type="warning" />
<el-alert show-icon title="错误提示的文案" type="error" />
</vab-card>
</el-col>
<el-col :lg="6" :md="12" :sm="24" :xl="6" :xs="24">
<vab-card shadow="never">
<template #header>
<span>element-plus风格 Message 消息提示</span>
</template>
<el-button plain type="primary" @click="open1">
消息
</el-button>
<el-button plain type="success" @click="open2">
成功
</el-button>
<el-button plain type="warning" @click="open3">
警告
</el-button>
<el-button plain type="danger" @click="open4">
错误
</el-button>
</vab-card>
<vab-card shadow="never">
<template #header>
<span>hey-ui风格 消息提示</span>
</template>
<el-button plain type="primary" @click="open5">
消息
</el-button>
<el-button plain type="success" @click="open6">
成功
</el-button>
<el-button plain type="warning" @click="open7">
警告
</el-button>
<el-button plain type="danger" @click="open8">
错误
</el-button>
</vab-card>
</el-col>
<el-col :lg="6" :md="12" :sm="24" :xl="6" :xs="24">
<vab-card shadow="never">
<template #header>
<span>Notification 消息提示</span>
</template>
<el-button plain type="info" @click="open9">消息</el-button>
<el-button plain type="success" @click="open10">
成功
</el-button>
<el-button plain type="warning" @click="open11">
警告
</el-button>
<el-button plain type="danger" @click="open12">
错误
</el-button>
</vab-card>
<vab-card shadow="never">
<template #header>
<span>Message Box消息提示</span>
</template>
<el-button plain type="primary" @click="open13">
消息提示
</el-button>
<el-button plain type="primary" @click="open14">
确认消息
</el-button>
</vab-card>
</el-col>
<el-col :lg="6" :md="12" :sm="24" :xl="6" :xs="24">
<vab-card shadow="never">
<template #header>
<span>更新提示</span>
</template>
<el-button plain @click="open15">更新提示</el-button>
</vab-card>
</el-col>
</el-row>
</div>
</template>
<script>
export default defineComponent({
name: 'Notice',
setup() {
const $pub = inject('$pub')
const $baseAlert = inject('$baseAlert')
const $baseNotify = inject('$baseNotify')
const $baseMessage = inject('$baseMessage')
const state = reactive({
vabUpdateRef: null,
data: [],
toggleIndex: 0,
})
const open1 = () => {
$baseMessage('这是一条消息提示', 'info')
}
const open2 = () => {
$baseMessage('恭喜你,这是一条成功消息', 'success')
}
const open3 = () => {
$baseMessage('警告哦,这是一条警告消息', 'warning')
}
const open4 = () => {
$baseMessage('错了哦,这是一条错误消息', 'error')
}
const open5 = () => {
$baseMessage('这是一条消息提示', 'info', 'vab-hey-message-info')
}
const open6 = () => {
$baseMessage(
'恭喜你,这是一条成功消息',
'success',
'vab-hey-message-success'
)
}
const open7 = () => {
$baseMessage(
'警告哦,这是一条警告消息',
'warning',
'vab-hey-message-warning'
)
}
const open8 = () => {
$baseMessage(
'错了哦,这是一条错误消息',
'error',
'vab-hey-message-error'
)
}
const open9 = () => {
$baseNotify(
'这是一条消息的提示消息',
'提示',
'info',
'bottom-right'
)
}
const open10 = () => {
$baseNotify(
'这是一条成功的提示消息',
'成功',
'success',
'bottom-right'
)
}
const open11 = () => {
$baseNotify(
'这是一条警告的提示消息',
'警告',
'warning',
'bottom-right'
)
}
const open12 = () => {
$baseNotify(
'这是一条错误的提示消息',
'错误',
'error',
'bottom-right'
)
}
const open13 = () => {
$baseAlert('这是一条消息提示')
}
const open14 = () => {
$baseAlert('这是一条确认消息')
}
const open15 = () => {
$pub('vab-update')
}
return {
...toRefs(state),
open1,
open2,
open3,
open4,
open5,
open6,
open7,
open8,
open9,
open10,
open11,
open12,
open13,
open14,
open15,
}
},
})
</script>
<style lang="scss" scoped>
$base: '.notice';
#{$base}-container {
padding: 0 !important;
background: $base-color-background !important;
}
</style>

View File

@@ -0,0 +1,87 @@
<template>
<div class="print-container">
<el-button type="primary" @click="print('imageRef')">
<vab-icon icon="printer-line" />
打印图片
</el-button>
<el-button type="primary" @click="print('tableRef')">
<vab-icon icon="printer-line" />
打印表格
</el-button>
<el-button type="primary" @click="remotePrint">
<vab-icon icon="printer-line" />
自定义打印
</el-button>
<img
ref="imageRef"
:src="
'https://gcore.jsdelivr.net/gh/' +
'chuzh' +
'ixin/image' +
'/table/vab-im' +
'age-1.jpg'
"
style="display: block; width: 520px; margin-top: 15px"
/>
<br />
<el-table ref="tableRef" :data="tableData" style="width: 520px">
<el-table-column label="姓名" prop="name" width="120px" />
<el-table-column label="地址" prop="address" />
</el-table>
</div>
</template>
<script>
import VabPrint from '@/plugins/VabPrint'
export default defineComponent({
name: 'Print',
setup() {
const state = reactive({
imageRef: null,
tableRef: null,
tableData: [
{
name: '马云',
address: '上海市普陀区金沙江路',
},
{
name: '马化腾',
address: '上海市普陀区金沙江路',
},
{
name: '李彦宏',
address: '上海市普陀区金沙江路',
},
{
name: '刘强东',
address: '上海市普陀区金沙江路',
},
],
})
const print = async (val) => {
await VabPrint(state[val])
}
const remotePrint = () => {
ElMessageBox.prompt('', '自定义打印', {
inputType: 'textarea',
inputValue: `<h1>Vue Admin Plus</h1>
<p>veujs-core.cn</p>`,
inputErrorMessage: 'Invalid Email',
confirmButtonText: '打印',
})
.then(({ value }) => {
VabPrint(value)
})
.catch(() => {})
}
return {
...toRefs(state),
print,
remotePrint,
}
},
})
</script>

View File

@@ -0,0 +1,96 @@
<template>
<div class="share-container">
<el-form inline :model="form" @submit.prevent>
<el-form-item label="URL">
<el-input v-model="form.url" />
</el-form-item>
<el-form-item>
<el-button
native-type="submit"
type="primary"
@click="copy($event)"
>
<vab-icon icon="file-copy-line" />
拷贝URL
</el-button>
<el-popover
placement="bottom-start"
title=""
trigger="hover"
:width="220"
>
<template #reference>
<el-button native-type="submit" type="primary">
<vab-icon icon="qr-code-line" />
生成二维码
</el-button>
</template>
<vab-qr-code
:dot-scale="0.5"
:logo-src="require('@/assets/logo.png')"
:text="form.url"
/>
</el-popover>
<!-- <el-popover
placement="bottom-start"
title=""
trigger="hover"
:width="220"
>
<template #reference>
<el-button native-type="submit" type="primary">
<vab-icon icon="qr-code-line" />
生成二维码
</el-button>
</template>
<vab-qr-code
:dot-scale="0.5"
:gif-bg-src="require('vue-qr/src/assets/dog.gif')"
:text="form.url"
/>
</el-popover> -->
</el-form-item>
</el-form>
</div>
</template>
<script>
import VabQrCode from '@/plugins/VabQrCode'
import clip from '@/utils/clipboard'
export default defineComponent({
name: 'Share',
components: {
VabQrCode,
},
setup() {
const state = reactive({
form: {
url: '',
},
})
const copy = (event) => {
clip(state.form.url, event)
}
onMounted(() => {
state.form.url = 'https://vuejs-core.cn/admin-plus'
})
return {
...toRefs(state),
copy,
}
},
})
</script>
<style lang="scss" scoped>
.share-container {
:deep() {
.el-input__inner {
width: 280px;
}
}
}
</style>

View File

@@ -0,0 +1,62 @@
<template>
<div class="social-container">
<el-button type="primary" @click="handleSocialLogin">
点击进行Github登录
</el-button>
<p v-if="data">{{ data }}</p>
</div>
</template>
<script>
import { useUserStore } from '@/store/modules/user'
import { login } from '@/utils/social'
export default defineComponent({
name: 'Social',
setup() {
const $baseLoading = inject('$baseLoading')
const $baseMessage = inject('$baseMessage')
const userStore = useUserStore()
const socialLogin = (data) => userStore.socialLogin(data)
const data = ref()
const handleSocialLogin = () => {
const loading = $baseLoading()
login(`https://github.com/login/oauth/authorize`, {
client_id:
process.env.NODE_ENV === 'development'
? 'bb8dfd34f6c6a57367e3'
: 'e104bdc488d009840c4f',
})
.then(async (_data) => {
data.value = _data
await socialLogin(_data)
// 登录页面使用,取消注释
// const routerPath =
// this.redirect === "/404" || this.redirect === "/403"
// ? "/"
// : this.redirect;
// this.$router.push(routerPath).catch(() => {});
})
.catch(() => {
$baseMessage(
'第三方登录失败,未返回令牌',
'error',
'vab-hey-message-error'
)
})
.finally(() => {
loading.close()
})
}
return {
data,
handleSocialLogin,
}
},
})
</script>

View File

@@ -0,0 +1,91 @@
<template>
<div class="tabs-container">
<el-button type="primary" @click="closeOthersTabs">关闭其他</el-button>
<el-button type="primary" @click="closeLeftTabs">关闭左侧</el-button>
<el-button type="primary" @click="closeRightTabs">关闭右侧</el-button>
<el-button type="primary" @click="closeAllTabs">关闭全部</el-button>
<el-button type="primary" @click="handleTabRemove(route.path)">
关闭当前
</el-button>
</div>
</template>
<script setup>
import { handleActivePath } from '@/utils/routes'
import { useTabsStore } from '@/store/modules/tabs'
const route = useRoute()
const router = useRouter()
const tabStore = useTabsStore()
const { getVisitedRoutes: visitedRoutes } = storeToRefs(tabStore)
const {
delVisitedRoute,
delOthersVisitedRoutes,
delLeftVisitedRoutes,
delRightVisitedRoutes,
delAllVisitedRoutes,
} = tabStore
const hoverRoute = ref(null)
/**
* 根据原生路径删除标签中的标签
* @param rawPath 原生路径
* @returns {Promise<void>}
*/
const handleTabRemove = async (rawPath) => {
if (isActive(rawPath)) await toLastTab()
await delVisitedRoute(rawPath)
}
/**
* 删除其他标签页
* @returns {Promise<void>}
*/
const closeOthersTabs = async () => {
if (hoverRoute.value) {
await router.push(hoverRoute.value)
await delOthersVisitedRoutes(hoverRoute.value.path)
} else await delOthersVisitedRoutes(handleActivePath(route, true))
}
/**
* 删除左侧标签页
* @returns {Promise<void>}
*/
const closeLeftTabs = async () => {
if (hoverRoute.value) {
await router.push(hoverRoute.value)
await delLeftVisitedRoutes(hoverRoute.value.path)
} else await delLeftVisitedRoutes(handleActivePath(route, true))
}
/**
* 删除右侧标签页
* @returns {Promise<void>}
*/
const closeRightTabs = async () => {
if (hoverRoute.value) {
await router.push(hoverRoute.value)
await delRightVisitedRoutes(hoverRoute.value.path)
} else await delRightVisitedRoutes(handleActivePath(route, true))
}
/**
* 删除所有标签页
* @returns {Promise<void>}
*/
const closeAllTabs = async () => {
await delAllVisitedRoutes()
await toLastTab()
}
/**
* 跳转最后一个标签页
*/
const toLastTab = async () => {
const latestView = visitedRoutes.value
.filter((_) => _.path !== handleActivePath(route, true))
.slice(-1)[0]
if (latestView) await router.push(latestView)
else await router.push('/')
}
const isActive = (path) => {
return path === handleActivePath(route, true)
}
</script>

View File

@@ -0,0 +1,209 @@
<template>
<div class="timeline-container">
<el-row :gutter="20">
<el-col :lg="8" :md="12" :sm="12" :xl="8" :xs="24">
<vab-card shadow="never">
<template #header>常规风格</template>
<el-timeline>
<el-timeline-item
v-for="(item, index) in activities"
:key="index"
:color="item.color"
:timestamp="item.timestamp"
>
<template v-if="!item.color" #dot>
<vab-icon v-if="item.icon" :icon="item.icon" />
<span
v-if="item.waver"
class="vab-dot"
:class="{
['vab-dot-' + item.waver]: true,
}"
>
<span></span>
</span>
</template>
<vab-card v-if="item.card" shadow="never">
{{ item.content }}
</vab-card>
<template v-else>
{{ item.content }}
</template>
</el-timeline-item>
</el-timeline>
</vab-card>
</el-col>
<el-col :lg="8" :md="12" :sm="12" :xl="8" :xs="24">
<vab-card shadow="never">
<template #header>卡片风格</template>
<el-timeline>
<el-timeline-item
v-for="(item, index) in activities"
:key="index"
:color="item.color"
:timestamp="item.timestamp"
>
<template v-if="!item.color" #dot>
<vab-icon v-if="item.icon" :icon="item.icon" />
<span
v-if="item.waver"
class="vab-dot"
:class="{
['vab-dot-' + item.waver]: true,
}"
>
<span></span>
</span>
</template>
<div
class="vab-info-card"
:class="{
['vab-info-card-' + item.cardType]: true,
}"
>
{{ item.content }}
</div>
</el-timeline-item>
</el-timeline>
</vab-card>
</el-col>
</el-row>
</div>
</template>
<script>
export default defineComponent({
name: 'Timeline',
setup() {
const state = reactive({
activities: [
{
content: '支持使用默认图标',
timestamp: '2021-04-12 20:46',
icon: 'account-circle-line',
cardType: 'warning',
},
{
content: '支持使用默认图标',
timestamp: '2021-04-18 20:46',
icon: 'archive-line',
cardType: 'error',
},
{
content: '支持自定义颜色',
timestamp: '2021-04-03 20:46',
color: '#13ce66',
cardType: 'success',
},
{
content: '支持默认颜色',
timestamp: '2021-04-03 20:46',
color: '#e4e7ed',
},
{
content: '支持success闪动',
timestamp: '2021-04-05 20:46',
waver: 'success',
},
{
content: '支持error闪动',
timestamp: '2021-04-05 20:46',
waver: 'error',
},
],
})
return {
...toRefs(state),
}
},
})
</script>
<style lang="scss" scoped>
.timeline-container {
padding: 0 !important;
background: $base-color-background !important;
:deep() {
.el-timeline-item__dot {
[class*='ri'] {
width: 12px;
height: 12px;
margin-left: -3px;
background: var(--el-color-white);
}
.vab-dot {
left: -1px;
width: 12px;
height: 12px;
margin: auto !important;
}
}
.el-card {
.el-card__header {
position: relative;
.card-header-radio {
position: absolute;
top: 20px;
right: $base-margin;
}
}
}
}
.vab-info-card {
position: relative;
width: 80%;
padding: $base-padding;
background: #e2e2e2;
border-radius: $base-border-radius + 2;
&::after {
position: absolute;
top: 8px;
left: -10px;
width: 0;
height: 0;
overflow: hidden;
content: '';
border-color: #e2e2e2 transparent transparent;
border-style: solid dashed dashed;
border-width: 10px;
}
&-success {
color: var(--el-color-white);
background: var(--el-color-success);
&::after {
border-color: var(--el-color-success) transparent
transparent;
}
}
&-error {
color: var(--el-color-white);
background: var(--el-color-error);
&::after {
border-color: var(--el-color-error) transparent transparent;
}
}
&-warning {
color: var(--el-color-white);
background: var(--el-color-warning);
&::after {
border-color: var(--el-color-warning) transparent
transparent;
}
}
}
}
</style>

View File

@@ -0,0 +1,34 @@
<template>
<div class="upload-container">
<vab-upload
ref="vabUploadRef"
:limit="50"
name="file"
:size="2"
url="/upload"
/>
<el-button type="primary" @click="handleShow()">模拟上传</el-button>
</div>
</template>
<script>
import VabUpload from '@/plugins/VabUpload'
export default defineComponent({
name: 'Upload',
components: {
VabUpload,
},
setup() {
const vabUploadRef = ref()
const handleShow = () => {
vabUploadRef.value.handleShow()
}
return {
vabUploadRef,
handleShow,
}
},
})
</script>

View File

@@ -0,0 +1,47 @@
<template>
<div class="watermark-container">
<el-button type="primary" @click="setWatermark(title)">
添加水印
</el-button>
<el-button type="primary" @click="setCustomWatermark">
添加自定义水印
</el-button>
<el-button type="primary" @click="setWatermark('')">移除水印</el-button>
</div>
</template>
<script lang="ts">
import Watermark from '@/utils/watermark'
import { useSettingsStore } from '@/store/modules/settings'
export default defineComponent({
name: 'Watermark',
setup() {
const settingsStore = useSettingsStore()
const { title } = storeToRefs(settingsStore)
const setWatermark = (value: string) => {
//@ts-ignore
Watermark.set(value)
}
const setCustomWatermark = () => {
ElMessageBox.prompt('请输入自定义水印', '温馨提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
})
.then(({ value }) => {
if (value) setWatermark(value)
})
.catch(() => {})
}
return {
title,
setWatermark,
setCustomWatermark,
}
},
})
</script>
<style lang="scss" scoped></style>

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

View File

@@ -0,0 +1,136 @@
<template>
<el-tabs tab-position="left">
<el-tab-pane label="添加动作">
<div v-for="item in nodeList" :key="item.type">
<el-button
class="add-node-btn"
type="primary"
@click="$_addNode(item)"
>
{{ item.label }}
</el-button>
</div>
</el-tab-pane>
<el-tab-pane label="添加组">
<el-button
class="add-node-btn"
type="primary"
@click="$_addTempalte"
>
模板
</el-button>
</el-tab-pane>
</el-tabs>
</template>
<script>
export default defineComponent({
name: 'AddPanel',
props: {
nodeData: {
type: Object,
default: () => {},
},
lf: {
type: Object || String,
default: () => {},
},
},
emits: ['addNodeFinish'],
data() {
return {
nodeList: [
{
type: 'user',
label: '用户',
},
{
type: 'push',
label: '推送',
},
],
}
},
methods: {
$_addNode(item) {
const { lf, nodeData } = this.$props
const { id, x, y } = nodeData
const nextNode = lf.addNode({
type: item.type,
x: x + 150,
y: y + 150,
})
const nextId = nextNode.id
lf.createEdge({ sourceNodeId: id, targetNodeId: nextId })
this.$emit('addNodeFinish')
},
$_addTempalte() {
const { lf, nodeData } = this.$props
const { id, x, y } = nodeData
const timeNode = lf.addNode({
type: 'download',
x,
y: y + 150,
})
const userNode = lf.addNode({
type: 'user',
x: x + 150,
y: y + 150,
})
const pushNode = lf.addNode({
type: 'push',
x: x + 150,
y: y + 300,
properties: {},
})
const endNode = lf.addNode({
type: 'end',
x: x + 300,
y: y + 150,
})
const endNode2 = lf.addNode({
type: 'end',
x: x + 300,
y: y + 300,
})
lf.createEdge({ sourceNodeId: id, targetNodeId: timeNode.id })
lf.createEdge({
sourceNodeId: timeNode.id,
targetNodeId: userNode.id,
})
lf.createEdge({
sourceNodeId: userNode.id,
targetNodeId: endNode.id,
endPoint: { x: x + 280, y: y + 150 },
text: {
value: 'Y',
x: x + 230,
y: y + 140,
},
})
lf.createEdge({
sourceNodeId: userNode.id,
targetNodeId: pushNode.id,
text: {
value: 'N',
x: x + 160,
y: y + 230,
},
})
lf.createEdge({
sourceNodeId: pushNode.id,
targetNodeId: endNode2.id,
endPoint: { x: x + 280, y: y + 300 },
})
this.$emit('addNodeFinish')
},
},
})
</script>
<style scoped>
.add-node-btn {
margin-right: 20px;
margin-bottom: 10px;
}
</style>

View File

@@ -0,0 +1,101 @@
<template>
<div>
<el-button-group>
<el-button plain size="small" @click="$_zoomIn">放大</el-button>
<el-button plain size="small" @click="$_zoomOut">缩小</el-button>
<el-button plain size="small" @click="$_zoomReset">
大小适应
</el-button>
<el-button plain size="small" @click="$_translateRest">
定位还原
</el-button>
<el-button plain size="small" @click="$_reset">
还原(大小&定位)
</el-button>
<el-button
:disabled="undoDisable"
plain
size="small"
@click="$_undo"
>
上一步(ctrl+z)
</el-button>
<el-button
:disabled="redoDisable"
plain
size="small"
@click="$_redo"
>
下一步(ctrl+y)
</el-button>
<el-button plain size="small" @click="$_download">
下载图片
</el-button>
<el-button plain size="small" @click="$_catData">
查看数据
</el-button>
</el-button-group>
</div>
</template>
<script>
export default defineComponent({
name: 'Control',
props: {
lf: {
type: Object || String,
default: () => {},
},
},
emits: ['cat-data'],
data() {
return {
undoDisable: true,
redoDisable: true,
graphData: null,
dataVisible: false,
}
},
mounted() {
this.$props.lf.on(
'history:change',
({ data: { undoAble, redoAble } }) => {
this.$data.undoDisable = !undoAble
this.$data.redoDisable = !redoAble
}
)
},
methods: {
$_zoomIn() {
this.$props.lf.zoom(true)
},
$_zoomOut() {
this.$props.lf.zoom(false)
},
$_zoomReset() {
this.$props.lf.resetZoom()
},
$_translateRest() {
this.$props.lf.resetTranslate()
},
$_reset() {
this.$props.lf.resetZoom()
this.$props.lf.resetTranslate()
},
$_undo() {
this.$props.lf.undo()
},
$_redo() {
this.$props.lf.redo()
},
$_download() {
this.$props.lf.getSnapshot()
},
$_catData() {
this.$emit('cat-data')
},
},
})
</script>
<style scoped></style>

View File

@@ -0,0 +1,36 @@
<template>
<div>
<vab-json-viewer copyable :expand-depth="5" sort :value="data" />
</div>
</template>
<script>
import VabJsonViewer from 'vue-json-viewer'
export default defineComponent({
components: { VabJsonViewer },
props: {
graphData: {
type: Object,
default: () => {},
},
},
data() {
return {
data: [],
}
},
created() {
this.data = JSON.parse(
JSON.stringify([
{
edges: this.graphData.edges,
nodes: this.graphData.nodes,
},
])
)
},
})
</script>
<style scoped></style>

View File

@@ -0,0 +1,146 @@
<template>
<div class="node-panel">
<div
v-for="item in nodeList"
:key="item.text"
class="node-item"
@mousedown="$_dragNode(item)"
>
<div class="node-item-icon" :class="item.class">
<div
v-if="item.type === 'user' || item.type === 'time'"
class="shape"
></div>
</div>
<span class="node-label">{{ item.text }}</span>
</div>
</div>
</template>
<script>
export default defineComponent({
name: 'NodePanel',
props: {
lf: {
type: Object,
default: () => {},
},
},
data() {
return {
nodeList: [
{
text: '开始',
type: 'start',
class: 'node-start',
},
{
text: '矩形',
type: 'rect',
class: 'node-rect',
},
{
type: 'user',
text: '用户',
class: 'node-user',
},
{
type: 'push',
text: '推送',
class: 'node-push',
},
{
type: 'download',
text: '位置',
class: 'node-download',
},
{
type: 'end',
text: '结束',
class: 'node-end',
},
],
}
},
methods: {
$_dragNode(item) {
this.$props.lf.dnd.startDrag({
type: item.type,
text: item.label,
})
},
},
})
</script>
<style lang="scss" scoped>
.node-panel {
position: absolute;
top: 100px;
left: $base-margin * 2;
z-index: 101;
width: 70px;
padding: 20px 10px;
text-align: center;
background-color: white;
border-radius: 6px;
box-shadow: 0 0 10px 1px rgb(228 224 219);
}
.node-item {
margin-bottom: 20px;
}
.node-item-icon {
display: block;
width: 30px;
height: 30px;
margin: auto;
background-size: cover;
}
.node-label {
font-size: 12px;
line-height: 30px;
user-select: none;
}
.node-start {
background: url('../background/start.png') no-repeat;
background-size: cover;
}
.node-rect {
border: 1px solid black;
}
.node-user {
background: url('../background/user.png') no-repeat;
background-size: cover;
}
.node-time {
background: url('../background/time.png') no-repeat;
background-size: cover;
}
.node-push {
background: url('../background/push.png') no-repeat;
background-size: cover;
}
.node-download {
background: url('../background/download.png') no-repeat;
background-size: cover;
}
.node-click {
background: url('../background/click.png') no-repeat;
background-size: cover;
}
.node-end {
background: url('../background/end.png') no-repeat;
background-size: cover;
}
</style>

View File

@@ -0,0 +1,63 @@
<template>
<div>
<el-form label-width="80px" :model="formData">
<el-form-item label="名称">
<el-input v-model="formData.name" />
</el-form-item>
<el-form-item label="活动区域">
<el-input v-model="formData.region" />
</el-form-item>
<el-form-item label="活动形式">
<el-input v-model="formData.type" />
</el-form-item>
<el-form-item>
<el-button type="primary" @click="onSubmit">保存</el-button>
</el-form-item>
</el-form>
</div>
</template>
<script>
export default defineComponent({
props: {
nodeData: {
type: Object,
default: () => {},
},
lf: {
type: Object || String,
default: () => {},
},
},
emits: ['onClose'],
data() {
return {
formData: {
name: '',
region: '',
type: '',
},
}
},
mounted() {
const { properties } = this.$props.nodeData
if (properties) {
this.$data.formData = Object.assign(
{},
this.$data.formData,
properties
)
}
},
methods: {
onSubmit() {
console.log('submit!')
const { id } = this.$props.nodeData
this.$props.lf.setProperties(id, this.$data.formData)
this.$emit('onClose')
},
},
})
</script>
<style scoped></style>

View File

@@ -0,0 +1,54 @@
<template>
<div class="property-dialog">
<user
v-if="nodeData.type === 'user'"
:lf="lf"
:node-data="nodeData"
@on-close="handleClose"
/>
<common-property
v-else
:lf="lf"
:node-data="nodeData"
@on-close="handleClose"
/>
</div>
</template>
<script>
import CommonProperty from './CommonProperty'
import User from './User.vue'
export default defineComponent({
name: 'PropertyDialog',
components: {
CommonProperty,
User,
},
props: {
nodeData: {
type: Object,
default: () => {},
},
lf: {
type: Object,
default: () => {},
},
},
emits: ['setPropertiesFinish'],
data() {
return {}
},
methods: {
handleClose() {
this.$emit('setPropertiesFinish')
},
},
})
</script>
<style>
.property-dialog {
padding: 20px;
}
</style>

View File

@@ -0,0 +1,118 @@
<template>
<div>
<el-form ref="form" label-width="80px" :model="form">
<el-form-item label="活动名称">
<el-input v-model="form.name" />
</el-form-item>
<el-form-item label="活动区域">
<el-select v-model="form.region" placeholder="请选择活动区域">
<el-option label="区域一" value="shanghai" />
<el-option label="区域二" value="beijing" />
</el-select>
</el-form-item>
<el-form-item label="活动时间">
<el-col :span="11">
<el-date-picker
v-model="form.date1"
placeholder="选择日期"
style="width: 100%"
type="date"
/>
</el-col>
<el-col class="line" :span="2">-</el-col>
<el-col :span="11">
<el-time-picker
v-model="form.date2"
placeholder="选择时间"
style="width: 100%"
/>
</el-col>
</el-form-item>
<el-form-item label="即时配送">
<el-switch v-model="form.delivery" />
</el-form-item>
<el-form-item label="活动性质">
<el-checkbox-group v-model="form.type">
<el-checkbox
label="美食/餐厅线上活动"
name="type"
value="美食/餐厅线上活动"
/>
<el-checkbox
label="地推活动"
name="type"
value="地推活动"
/>
<el-checkbox
label="线下主题活动"
name="type"
value="线下主题活动"
/>
<el-checkbox
label="单纯品牌曝光"
name="type"
value="单纯品牌曝光"
/>
</el-checkbox-group>
</el-form-item>
<el-form-item label="特殊资源">
<el-radio-group v-model="form.resource">
<el-radio label="线上品牌商赞助" value="线上品牌商赞助" />
<el-radio label="线下场地免费" value="线下场地免费" />
</el-radio-group>
</el-form-item>
<el-form-item label="活动形式">
<el-input v-model="form.desc" type="textarea" />
</el-form-item>
<el-form-item>
<el-button type="primary" @click="onSubmit">保存</el-button>
</el-form-item>
</el-form>
</div>
</template>
<script>
export default defineComponent({
props: {
nodeData: {
type: Object,
default: () => {},
},
lf: {
type: Object || String,
default: () => {},
},
},
emits: ['onClose'],
data() {
return {
form: {
name: '',
region: '',
date1: '',
date2: '',
delivery: false,
type: [],
resource: '',
desc: '',
},
}
},
mounted() {
const { properties } = this.$props.nodeData
if (properties) {
this.$data.form = Object.assign({}, this.$data.form, properties)
}
},
methods: {
onSubmit() {
console.log('submit!')
const { id } = this.$props.nodeData
this.$props.lf.setProperties(id, this.$data.form)
this.$emit('onClose')
},
},
})
</script>
<style scoped></style>

View File

@@ -0,0 +1,10 @@
// svg png 图片资源来自阿里字体库
// [阿里字体库](https://www.iconfont.cn/collections/index?spm=a313x.7781069.1998910419.4)
// svg图标建议使用自己创建的
export { default as registerStart } from './registerStart'
export { default as registerUser } from './registerUser'
export { default as registerPush } from './registerPush'
export { default as registerEnd } from './registerEnd'
export { default as registerPolyline } from './registerPolyline'
export { default as registerDownload } from './registerDownload'

View File

@@ -0,0 +1,84 @@
const NODE_COLOR = '#9932CC'
export default function registerDownload(lf) {
lf.register('download', ({ PolygonNode, PolygonNodeModel, h }) => {
class Node extends PolygonNode {
getIconShape() {
return h(
'svg',
{
x: 14,
y: 13,
width: 23,
height: 23,
viewBox: '0 0 1024 1024',
},
h('path', {
fill: NODE_COLOR,
d: 'M831.513034 319.863005h-95.945189c-17.662265 0-31.980365 14.3181-31.980365 31.980365 0 17.662265 14.3181 31.980365 31.980365 31.980366h64.218604c17.520025 0 31.722492 14.202467 31.722492 31.722492V863.786065c0 17.520025-14.202467 31.722492-31.722492 31.722492H159.66442c-17.520025 0-31.722492-14.202467-31.722493-31.722492V415.546228c0-17.520025 14.202467-31.722492 31.722493-31.722492h64.218603c17.662265 0 31.980365-14.3181 31.980366-31.980366 0-17.662265-14.3181-31.980365-31.980366-31.980365H127.937834c-35.322483 0-63.956637 28.634154-63.956637 63.956637v511.693008c0 35.322483 28.634154 63.956637 63.956637 63.956638h703.5752c35.322483 0 63.956637-28.634154 63.956638-63.956638V383.819642c0-35.32146-28.634154-63.956637-63.956638-63.956637z',
}),
h('path', {
fill: NODE_COLOR,
d: 'M310.382073 521.036817c-12.388145-12.388145-32.473599-12.388145-44.862767 0l-0.364297 0.364297c-12.388145 12.388145-12.388145 32.473599 0 44.862767l190.186573 190.186574c5.818519 6.813173 14.465456 11.137665 24.126491 11.137664h0.515746c9.662057 0 18.307971-4.324492 24.12649-11.137664L694.296883 566.263881c12.388145-12.388145 12.388145-32.473599 0-44.862767l-0.364297-0.364297c-12.388145-12.388145-32.473599-12.388145-44.862767 0L511.706311 658.400325V95.743598c0-17.520025-14.202467-31.722492-31.722492-31.722492h-0.515746c-17.520025 0-31.722492 14.202467-31.722493 31.722492v562.656727L310.382073 521.036817z',
})
)
}
getShape() {
const { model } = this.props
const {
width,
height,
x,
y,
fillOpacity,
strokeOpacity,
points,
} = model
const style = model.getNodeStyle()
const transform = `matrix(1 0 0 1 ${x - width / 2} ${y - height / 2})`
const pointsPath = points
.map((point) => point.join(','))
.join(' ')
return h(
'g',
{
transform,
},
[
h('polygon', {
...style,
points: pointsPath,
strokeOpacity,
fillOpacity,
}),
this.getIconShape(),
]
)
}
}
class Model extends PolygonNodeModel {
constructor(data, graphModel) {
data.text = {
value: (data.text && data.text.value) || '',
x: data.x,
y: data.y + 50,
}
super(data, graphModel)
const lenght = 25
this.points = [
[lenght, 0],
[lenght * 2, lenght],
[lenght, lenght * 2],
[0, lenght],
]
this.stroke = NODE_COLOR
}
}
return {
view: Node,
model: Model,
}
})
}

View File

@@ -0,0 +1,76 @@
export default function registerEnd(lf) {
lf.register('end', ({ CircleNode, CircleNodeModel, h }) => {
class EndNode extends CircleNode {
getIconShape() {
const { x, y, width, height } = this.props.model
const stroke = '#404040'
return h(
'svg',
{
x: x - width / 4,
y: y - height / 4,
width: width / 2,
height: width / 2,
viewBox: '0 0 1024 1024',
},
h('path', {
fill: stroke,
d: 'M212.992 526.336 212.992 526.336 212.992 526.336 215.04 526.336 212.992 526.336Z',
}),
// 圆形外框隐藏
// h(
// 'path',
// {
// fill: stroke,
// d: 'M817.152 202.752 817.152 202.752C737.28 122.88 628.736 75.776 509.952 75.776c-118.784 0-229.376 49.152-307.2 126.976l0 0c-77.824 77.824-126.976 186.368-126.976 307.2 0 118.784 49.152 229.376 126.976 307.2 77.824 79.872 188.416 126.976 307.2 126.976 120.832 0 229.376-49.152 307.2-126.976 79.872-77.824 126.976-186.368 126.976-307.2C946.176 389.12 897.024 280.576 817.152 202.752zM770.048 770.048c-65.536 65.536-157.696 108.544-260.096 108.544-102.4 0-194.56-40.96-260.096-108.544C184.32 704.512 141.312 612.352 141.312 509.952s40.96-194.56 108.544-260.096C317.44 184.32 409.6 141.312 509.952 141.312c100.352 0 192.512 40.96 258.048 106.496l2.048 2.048c65.536 65.536 108.544 157.696 108.544 260.096S837.632 704.512 770.048 770.048z'
// }
// ),
h('path', {
fill: stroke,
d: 'M724.992 296.96 724.992 296.96 296.96 296.96 296.96 724.992 724.992 724.992 724.992 296.96Z',
})
)
}
getShape() {
const { model } = this.props
const { x, y } = model
const style = model.getNodeStyle()
return h('g', {}, [
h('circle', {
...style,
cx: x,
cy: y,
}),
this.getIconShape(),
])
}
}
class EndModel extends CircleNodeModel {
constructor(data, graphModel) {
data.text = {
value: (data.text && data.text.value) || '',
x: data.x,
y: data.y + 35,
}
super(data, graphModel)
}
getConnectedSourceRules() {
const rules = super.getConnectedSourceRules()
const notAsTarget = {
message: '终止节点不能作为连线的起点',
validate: () => false,
}
rules.push(notAsTarget)
return rules
}
}
return {
view: EndNode,
model: EndModel,
}
})
}

View File

@@ -0,0 +1,14 @@
export default function registerPolyline(lf) {
lf.register('polyline', ({ PolylineEdge, PolylineEdgeModel }) => {
class ConnnectionModel extends PolylineEdgeModel {
constructor(data, graphModel) {
super(data, graphModel)
}
}
return {
view: PolylineEdge,
model: ConnnectionModel,
}
})
}

View File

@@ -0,0 +1,120 @@
export default function registerPush(lf, clickPlus, mouseDownPlus) {
lf.register('push', ({ PolygonNode, PolygonNodeModel, h }) => {
class Node extends PolygonNode {
getIconShape() {
const { model } = this.props
const { stroke } = model
return h(
'svg',
{
x: 18,
y: 18,
width: 30,
height: 30,
viewBox: '0 0 1024 1024',
},
h('path', {
fill: stroke,
d: 'M866.461538 39.384615H393.846154c-43.323077 0-78.769231 35.446154-78.769231 78.769231v1.969231c0 13.784615 7.876923 27.569231 19.692308 35.446154 5.907692 3.938462 80.738462 78.769231 80.738461 78.769231 5.907692 5.907692 15.753846 0 15.753846-7.876924 0-15.753846 13.784615-31.507692 29.538462-31.507692h334.769231c15.753846 0 31.507692 15.753846 31.507692 31.507692v531.692308c0 15.753846-15.753846 27.569231-31.507692 27.569231h-334.769231c-15.753846 0-27.569231-11.815385-27.569231-27.569231v-1.969231c0-7.876923-9.846154-11.815385-15.753846-5.907692 0 0-74.830769 74.830769-82.707692 78.769231-11.815385 7.876923-19.692308 19.692308-19.692308 35.446154v39.384615c0 43.323077 33.476923 78.769231 76.8 78.769231h472.615385c43.323077 0 80.738462-35.446154 80.738461-78.769231V118.153846c0-43.323077-35.446154-78.769231-78.769231-78.769231zM630.153846 945.230769c-33.476923 0-59.076923-25.6-59.076923-59.076923s25.6-59.076923 59.076923-59.076923 59.076923 25.6 59.076923 59.076923-25.6 59.076923-59.076923 59.076923z m-86.646154-474.584615L297.353846 224.492308c-11.815385-11.815385-29.538462-11.815385-41.353846 0l-41.353846 41.353846c-11.815385 11.815385-11.815385 29.538462 0 41.353846l90.584615 90.584615c11.815385 11.815385 3.938462 33.476923-13.784615 33.476923H29.538462c-15.753846 1.969231-29.538462 15.753846-29.538462 31.507693v59.076923c0 15.753846 13.784615 29.538462 29.538462 29.538461h259.938461c17.723077 0 25.6 21.661538 13.784615 33.476923l-90.584615 90.584616c-11.815385 11.815385-11.815385 29.538462 0 41.353846l41.353846 41.353846c11.815385 11.815385 29.538462 11.815385 41.353846 0L543.507692 512c9.846154-9.846154 9.846154-29.538462 0-41.353846z',
})
)
}
getPlusShape() {
const { model } = this.props
// 判断当前节点是否子节点
const graphData = lf.getGraphData()
const edges = graphData.edges
const hasChildNode = edges.some(
(_) => _.sourceNodeId === model.id
)
if (hasChildNode) {
return false
}
return h(
'svg',
{
x: 70,
y: 20,
width: 30,
height: 30,
viewBox: '0 0 1024 1024',
class: 'time-plus',
onClick: (e) => clickPlus(e, model),
onMousedown: (e) => mouseDownPlus(e, model),
onMouseUp: (e) => mouseDownPlus(e, model),
},
h('path', {
fill: '#f17611',
d: 'M512 512m-448 0a448 448 0 1 0 896 0 448 448 0 1 0-896 0Z',
}),
h('path', {
fill: '#ffffff',
d: 'M448 298.666667h128v426.666666h-128z',
}),
h('path', {
fill: '#ffffff',
d: 'M298.666667 448h426.666666v128H298.666667z',
})
)
}
getShape() {
const { model } = this.props
const {
width,
height,
x,
y,
fillOpacity,
strokeOpacity,
points,
} = model
const style = model.getNodeStyle()
const transform = `matrix(1 0 0 1 ${x - width / 2} ${y - height / 2})`
const pointsPath = points
.map((point) => point.join(','))
.join(' ')
return h(
'g',
{
transform,
},
[
h('polygon', {
...style,
points: pointsPath,
strokeOpacity,
fillOpacity,
}),
this.getIconShape(),
this.getPlusShape(),
]
)
}
}
class Model extends PolygonNodeModel {
constructor(data, graphModel) {
data.text = {
value: (data.text && data.text.value) || '',
x: data.x,
y: data.y + 50,
}
super(data, graphModel)
const lenght = 35
this.points = [
[lenght, 0],
[lenght * 2, lenght],
[lenght, lenght * 2],
[0, lenght],
]
}
}
return {
view: Node,
model: Model,
}
})
}

View File

@@ -0,0 +1,63 @@
export default function registerStart(lf) {
lf.register('start', ({ CircleNode, CircleNodeModel, h }) => {
class StartNode extends CircleNode {
getLabelShape() {
const { x, y } = this.props.model
return h(
'text',
{
fill: '#000000',
fontSize: 12,
x: x - 13,
y: y + 4,
width: 50,
height: 25,
},
'Start'
)
}
getShape() {
const { model } = this.props
const { x, y } = model
const style = model.getNodeStyle()
return h('g', {}, [
h('circle', {
...style,
cx: x,
cy: y,
}),
this.getLabelShape(),
])
}
}
class StartModel extends CircleNodeModel {
constructor(data, graphModel) {
data.text = {
value: (data.text && data.text.value) || '',
x: data.x,
y: data.y + 35,
dragable: false,
editable: true,
}
super(data, graphModel)
}
getConnectedTargetRules() {
const rules = super.getConnectedTargetRules()
const notAsTarget = {
message: '起始节点不能作为连线的终点',
validate: () => false,
}
rules.push(notAsTarget)
return rules
}
}
return {
view: StartNode,
model: StartModel,
}
})
}

View File

@@ -0,0 +1,105 @@
export default function registerUser(lf) {
lf.register('user', ({ PolygonNode, PolygonNodeModel, h }) => {
class Node extends PolygonNode {
getIconShape() {
const { stroke } = this.props.model
return h(
'svg',
{
x: 20,
y: 18,
width: 30,
height: 30,
viewBox: '0 0 1126 1024',
},
h('path', {
fill: stroke,
d: 'M792.576 379.392a25.6 25.6 0 0 0 25.2928 25.8048h283.2384A25.6 25.6 0 0 0 1126.4 379.392a25.6 25.6 0 0 0-25.2928-25.8048h-283.2384a25.6 25.6 0 0 0-25.344 25.8048z m303.9232 80.7424H761.856c-16.5376 0-29.9008 11.6224-29.9008 25.7536 0 14.1824 13.312 25.7536 29.9008 25.7536h334.6432c16.4864 0 29.9008-11.5712 29.9008-25.7536 0-14.1312-13.4144-25.7536-29.9008-25.7536z m4.608 106.496h-283.2384a25.6 25.6 0 0 0-25.344 25.7536 25.6 25.6 0 0 0 25.344 25.7536h283.2384A25.6 25.6 0 0 0 1126.4 592.384a25.6 25.6 0 0 0-25.2928-25.8048zM543.0272 1024H341.6576C150.8352 1024 0 1024 0 923.648v-20.1216c0-188.16 153.2928-341.1968 341.7088-341.1968h201.2672c188.416 0 341.76 153.0368 341.76 341.1968v20.0704C884.6848 1024 726.3232 1024 542.976 1024z m-203.1616-405.1456c-158.464 0-287.4368 128.4096-287.4368 286.208v20.48c0 40.9088 166.0928 40.9088 287.4368 40.9088h204.9536c100.4544 0 287.4368 0 287.4368-40.96v-20.3776c0-157.8496-128.9728-286.208-287.4368-286.208H339.8656z m92.416-76.7488a271.4112 271.4112 0 0 1-271.2064-271.0528A271.36 271.36 0 0 1 432.2816 0a271.36 271.36 0 0 1 271.2064 271.0528 271.4624 271.4624 0 0 1-271.2064 271.0528z m-215.3472-271.872c0 118.1696 96.6144 214.3232 215.3472 214.3232 118.784 0 215.3984-96.1536 215.3984-214.3232 0-118.2208-96.6144-214.3232-215.3984-214.3232S216.9344 152.0128 216.9344 270.2336z',
})
)
}
getShape() {
const { model } = this.props
const {
width,
height,
x,
y,
fillOpacity,
strokeOpacity,
points,
} = model
const style = model.getNodeStyle()
const transform = `matrix(1 0 0 1 ${x - width / 2} ${y - height / 2})`
const pointsPath = points
.map((point) => point.join(','))
.join(' ')
return h(
'g',
{
transform,
},
[
h('polygon', {
...style,
points: pointsPath,
strokeOpacity,
fillOpacity,
}),
this.getIconShape(),
]
)
}
}
class Model extends PolygonNodeModel {
constructor(data, graphModel) {
data.text = {
value: (data.text && data.text.value) || '',
x: data.x,
y: data.y + 50,
}
super(data, graphModel)
const lenght = 35
this.points = [
[lenght, 0],
[lenght * 2, lenght],
[lenght, lenght * 2],
[0, lenght],
]
// 右键菜单自由配置也可以通过边的properties或者其他属性条件更换不同菜单
this.menu = [
{
text: '删除',
className: 'lf-menu-delete',
icon: true,
callback(node) {
const comfirm = window.confirm('你确定要删除吗?')
comfirm && lf.deleteNode(node.id)
},
},
{
text: '编辑',
className: 'lf-menu-item',
callback(node) {
lf.editText(node.id)
},
},
{
text: '复制',
className: 'lf-menu-item',
callback(node) {
lf.cloneNode(node.id)
},
},
]
}
}
return {
view: Node,
model: Model,
}
})
}

View File

@@ -0,0 +1,303 @@
<template>
<div class="workflow-container">
<!-- 辅助工具栏 -->
<control v-if="lf" class="vab-control" :lf="lf" @cat-data="catData" />
<!-- 节点面板 -->
<node-panel :lf="lf" />
<!-- 画布 -->
<div id="container" ref="container"></div>
<!-- 用户节点自定义操作面板 -->
<add-panel
v-if="showAddPanel"
class="add-panel"
:lf="lf"
:node-data="addClickNode"
:style="addPanelStyle"
@add-node-finish="hideAddPanel"
/>
<!-- 属性面板 -->
<el-drawer
v-model="dialogVisible"
:before-close="closeDialog"
direction="rtl"
size="400px"
title="设置节点属性"
>
<property-dialog
v-if="dialogVisible"
:lf="lf"
:node-data="clickNode"
@set-properties-finish="closeDialog"
/>
</el-drawer>
<!-- 数据查看面板 -->
<el-dialog v-model="dataVisible" title="数据" width="50%">
<data-dialog :graph-data="graphData" />
</el-dialog>
</div>
</template>
<script>
import { getList } from '@/api/workflow'
import LogicFlow from '@logicflow/core'
import '@logicflow/core/lib/style/index.css'
import { Menu, Snapshot } from '@logicflow/extension'
import '@logicflow/extension/lib/style/index.css'
import NodePanel from './components/lFComponents/NodePanel'
import AddPanel from './components/lFComponents/AddPanel'
import Control from './components/lFComponents/Control'
import PropertyDialog from './components/propertySetting/PropertyDialog'
import DataDialog from './components/lFComponents/DataDialog'
import {
registerDownload,
registerEnd,
registerPolyline,
registerPush,
registerStart,
registerUser,
} from './components/registerNode'
export default defineComponent({
name: 'Workflow',
components: {
NodePanel,
AddPanel,
Control,
PropertyDialog,
DataDialog,
},
data() {
return {
data: [],
lf: null,
showAddPanel: false,
addPanelStyle: {
top: 0,
left: 0,
},
addClickNode: null,
clickNode: null,
dialogVisible: false,
graphData: null,
dataVisible: false,
}
},
created() {
this.fetchData()
},
methods: {
async fetchData() {
const { data } = await getList()
this.data = data
this.$_initLf()
},
$_initLf() {
const _this = this
// 画布配置
const config = {
container: this.$refs.container,
background: {
backgroundColor: '#f7f9ff',
},
grid: {
size: 10,
visible: false,
},
keyboard: {
enabled: true,
},
edgeTextDraggable: true,
guards: {
beforeClone(data) {
console.log('beforeClone', data)
return true
},
beforeDelete(data) {
// 可以根据data数据判断是否允许删除允许返回true,不允许返回false
// 文档: http://logic-flow.org/guide/basic/keyboard.html#%E5%A6%82%E4%BD%95%E9%98%BB%E6%AD%A2%E5%88%A0%E9%99%A4%E6%88%96%E8%80%85%E6%8B%B7%E8%B4%9D%E8%A1%8C%E4%B8%BA
console.log('beforeDelete', data)
// _this.$message('不允许删除', 'error')
return true
},
},
}
// 使用插件
LogicFlow.use(Menu)
LogicFlow.use(Snapshot)
// 渲染画布
this.lf = new LogicFlow({ ...config })
// 菜单配置文档http://logic-flow.org/guide/extension/extension-components.html#%E8%8F%9C%E5%8D%95
// 重置,增加,节点自由配置(以user节点为示例)
this.lf.setMenuConfig({
nodeMenu: [],
edgeMenu: [],
})
this.lf.addMenuConfig({
nodeMenu: [
{
text: '分享',
callback() {
_this.$baseAlert('分享成功!')
},
},
{
text: '属性',
callback(node) {
_this.$baseAlert(`
节点id${node.id}
节点类型:${node.type}
节点坐标:(x: ${node.x}, y: ${node.y})`)
},
},
],
edgeMenu: [
{
text: '属性',
callback(edge) {
_this.$baseAlert(`
边id${edge.id}
边类型:${edge.type}
边坐标:(x: ${edge.x}, y: ${edge.y})
源节点id${edge.sourceNodeId}
目标节点id${edge.targetNodeId}`)
},
},
],
})
// 设置主题
this.lf.setTheme({
circle: {
r: 20,
stroke: '#000000',
outlineColor: '#88f',
strokeWidth: 1,
},
rect: {
outlineColor: '#88f',
strokeWidth: 1,
},
polygon: {
strokeWidth: 1,
},
polyline: {
stroke: '#000000',
hoverStroke: '#000000',
selectedStroke: '#000000',
outlineColor: '#88f',
strokeWidth: 1,
},
nodeText: {
color: '#000000',
},
edgeText: {
color: '#000000',
background: {
fill: '#f7f9ff',
},
},
})
this.registerNode()
},
// 自定义
registerNode() {
registerStart(this.lf)
registerUser(this.lf)
registerEnd(this.lf)
registerPush(this.lf, this.clickPlus, this.mouseDownPlus)
registerDownload(this.lf)
registerPolyline(this.lf)
this.render()
},
render() {
this.lf.render(this.data)
this.event()
},
getData() {
const data = this.lf.getGraphData()
console.log(JSON.stringify(data))
},
event() {
this.lf.on('node:click', ({ data }) => {
console.log('node:click', data)
this.clickNode = data
this.dialogVisible = true
})
this.lf.on('edge:click', ({ data }) => {
console.log('edge:click', data)
this.clickNode = data
this.dialogVisible = true
})
this.lf.on('element:click', () => {
this.hideAddPanel()
})
this.lf.on('blank:click', () => {
this.hideAddPanel()
})
this.lf.on('connection:not-allowed', (data) => {
this.$message({
type: 'error',
message: data.msg,
})
})
this.lf.on('node:mousemove', () => {
console.log('on mousemove')
})
},
clickPlus(e, attributes) {
e.stopPropagation()
console.log('clickPlus', e, attributes)
const { clientX, clientY } = e
console.log(clientX, clientY)
this.addPanelStyle.top = `${clientY - 40}px`
this.addPanelStyle.left = `${clientX}px`
this.showAddPanel = true
this.addClickNode = attributes
},
mouseDownPlus(e, attributes) {
e.stopPropagation()
console.log('mouseDownPlus', e, attributes)
},
hideAddPanel() {
this.showAddPanel = false
this.addPanelStyle.top = 0
this.addPanelStyle.left = 0
this.addClickNode = null
},
closeDialog() {
this.dialogVisible = false
},
catData() {
this.graphData = this.lf.getGraphData()
this.dataVisible = true
},
},
})
</script>
<style lang="scss" scoped>
.workflow-container {
position: relative;
.vab-control {
position: absolute;
top: $base-margin * 2;
left: $base-margin * 2;
z-index: 2;
}
#container {
height: calc(#{$base-keep-alive-height} - #{$base-padding} * 2);
outline: none;
}
.time-plus {
cursor: pointer;
}
.add-panel {
position: absolute;
z-index: 11;
}
}
</style>