vue-fantable
Version:
Vue table component for huge data
1,917 lines (1,822 loc) • 441 kB
JavaScript
import { inject, ref, computed, watch, openBlock, createElementBlock, normalizeStyle, createElementVNode, normalizeClass, renderSlot, createTextVNode, toDisplayString, provide, createVNode, withDirectives, resolveDirective, h, nextTick, createApp, shallowRef, triggerRef, vModelText, resolveComponent, isVNode, defineComponent } from 'vue';
function debounce(func, wait, immediate) {
let timeout;
return function () {
const context = this;
const args = arguments;
clearTimeout(timeout);
timeout = setTimeout(function () {
timeout = null;
func.apply(context, args);
}, wait);
};
}
/**
* This method is like `clone` except that it recursively clones `value`.
* Object inheritance is preserved.
*
* @since 1.0.0
* @category Lang
* @param {*} value The value to recursively clone.
* @returns {*} Returns the deep cloned value.
* @see clone
* @example
*
* const objects = [{ 'a': 1 }, { 'b': 2 }]
*
* const deep = cloneDeep(objects)
* console.log(deep[0] === objects[0])
* // => false
*/
function deepClone(receive) {
// console.log(typeof receive)
// if (structuredClone) {
// return structuredClone(receive)
// }
return goClone(receive);
}
function goClone(receive) {
if (typeof receive !== 'object' || receive === null) {
return receive;
}
const result = Array.isArray(receive) ? [] : {};
if (Array.isArray(receive)) {
receive.forEach(item => {
if (typeof item === 'object' && item !== null) {
const afterDeep = goClone(item);
result.push(afterDeep);
} else {
result.push(item);
}
});
} else {
Object.keys(receive).forEach(item => {
if (typeof receive[item] !== 'object') {
result[item] = receive[item];
} else {
result[item] = goClone(receive[item]);
}
});
}
return result;
}
/*
* @hasValue
* @desc has value
* @param {array} arr
*/
function hasValue(rec) {
return rec !== null && rec !== undefined;
}
/*
* @isEmptyArray
* @desc is empty array
* @param {array} arr
*/
function isEmptyArray(arr) {
return !(Array.isArray(arr) && arr.length > 0);
}
/*
* @isEmptyValue
* @desc is empty value
* @param {array} arr
*/
function isEmptyValue(value) {
return !(value !== '' && value !== undefined && value !== null);
}
/*
* @isDefined
* @desc is defined
* @param {any} val
*/
function isDefined(val) {
return val !== undefined && val !== null;
}
/*
* @isFunction
* @desc is function
* @param {any} val
*/
function isFunction$1(val) {
return typeof val === 'function';
}
/*
* @isBoolean
* @desc is boolean
* @param {any} val
*/
function isBoolean(val) {
return typeof val === 'boolean';
}
/*
* @isNumber
* @desc is number
* @param {any} val
*/
function isNumber(val) {
return typeof val === 'number';
}
/*
* @getValByUnit
* @desc get value by unit
* @param {number|string} width - 宽度
*/
function getValByUnit(width) {
return typeof width === 'number' ? width + 'px' : width;
}
/*
* @scrollTo
* @desc element scrollTo https://developer.mozilla.org/zh-CN/docs/Web/API/Element/scrollTo
* @param {element} el - element
* @param {object} option - scroll option
*/
function scrollTo(el, option) {
if (isFunction$1(el.scrollTo)) {
el.scrollTo(option);
} else {
const {
top,
left
} = option;
el.scrollTop = top;
el.scrollLeft = left;
}
}
// prefix
const PREFIX_CLS$6 = 've-checkbox-';
// comps name
const COMPS_NAME$9 = {
VE_CHECKBOX: 'VeCheckbox',
VE_CHECKBOX_GROUP: 'VeCheckboxGroup'
};
/*
* @clsName
* @desc get class name
* @param {string} cls - class
*/
function clsName$7(cls) {
return PREFIX_CLS$6 + cls;
}
// prefix
// comps name
const COMPS_NAME$8 = {
VE_CHECKBOX: 'VeCheckbox',
VE_CHECKBOX_GROUP: 'VeCheckboxGroup'
};
const GROUP_SYMBOL = Symbol('groupSymbol');
const GROUP_MODEL_VALUE = Symbol('groupModelValue');
var _export_sfc = (sfc, props) => {
const target = sfc.__vccOpts || sfc;
for (const [key, val] of props) {
target[key] = val;
}
return target;
};
const __default__$1 = {
name: COMPS_NAME$9.VE_CHECKBOX,
};
const _sfc_main$1 = /*@__PURE__*/Object.assign(__default__$1, {
props: {
// 当前 checkbox 选中状态,实现 v-model
modelValue: {
type: [String, Number, Boolean],
default: null,
},
label: {
type: [String],
default: null,
},
// is disabled checked
disabled: Boolean,
// partial selection effect
indeterminate: Boolean,
// 是否是可控组件
isControlled: {
type: Boolean,
default: false,
},
// isControlled 为true 时生效
isSelected: {
type: Boolean,
default: false,
},
},
emits: ['checkedChange', 'update:modelValue'],
setup(__props, { expose: __expose, emit: __emit }) {
__expose();
const fatherGroup = inject(GROUP_SYMBOL, 'default');
const fatherGroupValue = inject(GROUP_MODEL_VALUE, {});
const props = __props;
const emit = __emit;
const model = ref(false);
initModel();
// get label content
function initModel() {
if (hasValue(props.modelValue)) {
model.value = props.modelValue;
} else if (isCheckBoxGroup()) {
model.value = fatherGroupValue.modelValue.includes(props.label);
}
}
const checkboxClass = computed(() => {
const checked = clsName$7('checked');
const disabled = clsName$7('disabled');
const indeterminate = clsName$7('indeterminate');
const content = clsName$7('content');
const result = {
[content]: true,
[checked]: internalIsSelected.value,
[disabled]: props.disabled,
[indeterminate]: props.indeterminate,
};
if (isCheckBoxGroup()) {
result[checked] = fatherGroupValue.modelValue.includes(props.label);
}
return result
});
// 是否横向显示还是纵向显示
const checkboxStyle = computed(() => {
const displayState = fatherGroupValue.isVerticalShow;
return {
display: displayState ? 'block' : 'inline-block',
}
});
// 是否选中
const internalIsSelected = computed(() => {
return props.isControlled ? props.isSelected : model.value
});
watch(() => props.modelValue, () => {
if (!props.disabled) {
model.value = props.modelValue;
}
});
// is checkbox group
function isCheckBoxGroup() {
return typeof fatherGroup === 'function'
}
// checkbox change
function onCheckboxChange(event) {
if (props.disabled) {
return false
}
const isChecked = event.target.checked;
// if (!props.isControlled) {
// emit('input', isChecked)
// }
// emit(EMIT_EVENTS.ON_CHECKED_CHANGE, isChecked)
emit('checkedChange', isChecked);
emit('update:modelValue', isChecked);
if (isCheckBoxGroup()) {
// update parent comp:checkbox-group
fatherGroup(props.label, isChecked);
}
}
const __returned__ = { fatherGroup, fatherGroupValue, props, emit, model, initModel, checkboxClass, checkboxStyle, internalIsSelected, isCheckBoxGroup, onCheckboxChange, get hasValue() { return hasValue }, get clsName() { return clsName$7 }, get COMPS_NAME() { return COMPS_NAME$9 }, get GROUP_SYMBOL() { return GROUP_SYMBOL }, get GROUP_MODEL_VALUE() { return GROUP_MODEL_VALUE }, computed, inject, watch, ref };
Object.defineProperty(__returned__, '__isScriptSetup', { enumerable: false, value: true });
return __returned__
}
});
const _hoisted_1$1 = ["checked", "value"];
function _sfc_render$1(_ctx, _cache, $props, $setup, $data, $options) {
return (openBlock(), createElementBlock("label", {
class: "ve-checkbox",
style: normalizeStyle($setup.checkboxStyle)
}, [
createElementVNode("span", {
class: normalizeClass($setup.checkboxClass)
}, [
createElementVNode("input", {
checked: $setup.internalIsSelected,
class: normalizeClass($setup.clsName('input')),
type: "checkbox",
value: $props.label,
onChange: $setup.onCheckboxChange
}, null, 42 /* CLASS, PROPS, NEED_HYDRATION */, _hoisted_1$1),
createElementVNode("span", {
class: normalizeClass($setup.clsName('inner'))
}, null, 2 /* CLASS */)
], 2 /* CLASS */),
createElementVNode("span", {
class: normalizeClass($setup.clsName('label'))
}, [
renderSlot(_ctx.$slots, "default", {}, () => [
createTextVNode(toDisplayString($props.label), 1 /* TEXT */)
])
], 2 /* CLASS */)
], 4 /* STYLE */))
}
var VeCheckbox = /*#__PURE__*/_export_sfc(_sfc_main$1, [['render',_sfc_render$1],['__file',"E:\\Full-stuck-study\\3-Vue\\vue-fantable\\packages\\ve-checkbox\\src\\index.vue"]]);
VeCheckbox.install = function (Vue) {
Vue.component('FanCheckbox', VeCheckbox);
Vue.component(VeCheckbox.name, VeCheckbox);
};
const __default__ = {
name: COMPS_NAME$8.VE_CHECKBOX_GROUP
};
const _sfc_main = /*@__PURE__*/Object.assign(__default__, {
props: {
modelValue: {
type: Array,
default() {
return []
},
},
// 是否垂直排列显示(当时checkbox组时生效)
isVerticalShow: {
type: Boolean,
default: false,
},
},
emits: ['update:modelValue', 'change'],
setup(__props, { expose: __expose, emit: __emit }) {
__expose();
// import { getChildCompsByName } from '@P/src/utils/index'
const emit = __emit;
const updateValueInject = (label, value) => {
if (value) {
if (!props.modelValue.includes(label)) {
const newValue = props.modelValue.slice();
newValue.push(label);
emit('update:modelValue', newValue);
}
} else {
if (props.modelValue.includes(label)) {
const newValue = props.modelValue.filter(item => item !== label);
emit('update:modelValue', newValue);
}
}
};
const props = __props;
provide(GROUP_SYMBOL, updateValueInject);
provide(GROUP_MODEL_VALUE, props);
// 更新子组件选中状态
// watch(() => props.modelValue, (newVal) => {
// if (value) {
// if (!props.modelValue.includes(label)) {
// const newValue = props.modelValue.slice().push(label)
// emit('update:modelValue', newValue)
// }
// } else {
// if (props.modelValue.includes(label)) {
// const newValue = props.modelValue.filter(item => item !== label)
// emit('update:modelValue', newValue)
// }
// }
// })
const __returned__ = { emit, updateValueInject, props, get GROUP_SYMBOL() { return GROUP_SYMBOL }, get COMPS_NAME() { return COMPS_NAME$8 }, get GROUP_MODEL_VALUE() { return GROUP_MODEL_VALUE }, provide };
Object.defineProperty(__returned__, '__isScriptSetup', { enumerable: false, value: true });
return __returned__
}
});
const _hoisted_1 = { class: "ve-checkbox-group" };
function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
return (openBlock(), createElementBlock("div", _hoisted_1, [
renderSlot(_ctx.$slots, "default")
]))
}
var VeCheckboxGroup = /*#__PURE__*/_export_sfc(_sfc_main, [['render',_sfc_render],['__file',"E:\\Full-stuck-study\\3-Vue\\vue-fantable\\packages\\ve-checkbox-group\\src\\index.vue"]]);
VeCheckboxGroup.install = function (Vue) {
Vue.component('FanCheckboxGroup', VeCheckboxGroup);
Vue.component(VeCheckboxGroup.name, VeCheckboxGroup);
};
// prefix
const PREFIX_CLS$5 = 've-contextmenu-';
// comps name
const COMPS_NAME$7 = {
VE_CONTEXTMENU: 'VeContextmenu'
};
// init data
const INIT_DATA = {
// PARENT_ID: 0,
PARENT_DEEP: -1
};
// contextmenu types
const CONTEXTMENU_NODE_TYPES$1 = {
// Separator
SEPARATOR: 'SEPARATOR'
};
// instance methods
// export const INSTANCE_METHODS = {
// HIDE_CONTEXTMENU: 'hideContextmenu',
// }
/*
* @clsName
* @desc get class name
* @param {string} cls - class
*/
function clsName$6(cls) {
return PREFIX_CLS$5 + cls;
}
// prefix
// comps name
const COMPS_NAME$6 = {
VE_ICON: 'VeIcon'
};
// icon name
const ICON_NAMES = {
FILTER: 'filter',
DOUBLE_RIGHT_ARROW: 'double-right-arrow',
DOUBLE_LEFT_ARROW: 'double-left-arrow',
TOP_ARROW: 'top-arrow',
RIGHT_ARROW: 'right-arrow',
BOTTOM_ARROW: 'bottom-arrow',
LEFT_ARROW: 'left-arrow',
SORT_TOP_ARROW: 'sort-top-arrow',
SORT_BOTTOM_ARROW: 'sort-bottom-arrow',
SEARCH: 'search'
};
var ArrowUp = {
name: 'IconArrowUp',
setup() {
return () => createVNode("svg", {
"width": "1em",
"height": "1em",
"viewBox": "0 0 48 48",
"fill": "none",
"xmlns": "http://www.w3.org/2000/svg"
}, [createVNode("path", {
"d": "M13 30L25 18L37 30",
"stroke": "currentColor",
"stroke-width": "2",
"stroke-linecap": "round",
"stroke-linejoin": "round"
}, null)]);
}
};
var ArrowDown = {
name: 'IconArrowDown',
setup() {
return () => createVNode("svg", {
"width": "1em",
"height": "1em",
"viewBox": "0 0 48 48",
"fill": "none",
"xmlns": "http://www.w3.org/2000/svg"
}, [createVNode("path", {
"d": "M36 18L24 30L12 18",
"stroke": "currentColor",
"stroke-width": "2",
"stroke-linecap": "round",
"stroke-linejoin": "round"
}, null)]);
}
};
var ArrowLeft = {
name: 'IconArrowLeft',
setup() {
return () => createVNode("svg", {
"width": "1em",
"height": "1em",
"viewBox": "0 0 48 48",
"fill": "none",
"xmlns": "http://www.w3.org/2000/svg"
}, [createVNode("path", {
"d": "M31 36L19 24L31 12",
"stroke": "currentColor",
"stroke-width": "2",
"stroke-linecap": "round",
"stroke-linejoin": "round"
}, null)]);
}
};
var ArrowRight = {
name: 'IconArrowRight',
setup() {
return () => createVNode("svg", {
"width": "1em",
"height": "1em",
"viewBox": "0 0 48 48",
"fill": "none",
"xmlns": "http://www.w3.org/2000/svg"
}, [createVNode("path", {
"d": "M19 12L31 24L19 36",
"stroke": "currentColor",
"stroke-width": "2",
"stroke-linecap": "round",
"stroke-linejoin": "round"
}, null)]);
}
};
var CaretDownFill = {
name: 'IconCaretDownFill',
setup() {
return () => createVNode("svg", {
"xmlns": "http://www.w3.org/2000/svg",
"class": "icon icon-tabler icon-tabler-caret-down-filled",
"width": "1em",
"height": "1em",
"viewBox": "0 0 24 24",
"stroke-width": "2",
"stroke": "currentColor",
"fill": "none",
"stroke-linecap": "round",
"stroke-linejoin": "round"
}, [createVNode("path", {
"stroke": "none",
"d": "M0 0h24v24H0z",
"fill": "none"
}, null), createVNode("path", {
"d": "M18 9c.852 0 1.297 .986 .783 1.623l-.076 .084l-6 6a1 1 0 0 1 -1.32 .083l-.094 -.083l-6 -6l-.083 -.094l-.054 -.077l-.054 -.096l-.017 -.036l-.027 -.067l-.032 -.108l-.01 -.053l-.01 -.06l-.004 -.057v-.118l.005 -.058l.009 -.06l.01 -.052l.032 -.108l.027 -.067l.07 -.132l.065 -.09l.073 -.081l.094 -.083l.077 -.054l.096 -.054l.036 -.017l.067 -.027l.108 -.032l.053 -.01l.06 -.01l.057 -.004l12.059 -.002z",
"stroke-width": "0",
"fill": "currentColor"
}, null)]);
}
};
var CaretUpFill = {
name: 'IconCaretUpFill',
setup() {
return () => createVNode("svg", {
"xmlns": "http://www.w3.org/2000/svg",
"class": "icon icon-tabler icon-tabler-caret-up-filled",
"width": "1em",
"height": "1em",
"viewBox": "0 0 24 24",
"stroke-width": "2",
"stroke": "currentColor",
"fill": "none",
"stroke-linecap": "round",
"stroke-linejoin": "round"
}, [createVNode("path", {
"stroke": "none",
"d": "M0 0h24v24H0z",
"fill": "none"
}, null), createVNode("path", {
"d": "M11.293 7.293a1 1 0 0 1 1.32 -.083l.094 .083l6 6l.083 .094l.054 .077l.054 .096l.017 .036l.027 .067l.032 .108l.01 .053l.01 .06l.004 .057l.002 .059l-.002 .059l-.005 .058l-.009 .06l-.01 .052l-.032 .108l-.027 .067l-.07 .132l-.065 .09l-.073 .081l-.094 .083l-.077 .054l-.096 .054l-.036 .017l-.067 .027l-.108 .032l-.053 .01l-.06 .01l-.057 .004l-.059 .002h-12c-.852 0 -1.297 -.986 -.783 -1.623l.076 -.084l6 -6z",
"stroke-width": "0",
"fill": "currentColor"
}, null)]);
}
};
var DoubleArrowLeft = {
name: 'IconDoubleArrowLeft',
setup() {
return () => createVNode("svg", {
"width": "1em",
"height": "1em",
"viewBox": "0 0 48 48",
"fill": "none",
"xmlns": "http://www.w3.org/2000/svg"
}, [createVNode("path", {
"d": "M24 36L12 24L24 12",
"stroke": "currentColor",
"stroke-width": "2",
"stroke-linecap": "round",
"stroke-linejoin": "round"
}, null), createVNode("path", {
"d": "M36 36L24 24L36 12",
"stroke": "currentColor",
"stroke-width": "2",
"stroke-linecap": "round",
"stroke-linejoin": "round"
}, null)]);
}
};
var DoubleArrowRight = {
name: 'IconDoubleArrowRight',
setup() {
return () => createVNode("svg", {
"width": "1em",
"height": "1em",
"viewBox": "0 0 48 48",
"fill": "none",
"xmlns": "http://www.w3.org/2000/svg"
}, [createVNode("path", {
"d": "M12 12L24 24L12 36",
"stroke": "currentColor",
"stroke-width": "2",
"stroke-linecap": "round",
"stroke-linejoin": "round"
}, null), createVNode("path", {
"d": "M24 12L36 24L24 36",
"stroke": "currentColor",
"stroke-width": "2",
"stroke-linecap": "round",
"stroke-linejoin": "round"
}, null)]);
}
};
var FilterVue = {
name: 'IconFilter',
setup() {
return () => createVNode("svg", {
"xmlns": "http://www.w3.org/2000/svg",
"class": "icon icon-tabler icon-tabler-filter",
"width": "1em",
"height": "1em",
"viewBox": "0 0 24 24",
"stroke-width": "1",
"stroke": "currentColor",
"fill": "none",
"stroke-linecap": "round"
}, [createVNode("path", {
"stroke": "none",
"d": "M0 0h24v24H0z",
"fill": "none"
}, null), createVNode("path", {
"d": "M4 4h16v2.172a2 2 0 0 1 -.586 1.414l-4.414 4.414v7l-6 2v-8.5l-4.48 -4.928a2 2 0 0 1 -.52 -1.345v-2.227z"
}, null)]);
}
};
var SearchVue = {
name: 'IconSearch',
setup() {
return () => createVNode("svg", {
"xmlns": "http://www.w3.org/2000/svg",
"class": "icon icon-tabler icon-tabler-search",
"width": "1em",
"height": "1em",
"viewBox": "0 0 24 24",
"stroke-width": "2",
"stroke": "currentColor",
"fill": "none",
"stroke-linecap": "round",
"stroke-linejoin": "round"
}, [createVNode("path", {
"stroke": "none",
"d": "M0 0h24v24H0z",
"fill": "none"
}, null), createVNode("path", {
"d": "M10 10m-7 0a7 7 0 1 0 14 0a7 7 0 1 0 -14 0"
}, null), createVNode("path", {
"d": "M21 21l-6 -6"
}, null)]);
}
};
var VeIcon = {
name: COMPS_NAME$6.VE_ICON,
props: {
// icon name
name: {
type: String,
required: true
},
color: {
type: String,
default: null
},
size: {
type: [Number, String],
default: ''
}
},
computed: {
// icon style
iconStyle() {
const {
color,
size
} = this;
const result = {
color,
'font-size': getValByUnit(size)
};
return result;
},
iconNameVue() {
const {
name
} = this;
// from @P/src/utils/constant ICON_NAMES
const map = {
filter: FilterVue,
'double-right-arrow': DoubleArrowRight,
'double-left-arrow': DoubleArrowLeft,
'right-arrow': ArrowRight,
'top-arrow': ArrowUp,
'bottom-arrow': ArrowDown,
'left-arrow': ArrowLeft,
'sort-top-arrow': CaretUpFill,
'sort-bottom-arrow': CaretDownFill,
search: SearchVue
};
return map[name];
}
},
methods: {},
render() {
const {
iconStyle
} = this;
return createVNode("span", {
"style": iconStyle
}, [createVNode(this.iconNameVue, null, null)]);
}
};
VeIcon.install = function (Vue) {
Vue.component('FanIcon', VeIcon);
Vue.component(VeIcon.name, VeIcon);
};
/*
fork from:
https://github.com/ElemeFE/element
*/
const trim = function (string) {
return (string || '').replace(/^[\s\uFEFF]+|[\s\uFEFF]+$/g, '');
};
// add class
function addClass(el, cls) {
if (!el) return;
let curClass = el.className;
const classes = (cls || '').split(' ');
for (let i = 0, j = classes.length; i < j; i++) {
const clsName = classes[i];
if (!clsName) continue;
if (el.classList) {
el.classList.add(clsName);
} else if (!hasClass(el, clsName)) {
curClass += ' ' + clsName;
}
}
if (!el.classList) {
el.className = curClass;
}
}
// remove class
function removeClass(el, cls) {
if (!el || !cls) return;
const classes = cls.split(' ');
let curClass = ' ' + el.className + ' ';
for (let i = 0, j = classes.length; i < j; i++) {
const clsName = classes[i];
if (!clsName) continue;
if (el.classList) {
el.classList.remove(clsName);
} else if (hasClass(el, clsName)) {
curClass = curClass.replace(' ' + clsName + ' ', ' ');
}
}
if (!el.classList) {
el.className = trim(curClass);
}
}
// has class
function hasClass(el, cls) {
if (!el || !cls) return false;
if (cls.indexOf(' ') !== -1) {
throw new Error('className should not contain space.');
}
if (el.classList) {
return el.classList.contains(cls);
} else {
return (' ' + el.className + ' ').indexOf(' ' + cls + ' ') > -1;
}
}
/* 获取当前元素的偏移(相对于整个document)
* offsetTop:元素最顶端距离文档顶端的距离,包含滚动条
* offsetleft:元素最左侧距离文档左侧的距离,包含滚动条
* left:元素最左侧距离文档左侧的距离,不包含滚动条
* top:元素最顶端距离文档顶端的距离,不包含滚动条
* right:元素最右侧距离文档右侧的距离,不包含滚动条
* bottom:元素最底端距离文档底端的距离,不包含滚动条
* right2:元素最左侧距离文档右侧的距离,不包含滚动条
* bottom2:元素最底端距离文档最底部的距离,不包含滚动条
* */
function getViewportOffset(triggerEl) {
const doc = document.documentElement;
const box = typeof triggerEl.getBoundingClientRect !== 'undefined' ? triggerEl.getBoundingClientRect() : 0;
const scrollLeft = (window.scrollX || doc.scrollLeft) - (doc.clientLeft || 0);
const scrollTop = (window.scrollY || doc.scrollTop) - (doc.clientTop || 0);
const offsetLeft = box.left + window.scrollX;
const offsetTop = box.top + window.scrollY;
const left = offsetLeft - scrollLeft;
const top = offsetTop - scrollTop;
return {
offsetTop,
offsetLeft,
left,
top,
right: doc.clientWidth - box.width - left,
bottom: doc.clientHeight - box.height - top,
right2: doc.clientWidth - left,
bottom2: doc.clientHeight - top
};
}
/* 获取当前元素的偏移(相对于外层容器)
* offsetTop:元素最顶端距离文档顶端的距离,包含滚动条
* offsetleft:元素最左侧距离文档左侧的距离,包含滚动条
* left:元素最左侧距离文档左侧的距离,不包含滚动条
* top:元素最顶端距离文档顶端的距离,不包含滚动条
* right:元素最右侧距离文档右侧的距离,不包含滚动条
* bottom:元素最底端距离文档底端的距离,不包含滚动条
* right2:元素最左侧距离文档右侧的距离,不包含滚动条
* bottom2:元素最底端距离文档最底部的距离,不包含滚动条
* */
function getViewportOffsetWithinContainer(triggerEl, containerEl) {
const {
offsetTop: tElOffsetTop,
offsetLeft: tElOffsetLeft,
left: tElLef,
top: tElTop,
right: tElRight,
bottom: tElBottom,
right2: tElRight2,
bottom2: tElBottom2
} = getViewportOffset(triggerEl);
const {
offsetTop: cElOffsetTop,
offsetLeft: cElOffsetLeft,
left: cElLef,
top: cElTop,
right: cElRight,
bottom: cElBottom,
right2: cElRight2,
bottom2: cElBottom2
} = getViewportOffset(containerEl);
return {
offsetTop: tElOffsetTop - cElOffsetTop,
offsetLeft: tElOffsetLeft - cElOffsetLeft,
left: tElLef - cElLef,
top: tElTop - cElTop,
right: tElRight - cElRight,
bottom: tElBottom - cElBottom,
right2: tElRight2 - cElRight2,
bottom2: tElBottom2 - cElBottom2
};
}
/* 获取鼠标相对于文档的坐标
* left:鼠标点击位置距离文档左侧的距离,包含滚动条
* top: 鼠标点击位置距离文档顶端的距离,包含滚动条
* right:鼠标点击位置距离文档右侧的距离,不包含滚动条
* bottom:鼠标点击位置距离文档底端的距离,不包含滚动条
* */
function getMousePosition(event) {
let x = 0;
let y = 0;
const doc = document.documentElement;
const body = document.body;
if (window.scrollY) {
x = window.scrollX;
y = window.scrollY;
} else {
x = (doc && doc.scrollLeft || body && body.scrollLeft || 0) - (doc && doc.clientLeft || body && body.clientLeft || 0);
y = (doc && doc.scrollTop || body && body.scrollTop || 0) - (doc && doc.clientTop || body && body.clientTop || 0);
}
x += event.clientX;
y += event.clientY;
const right = doc.clientWidth - event.clientX;
const bottom = doc.clientHeight - event.clientY;
return {
left: x,
top: y,
right,
bottom
};
}
/**
* Returns caret position in text input.
*
* @author https://stackoverflow.com/questions/263743/how-to-get-caret-position-in-textarea
* @param {HTMLElement} el An element to check.
* @returns {number}
*/
function getCaretPosition(el) {
const rootDocument = document;
if (el.selectionStart) {
return el.selectionStart;
} else if (rootDocument.selection) {
// IE8
el.focus();
const r = rootDocument.selection.createRange();
if (r === null) {
return 0;
}
const re = el.createTextRange();
const rc = re.duplicate();
re.moveToBookmark(r.getBookmark());
rc.setEndPoint('EndToStart', re);
return rc.text.length;
}
return 0;
}
/**
* Sets caret position in text input.
*
* @author http://blog.vishalon.net/index.php/javascript-getting-and-setting-caret-position-in-textarea/
* @param {Element} element An element to process.
* @param {number} pos The selection start position.
* @param {number} endPos The selection end position.
*/
function setCaretPosition(element, pos, endPos) {
if (endPos === undefined) {
endPos = pos;
}
if (element.setSelectionRange) {
element.focus();
try {
element.setSelectionRange(pos, endPos);
} catch (err) {
const elementParent = element.parentNode;
const parentDisplayValue = elementParent.style.display;
elementParent.style.display = 'block';
element.setSelectionRange(pos, endPos);
elementParent.style.display = parentDisplayValue;
}
}
}
/**
* Generate a non duplicate ID
*/
function getRandomId() {
return Date.now().toString(36) + Math.random().toString(36).slice(2);
}
/*
events outside
desc: 绑定元素触发的事件不在指定事件中,将会触发。此指令可替代 clickoutside
*/
var eventsOutside = {
mounted(el, binding, vNode) {
const {
events,
callback
} = binding.value;
if (Array.isArray(events) && events.length && typeof callback === 'function') {
const handler = e => {
if (!el.contains(e.target) && el !== e.target) {
callback(e);
} else {
return false;
}
};
el.__eventsOutside__ = handler;
events.forEach(eventName => {
document.addEventListener(eventName, handler, true);
});
} else {
const compName = vNode.context.name;
console.error(`[events-outside] Please provided 'events' and 'callback' in ${compName}`);
}
},
unmounted(el, binding, vNode) {
const {
events
} = binding.value;
events.forEach(eventName => {
document.removeEventListener(eventName, el.__eventsOutside__, true);
});
el.__eventsOutside__ = null;
}
};
var VeContextmenu = {
name: COMPS_NAME$7.VE_CONTEXTMENU,
directives: {
'events-outside': eventsOutside
},
props: {
options: {
type: Array,
required: true
},
// eventTarget: contextmenu event will register on it
eventTarget: {
type: [String, HTMLElement],
required: true
}
},
emits: ['nodeClick'],
data() {
return {
internalOptions: [],
panelOptions: [],
// event target element
eventTargetEl: '',
// root contextmenu id
rootContextmenuId: '',
/*
is children panels clicked
如果点击了则不关闭 panels
*/
isChildrenPanelsClicked: false,
/*
is panel right direction
决定了子 panel 默认展示方向
*/
isPanelRightDirection: true,
/*
is panels remove
防止hover后菜单被移除,仍然显示子集菜单的问题
*/
isPanelsEmptyed: true
};
},
computed: {
// active menus ids
activeMenuIds() {
const {
panelOptions
} = this;
return panelOptions.map(x => x.parentId);
}
},
watch: {
options: {
handler(val) {
if (Array.isArray(val) && val.length > 0) {
/*
如果配置项修改,则重新销毁并创建
*/
this.removeOrEmptyPanels(true);
this.rootContextmenuId = this.getRandomIdWithPrefix();
this.createInternalOptions();
this.createPanelOptions({
options: this.internalOptions
});
this.resetContextmenu();
this.addRootContextmenuPanelToBody();
}
},
immediate: true
},
eventTarget: {
handler(val) {
if (val) {
this.registerContextmenuEvent();
}
},
immediate: true
}
},
created() {
this.debounceCreatePanelByHover = debounce(this.createPanelByHover, 300);
},
mounted() {
this.addRootContextmenuPanelToBody();
},
unmounted() {
this.removeContextmenuEvent();
this.removeOrEmptyPanels(true);
},
methods: {
// get random id
getRandomIdWithPrefix() {
return clsName$6(getRandomId());
},
// has children
hasChildren(option) {
return Array.isArray(option.children) && option.children.length;
},
// get panel option by menu id
getPanelOptionByMenuId(options, menuId) {
for (let i = 0; i < options.length; i++) {
if (options[i].id === menuId) {
return options[i].children;
}
if (options[i].children) {
const panelOption = this.getPanelOptionByMenuId(options[i].children, menuId);
if (panelOption) return panelOption;
}
}
},
// get parent contextmenu panel element
getParentContextmenuPanelEl(contextmenuPanelId) {
let result;
const {
panelOptions
} = this;
const panelIndex = panelOptions.findIndex(x => x.parentId === contextmenuPanelId);
if (panelIndex > 0) {
// preview panel's panelId
const parentPanelId = panelOptions[panelIndex - 1].parentId;
result = document.querySelector(`#${parentPanelId}`);
}
return result;
},
// create panel by hover
createPanelByHover({
event,
menu
}) {
const {
internalOptions,
panelOptions
} = this;
// 如果被移除则不创建
if (this.isPanelsEmptyed) {
return false;
}
// has already exists
if (panelOptions.findIndex(x => x.parentId === menu.id) > -1) {
return false;
}
// remove panels
// 移除 panel 深度大于等于当前悬浮菜单的。从后往前删除
const deletePanelDeeps = panelOptions.filter(x => x.parentDeep >= menu.deep).map(x => x.parentDeep).reverse();
if (deletePanelDeeps.length) {
for (let i = deletePanelDeeps.length - 1; i >= 0; i--) {
const delIndex = panelOptions.findIndex(x => x.parentDeep === deletePanelDeeps[i]);
if (delIndex > -1) {
this.panelOptions.splice(delIndex, 1);
}
}
}
const panelOption = this.getPanelOptionByMenuId(internalOptions, menu.id);
if (panelOption) {
this.createPanelOptions({
options: panelOption,
currentMenu: menu
});
this.$nextTick(() => {
this.addContextmenuPanelToBody({
contextmenuId: menu.id
});
this.showContextmenuPanel({
event,
contextmenuId: menu.id
});
});
}
},
// create panels option
createPanelOptions({
options,
currentMenu
}) {
const {
hasChildren,
rootContextmenuId
} = this;
if (Array.isArray(options)) {
//
const menus = options.map(option => {
return {
hasChildren: hasChildren(option),
...option
};
});
this.panelOptions.push({
parentId: currentMenu ? currentMenu.id : rootContextmenuId,
parentDeep: currentMenu ? currentMenu.deep : INIT_DATA.PARENT_DEEP,
menus
});
}
},
// create internal options recursion
createInternalOptionsRecursion(options, deep = 0) {
options.id = this.getRandomIdWithPrefix();
options.deep = deep;
deep++;
if (Array.isArray(options.children)) {
options.children.map(option => {
return this.createInternalOptionsRecursion(option, deep);
});
}
return options;
},
// create internal options
createInternalOptions() {
this.internalOptions = deepClone(this.options).map(option => {
return this.createInternalOptionsRecursion(option);
});
},
// show root contextmenu panel
showRootContextmenuPanel(event) {
event.preventDefault();
const {
rootContextmenuId
} = this;
if (rootContextmenuId) {
// refresh contextmenu
this.resetContextmenu();
this.showContextmenuPanel({
event,
contextmenuId: rootContextmenuId,
isRootContextmenu: true
});
this.isPanelsEmptyed = false;
}
},
// show contextmenu panel
showContextmenuPanel({
event,
contextmenuId,
isRootContextmenu
}) {
const {
getParentContextmenuPanelEl
} = this;
const contextmenuPanelEl = document.querySelector(`#${contextmenuId}`);
if (contextmenuPanelEl) {
// remove first
contextmenuPanelEl.innerHTML = '';
contextmenuPanelEl.appendChild(this.$refs[contextmenuId]);
contextmenuPanelEl.style.position = 'absolute';
contextmenuPanelEl.classList.add(clsName$6('popper'));
const {
width: currentPanelWidth,
height: currentPanelHeight
} = contextmenuPanelEl.getBoundingClientRect();
if (isRootContextmenu) {
const {
left: clickLeft,
top: clickTop,
right: clickRight,
bottom: clickBottom
} = getMousePosition(event);
let panelX = 0;
let panelY = 0;
// 右方宽度够显示
if (clickRight >= currentPanelWidth) {
panelX = clickLeft;
this.isPanelRightDirection = true;
} else {
// 右方宽度不够显示在鼠标点击左方
panelX = clickLeft - currentPanelWidth;
this.isPanelRightDirection = false;
}
// 下方高度够显示
if (clickBottom >= currentPanelHeight) {
panelY = clickTop;
} else {
// 下方高度不够显示在鼠标点击上方
panelY = clickTop - currentPanelHeight;
}
contextmenuPanelEl.style.left = panelX + 'px';
contextmenuPanelEl.style.top = panelY + 'px';
} else {
const parentContextmenuPanelEl = getParentContextmenuPanelEl(contextmenuId);
if (parentContextmenuPanelEl) {
const {
left: parentPanelLeft,
right: parentPanelRight
} = getViewportOffset(parentContextmenuPanelEl);
const {
top: clickTop,
bottom: clickBottom
} = getMousePosition(event);
const {
width: parentPanelWidth
} = parentContextmenuPanelEl.getBoundingClientRect();
let panelX = 0;
let panelY = 0;
// 如果默认展示在右方向
if (this.isPanelRightDirection) {
// 右方宽度够显示
if (parentPanelRight >= currentPanelWidth) {
panelX = parentPanelLeft + parentPanelWidth;
} else {
// 右方宽度不够显示在鼠标点击左方
panelX = parentPanelLeft - parentPanelWidth;
}
} else {
// 如果默认展示在左方向
// 左方宽度够显示
if (parentPanelLeft >= currentPanelWidth) {
panelX = parentPanelLeft - parentPanelWidth;
} else {
// 左方宽度不够显示在鼠标点击右方
panelX = parentPanelLeft + parentPanelWidth;
}
}
// 下方高度够显示
if (clickBottom >= currentPanelHeight) {
panelY = clickTop;
} else {
// 下方高度不够显示在鼠标点击上方
panelY = clickTop - currentPanelHeight;
}
contextmenuPanelEl.style.left = panelX + 'px';
contextmenuPanelEl.style.top = panelY + 'px';
}
}
}
},
// empty contextmenu panels
emptyContextmenuPanels() {
// wait for children panel clicked by setTimeout
// 如果点击的是非 root panel 不关闭
setTimeout(() => {
if (this.isChildrenPanelsClicked) {
this.isChildrenPanelsClicked = false;
} else {
this.removeOrEmptyPanels();
this.isPanelsEmptyed = true;
}
});
},
// remove or empty panels
removeOrEmptyPanels(isRemove) {
const {
panelOptions
} = this;
panelOptions.forEach(panelOption => {
const contextmenuPanelEl = document.querySelector(`#${panelOption.parentId}`);
if (contextmenuPanelEl) {
if (isRemove) {
contextmenuPanelEl.remove();
} else {
contextmenuPanelEl.innerHTML = '';
}
}
});
},
// reset contextmeny
resetContextmenu() {
this.panelOptions = [];
this.createPanelOptions({
options: this.internalOptions
});
},
// add context menu panel to body
addContextmenuPanelToBody({
contextmenuId
}) {
const contextmenuPanelEl = document.querySelector(`#${contextmenuId}`);
if (contextmenuPanelEl) {
return false;
} else {
const containerEl = document.createElement('div');
containerEl.setAttribute('id', contextmenuId);
document.body.appendChild(containerEl);
}
},
// add root contextmenu panel to body
addRootContextmenuPanelToBody() {
if (this.rootContextmenuId) {
this.addContextmenuPanelToBody({
contextmenuId: this.rootContextmenuId
});
}
},
// register contextmenu event
registerContextmenuEvent() {
const {
eventTarget
} = this;
if (typeof eventTarget === 'string' && eventTarget.length > 0) {
this.eventTargetEl = document.querySelector(eventTarget);
} else {
this.eventTargetEl = eventTarget;
}
if (this.eventTargetEl) {
// contextmenu is on the current element
this.eventTargetEl.addEventListener('contextmenu', this.showRootContextmenuPanel);
}
},
// unregister contextmen event
removeContextmenuEvent() {
if (this.eventTargetEl) {
this.eventTargetEl.removeEventListener('contextmenu', this.showRootContextmenuPanel);
}
},
// hide contextmenu
// INSTANCE_METHODS.HIDE_CONTEXTMENU
hideContextmenu() {
this.emptyContextmenuPanels();
}
},
render() {
const {
panelOptions,
activeMenuIds,
hasChildren,
emptyContextmenuPanels,
debounceCreatePanelByHover
} = this;
const contextmenuProps = {
class: ['ve-contextmenu'],
style: {
display: 'none'
}
};
return createVNode("div", contextmenuProps, [panelOptions.map((panelOption, panelIndex) => {
const contextmenuPanelProps = {
ref: panelOption.parentId,
class: clsName$6('panel'),
onClick: () => {
if (panelIndex !== 0) {
this.isChildrenPanelsClicked = true;
}
},
onContextmenu: e => {
e.preventDefault();
}
};
return withDirectives(createVNode("div", contextmenuPanelProps, [createVNode("ul", {
"class": clsName$6('list')
}, [panelOption.menus.map(menu => {
let contextmenuNodeProps;
if (menu.type !== CONTEXTMENU_NODE_TYPES$1.SEPARATOR) {
const nodeActive = clsName$6('node-active');
const nodeDisable = clsName$6('node-disabled');
contextmenuNodeProps = {
class: {
[clsName$6('node')]: true,
[nodeActive]: activeMenuIds.includes(menu.id),
[nodeDisable]: menu.disabled
},
onMouseover: event => {
// disable
if (!menu.disabled) {
debounceCreatePanelByHover({
event,
menu
});
}
},
onClick: () => {
if (!menu.disabled && !hasChildren(menu)) {
// EMIT_EVENTS.ON_NODE_CLICK,
this.$emit('nodeClick', menu.type);
setTimeout(() => {
emptyContextmenuPanels();
}, 50);
}
}
};
} else {
// separator
contextmenuNodeProps = {
class: {
[clsName$6('node-separator')]: true
}
};
}
if (menu.type !== CONTEXTMENU_NODE_TYPES$1.SEPARATOR) {
return createVNode("li", contextmenuNodeProps, [createVNode("span", {
"class": clsName$6('node-label')
}, [menu.label]), menu.hasChildren && createVNode(VeIcon, {
"class": clsName$6('node-icon-postfix'),
"name": ICON_NAMES.RIGHT_ARROW
}, null)]);
} else {
return createVNode("li", contextmenuNodeProps, null);
}
})])]), [[resolveDirective("events-outside"), {
events: ['click'],
callback: e => {
// only for root panel
if (panelIndex === 0) {
emptyContextmenuPanels();
}
}
}]]);
})]);
}
};
VeContextmenu.install = function (Vue) {
Vue.component('FanContextmenu', VeContextmenu);
Vue.component(VeContextmenu.name, VeContextmenu);
};
var clickoutside = {
mounted(el, binding, vNode) {
if (typeof binding.value !== 'function') {
let msg = `in [clickoutside] directives, provided expression '${binding.expression}' is not a function `;
const compName = vNode.context.name;
if (compName) {
msg += `in ${compName}`;
}
console.error(msg);
}
const handler = e => {
if (!el.contains(e.target) && el !== e.target) {
binding.value(e);
} else {
return false;
}
};
el.__clickOutSide__ = handler;
document.addEventListener('click', handler, true);
},
unmounted(el) {
document.removeEventListener('click', el.__clickOutSide__, true);
el.__clickOutSide__ = null;
}
};
// prefix
const PREFIX_CLS$4 = 've-radio-';
// comps name
const COMPS_NAME$5 = {
VE_RADIO: 'VeRadio'
};
/*
* @clsName
* @desc get class name
* @param {string} cls - class
*/
function clsName$5(cls) {
return PREFIX_CLS$4 + cls;
}
var VeRadio = {
name: COMPS_NAME$5.VE_RADIO,
props: {
// 当前 checkbox 选中状态,实现 v-model
modelValue: {
type: [String, Number, Boolean],
default: null
},
label: {
type: String,
default: null
},
// is disabled checked
disabled: Boolean,
// 是否是可控组件
isControlled: {
type: Boolean,
default: false
},
// isControlled 为true 时生效
isSelected: {
type: Boolean,
default: false
}
},
emits: ['radioChange', 'update:modelValue'],
data() {
return {
// 当前checkbox 选中状态
model: this.modelValue
};
},
computed: {
radioClass() {
const disableState = this.disabled;
const disabled = clsName$5('disabled');
return [clsName$5('container'), {
[clsName$5('checked')]: this.internalIsSelected,
[disabled]: disableState
}];
},
// 是否选中
internalIsSelected() {
return this.isControlled ? this.isSelected : this.model;
}
},
watch: {
modelValue() {
this.updateModelBySingle();
}
},
created() {
this.initModel();
},
methods: {
// checked change
initModel() {
if (hasValue(this.modelValue)) {
this.internalOptions = [].concat(this.modelValue);
}
},
checkedChange(event) {
if (this.disabled) {
return false;
}
const isChecked = event.target.checked;
if (!this.isControlled) {
this.$emit('update:modelValue', isChecked);
}
// this.$emit(EMIT_EVENTS.ON_RADIO_CHANGE, isChecked)
this.$emit('radioChange', isChecked);
},
// get label content
getLabelContent() {
const {
label,
$slots
} = this;
return label || $slots.default;
},
// 通过单选更新 model
updateModelBySingle() {
if (!this.disabled) {
this.model = this.modelValue;
}
}
},
render() {
const {
label,
radioClass,
checkedChange,
getLabelContent,
internalIsSelected
} = this;
return createVNode("label", {
"class": 've-radio'
}, [createVNode("span", {
"class": radioClass
}, [createVNode("input", {
"checked": internalIsSelected,
"class": clsName$5('input'),
"type": "radio",
"value": label,
"onChange": checkedChange
}, null), createVNode("span", {
"class": clsName$5('inner')
}, null)]), createVNode("span", {
"class": clsName$5('label')
}, [getLabelContent()])]);
}
};
VeRadio.install = function (Vue) {
Vue.component('FanRadio', VeRadio);
Vue.component(VeRadio.name, VeRadio);
};
// emit events
// comps name
const COMPS_NAME$4 = {
VE_DROPDOWN: 'VeDropdown'
};
/*
* @clsName
* @desc get class name
* @param {string} cls - class
*/
function clsName$4(cls) {
return 've-dropdown-' + cls;
}
var VeDropdown = {
name: COMPS_NAME$4.VE_DROPDOWN,
directives: {
'click-outside': clickoutside
},
props: {
// 如果是 select 组件将特殊处理
isSelect: {
type: Boolean,
default: false
},
showOperation: {
type: Boolean,
default: false
},
width: {
type: Number,
default: 90
},
// select 的最大宽度(超出隐藏)
maxWidth: {
type: Number,
default: 0
},
// max height
maxHeight: {
type: Number,
default: 1000
},
// 如果为true 会包含 checkbox
isMultiple: {
type: Boolean,
default: false
},
// 用户传入 v-model 的值 [{value/label/selected}]
modelValue: {
type: [Array],
default: null
},
// 文本居中方式 left|center|right
textAlign: {
type: String,
default: 'left'
},
// 是否支持输入input
isInput: {
type: Boolean,
default: false
},
// confirm filter text
confirmFilterText: {
type: String,
default: ''
},
// confirm filter text
resetFilterText: {
type: String,
default: ''
},
// hide by single selection item click
hideByItemClick: {
type: Boolean,
default: false
},
// is show radio when single selection
showRadio: {
type: Boolean,
default: false
},
// 当 isControlled=true ,visible 生效
visible: {
type: Boolean,
default: false
},
// is controlled
isControlled: {
type: Boolean,
default: false
},
// is custom content
isCustomContent: {
type: Boolean,
default: false
},
// instance between dropdown items and trigger element
defaultInstance: {
type: Number,
default: 5
},
// popper append to element
popperAppendTo: {
type: [String, HTMLElement],
default: function () {
return document.body;
}
},
/*
before visible change
如果返回false 则阻止显示或者关闭
*/
beforeVisibleChange: {
type: Function,
default: null
}
},
emits: ['update:modelValue', 'dropdownVisibleChange', 'filterConfirm', 'filterReset', 'itemSelectChange'],
data() {
return {
internalVisible: false,
internalOptions: [],
inputValue: '',
// 是否显示触发器被点击了(被点击将忽略 clickOutside 事件)
isDropdownShowTriggerClicked: false,
// root id
rootId: '',
// dropdown items panel id
dropdownItemsPanelId: '',
// 弹出被添加到的目标元素
popperAppendToEl: null,
// 弹出被添加到的目标元素标签名称
appendToElTagName: null
};
},
computed: {
// is dropdown visible
isDropdownVisible() {
return this.isControlled ? this.visible : this.internalVisible;
},
// 获取最大宽度(不设置则是无穷大)
getMaxWidth() {
let result = Infinity;
const maxWidth = this.maxWidth;
const width = this.width;
if (maxWidth && maxWidth > 0 && maxWidth > width) {
result = maxWidth;
}
return result;
},
// selected labels
selectedLabels() {
return this.internalOptions.filter(x => x.selected).map(x => {
if (x.selected) {
return x.label;
}
return null;
});
},
// operation buttons class
operationFilterClass() {
let result = null;
result = {
[clsName$4('filter-disable')]: this.selectedLabels.length === 0
};
return result;
},
// dropdown items class
dropdownItemsClass() {
const ddShow = clsName$4('dd-show');
return {
[clsName$4('dd')]: true,
[ddShow]: this.isDropdownVisible
};
}
},
watch: {
modelValue() {
this.initModel();
},
visible: {
handler(visible) {
const {
isControlled,
showDropDown,
hideDropDown
} = this;
// deal after mounted hook
setTimeout(() => {
if (isControlled) {
if (visible) {
showDropDown();
} else {
hideDropDown();
}
}
});
},
immediate: true
}
},
created() {
this.initModel();
},
mounted() {
this.addRootElementToElement();
this.$nextTick(() => {
const targetEl = this.appendToElTagName