UNPKG

@wiajs/ui

Version:

wia ui packages

1,203 lines (1,201 loc) 47.3 kB
// 海口永力新能源技术有限公司 版权所有 /** @jsxImportSource @wiajs/core */ import { jsx as _jsx, jsxs as _jsxs } from "@wiajs/core/jsx-runtime"; import { log as Log } from '@wiajs/util'; import Ud from '../uploader'; // eslint-disable-line const log = Log({ m: 'tabulate' }) // 创建日志实例 ; const g = { /** @type {*} - 动态加载并缓存 */ tabulator: null, /** @type {*} */ lightbox: null }; /** * @typedef {import('../editTable/index').default} EditTable */ /** * @typedef {import('jquery')} $ * @typedef {JQuery} Dom */ // const {$} = window export default class Tabulate { /** * 构造函数 * @param {Object} options - 组件配置选项 * @param {string} options.containerName -表格容器name属性 * @param {string} options.addButtonName - 添加按钮name属性 * @param {Object} options.baseTableInfo - 基础表格信息 * @param {*} options.targetBox - 表格DOM * @param {Function} options.getSelAll - 获取公司数据的方法 * @param {Function} options.saveEmbTb - 保存表格数据的方法 * @param {*} options.viewid - cardId * @param {*} options.prjid - 项目id * @param {*} options.upload - 上传参数 * */ constructor(options){ // 配置参数 this.containerName = options.containerName || 't-table'; this.addButtonName = options.addButtonName || 'add-button'; this.baseTableInfo = options.baseTableInfo || {}; this.getSelAll = options.getSelAll || (()=>Promise.resolve([])); this.targetBox = options.targetBox; this.viewid = options.viewid; this.prjid = options.prjid; this.upload = options.upload; this.saveEmbTb = options.saveEmbTb || (()=>Promise.resolve([])); // 实例属性 this.tEditTable = null; this.newData = {}; this.companyData = []; this.deletedData = []; this.fileObj = {}; this.formatMap = {}; // 初始化 this.init(); } /** * 初始化组件 */ async init() { try { await this.prepareCompanyData(); this.createTableStructure(); await this.loadTabulatorAndRender(); this.bindEvents(); } catch (error) { log.err(error, 'init'); } } /** * 获取并准备数据 */ async prepareCompanyData() { try { const result = await this.getSelAll({ ref: 'value-base/company:id', fields: 'id name' }); this.companyData = result?.map((com)=>({ id: com.id, label: com.name, value: com.name })); } catch (error) { log.err(error, 'prepareCompanyData'); this.companyData = []; } } /** * 创建表格DOM结构 */ createTableStructure() { try { this.targetBox.forEach((table)=>{ table.style.display = 'none'; console.log(table.parentNode.children, 'table.parentNode'); // 获取当前 .data-table 的父节点(兄弟元素的共同容器) const parent = table.parentNode; // 在父节点中查找 name="t-table" 的 div const tTableDiv = parent.querySelector('div[name="t-table"]'); const tTableBtn = parent.querySelector('button[name="add-button"]'); if (tTableDiv && tTableBtn) { tTableDiv.style.display = 'block'; tTableBtn.style.display = 'block'; } else { const newDiv = document.createElement('div'); newDiv.setAttribute('name', this.containerName); table.insertAdjacentElement('afterend', newDiv); const newButton = document.createElement('button'); newButton.textContent = '新增' // 设置按钮文本 ; // newButton.id = 'add-button'; // 设置按钮ID(可选) newButton.setAttribute('name', this.addButtonName); newButton.classList.add('btn') // 添加样式类(可选) ; newButton.style.background = '#007bff'; newButton.style.color = '#fff'; newButton.style.marginBottom = '10px'; // 插入到 oldDiv 前方 if (newDiv && newDiv.parentNode) { newDiv.parentNode.insertBefore(newButton, newDiv); } else { console.error('oldDiv 不存在或没有父元素'); } } }); } catch (error) { log.err(error, 'createTableStructure'); } } /** * 销毁实例 * @param {*} instance * @returns */ destroyTabulateInstance(instance) { try { if (!instance) return; // 清除实例关联的DOM(如容器内的表格结构) const containers = document.querySelectorAll(`div[name="${instance.containerName}"]`); containers?.forEach((container)=>{ container.innerHTML = '' // 清空容器 ; }); // 移除事件监听(如果实例有绑定事件,如按钮点击) const addButtons = document.querySelectorAll(`button[name="${instance.addButtonName}"]`); addButtons?.forEach((button)=>{ button.removeEventListener('click', ()=>this.addRow()); }); // 清除实例引用(释放内存) instance = null; } catch (error) { log.err(error, 'destroyTabulateInstance'); } } /** * 加载Tabulator并渲染表格 * 有问题 */ async loadTabulatorAndRender() { try { // 准备表格数据和列配置 const tableData = this.prepareTableData(); const columns = this.prepareColumns(); let container; this.targetBox.forEach((table)=>{ const parent = table.parentNode; // 在父节点中查找 name="t-table" 的 div const tTableDiv = parent.querySelector('div[name="t-table"]'); container = tTableDiv; // const container = document.querySelector(`[name="${this.containerName}"]`) }); // 初始化表格 // const container = document.querySelector(`[name="${this.containerName}"]`) if (!g.tabulator) { // @ts-ignore // import tabulatorTables from 'https://cdn.jsdelivr.net/npm/tabulator-tables@6.3.1/+esm' //! g.tabulator = await import('https://cos.brains.fund/wia/tabulator.mjs') await import('https://cos.brains.fund/wia/tabulator.min.js'); g.tabulator = true; } //! this.tEditTable = new g.tabulator.Tabulator(container, { this.tEditTable = new Tabulator(container, { data: tableData, layout: columns.length > 7 ? 'fitDataStretch' : 'fitColumns', columns: columns, rowFormatter: (row)=>this.formatRow(row) }); this.tEditTable.on('cellEdited', (cell)=>this.handleCellEdit(cell)); } catch (error) { log.err(error, 'loadTabulatorAndRender'); } } /** * @param {any[]} head * @param {any[]} data */ formatData(head, data) { const formatMap = {}; const dateMap = []; head.forEach((item)=>{ if (item.div !== undefined && item.idx !== undefined) { // @ts-expect-error formatMap[item.idx] = item.div; } if (item.type === 'date') { dateMap.push(item.idx); } }); console.log(dateMap, 'dateMap'); this.formatMap = formatMap; // 格式化数据 return data.map((row)=>{ return row.map((value, index)=>{ if (formatMap[index] && typeof value === 'number') { return value / formatMap[index]; } if (dateMap.includes(index)) { const dateStr = value // 获取原始值(2069-04-20T16:00:00.000Z) ; if (!dateStr) return ''; const utcDate = new Date(dateStr); const chinaTimeTimestamp = utcDate.getTime() + 8 * 60 * 60 * 1000 // 8小时的毫秒数 ; const chinaDate = new Date(chinaTimeTimestamp); const year = chinaDate.getUTCFullYear(); const month = String(chinaDate.getUTCMonth() + 1).padStart(2, '0') // 月份从0开始 ; const day = String(chinaDate.getUTCDate()).padStart(2, '0'); return `${year}-${month}-${day}`; } return value; }); }); } /** * 准备表格数据 */ prepareTableData() { try { const baseData = this.baseTableInfo?.data || []; const result = this.formatData(this.baseTableInfo.head, baseData); return result[0] === null ? [] : result.map((item, index)=>({ id: index, ...item })); } catch (error) { log.err(error, 'prepareTableData'); } } //Create Date Editor /** * @param {{ getValue: () => any; }} cell * @param {(arg0: () => void) => void} onRendered * @param {(arg0: any) => void} success * @param {() => void} cancel */ dateEditor(cell, onRendered, success, cancel) { //cell - the cell component for the editable cell //onRendered - function to call when the editor has been rendered //success - function to call to pass thesuccessfully updated value to Tabulator //cancel - function to call to abort the edit and return to a normal cell //create and style input var cellValue = cell.getValue(), input = document.createElement('input'); input.setAttribute('type', 'date'); input.style.padding = '4px'; input.style.width = '100%'; input.style.boxSizing = 'border-box'; input.value = cellValue; onRendered(()=>{ input.focus(); input.style.height = '100%'; }); function onChange() { if (input.value != cellValue) { success(input.value); } else { cancel(); } } //submit new value on blur or change input.addEventListener('blur', onChange); //submit new value on enter input.addEventListener('keydown', (e1)=>{ if (e1.keyCode == 13) { onChange(); } if (e1.keyCode == 27) { cancel(); } }); return input; } // 辅助函数:根据ID获取对应的中文名称 /** * @param {any[]} options * @param {any} id */ getOptionNameById(options, id) { console.log(options, 'options'); console.log(id, 'id'); const option = options.find((/** @type {{ value: any; }} */ item)=>item.value == id); return option ? option.label : ''; } /** * @param {{ [s: string]: any; } | ArrayLike<any>} data */ convertToOptions(data) { // 处理第一种格式:{0:'待定',1:'前期开发',...} if (typeof data === 'object' && !Array.isArray(data)) { return Object.entries(data).map(([key, value])=>({ value: Number(key), label: value })); } else if (Array.isArray(data)) { return data.map((item)=>({ value: item, label: item })); } return []; } /** * 准备列配置 */ prepareColumns() { try { // @ts-ignore const head = this.baseTableInfo?.head || []; const columns = head.slice(1).map((item, index)=>{ const fieldIndex = head[0]?.hide?.length > 0 ? index + 1 : index; // 编辑器类型判断:新增upload类型支持 let editor = false; if (item.editor === 'image') { // @ts-ignore editor = this.imageUploadEditor.bind(this); } else if (item.editor === 'upload') { // @ts-ignore editor = (...args)=>this.fileUploadEditor(fieldIndex, false, ...args); // editor = this.fileUploadEditor.bind(this,index); // 绑定文件上传编辑器 } else if (item.editor === 'date') { // @ts-expect-error editor = this.dateEditor; } else if (item.editor) { editor = item.editor; } return { title: item.name, field: fieldIndex.toString(), editor: editor, ...item.editor === 'upload' && { formatter: (...args)=>this.fileUploadEditor(fieldIndex, true, ...args) }, ...item.type === 'date' && { formatter: (cell)=>{ const dateStr = cell.getValue() // 获取原始值(2069-04-20T16:00:00.000Z) ; if (!dateStr) return ''; const date = new Date(dateStr); if (isNaN(date.getTime())) { // 检查日期是否有效 return '无效日期'; } // 格式化为YYYY-MM-DD(使用UTC时间) const year = date.getUTCFullYear(); const month = String(date.getUTCMonth() + 1).padStart(2, '0') // 月份从0开始 ; const day = String(date.getUTCDate()).padStart(2, '0'); return `${year}-${month}-${day}`; } }, editorParams: item.editor === 'list' ? this.getListEditorParams(item) : {}, // 格式化显示:根据ID显示中文名称 ...item.editor === 'list' && item.option && { formatter: (cell)=>{ return this.getOptionNameById(this.getListEditorParams(item).values, cell.getValue()); } } }; }); // 新增:添加操作列(删除按钮) columns.push({ title: '操作', field: 'action', fixedWidth: true, width: 80, formatter: (/** @type {{ getRow: () => Object; }} */ cell)=>{ const btn = document.createElement('button'); btn.textContent = '删除'; btn.style.background = '#dc3545'; btn.style.color = '#fff'; btn.style.border = 'none'; btn.style.padding = '3px 8px'; btn.style.borderRadius = '3px'; btn.style.cursor = 'pointer'; // 绑定删除事件 btn.addEventListener('click', (e1)=>{ e1.stopPropagation() // 阻止事件冒泡 ; this.deleteRow(cell.getRow()); }); return btn; }, hozAlign: 'center', cellClick: false }); return columns; } catch (error) { log.err(error, 'prepareColumns'); } } /** * 获取上传编辑器参数 * @param {Object} column - 列配置信息 */ getUploadEditorParams(column) { try { return { url: column.uploadUrl || 'https://lianlian.pub/img/upload', dir: column.uploadDir || 'table/uploads', accept: column.accept || '*', limit: column.limit || 1, multiple: column.multiple || false }; } catch (error) { log.err(error, 'getUploadEditorParams'); } } /** * 删除行处理 * @param {Object} row - Tabulator行对象 */ deleteRow(row) { try { if (!confirm('确定要删除此行数据吗?')) { return; } const rowData = row.getData(); const rowId = rowData.id; const _r = Object.entries(rowData).filter(([key])=>key !== 'id').sort((a, b)=>parseInt(a[0]) - parseInt(b[0])).map((item)=>item[1]); // 处理删除数据记录 if (rowId >= 0) { // 原有数据(正数ID)- 需要记录到删除列表 this.deletedData.push({ index: rowId, data: _r }); } else { // 新增未保存数据(负数ID)- 直接从newData中移除 delete this.newData[rowId]; } // 从表格中移除行 this.tEditTable.deleteRow(rowId); } catch (error) { log.err(error, 'deleteRow'); } } /** * 获取列表编辑器参数 */ getListEditorParams(item) { try { if (item.option) { const convertData = this.convertToOptions(item.option); console.log(convertData, 'convertData'); return { // 转换选项为Tabulator需要的格式 {value: id, label: 中文名称} values: convertData }; } else { return { values: this.companyData, autocomplete: true, freetext: true, allowEmpty: true, itemFormatter: (label, value, item, element)=>{ return ` <div style="padding: 5px;"> <strong>${label}</strong> </div> `; } }; } } catch (error) { log.err(error, 'getListEditorParams'); } } /** * 切换编辑/预览模式 * @param {boolean} isEditing - 是否为编辑模式 */ togglePreview() { try { this.targetBox.forEach((/** @type {{ style: { display: string; }; }} */ table)=>{ table.style.display = 'block'; const parent = table.parentNode; const tTableDiv = parent.querySelector('div[name="t-table"]'); const addButton = parent.querySelector('button[name="add-button"]'); // @ts-expect-error tTableDiv.style.display = 'none'; // @ts-expect-error addButton.style.display = 'none'; }); } catch (error) { log.err(error, 'togglePreview'); } } /** * 绑定事件 */ bindEvents() { try { const addButton = document.querySelector(`[name="${this.addButtonName}"]`); if (addButton) { addButton.addEventListener('click', ()=>this.addRow()); } } catch (error) { log.err(error, 'bindEvents'); } // const addButton = document.getElementById(this.addButtonName); } /** * 添加新行 */ addRow() { try { if (!this.tEditTable) return; const data = this.tEditTable.getData(); const len = data.length; this.tEditTable.addRow({ id: -(len + 1) }); } catch (error) { log.err(error, 'addRow'); } } /** * 处理单元格编辑 * @param {Object} cell - 单元格对象 */ handleCellEdit(cell) { try { const eidtData = cell.getRow().getData(); const id = eidtData.id; const row = this.tEditTable.getRow(id); if (this.baseTableInfo.head?.[0].editOpt) { this.handleEditWithOptions(eidtData, id); } else { this.handleFreeEdit(eidtData, id); } row?.reformat(); } catch (error) { log.err(error, 'handleCellEdit'); } } /** * 处理带选项的编辑 * @param {Object} data - 行数据 * @param {number} id - 行ID */ handleEditWithOptions(data, id) { try { const searchIdx = this.baseTableInfo.head[0].editOpt.search[0]; const name = data[searchIdx + 1]; const result = this.companyData.find((item)=>item.label === name); this.newData[id] = { ...id < 0 && { 0: result?.id }, ...data, id, [searchIdx + 1]: result ? result.label : data[searchIdx + 1] }; } catch (error) { log.err(error, 'handleEditWithOptions'); } } /** * 处理自由编辑 * @param {Object} data - 行数据 * @param {number} id - 行ID */ handleFreeEdit(data, id) { try { const hasOwnEnumerable0 = Object.prototype.propertyIsEnumerable.call(data, '0'); this.newData[id] = { ...data, ...hasOwnEnumerable0 ? {} : { 0: null }, id: id }; } catch (error) { log.err(error, 'handleFreeEdit'); } } /** * 格式化行样式 * @param {Object} row - 行对象 */ formatRow(row) { try { const data = row.getData(); row.getElement().style.backgroundColor = data.editing ? '#fffde7' : ''; } catch (error) { log.err(error, 'formatRow'); } } /** * 保存表格数据 * @returns {Object} 保存的数据 */ async saveTable() { try { this.newData = this.processData(this.newData); this.deletedData = this.processData(this.deletedData); const add = []; const update = []; const del = []; const keys = Object.keys(this.formatMap); Object.keys(this.newData).forEach((key)=>{ const rowData = this.newData[key]; for(const o in rowData){ if (keys.includes(o)) { rowData[o] = rowData[o] * this.formatMap[o]; } } if (key < 0) { const _r = Object.entries(this.newData[key]).filter(([key])=>key !== 'id') // 过滤掉 id 键 .sort((a, b)=>parseInt(a[0]) - parseInt(b[0])) // 按数字键排序 .map((item)=>item[1]) // 只保留值 ; add.push(_r); } else { const _r = Object.entries(this.newData[key]).filter(([key])=>key !== 'id') // 过滤掉 id 键 .sort((a, b)=>parseInt(a[0]) - parseInt(b[0])) // 按数字键排序 .map((item)=>item[1]) // 只保留值 ; update.push({ index: key, data: _r }); } }); const params = { viewid: this.viewid, id: this.prjid, add, update, del: this.deletedData }; console.log(params, 'params'); await this.saveEmbTb(params); } catch (error) { log.err(error, 'saveTable'); } } /** * 文件上传编辑器 - 渲染Uploader组件 * @param {Object} cell - 单元格对象 * @param {Object} index - 编辑器参数 * * @returns {HTMLElement} 编辑器元素 */ fileUploadEditor(index, read, cell) { try { const cellValue = cell.getValue() || []; const row = cell.getRow(); const rowId = row.getData().id; // @ts-expect-error this.fileObj[`${rowId}-${index}`] = this.fileObj[`${rowId}-${index}`] ?? cellValue; console.log(cellValue, 'value'); const i = -1; // if (Array.isArray(cellValue) ){ // for (const v of cellValue) { // i++ // v._idx = i // 数据加索引,方便浏览 // } // } const container = document.createElement('td'); container.className = 'upload-editor-container'; const $container = $(container); const att = $(/*#__PURE__*/ _jsx("div", { class: `etAttach` })); att.appendTo($container); att.append(/*#__PURE__*/ _jsx("div", { class: "attach-wrap" })); // 封装层,超出左右滑动 const wrap = att.find('.attach-wrap'); for (const v of this.fileObj[`${rowId}-${index}`]){ const { _idx, name, url, type, ext } = v; const field = ''; const abb = ''; this.addItem(wrap, field, type, ext, name, abb, url, _idx, read); } if (!read) { wrap.append(/*#__PURE__*/ _jsxs("div", { class: "attach-item wia_uploader", children: [ /*#__PURE__*/ _jsx("input", { name: `field-attach-add`, type: "hidden" }), /*#__PURE__*/ _jsx("div", { class: "_choose", children: /*#__PURE__*/ _jsx("div", { name: "btnAdd", class: "_input" }) }) ] })); // 初始化Uploader组件 const { dir, url } = this.upload; let { token } = this.upload; token = token ?? 'token'; const uploader = new Ud({ dir, url, el: wrap.class('wia_uploader'), input: wrap.name(`field-attach-add`), choose: wrap.class('_choose'), upload: true, preview: false, delete: true, accept: '*', abb: 'lisi', multiple: true, left: 250, header: { 'x-wia-token': $.store.get(token) } }); uploader.on('success', (rs, file, files)=>{ console.log('uploader succ', { rs, file, files }); const eidtData = cell.getRow().getData(); const id = eidtData.id; const row = this.tEditTable.getRow(id); const hasOwnEnumerable0 = Object.prototype.propertyIsEnumerable.call(eidtData, '0'); this.fileObj[`${rowId}-${index}`].push({ id: file.id, cat: '', abb: file.name, type: file.type, ext: file.ext, url: file.url }); const defaultArr = this.fileObj[`${rowId}-${index}`].map((file)=>file.id); this.newData[id] = { ...eidtData, ...hasOwnEnumerable0 ? {} : { 0: null }, [index]: defaultArr }; this.newData = this.processData(this.newData); }); } $container.click((event)=>this.attachClick(event, index, cell)) // 点击浏览大图 ; container.attachData = cellValue; $container.data('idx', rowId) // td 保存 EditTable 的数据索引 ; return container; } catch (error) { log.err(error, 'fileUploadEditor'); } } /** * 处理对象,将包含id属性的对象数组转换为id数组 * @param {any} data - 需要处理的数据(对象、数组或基本类型) * @returns {any} data 处理后的新数据 */ processData(data) { try { // 1. 如果是对象(非null且非数组),递归处理每个属性 if (typeof data === 'object' && data !== null && !Array.isArray(data)) { const processed = {}; for(const key in data){ if (Object.hasOwn(data, key)) { // @ts-expect-error processed[key] = this.processData(data[key]) // 递归处理子属性 ; } } return processed; } // 2. 如果是数组,检查每个元素是否是含id的对象 if (Array.isArray(data)) { return data.map((item)=>{ // 若元素是对象且包含id属性,则提取id if (typeof item === 'object' && item !== null && 'id' in item) { return item.id; } // 否则递归处理元素(处理嵌套数组/对象) return this.processData(item); }); } // 3. 基本类型(null、number、string等)直接返回 return data; } catch (error) { log.err(error, 'processData'); } } /** * 点击tr、td 浏览大图或删除附件 * @param {*} ev */ async attachClick(ev, index, cell) { try { const _ = this; const eidtData = cell.getRow().getData(); const id = eidtData.id; const row = this.tEditTable.getRow(id); // 如果点击的是 input 则不处理 if (ev.target.type === 'file') return; const td = $(ev).upper('td'); const idx = td.data('idx') // EditTable data 索引 ; let value = td?.dom?.attachData; // this.fileObj[`${idx}-${index}`] = this.fileObj[`${idx}-${index}`] ?? value const tr = $(ev).upper('tr'); if (!value) value = tr?.dom?.attachData; value = value ?? []; const att = $(ev).upper('.attach-item'); const wrap = $(ev).upper('._wrap'); let i = att.data('idx') // 附件数据索引,新增附件没有 ; console.log(i, 'i'); console.log(idx, 'idx'); const field = att.data('field'); const btnDel = $(ev).upper('.attach-delete'); // 删除 if (btnDel.dom) { if (att.dom) { // 新增附件删除 // if (field.endsWith('-attach-add')) { if (wrap.dom) { const src = wrap.find('._file').data('src'); // uploader 维护 input if (src) td.dom.uploader?.remove({ url: src }); } else { const hasOwnEnumerable0 = Object.prototype.propertyIsEnumerable.call(eidtData, '0'); _.fileObj[`${idx}-${index}`]?.splice(i, 1); const defaultArr = this.fileObj[`${idx}-${index}`].map((file)=>file.id); _.newData[id] = { ...eidtData, ...hasOwnEnumerable0 ? {} : { 0: null }, [index]: defaultArr }; const el = att.upper('.etAttach'); if (!el.dom.attachDel) el.dom.attachDel = []; // 保存被删除元素的信息:DOM克隆、父节点、前一个兄弟节点(用于还原位置) console.log(idx, field, value, att); el.dom.attachDel.push({ idx, field, value, att, parent: att.dom.parentNode, prev: att.dom.previousSibling }); att.remove() // 移除DOM元素 ; } } } else if (att.dom && tr.dom) { // 新增附件没有idx,使用 src let src = ''; if (wrap.dom) src = wrap.find('._file').data('src'); const add = td.find('[name$="-attach-add"]'); const addVal = add.dom?.uploadData ?? []; const data = [ ...value, ...addVal ]; // 浏览图片附件 let v; if (src) { i = -1; v = addVal.find((v)=>v.url === src); } else v = data.find((v)=>v._idx === i); const { type, ext } = v || {}; console.log(type, 'type'); console.log(ext, 'ext'); let { url } = v || {}; if (type === 'doc') { console.log(url, 'doc'); if ([ 'doc', 'docx', 'xls', 'xlsx', 'ppt' ].includes(ext)) url = `https://view.officeapps.live.com/op/view.aspx?src=${url}&wdOrigin=BROWSELINK`; window.open(url, '_blank'); } else if (type === 'img' || type === 'video') { if (!g.lightbox) { // @ts-expect-error // if (!g.anime) g.anime = await import('https://cdn.jsdelivr.net/npm/animejs@4/+esm') // @ts-expect-error // const m = await import('https://cdn.jsdelivr.net/npm/glightbox@3/+esm') const m = await import('https://cos.wia.pub/wiajs/glightbox.mjs'); g.lightbox = m.default; setTimeout(()=>_.showImg(data, i, src), 1000); } else _.showImg(data, i, src); } } } catch (e1) { log.err(e1, 'attachClick'); } } /** * 使用 lightbox 图片浏览 * @param {*[]} data - 附件数据 * @param {number} idx * @param {string} src */ showImg(data, idx, src) { try { 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.url === src || v._idx === idx) id = i; lbox.insertSlide({ href: v.url }); } } // lbox.open() lbox.openAt(id); } } catch (error) { log.err(e, 'showImg'); } } /** * 添加子项 * @param {*} wrap * @param {string} field * @param {string} type * @param {string} ext - 后缀 * @param {string} name - 名称 * @param {string} abb - 缩写标签 * @param {string} [url] * @param {number} [idx] - 附件数组索引,便于点击连续浏览 * @param {boolean} [read] -仅读 */ addItem(wrap, field, type, ext, name, abb, url, idx, read) { let R; try { let el; if (type === 'img') { el = /*#__PURE__*/ _jsxs("div", { class: "attach-item", "data-idx": idx, "data-field": field, children: [ /*#__PURE__*/ _jsx("img", { src: url, alt: abb, title: name, loading: "lazy" }), abb && /*#__PURE__*/ _jsx("p", { children: abb }), !read && /*#__PURE__*/ _jsx("div", { class: "attach-delete", children: /*#__PURE__*/ _jsx("i", { class: "icon wiaicon", children: "" }) }) ] }); } else if (type === 'video') { ext = ext ?? 'mp4'; el = /*#__PURE__*/ _jsxs("div", { class: "attach-item", "data-idx": idx, "data-field": field, children: [ /*#__PURE__*/ _jsx("video", { controls: true, preload: "none", children: /*#__PURE__*/ _jsx("source", { src: url, type: `${type}/${ext}` }) }), abb && /*#__PURE__*/ _jsx("p", { children: abb }), !read && /*#__PURE__*/ _jsx("div", { class: "attach-delete", children: /*#__PURE__*/ _jsx("i", { class: "icon wiaicon", children: "" }) }) ] }); } else if (type === 'doc') { const src = this.getThumb(ext); el = /*#__PURE__*/ _jsxs("div", { class: "attach-item", "data-idx": idx, "data-field": field, children: [ /*#__PURE__*/ _jsx("img", { src: src, alt: abb, title: name, loading: "lazy" }), abb && /*#__PURE__*/ _jsx("p", { children: abb }), !read && /*#__PURE__*/ _jsx("div", { class: "attach-delete", children: /*#__PURE__*/ _jsx("i", { class: "icon wiaicon", children: "" }) }) ] }); } if (wrap && el) { el = $(el); R = el; const ud = wrap.find('.wia_uploader'); if (ud.dom) el.insertBefore(ud); else el.appendTo(wrap); } } catch (e1) { log.err(e1, 'addItem'); } return R; } /** * 获取上传文件缩略图标 * @param {string} ext * @param {string} [url] * @returns {string} */ getThumb(ext, url) { let R; try { ext = `.${ext}`; if (ext.endsWith('.docx') || ext.endsWith('.docm')) ext = '.doc'; else if (ext.endsWith('.pptx')) ext = '.ppt'; else if (ext.endsWith('.xlsx') || ext.endsWith('.xlsm') || ext.endsWith('.xlsb')) ext = '.xls'; ext = ext.replace(/^\.+/, '.'); if (/\.(pdf|xls|doc|csv|txt|zip|rar|ppt|avi|mov|mp3)/i.test(ext)) R = `https://cos.wia.pub/wiajs/img/uploader/${ext.substring(1)}.png`; else R = url ?? 'https://cos.wia.pub/wiajs/img/uploader/raw.png'; } catch (e1) { log.err(e1, 'getThumb'); } return R; } /** * 自定义图片上传编辑器 * @param {Object} cell - 单元格 * @param {Function} onRendered - 渲染回调 * @param {Function} success - 成功回调 * @param {Function} cancel - 取消回调 * @param {Object} editorParams - 编辑器参数 * @returns {HTMLElement} 编辑器元素 */ imageUploadEditor(cell, onRendered, success, cancel, editorParams) { const cellValue = cell.getValue() || []; const container = document.createElement('div'); container.className = 'image-upload-editor'; // 创建上传区域 const uploadContent = document.createElement('div'); uploadContent.className = 'upload-content'; const icon = document.createElement('i'); icon.className = 'fas fa-cloud-upload-alt'; const text = document.createElement('span'); text.textContent = cellValue.length > 0 ? `已上传 ${cellValue.length} 张图片 (点击添加)` : '点击或拖拽上传图片'; uploadContent.appendChild(icon); uploadContent.appendChild(text); // 进度条 const progressBar = document.createElement('div'); progressBar.className = 'progress-bar'; const progress = document.createElement('div'); progress.className = 'progress'; progressBar.appendChild(progress); // 状态文本 const statusText = document.createElement('div'); statusText.className = 'status-text'; // 文件输入 const input = document.createElement('input'); input.type = 'file'; input.multiple = true; input.accept = 'image/*'; // 组装元素 container.appendChild(uploadContent); container.appendChild(progressBar); container.appendChild(statusText); container.appendChild(input); // 点击触发文件选择 container.addEventListener('click', (ev)=>{ if (ev.target !== input) { input.click(); } }); // 处理文件选择 input.addEventListener('change', (ev)=>{ this.handleFileUpload(ev, cellValue, progress, statusText, success); }); // 拖拽功能 this.setupDragAndDrop(container, uploadContent, input); return container; } /** * 处理文件上传 * @param {*} ev * @param {*} cellValue * @param {*} progress * @param {*} statusText * @param {*} success * @returns */ handleFileUpload(ev, cellValue, progress, statusText, success) { const files = Array.from(ev.target.files); if (files.length === 0) return; // 更新状态 statusText.textContent = `上传中: 0/${files.length}`; progress.style.width = '0%'; const newImages = [ ...cellValue ]; let uploadedCount = 0; files.forEach((file, index)=>{ setTimeout(()=>{ const reader = new FileReader(); reader.onload = (e1)=>{ const imageData = { id: Date.now() + index, name: file.name, size: file.size, type: file.type, url: e1.target.result, status: 'uploaded' }; newImages.push(imageData); uploadedCount++; // 更新进度 const percent = Math.round(uploadedCount / files.length * 100); progress.style.width = `${percent}%`; statusText.textContent = `上传中: ${uploadedCount}/${files.length}`; // 全部完成 if (uploadedCount === files.length) { setTimeout(()=>{ success(newImages); statusText.textContent = `上传完成! ${files.length}张图片`; }, 300); } }; reader.readAsDataURL(file); }, index * 300); }); } /** * 设置拖拽功能 */ setupDragAndDrop(container, uploadContent, input) { container.addEventListener('dragover', (e1)=>{ e1.preventDefault(); uploadContent.style.borderColor = '#2ecc71'; uploadContent.style.backgroundColor = '#e8f5e9'; }); container.addEventListener('dragleave', ()=>{ uploadContent.style.borderColor = '#3498db'; uploadContent.style.backgroundColor = ''; }); container.addEventListener('drop', (ev)=>{ ev.preventDefault(); uploadContent.style.borderColor = '#3498db'; uploadContent.style.backgroundColor = ''; if (ev.dataTransfer.files.length > 0) { input.files = e.dataTransfer.files; const event = new Event('change', { bubbles: true }); input.dispatchEvent(event); } }); } /** * 图片格式化函数 * @param {Object} cell - 单元格 * @returns {HTMLElement} 格式化后的元素 */ imageFormatter(cell) { const images = cell.getValue() || []; const container = document.createElement('div'); container.className = 'image-cell'; images.forEach((img)=>{ if (img.url) { const imgElement = document.createElement('img'); imgElement.src = img.url; imgElement.className = 'thumbnail'; imgElement.title = img.name || '图片预览'; container.appendChild(imgElement); } }); if (images.length === 0) { container.textContent = '无图片'; container.style.color = '#95a5a6'; container.style.fontStyle = 'italic'; } return container; } }