This commit is contained in:
oneao committed 2026-04-25 17:28:57 +08:00
1 parent e3d765a9d4
commit 04491e14a3
977 files changed
+67280

No files matched your search

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><!-- Icon from Lucide by Lucide Contributors - https://github.com/lucide-icons/lucide/blob/main/LICENSE --><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12.586 12.586L19 19M3.688 3.037a.497.497 0 0 0-.651.651l6.5 15.999a.501.501 0 0 0 .947-.062l1.569-6.083a2 2 0 0 1 1.448-1.479l6.124-1.579a.5.5 0 0 0 .063-.947z"/></svg>

After

Width:  |  Height:  |  Size: 466 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><!-- Icon from Lucide by Lucide Contributors - https://github.com/lucide-icons/lucide/blob/main/LICENSE --><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 11v6m4-6v6m5-11v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6M3 6h18M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/></svg>

After

Width:  |  Height:  |  Size: 405 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><!-- Icon from Lucide by Lucide Contributors - https://github.com/lucide-icons/lucide/blob/main/LICENSE --><g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"><path d="M12 15V3m9 12v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><path d="m7 10l5 5l5-5"/></g></svg>

After

Width:  |  Height:  |  Size: 386 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><!-- Icon from Lucide by Lucide Contributors - https://github.com/lucide-icons/lucide/blob/main/LICENSE --><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 3v12m5-7l-5-5l-5 5m14 7v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/></svg>

After

Width:  |  Height:  |  Size: 368 B

