UNPKG

@wiajs/ui

Version:

wia app ui packages

1,368 lines 74.3 kB
/** @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