大数据表格
This commit is contained in:
parent
94b9fdd23b
commit
bd15995568
@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Domains\Real\Repositories;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use App\Core\Repository;
|
||||
use App\Models\Real\Order as Model;
|
||||
|
||||
|
329
frontend/src/components/table/assist.js
Normal file
329
frontend/src/components/table/assist.js
Normal file
@ -0,0 +1,329 @@
|
||||
import Vue from 'vue';
|
||||
const isServer = Vue.prototype.$isServer;
|
||||
// 判断参数是否是其中之一
|
||||
export function oneOf(value, validList) {
|
||||
for (let i = 0; i < validList.length; i++) {
|
||||
if (value === validList[i]) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
export function camelcaseToHyphen(str) {
|
||||
return str.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();
|
||||
}
|
||||
|
||||
// For Modal scrollBar hidden
|
||||
let cached;
|
||||
export function getScrollBarSize(fresh) {
|
||||
if (isServer) return 0;
|
||||
if (fresh || cached === undefined) {
|
||||
const inner = document.createElement('div');
|
||||
inner.style.width = '100%';
|
||||
inner.style.height = '200px';
|
||||
|
||||
const outer = document.createElement('div');
|
||||
const outerStyle = outer.style;
|
||||
|
||||
outerStyle.position = 'absolute';
|
||||
outerStyle.top = 0;
|
||||
outerStyle.left = 0;
|
||||
outerStyle.pointerEvents = 'none';
|
||||
outerStyle.visibility = 'hidden';
|
||||
outerStyle.width = '200px';
|
||||
outerStyle.height = '150px';
|
||||
outerStyle.overflow = 'hidden';
|
||||
|
||||
outer.appendChild(inner);
|
||||
|
||||
document.body.appendChild(outer);
|
||||
|
||||
const widthContained = inner.offsetWidth;
|
||||
outer.style.overflow = 'scroll';
|
||||
let widthScroll = inner.offsetWidth;
|
||||
|
||||
if (widthContained === widthScroll) {
|
||||
widthScroll = outer.clientWidth;
|
||||
}
|
||||
|
||||
document.body.removeChild(outer);
|
||||
|
||||
cached = widthContained - widthScroll;
|
||||
}
|
||||
return cached;
|
||||
}
|
||||
|
||||
// watch DOM change
|
||||
export const MutationObserver = isServer ? false : window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver || false;
|
||||
|
||||
const SPECIAL_CHARS_REGEXP = /([\:\-\_]+(.))/g;
|
||||
const MOZ_HACK_REGEXP = /^moz([A-Z])/;
|
||||
|
||||
function camelCase(name) {
|
||||
return name.replace(SPECIAL_CHARS_REGEXP, function(_, separator, letter, offset) {
|
||||
return offset ? letter.toUpperCase() : letter;
|
||||
}).replace(MOZ_HACK_REGEXP, 'Moz$1');
|
||||
}
|
||||
// getStyle
|
||||
export function getStyle(element, styleName) {
|
||||
if (!element || !styleName) return null;
|
||||
styleName = camelCase(styleName);
|
||||
if (styleName === 'float') {
|
||||
styleName = 'cssFloat';
|
||||
}
|
||||
try {
|
||||
const computed = document.defaultView.getComputedStyle(element, '');
|
||||
return element.style[styleName] || computed ? computed[styleName] : null;
|
||||
} catch (e) {
|
||||
return element.style[styleName];
|
||||
}
|
||||
}
|
||||
|
||||
// firstUpperCase
|
||||
function firstUpperCase(str) {
|
||||
return str.toString()[0].toUpperCase() + str.toString().slice(1);
|
||||
}
|
||||
export { firstUpperCase };
|
||||
|
||||
// Warn
|
||||
export function warnProp(component, prop, correctType, wrongType) {
|
||||
correctType = firstUpperCase(correctType);
|
||||
wrongType = firstUpperCase(wrongType);
|
||||
console.error(`[iView warn]: Invalid prop: type check failed for prop ${prop}. Expected ${correctType}, got ${wrongType}. (found in component: ${component})`); // eslint-disable-line
|
||||
}
|
||||
|
||||
function typeOf(obj) {
|
||||
const toString = Object.prototype.toString;
|
||||
const map = {
|
||||
'[object Boolean]': 'boolean',
|
||||
'[object Number]': 'number',
|
||||
'[object String]': 'string',
|
||||
'[object Function]': 'function',
|
||||
'[object Array]': 'array',
|
||||
'[object Date]': 'date',
|
||||
'[object RegExp]': 'regExp',
|
||||
'[object Undefined]': 'undefined',
|
||||
'[object Null]': 'null',
|
||||
'[object Object]': 'object'
|
||||
};
|
||||
return map[toString.call(obj)];
|
||||
}
|
||||
|
||||
// deepCopy
|
||||
function deepCopy(data) {
|
||||
const t = typeOf(data);
|
||||
let o;
|
||||
|
||||
if (t === 'array') {
|
||||
o = [];
|
||||
} else if (t === 'object') {
|
||||
o = {};
|
||||
} else {
|
||||
return data;
|
||||
}
|
||||
|
||||
if (t === 'array') {
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
o.push(deepCopy(data[i]));
|
||||
}
|
||||
} else if (t === 'object') {
|
||||
for (let i in data) {
|
||||
o[i] = deepCopy(data[i]);
|
||||
}
|
||||
}
|
||||
return o;
|
||||
}
|
||||
|
||||
export { deepCopy };
|
||||
|
||||
// scrollTop animation
|
||||
export function scrollTop(el, from = 0, to, duration = 500, endCallback) {
|
||||
if (!window.requestAnimationFrame) {
|
||||
window.requestAnimationFrame = (
|
||||
window.webkitRequestAnimationFrame ||
|
||||
window.mozRequestAnimationFrame ||
|
||||
window.msRequestAnimationFrame ||
|
||||
function(callback) {
|
||||
return window.setTimeout(callback, 1000 / 60);
|
||||
}
|
||||
);
|
||||
}
|
||||
const difference = Math.abs(from - to);
|
||||
const step = Math.ceil(difference / duration * 50);
|
||||
|
||||
function scroll(start, end, step) {
|
||||
if (start === end) {
|
||||
endCallback && endCallback();
|
||||
return;
|
||||
}
|
||||
|
||||
let d = (start + step > end) ? end : start + step;
|
||||
if (start > end) {
|
||||
d = (start - step < end) ? end : start - step;
|
||||
}
|
||||
|
||||
if (el === window) {
|
||||
window.scrollTo(d, d);
|
||||
} else {
|
||||
el.scrollTop = d;
|
||||
}
|
||||
window.requestAnimationFrame(() => scroll(d, end, step));
|
||||
}
|
||||
scroll(from, to, step);
|
||||
}
|
||||
|
||||
// Find components upward
|
||||
function findComponentUpward(context, componentName, componentNames) {
|
||||
if (typeof componentName === 'string') {
|
||||
componentNames = [componentName];
|
||||
} else {
|
||||
componentNames = componentName;
|
||||
}
|
||||
|
||||
let parent = context.$parent;
|
||||
let name = parent.$options.name;
|
||||
while (parent && (!name || componentNames.indexOf(name) < 0)) {
|
||||
parent = parent.$parent;
|
||||
if (parent) name = parent.$options.name;
|
||||
}
|
||||
return parent;
|
||||
}
|
||||
export { findComponentUpward };
|
||||
|
||||
// Find component downward
|
||||
export function findComponentDownward(context, componentName) {
|
||||
const childrens = context.$children;
|
||||
let children = null;
|
||||
|
||||
if (childrens.length) {
|
||||
for (const child of childrens) {
|
||||
const name = child.$options.name;
|
||||
if (name === componentName) {
|
||||
children = child;
|
||||
break;
|
||||
} else {
|
||||
children = findComponentDownward(child, componentName);
|
||||
if (children) break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return children;
|
||||
}
|
||||
|
||||
// Find components downward
|
||||
export function findComponentsDownward(context, componentName) {
|
||||
return context.$children.reduce((components, child) => {
|
||||
if (child.$options.name === componentName) components.push(child);
|
||||
const foundChilds = findComponentsDownward(child, componentName);
|
||||
return components.concat(foundChilds);
|
||||
}, []);
|
||||
}
|
||||
|
||||
// Find components upward
|
||||
export function findComponentsUpward(context, componentName) {
|
||||
let parents = [];
|
||||
const parent = context.$parent;
|
||||
if (parent) {
|
||||
if (parent.$options.name === componentName) parents.push(parent);
|
||||
return parents.concat(findComponentsUpward(parent, componentName));
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
// Find brothers components
|
||||
export function findBrothersComponents(context, componentName, exceptMe = true) {
|
||||
let res = context.$parent.$children.filter(item => {
|
||||
return item.$options.name === componentName;
|
||||
});
|
||||
let index = res.findIndex(item => item._uid === context._uid);
|
||||
if (exceptMe) res.splice(index, 1);
|
||||
return res;
|
||||
}
|
||||
|
||||
/* istanbul ignore next */
|
||||
const trim = function(string) {
|
||||
return (string || '').replace(/^[\s\uFEFF]+|[\s\uFEFF]+$/g, '');
|
||||
};
|
||||
|
||||
/* istanbul ignore next */
|
||||
export 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;
|
||||
}
|
||||
}
|
||||
|
||||
/* istanbul ignore next */
|
||||
export 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;
|
||||
}
|
||||
}
|
||||
|
||||
/* istanbul ignore next */
|
||||
export 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);
|
||||
}
|
||||
}
|
||||
|
||||
export const dimensionMap = {
|
||||
xs: '480px',
|
||||
sm: '768px',
|
||||
md: '992px',
|
||||
lg: '1200px',
|
||||
xl: '1600px'
|
||||
};
|
||||
|
||||
export function setMatchMedia() {
|
||||
if (typeof window !== 'undefined') {
|
||||
const matchMediaPolyfill = mediaQuery => {
|
||||
return {
|
||||
media: mediaQuery,
|
||||
matches: false,
|
||||
on() {},
|
||||
off() {}
|
||||
};
|
||||
};
|
||||
window.matchMedia = window.matchMedia || matchMediaPolyfill;
|
||||
}
|
||||
}
|
||||
|
||||
export const sharpMatcherRegx = /#([^#]+)$/;
|
137
frontend/src/components/table/cell.vue
Normal file
137
frontend/src/components/table/cell.vue
Normal file
@ -0,0 +1,137 @@
|
||||
<template>
|
||||
<div :class="classes" ref="cell">
|
||||
<template v-if="renderType === 'index'">
|
||||
<span>{{ column.indexMethod ? column.indexMethod(row) : (naturalIndex + 1) }}</span>
|
||||
</template>
|
||||
<template v-if="renderType === 'selection'">
|
||||
<Checkbox
|
||||
:value="checked"
|
||||
@click.native.stop="handleClick"
|
||||
@on-change="toggleSelect"
|
||||
:disabled="disabled"
|
||||
></Checkbox>
|
||||
</template>
|
||||
<template v-if="renderType === 'html'">
|
||||
<span v-html="row[column.key]"></span>
|
||||
</template>
|
||||
<template v-if="renderType === 'normal'">
|
||||
<template v-if="column.tooltip">
|
||||
<Tooltip
|
||||
transfer
|
||||
:content="row[column.key]"
|
||||
:disabled="!showTooltip"
|
||||
:max-width="300"
|
||||
class="ivu-table-cell-tooltip"
|
||||
>
|
||||
<span
|
||||
ref="content"
|
||||
@mouseenter="handleTooltipIn"
|
||||
@mouseleave="handleTooltipOut"
|
||||
class="ivu-table-cell-tooltip-content"
|
||||
>{{ row[column.key] }}</span>
|
||||
</Tooltip>
|
||||
</template>
|
||||
<span v-else>{{row[column.key]}}</span>
|
||||
</template>
|
||||
<template v-if="renderType === 'expand' && !row._disableExpand">
|
||||
<div :class="expandCls" @click="toggleExpand">
|
||||
<Icon type="ios-arrow-forward"></Icon>
|
||||
</div>
|
||||
</template>
|
||||
<Cell
|
||||
v-if="renderType === 'render'"
|
||||
:row="row"
|
||||
:column="column"
|
||||
:index="index"
|
||||
:render="column.render"
|
||||
></Cell>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import Cell from "./expand";
|
||||
|
||||
export default {
|
||||
name: "TableCell",
|
||||
components: { Cell },
|
||||
props: {
|
||||
prefixCls: String,
|
||||
row: Object,
|
||||
column: Object,
|
||||
naturalIndex: Number, // index of rebuildData
|
||||
index: Number, // _index of data
|
||||
checked: Boolean,
|
||||
disabled: Boolean,
|
||||
expanded: Boolean,
|
||||
fixed: {
|
||||
type: [Boolean, String],
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
renderType: "",
|
||||
uid: -1,
|
||||
context: this.$parent.$parent.$parent.currentContext,
|
||||
showTooltip: false // 鼠标滑过overflow文本时,再检查是否需要显示
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
classes() {
|
||||
return [
|
||||
`${this.prefixCls}-cell`,
|
||||
{
|
||||
[`${this.prefixCls}-hidden`]:
|
||||
!this.fixed &&
|
||||
this.column.fixed &&
|
||||
(this.column.fixed === "left" || this.column.fixed === "right"),
|
||||
[`${this.prefixCls}-cell-ellipsis`]: this.column.ellipsis || false,
|
||||
[`${this.prefixCls}-cell-with-expand`]: this.renderType === "expand",
|
||||
[`${this.prefixCls}-cell-with-selection`]:
|
||||
this.renderType === "selection"
|
||||
}
|
||||
];
|
||||
},
|
||||
expandCls() {
|
||||
return [
|
||||
`${this.prefixCls}-cell-expand`,
|
||||
{
|
||||
[`${this.prefixCls}-cell-expand-expanded`]: this.expanded
|
||||
}
|
||||
];
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
toggleSelect() {
|
||||
this.$parent.$parent.$parent.toggleSelect(this.index);
|
||||
},
|
||||
toggleExpand() {
|
||||
this.$parent.$parent.$parent.toggleExpand(this.index);
|
||||
},
|
||||
handleClick() {
|
||||
// 放置 Checkbox 冒泡
|
||||
},
|
||||
handleTooltipIn() {
|
||||
const $content = this.$refs.content;
|
||||
this.showTooltip = $content.scrollWidth > $content.offsetWidth;
|
||||
},
|
||||
handleTooltipOut() {
|
||||
this.showTooltip = false;
|
||||
}
|
||||
},
|
||||
created() {
|
||||
if (this.column.type === "index") {
|
||||
this.renderType = "index";
|
||||
} else if (this.column.type === "selection") {
|
||||
this.renderType = "selection";
|
||||
} else if (this.column.type === "html") {
|
||||
this.renderType = "html";
|
||||
} else if (this.column.type === "expand") {
|
||||
this.renderType = "expand";
|
||||
} else if (this.column.render) {
|
||||
this.renderType = "render";
|
||||
} else {
|
||||
this.renderType = "normal";
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
59
frontend/src/components/table/csv.js
Normal file
59
frontend/src/components/table/csv.js
Normal file
@ -0,0 +1,59 @@
|
||||
/*
|
||||
inspired by https://www.npmjs.com/package/react-csv-downloader
|
||||
now removed from Github
|
||||
*/
|
||||
|
||||
const newLine = '\r\n';
|
||||
const appendLine = (content, row, { separator, quoted }) => {
|
||||
const line = row.map(data => {
|
||||
if (!quoted) return data;
|
||||
// quote data
|
||||
data = typeof data === 'string' ? data.replace(/"/g, '"') : data;
|
||||
return `"${data}"`;
|
||||
});
|
||||
content.push(line.join(separator));
|
||||
};
|
||||
|
||||
const defaults = {
|
||||
separator: ',',
|
||||
quoted: false
|
||||
};
|
||||
|
||||
export default function csv(columns, datas, options, noHeader = false) {
|
||||
options = Object.assign({}, defaults, options);
|
||||
let columnOrder;
|
||||
const content = [];
|
||||
const column = [];
|
||||
|
||||
if (columns) {
|
||||
columnOrder = columns.map(v => {
|
||||
if (typeof v === 'string') return v;
|
||||
if (!noHeader) {
|
||||
column.push(typeof v.title !== 'undefined' ? v.title : v.key);
|
||||
}
|
||||
return v.key;
|
||||
});
|
||||
if (column.length > 0) appendLine(content, column, options);
|
||||
} else {
|
||||
columnOrder = [];
|
||||
datas.forEach(v => {
|
||||
if (!Array.isArray(v)) {
|
||||
columnOrder = columnOrder.concat(Object.keys(v));
|
||||
}
|
||||
});
|
||||
if (columnOrder.length > 0) {
|
||||
columnOrder = columnOrder.filter((value, index, self) => self.indexOf(value) === index);
|
||||
if (!noHeader) appendLine(content, columnOrder, options);
|
||||
}
|
||||
}
|
||||
|
||||
if (Array.isArray(datas)) {
|
||||
datas.forEach(row => {
|
||||
if (!Array.isArray(row)) {
|
||||
row = columnOrder.map(k => (typeof row[k] !== 'undefined' ? row[k] : ''));
|
||||
}
|
||||
appendLine(content, row, options);
|
||||
});
|
||||
}
|
||||
return content.join(newLine);
|
||||
}
|
36
frontend/src/components/table/dom.js
Normal file
36
frontend/src/components/table/dom.js
Normal file
@ -0,0 +1,36 @@
|
||||
import Vue from 'vue';
|
||||
const isServer = Vue.prototype.$isServer;
|
||||
|
||||
/* istanbul ignore next */
|
||||
export const on = (function() {
|
||||
if (!isServer && document.addEventListener) {
|
||||
return function(element, event, handler) {
|
||||
if (element && event && handler) {
|
||||
element.addEventListener(event, handler, false);
|
||||
}
|
||||
};
|
||||
} else {
|
||||
return function(element, event, handler) {
|
||||
if (element && event && handler) {
|
||||
element.attachEvent('on' + event, handler);
|
||||
}
|
||||
};
|
||||
}
|
||||
})();
|
||||
|
||||
/* istanbul ignore next */
|
||||
export const off = (function() {
|
||||
if (!isServer && document.removeEventListener) {
|
||||
return function(element, event, handler) {
|
||||
if (element && event) {
|
||||
element.removeEventListener(event, handler, false);
|
||||
}
|
||||
};
|
||||
} else {
|
||||
return function(element, event, handler) {
|
||||
if (element && event) {
|
||||
element.detachEvent('on' + event, handler);
|
||||
}
|
||||
};
|
||||
}
|
||||
})();
|
21
frontend/src/components/table/expand.js
Normal file
21
frontend/src/components/table/expand.js
Normal file
@ -0,0 +1,21 @@
|
||||
export default {
|
||||
name: 'TableExpand',
|
||||
functional: true,
|
||||
props: {
|
||||
row: Object,
|
||||
render: Function,
|
||||
index: Number,
|
||||
column: {
|
||||
type: Object,
|
||||
default: null
|
||||
}
|
||||
},
|
||||
render: (h, ctx) => {
|
||||
const params = {
|
||||
row: ctx.props.row,
|
||||
index: ctx.props.index
|
||||
};
|
||||
if (ctx.props.column) params.column = ctx.props.column;
|
||||
return ctx.props.render(h, params);
|
||||
}
|
||||
};
|
76
frontend/src/components/table/export-csv.js
Normal file
76
frontend/src/components/table/export-csv.js
Normal file
@ -0,0 +1,76 @@
|
||||
function has(browser) {
|
||||
const ua = navigator.userAgent;
|
||||
if (browser === 'ie') {
|
||||
const isIE = ua.indexOf('compatible') > -1 && ua.indexOf('MSIE') > -1;
|
||||
if (isIE) {
|
||||
const reIE = new RegExp('MSIE (\\d+\\.\\d+);');
|
||||
reIE.test(ua);
|
||||
return parseFloat(RegExp['$1']);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return ua.indexOf(browser) > -1;
|
||||
}
|
||||
}
|
||||
|
||||
const csv = {
|
||||
_isIE11() {
|
||||
let iev = 0;
|
||||
const ieold = (/MSIE (\d+\.\d+);/.test(navigator.userAgent));
|
||||
const trident = !!navigator.userAgent.match(/Trident\/7.0/);
|
||||
const rv = navigator.userAgent.indexOf('rv:11.0');
|
||||
|
||||
if (ieold) {
|
||||
iev = Number(RegExp.$1);
|
||||
}
|
||||
if (navigator.appVersion.indexOf('MSIE 10') !== -1) {
|
||||
iev = 10;
|
||||
}
|
||||
if (trident && rv !== -1) {
|
||||
iev = 11;
|
||||
}
|
||||
|
||||
return iev === 11;
|
||||
},
|
||||
|
||||
_isEdge() {
|
||||
return /Edge/.test(navigator.userAgent);
|
||||
},
|
||||
|
||||
_getDownloadUrl(text) {
|
||||
const BOM = '\uFEFF';
|
||||
// Add BOM to text for open in excel correctly
|
||||
if (window.Blob && window.URL && window.URL.createObjectURL) {
|
||||
const csvData = new Blob([BOM + text], { type: 'text/csv' });
|
||||
return URL.createObjectURL(csvData);
|
||||
} else {
|
||||
return 'data:attachment/csv;charset=utf-8,' + BOM + encodeURIComponent(text);
|
||||
}
|
||||
},
|
||||
|
||||
download(filename, text) {
|
||||
if (has('ie') && has('ie') < 10) {
|
||||
// has module unable identify ie11 and Edge
|
||||
const oWin = window.top.open('about:blank', '_blank');
|
||||
oWin.document.charset = 'utf-8';
|
||||
oWin.document.write(text);
|
||||
oWin.document.close();
|
||||
oWin.document.execCommand('SaveAs', filename);
|
||||
oWin.close();
|
||||
} else if (has('ie') === 10 || this._isIE11() || this._isEdge()) {
|
||||
const BOM = '\uFEFF';
|
||||
const csvData = new Blob([BOM + text], { type: 'text/csv' });
|
||||
navigator.msSaveBlob(csvData, filename);
|
||||
} else {
|
||||
const link = document.createElement('a');
|
||||
link.download = filename;
|
||||
link.href = this._getDownloadUrl(text);
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export default csv;
|
16
frontend/src/components/table/header.js
Normal file
16
frontend/src/components/table/header.js
Normal file
@ -0,0 +1,16 @@
|
||||
export default {
|
||||
name: 'TableRenderHeader',
|
||||
functional: true,
|
||||
props: {
|
||||
render: Function,
|
||||
column: Object,
|
||||
index: Number
|
||||
},
|
||||
render: (h, ctx) => {
|
||||
const params = {
|
||||
column: ctx.props.column,
|
||||
index: ctx.props.index
|
||||
};
|
||||
return ctx.props.render(h, params);
|
||||
}
|
||||
};
|
2
frontend/src/components/table/index.js
Normal file
2
frontend/src/components/table/index.js
Normal file
@ -0,0 +1,2 @@
|
||||
import Table from './table.vue';
|
||||
export default Table;
|
31
frontend/src/components/table/mixin.js
Normal file
31
frontend/src/components/table/mixin.js
Normal file
@ -0,0 +1,31 @@
|
||||
export default {
|
||||
methods: {
|
||||
alignCls(column, row = {}) {
|
||||
let cellClassName = '';
|
||||
if (row.cellClassName && column.key && row.cellClassName[column.key]) {
|
||||
cellClassName = row.cellClassName[column.key];
|
||||
}
|
||||
return [
|
||||
{
|
||||
[`${cellClassName}`]: cellClassName, // cell className
|
||||
[`${column.className}`]: column.className, // column className
|
||||
[`${this.prefixCls}-column-${column.align}`]: column.align,
|
||||
[`${this.prefixCls}-hidden`]: (this.fixed === 'left' && column.fixed !== 'left') || (this.fixed === 'right' && column.fixed !== 'right') || (!this.fixed && column.fixed && (column.fixed === 'left' || column.fixed === 'right'))
|
||||
}
|
||||
];
|
||||
},
|
||||
isPopperShow(column) {
|
||||
return column.filters && ((!this.fixed && !column.fixed) || (this.fixed === 'left' && column.fixed === 'left') || (this.fixed === 'right' && column.fixed === 'right'));
|
||||
},
|
||||
setCellWidth(column) {
|
||||
let width = '';
|
||||
if (column.width) {
|
||||
width = column.width;
|
||||
} else if (this.columnsWidth[column._index]) {
|
||||
width = this.columnsWidth[column._index].width;
|
||||
}
|
||||
if (width === '0') width = '';
|
||||
return width;
|
||||
}
|
||||
}
|
||||
};
|
245
frontend/src/components/table/table-body.vue
Normal file
245
frontend/src/components/table/table-body.vue
Normal file
@ -0,0 +1,245 @@
|
||||
<template>
|
||||
<table cellspacing="0" cellpadding="0" border="0" :style="styleObject">
|
||||
<colgroup>
|
||||
<col v-for="(column, index) in columns" :width="setCellWidth(column)">
|
||||
</colgroup>
|
||||
<tbody :class="[prefixCls + '-tbody']">
|
||||
<div :style="{height: `${topPlaceholderHeight}px`}"></div>
|
||||
|
||||
<template v-for="table in showData">
|
||||
<template v-for="(row, index) in table">
|
||||
<table-tr
|
||||
:row="row"
|
||||
:key="row._rowKey"
|
||||
:prefix-cls="prefixCls"
|
||||
@mouseenter.native.stop="handleMouseIn(row._index)"
|
||||
@mouseleave.native.stop="handleMouseOut(row._index)"
|
||||
@click.native="clickCurrentRow(row._index)"
|
||||
@dblclick.native.stop="dblclickCurrentRow(row._index)"
|
||||
>
|
||||
<td v-for="column in columns" :class="alignCls(column, row)">
|
||||
<Cell
|
||||
:fixed="fixed"
|
||||
:prefix-cls="prefixCls"
|
||||
:row="row"
|
||||
:key="column._columnKey"
|
||||
:column="column"
|
||||
:natural-index="index"
|
||||
:index="row._index"
|
||||
:checked="rowChecked(row._index)"
|
||||
:disabled="rowDisabled(row._index)"
|
||||
:expanded="rowExpanded(row._index)"
|
||||
></Cell>
|
||||
</td>
|
||||
</table-tr>
|
||||
<tr v-if="rowExpanded(row._index)" :class="{[prefixCls + '-expanded-hidden']: fixed}">
|
||||
<td :colspan="columns.length" :class="prefixCls + '-expanded-cell'">
|
||||
<Expand :key="row._rowKey" :row="row" :render="expandRender" :index="row._index"></Expand>
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
</template>
|
||||
<div :style="{height: `${bottomPlaceholderHeight}px`}"></div>
|
||||
</tbody>
|
||||
</table>
|
||||
</template>
|
||||
<script>
|
||||
// todo :key="row"
|
||||
import TableTr from "./table-tr.vue";
|
||||
import Cell from "./cell.vue";
|
||||
import Expand from "./expand.js";
|
||||
import Mixin from "./mixin";
|
||||
|
||||
export default {
|
||||
name: "TableBody",
|
||||
mixins: [Mixin],
|
||||
components: { Cell, Expand, TableTr },
|
||||
props: {
|
||||
prefixCls: String,
|
||||
styleObject: Object,
|
||||
columns: Array,
|
||||
data: Array, // rebuildData
|
||||
objData: Object,
|
||||
columnsWidth: Object,
|
||||
fixed: {
|
||||
type: [Boolean, String],
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
times0: 0, // 当前是第几轮
|
||||
times1: 0,
|
||||
times2: -1,
|
||||
placeholderHeight: 0, // 占位容器的总高度(上 + 下)
|
||||
topPlaceholderHeight: 0, // 顶部占位容器高度
|
||||
bottomPlaceholderHeight: 0, // 底部占位容器高度
|
||||
currentIndex: -1, // 当前展示的表格是第几个
|
||||
showData: []
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
data: {
|
||||
handler() {
|
||||
this.clear();
|
||||
this.$nextTick(() => {
|
||||
this.handleStyle();
|
||||
});
|
||||
}
|
||||
},
|
||||
scrollTop: {
|
||||
handler() {
|
||||
this.$nextTick(() => {
|
||||
this.handleStyle();
|
||||
});
|
||||
}
|
||||
},
|
||||
currentIndex: {
|
||||
handler() {
|
||||
this.setTopPlace();
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
scrollTop() {
|
||||
return this.$parent.scrollTop;
|
||||
},
|
||||
bodyHeight() {
|
||||
return this.$parent.bodyHeight; // 表格高度
|
||||
},
|
||||
rowHeight() {
|
||||
let height = { small: 40, large: 60, default: 48 };
|
||||
return height[this.$parent.size]; // 行高
|
||||
},
|
||||
showRowNum() {
|
||||
return parseInt(this.bodyHeight / this.rowHeight) + 15; // 一次显示多少行
|
||||
},
|
||||
moduleHeight() {
|
||||
return this.showRowNum * this.rowHeight; // 容器高度
|
||||
},
|
||||
totalRowHeight() {
|
||||
return this.data.length * this.rowHeight; // 内容总高
|
||||
},
|
||||
expandRender() {
|
||||
let render = function() {
|
||||
return "";
|
||||
};
|
||||
for (let i = 0; i < this.columns.length; i++) {
|
||||
const column = this.columns[i];
|
||||
if (column.type && column.type === "expand") {
|
||||
if (column.render) render = column.render;
|
||||
}
|
||||
}
|
||||
return render;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
rowChecked(_index) {
|
||||
return this.objData[_index] && this.objData[_index]._isChecked;
|
||||
},
|
||||
rowDisabled(_index) {
|
||||
return this.objData[_index] && this.objData[_index]._isDisabled;
|
||||
},
|
||||
rowExpanded(_index) {
|
||||
return this.objData[_index] && this.objData[_index]._isExpanded;
|
||||
},
|
||||
handleMouseIn(_index) {
|
||||
this.$parent.handleMouseIn(_index);
|
||||
},
|
||||
handleMouseOut(_index) {
|
||||
this.$parent.handleMouseOut(_index);
|
||||
},
|
||||
clickCurrentRow(_index) {
|
||||
this.$parent.clickCurrentRow(_index);
|
||||
},
|
||||
dblclickCurrentRow(_index) {
|
||||
this.$parent.dblclickCurrentRow(_index);
|
||||
},
|
||||
handleStyle() {
|
||||
this.placeholderHeight = this.totalRowHeight - this.moduleHeight * 3; // 占位容器的总高度(上 + 下)
|
||||
|
||||
this.topPlaceholderHeight =
|
||||
parseInt(this.scrollTop / this.moduleHeight) * this.moduleHeight;
|
||||
|
||||
this.bottomPlaceholderHeight =
|
||||
this.placeholderHeight - this.topPlaceholderHeight < 0
|
||||
? 0
|
||||
: this.placeholderHeight - this.topPlaceholderHeight;
|
||||
|
||||
this.currentIndex = parseInt(
|
||||
(this.scrollTop % (this.moduleHeight * 3)) / this.moduleHeight
|
||||
);
|
||||
},
|
||||
clear() {
|
||||
this.showData = [];
|
||||
this.currentIndex = -1;
|
||||
this.placeholderHeight = 0;
|
||||
this.topPlaceholderHeight = 0;
|
||||
this.bottomPlaceholderHeight = 0;
|
||||
},
|
||||
setTopPlace() {
|
||||
let scrollTop = this.scrollTop;
|
||||
let t0 = 0;
|
||||
let t1 = 0;
|
||||
let t2 = 0;
|
||||
if (scrollTop > this.moduleHeight) {
|
||||
switch (this.currentIndex) {
|
||||
case 0:
|
||||
t0 = parseInt(scrollTop / (this.moduleHeight * 3));
|
||||
t1 = t2 = t0;
|
||||
break;
|
||||
case 1:
|
||||
t1 = parseInt(
|
||||
(scrollTop - this.moduleHeight) / (this.moduleHeight * 3)
|
||||
);
|
||||
t0 = t1 + 1;
|
||||
t2 = t1;
|
||||
break;
|
||||
case 2:
|
||||
t2 = parseInt(
|
||||
(scrollTop - this.moduleHeight * 2) / (this.moduleHeight * 3)
|
||||
);
|
||||
t0 = t1 = t2 + 1;
|
||||
}
|
||||
}
|
||||
this.times0 = t0;
|
||||
this.times1 = t1;
|
||||
this.times2 = t2;
|
||||
this.setTableData();
|
||||
},
|
||||
setTableData() {
|
||||
let count1 = this.times0 * this.showRowNum * 3;
|
||||
let table1 = this.data.slice(count1, count1 + this.showRowNum);
|
||||
let count2 = this.times1 * this.showRowNum * 3;
|
||||
let table2 = this.data.slice(
|
||||
count2 + this.showRowNum,
|
||||
count2 + this.showRowNum * 2
|
||||
);
|
||||
let count3 = this.times2 * this.showRowNum * 3;
|
||||
let table3 = this.data.slice(
|
||||
count3 + this.showRowNum * 2,
|
||||
count3 + this.showRowNum * 3
|
||||
);
|
||||
|
||||
switch (this.currentIndex) {
|
||||
case 0:
|
||||
this.showData = [table1, table2, table3];
|
||||
break;
|
||||
case 1:
|
||||
this.showData = [table2, table3, table1];
|
||||
break;
|
||||
case 2:
|
||||
this.showData = [table3, table1, table2];
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
|
||||
<style scoped>
|
||||
>>> .ivu-table td {
|
||||
word-break: keep-all;
|
||||
white-space: nowrap;
|
||||
}
|
||||
</style>
|
265
frontend/src/components/table/table-head.vue
Normal file
265
frontend/src/components/table/table-head.vue
Normal file
@ -0,0 +1,265 @@
|
||||
<template>
|
||||
<table cellspacing="0" cellpadding="0" border="0" :style="styles">
|
||||
<colgroup>
|
||||
<col v-for="(column, index) in columns" :width="setCellWidth(column)">
|
||||
<col v-if="$parent.showVerticalScrollBar" :width="$parent.scrollBarWidth">
|
||||
</colgroup>
|
||||
<thead>
|
||||
<tr v-for="(cols, rowIndex) in headRows">
|
||||
<th
|
||||
v-for="(column, index) in cols"
|
||||
:colspan="column.colSpan"
|
||||
:rowspan="column.rowSpan"
|
||||
:class="alignCls(column)"
|
||||
>
|
||||
<div :class="cellClasses(column)">
|
||||
<template v-if="column.type === 'expand'">
|
||||
<span v-if="!column.renderHeader">{{ column.title || '' }}</span>
|
||||
<render-header v-else :render="column.renderHeader" :column="column" :index="index"></render-header>
|
||||
</template>
|
||||
<template v-else-if="column.type === 'selection'">
|
||||
<Checkbox :value="isSelectAll" :disabled="!data.length" @on-change="selectAll"></Checkbox>
|
||||
</template>
|
||||
<template v-else>
|
||||
<span
|
||||
v-if="!column.renderHeader"
|
||||
:class="{[prefixCls + '-cell-sort']: column.sortable}"
|
||||
@click="handleSortByHead(getColumn(rowIndex, index)._index)"
|
||||
>{{ column.title || '#' }}</span>
|
||||
<render-header v-else :render="column.renderHeader" :column="column" :index="index"></render-header>
|
||||
<span :class="[prefixCls + '-sort']" v-if="column.sortable">
|
||||
<i
|
||||
class="ivu-icon ivu-icon-md-arrow-dropup"
|
||||
:class="{on: getColumn(rowIndex, index)._sortType === 'asc'}"
|
||||
@click="handleSort(getColumn(rowIndex, index)._index, 'asc')"
|
||||
></i>
|
||||
<i
|
||||
class="ivu-icon ivu-icon-md-arrow-dropdown"
|
||||
:class="{on: getColumn(rowIndex, index)._sortType === 'desc'}"
|
||||
@click="handleSort(getColumn(rowIndex, index)._index, 'desc')"
|
||||
></i>
|
||||
</span>
|
||||
<Poptip
|
||||
v-if="isPopperShow(column)"
|
||||
v-model="getColumn(rowIndex, index)._filterVisible"
|
||||
placement="bottom"
|
||||
popper-class="ivu-table-popper"
|
||||
transfer
|
||||
@on-popper-hide="handleFilterHide(getColumn(rowIndex, index)._index)"
|
||||
>
|
||||
<span :class="[prefixCls + '-filter']">
|
||||
<i
|
||||
class="ivu-icon ivu-icon-ios-funnel"
|
||||
:class="{on: getColumn(rowIndex, index)._isFiltered}"
|
||||
></i>
|
||||
</span>
|
||||
|
||||
<div
|
||||
slot="content"
|
||||
:class="[prefixCls + '-filter-list']"
|
||||
v-if="getColumn(rowIndex, index)._filterMultiple"
|
||||
>
|
||||
<div :class="[prefixCls + '-filter-list-item']">
|
||||
<checkbox-group v-model="getColumn(rowIndex, index)._filterChecked">
|
||||
<checkbox
|
||||
v-for="(item, index) in column.filters"
|
||||
:key="index"
|
||||
:label="item.value"
|
||||
>{{ item.label }}</checkbox>
|
||||
</checkbox-group>
|
||||
</div>
|
||||
<div :class="[prefixCls + '-filter-footer']">
|
||||
<i-button
|
||||
type="text"
|
||||
size="small"
|
||||
:disabled="!getColumn(rowIndex, index)._filterChecked.length"
|
||||
@click.native="handleFilter(getColumn(rowIndex, index)._index)"
|
||||
>{{ t('i.table.confirmFilter') }}</i-button>
|
||||
<i-button
|
||||
type="text"
|
||||
size="small"
|
||||
@click.native="handleReset(getColumn(rowIndex, index)._index)"
|
||||
>{{ t('i.table.resetFilter') }}</i-button>
|
||||
</div>
|
||||
</div>
|
||||
<div slot="content" :class="[prefixCls + '-filter-list']" v-else>
|
||||
<ul :class="[prefixCls + '-filter-list-single']">
|
||||
<li
|
||||
:class="itemAllClasses(getColumn(rowIndex, index))"
|
||||
@click="handleReset(getColumn(rowIndex, index)._index)"
|
||||
>{{ t('i.table.clearFilter') }}</li>
|
||||
<li
|
||||
:class="itemClasses(getColumn(rowIndex, index), item)"
|
||||
v-for="item in column.filters"
|
||||
@click="handleSelect(getColumn(rowIndex, index)._index, item.value)"
|
||||
>{{ item.label }}</li>
|
||||
</ul>
|
||||
</div>
|
||||
</Poptip>
|
||||
</template>
|
||||
</div>
|
||||
</th>
|
||||
|
||||
<th
|
||||
v-if="$parent.showVerticalScrollBar && rowIndex===0"
|
||||
:class="scrollBarCellClass()"
|
||||
:rowspan="headRows.length"
|
||||
></th>
|
||||
</tr>
|
||||
</thead>
|
||||
</table>
|
||||
</template>
|
||||
<script>
|
||||
import renderHeader from "./header";
|
||||
import Mixin from "./mixin";
|
||||
|
||||
export default {
|
||||
name: "TableHead",
|
||||
mixins: [Mixin],
|
||||
components: { renderHeader },
|
||||
props: {
|
||||
prefixCls: String,
|
||||
styleObject: Object,
|
||||
columns: Array,
|
||||
objData: Object,
|
||||
data: Array, // rebuildData
|
||||
columnsWidth: Object,
|
||||
fixed: {
|
||||
type: [Boolean, String],
|
||||
default: false
|
||||
},
|
||||
columnRows: Array,
|
||||
fixedColumnRows: Array
|
||||
},
|
||||
computed: {
|
||||
styles() {
|
||||
const style = Object.assign({}, this.styleObject);
|
||||
const width = parseInt(this.styleObject.width);
|
||||
style.width = `${width}px`;
|
||||
return style;
|
||||
},
|
||||
isSelectAll() {
|
||||
let isSelectAll = true;
|
||||
if (!this.data.length) isSelectAll = false;
|
||||
if (!this.data.find(item => !item._disabled)) isSelectAll = false; // #1751
|
||||
for (let i = 0; i < this.data.length; i++) {
|
||||
if (
|
||||
!this.objData[this.data[i]._index]._isChecked &&
|
||||
!this.objData[this.data[i]._index]._isDisabled
|
||||
) {
|
||||
isSelectAll = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return isSelectAll;
|
||||
},
|
||||
headRows() {
|
||||
const isGroup = this.columnRows.length > 1;
|
||||
if (isGroup) {
|
||||
return this.fixed ? this.fixedColumnRows : this.columnRows;
|
||||
} else {
|
||||
return [this.columns];
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
cellClasses(column) {
|
||||
return [
|
||||
`${this.prefixCls}-cell`,
|
||||
{
|
||||
[`${this.prefixCls}-hidden`]:
|
||||
!this.fixed &&
|
||||
column.fixed &&
|
||||
(column.fixed === "left" || column.fixed === "right"),
|
||||
[`${this.prefixCls}-cell-with-selection`]: column.type === "selection"
|
||||
}
|
||||
];
|
||||
},
|
||||
scrollBarCellClass() {
|
||||
let hasRightFixed = false;
|
||||
for (let i in this.headRows) {
|
||||
for (let j in this.headRows[i]) {
|
||||
if (this.headRows[i][j].fixed === "right") {
|
||||
hasRightFixed = true;
|
||||
break;
|
||||
}
|
||||
if (hasRightFixed) break;
|
||||
}
|
||||
}
|
||||
return [
|
||||
{
|
||||
[`${this.prefixCls}-hidden`]: hasRightFixed
|
||||
}
|
||||
];
|
||||
},
|
||||
itemClasses(column, item) {
|
||||
return [
|
||||
`${this.prefixCls}-filter-select-item`,
|
||||
{
|
||||
[`${this.prefixCls}-filter-select-item-selected`]:
|
||||
column._filterChecked[0] === item.value
|
||||
}
|
||||
];
|
||||
},
|
||||
itemAllClasses(column) {
|
||||
return [
|
||||
`${this.prefixCls}-filter-select-item`,
|
||||
{
|
||||
[`${this.prefixCls}-filter-select-item-selected`]: !column
|
||||
._filterChecked.length
|
||||
}
|
||||
];
|
||||
},
|
||||
selectAll() {
|
||||
const status = !this.isSelectAll;
|
||||
this.$parent.selectAll(status);
|
||||
},
|
||||
handleSort(index, type) {
|
||||
const column = this.columns[index];
|
||||
const _index = column._index;
|
||||
|
||||
if (column._sortType === type) {
|
||||
type = "normal";
|
||||
}
|
||||
this.$parent.handleSort(_index, type);
|
||||
},
|
||||
handleSortByHead(index) {
|
||||
const column = this.columns[index];
|
||||
if (column.sortable) {
|
||||
const type = column._sortType;
|
||||
if (type === "normal") {
|
||||
this.handleSort(index, "asc");
|
||||
} else if (type === "asc") {
|
||||
this.handleSort(index, "desc");
|
||||
} else {
|
||||
this.handleSort(index, "normal");
|
||||
}
|
||||
}
|
||||
},
|
||||
handleFilter(index) {
|
||||
this.$parent.handleFilter(index);
|
||||
},
|
||||
handleSelect(index, value) {
|
||||
this.$parent.handleFilterSelect(index, value);
|
||||
},
|
||||
handleReset(index) {
|
||||
this.$parent.handleFilterReset(index);
|
||||
},
|
||||
handleFilterHide(index) {
|
||||
this.$parent.handleFilterHide(index);
|
||||
},
|
||||
// 因为表头嵌套不是深拷贝,所以没有 _ 开头的方法,在 isGroup 下用此列
|
||||
getColumn(rowIndex, index) {
|
||||
const isGroup = this.columnRows.length > 1;
|
||||
|
||||
if (isGroup) {
|
||||
const id = this.headRows[rowIndex][index].__id;
|
||||
return this.columns.filter(item => item.__id === id)[0];
|
||||
} else {
|
||||
return this.headRows[rowIndex][index];
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
35
frontend/src/components/table/table-tr.vue
Normal file
35
frontend/src/components/table/table-tr.vue
Normal file
@ -0,0 +1,35 @@
|
||||
<template>
|
||||
<tr :class="rowClasses(row._index)">
|
||||
<slot></slot>
|
||||
</tr>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
row: Object,
|
||||
prefixCls: String
|
||||
},
|
||||
computed: {
|
||||
objData() {
|
||||
return this.$parent.objData;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
rowClasses(_index) {
|
||||
return [
|
||||
`${this.prefixCls}-row`,
|
||||
this.rowClsName(_index),
|
||||
{
|
||||
[`${this.prefixCls}-row-highlight`]:
|
||||
this.objData[_index] && this.objData[_index]._isHighlight,
|
||||
[`${this.prefixCls}-row-hover`]:
|
||||
this.objData[_index] && this.objData[_index]._isHover
|
||||
}
|
||||
];
|
||||
},
|
||||
rowClsName(_index) {
|
||||
return this.$parent.$parent.rowClassName(this.objData[_index], _index);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
1081
frontend/src/components/table/table.vue
Normal file
1081
frontend/src/components/table/table.vue
Normal file
File diff suppressed because it is too large
Load Diff
93
frontend/src/components/table/util.js
Normal file
93
frontend/src/components/table/util.js
Normal file
@ -0,0 +1,93 @@
|
||||
import { deepCopy } from './assist';
|
||||
|
||||
const convertColumnOrder = (columns, fixedType) => {
|
||||
let list = [];
|
||||
let other = [];
|
||||
columns.forEach((col) => {
|
||||
if (col.fixed && col.fixed === fixedType) {
|
||||
list.push(col);
|
||||
} else {
|
||||
other.push(col);
|
||||
}
|
||||
});
|
||||
return list.concat(other);
|
||||
};
|
||||
|
||||
export { convertColumnOrder };
|
||||
|
||||
// set forTableHead to true when convertToRows, false in normal cases like table.vue
|
||||
const getAllColumns = (cols, forTableHead = false) => {
|
||||
const columns = deepCopy(cols);
|
||||
const result = [];
|
||||
columns.forEach((column) => {
|
||||
if (column.children) {
|
||||
if (forTableHead) result.push(column);
|
||||
result.push.apply(result, getAllColumns(column.children, forTableHead));
|
||||
} else {
|
||||
result.push(column);
|
||||
}
|
||||
});
|
||||
return result;
|
||||
};
|
||||
|
||||
export { getAllColumns };
|
||||
|
||||
const convertToRows = (columns, fixedType = false) => {
|
||||
const originColumns = fixedType ? fixedType === 'left' ? deepCopy(convertColumnOrder(columns, 'left')) : deepCopy(convertColumnOrder(columns, 'right')) : deepCopy(columns);
|
||||
let maxLevel = 1;
|
||||
const traverse = (column, parent) => {
|
||||
if (parent) {
|
||||
column.level = parent.level + 1;
|
||||
if (maxLevel < column.level) {
|
||||
maxLevel = column.level;
|
||||
}
|
||||
}
|
||||
if (column.children) {
|
||||
let colSpan = 0;
|
||||
column.children.forEach((subColumn) => {
|
||||
traverse(subColumn, column);
|
||||
colSpan += subColumn.colSpan;
|
||||
});
|
||||
column.colSpan = colSpan;
|
||||
} else {
|
||||
column.colSpan = 1;
|
||||
}
|
||||
};
|
||||
|
||||
originColumns.forEach((column) => {
|
||||
column.level = 1;
|
||||
traverse(column);
|
||||
});
|
||||
|
||||
const rows = [];
|
||||
for (let i = 0; i < maxLevel; i++) {
|
||||
rows.push([]);
|
||||
}
|
||||
|
||||
const allColumns = getAllColumns(originColumns, true);
|
||||
|
||||
allColumns.forEach((column) => {
|
||||
if (!column.children) {
|
||||
column.rowSpan = maxLevel - column.level + 1;
|
||||
} else {
|
||||
column.rowSpan = 1;
|
||||
}
|
||||
rows[column.level - 1].push(column);
|
||||
});
|
||||
|
||||
return rows;
|
||||
};
|
||||
|
||||
export { convertToRows };
|
||||
|
||||
const getRandomStr = function(len = 32) {
|
||||
const $chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890';
|
||||
const maxPos = $chars.length;
|
||||
let str = '';
|
||||
for (let i = 0; i < len; i++) {
|
||||
str += $chars.charAt(Math.floor(Math.random() * maxPos));
|
||||
}
|
||||
return str;
|
||||
};
|
||||
|
||||
export { getRandomStr };
|
@ -0,0 +1,81 @@
|
||||
<template>
|
||||
<button class="vue-bigdata-table-button" @click="handleClick">
|
||||
<span v-if="type === 'confirm'" class="icon confirm-icon">
|
||||
<i class="line confirm-icon-line1 line1"></i>
|
||||
<i class="line confirm-icon-line2 line2"></i>
|
||||
</span>
|
||||
<span v-else class="icon cancel-icon">
|
||||
<i class="line cancel-icon-line1 line1"></i>
|
||||
<i class="line cancel-icon-line2 line2"></i>
|
||||
</span>
|
||||
</button>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
name: "Button",
|
||||
props: {
|
||||
type: String
|
||||
},
|
||||
methods: {
|
||||
handleClick(e) {
|
||||
this.$emit("click", e);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
<style lang="less">
|
||||
.vue-bigdata-table-button {
|
||||
padding: 2px 7px;
|
||||
width: 20px;
|
||||
background: transparent;
|
||||
border: none;
|
||||
vertical-align: middle;
|
||||
outline: none;
|
||||
transform: translateY(3px);
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
.line {
|
||||
background: #2d8cf0;
|
||||
transition: background 0.2s ease;
|
||||
}
|
||||
}
|
||||
.icon {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
i {
|
||||
display: inline-block;
|
||||
position: absolute;
|
||||
left: 0px;
|
||||
top: 0px;
|
||||
background: #000000;
|
||||
border-radius: 1px;
|
||||
height: 2px;
|
||||
}
|
||||
}
|
||||
.line {
|
||||
transition: background 0.2s ease;
|
||||
}
|
||||
.confirm-icon {
|
||||
&-line1 {
|
||||
width: 7px;
|
||||
transform: rotateZ(45deg) translate(3px, 0px);
|
||||
}
|
||||
&-line2 {
|
||||
width: 10px;
|
||||
transform: rotateZ(-45deg) translate(5px, 4px);
|
||||
}
|
||||
}
|
||||
.cancel-icon {
|
||||
&-line1 {
|
||||
width: 12px;
|
||||
transform: rotateZ(45deg);
|
||||
}
|
||||
&-line2 {
|
||||
width: 12px;
|
||||
transform: rotateZ(-45deg);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
@ -0,0 +1,59 @@
|
||||
import Input from './input.vue';
|
||||
import Button from './button.vue';
|
||||
export default (h, { row, col, value, beforeSave, initRowIndex }, table) => {
|
||||
return h('div', {
|
||||
'class': 'edit-item-con'
|
||||
}, [
|
||||
h(Input, {
|
||||
'class': 'edit-item-input',
|
||||
props: {
|
||||
value: value
|
||||
},
|
||||
on: {
|
||||
input(res) {
|
||||
table.editContent = res;
|
||||
}
|
||||
}
|
||||
}),
|
||||
h('div', {
|
||||
'class': 'edit-item-btn-con'
|
||||
}, [
|
||||
h(Button, {
|
||||
'class': 'edit-btn',
|
||||
props: {
|
||||
type: 'confirm'
|
||||
},
|
||||
on: {
|
||||
click() {
|
||||
if (beforeSave({ row, col, value, initRowIndex })) {
|
||||
table.$emit('on-success-save', {
|
||||
row: row,
|
||||
col: col,
|
||||
value: table.editContent,
|
||||
initRowIndex: initRowIndex
|
||||
});
|
||||
} else {
|
||||
table.$emit('on-fail-save', {
|
||||
row: row,
|
||||
col: col,
|
||||
value: table.editContent,
|
||||
initRowIndex: initRowIndex
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}),
|
||||
h(Button, {
|
||||
'class': 'edit-btn',
|
||||
props: {
|
||||
type: 'cancel'
|
||||
},
|
||||
on: {
|
||||
click() {
|
||||
table.$emit('on-cancel-edit');
|
||||
}
|
||||
}
|
||||
})
|
||||
])
|
||||
]);
|
||||
};
|
@ -0,0 +1,46 @@
|
||||
<template>
|
||||
<input class="vue-bigdata-table-input" :value="value" @input="handleInput">
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
name: "Input",
|
||||
props: {
|
||||
value: [String, Number]
|
||||
},
|
||||
methods: {
|
||||
handleInput(e) {
|
||||
this.$emit("input", e.target.value);
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.$emit("input", this.value);
|
||||
}
|
||||
};
|
||||
</script>
|
||||
<style lang="less">
|
||||
.vue-bigdata-table-input {
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
height: 32px;
|
||||
line-height: 1.5;
|
||||
padding: 4px 7px;
|
||||
font-size: 12px;
|
||||
border: 1px solid #dddee1;
|
||||
border-radius: 4px;
|
||||
color: #495060;
|
||||
background-color: #fff;
|
||||
background-image: none;
|
||||
position: relative;
|
||||
cursor: text;
|
||||
outline: none;
|
||||
transition: border 0.2s ease-in-out, background 0.2s ease-in-out,
|
||||
box-shadow 0.2s ease-in-out, -webkit-box-shadow 0.2s ease-in-out;
|
||||
&:hover {
|
||||
border-color: #57a3f3;
|
||||
}
|
||||
&:focus {
|
||||
border-color: #57a3f3;
|
||||
box-shadow: 0 0 0 2px rgba(45, 140, 240, 0.2);
|
||||
}
|
||||
}
|
||||
</style>
|
279
frontend/src/components/vue-bigdata-table/components/item-table.vue
Executable file
279
frontend/src/components/vue-bigdata-table/components/item-table.vue
Executable file
@ -0,0 +1,279 @@
|
||||
<template>
|
||||
<div class="vue-bigdata-table-item-table">
|
||||
<table
|
||||
v-show="showTable && fixedCol >= 0"
|
||||
@paste="handlePaste"
|
||||
:class="['vue-bigdata-table-data-table', 'vue-bigdata-table-fixed-table', showFixedBoxShadow ? 'box-shadow' : '']"
|
||||
cellspacing="0"
|
||||
cellpadding="0"
|
||||
border="0"
|
||||
>
|
||||
<colgroup>
|
||||
<col
|
||||
v-if="i <= fixedCol"
|
||||
:width="width"
|
||||
v-for="(width, i) in widthArr"
|
||||
:key="'colgroup-fixed-' + i"
|
||||
>
|
||||
</colgroup>
|
||||
<tbody>
|
||||
<tr
|
||||
v-for="(tr, index) in itemData"
|
||||
:key="index"
|
||||
:style="{background: currentMouseEnterIndex === index && canSelectText ? '#ebf7ff' : ''}"
|
||||
:class="[stripe && (indexBase + index) % 2 !== 0 ? 'stripe-gray' : '', tr.className, currentScrollToRowIndex === indexBase + index ? 'scroll-to-row-tip' : '', indexBase + index === highlightRowIndex ? 'highlight-row' : '']"
|
||||
@click="handleClickTr(indexBase + index, tr.initRowIndex)"
|
||||
@mouseenter.stop="handleMouseIn(index)"
|
||||
@mouseleave.stop="handleMouseLeave"
|
||||
>
|
||||
<td
|
||||
v-if="showIndex"
|
||||
:class="['vue-bigdata-table-cell', 'vue-bigdata-table-data-table-center']"
|
||||
>
|
||||
<render-dom
|
||||
:render="indexRender"
|
||||
:back-value="{index: (indexBase + index), params: indexRenderParams}"
|
||||
></render-dom>
|
||||
</td>
|
||||
<td
|
||||
v-if="i <= fixedColCom"
|
||||
:data-row="indexBase + index"
|
||||
:data-col="i"
|
||||
@click="handleClickTd(indexBase + index, i, tr.initRowIndex)"
|
||||
@dblclick="handleDblclickTd(indexBase + index, i, (typeof td === 'object' && td !== null) ? td.value : td)"
|
||||
v-for="(td, i) in tr"
|
||||
:class="['vue-bigdata-table-cell',
|
||||
setAlign(i),
|
||||
(typeof td === 'object' && td !== null) ? td.className : '',
|
||||
getSelectCellClasses(indexBase + index, i)
|
||||
]"
|
||||
:style="rowStyles"
|
||||
:key="i"
|
||||
>
|
||||
<template v-if="!canEdit || (canEdit && `${indexBase + index}-${i}` !== edittingTd)">
|
||||
<div
|
||||
v-if="!showCellRender[showIndex ? (i + 1) : i]"
|
||||
class="vue-bigdata-table-cell"
|
||||
>{{ (typeof td === 'object' && td !== null) ? td.value : td }}</div>
|
||||
<template v-else>
|
||||
<render-dom
|
||||
:render="showCellRender[showIndex ? (i + 1) : i]"
|
||||
:back-value="{row: indexBase + index, col: i, value: (typeof td === 'object' && td !== null) ? td.value : td}"
|
||||
></render-dom>
|
||||
</template>
|
||||
</template>
|
||||
<render-dom
|
||||
v-else
|
||||
:render="editCellRender"
|
||||
:back-value="{row: indexBase + index, col: i, value: (typeof td === 'object' && td !== null) ? td.value : td, beforeSave}"
|
||||
></render-dom>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<table
|
||||
v-show="showTable"
|
||||
@paste="handlePaste"
|
||||
ref="itemTable"
|
||||
class="vue-bigdata-table-data-table vue-bigdata-table-content-table"
|
||||
:style="{position: fixedCol < 0 ? '' : 'absolute'}"
|
||||
cellspacing="0"
|
||||
cellpadding="0"
|
||||
border="0"
|
||||
width="100%"
|
||||
>
|
||||
<colgroup>
|
||||
<col :width="width" v-for="(width, i) in widthArr" :key="'colgroup-' + i">
|
||||
</colgroup>
|
||||
<tbody>
|
||||
<tr
|
||||
v-for="(tr, index) in itemData"
|
||||
:key="index"
|
||||
@click="handleClickTr(indexBase + index, tr.initRowIndex)"
|
||||
@mouseenter.stop="handleMouseIn(index)"
|
||||
@mouseleave.stop="handleMouseLeave"
|
||||
:style="{background: currentMouseEnterIndex === index && canSelectText ? '#ebf7ff' : ''}"
|
||||
:class="[stripe && (indexBase + index) % 2 !== 0 ? 'stripe-gray' : '', tr.className, currentScrollToRowIndex === indexBase + index ? 'scroll-to-row-tip' : '', indexBase + index === highlightRowIndex ? 'highlight-row' : '']"
|
||||
>
|
||||
<td
|
||||
v-if="showIndex"
|
||||
:class="['vue-bigdata-table-cell', 'vue-bigdata-table-data-table-center']"
|
||||
>
|
||||
<render-dom v-if="fixedCol < 0" :render="indexRender" :back-value="(indexBase + index)"></render-dom>
|
||||
</td>
|
||||
<td
|
||||
v-for="(td, i) in tr"
|
||||
:data-row="indexBase + index"
|
||||
:data-col="i"
|
||||
@click="handleClickTd(indexBase + index, i, tr.initRowIndex)"
|
||||
@dblclick="handleDblclickTd(indexBase + index, i, (typeof td === 'object' && td !== null) ? td.value : td)"
|
||||
:class="['vue-bigdata-table-cell',
|
||||
setAlign(i),
|
||||
(typeof td === 'object' && td !== null) ? td.className : '',
|
||||
getSelectCellClasses(indexBase + index, i)
|
||||
]"
|
||||
:style="rowStyles"
|
||||
:key="i"
|
||||
>
|
||||
<template v-if="!canEdit || (canEdit && `${indexBase + index}-${i}` !== edittingTd)">
|
||||
<div
|
||||
v-if="(!showCellRender[showIndex ? (i + 1) : i]) && (i >= fixedCol)"
|
||||
class="vue-bigdata-table-cell"
|
||||
>{{ (typeof td === 'object' && td !== null) ? td.value : td }}</div>
|
||||
<template v-else-if="i >= fixedCol">
|
||||
<render-dom
|
||||
:render="showCellRender[showIndex ? (i + 1) : i]"
|
||||
:back-value="{row: indexBase + index, col: i, value: (typeof td === 'object' && td !== null) ? td.value : td}"
|
||||
></render-dom>
|
||||
</template>
|
||||
</template>
|
||||
<render-dom
|
||||
v-else
|
||||
:render="editCellRender"
|
||||
:back-value="{row: indexBase + index, col: i, value: (typeof td === 'object' && td !== null) ? td.value : td, beforeSave, initRowIndex: tr.initRowIndex}"
|
||||
></render-dom>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import renderDom from "./renderDom";
|
||||
export default {
|
||||
name: "ItemTable",
|
||||
components: {
|
||||
renderDom
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
prefix: "vue-bigdata-table-data-table",
|
||||
tableWidth: 0,
|
||||
currentMouseEnterIndex: -1,
|
||||
editInputValue: ""
|
||||
};
|
||||
},
|
||||
props: {
|
||||
times: Number,
|
||||
tableIndex: Number,
|
||||
itemData: Array,
|
||||
rowStyles: Object,
|
||||
widthArr: Array,
|
||||
columns: Array,
|
||||
itemNum: Number,
|
||||
showIndex: Boolean,
|
||||
indexRender: Function,
|
||||
stripe: Boolean,
|
||||
fixedCol: Number,
|
||||
currentScrollToRowIndex: Number,
|
||||
canEdit: Boolean,
|
||||
edittingTd: String,
|
||||
startEditType: String,
|
||||
showFixedBoxShadow: Boolean,
|
||||
editCellRender: Function,
|
||||
beforeSave: Function,
|
||||
canSelectText: Boolean,
|
||||
startSelect: Object,
|
||||
endSelect: Object,
|
||||
disabledHover: Boolean,
|
||||
highlightRow: Boolean,
|
||||
highlightRowIndex: Number,
|
||||
indexRenderParams: Object
|
||||
},
|
||||
computed: {
|
||||
showTable() {
|
||||
return this.itemData.length > 0;
|
||||
},
|
||||
indexBase() {
|
||||
return (
|
||||
this.times * this.itemNum * 3 + this.itemNum * (this.tableIndex - 1)
|
||||
);
|
||||
},
|
||||
showCellRender() {
|
||||
return this.columns.map(item => {
|
||||
return item.cellRender ? item.cellRender : undefined;
|
||||
});
|
||||
},
|
||||
baseIndex() {
|
||||
return this.showIndex ? 1 : 0;
|
||||
},
|
||||
fixedColCom() {
|
||||
return this.showIndex ? this.fixedCol - 1 : this.fixedCol;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
setAlign(i) {
|
||||
let columns = this.columns[i + this.baseIndex];
|
||||
if (!columns) return;
|
||||
let col = columns;
|
||||
return this.prefix + "-" + col.align;
|
||||
},
|
||||
backValue(row, col) {
|
||||
return {
|
||||
row: row,
|
||||
col: col
|
||||
};
|
||||
},
|
||||
handleMouseIn(index) {
|
||||
if (!this.disabledHover) return;
|
||||
this.currentMouseEnterIndex = index;
|
||||
},
|
||||
handleMouseLeave() {
|
||||
this.currentMouseEnterIndex = -1;
|
||||
},
|
||||
handleClickTr(row, initRowIndex) {
|
||||
this.$emit("on-click-tr", row, initRowIndex);
|
||||
},
|
||||
handleClickTd(row, col, initRowIndex) {
|
||||
this.$emit("on-click-td", {
|
||||
row,
|
||||
col,
|
||||
edittingTd: this.edittingTd,
|
||||
initRowIndex
|
||||
});
|
||||
},
|
||||
editCell(row, col) {
|
||||
this.$emit("on-edit-cell", row, col);
|
||||
},
|
||||
handleDblclickTd(row, col, value) {
|
||||
this.editInputValue = value;
|
||||
if (this.canEdit && this.startEditType === "dblclick")
|
||||
this.editCell(row, col);
|
||||
},
|
||||
getSelectCellClasses(row, col) {
|
||||
let startSelect = this.startSelect;
|
||||
let endSelect = this.endSelect;
|
||||
let startRow = parseInt(startSelect["row"]);
|
||||
let endRow = parseInt(endSelect["row"]);
|
||||
let startCol = parseInt(startSelect["col"]);
|
||||
return [
|
||||
startRow === row && startCol === col ? "start-select-cell" : "",
|
||||
endRow === row && endSelect["col"] === col ? "end-select-cell" : "",
|
||||
startRow === row && endSelect["col"] === col
|
||||
? "right-top-select-cell"
|
||||
: "",
|
||||
endRow === row && startCol === col ? "left-bottom-select-cell" : "",
|
||||
startRow === row && col > startCol && col < endSelect["col"]
|
||||
? "top-center-select-cell"
|
||||
: "",
|
||||
endRow === row && col > startCol && col < endSelect["col"]
|
||||
? "bottom-center-select-cell"
|
||||
: "",
|
||||
startCol === col && row > startRow && row < endRow
|
||||
? "left-center-select-cell"
|
||||
: "",
|
||||
endSelect["col"] === col && row > startRow && row < endRow
|
||||
? "right-center-select-cell"
|
||||
: ""
|
||||
];
|
||||
},
|
||||
handlePaste(e) {
|
||||
const data = e.clipboardData.getData("text/plain");
|
||||
const rowsData = data.split(/[\n\u0085\u2028\u2029]|\r\n?/g).map(row => {
|
||||
return row.split("\t");
|
||||
});
|
||||
this.$emit("on-paste", rowsData);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
@ -0,0 +1,11 @@
|
||||
export default {
|
||||
name: 'RenderCell',
|
||||
functional: true,
|
||||
props: {
|
||||
render: Function,
|
||||
backValue: [Number, Object]
|
||||
},
|
||||
render: (h, ctx) => {
|
||||
return ctx.props.render(h, ctx.props.backValue, ctx.parent);
|
||||
}
|
||||
};
|
@ -0,0 +1,51 @@
|
||||
<template>
|
||||
<span class="sort-button-wrapper" @click="handleSort">
|
||||
<i
|
||||
data-sort-btn="up"
|
||||
:class="[currentActiveColSort && currentSortType === 'up' ? 'sort-button-item-up-active' : '']"
|
||||
class="sort-button-item sort-button-item-up"
|
||||
></i>
|
||||
<i
|
||||
data-sort-btn="down"
|
||||
:class="[currentActiveColSort && currentSortType === 'down' ? 'sort-button-item-down-active' : '']"
|
||||
class="sort-button-item sort-button-item-down"
|
||||
></i>
|
||||
</span>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
name: "sortButton",
|
||||
data() {
|
||||
return {
|
||||
sortingType: ""
|
||||
};
|
||||
},
|
||||
props: {
|
||||
colIndex: Number,
|
||||
currentSortColIndex: Number,
|
||||
currentSortType: String
|
||||
},
|
||||
computed: {
|
||||
currentActiveColSort() {
|
||||
return this.colIndex === this.currentSortColIndex;
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
currentSortType(type) {
|
||||
if (this.currentSortColIndex === this.colIndex) this.sortingType = type;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleSort(e) {
|
||||
const sortType = e.target.getAttribute("data-sort-btn");
|
||||
if (this.sortingType === sortType) {
|
||||
this.sortingType = "";
|
||||
this.$emit("on-cancel-sort");
|
||||
} else {
|
||||
this.sortingType = sortType;
|
||||
this.$emit("on-sort", this.colIndex, sortType);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
2
frontend/src/components/vue-bigdata-table/index.js
Normal file
2
frontend/src/components/vue-bigdata-table/index.js
Normal file
@ -0,0 +1,2 @@
|
||||
import bigdataTable from './vue-bigdata-table.vue';
|
||||
export default bigdataTable;
|
217
frontend/src/components/vue-bigdata-table/mixins/data-handle.js
Normal file
217
frontend/src/components/vue-bigdata-table/mixins/data-handle.js
Normal file
@ -0,0 +1,217 @@
|
||||
import ItemTable from '../components/item-table.vue';
|
||||
import { iteratorByTimes, getHeaderWords } from '../util';
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
times0: 0, // 当前是第几轮
|
||||
times1: 0,
|
||||
times2: -1,
|
||||
table1Data: [],
|
||||
table2Data: [],
|
||||
table3Data: [],
|
||||
currentIndex: 0, // 当前展示的表格是第几个
|
||||
itemNum: 0, // 一块数据显示的数据条数
|
||||
timer: null,
|
||||
scrollLeft: 0,
|
||||
insideTableData: [],
|
||||
initTableData: [] // 初始表格数据,用于恢复搜索和筛选,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
cellNum() { // 表格列数
|
||||
return this.columnsHandled.length;
|
||||
},
|
||||
columnsHandled() {
|
||||
let columns = [...this.columns];
|
||||
if (this.colNum > this.columns.length) {
|
||||
let colLength = this.colNum - this.columns.length;
|
||||
let headerWordsArr = getHeaderWords(colLength);
|
||||
iteratorByTimes(colLength, (i) => {
|
||||
columns.push({
|
||||
title: headerWordsArr[i]
|
||||
});
|
||||
});
|
||||
}
|
||||
if (this.showIndex) {
|
||||
columns.unshift({
|
||||
title: 'No',
|
||||
align: 'center',
|
||||
width: this.indexWidthInside
|
||||
});
|
||||
}
|
||||
return columns;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getComputedTableDataIndex(colIndex) {
|
||||
return this.showIndex ? (colIndex - 1) : colIndex;
|
||||
},
|
||||
handleScroll(e) {
|
||||
let ele = e.srcElement || e.target;
|
||||
let { scrollTop, scrollLeft } = ele;
|
||||
this.scrollLeft = scrollLeft;
|
||||
// let direction = (scrollTop - this.scrollTop) > 0 ? 1 : ((scrollTop - this.scrollTop) < 0 ? -1 : 0); // 1 => down -1 => up 0 => stop
|
||||
this.currentIndex = parseInt((scrollTop % (this.moduleHeight * 3)) / this.moduleHeight);
|
||||
this.scrollTop = scrollTop;
|
||||
this.$nextTick(() => {
|
||||
this.setTopPlace();
|
||||
});
|
||||
},
|
||||
setTableData() {
|
||||
let count1 = this.times0 * this.itemNum * 3;
|
||||
this.table1Data = this.insideTableData.slice(count1, count1 + this.itemNum);
|
||||
let count2 = this.times1 * this.itemNum * 3;
|
||||
this.table2Data = this.insideTableData.slice(count2 + this.itemNum, count2 + this.itemNum * 2);
|
||||
let count3 = this.times2 * this.itemNum * 3;
|
||||
this.table3Data = this.insideTableData.slice(count3 + this.itemNum * 2, count3 + this.itemNum * 3);
|
||||
},
|
||||
getTables(h) {
|
||||
let table1 = this.getItemTable(h, this.table1Data, 1);
|
||||
let table2 = this.getItemTable(h, this.table2Data, 2);
|
||||
let table3 = this.getItemTable(h, this.table3Data, 3);
|
||||
if (this.currentIndex === 0) {
|
||||
return [table1, table2, table3];
|
||||
} else if (this.currentIndex === 1) {
|
||||
return [table2, table3, table1];
|
||||
} else {
|
||||
return [table3, table1, table2];
|
||||
}
|
||||
},
|
||||
renderTable(h) {
|
||||
return h('div', {
|
||||
style: this.tableWidthStyles
|
||||
}, this.getTables(h));
|
||||
},
|
||||
getItemTable(h, data, index) {
|
||||
return h(ItemTable, {
|
||||
props: {
|
||||
times: this['times' + (index - 1)],
|
||||
tableIndex: index,
|
||||
itemData: data,
|
||||
itemNum: this.itemNum,
|
||||
rowStyles: this.rowStyles,
|
||||
widthArr: this.colWidthArr,
|
||||
columns: this.columnsHandled,
|
||||
showIndex: this.showIndex,
|
||||
indexRender: this.indexRender,
|
||||
stripe: this.stripe,
|
||||
fixedCol: this.fixedCol,
|
||||
currentScrollToRowIndex: this.currentScrollToRowIndex,
|
||||
canEdit: this.canEdit,
|
||||
edittingTd: this.edittingTd,
|
||||
startEditType: this.startEditType,
|
||||
showFixedBoxShadow: this.showFixedBoxShadow,
|
||||
editCellRender: this.editCellRender,
|
||||
beforeSave: this.beforeSave,
|
||||
canSelectText: this.canSelectText,
|
||||
startSelect: this.startSelect,
|
||||
endSelect: this.endSelect,
|
||||
disabledHover: this.disabledHover,
|
||||
highlightRow: this.highlightRow,
|
||||
highlightRowIndex: this.highlightRowIndex,
|
||||
indexRenderParams: this.indexRenderParams
|
||||
},
|
||||
on: {
|
||||
'on-click-tr': (index, initRowIndex) => {
|
||||
if (this.highlightRow) this.highlightRowIndex = index;
|
||||
this.$emit('on-click-tr', index, initRowIndex);
|
||||
},
|
||||
'on-click-td': (params) => {
|
||||
this.$emit('on-click-td', params);
|
||||
},
|
||||
'on-edit-cell': (row, col) => {
|
||||
// this.edittingTd = `${row}-${col}`;
|
||||
this._editCell(row, col, false);
|
||||
},
|
||||
'on-success-save': ({ row, col, value, initRowIndex, oldValue }) => {
|
||||
let data = [...this.value];
|
||||
data[initRowIndex][col] = value;
|
||||
this.$emit('input', data);
|
||||
this.$emit('on-success-save', { row, col, value, initRowIndex, oldValue });
|
||||
this.edittingTd = '';
|
||||
},
|
||||
'on-fail-save': ({ row, col, value, initRowIndex }) => {
|
||||
this.$emit('on-fail-save', { row, col, value, initRowIndex });
|
||||
},
|
||||
'on-cancel-edit': () => {
|
||||
this.edittingTd = '';
|
||||
},
|
||||
'on-paste': (data) => {
|
||||
if (!this.paste) return;
|
||||
let value = [...this.value];
|
||||
let rowLength = data.length;
|
||||
let startSelect = this.startSelect;
|
||||
let endSelect = this.endSelect;
|
||||
let startRow = startSelect.row;
|
||||
let startCol = startSelect.col;
|
||||
let endRow = endSelect.row;
|
||||
let endCol = endSelect.col;
|
||||
let selectRow = endRow - startRow + 1;
|
||||
let selectCol = endCol - startCol + 1;
|
||||
// let lastColLength = value[0].length - startCol;
|
||||
// let lastRowLength = value.length - startRow;
|
||||
if (rowLength === 0) return;
|
||||
let colLength = data[0].length;
|
||||
if (colLength === 0) return;
|
||||
// 使用复制的数据替换原数据
|
||||
for (let r = 0; r < rowLength && r < selectRow; r++) {
|
||||
for (let c = 0; c < colLength && c < selectCol; c++) {
|
||||
let valueRow = startRow + r;
|
||||
let valueCol = startCol + c;
|
||||
if (typeof value[valueRow][valueCol] === 'object') {
|
||||
value[valueRow][valueCol].value = data[r][c];
|
||||
} else {
|
||||
value[valueRow][valueCol] = data[r][c];
|
||||
}
|
||||
}
|
||||
}
|
||||
// for (let r = startRow; r < selectRow; r++) {
|
||||
// for (let c = startCol; c < selectCol; c++) {
|
||||
// //
|
||||
// }
|
||||
// }
|
||||
this.$emit('input', value);
|
||||
this.$emit('on-paste', data);
|
||||
this._tableResize();
|
||||
}
|
||||
},
|
||||
key: 'table-item-key' + index,
|
||||
ref: 'itemTable' + index,
|
||||
attrs: {
|
||||
'data-index': index
|
||||
}
|
||||
});
|
||||
},
|
||||
_scrollToIndexRow(index) {
|
||||
index = parseInt(index);
|
||||
if (isNaN(index) || index >= this.insideTableData.length || index < 0) return;
|
||||
let scrollTop = index * this.itemRowHeight;
|
||||
this.$refs.outer.scrollTop = scrollTop;
|
||||
this.currentScrollToRowIndex = index;
|
||||
clearTimeout(this.timer);
|
||||
this.timer = setTimeout(() => {
|
||||
this.currentScrollToRowIndex = -1;
|
||||
}, 1800);
|
||||
},
|
||||
// 给表格数据添加行号,用于排序后正确修改数据
|
||||
setInitIndex(tableData) {
|
||||
return tableData.map((item, i) => {
|
||||
let row = item;
|
||||
row.initRowIndex = i;
|
||||
return row;
|
||||
});
|
||||
},
|
||||
// 获取指定行的初始行号
|
||||
_getInitRowIndexByIndex(index) {
|
||||
return this.insideTableData[index].initRowIndex;
|
||||
},
|
||||
_getIndexByInitRowIndex(index) {
|
||||
let i = -1;
|
||||
let len = this.insideTableData.length;
|
||||
while (++i < len) {
|
||||
let row = this.insideTableData[i];
|
||||
if (row.initRowIndex === index) return i;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
90
frontend/src/components/vue-bigdata-table/mixins/edit.js
Normal file
90
frontend/src/components/vue-bigdata-table/mixins/edit.js
Normal file
@ -0,0 +1,90 @@
|
||||
import { findNodeUpper } from '../util';
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
edittingTd: '', // 正在编辑的单元格的行号和列号拼接的字符串 `${row}-${col}`
|
||||
editContent: '', // 用来保存编辑的内容
|
||||
selectCellsStart: {}, // 编辑模式下可选中多行多列,此用来保存其实单元格行列号
|
||||
selectCellsEnd: {},
|
||||
selectTotalColStartIndex: -1, // 选取整列起始序列号
|
||||
selectTotalColEndIndex: -1
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
startSelect() {
|
||||
return {
|
||||
row: Math.min(this.selectCellsStart.row, this.selectCellsEnd.row),
|
||||
col: Math.min(this.selectCellsStart.col, this.selectCellsEnd.col)
|
||||
};
|
||||
},
|
||||
endSelect() {
|
||||
return {
|
||||
row: Math.max(this.selectCellsStart.row, this.selectCellsEnd.row),
|
||||
col: Math.max(this.selectCellsStart.col, this.selectCellsEnd.col)
|
||||
};
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
selectable() {
|
||||
this.selectCellsStart = {
|
||||
row: -1,
|
||||
col: -1
|
||||
};
|
||||
this.selectCellsEnd = {
|
||||
row: -1,
|
||||
col: -1
|
||||
};
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
_editCell(row, col, scrollToView = true) {
|
||||
if (!this.canEdit || row < 0 || row > this.insideTableData.length || col < 0 || col > this.columns.length || this.edittingTd === `${row}-${col}`) return;
|
||||
if (scrollToView && parseInt(this.edittingTd.split('-')[0]) !== row) this.scrollToRow(row);
|
||||
this.edittingTd = `${row}-${col}`;
|
||||
},
|
||||
handleMousedownOnTable(e) {
|
||||
if (e.button !== 0 || (!this.paste && !this.selectable)) return;
|
||||
let currentTd = e.target.tagName === 'TD' ? e.target : findNodeUpper(e.target, 'td');
|
||||
this.selectCellsStart = {
|
||||
row: currentTd.getAttribute('data-row'),
|
||||
col: currentTd.getAttribute('data-col')
|
||||
};
|
||||
this.selectCellsEnd = {
|
||||
row: currentTd.getAttribute('data-row'),
|
||||
col: currentTd.getAttribute('data-col')
|
||||
};
|
||||
this.canSelectText = false;
|
||||
document.addEventListener('mousemove', this.handleMoveOnTable);
|
||||
document.addEventListener('mouseup', this.handleUpOnTable);
|
||||
},
|
||||
handleMoveOnTable(e) {
|
||||
if (!(e.target.tagName === 'TD' || findNodeUpper(e.target, 'td'))) return;
|
||||
let currentTd = e.target.tagName === 'TD' ? e.target : findNodeUpper(e.target, 'td');
|
||||
this.selectCellsEnd = {
|
||||
row: currentTd.getAttribute('data-row'),
|
||||
col: currentTd.getAttribute('data-col')
|
||||
};
|
||||
},
|
||||
handleUpOnTable(e) {
|
||||
if (!this.paste && !this.selectable) return;
|
||||
this.canSelectText = true;
|
||||
this.handleMoveOnTable(e);
|
||||
document.removeEventListener('mousemove', this.handleMoveOnTable);
|
||||
document.removeEventListener('mouseup', this.handleUpOnTable);
|
||||
this.$emit('on-select-cells', {
|
||||
start: {
|
||||
row: this.startSelect.row,
|
||||
col: this.startSelect.col
|
||||
},
|
||||
end: {
|
||||
row: this.endSelect.row,
|
||||
col: this.endSelect.col
|
||||
}
|
||||
});
|
||||
},
|
||||
_selectCell(row, col) {
|
||||
this.selectCellsStart = { row, col };
|
||||
this.selectCellsEnd = { row, col };
|
||||
}
|
||||
}
|
||||
};
|
@ -0,0 +1,31 @@
|
||||
export default {
|
||||
methods: {
|
||||
_createEmptyData() {
|
||||
// this.$nextTick(() => {
|
||||
let rowNum = this.rowNum;
|
||||
let colNum = this.colNum;
|
||||
if (this.rowNum && this.colNum) {
|
||||
console.log(this.value.length, this.rowNum, this.colNum);
|
||||
let valueRowNum = this.value.length;
|
||||
let valueColNum = this.value[0] ? this.value[0].length : 0;
|
||||
let totalRowNum = valueRowNum + rowNum;
|
||||
let totalColNum = valueColNum + colNum;
|
||||
let value = [...this.value];
|
||||
console.log(totalRowNum, valueRowNum);
|
||||
for (let r = valueRowNum; r < totalRowNum; r++) {
|
||||
value.push([]);
|
||||
for (let c = valueColNum; c < totalColNum; c++) {
|
||||
value[r].push('');
|
||||
}
|
||||
}
|
||||
// this.
|
||||
console.log(value);
|
||||
this.$emit('input', value);
|
||||
// this.$nextTick(() => {
|
||||
// this._tableResize();
|
||||
// })
|
||||
}
|
||||
// });
|
||||
}
|
||||
}
|
||||
};
|
14
frontend/src/components/vue-bigdata-table/mixins/filter.js
Normal file
14
frontend/src/components/vue-bigdata-table/mixins/filter.js
Normal file
@ -0,0 +1,14 @@
|
||||
import { hasOneOf } from '../util';
|
||||
export default {
|
||||
methods: {
|
||||
_filter(col, queryArr) {
|
||||
let value = [...this.value];
|
||||
this.insideTableData = value.filter(item => hasOneOf(item[col], queryArr));
|
||||
this._tableResize();
|
||||
},
|
||||
_cancelFilter() {
|
||||
this.insideTableData = [...this.value];
|
||||
this._tableResize();
|
||||
}
|
||||
}
|
||||
};
|
@ -0,0 +1,65 @@
|
||||
import { findNodeUpper } from '../util';
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
isOnCellEdge: false, // 鼠标是否在表头的两个单元格之间的边框上
|
||||
canResizeCell: false,
|
||||
initCellX: 0, // 用于计算鼠标移动的距离
|
||||
scrollLeft: 0,
|
||||
colIndex: 0, // 在表头上移动时鼠标所在列的序号,
|
||||
atLeftGivenArea: false, // 是否在表头单元格指定区域(距左侧)
|
||||
atRightGivenArea: false // 是否在表头单元格指定区域(距右侧)
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
handleMousemove(e) {
|
||||
const target = e.srcElement || e.target;
|
||||
let cell = target.tagName.toUpperCase() === 'TH' ? target : findNodeUpper(target, 'th');
|
||||
let cellDomRect = cell.getBoundingClientRect();
|
||||
let atLeft = (e.pageX - cellDomRect.left) < (cellDomRect.width / 2);
|
||||
let atLeftGivenArea = (cellDomRect.left + this.atLeftCellPosi) >= e.pageX;
|
||||
let atRightGivenArea = (cellDomRect.right - e.pageX) <= this.atRightCellPosi;
|
||||
let cellIndex = parseInt(cell.getAttribute('data-index')); // 当前单元格的序号
|
||||
if (atLeft && cellIndex !== 0) {
|
||||
this.isOnCellEdge = (e.pageX - cellDomRect.left) <= 1 && cellIndex - 1 !== this.fixedCol;
|
||||
} else if (!atLeft && cellIndex !== this.cellNum - 1) {
|
||||
this.isOnCellEdge = (cellDomRect.right - e.pageX) <= 1 && cellIndex !== this.fixedCol;
|
||||
}
|
||||
e.atRightGivenArea = atRightGivenArea;
|
||||
e.atLeftGivenArea = atLeftGivenArea;
|
||||
this.atRightGivenArea = atRightGivenArea;
|
||||
this.atLeftGivenArea = atLeftGivenArea;
|
||||
let index = 0; // 调整表格列宽的左侧的表格的序列
|
||||
e.colIndex = cellIndex;
|
||||
this.colIndex = cellIndex;
|
||||
this.$emit('on-moving-on-header', e);
|
||||
if (this.canResizeCell) {
|
||||
if (atLeft) {
|
||||
index = cellIndex - 1;
|
||||
} else {
|
||||
index = cellIndex;
|
||||
}
|
||||
if (index === this.fixedCol) return;
|
||||
let widthLeft = this.widthArr[index] + e.pageX - this.initCellX;
|
||||
let widthRight = this.widthArr[index + 1] + this.initCellX - e.pageX;
|
||||
this.widthArr.splice(index, 2, widthLeft, widthRight);
|
||||
this.initCellX = e.pageX;
|
||||
}
|
||||
},
|
||||
handleMousedown(e) {
|
||||
e.colIndex = this.cellIndex;
|
||||
this.$emit('on-mousedown-on-header', e);
|
||||
if (this.isOnCellEdge) {
|
||||
this.canResizeCell = true;
|
||||
this.initCellX = e.pageX;
|
||||
}
|
||||
},
|
||||
canNotMove(e) {
|
||||
this.canResizeCell = false;
|
||||
e.colIndex = this.colIndex;
|
||||
e.atLeftGivenArea = this.atLeftGivenArea;
|
||||
e.atRightGivenArea = this.atRightGivenArea;
|
||||
this.$emit('on-mouseup-on-header', e);
|
||||
}
|
||||
}
|
||||
};
|
@ -0,0 +1,9 @@
|
||||
import headerMove from './header-move';
|
||||
import styleComputed from './style-compute';
|
||||
import dataHandle from './data-handle';
|
||||
import edit from './edit';
|
||||
import emptyTable from './empty-table';
|
||||
import sort from './sort';
|
||||
import filter from './filter';
|
||||
|
||||
export default [headerMove, styleComputed, dataHandle, edit, emptyTable, sort, filter];
|
40
frontend/src/components/vue-bigdata-table/mixins/sort.js
Normal file
40
frontend/src/components/vue-bigdata-table/mixins/sort.js
Normal file
@ -0,0 +1,40 @@
|
||||
import { sortArr, sortDesArr } from '../util';
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
sortedByColIndex: -1,
|
||||
sortedType: ''
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
showSortBtn(colIndex) {
|
||||
const sortable = this.sortable ? true : this.sortIndex !== undefined;
|
||||
return (sortable && !(this.showIndex && colIndex === 0) && (typeof this.sortIndex === 'number' ? colIndex <= this.sortIndex : this.sortIndex.indexOf(colIndex) >= 0)) || this.columnsHandled[colIndex].sortable;
|
||||
},
|
||||
handleSort(colIndex, sortType) {
|
||||
this.sortedByColIndex = colIndex;
|
||||
this.sortedType = sortType;
|
||||
let valueArr = [...this.value];
|
||||
colIndex = this.showIndex ? colIndex - 1 : colIndex;
|
||||
if (sortType === 'up') {
|
||||
sortArr(valueArr, colIndex);
|
||||
} else {
|
||||
sortDesArr(valueArr, colIndex);
|
||||
}
|
||||
this.insideTableData = [...valueArr];
|
||||
},
|
||||
handleCancelSort() {
|
||||
this.sortedByColIndex = -1;
|
||||
this.sortedType = '';
|
||||
this.insideTableData = [...this.value];
|
||||
},
|
||||
initSort() {
|
||||
if (this.defaultSort) {
|
||||
const colIndex = parseInt(Object.keys(this.defaultSort)[0]);
|
||||
if (!(colIndex || colIndex === 0)) return;
|
||||
const sortType = this.defaultSort[colIndex];
|
||||
this.handleSort(colIndex, sortType);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
@ -0,0 +1,202 @@
|
||||
import { getScrollbarWidth } from '../util';
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
wrapperHeight: 0,
|
||||
scrollTop: 0,
|
||||
moduleHeight: 0, // 三个tr块中的一块的高度
|
||||
topPlaceholderHeight: 0, // 顶部占位容器高度
|
||||
tableWidth: 0,
|
||||
widthArr: [], // 用于给数据表格传递列宽
|
||||
totalRowHeight: 0, // 如果全量渲染应该是多高,用于计算占位
|
||||
currentScrollToRowIndex: -1, // 当前跳转到的行号,用于做闪烁提示
|
||||
canSelectText: true, // 用于控制是否可选中表格文字
|
||||
indexWidthInside: 0,
|
||||
outerWidth: 0, // 外面容器宽度
|
||||
oldTableWidth: 0, // 旧的表格宽度,用于重新计算列宽
|
||||
highlightRowIndex: -1, // 高亮行号
|
||||
updateID: 0
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
fixedColCom() {
|
||||
return this.showIndex ? (this.fixedCol + 1) : this.fixedCol;
|
||||
},
|
||||
wrapperClasses() {
|
||||
return [
|
||||
this.prefix,
|
||||
this.fixed ? `${this.prefix}-fixed` : ''
|
||||
];
|
||||
},
|
||||
headerStyle() {
|
||||
return {
|
||||
height: this.headerHeight + 'px',
|
||||
transform: 'translateX(0)'
|
||||
};
|
||||
},
|
||||
showFixedBoxShadow() {
|
||||
return this.scrollLeft !== 0;
|
||||
},
|
||||
tableWidthStyles() {
|
||||
return { width: this.tableWidth + 'px' };
|
||||
},
|
||||
rowStyles() {
|
||||
return this.rowHeight !== undefined ? { height: `${this.rowHeight}px` } : {};
|
||||
},
|
||||
placeholderHeight() {
|
||||
return this.totalRowHeight - this.moduleHeight * 3; // 占位容器的总高度(上 + 下)
|
||||
},
|
||||
bottomPlaceholderHeight() {
|
||||
return (this.placeholderHeight - this.topPlaceholderHeight) < 0 ? 0 : this.placeholderHeight - this.topPlaceholderHeight;
|
||||
},
|
||||
itemRowHeight() {
|
||||
return this.rowHeight === undefined ? 48 : this.rowHeight;
|
||||
},
|
||||
colWidthArr() {
|
||||
let len = this.cellNum;
|
||||
let colWidthArr = [];
|
||||
if (this.fixedWrapperWidth) {
|
||||
let width = this.outerWidth;
|
||||
let num = this.cellNum;
|
||||
if (this.showIndex) {
|
||||
colWidthArr.push(this.indexWidth);
|
||||
width -= this.indexWidth;
|
||||
num -= 1;
|
||||
}
|
||||
let i = -1;
|
||||
let itemColWidth = width / num;
|
||||
while (++i < num) {
|
||||
colWidthArr.push(itemColWidth);
|
||||
}
|
||||
} else {
|
||||
let i = 0;
|
||||
let hasWidthCellCount = 0; // 统计设置了width的列的数量,从而为没有设置width的列分配宽度
|
||||
let noWidthCellIndexArr = []; // 没有设置宽度的列的序列
|
||||
let hasWidthCellTotalWidth = 0; // 设置了width的列一共多宽
|
||||
while (i < len) {
|
||||
if (this.columnsHandled[i].width) {
|
||||
hasWidthCellCount++;
|
||||
hasWidthCellTotalWidth += this.columnsHandled[i].width;
|
||||
colWidthArr.push(this.columnsHandled[i].width);
|
||||
} else {
|
||||
noWidthCellIndexArr.push(i);
|
||||
colWidthArr.push(0);
|
||||
}
|
||||
i++;
|
||||
}
|
||||
let noWidthCellWidth = (this.tableWidth - hasWidthCellTotalWidth) / (len - hasWidthCellCount);
|
||||
let w = 0;
|
||||
let indexArrLen = noWidthCellIndexArr.length;
|
||||
while (w < indexArrLen) {
|
||||
colWidthArr[noWidthCellIndexArr[w]] = noWidthCellWidth;
|
||||
w++;
|
||||
}
|
||||
// this.widthArr = colWidthArr;
|
||||
}
|
||||
return colWidthArr;
|
||||
},
|
||||
cursorOnHeader() {
|
||||
return this.headerTrStyle.cursor ? this.headerTrStyle.cursor : ((this.isOnCellEdge || this.canResizeCell) ? 'col-resize' : 'default');
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
highlightRow() {
|
||||
this._clearCurrentRow();
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
_tableResize() {
|
||||
this.$nextTick(() => {
|
||||
this.updateHeight();
|
||||
this.setComputedProps();
|
||||
let scrollBarWidth = this.totalRowHeight > this.wrapperHeight ? getScrollbarWidth() : 0;
|
||||
this.outerWidth = this.$refs.outer.offsetWidth - 2 - scrollBarWidth;
|
||||
let width = this.colWidth * this.columns.length + (this.showIndex ? this.indexWidthInside : 0);
|
||||
// this.tableWidth = width > this.outerWidth ? width : this.outerWidth;
|
||||
this.tableWidth = this.fixedWrapperWidth ? this.outerWidth : (width > this.outerWidth ? width : this.outerWidth);
|
||||
if (width < this.outerWidth) this._setColWidthArr();
|
||||
this.widthArr = this.colWidthArr;
|
||||
});
|
||||
},
|
||||
updateHeight() {
|
||||
this.$nextTick(() => {
|
||||
let wrapperHeight = this.$refs.outer.offsetHeight;
|
||||
this.itemNum = Math.ceil((wrapperHeight - this.headerHeight) / this.itemRowHeight) + this.appendNum;
|
||||
this.moduleHeight = this.itemNum * this.itemRowHeight;
|
||||
this.wrapperHeight = wrapperHeight;
|
||||
this.setTopPlace();
|
||||
});
|
||||
},
|
||||
setComputedProps() {
|
||||
const len = this.insideTableData.length;
|
||||
this.totalRowHeight = len * this.itemRowHeight;
|
||||
},
|
||||
setIndexWidth(len) {
|
||||
let width = 70;
|
||||
if (len <= 99) {
|
||||
width = 50;
|
||||
} else if (len > 99 && len <= 1000) {
|
||||
width = 60;
|
||||
} else if (len > 1000 && len <= 10000) {
|
||||
width = 70;
|
||||
} else if (len > 10000 && len <= 100000) {
|
||||
width = 90;
|
||||
} else {
|
||||
width = 100;
|
||||
}
|
||||
return width;
|
||||
},
|
||||
setTopPlace() {
|
||||
let scrollTop = this.scrollTop;
|
||||
let t0 = 0;
|
||||
let t1 = 0;
|
||||
let t2 = 0;
|
||||
if (scrollTop > this.moduleHeight) {
|
||||
switch (this.currentIndex) {
|
||||
case 0:
|
||||
t0 = parseInt(scrollTop / (this.moduleHeight * 3));
|
||||
t1 = t2 = t0;
|
||||
break;
|
||||
case 1:
|
||||
t1 = parseInt((scrollTop - this.moduleHeight) / (this.moduleHeight * 3));
|
||||
t0 = t1 + 1;
|
||||
t2 = t1;
|
||||
break;
|
||||
case 2:
|
||||
t2 = parseInt((scrollTop - this.moduleHeight * 2) / (this.moduleHeight * 3));
|
||||
t0 = t1 = t2 + 1;
|
||||
}
|
||||
}
|
||||
this.times0 = t0;
|
||||
this.times1 = t1;
|
||||
this.times2 = t2;
|
||||
this.topPlaceholderHeight = parseInt(scrollTop / this.moduleHeight) * this.moduleHeight;
|
||||
this.setTableData();
|
||||
},
|
||||
_initMountedHandle() {
|
||||
if (this.indexWidth === undefined) this.indexWidthInside = this.setIndexWidth(this.insideTableData.length);
|
||||
else this.indexWidthInside = this.indexWidth;
|
||||
this.oldTableWidth = this.colWidthArr.reduce((sum, b) => {
|
||||
return sum + b;
|
||||
}, 0);
|
||||
this.widthArr = this.colWidthArr;
|
||||
if ((this.colWidth * this.columns.length + (this.showIndex ? this.indexWidthInside : 0)) < this.outerWidth) this._setColWidthArr();
|
||||
},
|
||||
_setColWidthArr() {
|
||||
let widthArr = this.widthArr.map(width => {
|
||||
return width / this.oldTableWidth * this.tableWidth;
|
||||
});
|
||||
this.oldTableWidth = this.tableWidth;
|
||||
this.widthArr = widthArr;
|
||||
},
|
||||
_clearCurrentRow() {
|
||||
this.highlightRowIndex = -1;
|
||||
},
|
||||
refreshHeader() {
|
||||
this.updateID++;
|
||||
},
|
||||
_setHighlightRow(row) {
|
||||
this.highlightRowIndex = row;
|
||||
}
|
||||
}
|
||||
};
|
136
frontend/src/components/vue-bigdata-table/util/index.js
Normal file
136
frontend/src/components/vue-bigdata-table/util/index.js
Normal file
@ -0,0 +1,136 @@
|
||||
export const findNodeUpper = (ele, tag) => {
|
||||
if (ele.parentNode) {
|
||||
if (ele.parentNode.tagName === tag.toUpperCase()) {
|
||||
return ele.parentNode;
|
||||
} else {
|
||||
if (ele.parentNode) return findNodeUpper(ele.parentNode, tag);
|
||||
else return false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const getScrollbarWidth = () => {
|
||||
let oP = document.createElement('p');
|
||||
let styles = {
|
||||
width: '100px',
|
||||
height: '100px',
|
||||
overflowY: 'scroll'
|
||||
};
|
||||
for (let i in styles) {
|
||||
oP.style[i] = styles[i];
|
||||
}
|
||||
document.body.appendChild(oP);
|
||||
let scrollbarWidth = oP.offsetWidth - oP.clientWidth;
|
||||
oP.remove();
|
||||
return scrollbarWidth;
|
||||
};
|
||||
|
||||
export const createNewArray = (length, content = undefined) => {
|
||||
let i = -1;
|
||||
let arr = [];
|
||||
while (++i < length) {
|
||||
let con = Array.isArray(content) ? content[i] : content;
|
||||
arr.push(con);
|
||||
}
|
||||
return arr;
|
||||
};
|
||||
|
||||
export const iteratorByTimes = (times, fn) => {
|
||||
let i = -1;
|
||||
while (++i < times) {
|
||||
fn(i);
|
||||
}
|
||||
};
|
||||
|
||||
export const getHeaderWords = (length) => {
|
||||
let wordsArr = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'];
|
||||
let headerArr = [];
|
||||
if (length <= 26) {
|
||||
headerArr = wordsArr.slice(0, length);
|
||||
} else {
|
||||
headerArr = [...wordsArr];
|
||||
let num = length - 26;
|
||||
let firstWordIndex = 0;
|
||||
let secondWordIndex = 0;
|
||||
let i = -1;
|
||||
while (++i < num) {
|
||||
firstWordIndex = Math.floor(i / 26);
|
||||
secondWordIndex = i % 26;
|
||||
let sumWord = `${wordsArr[firstWordIndex]}${wordsArr[secondWordIndex]}`;
|
||||
headerArr.push(sumWord);
|
||||
}
|
||||
}
|
||||
return headerArr;
|
||||
};
|
||||
|
||||
// 获取数组中第一个不为空的值
|
||||
export const getFirstNotNullValue = (array, index) => {
|
||||
if (!(array && array.length)) return false;
|
||||
let r = -1;
|
||||
let rowLength = array.length;
|
||||
while (++r < rowLength) {
|
||||
let item = array[r][index];
|
||||
if (item || item === 0) return item;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
export const sortArr = (arr, index) => {
|
||||
const isChineseReg = new RegExp('[\\u4E00-\\u9FFF]+', 'g');
|
||||
if (arr.length <= 1) return;
|
||||
const firstNotNullValue = getFirstNotNullValue(arr, index);
|
||||
if (!firstNotNullValue && firstNotNullValue !== 0) return;
|
||||
if (!isChineseReg.test(firstNotNullValue)) {
|
||||
if (isNaN(Number(firstNotNullValue))) {
|
||||
// 非中文非数值
|
||||
arr.sort();
|
||||
} else {
|
||||
// 数值型
|
||||
arr.sort((a, b) => {
|
||||
return a[index] - b[index];
|
||||
});
|
||||
}
|
||||
} else {
|
||||
arr.sort((a, b) => {
|
||||
return a[index].localeCompare(b[index], 'zh');
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 倒序
|
||||
export const sortDesArr = (arr, index) => {
|
||||
const isChineseReg = new RegExp('[\\u4E00-\\u9FFF]+', 'g');
|
||||
if (arr.length <= 1) return;
|
||||
const firstNotNullValue = getFirstNotNullValue(arr, index);
|
||||
if (!firstNotNullValue && firstNotNullValue !== 0) return;
|
||||
if (!isChineseReg.test(firstNotNullValue)) {
|
||||
if (isNaN(Number(firstNotNullValue))) {
|
||||
// 非中文非数值
|
||||
arr.sort().reverse();
|
||||
} else {
|
||||
// 数值型
|
||||
arr.sort((a, b) => {
|
||||
return b[index] - a[index];
|
||||
});
|
||||
}
|
||||
} else {
|
||||
arr.sort((a, b) => {
|
||||
return b[index].localeCompare(a[index], 'zh');
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export const hasOneOf = (str, targetArr) => {
|
||||
let len = targetArr.length;
|
||||
let i = -1;
|
||||
while (++i < len) {
|
||||
if (str.indexOf(targetArr[i]) >= 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
export const oneOf = (ele, targetArr) => {
|
||||
return targetArr.indexOf(ele) > -1;
|
||||
};
|
311
frontend/src/components/vue-bigdata-table/vue-bigdata-table.less
Executable file
311
frontend/src/components/vue-bigdata-table/vue-bigdata-table.less
Executable file
@ -0,0 +1,311 @@
|
||||
@prefix: ~"vue-bigdata-table";
|
||||
@select-border-color: #3695FE;
|
||||
|
||||
@keyframes scroll-tip {
|
||||
0% {
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
50% {
|
||||
background: #d0e8ff;
|
||||
}
|
||||
}
|
||||
|
||||
.@{prefix} {
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
|
||||
* {
|
||||
font-family: "Helvetica Neue", Helvetica, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "微软雅黑", Arial, sans-serif;
|
||||
color: #495060;
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
&-outer {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
border: 1px solid #e9eaec;
|
||||
box-sizing: border-box;
|
||||
position: relative;
|
||||
|
||||
.@{prefix}-header-wrapper {
|
||||
box-sizing: border-box;
|
||||
z-index: 70;
|
||||
|
||||
&.header-wrapper-fixed {
|
||||
position: -webkit-sticky;
|
||||
position: sticky;
|
||||
}
|
||||
|
||||
table {
|
||||
table-layout: fixed;
|
||||
height: 100%;
|
||||
|
||||
tr th {
|
||||
border-right: 1px solid #e9eaec;
|
||||
border-bottom: 1px solid #e9eaec;
|
||||
background: #fff;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
word-break: break-all;
|
||||
|
||||
// &:last-child{
|
||||
// border-right: none;
|
||||
// }
|
||||
.@{prefix}-header-inside-wrapper {
|
||||
box-sizing: border-box;
|
||||
padding: 0 8px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&-fixed-header {
|
||||
position: -webkit-sticky;
|
||||
position: sticky;
|
||||
transform: translateX(0);
|
||||
left: 0;
|
||||
z-index: 110;
|
||||
transition: box-shadow .2s ease;
|
||||
|
||||
&.box-shadow {
|
||||
box-shadow: 2px 0 6px -2px rgba(0, 0, 0, .2);
|
||||
transition: box-shadow .2s ease;
|
||||
}
|
||||
}
|
||||
|
||||
&-wrapper {
|
||||
width: 100%;
|
||||
border-bottom: none;
|
||||
|
||||
.@{prefix}-content {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
// &.noselect-text{
|
||||
// -webkit-touch-callout: none;
|
||||
// -webkit-user-select: none;
|
||||
// -khtml-user-select: none;
|
||||
// -moz-user-select: none;
|
||||
// -ms-user-select: none;
|
||||
// user-select: none;
|
||||
// }
|
||||
}
|
||||
|
||||
&:nth-child(2) {
|
||||
border-top: 1px solid #e9eaec;
|
||||
}
|
||||
|
||||
&:nth-child(4) {
|
||||
border-bottom: 1px solid #e9eaec;
|
||||
}
|
||||
|
||||
.@{prefix}-data-table {
|
||||
&.@{prefix}-content-table {
|
||||
|
||||
left: 0;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
border-bottom: none;
|
||||
border-top: none;
|
||||
table-layout: fixed;
|
||||
|
||||
tr {
|
||||
background: #fff;
|
||||
|
||||
&.scroll-to-row-tip {
|
||||
animation: scroll-tip .6s 3;
|
||||
}
|
||||
|
||||
td {
|
||||
min-width: 0;
|
||||
height: 48px;
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
vertical-align: middle;
|
||||
border-bottom: 1px solid #e9eaec;
|
||||
border-right: 1px solid #e9eaec;
|
||||
|
||||
border-left: 1px solid transparent;
|
||||
border-top: 1px solid transparent; // 表格选中
|
||||
|
||||
.@{prefix}-cell {
|
||||
box-sizing: border-box;
|
||||
padding: 0 18px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
// &:last-child{
|
||||
// border-right: none;
|
||||
// }
|
||||
.edit-item-con {
|
||||
width: 100%;
|
||||
text-align: left;
|
||||
padding: 0 6px;
|
||||
box-sizing: border-box;
|
||||
|
||||
.edit-item {
|
||||
&-input {
|
||||
width: ~"calc(100% - 50px)";
|
||||
float: left;
|
||||
}
|
||||
|
||||
&-btn-con {
|
||||
float: left;
|
||||
|
||||
.edit-btn {
|
||||
width: 20px;
|
||||
margin: 7px 4px 0 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.start-select-cell {
|
||||
border-left: 1px solid @select-border-color;
|
||||
border-top: 1px solid @select-border-color;
|
||||
}
|
||||
|
||||
&.end-select-cell {
|
||||
border-right: 1px solid @select-border-color;
|
||||
border-bottom: 1px solid @select-border-color;
|
||||
}
|
||||
|
||||
&.right-top-select-cell {
|
||||
border-right: 1px solid @select-border-color;
|
||||
border-top: 1px solid @select-border-color;
|
||||
}
|
||||
|
||||
&.left-bottom-select-cell {
|
||||
border-left: 1px solid @select-border-color;
|
||||
border-bottom: 1px solid @select-border-color;
|
||||
}
|
||||
|
||||
&.top-center-select-cell {
|
||||
border-top: 1px solid @select-border-color;
|
||||
}
|
||||
|
||||
&.bottom-center-select-cell {
|
||||
border-bottom: 1px solid @select-border-color;
|
||||
}
|
||||
|
||||
&.left-center-select-cell {
|
||||
border-left: 1px solid @select-border-color;
|
||||
}
|
||||
|
||||
&.right-center-select-cell {
|
||||
border-right: 1px solid @select-border-color;
|
||||
}
|
||||
}
|
||||
|
||||
&.stripe-gray {
|
||||
background: #f8f8f9;
|
||||
}
|
||||
|
||||
&.highlight-row {
|
||||
background: #ebf7ff;
|
||||
}
|
||||
}
|
||||
|
||||
&-left {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
&-center {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
&-right {
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&-fixed {
|
||||
.@{prefix}-header-wrapper {
|
||||
top: 0;
|
||||
left: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
}
|
||||
|
||||
&-fixed-table {
|
||||
position: -webkit-sticky;
|
||||
position: sticky;
|
||||
left: 0;
|
||||
z-index: 60;
|
||||
transition: box-shadow .2s ease;
|
||||
|
||||
&.box-shadow {
|
||||
box-shadow: 2px 0 6px -2px rgba(0, 0, 0, .2);
|
||||
transition: box-shadow .2s ease;
|
||||
}
|
||||
|
||||
td {
|
||||
border-right: 1px solid #e9eaec;
|
||||
// &:last-child{
|
||||
// border-right: 1px solid #e9eaec !important;
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
&-item-table {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.sort-button {
|
||||
&-wrapper {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
width: 10px;
|
||||
height: 11px;
|
||||
transform: translateY(1px);
|
||||
}
|
||||
|
||||
&-item {
|
||||
position: absolute;
|
||||
display: inline-block;
|
||||
width: 0;
|
||||
height: 0;
|
||||
border: 4px solid transparent;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
cursor: pointer;
|
||||
transition: border-color .2s ease;
|
||||
|
||||
&-up {
|
||||
top: -4px;
|
||||
border-bottom: 4px solid #bbbec4;
|
||||
|
||||
&:hover {
|
||||
border-bottom: 4px solid #495060;
|
||||
}
|
||||
|
||||
&-active {
|
||||
border-bottom: 4px solid #2d8cf0;
|
||||
}
|
||||
}
|
||||
|
||||
&-down {
|
||||
bottom: -4px;
|
||||
border-top: 4px solid #bbbec4;
|
||||
|
||||
&:hover {
|
||||
border-top: 4px solid #495060;
|
||||
}
|
||||
|
||||
&-active {
|
||||
border-top: 4px solid #2d8cf0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
441
frontend/src/components/vue-bigdata-table/vue-bigdata-table.vue
Normal file
441
frontend/src/components/vue-bigdata-table/vue-bigdata-table.vue
Normal file
@ -0,0 +1,441 @@
|
||||
<style lang="less">
|
||||
@import "./vue-bigdata-table.less";
|
||||
</style>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="vue-bigdata-table-outer"
|
||||
ref="outer"
|
||||
@DOMMouseScroll="handleScroll"
|
||||
@scroll="handleScroll"
|
||||
>
|
||||
<div :class="wrapperClasses" :style="tableWidthStyles">
|
||||
<div class="vue-bigdata-table-wrapper" ref="outWrapper">
|
||||
<div
|
||||
:class="['vue-bigdata-table-header-wrapper', fixed ? 'header-wrapper-fixed' : '']"
|
||||
:style="headerStyle"
|
||||
>
|
||||
<slot name="top" :colWidthArr="widthArr"></slot>
|
||||
<table
|
||||
v-if="fixedCol >= 0"
|
||||
:class="['vue-bigdata-table-fixed-header', showFixedBoxShadow ? 'box-shadow' : '']"
|
||||
cellspacing="0"
|
||||
cellpadding="0"
|
||||
border="0"
|
||||
>
|
||||
<colgroup>
|
||||
<col
|
||||
v-if="i <= fixedCol"
|
||||
:width="width"
|
||||
v-for="(width, i) in widthArr"
|
||||
:key="'header-key-fixed-' + i"
|
||||
>
|
||||
</colgroup>
|
||||
<tr
|
||||
:style="{cursor: cursorOnHeader}"
|
||||
:data-update="updateID"
|
||||
@mousemove.capture.prevent="handleMousemove"
|
||||
@mousedown="handleMousedown"
|
||||
@mouseup="canNotMove"
|
||||
@mouseleave="canNotMove"
|
||||
>
|
||||
<th
|
||||
v-if="i <= fixedCol"
|
||||
v-for="(col, i) in columnsHandled"
|
||||
:data-index="i"
|
||||
:key="`table-title-${i}`"
|
||||
style="border-right: 1px solid #e9eaec;"
|
||||
>
|
||||
<span v-if="!col.render">
|
||||
{{ col.title }}
|
||||
<sort-button
|
||||
v-if="showSortBtn(i)"
|
||||
:col-index="i"
|
||||
@on-sort="handleSort"
|
||||
@on-cancel-sort="handleCancelSort"
|
||||
:current-sort-col-index="sortedByColIndex"
|
||||
:current-sort-type="sortedType"
|
||||
></sort-button>
|
||||
</span>
|
||||
<render-dom v-else :render="col.render" :back-value="getComputedTableDataIndex(i)"></render-dom>
|
||||
</th>
|
||||
</tr>
|
||||
</table>
|
||||
<table
|
||||
ref="headerTable"
|
||||
style="position: absolute;left: 0;top: 0;"
|
||||
cellspacing="0"
|
||||
cellpadding="0"
|
||||
border="0"
|
||||
width="100%"
|
||||
>
|
||||
<colgroup>
|
||||
<col :width="width" v-for="(width, i) in widthArr" :key="'header-key-' + i">
|
||||
</colgroup>
|
||||
<tr
|
||||
:style="{cursor: cursorOnHeader}"
|
||||
:data-update="updateID"
|
||||
@mousemove.capture.prevent="handleMousemove"
|
||||
@mousedown="handleMousedown"
|
||||
@mouseup="canNotMove"
|
||||
@mouseleave="canNotMove"
|
||||
>
|
||||
<th v-for="(col, i) in columnsHandled" :data-index="i" :key="`table-title-${i}`">
|
||||
<span v-if="!col.render && (i > fixedCol)">
|
||||
{{ col.title }}
|
||||
<sort-button
|
||||
v-if="showSortBtn(i)"
|
||||
:col-index="i"
|
||||
@on-sort="handleSort"
|
||||
@on-cancel-sort="handleCancelSort"
|
||||
:current-sort-col-index="sortedByColIndex"
|
||||
:current-sort-type="sortedType"
|
||||
></sort-button>
|
||||
</span>
|
||||
<render-dom
|
||||
v-else-if="(i > fixedCol)"
|
||||
:render="col.render"
|
||||
:back-value="getComputedTableDataIndex(i)"
|
||||
></render-dom>
|
||||
</th>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<div class="vue-bigdata-table-content" @mousedown="handleMousedownOnTable">
|
||||
<div :style="{height: `${topPlaceholderHeight}px`}"></div>
|
||||
<render-dom :render="renderTable"></render-dom>
|
||||
<div :style="{height: `${bottomPlaceholderHeight}px`}"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Spin fix size="large" v-if="loading">
|
||||
<slot name="loading"></slot>
|
||||
</Spin>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import renderDom from "./components/renderDom";
|
||||
import sortButton from "./components/sort-button.vue";
|
||||
import editRender from "./components/input-render";
|
||||
import mixins from "./mixins";
|
||||
export default {
|
||||
name: "bigdataTable",
|
||||
components: {
|
||||
renderDom,
|
||||
sortButton
|
||||
},
|
||||
mixins: [...mixins],
|
||||
props: {
|
||||
loading: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
/**
|
||||
* @description 是否显示序列号列
|
||||
*/
|
||||
showIndex: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
rowNum: Number,
|
||||
colNum: Number,
|
||||
/**
|
||||
* @description 表格数据二维数组
|
||||
*/
|
||||
value: {
|
||||
type: Array
|
||||
},
|
||||
/**
|
||||
* @description 表格行高
|
||||
*/
|
||||
rowHeight: {
|
||||
type: Number,
|
||||
default: 48
|
||||
},
|
||||
/**
|
||||
* @description 是否固定表头
|
||||
*/
|
||||
fixed: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
/**
|
||||
* @description 设为true后表格列宽总是平分容器宽度减去indexWidth后的宽度
|
||||
*/
|
||||
fixedWrapperWidth: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
/**
|
||||
* @description 是否取消鼠标悬浮高亮效果
|
||||
*/
|
||||
disabledHover: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
/**
|
||||
* @description 点击一行是否高亮
|
||||
*/
|
||||
highlightRow: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
/**
|
||||
* @description 表头数组,元素为单个表头的对象,【{ title: 'xxx', render: (h) => {} }】
|
||||
*/
|
||||
columns: {
|
||||
type: Array
|
||||
},
|
||||
/**
|
||||
* @description 列宽,如果单独列中指定了宽度则按单独列,如果所有宽度加起来比容器宽度小,则平分宽度,否则用colWidth
|
||||
*/
|
||||
colWidth: {
|
||||
type: Number,
|
||||
default: 100
|
||||
},
|
||||
/**
|
||||
* @description 表头高度
|
||||
*/
|
||||
headerHeight: {
|
||||
type: Number,
|
||||
default: 52
|
||||
},
|
||||
/**
|
||||
* @description 表头tr行的样式
|
||||
*/
|
||||
headerTrStyle: {
|
||||
type: Object,
|
||||
default: () => {
|
||||
return {};
|
||||
}
|
||||
},
|
||||
/**
|
||||
* @description 序列号列宽,如果没有设置,则会根据数据行数自动计算适合的宽度
|
||||
*/
|
||||
indexWidth: Number,
|
||||
/**
|
||||
* @description 序列号渲染render
|
||||
*/
|
||||
indexRender: {
|
||||
type: Function,
|
||||
default: (h, params) => {
|
||||
return h("span", params.index + 1);
|
||||
}
|
||||
},
|
||||
/**
|
||||
* @description indexRender的第三个参数
|
||||
*/
|
||||
indexRenderParams: {
|
||||
type: Object,
|
||||
default: () => {
|
||||
return {};
|
||||
}
|
||||
},
|
||||
/**
|
||||
* @description 是否显示斑马线
|
||||
*/
|
||||
stripe: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
/**
|
||||
* @description 当前鼠标在表头单元格左侧atLeftCellPosi像素处
|
||||
*/
|
||||
atLeftCellPosi: {
|
||||
type: Number,
|
||||
default: 80
|
||||
},
|
||||
/**
|
||||
* @description 当前鼠标在表头单元格右侧atRightCellPosi像素处
|
||||
*/
|
||||
atRightCellPosi: {
|
||||
type: Number,
|
||||
default: 80
|
||||
},
|
||||
/**
|
||||
* @description 固定的列的范围,[0, fixedCol],设为2即固定0,1,2列,这三列横向不滚动
|
||||
*/
|
||||
fixedCol: {
|
||||
type: Number,
|
||||
default: -1
|
||||
},
|
||||
/**
|
||||
* @description 根据表格容器高度计算内置单个表格(1/3)渲染的行数基础上额外渲染的行数,行数越多表格接替渲染效果越好,但越耗性能
|
||||
*/
|
||||
appendNum: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
/**
|
||||
* @description 当前是否可编辑
|
||||
*/
|
||||
canEdit: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
/**
|
||||
* @description 触发编辑单元格的方式,enum:['dblclick' => 双击单元格]
|
||||
*/
|
||||
startEditType: {
|
||||
type: String,
|
||||
default: "dblclick"
|
||||
},
|
||||
/**
|
||||
* @description 编辑单元格所渲染元素的render函数,如果不传则使用内置元素
|
||||
*/
|
||||
editCellRender: {
|
||||
type: Function,
|
||||
default: editRender
|
||||
},
|
||||
/**
|
||||
* @description 保存修改的单元格内容之前的钩子,如果该函数返回false,则阻止保存
|
||||
*/
|
||||
beforeSave: {
|
||||
type: Function,
|
||||
default: () => {
|
||||
return true;
|
||||
}
|
||||
},
|
||||
/**
|
||||
* @description 是否可选择单元格
|
||||
*/
|
||||
selectable: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
/**
|
||||
* @description 是否可粘贴,如果设为true,则selectable效果为true
|
||||
*/
|
||||
paste: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
/**
|
||||
* @description 是否可排序
|
||||
*/
|
||||
sortable: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
/**
|
||||
* @description 开启排序的列序号数组或序号
|
||||
*/
|
||||
sortIndex: [Array, Number],
|
||||
/**
|
||||
* @description 默认按指定列指定排序方式排序
|
||||
*/
|
||||
defaultSort: Object
|
||||
},
|
||||
/**
|
||||
* Events
|
||||
* @on-click-tr des: 点击行时触发
|
||||
* arg: index: 当前行号, initRowIndex: 初始行号(排序筛选之前)
|
||||
* @on-click-td des: 点击单元格触发
|
||||
* arg: { row: 行号, col: 列号 }
|
||||
* @on-success-save des: 编辑成功时触发
|
||||
* arg: { row: 行号, col: 列号, value: 修改后的值, initRowIndex: 初始行号 }
|
||||
* @on-fail-save des: 编辑失败时触发
|
||||
* arg: 同on-success-save
|
||||
* @on-paste des: 粘贴数据成功时触发
|
||||
* des: data: 粘贴的数据的二维数组
|
||||
*/
|
||||
data() {
|
||||
return {
|
||||
prefix: "vue-bigdata-table"
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
// 涉及到表格容器尺寸变化或数据变化的情况调用此方法重新计算相关值
|
||||
resize(changeInitIndex) {
|
||||
// this.insideTableData = [...this.value]
|
||||
this.$nextTick(() => {
|
||||
if (changeInitIndex)
|
||||
this.insideTableData = this.setInitIndex(this.value);
|
||||
else this.insideTableData = [...this.value];
|
||||
this.initSort();
|
||||
// this._initMountedHandle();
|
||||
});
|
||||
// this._tableResize();
|
||||
},
|
||||
// 获取表格横向滚动的距离
|
||||
getScrollLeft() {
|
||||
return this.$refs.outer.scrollLeft;
|
||||
},
|
||||
// 调用此方法跳转到某条数据
|
||||
scrollToRow(row) {
|
||||
this._scrollToIndexRow(row);
|
||||
},
|
||||
// canEdit为true时调用此方法使第row+1行第col+1列变为编辑状态,这里的行列指的是表格显示的行和除序列号列的列
|
||||
editCell(row, col, scrollToView) {
|
||||
this._editCell(row, col, scrollToView);
|
||||
},
|
||||
// canEdit为true时调用此方法使指定单元格被选中
|
||||
selectCell(row, col) {
|
||||
this._selectCell(row, col);
|
||||
},
|
||||
// 手动设置高亮行
|
||||
setHighlightRow(row) {
|
||||
this._setHighlightRow(row);
|
||||
},
|
||||
/**
|
||||
* @argument {Number} col 要按哪一列筛选的列号
|
||||
* @argument {Array} queryArr 筛选关键字数组
|
||||
* @description 按照某一列的指定关键词进行筛选
|
||||
*/
|
||||
filter(col, queryArr) {
|
||||
this._filter(col, queryArr);
|
||||
},
|
||||
/**
|
||||
* @description 取消筛选
|
||||
*/
|
||||
cancelFilter() {
|
||||
this._cancelFilter();
|
||||
},
|
||||
undo() {
|
||||
this._undo();
|
||||
},
|
||||
/**
|
||||
* @description 清除高亮项目
|
||||
*/
|
||||
clearCurrentRow() {
|
||||
this._clearCurrentRow();
|
||||
},
|
||||
/**
|
||||
* @description 获取指定行的初始行号
|
||||
*/
|
||||
getInitRowIndexByIndex(row) {
|
||||
return this._getInitRowIndexByIndex(row);
|
||||
},
|
||||
/**
|
||||
* @description 获取指定初始行号的当前行号
|
||||
*/
|
||||
getIndexByInitRowIndex(initRow) {
|
||||
return this._getIndexByInitRowIndex(initRow);
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value() {
|
||||
this.$nextTick(() => {
|
||||
this.insideTableData = this.setInitIndex(this.value);
|
||||
this.initSort();
|
||||
this._initMountedHandle();
|
||||
});
|
||||
},
|
||||
insideTableData() {
|
||||
this._tableResize();
|
||||
},
|
||||
defaultSort() {
|
||||
this.insideTableData = this.setInitIndex(this.value);
|
||||
this._initMountedHandle();
|
||||
this.resize();
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.$nextTick(() => {
|
||||
this.insideTableData = this.setInitIndex(this.value);
|
||||
this._initMountedHandle();
|
||||
this.resize();
|
||||
});
|
||||
}
|
||||
};
|
||||
</script>
|
@ -127,15 +127,14 @@
|
||||
</div>
|
||||
</Col>
|
||||
<Col offset="1" span="7">
|
||||
<Table
|
||||
<BTable
|
||||
ref="cardSelection"
|
||||
:loading="cardLoading"
|
||||
:columns="cardColumns"
|
||||
size="small"
|
||||
:loading="cardLoading"
|
||||
:columns="cardColumns2"
|
||||
:data="cards"
|
||||
@on-selection-change="handleSelectCards"
|
||||
height="450"
|
||||
></Table>
|
||||
:height="449"
|
||||
></BTable>
|
||||
</Col>
|
||||
</Row>
|
||||
</div>
|
||||
@ -148,3 +147,14 @@
|
||||
</template>
|
||||
|
||||
<script src="./js/cards.js"></script>
|
||||
|
||||
<style scoped>
|
||||
>>> .ivu-modal {
|
||||
top: 50px;
|
||||
}
|
||||
|
||||
>>> .ivu-table {
|
||||
font-size: 11px;
|
||||
}
|
||||
</style>
|
||||
|
||||
|
@ -1,4 +1,7 @@
|
||||
export default {
|
||||
components: {
|
||||
BTable: resolve => require(['components/table'], resolve)
|
||||
},
|
||||
props: {
|
||||
show: {
|
||||
type: Boolean,
|
||||
@ -20,13 +23,17 @@ export default {
|
||||
show: true
|
||||
},
|
||||
params: {
|
||||
company_id: "",
|
||||
package_id: "",
|
||||
carrier_operator: "",
|
||||
time: [],
|
||||
used: 0,
|
||||
sim: ""
|
||||
company_id: '',
|
||||
package_id: '',
|
||||
carrier_operator: '',
|
||||
time: [
|
||||
this.moment().subtract('2', 'months').startOf('month').format('YYYY-MM-DD'),
|
||||
this.moment().subtract('2', 'months').endOf('month').format('YYYY-MM-DD')
|
||||
],
|
||||
used: '',
|
||||
sim: ''
|
||||
},
|
||||
showCards: [],
|
||||
orderColumns: [
|
||||
{
|
||||
width: 60,
|
||||
@ -96,52 +103,52 @@ export default {
|
||||
{
|
||||
title: "订单编号",
|
||||
key: "sn",
|
||||
width: 200
|
||||
width: 165
|
||||
},
|
||||
|
||||
{
|
||||
title: "企业名称",
|
||||
key: "company_name",
|
||||
width: 280
|
||||
width: 210
|
||||
},
|
||||
{
|
||||
title: "运营商",
|
||||
key: "carrier_operator_name",
|
||||
width: 90
|
||||
width: 75
|
||||
},
|
||||
{
|
||||
title: "套餐名称",
|
||||
key: "package_name",
|
||||
width: 180
|
||||
width: 150
|
||||
},
|
||||
{
|
||||
title: "支付方式",
|
||||
key: "pay_channel_name",
|
||||
width: 100
|
||||
width: 90
|
||||
},
|
||||
{
|
||||
title: "订单卡量",
|
||||
key: "counts",
|
||||
width: 100
|
||||
width: 90
|
||||
},
|
||||
{
|
||||
title: "订单金额",
|
||||
key: "total_price",
|
||||
width: 120
|
||||
width: 100
|
||||
},
|
||||
|
||||
{
|
||||
title: "订单时间",
|
||||
key: "order_at",
|
||||
width: 170
|
||||
width: 150
|
||||
},
|
||||
{
|
||||
title: "所需卡量",
|
||||
key: "",
|
||||
width: 100
|
||||
width: 90
|
||||
}
|
||||
],
|
||||
cardColumns: [
|
||||
cardColumns2: [
|
||||
{
|
||||
width: 60,
|
||||
align: "center",
|
||||
@ -201,6 +208,11 @@ export default {
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
title: "序号",
|
||||
key: "_index",
|
||||
width: 140
|
||||
},
|
||||
{
|
||||
title: "SIM",
|
||||
key: "sim",
|
||||
@ -231,6 +243,87 @@ export default {
|
||||
key: "package_name",
|
||||
width: 200
|
||||
}
|
||||
],
|
||||
|
||||
cardColumns: [
|
||||
{
|
||||
width: 60,
|
||||
align: 'center',
|
||||
render: (h) => {
|
||||
let value = false;
|
||||
let indeterminate = false;
|
||||
|
||||
let select = this.selected.find(item => {
|
||||
return item.id === this.order_id;
|
||||
});
|
||||
|
||||
value = !!select;
|
||||
|
||||
indeterminate =
|
||||
select &&
|
||||
select.cards &&
|
||||
select.cards.length !== this.cards.length;
|
||||
|
||||
return h("Checkbox", {
|
||||
props: {
|
||||
indeterminate: value && indeterminate,
|
||||
value: value
|
||||
},
|
||||
on: {
|
||||
input: value => {
|
||||
console.log(value);
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
cellRender: (h, context) => {
|
||||
let value = !!context.value;
|
||||
|
||||
return h("Checkbox", {
|
||||
props: {
|
||||
value: value
|
||||
},
|
||||
on: {
|
||||
input: value => {
|
||||
console.log(value);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
title: "SIM",
|
||||
width: 135,
|
||||
align: 'center'
|
||||
},
|
||||
{
|
||||
title: "状态",
|
||||
width: 100,
|
||||
align: 'center',
|
||||
cellRender: (h, context) => {
|
||||
return h(
|
||||
"Tag", {
|
||||
props: {
|
||||
color: context.value ? "error" : "primary"
|
||||
}
|
||||
},
|
||||
context.value ? "已使用" : "未使用"
|
||||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
title: "数量",
|
||||
width: 80,
|
||||
align: 'center'
|
||||
},
|
||||
{
|
||||
title: "VD企业",
|
||||
width: 210
|
||||
},
|
||||
{
|
||||
title: "VD套餐",
|
||||
width: 150
|
||||
}
|
||||
]
|
||||
};
|
||||
},
|
||||
@ -297,6 +390,17 @@ export default {
|
||||
this.$store
|
||||
.dispatch("getCards", params)
|
||||
.then(cards => {
|
||||
this.showCards = cards.map(item => {
|
||||
return [
|
||||
false,
|
||||
item.sim,
|
||||
item.virtual_order_id ? 1 : 0,
|
||||
item.counts,
|
||||
item.virtual ? item.virtual.company_name : '',
|
||||
item.virtual ? item.virtual.package_name : ''
|
||||
];
|
||||
});
|
||||
|
||||
this.cardLoading = false;
|
||||
resolve(cards);
|
||||
})
|
||||
@ -316,12 +420,13 @@ export default {
|
||||
},
|
||||
resetSearch() {
|
||||
for (let k in this.params) {
|
||||
if (k === "time") {
|
||||
this.params[k] = [];
|
||||
} else if (k === "used") {
|
||||
this.params[k] = 0;
|
||||
if (k === 'time') {
|
||||
this.params[k] = [
|
||||
this.moment().subtract('2', 'months').startOf('month').format('YYYY-MM-DD'),
|
||||
this.moment().subtract('2', 'months').endOf('month').format('YYYY-MM-DD')
|
||||
];
|
||||
} else {
|
||||
this.params[k] = "";
|
||||
this.params[k] = '';
|
||||
}
|
||||
}
|
||||
this.index(1);
|
||||
@ -335,15 +440,20 @@ export default {
|
||||
cards: cards
|
||||
};
|
||||
|
||||
this.cards = this.cards.map(item => {
|
||||
item._checked = true;
|
||||
this.showCards = this.showCards.map(item => {
|
||||
item[0] = true;
|
||||
return item;
|
||||
});
|
||||
|
||||
this.$store.commit("PUSH_REAL_ORDER_SELECTED", obj);
|
||||
this.$store.commit('PUSH_REAL_ORDER_SELECTED', obj);
|
||||
});
|
||||
} else {
|
||||
this.$store.commit("REMOVE_REAL_ORDER_SELECTED", row.id);
|
||||
this.showCards = this.showCards.map(item => {
|
||||
item[0] = false;
|
||||
return item;
|
||||
});
|
||||
|
||||
this.$store.commit('REMOVE_REAL_ORDER_SELECTED', row.id);
|
||||
}
|
||||
},
|
||||
handleSelectCards(t, a, b) {
|
||||
|
@ -144,6 +144,9 @@ export default {
|
||||
this.params[k] = '';
|
||||
}
|
||||
|
||||
this.params.unit_price = 0;
|
||||
this.params.counts = 0;
|
||||
this.params.area = [];
|
||||
this.my_show = false;
|
||||
},
|
||||
handleChange(type) {
|
||||
|
@ -10,6 +10,7 @@ module.exports = {
|
||||
lintOnSave: true,
|
||||
chainWebpack: config => {
|
||||
config.resolve.alias
|
||||
.set('node_modules', resolve('node_modules'))
|
||||
.set('@', resolve('src')) // key,value自行定义,比如.set('@@', resolve('src/components'))
|
||||
.set('src', resolve('src'))
|
||||
.set('views', resolve('src/views'))
|
||||
|
@ -1,13 +1,20 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
|
||||
require_once realpath(dirname(__FILE__) . '/TestCase.php');
|
||||
|
||||
Schema::dropIfExists('real_order_cards_table_partition');
|
||||
DB::table('virtual_orders')->truncate();
|
||||
|
||||
Schema::create('real_order_cards_table_partition', function (Blueprint $table) {
|
||||
DB::table('virtual_order_cards_partition')->truncate();
|
||||
|
||||
dd();
|
||||
|
||||
Schema::dropIfExists('real_order_cards_partition');
|
||||
|
||||
Schema::create('real_order_cards_partition', function (Blueprint $table) {
|
||||
$table->increments('id')->comment('关联表ID');
|
||||
$table->tinyInteger('type')->unsigned()->default(0)->comment('订单类型(1:套餐续费 2:续费包 3:加油包 4:可选包 5:附加包)');
|
||||
$table->bigInteger('sim')->unsigned()->default(0)->comment('sim号');
|
||||
|
Loading…
x
Reference in New Issue
Block a user