+324
View File
@@ -0,0 +1,324 @@
/* 整体布局 */
.g3-file-container {
height: 100%;
display: flex;
flex-direction: column;
background: #ffffff;
overflow: hidden;
}
.g3-file-container .header {
display: flex;
align-items: center;
padding: 10px 14px;
background: #fafbfc;
border-bottom: 1px solid #eef0f2;
}
.g3-file-container .main {
flex: 1;
min-height: 0;
overflow: auto;
display: flex;
flex-wrap: wrap;
align-items: flex-start;
/* justify-content: center; */
}
.g3-file-container .main > * {
margin: 7px;
}
.g3-file-container .empty-tip {
flex: 1;
min-height: 0;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-size: 20px;
color: #999;
}
.g3-file-container .footer {
display: flex;
align-items: center;
justify-content: space-between;
padding: 10px 14px;
background: #fafbfc;
border-top: 1px solid #eef0f2;
}
.g3-file-container .header,
.g3-file-container .footer {
flex-shrink: 0;
}
.footer div {
display: flex;
align-items: center;
}
.footer .pending-count {
margin-right: 12px;
font-size: 14px;
}
.footer .pending-count .pending-number {
margin-left: 4px;
}
/* 全选 */
.g3-file-container .check-all {
display: flex;
align-items: center;
margin-right: 12px;
font-size: 14px;
cursor: pointer;
}
.g3-file-container .check-all input {
width: 16px;
height: 16px;
}
.g3-file-container .check-all span {
margin-left: 4px;
}
/* 按钮 */
.g3-file-container button {
font-size: 13px;
border-radius: 4px;
background: #165dff;
color: #fff;
cursor: pointer;
transition: all 0.2s ease;
border: none;
width: auto;
height: 28px;
line-height: 1;
padding: 0 15px;
display: flex;
align-items: center;
justify-content: center;
}
.g3-file-container button svg {
margin-right: 4px;
}
.g3-file-container button + button {
margin-left: 12px;
}
.g3-file-container button:hover:not(:disabled) {
filter: brightness(1.2);
}
.g3-file-container button:active:not(:disabled) {
filter: brightness(0.9);
}
.g3-file-container button:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.g3-file-container .delete-all {
background: #ff4d4f;
}
.g3-file-container .file-item {
width: calc(50% - 20px);
position: relative;
overflow: hidden;
cursor: pointer;
transition: all 0.18s ease;
border: 1px solid #eef0f2;
border-radius: 6px;
background: #fff;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04);
}
.g3-file-container .file-item:hover {
border-color: #d6e4ff;
box-shadow: 0 6px 18px rgba(0, 0, 0, 0.08);
transform: translateY(-2px);
}
.file-item .file-preview {
height: 120px;
width: 100%;
display: flex;
align-items: center;
justify-content: center;
}
.file-item .file-preview svg,
.file-item .file-preview img {
width: 100%;
height: 100%;
}
.file-item .file-checkbox {
position: absolute;
top: 10px;
left: 10px;
width: 16px;
height: 16px;
}
.file-item .file-info {
padding: 10px 10px 12px;
font-size: 12px;
color: #999;
line-height: 1.4;
display: flex;
flex-direction: column;
align-items: center;
gap: 4px;
width: 100%;
box-sizing: border-box;
}
.file-item .file-info span {
display: block;
width: 100%;
text-align: center;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.file-item .file-info .file-name {
font-size: 13px;
color: #1f2937;
font-weight: 600;
}
.file-item .file-actions {
display: flex;
align-items: center;
justify-content: flex-end;
margin-bottom: 8px;
margin-right: 8px;
}
.file-item .file-action {
width: 24px;
height: 24px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 6px;
/* 👇 稍微更明显 */
background: rgba(0, 0, 0, 0.06);
margin-left: 6px;
color: rgba(0, 0, 0, 0.6);
cursor: pointer;
transition: all 0.2s ease;
}
/* hover:明显增强 */
.file-item .file-action:hover {
background: rgba(22, 93, 255, 0.12);
color: #165dff;
}
/* active:点击反馈 */
.file-item .file-action:active {
background: rgba(22, 93, 255, 0.18);
transform: scale(0.96);
}
.file-item .file-action.is-disabled {
opacity: 0.4;
pointer-events: none;
cursor: not-allowed;
}
.g3-download-modal {
position: absolute;
inset: 0;
z-index: 9999;
}
.g3-download-mask {
position: absolute;
inset: 0;
background: rgba(0, 0, 0, 0.4);
}
.g3-download-box {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: #fff;
padding: 20px;
border-radius: 10px;
width: 260px;
text-align: center;
}
.g3-download-bar {
height: 6px;
background: #eee;
border-radius: 4px;
margin-top: 10px;
overflow: hidden;
}
.g3-download-bar-inner {
height: 100%;
width: 0%;
background: #4caf50;
transition: width 0.2s;
}
/* 文件项选中样式 */
.g3-file-container .file-item.file-item-selected {
border-color: #165dff;
box-shadow: 0 0 0 2px rgba(22, 93, 255, 0.2);
background: rgba(22, 93, 255, 0.05);
}
.g3-file-container .file-item.file-item-selected:hover {
border-color: #165dff;
box-shadow: 0 6px 18px rgba(22, 93, 255, 0.2);
transform: translateY(-2px);
}
/* 未上传标记 */
.file-item .file-pending-badge {
position: absolute;
top: 10px;
right: 10px;
background: #ff9800;
color: white;
font-size: 10px;
padding: 2px 6px;
border-radius: 10px;
z-index: 1;
}
+908
View File
@@ -0,0 +1,908 @@
(function ($) {
'use strict';
var G3File = {
files: [],
selectedFiles: new Set(),
loadScripts: function (items, callback) {
var self = this;
var remaining = items.length;
function done() {
remaining--;
if (remaining === 0) {
callback && callback();
}
}
items.forEach(function (item) {
// 已加载
if (item.check && item.check()) {
done();
return;
}
// 防重复加载
var key = item.key;
if (window[key + '_loading']) {
var timer = setInterval(function () {
if (item.check && item.check()) {
clearInterval(timer);
done();
}
}, 50);
return;
}
window[key + '_loading'] = true;
var script = document.createElement('script');
script.src = item.src;
script.onload = function () {
window[key + '_loading'] = false;
done();
};
script.onerror = function () {
window[key + '_loading'] = false;
console.error(key + ' load failed');
done();
};
document.head.appendChild(script);
});
},
// =====================
// 初始化
// =====================
init: function (containerId, options) {
var self = this;
this.$container = $('#' + containerId);
this.options = $.extend({}, this.defaults, options);
this.language = this.languages[this.options.language] || this.languages['zh-CN'];
this.files = this.options.data || [];
this.loadScripts([
{
key: 'fileSaver',
src: '/custom-file/libs/FileSaver.min.js',
check: function () {
return typeof window.saveAs === 'function';
}
},
{
key: 'jsZip',
src: '/custom-file/libs/jszip.min.js',
check: function () {
return typeof window.JSZip === 'function';
}
}
], function () {
self.render();
self.bindEvents();
self.updatePermissions(self.options.permissions);
if (self.options.data?.length) {
self.loadFiles(self.options.data);
}
});
return this;
},
// =====================
// 默认配置
// =====================
defaults: {
data: [],
onDelete: null,
onDownload: null,
onSelect: null,
onUpload: null,
language: 'zh-CN',
maxFileSize: 10 * 1024 * 1024, // 默认最大10MB
permissions: {
select: true,
upload: true,
download: true,
delete: true,
view: true
}
},
// =====================
// 多语言
// =====================
languages: {
'zh-CN': {
selectAll: '全选',
download: '批量下载',
delete: '删除',
upload: '上传',
select: '选择',
confirmDelete: '确定要删除选中的 {count} 个文件吗?',
downloading: '准备下载 {count} 个文件',
deleting: '删除中...',
name: '名称',
size: '大小',
date: '日期',
pending: '未上传',
selectFiles: '选择文件',
emptyTip1: '拖拽文件到这里 …',
emptyTip2: '支持多文件同时上传/下载',
emptyTip3: '支持粘贴图片上传',
fileSizeError: '文件大小超过限制'
},
'en-US': {
selectAll: 'Select All',
download: 'Batch Download',
delete: 'Delete',
upload: 'Upload',
select: 'Select',
confirmDelete: 'Are you sure to delete {count} selected files?',
downloading: 'Preparing to download {count} files',
deleting: 'Deleting...',
name: 'Name',
size: 'Size',
date: 'Date',
pending: 'Pending',
selectFiles: 'Select Files',
fileSizeError: 'File size exceeds limit'
}
},
// =====================
// 静态icon合计
// =====================
staticIconMap: {
download: `
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24"><g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"><path d="M12 15V3m9 12v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><path d="m7 10l5 5l5-5"/></g></svg>
`,
delete: `
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 11v6m4-6v6m5-11v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6M3 6h18M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/></svg>
`,
select: `
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12.586 12.586L19 19M3.688 3.037a.497.497 0 0 0-.651.651l6.5 15.999a.501.501 0 0 0 .947-.062l1.569-6.083a2 2 0 0 1 1.448-1.479l6.124-1.579a.5.5 0 0 0 .063-.947z"/></svg>
`,
upload: `
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 3v12m5-7l-5-5l-5 5m14 7v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/></svg>
`,
more: `
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24"><g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"><circle cx="12" cy="12" r="1"/><circle cx="19" cy="12" r="1"/><circle cx="5" cy="12" r="1"/></g></svg>
`,
pdf: `
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32"><path fill="#909090" d="m24.1 2.072l5.564 5.8v22.056H8.879V30h20.856V7.945z"/><path fill="#f4f4f4" d="M24.031 2H8.808v27.928h20.856V7.873z"/><path fill="#7a7b7c" d="M8.655 3.5h-6.39v6.827h20.1V3.5z"/><path fill="#dd2025" d="M22.472 10.211H2.395V3.379h20.077z"/><path fill="#464648" d="M9.052 4.534H7.745v4.8h1.028V7.715L9 7.728a2 2 0 0 0 .647-.117a1.4 1.4 0 0 0 .493-.291a1.2 1.2 0 0 0 .335-.454a2.1 2.1 0 0 0 .105-.908a2.2 2.2 0 0 0-.114-.644a1.17 1.17 0 0 0-.687-.65a2 2 0 0 0-.409-.104a2 2 0 0 0-.319-.026m-.189 2.294h-.089v-1.48h.193a.57.57 0 0 1 .459.181a.92.92 0 0 1 .183.558c0 .246 0 .469-.222.626a.94.94 0 0 1-.524.114m3.671-2.306c-.111 0-.219.008-.295.011L12 4.538h-.78v4.8h.918a2.7 2.7 0 0 0 1.028-.175a1.7 1.7 0 0 0 .68-.491a1.9 1.9 0 0 0 .373-.749a3.7 3.7 0 0 0 .114-.949a4.4 4.4 0 0 0-.087-1.127a1.8 1.8 0 0 0-.4-.733a1.6 1.6 0 0 0-.535-.4a2.4 2.4 0 0 0-.549-.178a1.3 1.3 0 0 0-.228-.017m-.182 3.937h-.1V5.392h.013a1.06 1.06 0 0 1 .6.107a1.2 1.2 0 0 1 .324.4a1.3 1.3 0 0 1 .142.526c.009.22 0 .4 0 .549a3 3 0 0 1-.033.513a1.8 1.8 0 0 1-.169.5a1.1 1.1 0 0 1-.363.36a.67.67 0 0 1-.416.106m5.08-3.915H15v4.8h1.028V7.434h1.3v-.892h-1.3V5.43h1.4v-.892"/><path fill="#dd2025" d="M21.781 20.255s3.188-.578 3.188.511s-1.975.646-3.188-.511m-2.357.083a7.5 7.5 0 0 0-1.473.489l.4-.9c.4-.9.815-2.127.815-2.127a14 14 0 0 0 1.658 2.252a13 13 0 0 0-1.4.288Zm-1.262-6.5c0-.949.307-1.208.546-1.208s.508.115.517.939a10.8 10.8 0 0 1-.517 2.434a4.4 4.4 0 0 1-.547-2.162Zm-4.649 10.516c-.978-.585 2.051-2.386 2.6-2.444c-.003.001-1.576 3.056-2.6 2.444M25.9 20.895c-.01-.1-.1-1.207-2.07-1.16a14 14 0 0 0-2.453.173a12.5 12.5 0 0 1-2.012-2.655a11.8 11.8 0 0 0 .623-3.1c-.029-1.2-.316-1.888-1.236-1.878s-1.054.815-.933 2.013a9.3 9.3 0 0 0 .665 2.338s-.425 1.323-.987 2.639s-.946 2.006-.946 2.006a9.6 9.6 0 0 0-2.725 1.4c-.824.767-1.159 1.356-.725 1.945c.374.508 1.683.623 2.853-.91a23 23 0 0 0 1.7-2.492s1.784-.489 2.339-.623s1.226-.24 1.226-.24s1.629 1.639 3.2 1.581s1.495-.939 1.485-1.035"/><path fill="#909090" d="M23.954 2.077V7.95h5.633z"/><path fill="#f4f4f4" d="M24.031 2v5.873h5.633z"/><path fill="#fff" d="M8.975 4.457H7.668v4.8H8.7V7.639l.228.013a2 2 0 0 0 .647-.117a1.4 1.4 0 0 0 .493-.291a1.2 1.2 0 0 0 .332-.454a2.1 2.1 0 0 0 .105-.908a2.2 2.2 0 0 0-.114-.644a1.17 1.17 0 0 0-.687-.65a2 2 0 0 0-.411-.105a2 2 0 0 0-.319-.026m-.189 2.294h-.089v-1.48h.194a.57.57 0 0 1 .459.181a.92.92 0 0 1 .183.558c0 .246 0 .469-.222.626a.94.94 0 0 1-.524.114m3.67-2.306c-.111 0-.219.008-.295.011l-.235.006h-.78v4.8h.918a2.7 2.7 0 0 0 1.028-.175a1.7 1.7 0 0 0 .68-.491a1.9 1.9 0 0 0 .373-.749a3.7 3.7 0 0 0 .114-.949a4.4 4.4 0 0 0-.087-1.127a1.8 1.8 0 0 0-.4-.733a1.6 1.6 0 0 0-.535-.4a2.4 2.4 0 0 0-.549-.178a1.3 1.3 0 0 0-.228-.017m-.182 3.937h-.1V5.315h.013a1.06 1.06 0 0 1 .6.107a1.2 1.2 0 0 1 .324.4a1.3 1.3 0 0 1 .142.526c.009.22 0 .4 0 .549a3 3 0 0 1-.033.513a1.8 1.8 0 0 1-.169.5a1.1 1.1 0 0 1-.363.36a.67.67 0 0 1-.416.106m5.077-3.915h-2.43v4.8h1.028V7.357h1.3v-.892h-1.3V5.353h1.4v-.892"/></svg>
`,
video: `
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32"><path fill="#e75749" d="M4.5 2.375A2.56 2.56 0 0 1 5.861 2h20.366a2.545 2.545 0 0 1 2.423 1.951a3.4 3.4 0 0 1 .072.862v11.176h-1.573a2 2 0 0 0-.22.022a5 5 0 0 0-.581-.011c-.3.007-.61-.014-.914.014a2.3 2.3 0 0 0-.4-.02h-4.739c-.2 0-.392.008-.587-.01l.1-.065l-6.353-3.813v3.889c-2.213-.005-4.427 0-6.64 0a2 2 0 0 0-.251.021c-.212-.032-.427-.016-.642-.016c-.284.006-.57-.015-.853.015a2 2 0 0 0-.253-.022H3.275c.006-3.635 0-7.27 0-10.9a4.8 4.8 0 0 1 .071-1.131A2.54 2.54 0 0 1 4.5 2.375m.54 2.188a.64.64 0 0 0-.489.606v1.316a.64.64 0 0 0 .627.6h1.309a.64.64 0 0 0 .6-.608c.005-.426 0-.853 0-1.279a.64.64 0 0 0-.329-.575a.7.7 0 0 0-.358-.078H5.264a1 1 0 0 0-.224.018m20.341 0a.64.64 0 0 0-.474.607v1.306a.64.64 0 0 0 .6.608c.436.005.872 0 1.308 0a.64.64 0 0 0 .627-.606c.006-.438 0-.876 0-1.314a.64.64 0 0 0-.594-.621c-.348-.009-.7 0-1.045 0a2 2 0 0 0-.422.024ZM5.042 8.38a.64.64 0 0 0-.452.4a1.1 1.1 0 0 0-.041.386v1.135a.637.637 0 0 0 .6.6c.4.008.795 0 1.192 0a.8.8 0 0 0 .395-.065a.64.64 0 0 0 .36-.574V8.983a.64.64 0 0 0-.6-.624c-.338-.008-.677 0-1.016 0a2.4 2.4 0 0 0-.438.021m20.338 0a.64.64 0 0 0-.433.4a1.1 1.1 0 0 0-.041.387v1.131a.64.64 0 0 0 .608.607c.406.006.812 0 1.218 0a.74.74 0 0 0 .38-.078a.64.64 0 0 0 .335-.558V8.985a.637.637 0 0 0-.593-.622c-.349-.009-.7 0-1.047 0a2 2 0 0 0-.428.022ZM5.041 12.2a.64.64 0 0 0-.491.608c0 .438-.005.877 0 1.315a.64.64 0 0 0 .627.6h1.3a.64.64 0 0 0 .608-.607v-1.307a.64.64 0 0 0-.605-.626c-.348-.007-.7 0-1.045 0a2 2 0 0 0-.394.017m20.335.006a.64.64 0 0 0-.43.394a1.1 1.1 0 0 0-.041.386v1.131a.64.64 0 0 0 .607.608c.435.005.87 0 1.306 0a.64.64 0 0 0 .628-.605c.006-.438 0-.876 0-1.313a.637.637 0 0 0-.592-.622c-.349-.01-.7 0-1.046 0a2 2 0 0 0-.431.015Z"/><path fill="#fff" d="M13.458 12.1q3.176 1.908 6.353 3.813l-.1.065q-3.127 1.872-6.251 3.748V15.99q-.003-1.941-.002-3.89"/><path fill="#c0392b" d="M3.275 15.988h1.541a2 2 0 0 1 .253.022a.65.65 0 0 0-.409.273a.9.9 0 0 0-.111.518v1.045a.686.686 0 0 0 .685.689h1.192a.68.68 0 0 0 .657-.569c.008-.447 0-.9 0-1.343a.63.63 0 0 0-.521-.613a2 2 0 0 1 .251-.021h6.64v3.736q3.127-1.871 6.251-3.748c.2.018.391.007.587.01h4.741a2.3 2.3 0 0 1 .4.02a.64.64 0 0 0-.475.385a1 1 0 0 0-.045.407v1.014a.677.677 0 0 0 .691.719h1.16a.684.684 0 0 0 .686-.658v-.984a1.07 1.07 0 0 0-.1-.589a.64.64 0 0 0-.42-.292a2 2 0 0 1 .22-.022h1.573c.011 1.721 0 3.443 0 5.165v6.293a2.57 2.57 0 0 1-.612 1.651a2.54 2.54 0 0 1-1.563.868a8 8 0 0 1-.981.033H6.724a9 9 0 0 1-1.368-.048a2.56 2.56 0 0 1-2-1.846a2.8 2.8 0 0 1-.09-.746V16.891c.007-.3-.002-.602.009-.903m1.795 3.843a.6.6 0 0 0-.386.234a.76.76 0 0 0-.134.464v1.162a.685.685 0 0 0 .686.658h1.283a.687.687 0 0 0 .568-.716v-1.191a.62.62 0 0 0-.2-.458a.7.7 0 0 0-.487-.17H5.264a1 1 0 0 0-.194.017m20.366 0a.62.62 0 0 0-.522.58v1.222a.676.676 0 0 0 .69.718c.428 0 .857.005 1.285 0a.7.7 0 0 0 .562-.684v-1.013a.9.9 0 0 0-.123-.568a.66.66 0 0 0-.532-.267h-.954a2.3 2.3 0 0 0-.406.011ZM5.071 23.648a.6.6 0 0 0-.4.249a.77.77 0 0 0-.124.448v1.132a.7.7 0 0 0 .6.688h1.376a.687.687 0 0 0 .567-.717v-1.189a.62.62 0 0 0-.2-.459a.7.7 0 0 0-.484-.17H5.265a1 1 0 0 0-.194.018m20.362 0a.62.62 0 0 0-.492.427a1 1 0 0 0-.029.33V25.6a.68.68 0 0 0 .656.567h1.193a.69.69 0 0 0 .686-.719v-1.164a.66.66 0 0 0-.286-.545a1.46 1.46 0 0 0-.787-.11c-.312.01-.629-.02-.94.018Z"/></svg>
`,
default_file: `
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 16 16"><g fill="none"><path fill="url(#SVGsXCcfeOf)" d="M8.004 1L9.5 4.5L13 6l-.008 7.5a1.5 1.5 0 0 1-1.5 1.5H4.5A1.5 1.5 0 0 1 3 13.5v-11A1.5 1.5 0 0 1 4.5 1z"/><path fill="url(#SVGUVHt30XG)" fill-opacity=".5" d="M8.004 1L9.5 4.5L13 6l-.008 7.5a1.5 1.5 0 0 1-1.5 1.5H4.5A1.5 1.5 0 0 1 3 13.5v-11A1.5 1.5 0 0 1 4.5 1z"/><path fill="url(#SVG176kqd6n)" d="M8 4.5V1l5 5H9.5A1.5 1.5 0 0 1 8 4.5"/><defs><linearGradient id="SVGsXCcfeOf" x1="9.994" x2="11.264" y1="1" y2="12.781" gradientUnits="userSpaceOnUse"><stop stop-color="#6CE0FF"/><stop offset="1" stop-color="#4894FE"/></linearGradient><linearGradient id="SVG176kqd6n" x1="10.492" x2="9.242" y1="3.083" y2="5.167" gradientUnits="userSpaceOnUse"><stop stop-color="#9FF0F9"/><stop offset="1" stop-color="#B3E0FF"/></linearGradient><radialGradient id="SVGUVHt30XG" cx="0" cy="0" r="1" gradientTransform="rotate(130.372 6.372 3.818)scale(8.35524 4.87457)" gradientUnits="userSpaceOnUse"><stop offset=".362" stop-color="#4A43CB"/><stop offset="1" stop-color="#4A43CB" stop-opacity="0"/></radialGradient></defs></g></svg>
`
},
// =====================
// 生成唯一ID
// =====================
generateUniqueId: function () {
return 'file_' + Math.random().toString(36).slice(2) + Date.now();
},
// =====================
// 获取文件图标
// =====================
getFileIcon: function (filename) {
var ext = filename.split('.').pop().toLowerCase();
return this.staticIconMap[ext] || this.staticIconMap['default_file'];
},
// =====================
// 更新语言
// =====================
updateLanguage: function (language) {
this.language = this.languages[language] || this.languages['zh-CN'];
this.render();
return this;
},
// =====================
// 渲染
// =====================
render: function () {
const html = `
<div class="g3-file-container">
<div class="header">
<div class="check-all">
<input type="checkbox" class="check-all-checkbox" style="cursor: pointer;" />
<span>${this.language.selectAll}</span>
</div>
<button class="download-all" disabled>
${this.staticIconMap.download}
${this.language.download}
</button>
<button class="delete-all" disabled>
${this.staticIconMap.delete}
${this.language.delete}
</button>
</div>
<div class="main" style="display: none;">main</div>
<div class="empty-tip">
<span>${this.language.emptyTip1}</span>
<span>${this.language.emptyTip2}</span>
<span>${this.language.emptyTip3}</span>
</div>
<div class="footer">
<div class="pending-count">
<span>${this.language.pending}</span>
<span class="pending-number">0</span>
</div>
<div>
<button class="select" disabled>
${this.staticIconMap.select}
${this.language.select}
</button>
<button class="upload" disabled>
${this.staticIconMap.upload}
${this.language.upload}
</button>
</div>
<input type="file" class="file-input" style="display: none;" multiple />
</div>
</div>
`;
this.$container.html(html);
this.$fileList = this.$container.find('.main');
this.$emptyTip = this.$container.find('.empty-tip');
this.$checkAll = this.$container.find('.check-all-checkbox');
this.$btnDownload = this.$container.find('.download-all');
this.$btnDelete = this.$container.find('.delete-all');
this.$btnUpload = this.$container.find('.upload');
this.$btnSelect = this.$container.find('.select');
this.$fileInput = this.$container.find('.file-input');
this.$pendingCount = this.$container.find('.pending-number');
return this;
},
bindEvents: function () {
var self = this;
// 选择
this.$checkAll.off('change').on('change', function () {
var checked = $(this).is(':checked');
self.selectedFiles.clear();
if (checked) {
self.files.forEach(function (file) {
self.selectedFiles.add(file._id);
});
}
self.$fileList.find('.file-checkbox').prop('checked', checked);
self.updateToolbarState();
});
// 批量下载
this.$btnDownload.off('click').on('click', function () {
var idSet = self.selectedFiles || new Set();
var files = self.files.filter(function (item) {
return idSet.has(item._id);
});
if (typeof self.options?.onDownloadBatch === 'function') {
self.options.onDownloadBatch(files);
return;
}
self.internalDownloadBatch(files);
});
// 批量删除
this.$btnDelete.off('click').on('click', function () {
var idSet = self.selectedFiles || new Set();
if (idSet.size === 0) return;
var deleteFiles = self.files.filter(function (item) {
return idSet.has(item._id);
});
// =====================
// 1. 优先走外部回调(如果你要接后端)
// =====================
if (typeof self.options?.onDeleteBatch === 'function') {
self.options.onDeleteBatch(deleteFiles, function (success) {
if (!success) return;
// 成功后再清理本地
self.internalDeleteBatch(deleteFiles);
});
return;
}
// =====================
// 2. 没有外部逻辑 -> 直接本地删除
// =====================
self.internalDeleteBatch(deleteFiles);
});
// =====================
// 选择按钮点击事件(选择本地文件)
// =====================
this.$btnSelect.off('click').on('click', function () {
self.$fileInput.click();
});
// =====================
// 上传按钮点击事件(交给外部处理)
// =====================
this.$btnUpload.off('click').on('click', function () {
// 获取所有未上传的文件
const pendingFiles = self.files.filter(file => !file.uploaded);
if (typeof self.options.onUpload === 'function') {
self.options.onUpload(pendingFiles);
}
});
// =====================
// 文件选择事件
// =====================
this.$fileInput.off('change').on('change', function (e) {
var files = e.target.files;
if (!files || files.length === 0) return;
var maxSize = self.options.maxFileSize || 10 * 1024 * 1024;
var validFiles = [];
var invalidFiles = [];
for (var i = 0; i < files.length; i++) {
var file = files[i];
if (file.size > maxSize) {
invalidFiles.push(file);
} else {
validFiles.push(file);
}
}
if (invalidFiles.length > 0) {
var errorMsg = self.language.fileSizeError || '文件大小超过限制';
alert(errorMsg + ': ' + invalidFiles.map(f => f.name).join(', '));
}
if (validFiles.length > 0) {
// 在本地临时插入文件
var tempFiles = validFiles.map(function (file) {
// 为图片文件创建本地临时连接
let url = '';
if (self.isImageFile(file.name)) {
url = URL.createObjectURL(file);
}
return {
_id: self.generateUniqueId(),
name: file.name,
size: file.size,
uploadDate: new Date().toISOString(),
uploaded: false, // 标记为未上传
url: url, // 图片文件使用本地临时连接,其他文件为空
file: file // 保存原始文件对象
};
});
// 添加到文件列表
self.files = self.files.concat(tempFiles);
self.renderFileList();
self.updateToolbarState();
if (typeof self.options.onSelect === 'function') {
self.options.onSelect(validFiles);
}
}
// 重置文件输入框,以便可以重复选择相同的文件
self.$fileInput.val('');
});
},
// =====================
// 文件判断
// =====================
isImageFile: function (filename) {
if (!filename || typeof filename !== 'string') return false;
const ext = filename.slice(filename.lastIndexOf('.') + 1).toLowerCase();
const imageExts = new Set(['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp']);
return imageExts.has(ext);
},
// =====================
// 更新工具栏状态
// =====================
updateToolbarState: function () {
var hasSelection = this.selectedFiles.size > 0;
this.$btnDownload.prop('disabled', !hasSelection);
this.$btnDelete.prop('disabled', !hasSelection);
var allSelected =
this.files.length > 0 &&
this.selectedFiles.size === this.files.length;
this.$checkAll.prop('checked', allSelected);
// 更新未上传文件数量
var pendingCount = this.files.filter(file => !file.uploaded).length;
this.$pendingCount.text(pendingCount);
var self = this;
this.$fileList.find('.file-item').each(function () {
var id = $(this).data('id');
var isSelected = self.selectedFiles.has(id);
$(this).find('.file-checkbox')
.prop('checked', isSelected);
// 添加或移除选中样式
if (isSelected) {
$(this).addClass('file-item-selected');
} else {
$(this).removeClass('file-item-selected');
}
});
},
// =====================
// 加载文件
// =====================
loadFiles: function (files) {
var self = this;
this.files = files.map(function (file) {
return Object.assign({}, file, {
_id: self.generateUniqueId(),
uploaded: true
});
});
this.selectedFiles.clear();
this.renderFileList();
this.updateToolbarState();
},
// =====================
// 渲染文件列表
// =====================
renderFileList: function () {
var self = this;
if (this.files.length === 0) {
this.$fileList.hide();
this.$emptyTip.show();
return;
}
this.$fileList.show();
this.$emptyTip.hide();
var html = '';
$.each(this.files, function (_, file) {
html += self.createFileItem(file);
});
this.$fileList.html(html);
this.bindFileItemEvents();
},
// =====================
// 格式化文件大小
// =====================
formatFileSize: function (bytes) {
if (bytes === null || bytes === undefined || isNaN(bytes) || bytes <= 0) {
return '-';
}
var k = 1024;
var sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
var i = Math.floor(Math.log(bytes) / Math.log(k));
i = Math.max(0, Math.min(i, sizes.length - 1));
return (bytes / Math.pow(k, i)).toFixed(2) + ' ' + sizes[i];
},
// =====================
// 格式化日期
// =====================
formatDate: function (dateStr) {
if (!dateStr) return '-';
var date = new Date(dateStr);
if (isNaN(date.getTime())) {
return '-';
}
var year = date.getFullYear();
var month = ('0' + (date.getMonth() + 1)).slice(-2);
var day = ('0' + date.getDate()).slice(-2);
var hours = ('0' + date.getHours()).slice(-2);
var minutes = ('0' + date.getMinutes()).slice(-2);
var seconds = ('0' + date.getSeconds()).slice(-2);
return year + '-' + month + '-' + day + ' ' + hours + ':' + minutes + ':' + seconds;
},
// =====================
// 创建文件项
// =====================
createFileItem: function (file) {
const perm = this.options.permissions || {};
const downloadDisabled = !perm.download;
const deleteDisabled = !perm.delete;
let content = '';
if (this.isImageFile(file.name)) {
content = `<img src="${file.url}" alt="${file.name}" class="file-img" />`;
} else {
content = this.getFileIcon(file.name);
}
// 未上传标记
const pendingBadge = file.uploaded ? '' : `
<div class="file-pending-badge">
${this.language.pending}
</div>
`;
return `
<div class="file-item" data-id="${file._id}">
<input type="checkbox" class="file-checkbox" />
<div class="file-preview">${content}</div>
${pendingBadge}
<div class="file-info">
<span class="file-name">${file.name}</span>
<span>${this.formatFileSize(file.size)}</span>
<span>${this.formatDate(file.uploadDate)}</span>
</div>
<div class="file-actions">
<div class="file-action download ${downloadDisabled ? 'is-disabled' : ''}">
${this.staticIconMap.download}
</div>
<div class="file-action delete ${deleteDisabled ? 'is-disabled' : ''}">
${this.staticIconMap.delete}
</div>
<div class="file-action more">
${this.staticIconMap.more}
</div>
</div>
</div>
`;
},
// =====================
// 绑定文件项事件
// =====================
bindFileItemEvents: function () {
var self = this;
// =====================
// 文件item点击事件
// =====================
this.$fileList.off('click', '.file-item')
.on('click', '.file-item', function (e) {
var $checkbox = $(this).find('.file-checkbox');
$checkbox.prop('checked', !$checkbox.is(':checked'));
$checkbox.trigger('change');
});
this.$fileList.off('click', '.download')
.on('click', '.download', function (e) {
e.preventDefault();
e.stopPropagation();
var id = $(this).closest('.file-item').data('id');
var file = self.files.find(f => f._id === id);
if (!file) return;
if (typeof self.options.onDownload === 'function') {
self.options.onDownload([file]);
return;
}
self.internalDownload(file);
});
this.$fileList.off('change', '.file-checkbox')
.on('change', '.file-checkbox', function () {
var id = $(this).closest('.file-item').data('id');
if ($(this).is(':checked')) {
self.selectedFiles.add(id);
} else {
self.selectedFiles.delete(id);
}
self.updateToolbarState();
});
this.$fileList.off('click', '.delete')
.on('click', '.delete', function (e) {
e.preventDefault();
e.stopPropagation();
var id = $(this).closest('.file-item').data('id');
var file = self.files.find(f => f._id === id);
if (!file) return;
if (typeof self.options.onDelete === 'function') {
self.options.onDelete([file]);
return;
}
self.internalDelete(file);
});
// =====================
// 更多按钮点击事件(阻止冒泡)
// =====================
this.$fileList.off('click', '.more')
.on('click', '.more', function (e) {
e.preventDefault();
e.stopPropagation();
});
},
getUniqueFileName: function (fileName, files) {
// 保存原始文件名
const originalFileName = fileName;
let uniqueFileName = fileName;
let counter = 1;
// 如果文件名已经存在,则在文件名后加上 (1), (2), ...
while (files.has(uniqueFileName)) {
// 基于原始文件名为基础来添加计数器
const fileNameWithoutExtension = originalFileName.replace(/\.[^/.]+$/, "");
const extension = originalFileName.slice(originalFileName.lastIndexOf("."));
uniqueFileName = `${fileNameWithoutExtension} (${counter})${extension}`;
counter++;
}
return uniqueFileName;
},
getFileBlob: async function (url) {
if (!url) {
throw new Error('无效的文件URL');
}
const res = await fetch(url);
if (!res.ok) {
throw new Error('网络请求失败,状态码:' + res.status);
}
// ⭐ 比 blob 更轻一点(后续 zip 更快)
const buffer = await res.arrayBuffer();
return buffer;
},
// =====================
// 内部下载文件
// =====================
internalDownload: async function (file) {
if (!file) {
console.warn('下载失败:文件对象无效', file);
return;
}
console.log('开始下载文件:', file.name);
try {
let blob;
if (file.uploaded && file.url) {
// 已上传的文件,从URL下载
const buffer = await this.getFileBlob(file.url);
blob = new Blob([buffer]);
} else if (file.file) {
// 未上传的文件,使用本地文件对象
blob = file.file;
} else {
console.warn('下载失败:文件没有URL或本地文件对象', file);
return;
}
saveAs(blob, file.name || '未命名文件');
} catch (err) {
console.error('文件下载失败:', err);
}
},
// =====================
// 内部下载所有文件
// =====================
internalDownloadBatch: async function (files, callback) {
if (!files || files.length === 0) return;
const zip = new JSZip();
const addedFiles = new Set();
const total = files.length;
let current = 0;
let failedCount = 0;
const concurrency = 3;
const downloadOne = async (item) => {
try {
let content;
if (item.uploaded && item.url) {
// 已上传的文件,从URL下载
content = await this.getFileBlob(item.url);
} else if (item.file) {
// 未上传的文件,使用本地文件对象
content = await item.file.arrayBuffer();
} else {
console.warn('跳过失败文件:', item.name, '没有URL或本地文件对象');
failedCount++;
return;
}
const uniqueFileName = this.getUniqueFileName(item.name, addedFiles);
zip.file(uniqueFileName, content);
addedFiles.add(uniqueFileName);
} catch (err) {
console.warn('跳过失败文件:', item.name, err);
failedCount++;
} finally {
current++;
}
};
const runner = async () => {
while (files.length) {
const item = files.shift();
await downloadOne(item);
}
};
try {
await Promise.all(
Array.from({ length: concurrency }, runner)
);
const blob = await zip.generateAsync({ type: "blob" });
const now = new Date();
const pad = n => n < 10 ? "0" + n : n;
const fileName =
now.getFullYear() +
pad(now.getMonth() + 1) +
pad(now.getDate()) +
pad(now.getHours()) +
pad(now.getMinutes());
saveAs(blob, fileName + ".zip");
console.log(
`下载完成:${total - failedCount}/${total},失败:${failedCount}`
);
callback && callback();
} catch (err) {
console.error("Batch download failed:", err);
callback && callback();
}
},
// =====================
// 内部删除文件
// =====================
internalDelete: function (file) {
if (!file) return;
// 清理本地临时连接
if (file.url && file.uploaded === false) {
try {
URL.revokeObjectURL(file.url);
} catch (e) {
console.warn('Failed to revoke object URL:', e);
}
}
this.files = this.files.filter(f => f._id !== file._id);
this.selectedFiles.delete(file._id);
this.updateToolbarState();
this.renderFileList();
},
// =====================
// 内部删除所有文件
// =====================
internalDeleteBatch: function (files) {
if (!files || files.length === 0) return;
files.forEach(file => this.internalDelete(file));
this.updateToolbarState();
this.renderFileList();
},
// =====================
// 更新权限
// =====================
updatePermissions: function (permissions) {
if (!permissions) return;
this.options.permissions = permissions;
this.$btnSelect?.prop('disabled', !permissions.select);
this.$btnUpload?.prop('disabled', !permissions.upload);
this.$btnDownload?.prop('disabled', !permissions.download);
this.$btnDelete?.prop('disabled', !permissions.delete);
this.$fileList.find('.file-action.download')
.toggleClass('is-disabled', !permissions.download);
this.$fileList.find('.file-action.delete')
.toggleClass('is-disabled', !permissions.delete);
},
};
// ==========================
// jQuery 插件封装
// ==========================
$.fn.G3File = function (options) {
var args = Array.prototype.slice.call(arguments, 1);
var instance;
this.each(function () {
var $this = $(this);
instance = $this.data('G3File');
// ========= 初始化 =========
if (!instance) {
instance = Object.create(G3File);
instance.init(this.id, options);
$this.data('G3File', instance);
// ========= 方法调用 =========
} else if (typeof options === 'string') {
var method = options;
if (instance[method]) {
instance[method].apply(instance, args);
}
}
});
// ⭐关键:返回 instance(支持 file.xxx)
return instance || this;
};
})(jQuery);
+3
View File
@@ -0,0 +1,3 @@
(function(a,b){if("function"==typeof define&&define.amd)define([],b);else if("undefined"!=typeof exports)b();else{b(),a.FileSaver={exports:{}}.exports}})(this,function(){"use strict";function b(a,b){return"undefined"==typeof b?b={autoBom:!1}:"object"!=typeof b&&(console.warn("Deprecated: Expected third argument to be a object"),b={autoBom:!b}),b.autoBom&&/^\s*(?:text\/\S*|application\/xml|\S*\/\S*\+xml)\s*;.*charset\s*=\s*utf-8/i.test(a.type)?new Blob(["\uFEFF",a],{type:a.type}):a}function c(a,b,c){var d=new XMLHttpRequest;d.open("GET",a),d.responseType="blob",d.onload=function(){g(d.response,b,c)},d.onerror=function(){console.error("could not download file")},d.send()}function d(a){var b=new XMLHttpRequest;b.open("HEAD",a,!1);try{b.send()}catch(a){}return 200<=b.status&&299>=b.status}function e(a){try{a.dispatchEvent(new MouseEvent("click"))}catch(c){var b=document.createEvent("MouseEvents");b.initMouseEvent("click",!0,!0,window,0,0,0,80,20,!1,!1,!1,!1,0,null),a.dispatchEvent(b)}}var f="object"==typeof window&&window.window===window?window:"object"==typeof self&&self.self===self?self:"object"==typeof global&&global.global===global?global:void 0,a=/Macintosh/.test(navigator.userAgent)&&/AppleWebKit/.test(navigator.userAgent)&&!/Safari/.test(navigator.userAgent),g=f.saveAs||("object"!=typeof window||window!==f?function(){}:"download"in HTMLAnchorElement.prototype&&!a?function(b,g,h){var i=f.URL||f.webkitURL,j=document.createElement("a");g=g||b.name||"download",j.download=g,j.rel="noopener","string"==typeof b?(j.href=b,j.origin===location.origin?e(j):d(j.href)?c(b,g,h):e(j,j.target="_blank")):(j.href=i.createObjectURL(b),setTimeout(function(){i.revokeObjectURL(j.href)},4E4),setTimeout(function(){e(j)},0))}:"msSaveOrOpenBlob"in navigator?function(f,g,h){if(g=g||f.name||"download","string"!=typeof f)navigator.msSaveOrOpenBlob(b(f,h),g);else if(d(f))c(f,g,h);else{var i=document.createElement("a");i.href=f,i.target="_blank",setTimeout(function(){e(i)})}}:function(b,d,e,g){if(g=g||open("","_blank"),g&&(g.document.title=g.document.body.innerText="downloading..."),"string"==typeof b)return c(b,d,e);var h="application/octet-stream"===b.type,i=/constructor/i.test(f.HTMLElement)||f.safari,j=/CriOS\/[\d]+/.test(navigator.userAgent);if((j||h&&i||a)&&"undefined"!=typeof FileReader){var k=new FileReader;k.onloadend=function(){var a=k.result;a=j?a:a.replace(/^data:[^;]*;/,"data:attachment/file;"),g?g.location.href=a:location=a,g=null},k.readAsDataURL(b)}else{var l=f.URL||f.webkitURL,m=l.createObjectURL(b);g?g.location=m:location.href=m,g=null,setTimeout(function(){l.revokeObjectURL(m)},4E4)}});f.saveAs=g.saveAs=g,"undefined"!=typeof module&&(module.exports=g)});
//# sourceMappingURL=FileSaver.min.js.map
+13
View File
@@ -0,0 +1,13 @@
/*!
JSZip v3.10.1 - A JavaScript class for generating and reading zip files
<http://stuartk.com/jszip>
(c) 2009-2016 Stuart Knightley <stuart [at] stuartk.com>
Dual licenced under the MIT license or GPLv3. See https://raw.github.com/Stuk/jszip/main/LICENSE.markdown.
JSZip uses the library pako released under the MIT license :
https://github.com/nodeca/pako/blob/main/LICENSE
*/
!function(e){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=e();else if("function"==typeof define&&define.amd)define([],e);else{("undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:this).JSZip=e()}}(function(){return function s(a,o,h){function u(r,e){if(!o[r]){if(!a[r]){var t="function"==typeof require&&require;if(!e&&t)return t(r,!0);if(l)return l(r,!0);var n=new Error("Cannot find module '"+r+"'");throw n.code="MODULE_NOT_FOUND",n}var i=o[r]={exports:{}};a[r][0].call(i.exports,function(e){var t=a[r][1][e];return u(t||e)},i,i.exports,s,a,o,h)}return o[r].exports}for(var l="function"==typeof require&&require,e=0;e<h.length;e++)u(h[e]);return u}({1:[function(e,t,r){"use strict";var d=e("./utils"),c=e("./support"),p="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";r.encode=function(e){for(var t,r,n,i,s,a,o,h=[],u=0,l=e.length,f=l,c="string"!==d.getTypeOf(e);u<e.length;)f=l-u,n=c?(t=e[u++],r=u<l?e[u++]:0,u<l?e[u++]:0):(t=e.charCodeAt(u++),r=u<l?e.charCodeAt(u++):0,u<l?e.charCodeAt(u++):0),i=t>>2,s=(3&t)<<4|r>>4,a=1<f?(15&r)<<2|n>>6:64,o=2<f?63&n:64,h.push(p.charAt(i)+p.charAt(s)+p.charAt(a)+p.charAt(o));return h.join("")},r.decode=function(e){var t,r,n,i,s,a,o=0,h=0,u="data:";if(e.substr(0,u.length)===u)throw new Error("Invalid base64 input, it looks like a data url.");var l,f=3*(e=e.replace(/[^A-Za-z0-9+/=]/g,"")).length/4;if(e.charAt(e.length-1)===p.charAt(64)&&f--,e.charAt(e.length-2)===p.charAt(64)&&f--,f%1!=0)throw new Error("Invalid base64 input, bad content length.");for(l=c.uint8array?new Uint8Array(0|f):new Array(0|f);o<e.length;)t=p.indexOf(e.charAt(o++))<<2|(i=p.indexOf(e.charAt(o++)))>>4,r=(15&i)<<4|(s=p.indexOf(e.charAt(o++)))>>2,n=(3&s)<<6|(a=p.indexOf(e.charAt(o++))),l[h++]=t,64!==s&&(l[h++]=r),64!==a&&(l[h++]=n);return l}},{"./support":30,"./utils":32}],2:[function(e,t,r){"use strict";var n=e("./external"),i=e("./stream/DataWorker"),s=e("./stream/Crc32Probe"),a=e("./stream/DataLengthProbe");function o(e,t,r,n,i){this.compressedSize=e,this.uncompressedSize=t,this.crc32=r,this.compression=n,this.compressedContent=i}o.prototype={getContentWorker:function(){var e=new i(n.Promise.resolve(this.compressedContent)).pipe(this.compression.uncompressWorker()).pipe(new a("data_length")),t=this;return e.on("end",function(){if(this.streamInfo.data_length!==t.uncompressedSize)throw new Error("Bug : uncompressed data size mismatch")}),e},getCompressedWorker:function(){return new i(n.Promise.resolve(this.compressedContent)).withStreamInfo("compressedSize",this.compressedSize).withStreamInfo("uncompressedSize",this.uncompressedSize).withStreamInfo("crc32",this.crc32).withStreamInfo("compression",this.compression)}},o.createWorkerFrom=function(e,t,r){return e.pipe(new s).pipe(new a("uncompressedSize")).pipe(t.compressWorker(r)).pipe(new a("compressedSize")).withStreamInfo("compression",t)},t.exports=o},{"./external":6,"./stream/Crc32Probe":25,"./stream/DataLengthProbe":26,"./stream/DataWorker":27}],3:[function(e,t,r){"use strict";var n=e("./stream/GenericWorker");r.STORE={magic:"\0\0",compressWorker:function(){return new n("STORE compression")},uncompressWorker:function(){return new n("STORE decompression")}},r.DEFLATE=e("./flate")},{"./flate":7,"./stream/GenericWorker":28}],4:[function(e,t,r){"use strict";var n=e("./utils");var o=function(){for(var e,t=[],r=0;r<256;r++){e=r;for(var n=0;n<8;n++)e=1&e?3988292384^e>>>1:e>>>1;t[r]=e}return t}();t.exports=function(e,t){return void 0!==e&&e.length?"string"!==n.getTypeOf(e)?function(e,t,r,n){var i=o,s=n+r;e^=-1;for(var a=n;a<s;a++)e=e>>>8^i[255&(e^t[a])];return-1^e}(0|t,e,e.length,0):function(e,t,r,n){var i=o,s=n+r;e^=-1;for(var a=n;a<s;a++)e=e>>>8^i[255&(e^t.charCodeAt(a))];return-1^e}(0|t,e,e.length,0):0}},{"./utils":32}],5:[function(e,t,r){"use strict";r.base64=!1,r.binary=!1,r.dir=!1,r.createFolders=!0,r.date=null,r.compression=null,r.compressionOptions=null,r.comment=null,r.unixPermissions=null,r.dosPermissions=null},{}],6:[function(e,t,r){"use strict";var n=null;n="undefined"!=typeof Promise?Promise:e("lie"),t.exports={Promise:n}},{lie:37}],7:[function(e,t,r){"use strict";var n="undefined"!=typeof Uint8Array&&"undefined"!=typeof Uint16Array&&"undefined"!=typeof Uint32Array,i=e("pako"),s=e("./utils"),a=e("./stream/GenericWorker"),o=n?"uint8array":"array";function h(e,t){a.call(this,"FlateWorker/"+e),this._pako=null,this._pakoAction=e,this._pakoOptions=t,this.meta={}}r.magic="\b\0",s.inherits(h,a),h.prototype.processChunk=function(e){this.meta=e.meta,null===this._pako&&this._createPako(),this._pako.push(s.transformTo(o,e.data),!1)},h.prototype.flush=function(){a.prototype.flush.call(this),null===this._pako&&this._createPako(),this._pako.push([],!0)},h.prototype.cleanUp=function(){a.prototype.cleanUp.call(this),this._pako=null},h.prototype._createPako=function(){this._pako=new i[this._pakoAction]({raw:!0,level:this._pakoOptions.level||-1});var t=this;this._pako.onData=function(Line truncated