@wiajs/ui
Version:
wia app ui packages
607 lines (606 loc) • 21.3 kB
JavaScript
/** @jsxImportSource @wiajs/core */ /**
* editTable 中的附件模块
*/ import { jsx as _jsx, jsxs as _jsxs } from "@wiajs/core/jsx-runtime";
import { log as Log } from '@wiajs/util';
const log = Log({
m: 'attach'
}) // 创建日志实例
;
/**
* @typedef {import('./index').default} EditTable
*/ /**
* @typedef {import('jquery')} $
* @typedef {JQuery} Dom
*/ const g = {
/** @type {*} */ lightbox: null
};
/**
* 按分类渲染附件
* @param {EditTable} _
* @param {*} r
* @param {{_idx: number, id: number, cat:string, name:string,abb:string, url:string, status?:string, type:string, ext?:string}[]} value - 数据卡 值,对象数组
* @param {*} tr - 行 或 td
* @param {{cat: string, col: number}[]} cats - 分类数组,null 无分类,使用 td
* @param {number} idx - editTable 的数据索引
*/ function fillAttach(_, r, value, tr, cats, idx) {
try {
const { read } = r;
let td;
if (!cats) td = tr // 无分类,普通显示
;
// if (!value?.length) return
let i = -1;
for (const v of value){
i++;
v._idx = i // 数据加索引,方便浏览
;
}
if (!cats) {
fillTd(_, td, null, null, value, read, idx);
const $td = $(td);
$td.click(attachClick) // 点击浏览大图
;
td.attachData = value;
$td.data('idx', idx) // td 保存 EditTable 的数据索引
;
} else if (tr) {
tr.dom.attachData = value // 保存数据,用于点击浏览
;
tr.click(attachClick) // 点击浏览大图
;
for (const { cat, col } of cats){
// catid++
const td = document.createElement('td');
td.colSpan = col // cols[catid] // col * 2
;
const $td = $(td);
$td.data('idx', idx) // td 保存 EditTable 的数据索引
;
fillTd(_, td, cat, cats, value, read, idx);
tr.append(td);
// // 插入到空行前
// tbody.dom.insertBefore(tr.dom, null)
// tr.show()
// // setTimeout(() => _.attachLast(tr), 1000) // 换行时补全格线
}
}
} catch (e) {
log.err(e, 'fillAttach');
}
}
/**
*
* @param {*} _ - editDable 实例
* @param {*} td
* @param {string} cat
* @param {*[]} cats
* @param {*} value
* @param {boolean} read
* @param {number} idx - EditTable 数组数据索引
*/ function fillTd(_, td, cat, cats, value, read, idx) {
try {
const { data, opt } = _;
const { field } = data[idx];
let { upload } = data[idx];
upload = upload ?? opt.upload;
const $td = $(td);
const att = $(/*#__PURE__*/ _jsx("div", {
class: `etAttach ${cat ? 'etCat' : ''}`
}));
// <input name={`${field}-attach-del`} type="hidden" />
att.appendTo($td);
// 按 cat 分组,预先已定义cat,无需分组
// const cats = value.reduce((acc, v) => {
// const gp = acc[v.cat]
// if (gp) {
// gp.count++
// gp.data.push(v)
// } else acc[v.cat] = {data: [v], count: 1}
// return acc
// }, {})
if (cat) att.append(/*#__PURE__*/ _jsx("div", {
class: "attach-cat",
children: cat
}));
att.append(/*#__PURE__*/ _jsx("div", {
class: "attach-wrap"
}));
// 封装层,超出左右滑动
const wrap = att.find('.attach-wrap');
// 分类
let vs = value || [];
// 多个cat过滤,一个cat 全部显示
if (cats?.length > 1) vs = vs?.filter((v)=>v.cat === cat);
vs = vs ?? [];
// 添加分类项目
for (const v of vs){
const { _idx, name, url, type, ext } = v;
let { abb } = v;
if (!cat) abb = '';
addItem(wrap, field, type, ext, name, abb, url, _idx);
}
if (!read) {
// 添加附件上传
wrap.append(/*#__PURE__*/ _jsxs("div", {
class: "attach-item wia_uploader",
children: [
/*#__PURE__*/ _jsx("input", {
name: `${field}-attach-add`,
type: "hidden"
}),
/*#__PURE__*/ _jsxs("div", {
class: "_choose",
children: [
/*#__PURE__*/ _jsx("div", {
name: "btnAdd",
class: "_input"
}),
cat && /*#__PURE__*/ _jsx("p", {
children: "新增"
})
]
})
]
}));
if (_.Uploader) {
const { dir, url, token } = upload;
const ud = new _.Uploader({
// dir: `prj/${cat}`, // 图片存储路径
dir,
url,
el: wrap.class('wia_uploader'),
input: wrap.name(`${field}-attach-add`),
choose: wrap.class('_choose'),
label: !!cat,
delete: true,
accept: '*',
// accept: 'image/jpg,image/jpeg,image/png', // 选择文件类型
// compress: true, // 启动压缩
// quality: 0.8, // 压缩比
// maxSize: 200, // 压缩后最大尺寸单位 KB
// width: 80, // 指定宽
// height: 80, // 指定高
// resize: 'cover', // 按指定宽高自动居中裁剪
// aspectRatio: 1, // 宽高比
// crop: 'img/crop', // 按宽高比人工裁剪
preview: false,
multiple: true,
left: 250,
// 随文件上传的数据
// data: {bucket: 'attach', cat: '图像', abb: '我的头像'}, // 其他参数
header: {
'x-wia-token': $.store.get(token)
}
});
td.uploader = ud;
ud.on('choose', async (files)=>{
const abb = cat ? `${cat}1` : '';
let rs;
// 客户端输入 abb、附件类型(合同、图片等)
if (_.onAttach) rs = await _.onAttach(idx, cat, files);
const data = {
cat,
abb,
...rs
};
// let name = ''
// if (files?.[0].name) name = files[0].name
// if (rs?.abb) abb = rs.abb
// const el = addItem(wrap, `${field}-attach-add`, 'img', 'jpg', name, abb, '', value.length)
// el.hide()
ud.config({
// img: el, // 图片显示的容器
data
});
});
// 点击浏览
ud.on('success', (rs, file, files)=>{
console.log('uploader succ', {
rs,
file,
files
});
// FileData.push({id: file.id, type: file.type, url: file.url, ext: file.ext, _idx: file.id, abb: file.name})
// FileEl.data('id', file.id)
});
}
}
if (!opt.edit) wrap.find('._choose').hide();
} catch (e) {
log.err(e, 'fillTd');
}
}
/**
* 添加子项
* @param {*} wrap
* @param {string} field
* @param {string} type
* @param {string} ext - 后缀
* @param {string} name - 名称
* @param {string} abb - 缩写标签
* @param {string} [url]
* @param {number} [idx] - 附件数组索引,便于点击连续浏览
*/ function addItem(wrap, field, type, ext, name, abb, url, idx) {
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
}),
/*#__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
}),
/*#__PURE__*/ _jsx("div", {
class: "attach-delete",
children: /*#__PURE__*/ _jsx("i", {
class: "icon wiaicon",
children: ""
})
})
]
});
} else if (!type || type === 'doc') {
// 默认按 doc 处理,部分文档上传未识别为 doc,此处做兼容处理,避免附件无显示
const src = 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
}),
/*#__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 (e) {
log.err(e, 'addItem');
}
return R;
}
/**
* 处理附件删除
* @param {Dom} td - td
* @param {Dom} att - 附件(.attach-item)
* @param {Dom} wrap - 新增 ._wrap
* @param {string} field - 字段名
* @param {*} value - 附件数据值
* @param {number} idx - EditTable data 索引
*/ function delItem(td, att, wrap, field, value, idx) {
try {
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 el = att.upper('.etAttach');
if (!el.dom.attachDel) el.dom.attachDel = [];
// 保存被删除元素的信息:DOM克隆、父节点、前一个兄弟节点(用于还原位置)
el.dom.attachDel.push({
idx,
field,
value,
att,
parent: att.parentNode(),
prev: att.dom.previousSibling
});
att.remove() // 移除DOM元素
;
}
}
} catch (e) {
log.err(e, 'delItem');
}
}
/**
* 还原所有被删除的元素
* @param {*} el - EditTable
*/ function cancelDel(el) {
try {
const es = el.find('.etAttach');
for (const e of es){
if (!e.attachDel) continue;
// 遍历所有需要还原的元素
for (const r of e.attachDel){
if (r.att && r.parent) {
const { parent, att } = r;
const up = parent.findNode('.wia_uploader');
if (up.dom) up.before(att);
else parent.append(att);
}
}
e.attachDel = [];
}
} catch (e) {
log.err(e, 'cancelDel');
}
}
/**
* 还原所有被删除的元素
* @param {*} el - EditTable
* @param {*} data - EditTable data
* @returns {{idx: number, field: string, del:[id: number, url: string, value: *]}[]}
*/ function getDel(el, data) {
let R;
try {
const es = el.find('.etAttach');
for (const e of es.get()){
if (!e.attachDel) continue;
// 遍历所有需要还原的元素
const rs = [];
for (const r of e.attachDel){
if (r.att && r.parent) {
const { idx, field, value } = r;
const { id, url } = value;
rs.push({
idx,
field,
id,
url,
value: data[idx].value
});
}
}
if (rs.length) {
const map = new Map();
for (const { idx, field, id, url, value } of rs){
if (!map.has(idx)) map.set(idx, {
idx,
field,
value,
del: []
});
map.get(idx).del.push({
id,
url
});
}
R = Array.from(map.values());
}
}
} catch (e) {
log.err(e, 'cancelDel');
}
return R;
}
/**
* 从数据中获取一行数据,用于 kv 模式,动态生成 row col
* @param {string[]} cats - 分类
* @param {number[]} cols - 分类列数
* @param {number} max - 行总列数
* @param {number} catid - cat起始索引
* @returns {*[] & {catid: number}} - 每行分类
*/ function getRowCat(cats, cols, max, catid) {
/** @type {*[] & {catid: number}} */ let R;
if (!cats || !cols || !max) return;
try {
let col = 0;
let hasCol = 0;
for(let i = catid; i < cats.length; i++){
const r = cats[i];
let setCol = cols[i];
if (setCol > max) setCol = max;
col += setCol;
if (col <= max) {
if (!R) {
R = [];
R.catid = catid;
}
R.push(r);
hasCol += setCol;
} else break;
}
// 多余列
// if (R && hasCol < max) R[R.length - 1] += max - hasCol
} catch (e) {
log.err(e, 'getRowCat');
}
return R;
}
/**
* 自动换行时补全格线
* @param {*} row
*/ function attachLast(row) {
const attach = row.find('.etAttach');
const rs = [
...attach.dom.children
];
rs.forEach((r, i)=>{
r.classList.remove('attach-last');
const p = rs[i - 1];
const pre = p?.getBoundingClientRect();
const cur = r.getBoundingClientRect();
// if (prev && r.offsetTop > prev.offsetTop) {
if (cur.top > pre?.top) p.classList.add('attach-last');
if (i === rs.length - 1) r.classList.add('attach-last');
});
}
/**
* 添加数据到 data,用于点击浏览
* @param {*[]} value - 附件值
* @param {{id: number, url: string, cat: string, type: string, ext:string, name: string, abb:string}} item
*/ function addValue(value, item) {
try {
value.push(item);
value.at(-1)._idx = value.length - 1;
} catch (e) {
log.err(e, 'addValue');
}
}
/**
* 点击tr、td 浏览大图或删除附件
* @param {*} ev
*/ async function attachClick(ev) {
try {
// 如果点击的是 input 则不处理
if (ev.target.type === 'file') return;
const td = $(ev).upper('td');
const idx = td.data('idx') // EditTable data 索引
;
let value = td?.dom?.attachData;
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') // 附件数据索引,新增附件没有
;
const field = att.data('field');
const btnDel = $(ev).upper('.attach-delete');
// 删除
if (btnDel.dom) delItem(td, att, wrap, field, value[i], idx);
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 { ext, type } = v || {};
let { url } = v || {};
if (type === 'img' || type === 'video') {
if (!g.lightbox) {
// @ts-ignore
// if (!g.anime) g.anime = await import('https://cdn.jsdelivr.net/npm/animejs@4/+esm')
// @ts-ignore
// 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);
} 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');
}
}
} catch (e) {
log.err(e, 'attachClick');
}
}
/**
* 使用 lightbox 图片浏览
* @param {*[]} data - 附件数据
* @param {number} idx
* @param {string} src
*/ function showImg(data, idx, src) {
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);
}
}
/**
* 获取上传文件缩略图标
* @param {string} ext
* @param {string} [url]
* @returns {string}
*/ function 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 (e) {
log.err(e, 'getThumb');
}
return R;
}
export { fillAttach, getRowCat, getThumb, cancelDel, getDel };