@wiajs/ui
Version:
wia app ui packages
1,368 lines • 74.3 kB
JavaScript
/** @jsxImportSource @wiajs/core */ /**
* 数据表组件
*/ import { jsx as _jsx, jsxs as _jsxs } from "@wiajs/core/jsx-runtime";
import { Utils, Event } from '@wiajs/core';
import Table from '../table';
// import * as view from './view'
import { log as Log } from '@wiajs/util';
import { getThumb } from '../editTable/attach';
const log = Log({
m: 'dataTable'
}) // 创建日志实例
;
const g = {
/** @type {*} */ lightbox: null
};
// 缺省值
const def = {
selector: '.data-table',
name: 'tbData',
domProp: 'wiaDataTable'
};
const _cfg = {
pageLink: 10,
/** @type {string[]} */ fix: [],
sum: false,
layout: 'auto',
fontSize: 16,
padding: 16,
icon: 16
};
/**
* f7 DataTable 扩展,根据数据源动态创建表格
*/ export default class DataTable extends Event {
/**
* 表构造
* @param {*} page 页面实例
* @param {*} opts 选项,激活名称
*/ constructor(page, opts){
const opt = {
...def,
...opts
};
super(opt, [
page
]), this._sel = new Set() // 选择
, /** @type {*} */ this._tb = null // f7Table 实例
, /** @type {*} */ this.head = [] // 表头
, /** @type {*} */ this.data = [] // 数据
, /** @type {*} */ this.el = null // 整体容器,包括header
, /** @type {*} */ this.tb = null // html 表格
, this._lastW = 0 // resize
, this._lastH = 0 // resize
;
const cfg = {
..._cfg,
...opt.head[0] || {}
};
const _ = this;
// 加载全局函数
if (!$.openFileUrl) $.openFileUrl = openFileUrl;
if (!$.dataTb) $.dataTb = {};
if (!$.dataTb.formatVal) $.dataTb.formatVal = formatVal;
if (!$.dataTb.attachVal) $.dataTb.attachVal = attachVal;
if (!$.dataTb.number) $.dataTb.number = number;
_.page = page;
_.view = page.view;
_.opt = opt;
_.cfg = cfg;
_.head = opt.head;
_.lastW = window.innerWidth;
_.lastH = window.innerHeight;
// 容器
const $el = opt.el || page.view.findNode(opt.selector);
if ($el.length === 0) return undefined;
_.el = $el;
// 已创建,直接返回
if (_.el.dom[opt.domProp]) {
const instance = _.el.dom[opt.domProp];
_.destroy();
return instance;
}
_.el.dom[opt.domProp] = this;
// 表数据
if (opt.data && opt.data.length > 0) {
// 克隆数组数据,用于排序、分页,不改变原数据
_.data = [
...opt.data
];
if (cfg.page && !cfg.pageLink) cfg.pageLink = 10;
}
const { checkbox: ck } = cfg;
// 空数组作为 index
if (Array.isArray(ck) && ck.length === 0) cfg.checkbox = 'index';
// index 需对数组添加index属性
if (_.data && cfg.checkbox === 'index') _.data.forEach((v, x)=>v.index = x);
_.checkEvent = _.oncheck.bind(_);
// 生成表html
_.render();
}
get sel() {
return this._sel;
}
/**
* 清除选择(包括跨页),切换表头区及表头checkbox状态
*/ clearSel() {
if (this._sel?.size) this._sel.clear();
// 切换表头为非选择模式
this.el.removeClass('data-table-has-checked');
// 更新header的checkbox
const col = 0;
const ckb = this.el.findNode(`thead .checkbox-cell:nth-child(${col + 1}) input[type="checkbox"]`);
ckb.prop('indeterminate', false) // 部分选中
;
}
/**
* 选择表所有行,包括跨页
* 表头checkbox只能选择当前页面所有行
*/ selAll() {
this.clearSel();
this.data.forEach((v, x)=>this._sel.add(x));
// 每行checkbox
this.reCheck();
// 表头选择区
this._tb.headerSel();
// 发射行选择check事件,触发一次,逐行触发效率低
this.emit('local::check', this._sel);
}
/**
* 选择表所有行,包括跨页
* 表头checkbox只能选择当前页面所有行
*/ cancelSel() {
this.clearSel();
// 每行checkbox
this.reCheck();
// 表头选择区
this._tb.headerSel();
// 发射行选择check事件,触发一次,逐行触发效率低
this.emit('local::check', this._sel);
}
/**
* 按数据生成tbHead
// <tr>
// <th colspan="2" rowspan="1">合并的前两列</th>
// <th rowspan="2">第三列</th>
// <th rowspan="2">第四列</th>
// <th rowspan="2">第五列</th>
// </tr>
// <tr>
// <th>第一列</th>
// <th>第二列</th>
// </tr>
* @param {*} head 表头数据
* @returns
*/ th(head) {
let R;
try {
// 表头分组
const cat = head.some((v)=>v.cat);
if (cat) {
const hs1 = [] // 第一行
;
const hs2 = [];
if (head[0].checkbox) // 第二行
hs1.push(/*#__PURE__*/ _jsx("th", {
rowspan: "2",
class: "checkbox-cell",
children: /*#__PURE__*/ _jsxs("label", {
class: "checkbox",
children: [
/*#__PURE__*/ _jsx("input", {
type: "checkbox"
}),
/*#__PURE__*/ _jsx("i", {
class: "icon-checkbox"
})
]
})
}));
let lastCat = [
'',
0
];
for(let i = 1; i < head.length; i++){
const d = head[i];
const cls = [
d.type === 'number' ? 'numeric-cell' : 'label-cell'
];
if (d.sort) cls.push('sortable-cell');
if (d.cat || lastCat[1]) {
if (d.cat) {
lastCat = d.cat;
hs1.push(/*#__PURE__*/ _jsx("th", {
colspan: d.cat[1],
rowspan: "1",
children: d.cat[0]
}));
}
hs2.push(/*#__PURE__*/ _jsx("th", {
class: cls.join(' '),
children: d.name
}));
// @ts-ignore
lastCat[1]--;
} else {
hs1.push(/*#__PURE__*/ _jsx("th", {
rowspan: "2",
class: cls.join(' '),
children: d.name
}));
}
}
R = /*#__PURE__*/ _jsxs("thead", {
name: "tbHead",
children: [
/*#__PURE__*/ _jsx("tr", {
children: hs1
}),
/*#__PURE__*/ _jsx("tr", {
children: hs2
})
]
});
} else {
const hs = [];
if (head[0].checkbox) hs.push(/*#__PURE__*/ _jsx("th", {
class: "checkbox-cell",
children: /*#__PURE__*/ _jsxs("label", {
class: "checkbox",
children: [
/*#__PURE__*/ _jsx("input", {
type: "checkbox"
}),
/*#__PURE__*/ _jsx("i", {
class: "icon-checkbox"
})
]
})
}));
for(let i = 1; i < head.length; i++){
const d = head[i];
const cls = [
d.type === 'number' ? 'numeric-cell' : 'label-cell'
];
if (d.sort) cls.push('sortable-cell');
hs.push(/*#__PURE__*/ _jsx("th", {
class: cls.join(' '),
children: d.name
}));
}
R = /*#__PURE__*/ _jsx("thead", {
name: "tbHead",
children: /*#__PURE__*/ _jsx("tr", {
children: hs
})
});
}
} catch (e) {
log.err(e, 'th');
}
return R;
}
/**
* 按标头配置生成tr td数据模板
* 支持link、hide参数
* @param {*} head 表头数据
* @returns
*/ td(head) {
const R = [];
const { hide, link } = head[0];
let col = -1 // 数据列,隐藏字段需跳过
;
for(let i = 1, len = head.length; i < len; i++){
col++ // 从 0 开始
;
// 跳过隐藏列,隐藏列不显示
while(hide?.includes(col))col++;
const h = head[i];
// TODO 跳转链接,需触发页面事件,方便页面类执行跳转
if (h.value) {
col-- // 自带value,不消耗数据
;
// <a data-tag="edit">编辑</a>
R.push(/*#__PURE__*/ _jsx("td", {
class: "label-cell",
"data-col": i,
children: h.value
}));
} else {
h.idx = col // 对应数据列
;
const { type, div, qian, unit, mul, decimal, format, align } = h;
let opt = {
type,
div,
qian,
unit,
mul,
decimal,
format
};
opt = JSON.stringify(opt);
let cls = h.type === 'number' ? 'numeric-cell' : 'label-cell';
if (align) cls += `align-${align}`;
if (h.type === 'attach') {
R.push(/*#__PURE__*/ _jsx("td", {
class: cls,
"data-col": i,
"data-attach": true,
children: `$\{$.dataTb.attachVal(r[${col}], ${opt})}`
}));
} else if (h.link || link?.includes(i)) {
if (!h.link) h.link = '';
R.push(/*#__PURE__*/ _jsx("td", {
class: cls,
"data-link": h.link,
"data-col": i,
children: /*#__PURE__*/ _jsx("a", {
children: `$\{$.dataTb.formatVal(r[${col}], ${opt})}`
})
}));
} else {
R.push(/*#__PURE__*/ _jsx("td", {
class: cls,
"data-col": i,
children: `$\{$.dataTb.formatVal(r[${col}], ${opt})}`
}));
}
}
}
return R;
}
/**
* 按数据生成tr col 控制列宽
* 支持hide参数
* @param {*} head 表头数据
* @returns
*/ col(head) {
const R = [];
const cfg = {
..._cfg,
...head[0] || {}
};
const { hide, fontSize, padding, icon } = cfg;
let col = -1 // 隐藏字段需跳过
;
for(let i = cfg.checkbox ? 0 : 1, len = head.length; i < len; i++){
col++ // 数据索引从 0 开始
;
// 跳过隐藏列,隐藏列不显示
while(hide?.includes(col))col++;
const d = head[i];
/** @type {string[]} */ const style = [];
let { name, type, width, minWidth, maxWidth, sort } = d;
const add = sort ? icon + padding : padding;
// width 与 minWidth 二选一
if (width) width = width * fontSize + add;
else if (minWidth) minWidth = minWidth * fontSize + add;
else if (!width) {
// checkbox
if (i === 0) width = 40;
else width = fontSize * name.length + add;
}
if (maxWidth) maxWidth = maxWidth * fontSize + add;
if (width) style.push(`width: ${width}px`);
else if (minWidth) style.push(`min-width: ${minWidth}px`);
if (maxWidth) style.push(`max-width: ${maxWidth}px`);
R.push(/*#__PURE__*/ _jsx("col", {
style: style.join(';')
}));
}
return R;
}
/**
* 按数据生成tfoot的汇总行
* 支持hide参数
* @param {*[]} [r] - 汇总数组,接口返回传入,不传则前端计算
* @returns
*/ setSum(r) {
const _ = this;
try {
const { tb, cfg, head, data } = _;
if (!cfg.sum) return;
if (!head) {
console.log('param is null.');
return;
}
const count = data.length;
const cls = 'numeric-cell';
// 汇总计算
if (!r?.length) r = _.getSum(data);
const rs = [];
if (cfg.checkbox || _.group) rs.push(/*#__PURE__*/ _jsx("td", {
colspan: "2",
class: "label-cell",
children: `合计:${count}条`
}));
else rs.push(/*#__PURE__*/ _jsx("td", {
class: "label-cell",
children: `合计:${count}条`
}));
for(let i = 2, len = head.length; i < len; i++){
const h = head[i];
const { type, sum, idx } = h;
if (sum) {
if (sum === 'value') rs.push(/*#__PURE__*/ _jsx("td", {}));
else if (sum === true || sum === 'avg') rs.push(/*#__PURE__*/ _jsx("td", {
class: cls,
children: r[idx]
}));
else if (sum.includes('${count}')) {
const val = sum.replace('${count}', count);
rs.push(/*#__PURE__*/ _jsx("td", {
class: "label-cell",
children: val
}));
} else if (sum === 'count') rs.push(/*#__PURE__*/ _jsx("td", {
class: cls,
children: count
}));
else if (typeof sum === 'string') rs.push(/*#__PURE__*/ _jsx("td", {
class: "label-cell",
children: sum
}));
} else rs.push(/*#__PURE__*/ _jsx("td", {}));
}
const foot = tb.find('tfoot');
if (rs.length) foot.html(/*#__PURE__*/ _jsx("tr", {
children: rs
}));
else foot.empty();
} catch (e) {}
}
/**
* 对带sum属性的字段汇总或平均
* 空值跳过,不参与汇总或平均
* @param {*[]} data - 二维数组
*/ getSum(data) {
const _ = this;
if (!data?.length) return;
const R = Array(data[0].length).fill(0);
const cnt = Array(data[0].length).fill(0);
try {
const { head } = _;
// 遍历数据行,汇总数据
for (const d of data){
for (const h of head){
try {
const { idx } = h // 表头对应的数据列
;
// 空字符不参与统计
if (idx >= 0 && (h.sum === true || h.sum === 'avg') && d[idx] !== '' && d[idx] !== null && d[idx] !== 'null') {
cnt[idx]++;
R[idx] += Number(d[idx]);
} else if (idx >= 0 && h.sum === 'value' && !R[idx]) R[idx] = d[idx];
} catch (e) {
log.err(e, 'getSum:sum');
}
}
}
for (const h of head){
const { idx } = h;
if (idx >= 0 && h.sum === true) R[idx] = formatNum(R[idx]);
else if (idx >= 0 && h.sum === 'avg' && cnt[idx]) R[idx] = formatNum(R[idx] / cnt[idx]);
}
} catch (e) {}
return R;
}
/**
* 添加分组行,取 sum 数组值
* @param {*} r - 分组值
* @param {number} col - 分组列,对应 head 数组
* @param {string} no - 编号
* @param {*} opts - 选项,如 prop 属性
* @returns
*/ addGroup(r, col, no, opts) {
const _ = this;
try {
const { tb, cfg, head } = _;
const opt = {
prop: [],
...opts
};
const { level, prop } = opt;
if (!head[col]) return;
const { name } = head[col];
const { name: value, sum: d, count } = r;
const cls = 'numeric-cell';
const { checkbox: ck } = cfg;
const rs = [];
// 展开、折叠图标
// <i class="icon f7icon text-[16] font-[600] transition-transform duration-300">chevron_down</i>
if (level === 1) rs.push(/*#__PURE__*/ _jsx("td", {
class: "group-icon",
"data-group": value,
children: /*#__PURE__*/ _jsx("a", {
class: "text-blue-400",
children: /*#__PURE__*/ _jsx("i", {
class: "icon f7icon text-[16] font-[600]",
children: "chevron_down"
})
})
}));
else if (level === 2) rs.push(/*#__PURE__*/ _jsx("td", {
class: "group-icon",
"data-group2": value,
children: /*#__PURE__*/ _jsx("a", {
class: "text-blue-400 transition-transform duration-300",
children: /*#__PURE__*/ _jsx("i", {
class: "icon wiaicon text-[16] font-[300]",
style: "font-size:16px",
children: ""
})
})
}));
// <i class="icon f7icon text-[16] font-[600] transition-transform duration-300">chevron_down</i>
// {`${no}、${value.replace(/\d+-/, '')}:${count}条`}
// {`${no}、${value.replace(/\d+-/, '')}:${count}条`}
// 分组名称
// const cls = 'label-cell'
// const col = head.length - 1
// if (cfg.checkbox) {
// rs.push(
// <td colspan={cfg.groupCol} class="label-cell">
// <label class="checkbox">
// <input type="checkbox" data-group={`${value}`} />
// <i class="icon-checkbox" />
// </label>
// {`合计:${count}条`}
// </td>
// )
// } else {
// rs.push(
// <td colspan={cfg.groupCol} class="label-cell">
// {`合计:${count}条`}
// </td>
// )
// }
// const start = (cfg.checkbox ? 1 : 2) + cfg.groupCol
const start = 1 // + (cfg.groupCol ?? 0)
;
for(let i = start, len = head.length; i < len; i++){
const h = head[i];
const { type, sum, idx } = h;
// 分组列
if (i === col) rs.push(/*#__PURE__*/ _jsx("td", {
class: "label-cell group-cell",
children: `${value.replace(/\d+-/, '')}(${count})`
}));
else if (sum) {
if (sum === true || sum === 'avg') rs.push(/*#__PURE__*/ _jsx("td", {
class: cls,
children: d[idx]
}));
else if (sum.includes('${count}')) {
const val = sum.replace('${count}', count);
rs.push(/*#__PURE__*/ _jsx("td", {
class: "label-cell",
children: val
}));
} else if (sum === 'count') rs.push(/*#__PURE__*/ _jsx("td", {
class: cls,
children: count
}));
else if (sum === 'value') rs.push(/*#__PURE__*/ _jsx("td", {
class: "label-cell",
children: d[idx]
}));
else if (typeof sum === 'string') rs.push(/*#__PURE__*/ _jsx("td", {
class: "label-cell",
children: sum
}));
} else rs.push(/*#__PURE__*/ _jsx("td", {}));
}
const p = $(/*#__PURE__*/ _jsx("tr", {
name: `${name}-data`,
class: "data-table-group",
children: rs
}));
// 设置属性
if (prop?.length) {
for (const v of prop){
const ps = v.split('=');
if (ps.length > 1) p.attr(ps[0], ps[1]);
}
}
const tp = tb.find('[name$=-tp]');
p.insertBefore(tp);
} catch (e) {}
}
/**
* 分组显示
* @param {*[]} [data] - 数据
* @param {number[]} [cols] - 分组,表头列
* @param {number[]} [sort] - 排序,表头列
*/ setGroup(data, [c1, c2, c3] = [], sort = null) {
const _ = this;
if (!data?.length && !_.data?.length) return;
try {
const { tb, head, cfg, opt } = _;
if (!sort) sort = cfg.sort;
// view.clearView.bind(tb)() // 清除view
_.clear() // 清除view 和数据
;
// 浅拷贝数组数据(子数组与原数组一致),用于排序、分页,不改变原数据
if (data?.length) {
_.group = [
c1,
c2,
c3
] // 点击表头排序需要
;
_.data = [
...data
];
if (cfg.checkbox === 'index') // index 需对数组添加index属性,替代 idx
_.data.forEach((v, x)=>v.index = x);
}
// 增加一列,用于折叠图标
if (!cfg.checkbox) {
let el = tb.find('th.group-icon');
if (!el.dom) {
el = tb.findNode('th');
el.before(/*#__PURE__*/ _jsx("th", {
class: "group-icon"
}));
el = tb.findNode('colgroup');
el.dom.insertAdjacentHTML('afterbegin', '<col style="width: 40px">');
el = tb.findNode(`tr[name=${opt.name}-tp]`);
el.dom.insertAdjacentHTML('afterbegin', '<td class="intend-cell"/>');
}
}
// tb.clearView()
const rs1 = _.groupByCol(_.data, [
c1,
c2,
c3
], sort);
// 唯一id,避免重复添加
let { id: idx } = cfg;
idx = Array.isArray(idx) && idx?.length ? idx[0] : undefined;
if (rs1) {
let no1 = 0;
for (const r1 of rs1){
// 一级分组
if (!r1.data[0].name) {
no1++;
// 分组汇总行
_.addGroup(r1, c1, `${no1}`, {
level: 1,
prop: [
`data-group=${r1.name}`
]
});
// 数据行
_.tb.addView(r1.data, {
idx,
prop: [
`group=${r1.name}`
]
});
// _.addView(r1.data, {idx, prop: [`group=${r1.name}`]})
} else {
// 二级分组
no1++;
_.addGroup(r1, c1, `${no1}`, {
level: 1,
prop: [
`data-group=${r1.name}`
]
});
const rs2 = r1.data;
// 二级分组编号
let no2 = 0;
for (const r2 of rs2){
if (!r2.data[0].name) {
no2++;
_.addGroup(r2, c2, `${no1}.${no2}`, {
level: 2,
prop: [
`group=${r1.name}`,
`data-group2=${r2.name}`
]
});
_.tb.addView(r2.data, {
idx,
prop: [
`group=${r1.name}`,
`group2=${r2.name}`
]
});
// _.addView(r2.data, {prop: [`group=${r1.name}`, `group2=${r2.name}`]})
}
}
}
}
}
if (cfg.sum) _.setSum();
if (_.foldLevel) _.fold(_.foldLevel);
} catch (e) {
log.err(e, 'setGroup');
}
}
/**
* 折叠 三种状态:0: 不折的 1:折叠所有 2:展开一级折叠二级
* @param {number} [level] - 默认1,折叠一级及其下分组,相当于所有 0 不折叠
*/ fold(level = 1) {
const _ = this;
const { tb } = _;
try {
_.foldLevel = level;
// 0 不折叠
if (level === 0) _.open(0);
else if (level === 1) {
// 折叠所有
let es = tb.find('[group]');
es.hide();
es = tb.find('.group-icon').get();
for (const n of es){
const $n = $(n);
$n.data('iconTag', 1);
const icon = $n.find('i.f7icon');
const icon2 = $n.find('i.wiaicon');
if (icon?.dom) icon.dom.textContent = 'chevron_right' // .removeClass('rotate-90').addClass('rotate-0')
;
icon2?.addClass('rot-270');
}
} else if (level === 2) {
_.open(1) // 展开一级,折叠二级
;
let es = tb.find('[group2]');
es.hide();
es = tb.find('.group-icon').get();
for (const n of es){
const $n = $(n);
$n.data('iconTag', 1);
const icon2 = $n.find('i.wiaicon');
icon2?.addClass('rot-270');
}
}
} catch (e) {
log.err(e, 'fold');
}
}
/**
* 展开分组,被fold调用
* @param {number} level - 展开级别 0 所有
*/ open(level) {
const _ = this;
const { tb } = _;
try {
if (level === 0) {
let es = tb.find('[group]');
es.show();
es = tb.find('.group-icon').get();
for (const n of es){
const $n = $(n);
$n.data('iconTag', 0);
const icon = $n.find('i.f7icon');
const icon2 = $n.find('i.wiaicon');
if (icon?.dom) icon.dom.textContent = 'chevron_down' // .removeClass('rotate-90').addClass('rotate-0')
;
icon2?.removeClass('rot-270');
}
} else if (level === 1) {
let es = tb.find('[group]:not([group2])');
es.show();
es = tb.find('.group-icon').get();
for (const n of es){
const $n = $(n);
$n.data('iconTag', 0);
const icon = $n.find('i.f7icon');
if (icon?.dom) icon.dom.textContent = 'chevron_down' // .removeClass('rotate-90').addClass('rotate-0')
;
}
}
} catch (e) {
log.err(e, 'open');
}
}
/**
* 清除view 和数据
*/ clear() {
const _ = this;
try {
const { tb, opt } = _;
const { name } = opt;
// _.data = [] // 不清除数据
const body = tb.find('tbody');
const tp = body.find(`tr[name=${name}-tp]`);
body.html('');
body.append(tp);
const foot = tb.find('tfoot');
foot.html('');
_.clearSel();
} catch (e) {
log.err(e, 'clear');
}
}
/**
* 对二维数组进行三级分组
* @param {*[]} data - 数据
* @param {number[]} [cols] - 分组,表头列
* @param {number[]} [sort] - 排序,表头列
* @returns {*[]}
分组后的对象数组,结构为:
* [{
* name: "一级分组名",
* count: 一级分组总行数,
* data: [{
* name: "二级分组名",
* count: 二级分组行数,
* data: [{
* name: "三级分组名",
* count: 三级分组行数,
* data: [...] // 三级分组的数据
* }]
* }]
* }] */ groupByCol(data, [c1, c2, c3] = [], sort = null) {
let R;
const _ = this;
try {
const { head, cfg } = _;
// 表列对应的数据列
let id1 = 0;
let id2 = 0;
let id3 = 0;
if (c1) id1 = head[c1].idx;
if (c2) id2 = head[c2].idx;
if (c3) id3 = head[c3].idx;
const r1 = data.reduce((acc, r)=>{
const v = `${id1}-${r[id1]}` // 分组列名称,加列序号,避免重复
;
const gp = acc[v] // 分组
;
if (gp) {
gp.data.push(r);
gp.count++;
} else {
acc[v] = {
name: v,
val: r[id1],
id: id1,
data: [
r
],
count: 1
};
}
return acc;
}, {});
const rs1 = [];
// 对象转换为数组,方便排序
for (const k of Object.keys(r1)){
const r = r1[k];
if (r.data?.length) {
if (cfg.sum) {
r.sum = _.getSum(r.data) // 汇总计算
;
r.sum[id1] = r.val // 分组列写入sum 用于排序
;
// 排序映射隐藏列
if (head[c1].sort?.length) {
const j = head[c1].sort[0];
if (cfg.hide.includes(j)) r.sum[j] = r.data[0][j];
}
}
if (!id2 && sort?.length) _.sort(r.data, sort);
}
rs1.push(r);
}
// 二级分组
if (id2) {
// 2. 二级分组(遍历一级分组,对每个一级分组的 data 进行二级分组)
for (const r1 of rs1){
const r2 = r1.data.reduce((acc, r)=>{
const v = `${id2}-${r[id2]}` // 分组列名称
;
const gp = acc[v];
if (gp) {
gp.data.push(r);
gp.count++;
} else {
acc[v] = {
name: v,
id: id2,
val: r[id2],
data: [
r
],
count: 1
};
}
return acc;
}, {});
// 汇总计算
const rs2 = [];
for (const k of Object.keys(r2)){
const r = r2[k];
if (r.data?.length) {
if (cfg.sum) {
r.sum = _.getSum(r.data);
r.sum[id2] = r.val // 用于排序
;
if (head[c2].sort?.length) {
// 排序映射隐藏列
const j = head[c2].sort[0];
if (cfg.hide.includes(j)) r.sum[j] = r.data[0][j];
}
}
if (!id3 && sort?.length) _.sort(r.data, sort);
} else {
log({
r
}, 'groupByCol data null');
}
rs2.push(r);
}
// 3. 三级分组(遍历二级分组,对每个二级分组的 data 进行三级分组)
if (id3) {
for (const r2 of rs2){
const r3 = r2.data.reduce((acc, r)=>{
const v = `${id3}-${r[id3]}` // 分组列名称
;
const gp = acc[v];
if (gp) {
gp.data.push(r);
gp.count++;
} else {
acc[v] = {
name: v,
id: id3,
data: [
r
],
count: 1
};
}
return acc;
}, {});
// 汇总计算
const rs3 = [];
for (const k of Object.keys(r3)){
const r = r3[k];
if (cfg.sum) r.sum = _.getSum(r.data);
rs3.push(r);
}
// 替换二级分组的 data 为三级分组
r2.data = rs3;
}
}
// 对分组进行排序
if (sort?.length) sortSum(rs2, sort, head);
// 替换一级分组的 data 为二级分组
r1.data = rs2;
}
}
// 对分组进行排序
if (sort?.length) sortSum(rs1, sort, head);
R = rs1;
// 对每个分组的数据进行排序
// if (sortData) {
// rs.forEach(group => {
// group.data.sort((a, b) => (a[0] > b[0] ? 1 : -1)) // 按第一列排序
// })
// }
} catch (e) {
log.err(e, 'groupByCol');
}
return R;
}
/**
* 生成table表,包括 thead、tbody、分页
* @returns
*/ render() {
const _ = this;
const { opt } = _;
try {
const { head, el } = _;
if (!head) {
console.log('param is null.');
return;
}
const cfg = {
..._cfg,
...head[0] || {}
};
// checkbox
let { checkbox: ck, layout, sum, fix } = cfg;
// 固定表格,上下滚动
if (fix.includes('table')) el.append(/*#__PURE__*/ _jsx("div", {
class: "data-table-content overflow-auto"
}));
else el.append(/*#__PURE__*/ _jsx("div", {
class: "data-table-content"
}));
let ckv = '';
// checkbox
if (ck) {
if (Array.isArray(ck) && ck.length) {
ck = ck[0];
ckv = `$\{r[${ck}]}`;
} else if (ck === 'index') ckv = '${r.index}';
}
const { name } = opt;
const clas = [
'fix-h',
'fix-b'
] // 固定表头 表尾
;
if (fix.includes('right1')) clas.push('fix-r1');
if (fix.includes('right2')) clas.push('fix-r2');
if (fix.includes('left1')) clas.push('fix-l1');
if (fix.includes('left2')) clas.push('fix-l2');
if (fix.includes('left3')) clas.push('fix-l3');
if (fix.includes('left4')) clas.push('fix-l4');
if (fix.includes('left5')) clas.push('fix-l5');
const style = [
`table-layout: ${layout}`
];
const tb = $(/*#__PURE__*/ _jsx("table", {
name: name,
class: clas.join(' '),
style: style.join(';')
}));
// 保存tb
_.tb = tb;
// 加入到容器
const tbWrap = el.findNode('.data-table-content');
tbWrap.append(tb);
// 列宽
if (layout === 'fixed') tb.append(/*#__PURE__*/ _jsx("colgroup", {
children: _.col(head)
}));
// <table name="tbLoan">
// jsx 通过函数调用,实现html生成。
let v = this.th(head);
// 加入到表格
tb.append(v);
// 表主体
v = /*#__PURE__*/ _jsx("tbody", {
name: "tbBody",
children: /*#__PURE__*/ _jsxs("tr", {
name: `${name}-tp`,
style: "display: none",
children: [
ck && /*#__PURE__*/ _jsx("td", {
class: "checkbox-cell",
children: /*#__PURE__*/ _jsxs("label", {
class: "checkbox",
children: [
/*#__PURE__*/ _jsx("input", {
type: "checkbox",
"data-val": ckv
}),
/*#__PURE__*/ _jsx("i", {
class: "icon-checkbox"
})
]
})
}),
_.td(head)
]
})
});
// 加入到表格
tb.append(v);
if (sum) {
v = /*#__PURE__*/ _jsx("tfoot", {
name: "tbFoot"
});
tb.append(v);
}
if (cfg.page && !fix.includes('table')) {
v = /*#__PURE__*/ _jsx("div", {
class: "data-table-footer",
children: /*#__PURE__*/ _jsx("div", {
class: "dataTables_paginate paging_simple_numbers"
})
});
// 加入到容器,而非表格
tbWrap.after(v);
}
_.header = el.findNode('.data-table-header');
_.$headerSel = el.findNode('.data-table-header-selected');
// F7表格生成
_._tb = new Table(_.page, {
el,
name
});
// 绑定事件,如点击head排序
_.bind(fix?.length);
// 数据显示
if (_.data?.length) _.setView();
} catch (ex) {
console.log('render', {
ex: ex.message
});
}
}
/**
* checkbox change事件
* 注意,this为类实例,侦听对象作为参数sender传递,因为触发函数是bind后的函数!
* 向外触发 check事件,参数为 sel 数组,方便侦听者处理跨页行选择,如统计等
* @param {*} ev 事件
* @param {*} sender 事件侦听对象
* @returns
*/ oncheck(ev, sender) {
// const n = $(ev.target);
if (!sender) return;
const n = $(sender);
const m = this;
const { el } = this;
// 排除非数据
// if (n.upper(`tr[name="${self.opt.name}-data"]`).length) {
const val = n.data('val');
if (val != null) {
if (n.dom.checked) m._sel.add(val);
else m._sel.delete(val);
// console.log('oncheck', {sel: self._sel});
m.emit('local::check', m._sel);
}
// }
}
/**
* 绑定表格事件
* @param {boolean} fix - 固定表、列
*/ bind(fix) {
const _ = this;
try {
const { el, head, cfg } = _;
let { id: idx } = cfg;
idx = Array.isArray(idx) && idx?.length ? idx[0] : undefined;
// 字段 定义的 link,带值的,自动跳转
el.findNode('tbody').click('td[data-link]', (ev, sender)=>{
const n = $(sender);
if (n.length) {
const link = n.data('link');
const col = n.data('col');
const val = n.findNode('a').html().trim();
if (link) $.go(link);
else _.emit('local::link', {
col,
val
});
}
});
el.findNode('tbody').click('td[data-attach]', async (ev)=>{
const att = $(ev).upper('.attach-item');
const attWrap = $(ev).upper('.attach-wrap');
let list = attWrap.data('list');
const value = list;
let i = att.data('idx');
let v;
v = value.find((v)=>v._idx === i);
const { type, ext } = v || {};
let { url } = v || {};
if (type === 'img' || type === 'video') {
if (!g.lightbox) {
const m = await import('https://cos.wia.pub/wiajs/glightbox.mjs');
g.lightbox = m.default;
setTimeout(()=>_.showImg(value, i), 1000);
} else _.showImg(value, i);
} else if (url) {
if ([
'.doc',
'.docx',
'.docm',
'.xls',
'.xlsm',
'.xlsb',
'.xlsx',
'.pptx',
'.ppt'
].includes(`.${ext}`)) url = `https://view.officeapps.live.com/op/view.aspx?src=${url}&wdOrigin=BROWSELINK`;
window.open(url, '_blank');
}
});
// 头部指定的 link 字段,包含在上面link中,link值为空
// el.findNode('tbody').click('td[data-col]', (ev, sender) => {
// const n = $(sender)
// if (n.length) {
// const col = n.data('col')
// const val = n.findNode('a').html().trim()
// if (col && val) _.emit('local::link', {col, val})
// }
// })
// checkbox 变化
el.findNode('tbody').on('change', '.checkbox-cell input[type="checkbox"]', this.checkEvent);
// 选择行,按跨页选择重新处理表头选择区样式
this._tb.on('select', (rs)=>{
// 选中行
const len = _._sel.size;
// 改变表头操作面板
if (_.header.length > 0 && _.$headerSel.length > 0) {
if (len && !el.hasClass('data-table-has-checked')) el.addClass('data-table-has-checked');
else if (!len && el.hasClass('data-table-has-checked')) el.removeClass('data-table-has-checked');
// 选中数量,跨行选择数量与当前也选择数量不一致
_.$headerSel.find('.data-table-selected-count').text(len);
}
this.emit('local::select', rs);
});
// 表格排序
_._tb.on('sort', (cell, desc)=>{
if (cell.length > 0) {
const i = head.findIndex((v)=>v.name === cell.html());
const c = head[i];
if (c) {
if (_.group) {
const sort = desc ? [
i
] : [
-i
];
_.setGroup(null, _.group, sort);
} else {
const sort = desc ? [
i
] : [
-i
];
_.setView(null, _.viewOpts, sort);
// _.clearSel()
// _.sort(_.data, c.idx, c.type, desc)
// if (_.pageBar()) _.paging(1)
// else _.tb.setView(_.data, {idx})
}
}
}
});
// 分页 pagination
el.class('dataTables_paginate').click((ev)=>{
const lk = $(ev.target).upper('.page-link');
const prow = head[0].page;
const plink = head[0].pageLink;
if (lk.length > 0 && prow > 0) {
let i = lk.data('page');
if (Number.isInteger(i)) this.paging(i);
else if (i.startsWith('>')) {
i = Number.parseInt(i.substr(1), 10);
this.pageBar(i + 1);
this.paging(i + 1);
} else if (i.startsWith('<')) {
i = Number.parseInt(i.substr(1), 10);
this.pageBar(i - plink);
this.paging(i - plink);
}
}
});
if (fix) {
_.bindFix();
// setTimeout(() => _.bindFix(), 1000)
}
el.click((ev)=>{
// 分组 展开、折叠
const td = $(ev).upper('td.group-icon');
if (td.dom) {
let group = td.data('group');
if (group) {
const es = el.find(`[group="${group}"]`);
const icon = td.find('i.f7icon');
if (td.data('iconTag') === 1) {
td.data('iconTag', 0);
// el.removeClass('rotate-90').addClass('rotate-0')
if (icon.dom) icon.dom.textContent = 'chevron_down' // .removeClass('rotate-90').addClass('rotate-0')
;
$.nextTick(()=>es.show());
} else {
td.data('iconTag', 1);
// el.removeClass('rotate-0').addClass('rotate-45')
if (icon.dom) icon.dom.textContent = 'chevron_right' // .removeClass('rotate-90').addClass('rotate-0')
;
$.nextTick(()=>es.hide());
}
}
const group2 = td.data('group2');
if (group2) {
group = td.upper('tr').attr('group');
const es = el.find(`[group="${group}"][group2="${group2}"]`);
const icon = td.find('i.wiaicon');
if (td.data('iconTag') === 1) {
td.data('iconTag', 0);
if (icon) icon.removeClass('rot-270');
es.show();
} else {
td.data('iconTag', 1);
if (icon) icon.addClass('rot-270');
es.hide();
}
}
}
});
} catch (e) {
log.err(e, 'bind');
}
}
/**
* 使用 lightbox 图片浏览
* @param {*[]} data - 附件数据
* @param {number} idx
*/ async showImg(data, idx) {
if (g.lightbox) {
// window.dispatchEvent(new CustomEvent('animeReady'))
const lbox = g.lightbox({
selector: null
});
let id = 0;
let i = -1;
for (const v of data){
if (v.type === 'img' || v.type === 'video') {
i++;
if (v._idx === idx) id = i;
lbox.insertSlide({
href: v.url
});
}
}
// lbox.open()
lbox.openAt(id);
}
}
/**
* 固定表格动态设置表格高度
*/ bindFix() {
const _ = this;
try {
const { el } = _;
// 监听 DOM 变化
const observer = new MutationObserver(()=>_.resize());
observer.observe(el.dom, {
childList: true,
subtree: true
});
// 返回一个防抖函数,更新隐藏元素列表
const resizeHandler = debounce(()=>{
// 获取新值
const newWidth = window.innerWidth;
const newHeight = window.innerHeight;
// 计算变化值
const widthDiff = newWidth - _.lastW;
const heightDiff = newHeight - _.lastH;
// console.log(`窗口尺寸变化:\n宽度 ${_.lastW} → ${newWidth} (差值: ${widthDiff}px)\n高度 ${_.lastH} → ${newHeight} (差值: ${heightDiff}px)`)
// 更新旧值
_.lastW = newWidth;
_.lastH = newHeight;
_.resize(heightDiff);
}, 500);
// 监听窗口缩放
window.addEventListener('resize', resizeHandler()) // 500ms内仅触发一次
;
} catch (e) {
log.err(e, 'prebind');
}
}
unbind() {
this.el.off('change', '.checkbox-cell input[type="checkbox"]', this.checkEvent);
}
/**
* 判断是否有滚动条
* data-table-content overflow-auto
* @param {number} [ch] - change h
* @returns
*/ resize(ch) {
let R = 0;
const _ = this;
try {
const { view, el, tb, cfg } = _;
const { fix, height, width } = cfg;
const tbWrap = el.findNode('.data-table-co