UNPKG

@wiajs/ui

Version:

wia app ui packages

1,356 lines (1,353 loc) 122 kB
/** @jsxImportSource @wiajs/core */ /** * 在线编辑表格 */ import { jsx as _jsx, jsxs as _jsxs } from "@wiajs/core/jsx-runtime"; import { Event } from '@wiajs/core'; import { Page } from '@wiajs/core'; import DataTable from '../dataTable'; import { fillAttach, cancelDel, getDel } from './attach'; import * as tool from './tool'; import { log as Log } from '@wiajs/util'; const log = Log({ m: 'editTable' }) // 创建日志实例 ; /** * @typedef {import('jquery')} $ * @typedef {JQuery} Dom */ /** @typedef {object} Opts * @prop {Dom} tb - $table * @prop {string[]} [editTag] - 可编辑元素标签 * @prop {boolean} [edit] - 编辑模式 * @prop {boolean} [add] - 新增模式 * @prop {boolean} [kv] - key value * @prop {number} [col] - 最大列数 * @prop {number[]} [colWidth] - 列宽 * @prop {number} [viewid] - 数据卡id * @prop {*} [upload] - 上传接口 */ /** @typedef {object} Opt * @prop {Dom} tb - $table * @prop {string[]} editTag * @prop {boolean} edit * @prop {boolean} add * @prop {boolean} kv - key value * @prop {number} labelWidth - label 宽度 百分比 * @prop {number} col - 最大列数 * @prop {number[]} [colWidth] - 列宽 * @prop {number} [colRatio] - 列比 * @prop {number} [viewid] - 数据卡id * @prop {*} [upload] - 上传接口 * @prop {*} [prjid] - 项目id * @prop {*} [getSelAll] - 公司列表接口 * @prop {*} [saveEmbTb] - 保存表格接口 * */ /** @type {Opt} */ const def = { tb: null, editTag: [ 'input', 'textarea' ], edit: false, add: false, kv: false, labelWidth: 10, col: 8, // colWidth: [0.1, 0.15, 0.1, 0.15, 0.1, 0.15, 0.1, 0.15], colRatio: 1 }; /** * @enum {number} 数据类型-页面呈现方式 */ const DataType = { null: 0, text: 1, texts: 2, number: 3, date: 4, bool: 5, select: 6, radio: 7, checkbox: 8, chip: 9, button: 10, img: 11, file: 12, path: 13, url: 14, email: 15, tel: 16, password: 17, time: 18, datetime: 19, month: 20, week: 21, color: 22, attach: 23, table: 24, view: 25, page: 26, search: 27, html: 28, json: 29 }; /** * @enum {string} 数据类型-页面呈现方式 */ const DataTypes = { null: 'null', text: 'text', texts: 'texts', number: 'number', date: 'date', bool: 'bool', select: 'select', radio: 'radio', checkbox: 'checkbox', chip: 'chip', button: 'button', img: 'img', file: 'file', path: 'path', url: 'url', email: 'email', tel: 'tel', password: 'password', time: 'time', datetime: 'datetime', month: 'month', week: 'week', color: 'color', attach: 'attach', table: 'table', view: 'view', page: 'page', search: 'search', html: 'html', json: 'json' }; /** * EditTable */ let EditTable = class EditTable extends Event { /** * 构造函数 * @param {Page} page Page 实例 * @param {Opts} opts */ constructor(page, opts){ super(opts, [ page ]), /** @type {*} */ this.editTx = null // 当前被编辑的目标对象 , /** @type {boolean} */ this._keyDown = false // 记录键盘被按下的状态,当有键盘按下时其值为true , /** @type {*} */ this._selRow = null // 最近的选择行 , /** @type {*} */ this._editCell = null // 当前编辑对象 , /** @type {*} */ this.editRow = null // 最后编辑行 , /** @type {number} */ this.editCursorPos = 0 // 最后编辑光标位置 , /** @type {number} */ this.rowNum = 0 // 表行数 , /** @type {number} */ this.dataid = 0 // 当前数据索引,用于render row ; const _ = this; const opt = { ...def, ...opts }; _.opt = opt; _.tb = opt.tb; _.page = page; // 4 改为 8,兼容旧模式 if (!opt.colWidth) { opt.colRatio = 2; opt.col = opt.col * 2; if (opt.col === 8) opt.colWidth = [ 0.1, 0.15, 0.1, 0.15, 0.1, 0.15, 0.1, 0.15 ]; } _.init(); if (opt.edit) _.edit(); else _.view(); _.bind(); // const txs = $(tr).find('input.etCellView') // const spans = $(tr).find('span.etLabel') // spans.html('hello') // for (const tx of txs.get()) { // const $tx = $(tx) // $tx.click(ev => editSpec(tx)) // $tx.focus(ev => editSpec(tx)) // $tx.blur(ev => viewSpec(tx)) // $tx.upper('td').addClass('border-bot') // } } static hi(msg) { alert(msg); } /** * kv模式,构建空表头、表体 */ init() { const _ = this; const { opt, tb: tb1 } = _; const { colWidth, edit } = opt; try { // 列宽控制 const cg = tb1.find('colgroup'); if (!cg.dom && colWidth?.length) { tb1.prepend(/*#__PURE__*/ _jsx("colgroup", { children: colWidth.map((v)=>{ let width; if (v < 1) width = `${v * 100}%`; else width = `${v}px`; return /*#__PURE__*/ _jsx("col", { style: `width: ${width}` }); }) })); } // 构造空body let body = tb1.find('tbody'); if (!body.dom) { if (edit) tb1.append(/*#__PURE__*/ _jsx("tbody", { class: "etEdit" })); else tb1.append(/*#__PURE__*/ _jsx("tbody", { class: "etView" })); } body = tb1.find('tbody'); // 构造空表头 const th = tb1.find('thead'); if (!th.dom) { body.before(/*#__PURE__*/ _jsx("thead", { children: /*#__PURE__*/ _jsx("tr", { style: "display: none", class: "etRowOdd" }) })); } } catch (e) { log.err(e, 'init'); } } bind() { const _ = this; const { opt } = _; // 表格点击事件 // 编辑元素(input) 不能 focus,不能 onblur?原因:pointer-events: none _.tb.click(async (ev)=>{ if (!opt.edit) return; // 点击 input、select 则跳过 if ([ 'SELECT', 'INPUT' ].includes(ev.target.tagName)) return; const $ev = $(ev); const td = $ev.upper('td'); let span = $ev.upper('span'); span = td.find('span'); if (span.eq(0).css('display') === 'none') { // debugger return; } const idx = td?.data('idx') // 数据索引 ; const idv = td?.data('idv') // 数据中的value索引,多值数组模式下 ; const value = td?.attr('data-value') // 数据原值 data() 会自动转换 json 字符串 ; const r = _.data?.[idx]; if (r) { if (r.read) return; // 只读 // 方法2.2(更可靠) // document.body.setAttribute('tabindex', '-1') // document.body.focus() // document.body.removeAttribute('tabindex') let type = r.type ?? DataType.text // 默认单行字符串 ; // 多值 if (idv && idv >= 0 && Array.isArray(type) && type[idv]) { type = type[idv]; } const inputType = _.getInputType(type); if (inputType) { const span = td.find('span'); span.hide(); let tx = td.find('input'); if (!tx.dom) { tx = document.createElement('input'); tx.name = r.field; tx.type = inputType; td.append(tx); tx = $(tx); tx.addClass('dy-input'); tx.blur((ev)=>{ // _.viewCell() const val = tx.val(); span.eq(0).html(val); // 比较值是否被修改 if (`${val}` === `${value}`) { tx.hide(); span.show(); } }); } tx.val(span.eq(0).html()); tx.show(); tx.focus(); // 自动聚焦到输入框 // setTimeout(() => { // tx.focus() // }, 50) } else if (type === DataType.texts || type === DataTypes.texts) { const span = td.find('span'); if (!span.hasClass('edit')) { let tx = td.find('input'); if (!tx.dom) { tx = document.createElement('input'); tx.name = r.field; tx.value = span.html(); tx.hidden = true; td.append(tx); } span.dom.tabIndex = '-1'; // span 可编辑 // span.focus(ev => span.addClass('edit')) span.addClass('edit'); span.blur((ev)=>{ // _.viewCell() const val = span.html(); tx.value = val; if (`${val}` === `${value}`) { span.removeClass('edit') // span 可编辑 ; } }); span.focus(); // span.dom.addEventListener('focusout', ev => { // tx.value = span.html() // span.removeClass('edit') // span 可编辑 // }) } } else if (type === DataType.select || type === DataTypes.select) { const span = td.find('span'); span.hide(); const val = span.html(); let key; let sel = td.find('select'); if (!sel.dom) { sel = document.createElement('select'); sel.name = r.field; td.append(sel); sel = $(sel); sel.addClass('dy-select dy-select-primary'); sel.click((ev)=>ev.stopPropagation()) // 阻止事件冒泡 tb 无感知? ; // tx.addClass('dy-input') // tx.val(span.html()) // tx.change(ev => { sel.blur((ev)=>{ // _.viewCell() let val; const { option } = r; if (Array.isArray(option)) val = sel.val(); else if (typeof option === 'object') { key = sel.val(); val = option[key]; } span.html(val); if (`${val}` === `${value}`) { sel.hide(); span.show(); } }); sel.focus((ev)=>{ // 关联查询 _.fillOption(idx, td, sel); }); } // 选项 await _.fillOption(idx, td, sel); sel.show(); sel.focus(); setTimeout(()=>{ // 创建并触发鼠标事件来展开下拉框 const event = new MouseEvent('mousedown', { bubbles: true, cancelable: true, view: window }); sel.dom.dispatchEvent(event); }, 100); } else if ((type === DataType.search || type === DataTypes.search) && _.Autocomplete) { const span = td.find('span'); if (span.css('display') !== 'none') { span.hide(); let tx = td.find('.ac-input'); let dvAc = td.find('.autocomplete'); if (!tx.dom) { const { option, source, field } = r; let { placeholder } = r; placeholder = placeholder ?? '请输入'; td.append(/*#__PURE__*/ _jsx("div", { class: "autocomplete", children: /*#__PURE__*/ _jsx("div", { class: "ac-wrapper", children: /*#__PURE__*/ _jsx("input", { type: "text", name: field, class: "ac-input", placeholder: placeholder, autocomplete: "off" }) }) })); tx = td.find('.ac-input'); dvAc = td.find('.autocomplete'); // tx.addClass('dy-input') const ac = new _.Autocomplete(_.page, { el: dvAc, data: option, refEl: [ span.dom ], source }); tx.blur((ev)=>{ // 选择赋值在 blur 后 setTimeout(()=>{ const val = tx.val(); if (`${val}` === `${value}`) { dvAc.hide(); span.eq(0).html(val); span.show(); } }, 200); }); } // tx.val(span.eq(0).html()) dvAc.show(); const input = tx.dom; const { ac } = dvAc.dom; tx.focus() // 自动触发下拉 ; // 触发datalist下拉显示(需要特殊处理) // setTimeout(() => { // input.focus() // ac.showAllList() // }, 0) } } else if (type === DataType.bool || type === DataTypes.bool) { const span = td.find('span'); span.hide(); let val = span.html(); let key; let tx = td.find('select'); if (!tx.dom) { tx = document.createElement('select'); tx.name = r.field; td.append(tx); tx = $(tx); tx.addClass('dy-select dy-select-primary'); const option = { true: '是', false: '否' }; // 添加选项 let htm = []; for (const k of Object.keys(option)){ const v = option[k]; if (v === val) { key = k; htm.push(/*#__PURE__*/ _jsx("option", { selected: true, value: k, children: v })); } else htm.push(/*#__PURE__*/ _jsx("option", { value: k, children: v })); } tx.html(htm.join('')); if (key) tx.val(key); else tx.val(val); tx.click((ev)=>ev.stopPropagation()) // 阻止事件冒泡 ; // tx.addClass('dy-input') // tx.val(span.html()) tx.blur((ev)=>{ // _.viewCell() key = tx.val(); val = option[key]; span.html(val); if (`${val}` === `${value}`) { tx.hide(); span.show(); } }); } tx.show(); tx.focus(); setTimeout(()=>{ // 创建并触发鼠标事件来展开下拉框 // const event = new MouseEvent('mousedown') const event = new MouseEvent('mousedown', { bubbles: true, cancelable: true, view: window }); tx.dom.dispatchEvent(event); }, 100); // tx.click() } else if (type === DataType.url || type === DataTypes.url) { const span = td.find('span'); span.hide(); let tx = td.find('input'); if (!tx.dom) { tx = document.createElement('input'); tx.name = r.field; tx.type = 'url'; td.append(tx); tx = $(tx); tx.addClass('dy-input'); tx.blur((ev)=>{ // _.viewCell() const val = tx.val(); span.eq(0).find('a').attr('href', val); // 比较值是否被修改 if (`${val}` === `${value}`) { tx.hide(); span.show(); } }); } // urlChange tx.val(span.eq(0).find('a').attr('href')); tx.show(); tx.focus(); } } // for (const tag of _.opt.editTag) { // const tx = $ev.upper(tag) // if (tx.dom) { // _.editCell(tx.dom) // break // } // } }); } unbind() { const _ = this; } /** * 填充select option * @param {number} idx * @param {Dom} td * @param {Dom} sel * @returns */ async fillOption(idx, td, sel) { const _ = this; try { const { data } = _; const r = data[idx]; const { source } = r; let { name } = source?.param || {}; let { option } = r; // 引用字段 let refField; if (name?.includes('${')) { const lastRefField = td.data('lastRefField') // 保存关联 ; const match = name.match(/\$\{([^}]+)\}/); const ref = match?.[1]; const i = _.getDataIdx({ field: ref }); if (i) { // 关联节点 const n = _.tb.findNode(`[data-idx="${i}"]`); const v = n.findNode('span').html(); if (v && v !== lastRefField) { refField = v; td.data('lastRefField', v) // 保存关联 ; // 替换 'city:${province}' name = name.replace(`\${${ref}}`, v); } } } // 关联字段变化或无选项,动态获取 if (refField || source && !option) { sel.html(''); // 数据字典 // 默认 name = field if (!name) name = r.field; // source.param.name = name option = await getOption(source, name); log({ option }, 'getOption'); } if (option) { r.option = option; let key; const span = td.find('span'); const val = span.html(); // 添加选项 let htm = []; if (Array.isArray(option)) htm = option.map((v)=>{ let rt; if (v === val) rt = /*#__PURE__*/ _jsx("option", { selected: true, value: v, children: v }); else rt = /*#__PURE__*/ _jsx("option", { value: v, children: v }); return rt; }); else if (typeof option === 'object') { if (!val) { htm.push(/*#__PURE__*/ _jsx("option", { selected: true, value: "", children: "请选择" })); } for (const k of Object.keys(option)){ const v = option[k]; if (v === val) { key = k; htm.push(/*#__PURE__*/ _jsx("option", { selected: true, value: k, children: v })); } else htm.push(/*#__PURE__*/ _jsx("option", { value: k, children: v })); } } sel.html(htm.join('')); if (key) sel.val(key); else sel.val(val); } } catch (e) { log.err(e, 'fillOption'); } } /** * 获得数据索引 * @param {*} opts * @returns {number} */ getDataIdx(opts) { let R; const _ = this; try { const { field } = opts; if (field) { const idx = _.data.findIndex((v)=>v.field === field); if (idx >= 0) R = idx; } } catch (e) { log.err(e, 'getData'); } return R; } /** * 加载插件 * @param {*} cls * @param {*} [opts] */ use(cls, opts) { const _ = this; try { const { opt } = _; _[cls.name] = cls; if (cls.name === 'Uploader' && opts?.upload) opt.upload = opts.upload; else if (cls.name === 'Tabulate' && (opts?.getSelAll || opts?.saveEmbTb)) { opt.getSelAll = opts.getSelAll; opt.saveEmbTb = opts.saveEmbTb; opt.prjid = opts.prjid; opt.upload = opts.upload; } } catch (e) { log.err(e, 'use'); } } /** * input type * @param {DataType|DataTypes} type */ getInputType(type) { let R; switch(type){ case DataTypes.text: case DataType.text: R = 'text'; break; case DataTypes.number: case DataType.number: R = 'text'; break; case DataTypes.date: case DataType.date: R = 'date'; break; // urlChange // case DataTypes.url: // case DataType.url: // R = 'url' // break case DataTypes.email: case DataType.email: R = 'email'; break; case DataTypes.tel: case DataType.tel: R = 'tel'; break; case DataTypes.password: case DataType.password: R = 'password'; break; case DataTypes.time: case DataType.time: R = 'time'; break; case DataTypes.datetime: case DataType.datetime: R = 'datetime-local'; break; case DataTypes.month: case DataType.month: R = 'month'; break; case DataTypes.week: case DataType.week: R = 'week'; break; case DataTypes.color: case DataType.color: R = 'color'; break; } return R; } /* // 异步存储 set(key, data, expires) { _storage.save({ key: key, // Note: Do not use underscore("_") in key! data: data, expires: expires }); } // Promise同步方法 getStore(key) { // load _storage.load({ key: key, autoSync: true, syncInBackground: true }).then(data => { return data; }).catch(err => { console.warn(err); return null; }) } // async 的写法 async get(key) { try { let data = await storage.load({ key: key }); return data; } catch (err) { console.warn(err); return null; } } */ // Chrome、Safari 阻止浏览器的默认事件,实现 全选 mouseupCell(evt) { const ev = evt || window.event; ev.preventDefault(); } addHandler() { const data = this.tabulate.getData(); this.tabulate.addRow(); } saveTable() { this.tabulate.saveTable(); } /** * 初始化表格编辑器 */ async editModeTable() { const _ = this; const { opt } = _; //! 应该根据field 创建,支持多个内嵌表格编辑 //! 需判断是否已创建,避免重复创建 //! 应该根据字段类型(内嵌表)创建,而不是在编辑模式没有内嵌表也创建 if (_.hasTable && !_.tabulate && _.Tabulate) { _.tabulate = new _.Tabulate({ containerName: 't-table', addButtonName: 'add-button', targetBox: _.tb.tag('tbody')[0].querySelectorAll('.data-table'), baseTableInfo: _.baseTableInfo, getSelAll: opt.getSelAll, saveEmbTb: opt.saveEmbTb, viewid: opt.viewid, prjid: opt.prjid, upload: opt.upload }); } else { const tTableDivs = new Set(); const dataTables = _.tb.tag('tbody')[0].querySelectorAll('.data-table'); const addButton = document.querySelector(`[name="add-button"]`); dataTables.forEach((table)=>{ table.style.display = 'none'; // 获取当前 .data-table 的父节点(兄弟元素的共同容器) const parent = table.parentNode; // 在父节点中查找 name="t-table" 的 div const tTableDiv = parent.querySelector('div[name="t-table"]'); // 找到后添加到集合(去重,避免重复元素) if (tTableDiv) { tTableDivs.add(tTableDiv); } else { _.tabulate.destroyTabulateInstance(_.tabulate); _.tabulate = new _.Tabulate({ containerName: 't-table', addButtonName: 'add-button', targetBox: _.tb.tag('tbody')[0].querySelectorAll('.data-table'), baseTableInfo: _.baseTableInfo, getSelAll: opt.getSelAll, saveEmbTb: opt.saveEmbTb, viewid: opt.viewid, prjid: opt.prjid, upload: opt.upload }); } }); addButton.style.display = 'block'; // 将所有找到的 t-table 元素 display 设为 block tTableDivs.forEach((div)=>{ div.style.display = 'block'; }); } } /** * 编辑模式 */ edit() { const _ = this; _.opt.edit = true; _.tb.tag('tbody').addClass('etEdit').removeClass('etView'); _.editModeTable(); _.tb.find('._choose').show(); _.bind(); } /** * 浏览模式,禁止编辑 * 数据未保存到data,可取消 */ view() { const _ = this; try { _.opt.edit = false; _.tb.tag('tbody').addClass('etView').removeClass('etEdit'); _.unbind(); _.tabulate?.togglePreview(); _.tb.find('._choose').hide(); if (_.data) { const tds = _.tb.find('td[data-idx]'); for (const td of tds.get()){ try { const $td = $(td); const idx = $td.data('idx') // 数据索引 ; const d = _.data[idx]; const { type, option } = d || {}; if ((type === DataType.search || type === DataTypes.search) && _.Autocomplete) { const dvAc = $td.find('.autocomplete'); dvAc?.hide(); const span = $td.find('span'); span.show(); } else if (type !== DataType.checkbox && type !== DataTypes.checkbox && type !== DataType.radio && type !== DataTypes.radio && type !== DataType.attach && type !== DataTypes.attach && type !== DataType.table && type !== DataTypes.table && type !== DataType.view && type !== DataTypes.view && type !== DataType.page && type !== DataTypes.page) { const span = $td.find('span'); if (span) { span.removeClass('edit'); span.show(); } let tx = $td.find('input'); if (!tx.dom) tx = $td.find('select'); if (tx.dom) tx.hide(); } } catch (e) { log.err(e, 'view'); } } } } catch (e) { log.err(e, 'view'); } } /** * 保存修改数据到data,并切换到浏览视图 */ save() { const _ = this; try { _.opt.edit = false; _.tb.tag('tbody').addClass('etView').removeClass('etEdit'); _.unbind(); _.tb.find('._choose').hide(); if (_.hasTable && _.tabulate) _.tabulate.saveTable(); if (_.data) { const tds = _.tb.find('td[data-idx]'); for (const td of tds.get()){ try { const $td = $(td); const idx = $td.data('idx') // 数据索引 ; const d = _.data[idx]; const { type, option } = d || {}; if ((type === DataType.search || type === DataTypes.search) && _.Autocomplete) { const span = $td.find('span'); // span.eq(0).html(value) const tx = $td.find('input'); if (tx) { const val = tx.val(); // 设置 原始值 d.value = val; $td.data('value', val); const dvAc = $td.find('.autocomplete'); dvAc?.hide(); } span.show(); } else if (type === DataType.checkbox || type === DataTypes.checkbox) { const val = []; const ns = $td.find('input[type=checkbox]'); for (const n of ns.get()){ if (n.checked) { val.push($(n).val()); break; } } // 设置 原始值 d.value = val; $td.data('value', val); } else if (type === DataType.radio || type === DataTypes.radio) { let val; const ns = $td.find('input[type=radio]'); for (const n of ns.get()){ if (n.checked) { val = $(n).val(); break; } } // 设置 原始值 d.value = val; $td.data('value', val); } else if (type === DataType.url || type === DataTypes.url) { // urlChange const span = $td.find('span'); let tx = $td.find('input'); if (span) { span.eq(0).find('a').attr('href', tx.val()); span.removeClass('edit'); span.show(); } if (tx.dom) { const val = tx.val(); // 设置 原始值 d.value = val; $td.data('value', val); tx.hide(); } } else if (type !== DataType.attach && type !== DataTypes.attach && type !== DataType.table && type !== DataTypes.table && type !== DataType.view && type !== DataTypes.view && type !== DataType.page && type !== DataTypes.page) { const span = $td.find('span'); if (span) { span.removeClass('edit'); span.show(); } let tx = $td.find('input'); if (!tx.dom) tx = $td.find('select'); if (tx.dom) { const val = tx.val(); // 设置 原始值 d.value = val; $td.data('value', val); tx.hide(); } } } catch (e) { log.err(e, 'save'); } } } } catch (e) { log.err(e, 'save'); } } /** * 取消修改,还原值 */ cancel() { const _ = this; try { _.opt.edit = false; _.tb.tag('tbody').addClass('etView').removeClass('etEdit'); _.tabulate?.togglePreview(); _.unbind(); _.tb.find('._choose').hide(); if (_.data) { const tds = _.tb.find('td[data-idx]'); for (const td of tds.get()){ try { const $td = $(td); const idx = $td.data('idx') // 数据索引 ; const d = _.data[idx]; // const value = $td.data('value') // 原始值 const value = $td?.attr('data-value') // 数据原值 data() 会自动转换 json 字符串 ; const { type, option } = d || {}; if ((type === DataType.search || type === DataTypes.search) && _.Autocomplete) { const dvAc = $td.find('.autocomplete'); dvAc?.hide(); const span = $td.find('span'); span.eq(0).html(value); span.show(); } else if (type === DataType.checkbox || type === DataTypes.checkbox) { const ns = $td.find('input[type=checkbox]'); for (const n of ns.get()){ const val = $(n).val(); n.checked = value.includes(val); } } else if (type === DataType.radio || type === DataTypes.radio) { const ns = $td.find('input[type=radio]'); for (const n of ns.get()){ const val = $(n).val(); n.checked = value === val; } } else if (type === DataType.attach || type === DataTypes.attach) { // 取消新增 // uploader 维护 input $td.dom.uploader.clear() // 清空 input 和 uploader ; } else if (type === DataType.url || type === DataTypes.url) { const span = $td.find('span'); if (span.dom) { span.removeClass('edit'); span.eq(0).find('a').attr('href', value); span.show(); } let tx = $td.find('input'); if (tx.dom) { tx.val(value); tx.hide(); } } else if (type !== DataType.table && type !== DataTypes.table && type !== DataType.view && type !== DataTypes.view && type !== DataType.page && type !== DataTypes.page) { const span = $td.find('span'); if (span.dom) { span.html(value); span.removeClass('edit'); span.show(); } let tx = $td.find('input'); if (!tx.dom) tx = $td.find('select'); if (tx.dom) { tx.val(value); tx.hide(); } } } catch (e) { log.err(e, 'cancel'); } } } // 取消删除 cancelDel(_.tb); } catch (e) { log.err(e, 'cancel'); } } /** * 样式改为编辑状态,支持 input、span * textarea 不能自适应行高,不再支持 textarea,使用可编辑span替代 * span 不支持 onchange,使用 viewCell 获取更改值 * 如填入数据与原数据不同,cell 增加 edChange 样式 * @param {HTMLElement} tx - input 或 span 组件 * @param {*} sel 下拉列表, 下拉选择时,sel 为 true */ editCell(tx, sel) { const _ = this; const { opt } = _; if (tx && opt.edit) { const $tx = $(tx); // 点击同一网格编辑直接返回 if (!sel && this._editCell && tx === this._editCell) return; if (!opt.editTag.includes(tx.tagName.toLowerCase())) // && tx.tagName !== 'SPAN') // || tx.type != "text") return; // 最后编辑行 this.editRow = tool.getUpperObj(tx, 'TR'); // 最后编辑的对象 this.editTx = tx; // 去掉选择行样式为缺省奇偶样式 if (this._selRow) { this.setRowClass(this._selRow, ''); this._selRow = null; } if (tx.tagName === 'SPAN') { // 替换提示字符 if (/^~~.+~~/.exec(tx.innerHTML)) tx.innerHTML = tx.innerHTML.replace(/^~~.*~~/, ''); // tx.scrollIntoView(true); // tx.scrollTop = 50; } else if (tx.tagName === 'INPUT') { // tool.setClass(this._editCell, '') // tool.setClass(tx, 'etCellEdit') // $tx.addClass('fb-input', true) $tx.addClass('ds-input', true); // input 需全选方便替换! if (!sel) { tx.focus(); // 全选 tx.select(); } } // 记录当前正在编辑单元 this._editCell = tx; } } /** * 浏览状态,去掉编辑样式 * 对于 span,不会触发 onchange 事件,在这里将 span值同步给隐藏 input,方便向服务器端提交! * @param {HTMLElement} tx */ viewCell(tx) { if (!tx) return; // 下列选项会触发 viewCell if (tx.getAttribute('inputing') && tx.getAttribute('inputing') === '1') // 下拉选择时,保持编辑状态 return; // 去掉编辑样式 if (tx.tagName === 'INPUT') tool.setClass(tx, 'etCellView'); else if (tx.tagName === 'SPAN') { const txTo = tool.childTag(tx.parentNode, 'input'); let val = tx.innerHTML.replace(/^~~.*~~/, ''); // 对输入字符进行处理 val = val.replace(/<br>/g, '\n'); // val = val.replace(/&gt;/g, '>'); // val = val.replace(/&lt;/g, '<'); val = val.replace(/&nbsp;/g, ' '); if (!val || /^[\s\n\r]+$/.exec(val)) { if (tx.className === 'imgTitle') // style.textAlign === 'center') tx.innerHTML = '~~点击输入标题~~'; else tx.innerHTML = '~~点击输入~~<br><br>'; if (txTo) { const img = tool.childTag(txTo.parentNode, 'img'); if (img) { txTo.value = tool.format('![%s](%s)', '', img.getAttribute('src')); } else txTo.value = ''; } } else if (txTo) { // ,号干扰后台数据读取,转换为 \~,后台读取后再转换 // val = tx.innerHTML.replace(/,/g, '\~'); const img = tool.childTag(txTo.parentNode, 'img'); if (img) { txTo.value = tool.format('![%s](%s)', val || '', img.getAttribute('src')); } else txTo.value = val; } } this._editCell = null; } // 更改 行 样式 setRowClass(row, className) { if (!row) return; // 恢复原始 样式 if (className) tool.setClass(row, className); else { tool.setClass(row, ''); /* if (row.className.indexOf('Odd') > -1) tl.setClass(row, 'etRowOdd'); else tl.setClass(row, 'etRowEven'); */ } } // 对指定 列,设定 只读或非只读, readOnly(tb1, names, val) { try { const tbody = tool.tags(tb1, 'TBODY')[0]; const txs = tool.tags(tbody, 'INPUT'); for(let i = 0; i < txs.length; i++){ // 排除 隐藏列 if (txs[i].type === 'text') { txs[i].readOnly = !val; for(let j = 0; j < names.length; j++){ if (txs[i].name === `tx${names[j]}`) txs[i].readOnly = val; } } } } catch (e) { alert(e.message); } } // 对指定 列,设定隐藏 hideCol(tb1, names) { try { for(let i = 0; i < names.length; i++){ const th = tool.id(`th${names[i]}`); if (th) { th.style.display = 'none'; } } // 在线编辑模版必须 带 tbody,否则工作不正常 // ??? 为何屏蔽 tbody const tbody = tool.tags(tb1, 'TBODY')[0]; const txs = tool.tags(tbody, 'INPUT'); for(let i = 0; i < txs.length; i++){ // 排除 隐藏列 if (txs[i].type === 'text') { for(let j = 0; j < names.length; j++){ if (txs[i].name === `tx${names[j]}`) { const td = tool.getUpperObj(txs[i], 'TD'); if (td) td.style.display = 'none'; } } } } } catch (e) { alert(e.message); } } // 对指定 列,设定显示 showCol(tb1, ns) { try { for(let i = 0; i < ns.length; i++){ const th = tool.id(`th${ns[i]}`); if (th) { th.style.display = ''; } } // 在线编辑模版必须 带 tbody,否则工作不正常 // ??? 为何屏蔽 tbody const tbody = tool.tags(tb1, 'TBODY')[0]; const txs = tool.tags(tbody, 'INPUT'); for(let i = 0; i < txs.length; i++){ // 排除 隐藏列 if (txs[i].type === 'text') { for(let j = 0; j < ns.length; j++){ if (txs[i].name === `tx${ns[j]}`) { const td = tool.getUpperObj(txs[i], 'TD'); if (td) td.style.display = ''; } } } } } catch (e) { alert(e.message); } } lightonRow(row) { const r = tool.getUpperObj(row, 'TR'); if (!r || r === this._selRow) return; // 当前点击行高亮度显示 if (r.className.indexOf('Odd') > -1) this.setRowClass(r, 'etRowSelOdd'); else this.setRowClass(r, 'etRowSelEven'); // 将所有未被选中的行取消高亮度现实 this.setRowClass(this._selRow, ''); this._selRow = r; } // 指定表的指定列,指定数据删除 deleteData(tb1, name, val) { const txs = document.getElementsByName(name); for(let i = 1; i < txs.length; i++){ if (txs[i].value === val) { this.delRow(tb1, i); i--; } } } /** * 行选择,点击选择当前行,便于删除行 * 支持Checkbox 多选行,用于批量行操作 * @param obj */ selRow(obj, ev) { if (!obj) return; // if ( ev ) // ev.preventDefault(); const row = tool.getUpperObj(obj, 'TR'); if (row) { if (obj.tagName === 'TD') { if (row !== this._selRow) this._selRow = row; } else { const tbody = tool.getUpperObj(row, 'TBODY'); const tb1 = tbody.parentNode; const th = tool.tags(tb1, 'TH')[0]; if (tool.lastChild(tool.firstChild(row)).value) { tool.lastChild(th).value = tool.lastChild(th).value ? `${tool.lastChild(th).value},` : ''; if (obj.checked) tool.lastChild(th).value += tool.lastChild(tool.firstChild(row)).value; else tool.lastChild(th).value = tool.lastChild(th).value.replace(`${tool.lastChild(tool.firstChild(row)).value},`, ''); } } } } /** * 删除选择或最后编辑行,或当前行,一次只能删除一行 * @param tb * @param iRow 指定的行数 * 返回 剩下的行数 */ delRow(tb1, iRow) { let rs = 0; if (!tb1) return 0; try { const tbody = tool.tags(tb1, 'TBODY')[0]; const rows = tool.tags(tbody, 'TR'); if (rows.length === 0) return; let delRow = null; if (tb1 && iRow) delRow = tb1.rows[iRow + 1] // 删除选择行 ; else delRow = this._selRow; // 没有选择行,直接删除最后编辑行 if (!delRow && this.editRow) { delRow = this.editRow; // x('txInfo').value = 'this._editRow'; } if (!delRow) delRow = rows[rows.length - 1]; if (!delRow) return; if (this._selRow) { this.setRowClass(this._selRow, ''); this._selRow = null; } if (this._editCell) { tool.setClass(this._editCell, ''); this._editCell = null; } const th = tool.tags(tb1, 'TH')[0]; if (delRow.childElementCount > 0 && tool.lastChild(tool.firstChild(delRow)).value) { // 记录删除ID tool.lastChild(th).value = tool.lastChild(th).value ? `${tool.lastChild(th).value},` : ''; tool.lastChild(th).value += tool.lastChild(tool.firstChild(delRow)).value; } // alert( tl.lastChild(tl.firstChild(delRow)).value ); if (delRow === this._selRow) this._selRow = null; if (delRow === this.editRow) { this.editRow = null; this.editTx = null; } let preRow = delRow.previousSibling; let span = tool.tags(delRow, 'span')[0]; if (delRow.parentNode) delRow.parentNode.removeChild(delRow); // span 全屏编辑,没有数据,自动增加一行 if (span) { // 返回剩下行数 rs = tool.tags(tbody, 'TR').length; if (!rs) { preRow = this.addRow(tb1, '~~点击输入~~\n\r\n\r'); rs = 1; } if (preRow) { span = tool.tags(preRow, 'span')[0]; if (span) tool.cursorEnd(span); } } // 对tr重新排序,排除 文本节点干扰,移动端不需要,屏蔽!!!??? /* var i = 0; for(var j = 0; j < tb