mirror of
https://gitee.com/log4j/pig-ui.git
synced 2025-01-03 23:42:23 +08:00
'admin-21.09.25:更新修复诸多内容,请查看CHANGELOG.md'
This commit is contained in:
parent
cea507e688
commit
fe70746902
10
CHANGELOG.md
10
CHANGELOG.md
@ -2,6 +2,16 @@
|
|||||||
|
|
||||||
🎉🎉🔥 `vue-next-admin` 基于 vue3.x 、Typescript、vite、Element plus 等,适配手机、平板、pc 的后台开源免费模板库(vue2.x 请切换 vue-prev-admin 分支)
|
🎉🎉🔥 `vue-next-admin` 基于 vue3.x 、Typescript、vite、Element plus 等,适配手机、平板、pc 的后台开源免费模板库(vue2.x 请切换 vue-prev-admin 分支)
|
||||||
|
|
||||||
|
## 1.1.1
|
||||||
|
|
||||||
|
`2021.09.25`
|
||||||
|
|
||||||
|
- 🌟 更新 依赖更新最新版本(`"element-plus": "^1.1.0-beta.13"` 版本运行错误,`^1.1.0-beta.16`修复横向菜单卡死问题)
|
||||||
|
- 🐞 修复 Dialog 弹窗位置错误、Drawer 抽屉内边距、el-menu 菜单收起时背景色问题
|
||||||
|
- 🎯 优化 锁屏界面自动锁屏(s/秒)必须设置至少 1 秒
|
||||||
|
- 🎉 新增 分栏布局,鼠标移入当前项时,显示当前项菜单内容
|
||||||
|
- 🎉 新增 工作流(未完成)
|
||||||
|
|
||||||
## 1.1.0
|
## 1.1.0
|
||||||
|
|
||||||
`2021.09.10`
|
`2021.09.10`
|
||||||
|
@ -42,9 +42,11 @@
|
|||||||
#### 🏭 环境支持
|
#### 🏭 环境支持
|
||||||
|
|
||||||
| Edge | last 2 versions | last 2 versions | last 2 versions |
|
| Edge | last 2 versions | last 2 versions | last 2 versions |
|
||||||
| ---- | ---- | ---- | ---- |
|
| ------------------------------------------------------------------------ | --------------------------------------------------------------------------------- | ------------------------------------------------------------------------------ | ------------------------------------------------------------------------------ |
|
||||||
| ![Edge](https://cdn.jsdelivr.net/npm/@browser-logos/edge/edge_32x32.png) | ![Firefox](https://cdn.jsdelivr.net/npm/@browser-logos/firefox/firefox_32x32.png) | ![Chrome](https://cdn.jsdelivr.net/npm/@browser-logos/chrome/chrome_32x32.png) | ![Safari](https://cdn.jsdelivr.net/npm/@browser-logos/safari/safari_32x32.png) |
|
| ![Edge](https://cdn.jsdelivr.net/npm/@browser-logos/edge/edge_32x32.png) | ![Firefox](https://cdn.jsdelivr.net/npm/@browser-logos/firefox/firefox_32x32.png) | ![Chrome](https://cdn.jsdelivr.net/npm/@browser-logos/chrome/chrome_32x32.png) | ![Safari](https://cdn.jsdelivr.net/npm/@browser-logos/safari/safari_32x32.png) |
|
||||||
|
|
||||||
|
> 由于 Vue3 不再支持 IE11,故而 ElementPlus 也不支持 IE11 及之前版本。
|
||||||
|
|
||||||
#### ⚡ 使用说明
|
#### ⚡ 使用说明
|
||||||
|
|
||||||
建议使用 cnpm,因为 yarn 有时会报错。<a href="http://nodejs.cn/" target="_blank">node 版本 > 12xx.xx.x</a>
|
建议使用 cnpm,因为 yarn 有时会报错。<a href="http://nodejs.cn/" target="_blank">node 版本 > 12xx.xx.x</a>
|
||||||
@ -117,6 +119,7 @@ cnpm run build
|
|||||||
- <a href="https://github.com/jbaysolutions/vue-grid-layout" target="_blank">vue-grid-layout</a>
|
- <a href="https://github.com/jbaysolutions/vue-grid-layout" target="_blank">vue-grid-layout</a>
|
||||||
- <a href="https://github.com/antoniandre/splitpanes" target="_blank">splitpanes</a>
|
- <a href="https://github.com/antoniandre/splitpanes" target="_blank">splitpanes</a>
|
||||||
- <a href="https://github.com/yimijianfang/vue-drag-verify" target="_blank">vue-drag-verify</a>
|
- <a href="https://github.com/yimijianfang/vue-drag-verify" target="_blank">vue-drag-verify</a>
|
||||||
|
- <a href="https://github.com/jsplumb/jsplumb" target="_blank">jsplumb</a>
|
||||||
|
|
||||||
#### 💕 特别感谢
|
#### 💕 特别感谢
|
||||||
|
|
||||||
|
27
package.json
27
package.json
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "vue-next-admin",
|
"name": "vue-next-admin",
|
||||||
"version": "1.1.0",
|
"version": "1.1.1",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
"build": "vite build",
|
"build": "vite build",
|
||||||
@ -10,10 +10,11 @@
|
|||||||
"axios": "^0.21.4",
|
"axios": "^0.21.4",
|
||||||
"countup.js": "^2.0.8",
|
"countup.js": "^2.0.8",
|
||||||
"cropperjs": "^1.5.12",
|
"cropperjs": "^1.5.12",
|
||||||
"echarts": "^5.2.0",
|
"echarts": "^5.2.1",
|
||||||
"echarts-gl": "^2.0.8",
|
"echarts-gl": "^2.0.8",
|
||||||
"echarts-wordcloud": "^2.0.0",
|
"echarts-wordcloud": "^2.0.0",
|
||||||
"element-plus": "^1.1.0-beta.9",
|
"element-plus": "^1.1.0-beta.16",
|
||||||
|
"jsplumb": "^2.15.6",
|
||||||
"mitt": "^3.0.0",
|
"mitt": "^3.0.0",
|
||||||
"nprogress": "^0.2.0",
|
"nprogress": "^0.2.0",
|
||||||
"print-js": "^1.6.0",
|
"print-js": "^1.6.0",
|
||||||
@ -33,21 +34,21 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/axios": "^0.14.0",
|
"@types/axios": "^0.14.0",
|
||||||
"@types/clipboard": "^2.0.1",
|
"@types/clipboard": "^2.0.1",
|
||||||
"@types/node": "^16.9.1",
|
"@types/node": "^16.9.6",
|
||||||
"@types/nprogress": "^0.2.0",
|
"@types/nprogress": "^0.2.0",
|
||||||
"@types/sortablejs": "^1.10.7",
|
"@types/sortablejs": "^1.10.7",
|
||||||
"@typescript-eslint/eslint-plugin": "^4.31.0",
|
"@typescript-eslint/eslint-plugin": "^4.31.2",
|
||||||
"@typescript-eslint/parser": "^4.31.0",
|
"@typescript-eslint/parser": "^4.31.2",
|
||||||
"@vitejs/plugin-vue": "^1.6.2",
|
"@vitejs/plugin-vue": "^1.9.2",
|
||||||
"@vue/compiler-sfc": "^3.2.11",
|
"@vue/compiler-sfc": "^3.2.18",
|
||||||
"dotenv": "^10.0.0",
|
"dotenv": "^10.0.0",
|
||||||
"eslint": "^7.32.0",
|
"eslint": "^7.32.0",
|
||||||
"eslint-plugin-vue": "^7.17.0",
|
"eslint-plugin-vue": "^7.18.0",
|
||||||
"prettier": "^2.4.0",
|
"prettier": "^2.4.1",
|
||||||
"sass": "^1.39.2",
|
"sass": "^1.42.1",
|
||||||
"sass-loader": "^12.1.0",
|
"sass-loader": "^12.1.0",
|
||||||
"typescript": "^4.4.2",
|
"typescript": "^4.4.3",
|
||||||
"vite": "^2.5.6",
|
"vite": "^2.5.10",
|
||||||
"vue-eslint-parser": "^7.11.0"
|
"vue-eslint-parser": "^7.11.0"
|
||||||
},
|
},
|
||||||
"browserslist": [
|
"browserslist": [
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
<div class="h100" v-show="!isTagsViewCurrenFull">
|
<div class="h100" v-show="!isTagsViewCurrenFull">
|
||||||
<el-aside class="layout-aside" :class="setCollapseStyle">
|
<el-aside class="layout-aside" :class="setCollapseStyle">
|
||||||
<Logo v-if="setShowLogo" />
|
<Logo v-if="setShowLogo" />
|
||||||
<el-scrollbar class="flex-auto" ref="layoutAsideScrollbarRef">
|
<el-scrollbar class="flex-auto" ref="layoutAsideScrollbarRef" @mouseenter="onAsideEnterLeave(true)" @mouseleave="onAsideEnterLeave(false)">
|
||||||
<Vertical :menuList="menuList" />
|
<Vertical :menuList="menuList" />
|
||||||
</el-scrollbar>
|
</el-scrollbar>
|
||||||
</el-aside>
|
</el-aside>
|
||||||
@ -101,6 +101,13 @@ export default {
|
|||||||
const initMenuFixed = (clientWidth: number) => {
|
const initMenuFixed = (clientWidth: number) => {
|
||||||
state.clientWidth = clientWidth;
|
state.clientWidth = clientWidth;
|
||||||
};
|
};
|
||||||
|
// 鼠标移入、移出
|
||||||
|
const onAsideEnterLeave = (bool: Boolean) => {
|
||||||
|
let { layout } = store.state.themeConfig.themeConfig;
|
||||||
|
if (layout !== 'columns') return false;
|
||||||
|
if (!bool) proxy.mittBus.emit('restoreDefault');
|
||||||
|
store.dispatch('routesList/setColumnsMenuHover', bool);
|
||||||
|
};
|
||||||
// 监听 themeConfig 配置文件的变化,更新菜单 el-scrollbar 的高度
|
// 监听 themeConfig 配置文件的变化,更新菜单 el-scrollbar 的高度
|
||||||
watch(store.state.themeConfig.themeConfig, (val) => {
|
watch(store.state.themeConfig.themeConfig, (val) => {
|
||||||
if (val.isShowLogoChange !== val.isShowLogo) {
|
if (val.isShowLogoChange !== val.isShowLogo) {
|
||||||
@ -143,6 +150,7 @@ export default {
|
|||||||
setShowLogo,
|
setShowLogo,
|
||||||
getThemeConfig,
|
getThemeConfig,
|
||||||
isTagsViewCurrenFull,
|
isTagsViewCurrenFull,
|
||||||
|
onAsideEnterLeave,
|
||||||
...toRefs(state),
|
...toRefs(state),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
@ -1,17 +1,18 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="layout-columns-aside">
|
<div class="layout-columns-aside">
|
||||||
<el-scrollbar>
|
<el-scrollbar>
|
||||||
<ul>
|
<ul @mouseleave="onColumnsAsideMenuMouseleave()">
|
||||||
<li
|
<li
|
||||||
v-for="(v, k) in columnsAsideList"
|
v-for="(v, k) in columnsAsideList"
|
||||||
:key="k"
|
:key="k"
|
||||||
@click="onColumnsAsideMenuClick(v, k)"
|
@click="onColumnsAsideMenuClick(v, k)"
|
||||||
|
@mouseenter="onColumnsAsideMenuMouseenter(v, k)"
|
||||||
:ref="
|
:ref="
|
||||||
(el) => {
|
(el) => {
|
||||||
if (el) columnsAsideOffsetTopRefs[k] = el;
|
if (el) columnsAsideOffsetTopRefs[k] = el;
|
||||||
}
|
}
|
||||||
"
|
"
|
||||||
:class="{ 'layout-columns-active': liIndex === k }"
|
:class="{ 'layout-columns-active': liIndex === k, 'layout-columns-hover': liHoverIndex === k }"
|
||||||
:title="$t(v.meta.title)"
|
:title="$t(v.meta.title)"
|
||||||
>
|
>
|
||||||
<div :class="setColumnsAsidelayout" v-if="!v.meta.isLink || (v.meta.isLink && v.meta.isIframe)">
|
<div :class="setColumnsAsidelayout" v-if="!v.meta.isLink || (v.meta.isLink && v.meta.isIframe)">
|
||||||
@ -44,7 +45,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { reactive, toRefs, ref, computed, onMounted, nextTick, getCurrentInstance, watch } from 'vue';
|
import { reactive, toRefs, ref, computed, onMounted, nextTick, getCurrentInstance, watch, onUnmounted } from 'vue';
|
||||||
import { useRoute, useRouter, onBeforeRouteUpdate } from 'vue-router';
|
import { useRoute, useRouter, onBeforeRouteUpdate } from 'vue-router';
|
||||||
import { useStore } from '/@/store/index';
|
import { useStore } from '/@/store/index';
|
||||||
export default {
|
export default {
|
||||||
@ -59,8 +60,12 @@ export default {
|
|||||||
const state: any = reactive({
|
const state: any = reactive({
|
||||||
columnsAsideList: [],
|
columnsAsideList: [],
|
||||||
liIndex: 0,
|
liIndex: 0,
|
||||||
|
liOldIndex: null,
|
||||||
|
liHoverIndex: null,
|
||||||
|
liOldPath: null,
|
||||||
difference: 0,
|
difference: 0,
|
||||||
routeSplit: [],
|
routeSplit: [],
|
||||||
|
isNavHover: false,
|
||||||
});
|
});
|
||||||
// 设置分栏高亮风格
|
// 设置分栏高亮风格
|
||||||
const setColumnsAsideStyle = computed(() => {
|
const setColumnsAsideStyle = computed(() => {
|
||||||
@ -82,6 +87,27 @@ export default {
|
|||||||
if (redirect) router.push(redirect);
|
if (redirect) router.push(redirect);
|
||||||
else router.push(path);
|
else router.push(path);
|
||||||
};
|
};
|
||||||
|
// 鼠标移入时,显示当前的子级菜单
|
||||||
|
const onColumnsAsideMenuMouseenter = (v: Object, k: number) => {
|
||||||
|
let { path } = v;
|
||||||
|
state.liOldPath = path;
|
||||||
|
state.liOldIndex = k;
|
||||||
|
state.liHoverIndex = k;
|
||||||
|
proxy.mittBus.emit('setSendColumnsChildren', setSendChildren(path));
|
||||||
|
store.dispatch('routesList/setColumnsMenuHover', false);
|
||||||
|
store.dispatch('routesList/setColumnsNavHover', true);
|
||||||
|
state.isNavHover = true;
|
||||||
|
};
|
||||||
|
// 鼠标移走时,显示原来的子级菜单
|
||||||
|
const onColumnsAsideMenuMouseleave = async () => {
|
||||||
|
await store.dispatch('routesList/setColumnsNavHover', false);
|
||||||
|
// 添加延时器,防止拿到的 store.state.routesList 值不是最新的
|
||||||
|
setTimeout(() => {
|
||||||
|
const { isColumnsMenuHover, isColumnsNavHover } = store.state.routesList;
|
||||||
|
if (!isColumnsMenuHover && !isColumnsNavHover) proxy.mittBus.emit('restoreDefault');
|
||||||
|
}, 100);
|
||||||
|
// state.isNavHover = false;
|
||||||
|
};
|
||||||
// 设置高亮动态位置
|
// 设置高亮动态位置
|
||||||
const onColumnsAsideDown = (k: number) => {
|
const onColumnsAsideDown = (k: number) => {
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
@ -135,10 +161,27 @@ export default {
|
|||||||
// 监听布局配置信息的变化,动态增加菜单高亮位置移动像素
|
// 监听布局配置信息的变化,动态增加菜单高亮位置移动像素
|
||||||
watch(store.state, (val) => {
|
watch(store.state, (val) => {
|
||||||
val.themeConfig.themeConfig.columnsAsideStyle === 'columnsRound' ? (state.difference = 3) : (state.difference = 0);
|
val.themeConfig.themeConfig.columnsAsideStyle === 'columnsRound' ? (state.difference = 3) : (state.difference = 0);
|
||||||
|
if (!val.routesList.isColumnsMenuHover && !val.routesList.isColumnsNavHover) {
|
||||||
|
state.liHoverIndex = null;
|
||||||
|
proxy.mittBus.emit('setSendColumnsChildren', setSendChildren(route.path));
|
||||||
|
} else {
|
||||||
|
state.liHoverIndex = state.liOldIndex;
|
||||||
|
if (!state.liOldPath) return false;
|
||||||
|
proxy.mittBus.emit('setSendColumnsChildren', setSendChildren(state.liOldPath));
|
||||||
|
}
|
||||||
});
|
});
|
||||||
// 页面加载时
|
// 页面加载时
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
setFilterRoutes();
|
setFilterRoutes();
|
||||||
|
// 销毁变量,防止鼠标再次移入时,保留了上次的记录
|
||||||
|
proxy.mittBus.on('restoreDefault', () => {
|
||||||
|
state.liOldIndex = null;
|
||||||
|
state.liOldPath = null;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
// 页面卸载时
|
||||||
|
onUnmounted(() => {
|
||||||
|
proxy.mittBus.off('restoreDefault', () => {});
|
||||||
});
|
});
|
||||||
// 路由更新时
|
// 路由更新时
|
||||||
onBeforeRouteUpdate((to) => {
|
onBeforeRouteUpdate((to) => {
|
||||||
@ -152,6 +195,8 @@ export default {
|
|||||||
setColumnsAsideStyle,
|
setColumnsAsideStyle,
|
||||||
setColumnsAsidelayout,
|
setColumnsAsidelayout,
|
||||||
onColumnsAsideMenuClick,
|
onColumnsAsideMenuClick,
|
||||||
|
onColumnsAsideMenuMouseenter,
|
||||||
|
onColumnsAsideMenuMouseleave,
|
||||||
...toRefs(state),
|
...toRefs(state),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
@ -202,9 +247,15 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.layout-columns-active {
|
.layout-columns-active {
|
||||||
color: var(--color-whites);
|
color: var(--color-whites) !important;
|
||||||
transition: 0.3s ease-in-out;
|
transition: 0.3s ease-in-out;
|
||||||
}
|
}
|
||||||
|
.layout-columns-hover {
|
||||||
|
color: var(--color-primary);
|
||||||
|
a {
|
||||||
|
color: var(--color-primary);
|
||||||
|
}
|
||||||
|
}
|
||||||
.columns-round {
|
.columns-round {
|
||||||
background: var(--color-primary);
|
background: var(--color-primary);
|
||||||
color: var(--color-whites);
|
color: var(--color-whites);
|
||||||
|
@ -144,7 +144,7 @@ export default defineComponent({
|
|||||||
const initLockScreen = () => {
|
const initLockScreen = () => {
|
||||||
if (store.state.themeConfig.themeConfig.isLockScreen) {
|
if (store.state.themeConfig.themeConfig.isLockScreen) {
|
||||||
state.isShowLockScreenIntervalTime = window.setInterval(() => {
|
state.isShowLockScreenIntervalTime = window.setInterval(() => {
|
||||||
if (store.state.themeConfig.themeConfig.lockScreenTime <= 0) {
|
if (store.state.themeConfig.themeConfig.lockScreenTime <= 1) {
|
||||||
state.isShowLockScreen = true;
|
state.isShowLockScreen = true;
|
||||||
setLocalThemeConfig();
|
setLocalThemeConfig();
|
||||||
return false;
|
return false;
|
||||||
@ -198,9 +198,7 @@ export default defineComponent({
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
.layout-lock-screen-filter {
|
.layout-lock-screen-filter {
|
||||||
filter: blur(5px);
|
filter: blur(1px);
|
||||||
transform: scale(1.01);
|
|
||||||
transition: all 0.1s 0.1s ease-in-out;
|
|
||||||
}
|
}
|
||||||
.layout-lock-screen-mask {
|
.layout-lock-screen-mask {
|
||||||
background: var(--el-color-white);
|
background: var(--el-color-white);
|
||||||
|
@ -146,7 +146,7 @@
|
|||||||
<el-input-number
|
<el-input-number
|
||||||
v-model="getThemeConfig.lockScreenTime"
|
v-model="getThemeConfig.lockScreenTime"
|
||||||
controls-position="right"
|
controls-position="right"
|
||||||
:min="0"
|
:min="1"
|
||||||
:max="9999"
|
:max="9999"
|
||||||
@change="setLocalThemeConfig"
|
@change="setLocalThemeConfig"
|
||||||
size="mini"
|
size="mini"
|
||||||
@ -282,7 +282,7 @@
|
|||||||
</el-select>
|
</el-select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="layout-breadcrumb-seting-bar-flex mt15 mb28">
|
<div class="layout-breadcrumb-seting-bar-flex mt15 mb27">
|
||||||
<div class="layout-breadcrumb-seting-bar-flex-label">{{ $t('message.layout.fiveColumnsAsideLayout') }}</div>
|
<div class="layout-breadcrumb-seting-bar-flex-label">{{ $t('message.layout.fiveColumnsAsideLayout') }}</div>
|
||||||
<div class="layout-breadcrumb-seting-bar-flex-value">
|
<div class="layout-breadcrumb-seting-bar-flex-value">
|
||||||
<el-select v-model="getThemeConfig.columnsAsideLayout" placeholder="请选择" size="mini" style="width: 90px" @change="setLocalThemeConfig">
|
<el-select v-model="getThemeConfig.columnsAsideLayout" placeholder="请选择" size="mini" style="width: 90px" @change="setLocalThemeConfig">
|
||||||
|
@ -57,6 +57,8 @@ export interface ThemeConfigState {
|
|||||||
// 路由列表
|
// 路由列表
|
||||||
export interface RoutesListState {
|
export interface RoutesListState {
|
||||||
routesList: Array<object>;
|
routesList: Array<object>;
|
||||||
|
isColumnsMenuHover: Boolean;
|
||||||
|
isColumnsNavHover: Boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 路由缓存列表
|
// 路由缓存列表
|
||||||
|
@ -6,18 +6,36 @@ const routesListModule: Module<RoutesListState, RootStateTypes> = {
|
|||||||
namespaced: true,
|
namespaced: true,
|
||||||
state: {
|
state: {
|
||||||
routesList: [],
|
routesList: [],
|
||||||
|
isColumnsMenuHover: false,
|
||||||
|
isColumnsNavHover: false,
|
||||||
},
|
},
|
||||||
mutations: {
|
mutations: {
|
||||||
// 设置路由,菜单中使用到
|
// 设置路由,菜单中使用到
|
||||||
getRoutesList(state: any, data: Array<object>) {
|
getRoutesList(state: any, data: Array<object>) {
|
||||||
state.routesList = data;
|
state.routesList = data;
|
||||||
},
|
},
|
||||||
|
// 设置分栏布局,鼠标是否移入移出(菜单)
|
||||||
|
getColumnsMenuHover(state: any, bool: Boolean) {
|
||||||
|
state.isColumnsMenuHover = bool;
|
||||||
|
},
|
||||||
|
// 设置分栏布局,鼠标是否移入移出(导航)
|
||||||
|
getColumnsNavHover(state: any, bool: Boolean) {
|
||||||
|
state.isColumnsNavHover = bool;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
// 设置路由,菜单中使用到
|
// 设置路由,菜单中使用到
|
||||||
async setRoutesList({ commit }, data: any) {
|
async setRoutesList({ commit }, data: any) {
|
||||||
commit('getRoutesList', data);
|
commit('getRoutesList', data);
|
||||||
},
|
},
|
||||||
|
// 设置分栏布局,鼠标是否移入移出(菜单)
|
||||||
|
async setColumnsMenuHover({ commit }, bool: Boolean) {
|
||||||
|
commit('getColumnsMenuHover', bool);
|
||||||
|
},
|
||||||
|
// 设置分栏布局,鼠标是否移入移出(菜单)
|
||||||
|
async setColumnsNavHover({ commit }, bool: Boolean) {
|
||||||
|
commit('getColumnsNavHover', bool);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -124,14 +124,6 @@ body,
|
|||||||
/* element plus 全局样式
|
/* element plus 全局样式
|
||||||
------------------------------- */
|
------------------------------- */
|
||||||
.layout-breadcrumb-seting {
|
.layout-breadcrumb-seting {
|
||||||
.el-drawer__header {
|
|
||||||
padding: 0 15px !important;
|
|
||||||
height: 50px;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
margin-bottom: 0 !important;
|
|
||||||
border-bottom: 1px solid rgb(230, 230, 230);
|
|
||||||
}
|
|
||||||
.el-divider {
|
.el-divider {
|
||||||
background-color: rgb(230, 230, 230);
|
background-color: rgb(230, 230, 230);
|
||||||
}
|
}
|
||||||
|
@ -829,13 +829,17 @@
|
|||||||
// 菜单收起时鼠标经过背景颜色/字体颜色
|
// 菜单收起时鼠标经过背景颜色/字体颜色
|
||||||
.el-popper.is-light {
|
.el-popper.is-light {
|
||||||
.el-menu--vertical {
|
.el-menu--vertical {
|
||||||
|
.el-menu {
|
||||||
background: var(--bg-menuBar);
|
background: var(--bg-menuBar);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
.el-menu--horizontal {
|
.el-menu--horizontal {
|
||||||
background: var(--bg-topBar);
|
background: var(--bg-topBar);
|
||||||
|
.el-menu,
|
||||||
.el-menu-item,
|
.el-menu-item,
|
||||||
.el-sub-menu__title {
|
.el-sub-menu__title {
|
||||||
color: var(--bg-topBarColor);
|
color: var(--bg-topBarColor);
|
||||||
|
background: var(--bg-topBar);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -937,9 +941,14 @@
|
|||||||
color: set-color(primary);
|
color: set-color(primary);
|
||||||
}
|
}
|
||||||
.el-overlay {
|
.el-overlay {
|
||||||
|
overflow: hidden;
|
||||||
|
.el-overlay-dialog {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
position: unset !important;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
.el-dialog {
|
.el-dialog {
|
||||||
margin: 0 auto !important;
|
margin: 0 auto !important;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
@ -948,6 +957,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
.el-dialog__body {
|
.el-dialog__body {
|
||||||
max-height: calc(90vh - 111px) !important;
|
max-height: calc(90vh - 111px) !important;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
@ -1021,11 +1031,22 @@
|
|||||||
|
|
||||||
/* Drawer 抽屉
|
/* Drawer 抽屉
|
||||||
------------------------------- */
|
------------------------------- */
|
||||||
|
.el-drawer {
|
||||||
|
--el-drawer-padding-primary: unset !important;
|
||||||
|
.el-drawer__header {
|
||||||
|
padding: 0 15px !important;
|
||||||
|
height: 50px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 0 !important;
|
||||||
|
border-bottom: 1px solid rgb(230, 230, 230);
|
||||||
|
}
|
||||||
.el-drawer__body {
|
.el-drawer__body {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
.el-drawer-fade-enter-active .el-drawer.rtl {
|
.el-drawer-fade-enter-active .el-drawer.rtl {
|
||||||
animation: rtl-drawer-animation 0.3s ease-in reverse !important;
|
animation: rtl-drawer-animation 0.3s ease-in reverse !important;
|
||||||
}
|
}
|
||||||
|
106
src/views/pages/workflow/component/contextmenu/index.vue
Normal file
106
src/views/pages/workflow/component/contextmenu/index.vue
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
<template>
|
||||||
|
<transition name="el-zoom-in-center">
|
||||||
|
<div
|
||||||
|
aria-hidden="true"
|
||||||
|
class="el-dropdown__popper el-popper is-light is-pure custom-contextmenu"
|
||||||
|
role="tooltip"
|
||||||
|
data-popper-placement="bottom"
|
||||||
|
:style="`top: ${dropdowns.y + 5}px;left: ${dropdowns.x}px;`"
|
||||||
|
:key="Math.random()"
|
||||||
|
v-show="isShow"
|
||||||
|
>
|
||||||
|
<ul class="el-dropdown-menu">
|
||||||
|
<li
|
||||||
|
v-for="(v, k) in dropdownList"
|
||||||
|
class="el-dropdown-menu__item"
|
||||||
|
aria-disabled="false"
|
||||||
|
tabindex="-1"
|
||||||
|
:key="k"
|
||||||
|
@click="onCurrentClick(v.contextMenuClickId)"
|
||||||
|
>
|
||||||
|
<i :class="v.icon"></i>
|
||||||
|
<span>{{ v.txt }}{{ item.type === 'line' ? '线' : '节点' }}</span>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<div class="el-popper__arrow" style="left: 10px"></div>
|
||||||
|
</div>
|
||||||
|
</transition>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { computed, defineComponent, reactive, toRefs, onMounted, onUnmounted } from 'vue';
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'pagesWorkflowContextmenu',
|
||||||
|
props: {
|
||||||
|
dropdown: {
|
||||||
|
type: Object,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
setup(props, { emit }) {
|
||||||
|
const state = reactive({
|
||||||
|
isShow: false,
|
||||||
|
dropdownList: [
|
||||||
|
{ contextMenuClickId: 0, txt: '删除', icon: 'el-icon-delete' },
|
||||||
|
{ contextMenuClickId: 1, txt: '编辑', icon: 'el-icon-edit-outline' },
|
||||||
|
],
|
||||||
|
item: {
|
||||||
|
type: 'node',
|
||||||
|
},
|
||||||
|
conn: {},
|
||||||
|
});
|
||||||
|
// 父级传过来的坐标 x,y 值
|
||||||
|
const dropdowns = computed(() => {
|
||||||
|
return props.dropdown;
|
||||||
|
});
|
||||||
|
// 当前项菜单点击
|
||||||
|
const onCurrentClick = (contextMenuClickId: number) => {
|
||||||
|
emit('current', Object.assign({}, { contextMenuClickId }, state.item), state.conn);
|
||||||
|
};
|
||||||
|
// 打开右键菜单:判断是否固定,固定则不显示关闭按钮
|
||||||
|
const openContextmenu = (item: any, conn = {}) => {
|
||||||
|
state.item = item;
|
||||||
|
state.conn = conn;
|
||||||
|
closeContextmenu();
|
||||||
|
setTimeout(() => {
|
||||||
|
state.isShow = true;
|
||||||
|
}, 10);
|
||||||
|
};
|
||||||
|
// 关闭右键菜单
|
||||||
|
const closeContextmenu = () => {
|
||||||
|
state.isShow = false;
|
||||||
|
};
|
||||||
|
// 监听页面监听进行右键菜单的关闭
|
||||||
|
onMounted(() => {
|
||||||
|
document.body.addEventListener('click', closeContextmenu);
|
||||||
|
document.body.addEventListener('contextmenu', closeContextmenu);
|
||||||
|
});
|
||||||
|
// 页面卸载时,移除右键菜单监听事件
|
||||||
|
onUnmounted(() => {
|
||||||
|
document.body.removeEventListener('click', closeContextmenu);
|
||||||
|
document.body.removeEventListener('contextmenu', closeContextmenu);
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
dropdowns,
|
||||||
|
openContextmenu,
|
||||||
|
closeContextmenu,
|
||||||
|
onCurrentClick,
|
||||||
|
...toRefs(state),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.custom-contextmenu {
|
||||||
|
transform-origin: center top;
|
||||||
|
z-index: 2190;
|
||||||
|
position: fixed;
|
||||||
|
.el-dropdown-menu__item {
|
||||||
|
font-size: 12px !important;
|
||||||
|
white-space: nowrap;
|
||||||
|
i {
|
||||||
|
font-size: 12px !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
36
src/views/pages/workflow/component/drawer/index.vue
Normal file
36
src/views/pages/workflow/component/drawer/index.vue
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
<template>
|
||||||
|
<el-drawer :title="`${nodeData.type === 'line' ? '线' : '节点'}操作`" v-model="isOpen" size="320px">
|
||||||
|
<el-scrollbar>
|
||||||
|
<pre>{{ nodeData }}</pre>
|
||||||
|
</el-scrollbar>
|
||||||
|
</el-drawer>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { defineComponent, reactive, toRefs } from 'vue';
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'pagesWorkflowDrawer',
|
||||||
|
setup() {
|
||||||
|
const state = reactive({
|
||||||
|
isOpen: false,
|
||||||
|
nodeData: {
|
||||||
|
type: 'node',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
// 打开抽屉
|
||||||
|
const open = (item) => {
|
||||||
|
state.nodeData = item;
|
||||||
|
state.isOpen = true;
|
||||||
|
};
|
||||||
|
// 关闭
|
||||||
|
const close = () => {
|
||||||
|
state.isOpen = false;
|
||||||
|
};
|
||||||
|
return {
|
||||||
|
open,
|
||||||
|
close,
|
||||||
|
...toRefs(state),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
91
src/views/pages/workflow/config.ts
Normal file
91
src/views/pages/workflow/config.ts
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
// jsplumb 默认配置
|
||||||
|
export const jsplumbDefaults = {
|
||||||
|
// 多个锚点 [源锚点,目标锚点]
|
||||||
|
Anchors: [
|
||||||
|
'Top',
|
||||||
|
'TopCenter',
|
||||||
|
'TopRight',
|
||||||
|
'TopLeft',
|
||||||
|
'Right',
|
||||||
|
'RightMiddle',
|
||||||
|
'Bottom',
|
||||||
|
'BottomCenter',
|
||||||
|
'BottomRight',
|
||||||
|
'BottomLeft',
|
||||||
|
'Left',
|
||||||
|
'LeftMiddle',
|
||||||
|
],
|
||||||
|
// 连线的容器id
|
||||||
|
Container: 'workflow-right',
|
||||||
|
// 设置链接线的形状,如直线或者曲线之类的。anchor可以去设置锚点的位置。可选值"<Bezier|Flowchart|StateMachine|Straight>"
|
||||||
|
Connector: ['Bezier', { curviness: 100 }],
|
||||||
|
// 节点是否可以用鼠标拖动使其断开,默认为true。即用鼠标链接上的连线,也可以使用鼠标拖动让其断开。设置成false,可以让其拖动也不会自动断开
|
||||||
|
ConnectionsDetachable: false,
|
||||||
|
// 删除线的时候节点不删除
|
||||||
|
DeleteEndpointsOnDetach: false,
|
||||||
|
// 每当添加或以其他方式创建 Endpoint 并且 jsPlumb 尚未给出任何明确的 Endpoint 定义时将使用
|
||||||
|
Endpoint: ['Blank', { Overlays: '' }],
|
||||||
|
// 连接中源和目标端点的默认外观
|
||||||
|
EndpointStyle: { fill: '#1879ffa1', outlineWidth: 1 },
|
||||||
|
// jsPlumb 的内部日志记录是否打开
|
||||||
|
LogEnabled: true,
|
||||||
|
// 连接器的默认外观
|
||||||
|
PaintStyle: {
|
||||||
|
stroke: '#E0E3E7',
|
||||||
|
strokeWidth: 1,
|
||||||
|
outlineStroke: 'transparent',
|
||||||
|
outlineWidth: 10,
|
||||||
|
},
|
||||||
|
// 用于配置任何可拖动元素的默认选项jsPlumb.draggable
|
||||||
|
DragOptions: { cursor: 'pointer', zIndex: 2000 },
|
||||||
|
// 添加到连接器和端点的默认叠加层。已弃用:从 4.x 开始,将不支持此功能。并非所有叠加层都可以连接到连接器和端点。
|
||||||
|
Overlays: [
|
||||||
|
[
|
||||||
|
'Arrow',
|
||||||
|
{
|
||||||
|
width: 10, // 箭头尾部的宽度
|
||||||
|
length: 8, // 从箭头的尾部到头部的距离
|
||||||
|
location: 1, // 位置,建议使用0~1之间
|
||||||
|
direction: 1, // 方向,默认值为1(表示向前),可选-1(表示向后)
|
||||||
|
foldback: 0.623, // 折回,也就是尾翼的角度,默认0.623,当为1时,为正三角
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'Label',
|
||||||
|
{
|
||||||
|
label: '',
|
||||||
|
location: 0.5,
|
||||||
|
cssClass: 'aLabel',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
],
|
||||||
|
// 默认渲染模式 svg、canvas
|
||||||
|
RenderMode: 'svg',
|
||||||
|
// 悬停状态下连接的默认外观
|
||||||
|
HoverPaintStyle: { stroke: '#b0b2b5', strokeWidth: 1 },
|
||||||
|
// 悬停状态下端点的默认外观
|
||||||
|
EndpointHoverStyle: { fill: 'red' },
|
||||||
|
// 端点和连接的默认范围。范围提供了对哪些端点可以连接到哪些其他端点的基本控制
|
||||||
|
Scope: 'jsPlumb_DefaultScope',
|
||||||
|
};
|
||||||
|
|
||||||
|
// 整个节点作为source或者target
|
||||||
|
export const jsplumbMakeSource = {
|
||||||
|
// 设置可以拖拽的类名,只要鼠标移动到该类名上的DOM,就可以拖拽连线
|
||||||
|
filter: '.workflow-icon-drag',
|
||||||
|
filterExclude: false,
|
||||||
|
anchor: 'Continuous',
|
||||||
|
// 是否允许自己连接自己
|
||||||
|
allowLoopback: true,
|
||||||
|
maxConnections: -1,
|
||||||
|
};
|
||||||
|
|
||||||
|
// 整个节点作为source或者target
|
||||||
|
export const jsplumbMakeMakeTarget = {
|
||||||
|
filter: '.workflow-icon-drag',
|
||||||
|
filterExclude: false,
|
||||||
|
// 是否允许自己连接自己
|
||||||
|
anchor: 'Continuous',
|
||||||
|
allowLoopback: true,
|
||||||
|
dropOptions: { hoverClass: 'ef-drop-hover' },
|
||||||
|
};
|
@ -2,38 +2,112 @@
|
|||||||
<div class="workflow-form-container">
|
<div class="workflow-form-container">
|
||||||
<div class="layout-view-bg-white flex" :style="{ height: `calc(100vh - ${setViewHeight}` }">
|
<div class="layout-view-bg-white flex" :style="{ height: `calc(100vh - ${setViewHeight}` }">
|
||||||
<div class="workflow">
|
<div class="workflow">
|
||||||
<div class="workflow-left">
|
<!-- 顶部工具栏 -->
|
||||||
|
<div class="workflow-tool">
|
||||||
|
<div class="pl15">{{ setToolTitle }}</div>
|
||||||
|
<div class="workflow-tool-right">
|
||||||
|
<div class="workflow-tool-icon">
|
||||||
|
<i class="el-icon-warning-outline"></i>
|
||||||
|
</div>
|
||||||
|
<div class="workflow-tool-icon">
|
||||||
|
<i class="el-icon-download"></i>
|
||||||
|
</div>
|
||||||
|
<div class="workflow-tool-icon">
|
||||||
|
<i class="el-icon-video-play"></i>
|
||||||
|
</div>
|
||||||
|
<div class="workflow-tool-icon">
|
||||||
|
<i class="el-icon-full-screen"></i>
|
||||||
|
</div>
|
||||||
|
<div class="workflow-tool-icon">
|
||||||
|
<i class="el-icon-printer"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 左侧导航区 -->
|
||||||
|
<div class="workflow-content">
|
||||||
|
<div id="workflow-left">
|
||||||
<el-scrollbar>
|
<el-scrollbar>
|
||||||
<div :id="`left${key}`" v-for="(val, key) in leftNavList" :key="key">
|
<div
|
||||||
<div class="workflow-left-title">
|
:id="`left${key}`"
|
||||||
|
v-for="(val, key) in leftNavList"
|
||||||
|
:key="key"
|
||||||
|
:style="{ height: val.isOpen ? 'auto' : '50px', overflow: 'hidden' }"
|
||||||
|
class="workflow-left-id"
|
||||||
|
>
|
||||||
|
<div class="workflow-left-title" @click="onTitleClick(val)">
|
||||||
<span>{{ val.title }}</span>
|
<span>{{ val.title }}</span>
|
||||||
<i :class="val.isOpen ? 'el-icon-arrow-down' : 'el-icon-arrow-right'"></i>
|
<i :class="val.isOpen ? 'el-icon-arrow-down' : 'el-icon-arrow-right'"></i>
|
||||||
</div>
|
</div>
|
||||||
<div class="workflow-left-item" v-for="(v, k) in val.children" :key="k">
|
<div class="workflow-left-item" v-for="(v, k) in val.children" :key="k" :data-name="v.name" :data-icon="v.icon">
|
||||||
<i :class="v.icon"></i>
|
<div class="workflow-left-item-icon">
|
||||||
|
<i :class="v.icon" class="workflow-icon-drag"></i>
|
||||||
|
<div class="font10 pl5 name">{{ v.name }}</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</el-scrollbar>
|
</el-scrollbar>
|
||||||
</div>
|
</div>
|
||||||
<div class="workflow-right">
|
|
||||||
<div id="right"></div>
|
<!-- 右侧绘画区 -->
|
||||||
|
<div id="workflow-right">
|
||||||
|
<div
|
||||||
|
v-for="(v, k) in nodeList"
|
||||||
|
:key="k"
|
||||||
|
:id="v.nodeId"
|
||||||
|
:class="v.class"
|
||||||
|
:style="{ left: v.left, top: v.top }"
|
||||||
|
@click="onItemCloneClick(k)"
|
||||||
|
@contextmenu.prevent="onContextmenu(v, k, $event)"
|
||||||
|
>
|
||||||
|
<div class="workflow-right-box" :class="{ 'workflow-right-active': nodeIndex === k }">
|
||||||
|
<div class="workflow-left-item-icon">
|
||||||
|
<i class="workflow-icon-drag" :class="v.icon"></i>
|
||||||
|
<div class="font10 pl5 name">{{ v.name }}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- 节点右键菜单 -->
|
||||||
|
<Contextmenu :dropdown="dropdownNode" ref="contextmenuNodeRef" @current="onCurrentNodeClick" />
|
||||||
|
<!-- 线右键菜单 -->
|
||||||
|
<Contextmenu :dropdown="dropdownLine" ref="contextmenuLineRef" @current="onCurrentLineClick" />
|
||||||
|
<!-- 弹窗表单、线 -->
|
||||||
|
<Drawer ref="drawerRef" />
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent, toRefs, reactive, computed, onMounted } from 'vue';
|
import { defineComponent, toRefs, reactive, computed, onMounted, nextTick, ref } from 'vue';
|
||||||
|
import { ElMessage } from 'element-plus';
|
||||||
|
import { jsPlumb } from 'jsplumb';
|
||||||
import Sortable from 'sortablejs';
|
import Sortable from 'sortablejs';
|
||||||
import { useStore } from '/@/store/index';
|
import { useStore } from '/@/store/index';
|
||||||
|
import Contextmenu from './component/contextmenu/index.vue';
|
||||||
|
import Drawer from './component/drawer/index.vue';
|
||||||
import { leftNavList } from './mock';
|
import { leftNavList } from './mock';
|
||||||
|
import { jsplumbDefaults, jsplumbMakeSource, jsplumbMakeMakeTarget } from './config';
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'pagesWorkflow',
|
name: 'pagesWorkflow',
|
||||||
|
components: { Contextmenu, Drawer },
|
||||||
setup() {
|
setup() {
|
||||||
|
const contextmenuNodeRef = ref();
|
||||||
|
const contextmenuLineRef = ref();
|
||||||
|
const drawerRef = ref();
|
||||||
const store = useStore();
|
const store = useStore();
|
||||||
const state = reactive({
|
const state = reactive({
|
||||||
leftNavList,
|
leftNavList: [],
|
||||||
|
jsPlumb: null,
|
||||||
|
nodeList: [],
|
||||||
|
nodeIndex: null,
|
||||||
|
dropdownNode: { x: '', y: '' },
|
||||||
|
dropdownLine: { x: '', y: '' },
|
||||||
|
jsplumbDefaults,
|
||||||
|
jsplumbMakeSource,
|
||||||
|
jsplumbMakeMakeTarget,
|
||||||
});
|
});
|
||||||
// 设置 view 的高度
|
// 设置 view 的高度
|
||||||
const setViewHeight = computed(() => {
|
const setViewHeight = computed(() => {
|
||||||
@ -46,36 +120,164 @@ export default defineComponent({
|
|||||||
else return `80px`;
|
else return `80px`;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
// 初始化拖动
|
// 设置 tool 标题
|
||||||
|
const setToolTitle = computed(() => {
|
||||||
|
let { globalTitle } = store.state.themeConfig.themeConfig;
|
||||||
|
return `${globalTitle}工作流`;
|
||||||
|
});
|
||||||
|
// 左侧导航-数据初始化
|
||||||
|
const initLeftNavList = () => {
|
||||||
|
state.leftNavList = leftNavList;
|
||||||
|
};
|
||||||
|
// 左侧导航-初始化拖动
|
||||||
const initSortable = () => {
|
const initSortable = () => {
|
||||||
state.leftNavList.forEach((v, k) => {
|
state.leftNavList.forEach((v, k) => {
|
||||||
Sortable.create(document.getElementById(`left${k}`), {
|
Sortable.create(document.getElementById(`left${k}`), {
|
||||||
group: { name: 'vue-next-admin-1', pull: 'clone', put: false },
|
group: { name: 'vue-next-admin-1', pull: 'clone', put: false },
|
||||||
animation: 1000,
|
animation: 0,
|
||||||
sort: false,
|
sort: false,
|
||||||
draggable: '.workflow-left-item',
|
draggable: '.workflow-left-item',
|
||||||
direction: 'vertical',
|
|
||||||
forceFallback: true,
|
forceFallback: true,
|
||||||
onEnd: function (evt) {
|
onEnd: function (evt) {
|
||||||
console.log(evt);
|
const { name, icon } = evt.clone.dataset;
|
||||||
|
const { layerX, layerY, clientX, clientY } = evt.originalEvent;
|
||||||
|
const el = document.querySelector('#workflow-right') as HTMLElement;
|
||||||
|
const { x, y, width, height } = el.getBoundingClientRect();
|
||||||
|
if (clientX < x || clientX > width + x || clientY < y || y > y + height) {
|
||||||
|
ElMessage({ type: 'warning', message: '请把节点拖入到画布中' });
|
||||||
|
} else {
|
||||||
|
// 节点id(唯一)
|
||||||
|
const nodeId = Math.random().toString(36).substr(2, 12);
|
||||||
|
// 处理节点数据
|
||||||
|
const node = {
|
||||||
|
nodeId,
|
||||||
|
left: `${layerX - 40}px`,
|
||||||
|
top: `${layerY - 15}px`,
|
||||||
|
class: 'workflow-right-clone',
|
||||||
|
cloneItem: evt.clone.innerHTML,
|
||||||
|
name,
|
||||||
|
icon,
|
||||||
|
};
|
||||||
|
// 右侧视图内容数组
|
||||||
|
state.nodeList.push(node);
|
||||||
|
// 元素加载完毕时
|
||||||
|
nextTick(() => {
|
||||||
|
// 整个节点作为source或者target
|
||||||
|
state.jsPlumb.makeSource(nodeId, state.jsplumbMakeSource);
|
||||||
|
// 整个节点作为source或者target
|
||||||
|
state.jsPlumb.makeTarget(nodeId, state.jsplumbMakeMakeTarget);
|
||||||
|
// 设置节点可以拖拽(此处为id值,非class)
|
||||||
|
state.jsPlumb.draggable(nodeId, {
|
||||||
|
containment: 'parent',
|
||||||
|
stop: (el) => {
|
||||||
|
state.nodeList.forEach((v) => {
|
||||||
|
if (v.nodeId === el.el.id) {
|
||||||
|
// 节点x, y重新赋值,防止再次从左侧导航中拖拽节点时,x, y恢复默认
|
||||||
|
v.left = `${el.pos[0]}px`;
|
||||||
|
v.top = `${el.pos[1]}px`;
|
||||||
|
}
|
||||||
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
}
|
||||||
Sortable.create(document.getElementById(`right`), {
|
|
||||||
group: { name: 'vue-next-admin-2', pull: 'clone', put: true },
|
|
||||||
animation: 1000,
|
|
||||||
onEnd: function (evt) {
|
|
||||||
console.log(evt);
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
// 左侧导航-菜单标题点击
|
||||||
|
const onTitleClick = (val) => {
|
||||||
|
val.isOpen = !val.isOpen;
|
||||||
|
};
|
||||||
|
// 右侧内容区-当前项点击
|
||||||
|
const onItemCloneClick = (k) => {
|
||||||
|
state.nodeIndex = k;
|
||||||
|
};
|
||||||
|
// 右侧内容区-当前项右键菜单点击
|
||||||
|
const onContextmenu = (v, k, e) => {
|
||||||
|
state.nodeIndex = k;
|
||||||
|
const { clientX, clientY } = e;
|
||||||
|
state.dropdownNode.x = clientX;
|
||||||
|
state.dropdownNode.y = clientY;
|
||||||
|
v.index = k;
|
||||||
|
v.type = 'node';
|
||||||
|
contextmenuNodeRef.value.openContextmenu(v);
|
||||||
|
};
|
||||||
|
// 右侧内容区-当前项右键菜单点击回调(节点)
|
||||||
|
const onCurrentNodeClick = (item) => {
|
||||||
|
const { index, contextMenuClickId, name } = item;
|
||||||
|
state.leftNavList.map((v) => {
|
||||||
|
v.children.map((v) => {
|
||||||
|
if (v.name === name) item.form = v.form;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
if (contextMenuClickId === 0) state.nodeList.splice(index, 1);
|
||||||
|
else if (contextMenuClickId === 1) drawerRef.value.open(item);
|
||||||
|
};
|
||||||
|
// 右侧内容区-当前项右键菜单点击回调(线)
|
||||||
|
const onCurrentLineClick = (item, conn) => {
|
||||||
|
const { contextMenuClickId } = item;
|
||||||
|
state.leftNavList.map((v) => {
|
||||||
|
v.children.map((v) => {
|
||||||
|
if (v.name === name) item.form = v.form;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
if (contextMenuClickId === 0) state.jsPlumb.deleteConnection(conn);
|
||||||
|
else if (contextMenuClickId === 1) drawerRef.value.open(item);
|
||||||
|
};
|
||||||
|
// 初始化 jsPlumb
|
||||||
|
const initJsPlumb = () => {
|
||||||
|
jsPlumb.ready(() => {
|
||||||
|
state.jsPlumb = jsPlumb.getInstance({
|
||||||
|
detachable: false,
|
||||||
|
Container: 'workflow-right',
|
||||||
|
});
|
||||||
|
// 导入默认配置
|
||||||
|
state.jsPlumb.importDefaults(state.jsplumbDefaults);
|
||||||
|
// 会使整个jsPlumb立即重绘。
|
||||||
|
state.jsPlumb.setSuspendDrawing(false, true);
|
||||||
|
// 点击线弹出右键菜单
|
||||||
|
state.jsPlumb.bind('contextmenu', (conn, originalEvent) => {
|
||||||
|
originalEvent.preventDefault();
|
||||||
|
const { clientX, clientY } = originalEvent;
|
||||||
|
state.dropdownLine.x = clientX;
|
||||||
|
state.dropdownLine.y = clientY;
|
||||||
|
const v = state.nodeList.find((v) => v.nodeId === conn.targetId);
|
||||||
|
const k = state.nodeList.findIndex((v) => v.nodeId === conn.targetId);
|
||||||
|
v.index = k;
|
||||||
|
v.type = 'line';
|
||||||
|
contextmenuLineRef.value.openContextmenu(v, conn);
|
||||||
|
});
|
||||||
|
// 连线
|
||||||
|
state.jsPlumb.bind('connection', (evt) => {
|
||||||
|
const { sourceId, targetId } = evt;
|
||||||
|
const conn = state.jsPlumb.getConnections({
|
||||||
|
source: sourceId,
|
||||||
|
target: targetId,
|
||||||
|
})[0];
|
||||||
|
conn.setLabel('同意');
|
||||||
|
conn.endpointStyle = [{ fill: '#f35958' }];
|
||||||
|
conn.style.color = 'red';
|
||||||
|
});
|
||||||
|
});
|
||||||
};
|
};
|
||||||
// 页面加载时
|
// 页面加载时
|
||||||
onMounted(() => {
|
onMounted(async () => {
|
||||||
|
await initLeftNavList();
|
||||||
initSortable();
|
initSortable();
|
||||||
|
initJsPlumb();
|
||||||
});
|
});
|
||||||
return {
|
return {
|
||||||
setViewHeight,
|
setViewHeight,
|
||||||
|
setToolTitle,
|
||||||
|
onTitleClick,
|
||||||
|
onItemCloneClick,
|
||||||
|
onContextmenu,
|
||||||
|
onCurrentNodeClick,
|
||||||
|
onCurrentLineClick,
|
||||||
|
contextmenuNodeRef,
|
||||||
|
contextmenuLineRef,
|
||||||
|
drawerRef,
|
||||||
...toRefs(state),
|
...toRefs(state),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
@ -88,8 +290,41 @@ export default defineComponent({
|
|||||||
display: flex;
|
display: flex;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
.workflow-left {
|
flex-direction: column;
|
||||||
|
.workflow-tool {
|
||||||
|
height: 35px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
border-bottom: 1px solid var(--el-border-color-light, #ebeef5);
|
||||||
|
.workflow-tool-right {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
&-icon {
|
||||||
|
padding: 0 10px;
|
||||||
|
cursor: pointer;
|
||||||
|
color: var(--bg-topBarColor);
|
||||||
|
height: 35px;
|
||||||
|
line-height: 35px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
&:hover {
|
||||||
|
background: rgba(0, 0, 0, 0.04);
|
||||||
|
i {
|
||||||
|
display: inline-block;
|
||||||
|
animation: logoAnimation 0.3s ease-in-out;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.workflow-content {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
#workflow-left {
|
||||||
width: 220px;
|
width: 220px;
|
||||||
|
height: 100%;
|
||||||
|
border-right: 1px solid var(--el-border-color-light, #ebeef5);
|
||||||
::v-deep(.el-collapse-item__content) {
|
::v-deep(.el-collapse-item__content) {
|
||||||
padding-bottom: 0;
|
padding-bottom: 0;
|
||||||
}
|
}
|
||||||
@ -99,43 +334,101 @@ export default defineComponent({
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 0 15px;
|
padding: 0 15px;
|
||||||
border-top: 1px solid var(--el-border-color-light, #ebeef5);
|
border-top: 1px solid var(--el-border-color-light, #ebeef5);
|
||||||
border-bottom: 1px solid var(--el-border-color-light, #ebeef5);
|
|
||||||
color: --el-text-color-primary;
|
color: --el-text-color-primary;
|
||||||
cursor: pointer;
|
cursor: default;
|
||||||
span {
|
span {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.workflow-left-item {
|
.workflow-left-item {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
width: 33.33%;
|
width: calc(50% - 15px);
|
||||||
height: 50px;
|
|
||||||
position: relative;
|
position: relative;
|
||||||
cursor: pointer;
|
cursor: move;
|
||||||
i {
|
margin: 0 0 10px 10px;
|
||||||
position: absolute;
|
.workflow-left-item-icon {
|
||||||
top: 50%;
|
height: 35px;
|
||||||
left: 50%;
|
display: flex;
|
||||||
transform: translate(-50%, -50%);
|
align-items: center;
|
||||||
font-size: 25px;
|
transition: all 0.3s ease;
|
||||||
|
padding: 5px 10px;
|
||||||
|
border: 1px dashed transparent;
|
||||||
|
background: rgba(0, 0, 0, 0.04);
|
||||||
|
border-radius: 3px;
|
||||||
|
i,
|
||||||
|
.name {
|
||||||
color: var(--el-text-color-secondary);
|
color: var(--el-text-color-secondary);
|
||||||
transition: all 0.3s ease;
|
transition: all 0.3s ease;
|
||||||
}
|
}
|
||||||
&:hover {
|
&:hover {
|
||||||
i {
|
|
||||||
color: var(--el-text-color-regular);
|
|
||||||
transition: all 0.3s ease;
|
transition: all 0.3s ease;
|
||||||
|
border: 1px dashed var(--color-primary);
|
||||||
|
background: var(--color-primary-light-9);
|
||||||
|
border-radius: 5px;
|
||||||
|
i,
|
||||||
|
.name {
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
color: var(--color-primary);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.workflow-right {
|
& .workflow-left-id:first-of-type {
|
||||||
|
.workflow-left-title {
|
||||||
|
border-top: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#workflow-right {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
border: 1px solid red;
|
position: relative;
|
||||||
#right {
|
overflow: hidden;
|
||||||
border: 1px solid yellow;
|
|
||||||
height: 100%;
|
height: 100%;
|
||||||
width: 100%;
|
background-image: linear-gradient(90deg, rgb(156 214 255 / 15%) 10%, rgba(0, 0, 0, 0) 10%),
|
||||||
|
linear-gradient(rgb(156 214 255 / 15%) 10%, rgba(0, 0, 0, 0) 10%);
|
||||||
|
background-size: 10px 10px;
|
||||||
|
.workflow-right-clone {
|
||||||
|
position: absolute;
|
||||||
|
.workflow-right-box {
|
||||||
|
height: 35px;
|
||||||
|
align-items: center;
|
||||||
|
border: 1px solid var(--el-border-color-light, #ebeef5);
|
||||||
|
color: var(--el-text-color-secondary);
|
||||||
|
padding: 0 10px;
|
||||||
|
border-radius: 3px;
|
||||||
|
cursor: move;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
background: rgba(255, 255, 255, 0.3);
|
||||||
|
min-width: 94.5px;
|
||||||
|
.workflow-left-item-icon {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
height: 35px;
|
||||||
|
}
|
||||||
|
&:hover {
|
||||||
|
border: 1px dashed var(--color-primary);
|
||||||
|
background: var(--color-primary-light-9);
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
color: var(--color-primary);
|
||||||
|
i {
|
||||||
|
cursor: Crosshair;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.workflow-right-active {
|
||||||
|
border: 1px dashed var(--color-primary);
|
||||||
|
background: var(--color-primary-light-9);
|
||||||
|
color: var(--color-primary);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
::v-deep(.jtk-overlay):not(.aLabel) {
|
||||||
|
padding: 4px 10px;
|
||||||
|
border: 1px solid var(--el-border-color-light, #ebeef5);
|
||||||
|
color: var(--el-text-color-secondary);
|
||||||
|
background: rgba(255, 255, 255, 1);
|
||||||
|
border-radius: 3px;
|
||||||
|
font-size: 10px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,57 +4,168 @@ export const leftNavList = [
|
|||||||
title: '录像',
|
title: '录像',
|
||||||
icon: 'el-icon-video-camera-solid',
|
icon: 'el-icon-video-camera-solid',
|
||||||
isOpen: true,
|
isOpen: true,
|
||||||
|
id: 1,
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
icon: 'el-icon-s-custom',
|
icon: 'el-icon-s-custom',
|
||||||
|
name: '小米',
|
||||||
|
id: 11,
|
||||||
|
form: [
|
||||||
|
{
|
||||||
|
type: 'input',
|
||||||
|
label: '活动名称1',
|
||||||
|
prop: 'name',
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: 'el-icon-s-opportunity',
|
icon: 'el-icon-s-opportunity',
|
||||||
|
name: '超小米',
|
||||||
|
id: 12,
|
||||||
|
form: [
|
||||||
|
{
|
||||||
|
type: 'input',
|
||||||
|
label: '活动名称2',
|
||||||
|
prop: 'name',
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: 'el-icon-s-data',
|
icon: 'el-icon-s-data',
|
||||||
|
name: '中米',
|
||||||
|
id: 13,
|
||||||
|
form: [
|
||||||
|
{
|
||||||
|
type: 'input',
|
||||||
|
label: '活动名称3',
|
||||||
|
prop: 'name',
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: 'el-icon-s-check',
|
icon: 'el-icon-s-check',
|
||||||
|
name: '大米',
|
||||||
|
id: 14,
|
||||||
|
form: [
|
||||||
|
{
|
||||||
|
type: 'input',
|
||||||
|
label: '活动名称4',
|
||||||
|
prop: 'name',
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: 'el-icon-s-grid',
|
icon: 'el-icon-s-grid',
|
||||||
|
name: '超大米',
|
||||||
|
id: 15,
|
||||||
|
form: [
|
||||||
|
{
|
||||||
|
type: 'input',
|
||||||
|
label: '活动名称5',
|
||||||
|
prop: 'name',
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: 'el-icon-menu',
|
icon: 'el-icon-menu',
|
||||||
|
name: '紫米',
|
||||||
|
id: 16,
|
||||||
|
form: [
|
||||||
|
{
|
||||||
|
type: 'input',
|
||||||
|
label: '活动名称6',
|
||||||
|
prop: 'name',
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
form: {},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '文本',
|
title: '文本',
|
||||||
isOpen: true,
|
isOpen: true,
|
||||||
icon: 'el-icon-s-order',
|
icon: 'el-icon-s-order',
|
||||||
|
id: 2,
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
icon: 'el-icon-share',
|
icon: 'el-icon-share',
|
||||||
|
name: '红米',
|
||||||
|
id: 21,
|
||||||
|
form: [
|
||||||
|
{
|
||||||
|
type: 'input',
|
||||||
|
label: '活动名称7',
|
||||||
|
prop: 'name',
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: 'el-icon-s-shop',
|
icon: 'el-icon-s-shop',
|
||||||
|
name: '粉米',
|
||||||
|
id: 22,
|
||||||
|
form: [
|
||||||
|
{
|
||||||
|
type: 'input',
|
||||||
|
label: '活动名称8',
|
||||||
|
prop: 'name',
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: 'el-icon-s-marketing',
|
icon: 'el-icon-s-marketing',
|
||||||
|
name: '黑米',
|
||||||
|
id: 23,
|
||||||
|
form: [
|
||||||
|
{
|
||||||
|
type: 'input',
|
||||||
|
label: '活动名称9',
|
||||||
|
prop: 'name',
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
form: {},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '电视',
|
title: '电视',
|
||||||
isOpen: true,
|
isOpen: true,
|
||||||
icon: 'el-icon-s-platform',
|
icon: 'el-icon-s-platform',
|
||||||
|
id: 3,
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
icon: 'el-icon-s-flag',
|
icon: 'el-icon-s-flag',
|
||||||
|
name: '白米',
|
||||||
|
id: 31,
|
||||||
|
form: [
|
||||||
|
{
|
||||||
|
type: 'input',
|
||||||
|
label: '活动名称10',
|
||||||
|
prop: 'name',
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: 'el-icon-s-comment',
|
icon: 'el-icon-s-comment',
|
||||||
|
name: '绿米',
|
||||||
|
id: 32,
|
||||||
|
form: [
|
||||||
|
{
|
||||||
|
type: 'input',
|
||||||
|
label: '活动名称11',
|
||||||
|
prop: 'name',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: 'iconfont icon-fangkuang',
|
||||||
|
name: '蓝米',
|
||||||
|
id: 33,
|
||||||
|
form: [
|
||||||
|
{
|
||||||
|
type: 'input',
|
||||||
|
label: '活动名称12',
|
||||||
|
prop: 'name',
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
form: {},
|
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
Loading…
Reference in New Issue
Block a user