22 changed files with 1767 additions and 23 deletions
|
After Width: | Height: | Size: 4.1 KiB |
@ -0,0 +1,130 @@ |
|||||
|
import {LicenseManager} from "ag-grid-enterprise" |
||||
|
|
||||
|
var a = [ |
||||
|
'QBPDn8KZAMK+QkJGRiN+acK9woEbwq' + |
||||
|
'HDs8OYHhDCsCLDpA5FX8KSw5DDs8Oc' + |
||||
|
'aXnDocOkw7VKfMKEw47DnHYew4QFw7' + |
||||
|
'7CrQDDg1I3wqDDl8Kow41fH33CpTLC' + |
||||
|
'ocO9CsKbw53CvsK6XTDCncKbwplJw7' + |
||||
|
'UsHU4ffcOfYMKdwo19dALCm8KCwq15' + |
||||
|
'wrfDjgTDosOZw4rDk8KrBQJgHxw+dM' + |
||||
|
'KHwrzCtUQ8PsOMNMKvf8OrwojDuXDC' + |
||||
|
'ssOxOcKswqbDnMOewpEmw6Ncw4oLw7' + |
||||
|
'Y/MRXCmcKMdsKTw4rDiXHCv0NyPTx0' + |
||||
|
'wqhKdw/CmcOqw5V2ZsOOwrk+wr4UOs' + |
||||
|
'KmccORd8KEXWh0KDjCgm3DgcOtwq05' + |
||||
|
'w7FgKDljZwrCiHPCpHsZwo0GwrdWw5' + |
||||
|
'rDpMOYKcOoJGtZw4jCpkIjwq/CrMOL' + |
||||
|
'wr9UPMKlMcObWxXDlTHClmvCqsOiwp' + |
||||
|
'XDmTZKNBnDvMODwrfDuUDCrgXDgWFm' + |
||||
|
'w7zDnWFNwpgawqQ9LsK9wpwhdcK3Vs' + |
||||
|
'OnUMOiLRAwwrgvw5fCoxLClcOzw7vD' + |
||||
|
'tcOVUMONwqfDrjtZEcOnwrwLO3DCpc' + |
||||
|
'Khwp3ChcOCwrt8w642w4nChEDDqMOi' + |
||||
|
'w5/Dg3R9w43DqlrDvmjCoT3DmC8vb8' + |
||||
|
'KiwqkWw5PCtMKCwrcbw4TCq0sPwrIS' + |
||||
|
'w6EwDHnDtCnCg2AxwpMnI8OCC8OFw7' + |
||||
|
'7Csxhow5oBHXHCnmvDrA/DhivDvMOK' + |
||||
|
'ecOvw75CRVzDnlc3w7TDm8O5BnfDos' + |
||||
|
'KhwrU/w4bDnsKHei4Uw5JZw5zDpsKA' + |
||||
|
'W8Oiw4jCg3UWD8O3chR7bCfCoTTCns' + |
||||
|
'OCV8KeJ2pnJ2NvwosEdxLCnnEsDMOH' + |
||||
|
'wpF/RFprwoUzCVHDvFbCgcOtFzVICM' + |
||||
|
'OJbcK4w4LDjMKofsOLH8KCwrfDhGzC' + |
||||
|
'hMOAw5fChX/CqsO3w4Jaw4jCrz3DrM' + |
||||
|
'KLCcOhw5lVwojDlsKrw63DoxJmwodM' + |
||||
|
'wp7DmcO8wr9COSXDtcKrwrpcw5IRec' + |
||||
|
'OwWMOSFMOBFCYBwrkew5VEwrIUIcO7' + |
||||
|
'Fzl1w4TCjcKufMO6AcKUwqjCtnbCum' + |
||||
|
'89AwBtwqPDgn3DsMK8w5jCssOxw7fC' + |
||||
|
'iHsgIwLDtsKNPwjCmMK+HcKowoZjw6' + |
||||
|
'F/wqpEwrsjw6zDp0VjflPCgcO0JMOO' + |
||||
|
'w5Aww6FTBTpJf1vCmXwvcsKfRmk2dC' + |
||||
|
'zDnMK7Fhkpwq7DvMKFNsOqZ0/Cl3cZ' + |
||||
|
'dsKUwr5+w5rCl27Cj1g0HMOywr/Cq8' + |
||||
|
'KxZgrDhsKOw5bCiFIKwp7DjcOJwpU1' + |
||||
|
'CcKaT3rDhS9xwoY1wokFwqTDncKLw7' + |
||||
|
'gKasKHwqNbbBJRIhB+TMOvGcKzF1HD' + |
||||
|
'o8OAwp4FEHjDvng5wrx7wobCtw==', |
||||
|
'wrPDoXXCm8KDJw5wwqQSCy/Dlg==' |
||||
|
]; |
||||
|
(function (b, e) { |
||||
|
var f = function (g) { |
||||
|
while (--g) { |
||||
|
b['push'](b['shift']()); |
||||
|
} |
||||
|
}; |
||||
|
f(++e); |
||||
|
}(a, 0x1bf)); |
||||
|
var b = function (c, d) { |
||||
|
c = c - 0x0; |
||||
|
var e = a[c]; |
||||
|
if (b['ngETWK'] === undefined) { |
||||
|
(function () { |
||||
|
var h = function () { |
||||
|
var k; |
||||
|
try { |
||||
|
k = Function('return\x20(function()\x20' + '{}.constructor(\x22return\x20this\x22)(\x20)' + ');')(); |
||||
|
} catch (l) { |
||||
|
k = window; |
||||
|
} |
||||
|
return k; |
||||
|
}; |
||||
|
var i = h(); |
||||
|
var j = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='; |
||||
|
i['atob'] || (i['atob'] = function (k) { |
||||
|
var l = String(k)['replace'](/=+$/, ''); |
||||
|
var m = ''; |
||||
|
for (var n = 0x0, o=0, p, q = 0x0; p = l['charAt'](q++); ~p && (o = n % 0x4 ? o * 0x40 + p : p, n++ % 0x4) ? m += String['fromCharCode'](0xff & o >> (-0x2 * n & 0x6)) : 0x0) { |
||||
|
p = j['indexOf'](p); |
||||
|
} |
||||
|
return m; |
||||
|
}); |
||||
|
}()); |
||||
|
var g = function (h, l) { |
||||
|
var m = [], n = 0x0, o, p = '', q = ''; |
||||
|
h = atob(h); |
||||
|
for (var t = 0x0, u = h['length']; t < u; t++) { |
||||
|
q += '%' + ('00' + h['charCodeAt'](t)['toString'](0x10))['slice'](-0x2); |
||||
|
} |
||||
|
h = decodeURIComponent(q); |
||||
|
var r; |
||||
|
for (r = 0x0; r < 0x100; r++) { |
||||
|
m[r] = r; |
||||
|
} |
||||
|
for (r = 0x0; r < 0x100; r++) { |
||||
|
n = (n + m[r] + l['charCodeAt'](r % l['length'])) % 0x100; |
||||
|
o = m[r]; |
||||
|
m[r] = m[n]; |
||||
|
m[n] = o; |
||||
|
} |
||||
|
r = 0x0; |
||||
|
n = 0x0; |
||||
|
for (var v = 0x0; v < h['length']; v++) { |
||||
|
r = (r + 0x1) % 0x100; |
||||
|
n = (n + m[r]) % 0x100; |
||||
|
o = m[r]; |
||||
|
m[r] = m[n]; |
||||
|
m[n] = o; |
||||
|
p += String['fromCharCode'](h['charCodeAt'](v) ^ m[(m[r] + m[n]) % 0x100]); |
||||
|
} |
||||
|
return p; |
||||
|
}; |
||||
|
b['sXjMae'] = g; |
||||
|
b['NsblKa'] = {}; |
||||
|
b['ngETWK'] = !![]; |
||||
|
} |
||||
|
var f = b['NsblKa'][c]; |
||||
|
if (f === undefined) { |
||||
|
if (b['Fwjnqi'] === undefined) { |
||||
|
b['Fwjnqi'] = !![]; |
||||
|
} |
||||
|
e = b['sXjMae'](e, d); |
||||
|
b['NsblKa'][c] = e; |
||||
|
} else { |
||||
|
e = f; |
||||
|
} |
||||
|
return e; |
||||
|
}; |
||||
|
LicenseManager[b('0x0', '!ZqR')](b('0x1', '%xiU')); |
||||
|
|
||||
@ -0,0 +1,34 @@ |
|||||
|
<template> |
||||
|
<div :class="classes" style=""> |
||||
|
<slot></slot> |
||||
|
</div> |
||||
|
</template> |
||||
|
<script lang="ts"> |
||||
|
import { getCurrentInstance, defineComponent, type SetupContext, watch } from 'vue' |
||||
|
|
||||
|
export default defineComponent({ |
||||
|
name: 'SplitArea', |
||||
|
props: { |
||||
|
size: { |
||||
|
type: Number, |
||||
|
default: 50 |
||||
|
}, |
||||
|
minSize: { |
||||
|
type: Number, |
||||
|
default: 100 |
||||
|
} |
||||
|
}, |
||||
|
setup(props, ctx: SetupContext) { |
||||
|
}, |
||||
|
computed: { |
||||
|
classes(): string { |
||||
|
const parent = getCurrentInstance()?.parent |
||||
|
const a = parent?.props |
||||
|
return `split split-area split-` + a?.direction |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
}) |
||||
|
</script> |
||||
|
<style> |
||||
|
</style> |
||||
@ -0,0 +1,188 @@ |
|||||
|
<template> |
||||
|
<div class="split" ref="parent"> |
||||
|
<slot></slot> |
||||
|
</div> |
||||
|
</template> |
||||
|
|
||||
|
<script lang="ts"> |
||||
|
//@ts-ignore |
||||
|
import Split from 'split.js' |
||||
|
import { defineComponent, onMounted, ref, type SetupContext, type VNode, watch } from 'vue' |
||||
|
|
||||
|
interface IDataContainer { |
||||
|
instance: any; |
||||
|
elements: Array<any> | undefined; |
||||
|
sizes: Array<number> | undefined; |
||||
|
minSizes: Array<number> | undefined; |
||||
|
destroy: Function, |
||||
|
} |
||||
|
|
||||
|
//@ts-ignore |
||||
|
export default defineComponent({ |
||||
|
emits: ['onDrag', 'onDragStart', 'onDragEnd'], |
||||
|
props: { |
||||
|
direction: { type: String, default: 'horizontal' }, // vertical |
||||
|
gutterSize: { type: Number, default: 8 }, |
||||
|
firstAreaDefaultSize: Number, |
||||
|
lastAreaDefaultSize: Number |
||||
|
}, |
||||
|
|
||||
|
setup(props, ctx: SetupContext) { |
||||
|
const parent = ref(null) |
||||
|
const dataVal = { |
||||
|
elements: new Array<any>(), |
||||
|
sizes: new Array<number>(), |
||||
|
minSizes: new Array<number>(), |
||||
|
instance: null, |
||||
|
//@ts-ignore |
||||
|
destory: Function |
||||
|
} |
||||
|
|
||||
|
//@ts-ignore |
||||
|
const data = ref(dataVal as IDataContainer) |
||||
|
watch(() => props.direction, (value, oldValue) => { |
||||
|
init() |
||||
|
}) |
||||
|
let useFirstDefaultSize = props.firstAreaDefaultSize && props.firstAreaDefaultSize > 0 |
||||
|
let useLastDefaultSize = props.lastAreaDefaultSize && props.lastAreaDefaultSize > 0 |
||||
|
let splitSumCount = 0 |
||||
|
const init = () => { |
||||
|
if (data.value.instance !== null) { |
||||
|
data.value.instance?.destroy() |
||||
|
} |
||||
|
let splitCount = 0 |
||||
|
data.value.instance = null |
||||
|
const splitOptions = { |
||||
|
direction: props.direction || 'horizontal', |
||||
|
sizes: data.value.sizes || 50, |
||||
|
minSize: data.value.minSizes || 100, |
||||
|
gutterSize: props.gutterSize || 8, |
||||
|
cursor: props.direction === 'horizontal' ? 'col-resize' : 'row-resize', |
||||
|
onDrag: function() { |
||||
|
useFirstDefaultSize = false |
||||
|
useLastDefaultSize = false |
||||
|
ctx.emit('onDrag', data.value.instance?.getSizes()) |
||||
|
}, |
||||
|
onDragStart: function() { |
||||
|
ctx.emit('onDragStart', data.value.instance?.getSizes()) |
||||
|
}, |
||||
|
onDragEnd: function() { |
||||
|
ctx.emit('onDragEnd', data.value.instance?.getSizes()) |
||||
|
} |
||||
|
} |
||||
|
if (useFirstDefaultSize) { |
||||
|
splitOptions['elementStyle'] = function elementStyle(dimension, size, gutterSize) { |
||||
|
splitCount++ |
||||
|
let res |
||||
|
if (splitSumCount === 2 && useFirstDefaultSize && !useLastDefaultSize) { |
||||
|
// 2个split-area & 使用firstAreaDefaultSize & 未使用lastAreaDefaultSize |
||||
|
if (splitCount === 1) { |
||||
|
res = { [dimension]: `calc(${props.firstAreaDefaultSize}px - ${gutterSize}px)` } |
||||
|
} else { |
||||
|
res = { [dimension]: `calc(100% - ${props.firstAreaDefaultSize + gutterSize}px)` } |
||||
|
} |
||||
|
} else if (splitSumCount === 2 && !useFirstDefaultSize && useLastDefaultSize) { |
||||
|
// 2个split-area & 未使用firstAreaDefaultSize & 使用了lastAreaDefaultSize |
||||
|
if (splitCount === 2) { |
||||
|
res = { [dimension]: `calc(${props.lastAreaDefaultSize}px - ${gutterSize}px)` } |
||||
|
} else { |
||||
|
res = { [dimension]: `calc(100% - ${props.lastAreaDefaultSize + gutterSize}px)` } |
||||
|
} |
||||
|
} else if (splitSumCount === 3 && useFirstDefaultSize && useLastDefaultSize) { |
||||
|
// 3个split-area & 使用firstAreaDefaultSize & 使用了lastAreaDefaultSize |
||||
|
if (splitCount === 1) { |
||||
|
res = { [dimension]: `calc(${props.firstAreaDefaultSize}px - ${gutterSize}px)` } |
||||
|
} else if (splitCount === 3) { |
||||
|
res = { [dimension]: `calc(${props.lastAreaDefaultSize}px - ${gutterSize}px)` } |
||||
|
} else { |
||||
|
res = { [dimension]: `calc(100% - ${props.firstAreaDefaultSize + props.lastAreaDefaultSize + gutterSize}px)` } |
||||
|
} |
||||
|
} else { |
||||
|
res = { [dimension]: `calc(${size}% - ${gutterSize}px)` } |
||||
|
} |
||||
|
return res |
||||
|
} |
||||
|
} |
||||
|
//@ts-ignore |
||||
|
data.value.instance = Split(data.value.elements ?? [], |
||||
|
splitOptions as Split.Options |
||||
|
) |
||||
|
} |
||||
|
onMounted(() => { |
||||
|
data.value.elements = [] |
||||
|
data.value.sizes = [] |
||||
|
data.value.minSizes = [] |
||||
|
var parentVal = parent.value as unknown as HTMLElement |
||||
|
|
||||
|
if (parentVal) { |
||||
|
for (var i = 0; i < parentVal.children.length; i++) { |
||||
|
var child = parentVal.children[i] |
||||
|
data.value.elements.push(child) |
||||
|
} |
||||
|
} |
||||
|
if (ctx.slots.default) { |
||||
|
ctx.slots.default()?.forEach((vnode: VNode) => { |
||||
|
const vodeType = vnode.type as any |
||||
|
if (vodeType.name == 'SplitArea') { |
||||
|
if (vnode.props) { |
||||
|
const a = vnode.props as { |
||||
|
size: number; |
||||
|
minSize: number; |
||||
|
} |
||||
|
data.value.sizes?.push(a.size) |
||||
|
data.value.minSizes?.push(a.minSize) |
||||
|
} |
||||
|
} |
||||
|
}) |
||||
|
splitSumCount = ctx.slots.default().length |
||||
|
} |
||||
|
init() |
||||
|
}) |
||||
|
|
||||
|
const reset = () => { |
||||
|
useFirstDefaultSize = props.firstAreaDefaultSize && props.firstAreaDefaultSize > 0 |
||||
|
useLastDefaultSize = props.lastAreaDefaultSize && props.lastAreaDefaultSize > 0 |
||||
|
init() |
||||
|
} |
||||
|
const getSizes = () => { |
||||
|
return data.value.instance?.getSizes() || 0 |
||||
|
} |
||||
|
|
||||
|
return { data, init, parent, reset, getSizes } |
||||
|
} |
||||
|
}) |
||||
|
</script> |
||||
|
|
||||
|
<style> |
||||
|
.split { |
||||
|
-webkit-box-sizing: border-box; |
||||
|
-moz-box-sizing: border-box; |
||||
|
box-sizing: border-box; |
||||
|
overflow-y: auto; |
||||
|
overflow-x: hidden; |
||||
|
height: 100%; |
||||
|
width: 100%; |
||||
|
} |
||||
|
|
||||
|
.gutter { |
||||
|
background-color: #eee; |
||||
|
background-repeat: no-repeat; |
||||
|
background-position: 50%; |
||||
|
} |
||||
|
|
||||
|
.gutter.gutter-horizontal { |
||||
|
cursor: col-resize; |
||||
|
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAeCAYAAADkftS9AAAAIklEQVQoU2M4c+bMfxAGAgYYmwGrIIiDjrELjpo5aiZeMwF+yNnOs5KSvgAAAABJRU5ErkJggg=="); |
||||
|
} |
||||
|
|
||||
|
.gutter.gutter-vertical { |
||||
|
cursor: row-resize; |
||||
|
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB4AAAAFAQMAAABo7865AAAABlBMVEVHcEzMzMzyAv2sAAAAAXRSTlMAQObYZgAAABBJREFUeF5jOAMEEAIEEFwAn3kMwcB6I2AAAAAASUVORK5CYII="); |
||||
|
} |
||||
|
|
||||
|
.split.split-horizontal, |
||||
|
.gutter.gutter-horizontal { |
||||
|
height: 100%; |
||||
|
float: left; |
||||
|
} |
||||
|
</style> |
||||
@ -0,0 +1,47 @@ |
|||||
|
import { camelize } from "../util/string"; |
||||
|
import { events, isReadOnly } from "./sortableEvents"; |
||||
|
import { isHtmlAttribute } from "../util/tags"; |
||||
|
|
||||
|
function project(entries) { |
||||
|
return entries.reduce((res, [key, value]) => { |
||||
|
res[key] = value; |
||||
|
return res; |
||||
|
}, {}); |
||||
|
} |
||||
|
|
||||
|
function getComponentAttributes({ $attrs, componentData = {} }) { |
||||
|
const attributes = project( |
||||
|
Object.entries($attrs).filter(([key, _]) => isHtmlAttribute(key)) |
||||
|
); |
||||
|
return { |
||||
|
...attributes, |
||||
|
...componentData |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
function createSortableOption({ $attrs, callBackBuilder }) { |
||||
|
const options = project(getValidSortableEntries($attrs)); |
||||
|
Object.entries(callBackBuilder).forEach(([eventType, eventBuilder]) => { |
||||
|
events[eventType].forEach(event => { |
||||
|
options[`on${event}`] = eventBuilder(event); |
||||
|
}); |
||||
|
}); |
||||
|
const draggable = `[data-draggable]${options.draggable || ""}`; |
||||
|
return { |
||||
|
...options, |
||||
|
draggable |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
function getValidSortableEntries(value) { |
||||
|
return Object.entries(value) |
||||
|
.filter(([key, _]) => !isHtmlAttribute(key)) |
||||
|
.map(([key, value]) => [camelize(key), value]) |
||||
|
.filter(([key, _]) => !isReadOnly(key)); |
||||
|
} |
||||
|
|
||||
|
export { |
||||
|
getComponentAttributes, |
||||
|
createSortableOption, |
||||
|
getValidSortableEntries |
||||
|
}; |
||||
@ -0,0 +1,78 @@ |
|||||
|
//const getHtmlElementFromNode = ({ el }) => el;
|
||||
|
const getHtmlElementFromNode = node => { |
||||
|
const el = |
||||
|
node.el || (Array.isArray(node.children) && node.children[0].el.parentNode); |
||||
|
if (!el) { |
||||
|
console.error("使用 transition-group , 需要在slot中template内至少2层html标签"); |
||||
|
} |
||||
|
return el || {}; |
||||
|
}; |
||||
|
|
||||
|
const addContext = (domElement, context) => |
||||
|
(domElement.__draggable_context = context); |
||||
|
const getContext = domElement => domElement.__draggable_context; |
||||
|
|
||||
|
class ComponentStructure { |
||||
|
constructor({ |
||||
|
nodes: { header, default: defaultNodes, footer }, |
||||
|
root, |
||||
|
realList |
||||
|
}) { |
||||
|
this.defaultNodes = defaultNodes; |
||||
|
this.children = [...header, ...defaultNodes, ...footer]; |
||||
|
this.externalComponent = root.externalComponent; |
||||
|
this.rootTransition = root.transition; |
||||
|
this.tag = root.tag; |
||||
|
this.realList = realList; |
||||
|
} |
||||
|
|
||||
|
get _isRootComponent() { |
||||
|
return this.externalComponent || this.rootTransition; |
||||
|
} |
||||
|
|
||||
|
render(h, attributes) { |
||||
|
const { tag, children, _isRootComponent } = this; |
||||
|
const option = !_isRootComponent ? children : { default: () => children }; |
||||
|
return h(tag, attributes, option); |
||||
|
} |
||||
|
|
||||
|
updated() { |
||||
|
const { defaultNodes, realList } = this; |
||||
|
defaultNodes.forEach((node, index) => { |
||||
|
addContext(getHtmlElementFromNode(node), { |
||||
|
element: realList[index], |
||||
|
index |
||||
|
}); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
getUnderlyingVm(domElement) { |
||||
|
return getContext(domElement); |
||||
|
} |
||||
|
|
||||
|
getVmIndexFromDomIndex(domIndex, element) { |
||||
|
const { defaultNodes } = this; |
||||
|
const { length } = defaultNodes; |
||||
|
const domChildren = element.children; |
||||
|
const domElement = domChildren.item(domIndex); |
||||
|
|
||||
|
if (domElement === null) { |
||||
|
return length; |
||||
|
} |
||||
|
const context = getContext(domElement); |
||||
|
if (context) { |
||||
|
return context.index; |
||||
|
} |
||||
|
|
||||
|
if (length === 0) { |
||||
|
return 0; |
||||
|
} |
||||
|
const firstDomListElement = getHtmlElementFromNode(defaultNodes[0]); |
||||
|
const indexFirstDomListElement = [...domChildren].findIndex( |
||||
|
element => element === firstDomListElement |
||||
|
); |
||||
|
return domIndex < indexFirstDomListElement ? 0 : length; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export { ComponentStructure }; |
||||
@ -0,0 +1,56 @@ |
|||||
|
import { ComponentStructure } from "./componentStructure"; |
||||
|
import { isHtmlTag, isTransition } from "../util/tags"; |
||||
|
import { resolveComponent, TransitionGroup } from "vue"; |
||||
|
|
||||
|
function getSlot(slots, key) { |
||||
|
const slotValue = slots[key]; |
||||
|
return slotValue ? slotValue() : []; |
||||
|
} |
||||
|
|
||||
|
function computeNodes({ $slots, realList, getKey }) { |
||||
|
const normalizedList = realList || []; |
||||
|
const [header, footer] = ["header", "footer"].map(name => |
||||
|
getSlot($slots, name) |
||||
|
); |
||||
|
const { item } = $slots; |
||||
|
if (!item) { |
||||
|
throw new Error("draggable element must have an item slot"); |
||||
|
} |
||||
|
const defaultNodes = normalizedList.flatMap((element, index) => |
||||
|
item({ element, index }).map(node => { |
||||
|
node.key = getKey(element); |
||||
|
node.props = { ...(node.props || {}), "data-draggable": true }; |
||||
|
return node; |
||||
|
}) |
||||
|
); |
||||
|
if (defaultNodes.length !== normalizedList.length) { |
||||
|
throw new Error("Item slot must have only one child"); |
||||
|
} |
||||
|
return { |
||||
|
header, |
||||
|
footer, |
||||
|
default: defaultNodes |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
function getRootInformation(tag) { |
||||
|
const transition = isTransition(tag); |
||||
|
const externalComponent = !isHtmlTag(tag) && !transition; |
||||
|
return { |
||||
|
transition, |
||||
|
externalComponent, |
||||
|
tag: externalComponent |
||||
|
? resolveComponent(tag) |
||||
|
: transition |
||||
|
? TransitionGroup |
||||
|
: tag |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
function computeComponentStructure({ $slots, tag, realList, getKey }) { |
||||
|
const nodes = computeNodes({ $slots, realList, getKey }); |
||||
|
const root = getRootInformation(tag); |
||||
|
return new ComponentStructure({ nodes, root, realList }); |
||||
|
} |
||||
|
|
||||
|
export { computeComponentStructure }; |
||||
@ -0,0 +1,18 @@ |
|||||
|
const manageAndEmit = ["Start", "Add", "Remove", "Update", "End"]; |
||||
|
const emit = ["Choose", "Unchoose", "Sort", "Filter", "Clone"]; |
||||
|
const manage = ["Move"]; |
||||
|
const eventHandlerNames = [manage, manageAndEmit, emit] |
||||
|
.flatMap(events => events) |
||||
|
.map(evt => `on${evt}`); |
||||
|
|
||||
|
const events = { |
||||
|
manage, |
||||
|
manageAndEmit, |
||||
|
emit |
||||
|
}; |
||||
|
|
||||
|
function isReadOnly(eventName) { |
||||
|
return eventHandlerNames.indexOf(eventName) !== -1; |
||||
|
} |
||||
|
|
||||
|
export { events, isReadOnly }; |
||||
@ -0,0 +1,9 @@ |
|||||
|
function getConsole() { |
||||
|
if (typeof window !== "undefined") { |
||||
|
return window.console; |
||||
|
} |
||||
|
return global.console; |
||||
|
} |
||||
|
const console = getConsole(); |
||||
|
|
||||
|
export { console }; |
||||
@ -0,0 +1,15 @@ |
|||||
|
function removeNode(node) { |
||||
|
if (node.parentElement !== null) { |
||||
|
node.parentElement.removeChild(node); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
function insertNodeAt(fatherNode, node, position) { |
||||
|
const refNode = |
||||
|
position === 0 |
||||
|
? fatherNode.children[0] |
||||
|
: fatherNode.children[position - 1].nextSibling; |
||||
|
fatherNode.insertBefore(node, refNode); |
||||
|
} |
||||
|
|
||||
|
export { insertNodeAt, removeNode }; |
||||
@ -0,0 +1,12 @@ |
|||||
|
function cached(fn) { |
||||
|
const cache = Object.create(null); |
||||
|
return function cachedFn(str) { |
||||
|
const hit = cache[str]; |
||||
|
return hit || (cache[str] = fn(str)); |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
const regex = /-(\w)/g; |
||||
|
const camelize = cached(str => str.replace(regex, (_, c) => c.toUpperCase())); |
||||
|
|
||||
|
export { camelize }; |
||||
@ -0,0 +1,138 @@ |
|||||
|
const tags = [ |
||||
|
"a", |
||||
|
"abbr", |
||||
|
"address", |
||||
|
"area", |
||||
|
"article", |
||||
|
"aside", |
||||
|
"audio", |
||||
|
"b", |
||||
|
"base", |
||||
|
"bdi", |
||||
|
"bdo", |
||||
|
"blockquote", |
||||
|
"body", |
||||
|
"br", |
||||
|
"button", |
||||
|
"canvas", |
||||
|
"caption", |
||||
|
"cite", |
||||
|
"code", |
||||
|
"col", |
||||
|
"colgroup", |
||||
|
"data", |
||||
|
"datalist", |
||||
|
"dd", |
||||
|
"del", |
||||
|
"details", |
||||
|
"dfn", |
||||
|
"dialog", |
||||
|
"div", |
||||
|
"dl", |
||||
|
"dt", |
||||
|
"em", |
||||
|
"embed", |
||||
|
"fieldset", |
||||
|
"figcaption", |
||||
|
"figure", |
||||
|
"footer", |
||||
|
"form", |
||||
|
"h1", |
||||
|
"h2", |
||||
|
"h3", |
||||
|
"h4", |
||||
|
"h5", |
||||
|
"h6", |
||||
|
"head", |
||||
|
"header", |
||||
|
"hgroup", |
||||
|
"hr", |
||||
|
"html", |
||||
|
"i", |
||||
|
"iframe", |
||||
|
"img", |
||||
|
"input", |
||||
|
"ins", |
||||
|
"kbd", |
||||
|
"label", |
||||
|
"legend", |
||||
|
"li", |
||||
|
"link", |
||||
|
"main", |
||||
|
"map", |
||||
|
"mark", |
||||
|
"math", |
||||
|
"menu", |
||||
|
"menuitem", |
||||
|
"meta", |
||||
|
"meter", |
||||
|
"nav", |
||||
|
"noscript", |
||||
|
"object", |
||||
|
"ol", |
||||
|
"optgroup", |
||||
|
"option", |
||||
|
"output", |
||||
|
"p", |
||||
|
"param", |
||||
|
"picture", |
||||
|
"pre", |
||||
|
"progress", |
||||
|
"q", |
||||
|
"rb", |
||||
|
"rp", |
||||
|
"rt", |
||||
|
"rtc", |
||||
|
"ruby", |
||||
|
"s", |
||||
|
"samp", |
||||
|
"script", |
||||
|
"section", |
||||
|
"select", |
||||
|
"slot", |
||||
|
"small", |
||||
|
"source", |
||||
|
"span", |
||||
|
"strong", |
||||
|
"style", |
||||
|
"sub", |
||||
|
"summary", |
||||
|
"sup", |
||||
|
"svg", |
||||
|
"table", |
||||
|
"tbody", |
||||
|
"td", |
||||
|
"template", |
||||
|
"textarea", |
||||
|
"tfoot", |
||||
|
"th", |
||||
|
"thead", |
||||
|
"time", |
||||
|
"title", |
||||
|
"tr", |
||||
|
"track", |
||||
|
"u", |
||||
|
"ul", |
||||
|
"var", |
||||
|
"video", |
||||
|
"wbr" |
||||
|
]; |
||||
|
|
||||
|
function isHtmlTag(name) { |
||||
|
return tags.includes(name); |
||||
|
} |
||||
|
|
||||
|
function isTransition(name) { |
||||
|
return ["transition-group", "TransitionGroup"].includes(name); |
||||
|
} |
||||
|
|
||||
|
function isHtmlAttribute(value) { |
||||
|
return ( |
||||
|
["id", "class", "role", "style"].includes(value) || |
||||
|
value.startsWith("data-") || |
||||
|
value.startsWith("aria-") || |
||||
|
value.startsWith("on") |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
export { isHtmlTag, isHtmlAttribute, isTransition }; |
||||
@ -0,0 +1,318 @@ |
|||||
|
import Sortable from "sortablejs"; |
||||
|
import {insertNodeAt, removeNode} from "./util/htmlHelper"; |
||||
|
import {console} from "./util/console"; |
||||
|
import {createSortableOption, getComponentAttributes, getValidSortableEntries} from "./core/componentBuilderHelper"; |
||||
|
import {computeComponentStructure} from "./core/renderHelper"; |
||||
|
import {events} from "./core/sortableEvents"; |
||||
|
import {defineComponent, h, nextTick} from "vue"; |
||||
|
|
||||
|
function emit(evtName, evtData) { |
||||
|
evtData.realList = this.list // 罗一帆 2024-01-20 增加参数
|
||||
|
nextTick(() => this.$emit(evtName.toLowerCase(), evtData)); |
||||
|
} |
||||
|
|
||||
|
function manage(evtName) { |
||||
|
return (evtData, originalElement) => { |
||||
|
if (this.realList !== null) { |
||||
|
return this[`onDrag${evtName}`](evtData, originalElement); |
||||
|
} |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
function manageAndEmit(evtName) { |
||||
|
const delegateCallBack = manage.call(this, evtName); |
||||
|
return (evtData, originalElement) => { |
||||
|
delegateCallBack.call(this, evtData, originalElement); |
||||
|
emit.call(this, evtName, evtData); |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
let draggingElement = null; |
||||
|
|
||||
|
const props = { |
||||
|
list: { |
||||
|
type: Array, |
||||
|
required: false, |
||||
|
default: null |
||||
|
}, |
||||
|
modelValue: { |
||||
|
type: Array, |
||||
|
required: false, |
||||
|
default: null |
||||
|
}, |
||||
|
itemKey: { |
||||
|
type: [String, Function], |
||||
|
required: true |
||||
|
}, |
||||
|
clone: { |
||||
|
type: Function, |
||||
|
default: original => { |
||||
|
return original; |
||||
|
} |
||||
|
}, |
||||
|
tag: { |
||||
|
type: String, |
||||
|
default: "div" |
||||
|
}, |
||||
|
move: { |
||||
|
type: Function, |
||||
|
default: null |
||||
|
}, |
||||
|
componentData: { |
||||
|
type: Object, |
||||
|
required: false, |
||||
|
default: null |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
const emits = [ |
||||
|
"update:modelValue", |
||||
|
"change", |
||||
|
...[...events.manageAndEmit, ...events.emit].map(evt => evt.toLowerCase()) |
||||
|
]; |
||||
|
|
||||
|
const draggableComponent = defineComponent({ |
||||
|
name: "draggable", |
||||
|
|
||||
|
inheritAttrs: false, |
||||
|
|
||||
|
props, |
||||
|
|
||||
|
emits, |
||||
|
|
||||
|
data() { |
||||
|
return { |
||||
|
error: false |
||||
|
}; |
||||
|
}, |
||||
|
|
||||
|
render() { |
||||
|
try { |
||||
|
this.error = false; |
||||
|
const {$slots, $attrs, tag, componentData, realList, getKey} = this; |
||||
|
const componentStructure = computeComponentStructure({ |
||||
|
$slots, |
||||
|
tag, |
||||
|
realList, |
||||
|
getKey |
||||
|
}); |
||||
|
this.componentStructure = componentStructure; |
||||
|
const attributes = getComponentAttributes({$attrs, componentData}); |
||||
|
return componentStructure.render(h, attributes); |
||||
|
} catch (err) { |
||||
|
this.error = true; |
||||
|
return h("pre", {style: {color: "red"}}, err.stack); |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
created() { |
||||
|
if (this.list !== null && this.modelValue !== null) { |
||||
|
console.error( |
||||
|
"modelValue and list props are mutually exclusive! Please set one or another." |
||||
|
); |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
mounted() { |
||||
|
if (this.error) { |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
const {$attrs, $el, componentStructure} = this; |
||||
|
componentStructure.updated(); |
||||
|
|
||||
|
const sortableOptions = createSortableOption({ |
||||
|
$attrs, |
||||
|
callBackBuilder: { |
||||
|
manageAndEmit: event => manageAndEmit.call(this, event), |
||||
|
emit: event => emit.bind(this, event), |
||||
|
manage: event => manage.call(this, event) |
||||
|
} |
||||
|
}); |
||||
|
const targetDomElement = $el.nodeType === 1 ? $el : $el.parentElement; |
||||
|
this._sortable = new Sortable(targetDomElement, sortableOptions); |
||||
|
this.targetDomElement = targetDomElement; |
||||
|
targetDomElement.__draggable_component__ = this; |
||||
|
}, |
||||
|
|
||||
|
updated() { |
||||
|
this.componentStructure.updated(); |
||||
|
}, |
||||
|
|
||||
|
beforeUnmount() { |
||||
|
if (this._sortable !== undefined) this._sortable.destroy(); |
||||
|
}, |
||||
|
|
||||
|
computed: { |
||||
|
realList() { |
||||
|
const {list} = this; |
||||
|
return list ? list : this.modelValue; |
||||
|
}, |
||||
|
|
||||
|
getKey() { |
||||
|
const {itemKey} = this; |
||||
|
if (typeof itemKey === "function") { |
||||
|
return itemKey; |
||||
|
} |
||||
|
return element => element[itemKey]; |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
watch: { |
||||
|
$attrs: { |
||||
|
handler(newOptionValue) { |
||||
|
const {_sortable} = this; |
||||
|
if (!_sortable) return; |
||||
|
getValidSortableEntries(newOptionValue).forEach(([key, value]) => { |
||||
|
_sortable.option(key, value); |
||||
|
}); |
||||
|
}, |
||||
|
deep: true |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
methods: { |
||||
|
getUnderlyingVm(domElement) { |
||||
|
return this.componentStructure.getUnderlyingVm(domElement) || null; |
||||
|
}, |
||||
|
|
||||
|
getUnderlyingPotencialDraggableComponent(htmElement) { |
||||
|
//TODO check case where you need to see component children
|
||||
|
return htmElement.__draggable_component__; |
||||
|
}, |
||||
|
|
||||
|
emitChanges(evt) { |
||||
|
nextTick(() => this.$emit("change", evt)); |
||||
|
}, |
||||
|
|
||||
|
alterList(onList) { |
||||
|
if (this.list) { |
||||
|
onList(this.list); |
||||
|
return; |
||||
|
} |
||||
|
const newList = [...this.modelValue]; |
||||
|
onList(newList); |
||||
|
this.$emit("update:modelValue", newList); |
||||
|
}, |
||||
|
|
||||
|
spliceList() { |
||||
|
// @ts-ignore
|
||||
|
const spliceList = list => list.splice(...arguments); |
||||
|
this.alterList(spliceList); |
||||
|
}, |
||||
|
|
||||
|
updatePosition(oldIndex, newIndex) { |
||||
|
const updatePosition = list => |
||||
|
list.splice(newIndex, 0, list.splice(oldIndex, 1)[0]); |
||||
|
this.alterList(updatePosition); |
||||
|
}, |
||||
|
|
||||
|
getRelatedContextFromMoveEvent({to, related}) { |
||||
|
const component = this.getUnderlyingPotencialDraggableComponent(to); |
||||
|
if (!component) { |
||||
|
return {component}; |
||||
|
} |
||||
|
const list = component.realList; |
||||
|
const context = {list, component}; |
||||
|
if (to !== related && list) { |
||||
|
const destination = component.getUnderlyingVm(related) || {}; |
||||
|
return {...destination, ...context}; |
||||
|
} |
||||
|
return context; |
||||
|
}, |
||||
|
|
||||
|
getVmIndexFromDomIndex(domIndex) { |
||||
|
return this.componentStructure.getVmIndexFromDomIndex( |
||||
|
domIndex, |
||||
|
this.targetDomElement |
||||
|
); |
||||
|
}, |
||||
|
|
||||
|
onDragStart(evt) { |
||||
|
this.context = this.getUnderlyingVm(evt.item); |
||||
|
evt.item._underlying_vm_ = this.clone(this.context.element); |
||||
|
draggingElement = evt.item; |
||||
|
}, |
||||
|
|
||||
|
onDragAdd(evt) { |
||||
|
const element = evt.item._underlying_vm_; |
||||
|
if (element === undefined) { |
||||
|
return; |
||||
|
} |
||||
|
removeNode(evt.item); |
||||
|
const newIndex = this.getVmIndexFromDomIndex(evt.newIndex); |
||||
|
// @ts-ignore
|
||||
|
this.spliceList(newIndex, 0, element); |
||||
|
const added = {element, newIndex}; |
||||
|
this.emitChanges({added}); |
||||
|
}, |
||||
|
|
||||
|
onDragRemove(evt) { |
||||
|
//insertNodeAt(this.$el, evt.item, evt.oldIndex);
|
||||
|
insertNodeAt(evt.from, evt.item, evt.oldIndex); |
||||
|
if (evt.pullMode === "clone") { |
||||
|
removeNode(evt.clone); |
||||
|
return; |
||||
|
} |
||||
|
const {index: oldIndex, element} = this.context; |
||||
|
// @ts-ignore
|
||||
|
this.spliceList(oldIndex, 1); |
||||
|
const removed = {element, oldIndex}; |
||||
|
this.emitChanges({removed}); |
||||
|
}, |
||||
|
|
||||
|
onDragUpdate(evt) { |
||||
|
removeNode(evt.item); |
||||
|
insertNodeAt(evt.from, evt.item, evt.oldIndex); |
||||
|
const oldIndex = this.context.index; |
||||
|
const newIndex = this.getVmIndexFromDomIndex(evt.newIndex); |
||||
|
this.updatePosition(oldIndex, newIndex); |
||||
|
const moved = {element: this.context.element, oldIndex, newIndex}; |
||||
|
this.emitChanges({moved}); |
||||
|
}, |
||||
|
|
||||
|
computeFutureIndex(relatedContext, evt) { |
||||
|
if (!relatedContext.element) { |
||||
|
return 0; |
||||
|
} |
||||
|
const domChildren = [...evt.to.children].filter( |
||||
|
el => el.style["display"] !== "none" |
||||
|
); |
||||
|
const currentDomIndex = domChildren.indexOf(evt.related); |
||||
|
const currentIndex = relatedContext.component.getVmIndexFromDomIndex( |
||||
|
currentDomIndex |
||||
|
); |
||||
|
const draggedInList = domChildren.indexOf(draggingElement) !== -1; |
||||
|
return draggedInList || !evt.willInsertAfter |
||||
|
? currentIndex |
||||
|
: currentIndex + 1; |
||||
|
}, |
||||
|
|
||||
|
onDragMove(evt, originalEvent) { |
||||
|
const {move, realList} = this; |
||||
|
if (!move || !realList) { |
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
const relatedContext = this.getRelatedContextFromMoveEvent(evt); |
||||
|
const futureIndex = this.computeFutureIndex(relatedContext, evt); |
||||
|
const draggedContext = { |
||||
|
realList, // 罗一帆 2024-01-20 增加参数
|
||||
|
...this.context, |
||||
|
futureIndex |
||||
|
}; |
||||
|
const sendEvent = { |
||||
|
...evt, |
||||
|
relatedContext, |
||||
|
draggedContext |
||||
|
}; |
||||
|
return move(sendEvent, originalEvent); |
||||
|
}, |
||||
|
|
||||
|
onDragEnd() { |
||||
|
draggingElement = null; |
||||
|
} |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
export default draggableComponent; |
||||
@ -0,0 +1,19 @@ |
|||||
|
import { defineAsyncComponent } from 'vue' |
||||
|
import Split from './split/split.vue' |
||||
|
import SplitArea from './split/split-area.vue' |
||||
|
import draggable from './vuedraggable/vuedraggable' |
||||
|
|
||||
|
export const install = function(Vue: any) { |
||||
|
Vue.component('ag-grid-vue', defineAsyncComponent(() => { |
||||
|
return Promise.all([ |
||||
|
import('ag-grid-vue3'), |
||||
|
import ('./ag_grid_license') |
||||
|
]).then(([module, license]) => { |
||||
|
return module.AgGridVue |
||||
|
}) |
||||
|
})) |
||||
|
|
||||
|
Vue.component('draggable', draggable) |
||||
|
Vue.component('split', Split) |
||||
|
Vue.component('split-area', SplitArea) |
||||
|
} |
||||
@ -0,0 +1,38 @@ |
|||||
|
import _ from 'lodash' |
||||
|
import {type Component, h, toRaw} from 'vue' |
||||
|
import {ElIcon} from "element-plus" |
||||
|
import * as FaIcon from '@vicons/fa' |
||||
|
import * as ElementPlusIconsVue from '@element-plus/icons-vue' |
||||
|
|
||||
|
/** |
||||
|
* 渲染图标 |
||||
|
*/ |
||||
|
export function renderIcon(icon: string, props = {}): any { |
||||
|
if (!icon) { |
||||
|
return undefined; |
||||
|
} |
||||
|
let component: any = undefined; |
||||
|
if (typeof icon === 'string') { |
||||
|
if (icon.startsWith("element ")) { |
||||
|
icon = icon.substring("element ".length); |
||||
|
component = ElementPlusIconsVue[icon]; |
||||
|
|
||||
|
} else if (icon.startsWith("fa ")) { |
||||
|
icon = icon.substring("fa ".length); |
||||
|
component = FaIcon[icon]; |
||||
|
|
||||
|
// } else if (icon.startsWith('tb ')) {
|
||||
|
// icon = icon.substring("fa ".length);
|
||||
|
// component = Tabler[icon];
|
||||
|
|
||||
|
} else { |
||||
|
component = ElementPlusIconsVue[icon]; |
||||
|
if (!component) component = FaIcon[icon]; |
||||
|
} |
||||
|
} |
||||
|
if (!component) { |
||||
|
// component = AntdIcon["ProfileOutlined"];
|
||||
|
return undefined; |
||||
|
} |
||||
|
return () => h(ElIcon, props, {default: () => h(component)}); |
||||
|
} |
||||
@ -0,0 +1,232 @@ |
|||||
|
.app-wrap{ |
||||
|
height: 100%; |
||||
|
overflow: hidden; |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
.app-header{ |
||||
|
height:50px; |
||||
|
background: #545c64; |
||||
|
flex-shrink: 0; |
||||
|
display: flex; |
||||
|
flex-direction: row; |
||||
|
overflow: hidden; |
||||
|
.logo{ |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
margin: 0 20px; |
||||
|
} |
||||
|
.el-menu-item{ |
||||
|
line-height: 50px; |
||||
|
} |
||||
|
.el-menu--horizontal{ |
||||
|
height: 50px; |
||||
|
flex:1; |
||||
|
border: none; |
||||
|
&>.el-menu-item{ |
||||
|
height: 50px; |
||||
|
} |
||||
|
&>.el-sub-menu { |
||||
|
height: 50px; |
||||
|
.el-sub-menu__title{ |
||||
|
height: 50px; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
.user{ |
||||
|
display: flex; |
||||
|
flex-direction: row; |
||||
|
align-items: center; |
||||
|
margin-right: 10px; |
||||
|
&>span{ |
||||
|
display: inline-flex; |
||||
|
padding:5px; |
||||
|
background: #f4c521; |
||||
|
border-radius:15px; |
||||
|
color:#fff; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
.app-section{ |
||||
|
flex:1; |
||||
|
display: flex; |
||||
|
flex-direction: row; |
||||
|
.btns-toolbar{ |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
.btns{ |
||||
|
.item{ |
||||
|
height: 48px; |
||||
|
line-height: 48px; |
||||
|
text-align: center; |
||||
|
cursor: pointer; |
||||
|
font-size: 14px; |
||||
|
&:hover{ |
||||
|
background: #cccccc; |
||||
|
} |
||||
|
&.selected{ |
||||
|
background: #e8e8e8; |
||||
|
position: relative; |
||||
|
&:before{ |
||||
|
content: ''; |
||||
|
position: absolute; |
||||
|
width: 3px; |
||||
|
height: 100%; |
||||
|
background: #f4c521; |
||||
|
left:0; |
||||
|
top:0; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
.btns-top{ |
||||
|
flex:1; |
||||
|
} |
||||
|
.btns-bottom{ |
||||
|
|
||||
|
} |
||||
|
} |
||||
|
.btns-toolbar-left{ |
||||
|
flex-shrink: 0; |
||||
|
width:50px; |
||||
|
border-right:1px solid #dcdcdc |
||||
|
} |
||||
|
.btns-toolbar-right{ |
||||
|
flex-shrink: 0; |
||||
|
width:50px; |
||||
|
border-left:1px solid #dcdcdc; |
||||
|
&.btns-toolbar{ |
||||
|
.btns .item.selected:before{ |
||||
|
right:0; |
||||
|
left:auto; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
.section{ |
||||
|
flex:1; |
||||
|
overflow: hidden; |
||||
|
.section-item-wrap{ |
||||
|
height: 100%; |
||||
|
&>.title{ |
||||
|
border-bottom:1px solid #dcdcdc; |
||||
|
height: 35px; |
||||
|
line-height: 35px; |
||||
|
padding:0 0 0 10px; |
||||
|
font-size: 14px; |
||||
|
} |
||||
|
} |
||||
|
.section-tabs.el-tabs--card{ |
||||
|
height: 100%; |
||||
|
&>.el-tabs__header{ |
||||
|
box-sizing: border-box; |
||||
|
z-index: 0; |
||||
|
margin:0; |
||||
|
&>.el-tabs__nav-wrap{ |
||||
|
margin-bottom:0 |
||||
|
} |
||||
|
.el-tabs__item.is-active{ |
||||
|
position: relative; |
||||
|
z-index: 1; |
||||
|
&:before{ |
||||
|
content: ''; |
||||
|
width: 100%; |
||||
|
height: 1px; |
||||
|
background: #c61429; |
||||
|
position: absolute; |
||||
|
left:0; |
||||
|
top:0; |
||||
|
z-index: 999; |
||||
|
} |
||||
|
&:after{ |
||||
|
content: ''; |
||||
|
width: 100%; |
||||
|
height: 1px; |
||||
|
background: #fff; |
||||
|
position: absolute; |
||||
|
left:0; |
||||
|
bottom:0; |
||||
|
z-index: 999; |
||||
|
} |
||||
|
&:hover{ |
||||
|
&:after{ |
||||
|
background:#c5c5c5; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
.el-tabs__item{ |
||||
|
border-bottom:0; |
||||
|
} |
||||
|
.el-tabs__nav-prev{ |
||||
|
height: 40px; |
||||
|
background: #c9c9c9; |
||||
|
.el-icon{ |
||||
|
color:#c61429 |
||||
|
} |
||||
|
} |
||||
|
.el-tabs__nav-next{ |
||||
|
height: 40px; |
||||
|
background: #c9c9c9; |
||||
|
.el-icon{ |
||||
|
color:#c61429 |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
&>.el-tabs__content{ |
||||
|
flex:1; |
||||
|
&>.el-tab-pane{ |
||||
|
height: 100%; |
||||
|
} |
||||
|
.section-canvas{ |
||||
|
height: 100%; |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
.section-toolbar{ |
||||
|
flex-shrink: 0; |
||||
|
height: 30px; |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
.el-button{ |
||||
|
margin-left: 5px; |
||||
|
} |
||||
|
.section-toolbar-line{ |
||||
|
width: 1px; |
||||
|
height: 16px; |
||||
|
background: #dcdcdc; |
||||
|
margin:0 5px; |
||||
|
} |
||||
|
&.section-bottom-toolbar{ |
||||
|
justify-content: space-between; |
||||
|
.section-toolbar-left{ |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
} |
||||
|
.section-toolbar-right{ |
||||
|
display: flex; |
||||
|
flex-direction: row; |
||||
|
align-items: center; |
||||
|
.infor{ |
||||
|
background: #000; |
||||
|
margin:0 5px; |
||||
|
color:#fff; |
||||
|
font-size: 12px; |
||||
|
min-width: 120px; |
||||
|
text-align: center; |
||||
|
padding:3px 5px; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
.section-content{ |
||||
|
flex:1; |
||||
|
background: #e0e0e0; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.el-popper .el-divider--horizontal{ |
||||
|
margin:5px 0; |
||||
|
border-color:#656668 |
||||
|
} |
||||
@ -0,0 +1,208 @@ |
|||||
|
<template> |
||||
|
<div class="app-wrap"> |
||||
|
<div class="app-header"> |
||||
|
<div class="logo"><img :src="Logo" alt="" style="height: 30px;width: 169px"></div> |
||||
|
<el-menu |
||||
|
:default-active="activeIndex" |
||||
|
class="el-menu-demo" |
||||
|
mode="horizontal" |
||||
|
background-color="#545c64" |
||||
|
text-color="#fff" |
||||
|
active-text-color="#ffd04b" |
||||
|
@select="handleSelect" |
||||
|
> |
||||
|
<el-sub-menu index="1"> |
||||
|
<template #title>文件模型</template> |
||||
|
<el-menu-item index="1-1">新建模型</el-menu-item> |
||||
|
<el-menu-item index="1-2">保存</el-menu-item> |
||||
|
<el-divider></el-divider> |
||||
|
<el-menu-item index="1-3">导出</el-menu-item> |
||||
|
<el-menu-item index="1-4">导入</el-menu-item> |
||||
|
<el-divider></el-divider> |
||||
|
<el-menu-item index="1-5">退出</el-menu-item> |
||||
|
</el-sub-menu> |
||||
|
<el-sub-menu index="2"> |
||||
|
<template #title>编辑</template> |
||||
|
<el-menu-item index="2-1">查找</el-menu-item> |
||||
|
<el-divider></el-divider> |
||||
|
<el-menu-item index="2-2">复制</el-menu-item> |
||||
|
<el-menu-item index="2-3">粘贴</el-menu-item> |
||||
|
<el-menu-item index="2-4">删除</el-menu-item> |
||||
|
</el-sub-menu> |
||||
|
<el-sub-menu index="3"> |
||||
|
<template #title>仿真</template> |
||||
|
<el-menu-item index="3-1">开始仿真</el-menu-item> |
||||
|
<el-divider></el-divider> |
||||
|
<el-menu-item index="3-2">停止仿真</el-menu-item> |
||||
|
<el-divider></el-divider> |
||||
|
<el-menu-item index="3-3">暂停</el-menu-item> |
||||
|
<el-menu-item index="3-4">继续</el-menu-item> |
||||
|
<el-menu-item index="3-5">仿真速度X1</el-menu-item> |
||||
|
<el-menu-item index="3-6">仿真速度X2</el-menu-item> |
||||
|
<el-menu-item index="3-7">仿真速度X3</el-menu-item> |
||||
|
<el-menu-item index="3-8">仿真速度X4</el-menu-item> |
||||
|
<el-menu-item index="3-9">仿真速度X5</el-menu-item> |
||||
|
</el-sub-menu> |
||||
|
<el-menu-item index="4">基础数据</el-menu-item> |
||||
|
<el-menu-item index="5">小工具</el-menu-item> |
||||
|
</el-menu> |
||||
|
<div class="user"> |
||||
|
<span> |
||||
|
<component :is="renderIcon('element User')"></component> |
||||
|
</span> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="app-section"> |
||||
|
<div class="btns-toolbar btns-toolbar-left"> |
||||
|
<div class="btns btns-top"> |
||||
|
<div :class="['item',sectionLeftTitle==='模型'?'selected':'']" @click="btnLeftMe('模型')">模型</div> |
||||
|
<div :class="['item',sectionLeftTitle==='监控'?'selected':'']" @click="btnLeftMe('监控')">监控</div> |
||||
|
<div :class="['item',sectionLeftTitle==='对象'?'selected':'']" @click="btnLeftMe('对象')">对象</div> |
||||
|
</div> |
||||
|
<div class="btns btns-bottom"> |
||||
|
<div :class="['item',sectionBottomTitle==='任务'?'selected':'']" @click="btnBottomMe('任务')">任务</div> |
||||
|
<div :class="['item',sectionBottomTitle==='日志'?'selected':'']" @click="btnBottomMe('控制')">日志</div> |
||||
|
<div :class="['item',sectionBottomTitle==='脚本'?'selected':'']" @click="btnBottomMe('监控')">脚本</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
<Split class="section" :direction="'vertical'" style="flex-shrink: 0;flex-grow:1;"> |
||||
|
<SplitArea :class="['section-top']" :size="100 - defaultSize"> |
||||
|
<Split class="section" :direction="'horizontal'" style="flex-shrink: 0;flex-grow:1;"> |
||||
|
<SplitArea :class="['section-left']" :style="{width:sectionLeft}" :size="sectionLeftSize"> |
||||
|
<div class="section-item-wrap"> |
||||
|
<div class="title">{{ sectionLeftTitle }}</div> |
||||
|
</div> |
||||
|
</SplitArea> |
||||
|
<SplitArea :class="['section-center']" :size="100 - sectionLeftSize - sectionRightSize"> |
||||
|
<el-tabs type="card" class="section-tabs"> |
||||
|
<el-tab-pane label="标签1"> |
||||
|
<div class="section-canvas"> |
||||
|
<div class="section-top-toolbar section-toolbar"> |
||||
|
<el-button type="primary" :icon="renderIcon('element TopLeft')" link></el-button> |
||||
|
<span class="section-toolbar-line"></span> |
||||
|
<el-button type="primary" :icon="renderIcon('element TopRight')" link></el-button> |
||||
|
<span class="section-toolbar-line"></span> |
||||
|
<el-button type="primary" :icon="renderIcon('icon5 Resize')" link>尺寸</el-button> |
||||
|
<span class="section-toolbar-line"></span> |
||||
|
<el-button type="primary" :icon="renderIcon('antd SwitcherOutlined')" link>图层</el-button> |
||||
|
<span class="section-toolbar-line"></span> |
||||
|
<el-button type="primary" :icon="renderIcon('element Setting')" link>设置</el-button> |
||||
|
</div> |
||||
|
<div class="section-content"></div> |
||||
|
<div class="section-bottom-toolbar section-toolbar"> |
||||
|
<div class="section-toolbar-left"> |
||||
|
<el-button type="primary" :icon="renderIcon('fa MousePointer')" link></el-button> |
||||
|
<span class="section-toolbar-line"></span> |
||||
|
<el-button type="primary" :icon="renderIcon('element Aim')" link></el-button> |
||||
|
<span class="section-toolbar-line"></span> |
||||
|
<el-button type="primary" :icon="renderIcon('antd LineOutlined')" link></el-button> |
||||
|
<span class="section-toolbar-line"></span> |
||||
|
<el-button type="primary" :icon="renderIcon('icon5 BandageSharp')" link></el-button> |
||||
|
<span class="section-toolbar-line"></span> |
||||
|
<el-button type="primary" :icon="renderIcon('antd InsertRowLeftOutlined')" link></el-button> |
||||
|
</div> |
||||
|
<div class="section-toolbar-right"> |
||||
|
<el-button type="primary" size="small" round>清空选择</el-button> |
||||
|
<div class="infor"> |
||||
|
X=14.091,Y=12.397 |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</el-tab-pane> |
||||
|
<el-tab-pane label="标签2">标签2</el-tab-pane> |
||||
|
<el-tab-pane label="标签3">标签3</el-tab-pane> |
||||
|
<el-tab-pane label="标签4">标签4</el-tab-pane> |
||||
|
<el-tab-pane label="标签5">标签5</el-tab-pane> |
||||
|
<el-tab-pane label="标签6">标签6</el-tab-pane> |
||||
|
<el-tab-pane label="标签7">标签7</el-tab-pane> |
||||
|
<el-tab-pane label="标签8">标签8</el-tab-pane> |
||||
|
</el-tabs> |
||||
|
</SplitArea> |
||||
|
<SplitArea :class="['section-right']" :style="{width:sectionRight}" :size="sectionRightSize"> |
||||
|
<div class="section-item-wrap"> |
||||
|
<div class="title">{{ sectionRightTitle }}</div> |
||||
|
</div> |
||||
|
</SplitArea> |
||||
|
</Split> |
||||
|
</SplitArea> |
||||
|
<SplitArea :class="['section-bottom']" :style="{height:appMenuHeight}" :size="defaultSize"> |
||||
|
<div class="section-item-wrap"> |
||||
|
<div class="title">{{ sectionBottomTitle }}</div> |
||||
|
</div> |
||||
|
</SplitArea> |
||||
|
</Split> |
||||
|
<div class="btns-toolbar btns-toolbar-right"> |
||||
|
<div class="btns btns-top"> |
||||
|
<div :class="['item',sectionRightTitle==='属性'?'selected':'']" @click="btnRightMe('属性')">属性</div> |
||||
|
<div :class="['item',sectionRightTitle==='数据'?'selected':'']" @click="btnRightMe('数据')">数据</div> |
||||
|
<div :class="['item',sectionRightTitle==='警告'?'selected':'']" @click="btnRightMe('警告')">警告</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</template> |
||||
|
<script> |
||||
|
import './ModelMain.less' |
||||
|
import Logo from '@/assets/images/logo.png' |
||||
|
import Split from '@/components/split/split.vue' |
||||
|
import SplitArea from '@/components/split/split-area.vue' |
||||
|
import { renderIcon } from '@/utils/webutils.js' |
||||
|
|
||||
|
export default { |
||||
|
components: { |
||||
|
Split, |
||||
|
SplitArea |
||||
|
}, |
||||
|
mounted() { |
||||
|
}, |
||||
|
data() { |
||||
|
return { |
||||
|
Logo, |
||||
|
activeIndex: '', |
||||
|
defaultSize: 30, |
||||
|
appMenuHeight: '300px', |
||||
|
sectionLeft: '300px', |
||||
|
sectionRight: '300px', |
||||
|
sectionLeftSize: 20, |
||||
|
sectionRightSize: 20, |
||||
|
sectionLeftTitle: '模型', |
||||
|
sectionRightTitle: '属性', |
||||
|
sectionBottomTitle: '任务' |
||||
|
} |
||||
|
}, |
||||
|
methods: { |
||||
|
renderIcon, |
||||
|
handleSelect() { |
||||
|
|
||||
|
}, |
||||
|
btnLeftMe(name) { |
||||
|
if (this.sectionLeftTitle === name) { |
||||
|
this.sectionLeft = '0' |
||||
|
this.sectionLeftTitle = '' |
||||
|
} else { |
||||
|
this.sectionLeftTitle = name |
||||
|
this.sectionLeft = '300px' |
||||
|
} |
||||
|
}, |
||||
|
btnRightMe(name) { |
||||
|
if (this.sectionRightTitle === name) { |
||||
|
this.sectionRight = '0' |
||||
|
this.sectionRightTitle = '' |
||||
|
} else { |
||||
|
this.sectionRightTitle = name |
||||
|
this.sectionRight = '300px' |
||||
|
} |
||||
|
}, |
||||
|
btnBottomMe(name) { |
||||
|
if (this.sectionBottomTitle === name) { |
||||
|
this.appMenuHeight = '0' |
||||
|
this.sectionBottomTitle = '' |
||||
|
} else { |
||||
|
this.sectionBottomTitle = name |
||||
|
this.appMenuHeight = '300px' |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
</script> |
||||
Loading…
Reference in new issue