y
This commit is contained in:
1 parent
04491e14a3
commit
85dfdd200e
11 files changed
+794
-437
No files matched your search
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -23,7 +23,7 @@ export type AppListItem = HeaderItem | GroupItem;
|
|||||||
|
|
||||||
export const SUB_APPS: SubAppItem[] = [
|
export const SUB_APPS: SubAppItem[] = [
|
||||||
{
|
{
|
||||||
id: "/money/home",
|
id: "/finance/home",
|
||||||
title: "记账",
|
title: "记账",
|
||||||
icon: IconAppFinance,
|
icon: IconAppFinance,
|
||||||
group: "J",
|
group: "J",
|
||||||
|
|||||||
+227
-235
@@ -1,272 +1,264 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="zh">
|
<html lang="zh">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<title>附件组件(最终版)</title>
|
||||||
<title>家庭系统 Pro Demo</title>
|
|
||||||
|
|
||||||
<script src="https://cdn.tailwindcss.com"></script>
|
<style>
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
font-family: Arial;
|
||||||
|
background: #f5f6f8;
|
||||||
|
}
|
||||||
|
|
||||||
<style>
|
/* grid */
|
||||||
.hidden {
|
.grid {
|
||||||
display: none;
|
padding: 12px;
|
||||||
}
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
.toast {
|
/* card */
|
||||||
position: fixed;
|
.card {
|
||||||
bottom: 80px;
|
position: relative;
|
||||||
left: 50%;
|
background: #fff;
|
||||||
transform: translateX(-50%);
|
border-radius: 10px;
|
||||||
background: rgba(0, 0, 0, 0.8);
|
border: 1px solid transparent;
|
||||||
color: white;
|
display: flex;
|
||||||
padding: 8px 14px;
|
flex-direction: column;
|
||||||
border-radius: 10px;
|
cursor: pointer;
|
||||||
font-size: 12px;
|
}
|
||||||
}
|
|
||||||
</style>
|
.card:hover {
|
||||||
|
border-color: #1677ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card.selected {
|
||||||
|
background: #eaf2ff;
|
||||||
|
border-color: #1677ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* checkbox(左上) */
|
||||||
|
.check {
|
||||||
|
position: absolute;
|
||||||
|
top: 6px;
|
||||||
|
left: 6px;
|
||||||
|
z-index: 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 状态(右上) */
|
||||||
|
.status {
|
||||||
|
position: absolute;
|
||||||
|
top: 6px;
|
||||||
|
right: 6px;
|
||||||
|
background: #ff4d4f;
|
||||||
|
color: #fff;
|
||||||
|
font-size: 10px;
|
||||||
|
padding: 2px 5px;
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* thumb */
|
||||||
|
.thumb {
|
||||||
|
height: 90px;
|
||||||
|
background: #f0f2f5;
|
||||||
|
display:flex;
|
||||||
|
align-items:center;
|
||||||
|
justify-content:center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.thumb img {
|
||||||
|
width:100%;
|
||||||
|
height:100%;
|
||||||
|
object-fit:cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* info */
|
||||||
|
.info {
|
||||||
|
padding: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.name {
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.meta {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #888;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* actions */
|
||||||
|
.actions {
|
||||||
|
display: flex;
|
||||||
|
border-top: 1px solid #eee;
|
||||||
|
}
|
||||||
|
|
||||||
|
.actions button {
|
||||||
|
flex: 1;
|
||||||
|
border: none;
|
||||||
|
background: #fff;
|
||||||
|
padding: 6px 0;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.actions button:hover {
|
||||||
|
background: #f5f6f8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.danger {
|
||||||
|
color: #ff4d4f;
|
||||||
|
}
|
||||||
|
|
||||||
|
.more-menu {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 35px;
|
||||||
|
right: 6px;
|
||||||
|
background: #fff;
|
||||||
|
border: 1px solid #eee;
|
||||||
|
border-radius: 6px;
|
||||||
|
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.more-menu div {
|
||||||
|
padding: 6px 10px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.more-menu div:hover {
|
||||||
|
background: #f5f6f8;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body class="bg-gray-100">
|
<body>
|
||||||
|
|
||||||
<div id="app" class="max-w-md mx-auto min-h-screen flex flex-col">
|
<div class="grid" id="grid"></div>
|
||||||
|
|
||||||
<!-- ================= LOGIN ================= -->
|
<script>
|
||||||
<div id="login" class="flex-1 flex items-center justify-center p-6">
|
const files = [
|
||||||
<div class="bg-white p-6 rounded-2xl w-full shadow">
|
{ id: 1, name: "合同.pdf", type: "doc", size: "800KB", date: "2026-04-25" },
|
||||||
|
{ id: 2, name: "设计图.png", type: "img", url: "https://picsum.photos/400/300?1", size: "1.2MB", date: "2026-04-24", status: "未上传" }
|
||||||
|
];
|
||||||
|
|
||||||
<h1 class="text-xl font-bold mb-1">欢迎回来</h1>
|
let selected = new Set();
|
||||||
<p class="text-sm text-gray-400 mb-4">家庭系统登录</p>
|
|
||||||
|
|
||||||
<input id="username" class="w-full border p-2 rounded mb-3" placeholder="请输入名字">
|
function render() {
|
||||||
|
const grid = document.getElementById("grid");
|
||||||
|
grid.innerHTML = "";
|
||||||
|
|
||||||
<button onclick="login()" class="w-full bg-blue-500 text-white py-2 rounded">
|
files.forEach(f => {
|
||||||
登录
|
const card = document.createElement("div");
|
||||||
</button>
|
card.className = "card" + (selected.has(f.id) ? " selected" : "");
|
||||||
|
|
||||||
</div>
|
card.onclick = () => toggleSelect(f.id);
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- ================= FAMILY ONBOARD ================= -->
|
/* checkbox */
|
||||||
<div id="onboard" class="hidden flex-1 p-6">
|
const check = document.createElement("input");
|
||||||
|
check.type = "checkbox";
|
||||||
|
check.className = "check";
|
||||||
|
check.checked = selected.has(f.id);
|
||||||
|
|
||||||
<div class="bg-white p-6 rounded-2xl shadow">
|
check.onclick = (e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
toggleSelect(f.id);
|
||||||
|
};
|
||||||
|
|
||||||
<h1 class="text-lg font-bold">创建你的家庭</h1>
|
/* 状态 */
|
||||||
<p class="text-sm text-gray-400 mb-4">你还没有加入任何家庭</p>
|
if (f.status) {
|
||||||
|
const st = document.createElement("div");
|
||||||
<input id="familyName" class="w-full border p-2 rounded mb-3" placeholder="家庭名称">
|
st.className = "status";
|
||||||
|
st.innerText = f.status;
|
||||||
<button onclick="createFamily()" class="w-full bg-green-500 text-white py-2 rounded mb-2">
|
card.appendChild(st);
|
||||||
创建家庭
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button onclick="joinFamilyMock()" class="w-full bg-gray-200 py-2 rounded">
|
|
||||||
加入示例家庭
|
|
||||||
</button>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- ================= MAIN ================= -->
|
|
||||||
<div id="main" class="hidden flex-1 flex flex-col">
|
|
||||||
|
|
||||||
<!-- CONTENT -->
|
|
||||||
<div class="flex-1 p-4 space-y-3">
|
|
||||||
|
|
||||||
<!-- 家庭卡 -->
|
|
||||||
<div class="bg-white p-4 rounded-2xl shadow">
|
|
||||||
|
|
||||||
<div class="flex justify-between">
|
|
||||||
<div>
|
|
||||||
<h2 class="font-bold text-lg">🏠 <span id="familyTitle"></span></h2>
|
|
||||||
<p class="text-xs text-gray-400">邀请码:ABC123 · 3人</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button onclick="openSettings()" class="text-xs bg-gray-100 px-3 py-1 rounded-lg">
|
|
||||||
设置
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 成员 -->
|
|
||||||
<div class="bg-white p-4 rounded-2xl shadow">
|
|
||||||
|
|
||||||
<div class="flex justify-between mb-3">
|
|
||||||
<div class="font-semibold">家庭成员</div>
|
|
||||||
<button class="text-blue-500 text-sm">邀请</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="flex gap-4">
|
|
||||||
<div class="text-center">
|
|
||||||
<div class="w-10 h-10 bg-blue-200 rounded-full"></div>
|
|
||||||
<div class="text-xs mt-1">我</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="text-center">
|
|
||||||
<div class="w-10 h-10 bg-pink-200 rounded-full"></div>
|
|
||||||
<div class="text-xs mt-1">妈妈</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="text-center">
|
|
||||||
<div class="w-10 h-10 bg-yellow-200 rounded-full"></div>
|
|
||||||
<div class="text-xs mt-1">爸爸</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 子应用 -->
|
|
||||||
<div class="bg-white p-4 rounded-2xl shadow">
|
|
||||||
|
|
||||||
<div class="font-semibold mb-3">家庭工具</div>
|
|
||||||
|
|
||||||
<div class="grid grid-cols-3 gap-3">
|
|
||||||
|
|
||||||
<div class="bg-gray-50 p-3 rounded-xl text-center" onclick="openApp('记账')">
|
|
||||||
📒<div class="text-xs mt-1">记账</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="bg-gray-50 p-3 rounded-xl text-center" onclick="openApp('清单')">
|
|
||||||
🛒<div class="text-xs mt-1">清单</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="bg-gray-50 p-3 rounded-xl text-center" onclick="openApp('日程')">
|
|
||||||
📅<div class="text-xs mt-1">日程</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="bg-gray-50 p-3 rounded-xl text-center" onclick="openApp('物品')">
|
|
||||||
📦<div class="text-xs mt-1">物品</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="bg-gray-50 p-3 rounded-xl text-center" onclick="openApp('待办')">
|
|
||||||
📝<div class="text-xs mt-1">待办</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="bg-gray-100 p-3 rounded-xl text-center text-gray-400">
|
|
||||||
➕<div class="text-xs mt-1">添加</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- TABBAR -->
|
|
||||||
<div class="flex border-t bg-white">
|
|
||||||
|
|
||||||
<button class="flex-1 py-2" onclick="switchTab('home')">🏠</button>
|
|
||||||
<button class="flex-1 py-2" onclick="switchTab('apps')">🧩</button>
|
|
||||||
<button class="flex-1 py-2" onclick="switchTab('me')">👤</button>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- ================= SETTINGS ================= -->
|
|
||||||
<div id="settings" class="hidden flex-1 p-4">
|
|
||||||
|
|
||||||
<div class="bg-white p-4 rounded-2xl shadow">
|
|
||||||
|
|
||||||
<h2 class="font-bold mb-3">家庭设置</h2>
|
|
||||||
|
|
||||||
<div class="space-y-2 text-sm">
|
|
||||||
|
|
||||||
<div class="p-2 bg-gray-100 rounded">修改家庭名称</div>
|
|
||||||
<div class="p-2 bg-gray-100 rounded">成员管理</div>
|
|
||||||
<div class="p-2 bg-gray-100 rounded text-red-500">退出家庭</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button onclick="backHome()" class="mt-4 w-full bg-blue-500 text-white py-2 rounded">
|
|
||||||
返回
|
|
||||||
</button>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- toast -->
|
|
||||||
<div id="toast" class="toast hidden"></div>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
|
|
||||||
let state = {
|
|
||||||
user: null,
|
|
||||||
family: null
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function show(id) {
|
/* thumb */
|
||||||
document.querySelectorAll('#app > div').forEach(v => v.classList.add('hidden'));
|
const thumb = document.createElement("div");
|
||||||
document.getElementById(id).classList.remove('hidden');
|
thumb.className = "thumb";
|
||||||
|
|
||||||
|
if (f.type === "img") {
|
||||||
|
thumb.innerHTML = `<img src="${f.url}">`;
|
||||||
|
} else {
|
||||||
|
thumb.innerText = "📄";
|
||||||
}
|
}
|
||||||
|
|
||||||
function toast(msg) {
|
/* info */
|
||||||
const t = document.getElementById('toast');
|
const info = document.createElement("div");
|
||||||
t.innerText = msg;
|
info.className = "info";
|
||||||
t.classList.remove('hidden');
|
info.innerHTML = `
|
||||||
|
<div class="name">${f.name}</div>
|
||||||
|
<div class="meta">${f.date} · ${f.size}</div>
|
||||||
|
`;
|
||||||
|
|
||||||
setTimeout(() => {
|
/* actions */
|
||||||
t.classList.add('hidden');
|
const actions = document.createElement("div");
|
||||||
}, 1500);
|
actions.className = "actions";
|
||||||
}
|
|
||||||
|
|
||||||
/* ========== login ========== */
|
const btnView = createBtn("预览", () => preview(f));
|
||||||
function login() {
|
const btnDown = createBtn("下载", () => alert("下载 " + f.name));
|
||||||
const name = document.getElementById('username').value;
|
const btnDelete = createBtn("删除", () => deleteFile(f.id), "danger");
|
||||||
if (!name) return toast('请输入用户名');
|
|
||||||
|
|
||||||
state.user = name;
|
/* 更多 */
|
||||||
show('onboard');
|
const btnMore = document.createElement("button");
|
||||||
}
|
btnMore.innerText = "⋯";
|
||||||
|
|
||||||
/* ========== family ========== */
|
const menu = document.createElement("div");
|
||||||
function createFamily() {
|
menu.className = "more-menu";
|
||||||
const name = document.getElementById('familyName').value;
|
|
||||||
if (!name) return toast('请输入家庭名称');
|
|
||||||
|
|
||||||
state.family = name;
|
menu.innerHTML = `
|
||||||
enterApp();
|
<div>重命名</div>
|
||||||
}
|
<div>查看详情</div>
|
||||||
|
`;
|
||||||
|
|
||||||
function joinFamilyMock() {
|
btnMore.onclick = (e) => {
|
||||||
state.family = "幸福家庭";
|
e.stopPropagation();
|
||||||
enterApp();
|
menu.style.display = menu.style.display === "block" ? "none" : "block";
|
||||||
}
|
};
|
||||||
|
|
||||||
function enterApp() {
|
document.addEventListener("click", () => {
|
||||||
document.getElementById('familyTitle').innerText = state.family;
|
menu.style.display = "none";
|
||||||
show('main');
|
});
|
||||||
switchTab('home');
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ========== tab ========== */
|
actions.append(btnView, btnDown, btnDelete, btnMore);
|
||||||
function switchTab(tab) {
|
|
||||||
document.querySelectorAll('#main > div:nth-child(1) > div').forEach(v => {
|
|
||||||
v.style.display = 'none';
|
|
||||||
});
|
|
||||||
|
|
||||||
toast("切换:" + tab);
|
card.append(check, thumb, info, actions, menu);
|
||||||
}
|
grid.appendChild(card);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function openApp(name) {
|
function createBtn(text, fn, cls="") {
|
||||||
toast("进入:" + name);
|
const btn = document.createElement("button");
|
||||||
}
|
btn.innerText = text;
|
||||||
|
btn.className = cls;
|
||||||
|
btn.onclick = (e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
fn();
|
||||||
|
};
|
||||||
|
return btn;
|
||||||
|
}
|
||||||
|
|
||||||
/* ========== settings ========== */
|
function toggleSelect(id) {
|
||||||
function openSettings() {
|
if (selected.has(id)) selected.delete(id);
|
||||||
show('settings');
|
else selected.add(id);
|
||||||
}
|
render();
|
||||||
|
}
|
||||||
|
|
||||||
function backHome() {
|
function preview(f) {
|
||||||
show('main');
|
alert("预览 " + f.name);
|
||||||
}
|
}
|
||||||
|
|
||||||
</script>
|
function deleteFile(id) {
|
||||||
|
if (!confirm("确定删除?")) return;
|
||||||
|
const i = files.findIndex(f => f.id === id);
|
||||||
|
if (i > -1) files.splice(i, 1);
|
||||||
|
selected.delete(id);
|
||||||
|
render();
|
||||||
|
}
|
||||||
|
|
||||||
|
render();
|
||||||
|
</script>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
@@ -322,3 +322,15 @@
|
|||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 预览状态样式 */
|
||||||
|
.g3-file-container .file-item.file-item-preview {
|
||||||
|
border-color: #4caf50;
|
||||||
|
box-shadow: 0 0 0 2px rgba(76, 175, 80, 0.2);
|
||||||
|
background: rgba(76, 175, 80, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.g3-file-container .file-item.file-item-preview:hover {
|
||||||
|
border-color: #4caf50;
|
||||||
|
box-shadow: 0 6px 18px rgba(76, 175, 80, 0.2);
|
||||||
|
}
|
||||||
@@ -1,9 +1,10 @@
|
|||||||
(function ($) {
|
(function ($) {
|
||||||
'use strict';
|
"use strict";
|
||||||
|
|
||||||
var G3File = {
|
var G3File = {
|
||||||
files: [],
|
files: [],
|
||||||
selectedFiles: new Set(),
|
selectedFiles: new Set(),
|
||||||
|
previewFiles: new Set(),
|
||||||
|
|
||||||
loadScripts: function (items, callback) {
|
loadScripts: function (items, callback) {
|
||||||
var self = this;
|
var self = this;
|
||||||
@@ -25,7 +26,7 @@
|
|||||||
|
|
||||||
// 防重复加载
|
// 防重复加载
|
||||||
var key = item.key;
|
var key = item.key;
|
||||||
if (window[key + '_loading']) {
|
if (window[key + "_loading"]) {
|
||||||
var timer = setInterval(function () {
|
var timer = setInterval(function () {
|
||||||
if (item.check && item.check()) {
|
if (item.check && item.check()) {
|
||||||
clearInterval(timer);
|
clearInterval(timer);
|
||||||
@@ -35,19 +36,19 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
window[key + '_loading'] = true;
|
window[key + "_loading"] = true;
|
||||||
|
|
||||||
var script = document.createElement('script');
|
var script = document.createElement("script");
|
||||||
script.src = item.src;
|
script.src = item.src;
|
||||||
|
|
||||||
script.onload = function () {
|
script.onload = function () {
|
||||||
window[key + '_loading'] = false;
|
window[key + "_loading"] = false;
|
||||||
done();
|
done();
|
||||||
};
|
};
|
||||||
|
|
||||||
script.onerror = function () {
|
script.onerror = function () {
|
||||||
window[key + '_loading'] = false;
|
window[key + "_loading"] = false;
|
||||||
console.error(key + ' load failed');
|
console.error(key + " load failed");
|
||||||
done();
|
done();
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -61,36 +62,42 @@
|
|||||||
init: function (containerId, options) {
|
init: function (containerId, options) {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
this.$container = $('#' + containerId);
|
this.$container = $("#" + containerId);
|
||||||
this.options = $.extend({}, this.defaults, options);
|
this.options = $.extend({}, this.defaults, options);
|
||||||
|
|
||||||
this.language = this.languages[this.options.language] || this.languages['zh-CN'];
|
this.language =
|
||||||
|
this.languages[this.options.language] || this.languages["zh-CN"];
|
||||||
this.files = this.options.data || [];
|
this.files = this.options.data || [];
|
||||||
|
this.selectedFiles = new Set(); // 选中文件集合
|
||||||
|
this.previewFiles = new Set(); // 预览文件集合
|
||||||
|
|
||||||
this.loadScripts([
|
this.loadScripts(
|
||||||
{
|
[
|
||||||
key: 'fileSaver',
|
{
|
||||||
src: '/custom-file/libs/FileSaver.min.js',
|
key: "fileSaver",
|
||||||
check: function () {
|
src: "/custom-file/libs/FileSaver.min.js",
|
||||||
return typeof window.saveAs === 'function';
|
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);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
);
|
||||||
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;
|
return this;
|
||||||
},
|
},
|
||||||
@@ -104,56 +111,57 @@
|
|||||||
onDownload: null,
|
onDownload: null,
|
||||||
onSelect: null,
|
onSelect: null,
|
||||||
onUpload: null,
|
onUpload: null,
|
||||||
language: 'zh-CN',
|
onPreview: null,
|
||||||
|
language: "zh-CN",
|
||||||
maxFileSize: 10 * 1024 * 1024, // 默认最大10MB
|
maxFileSize: 10 * 1024 * 1024, // 默认最大10MB
|
||||||
permissions: {
|
permissions: {
|
||||||
select: true,
|
select: true,
|
||||||
upload: true,
|
upload: true,
|
||||||
download: true,
|
download: true,
|
||||||
delete: true,
|
delete: true,
|
||||||
view: true
|
view: true,
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
// =====================
|
// =====================
|
||||||
// 多语言
|
// 多语言
|
||||||
// =====================
|
// =====================
|
||||||
languages: {
|
languages: {
|
||||||
'zh-CN': {
|
"zh-CN": {
|
||||||
selectAll: '全选',
|
selectAll: "全选",
|
||||||
download: '批量下载',
|
download: "批量下载",
|
||||||
delete: '删除',
|
delete: "删除",
|
||||||
upload: '上传',
|
upload: "上传",
|
||||||
select: '选择',
|
select: "选择",
|
||||||
confirmDelete: '确定要删除选中的 {count} 个文件吗?',
|
confirmDelete: "确定要删除选中的 {count} 个文件吗?",
|
||||||
downloading: '准备下载 {count} 个文件',
|
downloading: "准备下载 {count} 个文件",
|
||||||
deleting: '删除中...',
|
deleting: "删除中...",
|
||||||
name: '名称',
|
name: "名称",
|
||||||
size: '大小',
|
size: "大小",
|
||||||
date: '日期',
|
date: "日期",
|
||||||
pending: '未上传',
|
pending: "未上传",
|
||||||
selectFiles: '选择文件',
|
selectFiles: "选择文件",
|
||||||
emptyTip1: '拖拽文件到这里 …',
|
emptyTip1: "拖拽文件到这里 …",
|
||||||
emptyTip2: '支持多文件同时上传/下载',
|
emptyTip2: "支持多文件同时上传/下载",
|
||||||
emptyTip3: '支持粘贴图片上传',
|
emptyTip3: "支持粘贴图片上传",
|
||||||
fileSizeError: '文件大小超过限制'
|
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",
|
||||||
},
|
},
|
||||||
'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'
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
// =====================
|
// =====================
|
||||||
@@ -183,29 +191,29 @@
|
|||||||
`,
|
`,
|
||||||
default_file: `
|
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>
|
<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
|
// 生成唯一ID
|
||||||
// =====================
|
// =====================
|
||||||
generateUniqueId: function () {
|
generateUniqueId: function () {
|
||||||
return 'file_' + Math.random().toString(36).slice(2) + Date.now();
|
return "file_" + Math.random().toString(36).slice(2) + Date.now();
|
||||||
},
|
},
|
||||||
|
|
||||||
// =====================
|
// =====================
|
||||||
// 获取文件图标
|
// 获取文件图标
|
||||||
// =====================
|
// =====================
|
||||||
getFileIcon: function (filename) {
|
getFileIcon: function (filename) {
|
||||||
var ext = filename.split('.').pop().toLowerCase();
|
var ext = filename.split(".").pop().toLowerCase();
|
||||||
return this.staticIconMap[ext] || this.staticIconMap['default_file'];
|
return this.staticIconMap[ext] || this.staticIconMap["default_file"];
|
||||||
},
|
},
|
||||||
|
|
||||||
// =====================
|
// =====================
|
||||||
// 更新语言
|
// 更新语言
|
||||||
// =====================
|
// =====================
|
||||||
updateLanguage: function (language) {
|
updateLanguage: function (language) {
|
||||||
this.language = this.languages[language] || this.languages['zh-CN'];
|
this.language = this.languages[language] || this.languages["zh-CN"];
|
||||||
this.render();
|
this.render();
|
||||||
return this;
|
return this;
|
||||||
},
|
},
|
||||||
@@ -259,15 +267,15 @@
|
|||||||
|
|
||||||
this.$container.html(html);
|
this.$container.html(html);
|
||||||
|
|
||||||
this.$fileList = this.$container.find('.main');
|
this.$fileList = this.$container.find(".main");
|
||||||
this.$emptyTip = this.$container.find('.empty-tip');
|
this.$emptyTip = this.$container.find(".empty-tip");
|
||||||
this.$checkAll = this.$container.find('.check-all-checkbox');
|
this.$checkAll = this.$container.find(".check-all-checkbox");
|
||||||
this.$btnDownload = this.$container.find('.download-all');
|
this.$btnDownload = this.$container.find(".download-all");
|
||||||
this.$btnDelete = this.$container.find('.delete-all');
|
this.$btnDelete = this.$container.find(".delete-all");
|
||||||
this.$btnUpload = this.$container.find('.upload');
|
this.$btnUpload = this.$container.find(".upload");
|
||||||
this.$btnSelect = this.$container.find('.select');
|
this.$btnSelect = this.$container.find(".select");
|
||||||
this.$fileInput = this.$container.find('.file-input');
|
this.$fileInput = this.$container.find(".file-input");
|
||||||
this.$pendingCount = this.$container.find('.pending-number');
|
this.$pendingCount = this.$container.find(".pending-number");
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
},
|
},
|
||||||
@@ -276,8 +284,8 @@
|
|||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
// 选择
|
// 选择
|
||||||
this.$checkAll.off('change').on('change', function () {
|
this.$checkAll.off("change").on("change", function () {
|
||||||
var checked = $(this).is(':checked');
|
var checked = $(this).is(":checked");
|
||||||
|
|
||||||
self.selectedFiles.clear();
|
self.selectedFiles.clear();
|
||||||
|
|
||||||
@@ -287,20 +295,20 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
self.$fileList.find('.file-checkbox').prop('checked', checked);
|
self.$fileList.find(".file-checkbox").prop("checked", checked);
|
||||||
|
|
||||||
self.updateToolbarState();
|
self.updateToolbarState();
|
||||||
});
|
});
|
||||||
|
|
||||||
// 批量下载
|
// 批量下载
|
||||||
this.$btnDownload.off('click').on('click', function () {
|
this.$btnDownload.off("click").on("click", function () {
|
||||||
var idSet = self.selectedFiles || new Set();
|
var idSet = self.selectedFiles || new Set();
|
||||||
|
|
||||||
var files = self.files.filter(function (item) {
|
var files = self.files.filter(function (item) {
|
||||||
return idSet.has(item._id);
|
return idSet.has(item._id);
|
||||||
});
|
});
|
||||||
|
|
||||||
if (typeof self.options?.onDownloadBatch === 'function') {
|
if (typeof self.options?.onDownloadBatch === "function") {
|
||||||
self.options.onDownloadBatch(files);
|
self.options.onDownloadBatch(files);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -309,7 +317,7 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
// 批量删除
|
// 批量删除
|
||||||
this.$btnDelete.off('click').on('click', function () {
|
this.$btnDelete.off("click").on("click", function () {
|
||||||
var idSet = self.selectedFiles || new Set();
|
var idSet = self.selectedFiles || new Set();
|
||||||
|
|
||||||
if (idSet.size === 0) return;
|
if (idSet.size === 0) return;
|
||||||
@@ -321,7 +329,7 @@
|
|||||||
// =====================
|
// =====================
|
||||||
// 1. 优先走外部回调(如果你要接后端)
|
// 1. 优先走外部回调(如果你要接后端)
|
||||||
// =====================
|
// =====================
|
||||||
if (typeof self.options?.onDeleteBatch === 'function') {
|
if (typeof self.options?.onDeleteBatch === "function") {
|
||||||
self.options.onDeleteBatch(deleteFiles, function (success) {
|
self.options.onDeleteBatch(deleteFiles, function (success) {
|
||||||
if (!success) return;
|
if (!success) return;
|
||||||
|
|
||||||
@@ -340,17 +348,17 @@
|
|||||||
// =====================
|
// =====================
|
||||||
// 选择按钮点击事件(选择本地文件)
|
// 选择按钮点击事件(选择本地文件)
|
||||||
// =====================
|
// =====================
|
||||||
this.$btnSelect.off('click').on('click', function () {
|
this.$btnSelect.off("click").on("click", function () {
|
||||||
self.$fileInput.click();
|
self.$fileInput.click();
|
||||||
});
|
});
|
||||||
|
|
||||||
// =====================
|
// =====================
|
||||||
// 上传按钮点击事件(交给外部处理)
|
// 上传按钮点击事件(交给外部处理)
|
||||||
// =====================
|
// =====================
|
||||||
this.$btnUpload.off('click').on('click', function () {
|
this.$btnUpload.off("click").on("click", function () {
|
||||||
// 获取所有未上传的文件
|
// 获取所有未上传的文件
|
||||||
const pendingFiles = self.files.filter(file => !file.uploaded);
|
const pendingFiles = self.files.filter((file) => !file.uploaded);
|
||||||
if (typeof self.options.onUpload === 'function') {
|
if (typeof self.options.onUpload === "function") {
|
||||||
self.options.onUpload(pendingFiles);
|
self.options.onUpload(pendingFiles);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -358,7 +366,7 @@
|
|||||||
// =====================
|
// =====================
|
||||||
// 文件选择事件
|
// 文件选择事件
|
||||||
// =====================
|
// =====================
|
||||||
this.$fileInput.off('change').on('change', function (e) {
|
this.$fileInput.off("change").on("change", function (e) {
|
||||||
var files = e.target.files;
|
var files = e.target.files;
|
||||||
if (!files || files.length === 0) return;
|
if (!files || files.length === 0) return;
|
||||||
|
|
||||||
@@ -376,15 +384,15 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (invalidFiles.length > 0) {
|
if (invalidFiles.length > 0) {
|
||||||
var errorMsg = self.language.fileSizeError || '文件大小超过限制';
|
var errorMsg = self.language.fileSizeError || "文件大小超过限制";
|
||||||
alert(errorMsg + ': ' + invalidFiles.map(f => f.name).join(', '));
|
alert(errorMsg + ": " + invalidFiles.map((f) => f.name).join(", "));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (validFiles.length > 0) {
|
if (validFiles.length > 0) {
|
||||||
// 在本地临时插入文件
|
// 在本地临时插入文件
|
||||||
var tempFiles = validFiles.map(function (file) {
|
var tempFiles = validFiles.map(function (file) {
|
||||||
// 为图片文件创建本地临时连接
|
// 为图片文件创建本地临时连接
|
||||||
let url = '';
|
let url = "";
|
||||||
if (self.isImageFile(file.name)) {
|
if (self.isImageFile(file.name)) {
|
||||||
url = URL.createObjectURL(file);
|
url = URL.createObjectURL(file);
|
||||||
}
|
}
|
||||||
@@ -396,7 +404,7 @@
|
|||||||
uploadDate: new Date().toISOString(),
|
uploadDate: new Date().toISOString(),
|
||||||
uploaded: false, // 标记为未上传
|
uploaded: false, // 标记为未上传
|
||||||
url: url, // 图片文件使用本地临时连接,其他文件为空
|
url: url, // 图片文件使用本地临时连接,其他文件为空
|
||||||
file: file // 保存原始文件对象
|
file: file, // 保存原始文件对象
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -405,13 +413,13 @@
|
|||||||
self.renderFileList();
|
self.renderFileList();
|
||||||
self.updateToolbarState();
|
self.updateToolbarState();
|
||||||
|
|
||||||
if (typeof self.options.onSelect === 'function') {
|
if (typeof self.options.onSelect === "function") {
|
||||||
self.options.onSelect(validFiles);
|
self.options.onSelect(validFiles);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 重置文件输入框,以便可以重复选择相同的文件
|
// 重置文件输入框,以便可以重复选择相同的文件
|
||||||
self.$fileInput.val('');
|
self.$fileInput.val("");
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -419,10 +427,10 @@
|
|||||||
// 文件判断
|
// 文件判断
|
||||||
// =====================
|
// =====================
|
||||||
isImageFile: function (filename) {
|
isImageFile: function (filename) {
|
||||||
if (!filename || typeof filename !== 'string') return false;
|
if (!filename || typeof filename !== "string") return false;
|
||||||
|
|
||||||
const ext = filename.slice(filename.lastIndexOf('.') + 1).toLowerCase();
|
const ext = filename.slice(filename.lastIndexOf(".") + 1).toLowerCase();
|
||||||
const imageExts = new Set(['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp']);
|
const imageExts = new Set(["jpg", "jpeg", "png", "gif", "bmp", "webp"]);
|
||||||
|
|
||||||
return imageExts.has(ext);
|
return imageExts.has(ext);
|
||||||
},
|
},
|
||||||
@@ -433,33 +441,31 @@
|
|||||||
updateToolbarState: function () {
|
updateToolbarState: function () {
|
||||||
var hasSelection = this.selectedFiles.size > 0;
|
var hasSelection = this.selectedFiles.size > 0;
|
||||||
|
|
||||||
this.$btnDownload.prop('disabled', !hasSelection);
|
this.$btnDownload.prop("disabled", !hasSelection);
|
||||||
this.$btnDelete.prop('disabled', !hasSelection);
|
this.$btnDelete.prop("disabled", !hasSelection);
|
||||||
|
|
||||||
var allSelected =
|
var allSelected =
|
||||||
this.files.length > 0 &&
|
this.files.length > 0 && this.selectedFiles.size === this.files.length;
|
||||||
this.selectedFiles.size === this.files.length;
|
|
||||||
|
|
||||||
this.$checkAll.prop('checked', allSelected);
|
this.$checkAll.prop("checked", allSelected);
|
||||||
|
|
||||||
// 更新未上传文件数量
|
// 更新未上传文件数量
|
||||||
var pendingCount = this.files.filter(file => !file.uploaded).length;
|
var pendingCount = this.files.filter((file) => !file.uploaded).length;
|
||||||
this.$pendingCount.text(pendingCount);
|
this.$pendingCount.text(pendingCount);
|
||||||
|
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
this.$fileList.find('.file-item').each(function () {
|
this.$fileList.find(".file-item").each(function () {
|
||||||
var id = $(this).data('id');
|
var id = $(this).data("id");
|
||||||
var isSelected = self.selectedFiles.has(id);
|
var isSelected = self.selectedFiles.has(id);
|
||||||
|
|
||||||
$(this).find('.file-checkbox')
|
$(this).find(".file-checkbox").prop("checked", isSelected);
|
||||||
.prop('checked', isSelected);
|
|
||||||
|
|
||||||
// 添加或移除选中样式
|
// 添加或移除选中样式
|
||||||
if (isSelected) {
|
if (isSelected) {
|
||||||
$(this).addClass('file-item-selected');
|
$(this).addClass("file-item-selected");
|
||||||
} else {
|
} else {
|
||||||
$(this).removeClass('file-item-selected');
|
$(this).removeClass("file-item-selected");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -473,7 +479,7 @@
|
|||||||
this.files = files.map(function (file) {
|
this.files = files.map(function (file) {
|
||||||
return Object.assign({}, file, {
|
return Object.assign({}, file, {
|
||||||
_id: self.generateUniqueId(),
|
_id: self.generateUniqueId(),
|
||||||
uploaded: true
|
uploaded: true,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -498,7 +504,7 @@
|
|||||||
this.$fileList.show();
|
this.$fileList.show();
|
||||||
this.$emptyTip.hide();
|
this.$emptyTip.hide();
|
||||||
|
|
||||||
var html = '';
|
var html = "";
|
||||||
$.each(this.files, function (_, file) {
|
$.each(this.files, function (_, file) {
|
||||||
html += self.createFileItem(file);
|
html += self.createFileItem(file);
|
||||||
});
|
});
|
||||||
@@ -512,38 +518,50 @@
|
|||||||
// =====================
|
// =====================
|
||||||
formatFileSize: function (bytes) {
|
formatFileSize: function (bytes) {
|
||||||
if (bytes === null || bytes === undefined || isNaN(bytes) || bytes <= 0) {
|
if (bytes === null || bytes === undefined || isNaN(bytes) || bytes <= 0) {
|
||||||
return '-';
|
return "-";
|
||||||
}
|
}
|
||||||
|
|
||||||
var k = 1024;
|
var k = 1024;
|
||||||
var sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
|
var sizes = ["B", "KB", "MB", "GB", "TB"];
|
||||||
|
|
||||||
var i = Math.floor(Math.log(bytes) / Math.log(k));
|
var i = Math.floor(Math.log(bytes) / Math.log(k));
|
||||||
i = Math.max(0, Math.min(i, sizes.length - 1));
|
i = Math.max(0, Math.min(i, sizes.length - 1));
|
||||||
|
|
||||||
return (bytes / Math.pow(k, i)).toFixed(2) + ' ' + sizes[i];
|
return (bytes / Math.pow(k, i)).toFixed(2) + " " + sizes[i];
|
||||||
},
|
},
|
||||||
|
|
||||||
// =====================
|
// =====================
|
||||||
// 格式化日期
|
// 格式化日期
|
||||||
// =====================
|
// =====================
|
||||||
formatDate: function (dateStr) {
|
formatDate: function (dateStr) {
|
||||||
if (!dateStr) return '-';
|
if (!dateStr) return "-";
|
||||||
|
|
||||||
var date = new Date(dateStr);
|
var date = new Date(dateStr);
|
||||||
|
|
||||||
if (isNaN(date.getTime())) {
|
if (isNaN(date.getTime())) {
|
||||||
return '-';
|
return "-";
|
||||||
}
|
}
|
||||||
|
|
||||||
var year = date.getFullYear();
|
var year = date.getFullYear();
|
||||||
var month = ('0' + (date.getMonth() + 1)).slice(-2);
|
var month = ("0" + (date.getMonth() + 1)).slice(-2);
|
||||||
var day = ('0' + date.getDate()).slice(-2);
|
var day = ("0" + date.getDate()).slice(-2);
|
||||||
var hours = ('0' + date.getHours()).slice(-2);
|
var hours = ("0" + date.getHours()).slice(-2);
|
||||||
var minutes = ('0' + date.getMinutes()).slice(-2);
|
var minutes = ("0" + date.getMinutes()).slice(-2);
|
||||||
var seconds = ('0' + date.getSeconds()).slice(-2);
|
var seconds = ("0" + date.getSeconds()).slice(-2);
|
||||||
|
|
||||||
return year + '-' + month + '-' + day + ' ' + hours + ':' + minutes + ':' + seconds;
|
return (
|
||||||
|
year +
|
||||||
|
"-" +
|
||||||
|
month +
|
||||||
|
"-" +
|
||||||
|
day +
|
||||||
|
" " +
|
||||||
|
hours +
|
||||||
|
":" +
|
||||||
|
minutes +
|
||||||
|
":" +
|
||||||
|
seconds
|
||||||
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
// =====================
|
// =====================
|
||||||
@@ -555,7 +573,7 @@
|
|||||||
const downloadDisabled = !perm.download;
|
const downloadDisabled = !perm.download;
|
||||||
const deleteDisabled = !perm.delete;
|
const deleteDisabled = !perm.delete;
|
||||||
|
|
||||||
let content = '';
|
let content = "";
|
||||||
|
|
||||||
if (this.isImageFile(file.name)) {
|
if (this.isImageFile(file.name)) {
|
||||||
content = `<img src="${file.url}" alt="${file.name}" class="file-img" />`;
|
content = `<img src="${file.url}" alt="${file.name}" class="file-img" />`;
|
||||||
@@ -564,15 +582,23 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 未上传标记
|
// 未上传标记
|
||||||
const pendingBadge = file.uploaded ? '' : `
|
const pendingBadge = file.uploaded
|
||||||
|
? ""
|
||||||
|
: `
|
||||||
<div class="file-pending-badge">
|
<div class="file-pending-badge">
|
||||||
${this.language.pending}
|
${this.language.pending}
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
// 预览状态(使用class标识)
|
||||||
|
const isPreview = this.previewFiles.has(file._id);
|
||||||
|
|
||||||
|
// 选中状态
|
||||||
|
const isSelected = this.selectedFiles.has(file._id);
|
||||||
|
|
||||||
return `
|
return `
|
||||||
<div class="file-item" data-id="${file._id}">
|
<div class="file-item ${isPreview ? "file-item-preview" : ""}" data-id="${file._id}">
|
||||||
<input type="checkbox" class="file-checkbox" />
|
<input type="checkbox" class="file-checkbox" ${isSelected ? "checked" : ""} />
|
||||||
<div class="file-preview">${content}</div>
|
<div class="file-preview">${content}</div>
|
||||||
${pendingBadge}
|
${pendingBadge}
|
||||||
|
|
||||||
@@ -583,11 +609,11 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="file-actions">
|
<div class="file-actions">
|
||||||
<div class="file-action download ${downloadDisabled ? 'is-disabled' : ''}">
|
<div class="file-action download ${downloadDisabled ? "is-disabled" : ""}">
|
||||||
${this.staticIconMap.download}
|
${this.staticIconMap.download}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="file-action delete ${deleteDisabled ? 'is-disabled' : ''}">
|
<div class="file-action delete ${deleteDisabled ? "is-disabled" : ""}">
|
||||||
${this.staticIconMap.delete}
|
${this.staticIconMap.delete}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -606,26 +632,57 @@
|
|||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
// =====================
|
// =====================
|
||||||
// 文件item点击事件
|
// 文件item点击事件(用于预览)
|
||||||
// =====================
|
// =====================
|
||||||
this.$fileList.off('click', '.file-item')
|
this.$fileList
|
||||||
.on('click', '.file-item', function (e) {
|
.off("click", ".file-item")
|
||||||
var $checkbox = $(this).find('.file-checkbox');
|
.on("click", ".file-item", function (e) {
|
||||||
$checkbox.prop('checked', !$checkbox.is(':checked'));
|
var target = $(e.target);
|
||||||
$checkbox.trigger('change');
|
// 如果点击的是 checkbox、download、delete、more 按钮,则不执行预览逻辑
|
||||||
});
|
if (
|
||||||
|
target.is(".file-checkbox") ||
|
||||||
|
target.closest(".file-action").length > 0
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
this.$fileList.off('click', '.download')
|
var id = $(this).data("id");
|
||||||
.on('click', '.download', function (e) {
|
var file = self.files.find((f) => f._id === id);
|
||||||
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 (!file) return;
|
||||||
|
|
||||||
if (typeof self.options.onDownload === 'function') {
|
// 切换预览状态(只允许同时预览一个文件)
|
||||||
|
var isPreview = self.previewFiles.has(id);
|
||||||
|
if (isPreview) {
|
||||||
|
// 取消预览
|
||||||
|
self.previewFiles.delete(id);
|
||||||
|
} else {
|
||||||
|
// 开启预览前,先清除其他文件的预览状态
|
||||||
|
self.previewFiles.clear();
|
||||||
|
self.previewFiles.add(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重新渲染文件列表以更新预览状态
|
||||||
|
self.renderFileList();
|
||||||
|
|
||||||
|
// 调用外部回调,传递文件和预览状态
|
||||||
|
if (typeof self.options.onPreview === "function") {
|
||||||
|
self.options.onPreview(file, !isPreview);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
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]);
|
self.options.onDownload([file]);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -633,11 +690,12 @@
|
|||||||
self.internalDownload(file);
|
self.internalDownload(file);
|
||||||
});
|
});
|
||||||
|
|
||||||
this.$fileList.off('change', '.file-checkbox')
|
this.$fileList
|
||||||
.on('change', '.file-checkbox', function () {
|
.off("change", ".file-checkbox")
|
||||||
var id = $(this).closest('.file-item').data('id');
|
.on("change", ".file-checkbox", function () {
|
||||||
|
var id = $(this).closest(".file-item").data("id");
|
||||||
|
|
||||||
if ($(this).is(':checked')) {
|
if ($(this).is(":checked")) {
|
||||||
self.selectedFiles.add(id);
|
self.selectedFiles.add(id);
|
||||||
} else {
|
} else {
|
||||||
self.selectedFiles.delete(id);
|
self.selectedFiles.delete(id);
|
||||||
@@ -646,17 +704,18 @@
|
|||||||
self.updateToolbarState();
|
self.updateToolbarState();
|
||||||
});
|
});
|
||||||
|
|
||||||
this.$fileList.off('click', '.delete')
|
this.$fileList
|
||||||
.on('click', '.delete', function (e) {
|
.off("click", ".delete")
|
||||||
|
.on("click", ".delete", function (e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
|
||||||
var id = $(this).closest('.file-item').data('id');
|
var id = $(this).closest(".file-item").data("id");
|
||||||
var file = self.files.find(f => f._id === id);
|
var file = self.files.find((f) => f._id === id);
|
||||||
|
|
||||||
if (!file) return;
|
if (!file) return;
|
||||||
|
|
||||||
if (typeof self.options.onDelete === 'function') {
|
if (typeof self.options.onDelete === "function") {
|
||||||
self.options.onDelete([file]);
|
self.options.onDelete([file]);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -667,11 +726,10 @@
|
|||||||
// =====================
|
// =====================
|
||||||
// 更多按钮点击事件(阻止冒泡)
|
// 更多按钮点击事件(阻止冒泡)
|
||||||
// =====================
|
// =====================
|
||||||
this.$fileList.off('click', '.more')
|
this.$fileList.off("click", ".more").on("click", ".more", function (e) {
|
||||||
.on('click', '.more', function (e) {
|
e.preventDefault();
|
||||||
e.preventDefault();
|
e.stopPropagation();
|
||||||
e.stopPropagation();
|
});
|
||||||
});
|
|
||||||
},
|
},
|
||||||
|
|
||||||
getUniqueFileName: function (fileName, files) {
|
getUniqueFileName: function (fileName, files) {
|
||||||
@@ -683,8 +741,13 @@
|
|||||||
// 如果文件名已经存在,则在文件名后加上 (1), (2), ...
|
// 如果文件名已经存在,则在文件名后加上 (1), (2), ...
|
||||||
while (files.has(uniqueFileName)) {
|
while (files.has(uniqueFileName)) {
|
||||||
// 基于原始文件名为基础来添加计数器
|
// 基于原始文件名为基础来添加计数器
|
||||||
const fileNameWithoutExtension = originalFileName.replace(/\.[^/.]+$/, "");
|
const fileNameWithoutExtension = originalFileName.replace(
|
||||||
const extension = originalFileName.slice(originalFileName.lastIndexOf("."));
|
/\.[^/.]+$/,
|
||||||
|
"",
|
||||||
|
);
|
||||||
|
const extension = originalFileName.slice(
|
||||||
|
originalFileName.lastIndexOf("."),
|
||||||
|
);
|
||||||
uniqueFileName = `${fileNameWithoutExtension} (${counter})${extension}`;
|
uniqueFileName = `${fileNameWithoutExtension} (${counter})${extension}`;
|
||||||
counter++;
|
counter++;
|
||||||
}
|
}
|
||||||
@@ -694,13 +757,13 @@
|
|||||||
|
|
||||||
getFileBlob: async function (url) {
|
getFileBlob: async function (url) {
|
||||||
if (!url) {
|
if (!url) {
|
||||||
throw new Error('无效的文件URL');
|
throw new Error("无效的文件URL");
|
||||||
}
|
}
|
||||||
|
|
||||||
const res = await fetch(url);
|
const res = await fetch(url);
|
||||||
|
|
||||||
if (!res.ok) {
|
if (!res.ok) {
|
||||||
throw new Error('网络请求失败,状态码:' + res.status);
|
throw new Error("网络请求失败,状态码:" + res.status);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ⭐ 比 blob 更轻一点(后续 zip 更快)
|
// ⭐ 比 blob 更轻一点(后续 zip 更快)
|
||||||
@@ -714,11 +777,11 @@
|
|||||||
// =====================
|
// =====================
|
||||||
internalDownload: async function (file) {
|
internalDownload: async function (file) {
|
||||||
if (!file) {
|
if (!file) {
|
||||||
console.warn('下载失败:文件对象无效', file);
|
console.warn("下载失败:文件对象无效", file);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('开始下载文件:', file.name);
|
console.log("开始下载文件:", file.name);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
let blob;
|
let blob;
|
||||||
@@ -730,14 +793,13 @@
|
|||||||
// 未上传的文件,使用本地文件对象
|
// 未上传的文件,使用本地文件对象
|
||||||
blob = file.file;
|
blob = file.file;
|
||||||
} else {
|
} else {
|
||||||
console.warn('下载失败:文件没有URL或本地文件对象', file);
|
console.warn("下载失败:文件没有URL或本地文件对象", file);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
saveAs(blob, file.name || '未命名文件');
|
saveAs(blob, file.name || "未命名文件");
|
||||||
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('文件下载失败:', err);
|
console.error("文件下载失败:", err);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -766,7 +828,7 @@
|
|||||||
// 未上传的文件,使用本地文件对象
|
// 未上传的文件,使用本地文件对象
|
||||||
content = await item.file.arrayBuffer();
|
content = await item.file.arrayBuffer();
|
||||||
} else {
|
} else {
|
||||||
console.warn('跳过失败文件:', item.name, '没有URL或本地文件对象');
|
console.warn("跳过失败文件:", item.name, "没有URL或本地文件对象");
|
||||||
failedCount++;
|
failedCount++;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -776,9 +838,8 @@
|
|||||||
zip.file(uniqueFileName, content);
|
zip.file(uniqueFileName, content);
|
||||||
|
|
||||||
addedFiles.add(uniqueFileName);
|
addedFiles.add(uniqueFileName);
|
||||||
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.warn('跳过失败文件:', item.name, err);
|
console.warn("跳过失败文件:", item.name, err);
|
||||||
failedCount++;
|
failedCount++;
|
||||||
} finally {
|
} finally {
|
||||||
current++;
|
current++;
|
||||||
@@ -793,14 +854,12 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await Promise.all(
|
await Promise.all(Array.from({ length: concurrency }, runner));
|
||||||
Array.from({ length: concurrency }, runner)
|
|
||||||
);
|
|
||||||
|
|
||||||
const blob = await zip.generateAsync({ type: "blob" });
|
const blob = await zip.generateAsync({ type: "blob" });
|
||||||
|
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
const pad = n => n < 10 ? "0" + n : n;
|
const pad = (n) => (n < 10 ? "0" + n : n);
|
||||||
|
|
||||||
const fileName =
|
const fileName =
|
||||||
now.getFullYear() +
|
now.getFullYear() +
|
||||||
@@ -812,11 +871,10 @@
|
|||||||
saveAs(blob, fileName + ".zip");
|
saveAs(blob, fileName + ".zip");
|
||||||
|
|
||||||
console.log(
|
console.log(
|
||||||
`下载完成:${total - failedCount}/${total},失败:${failedCount}`
|
`下载完成:${total - failedCount}/${total},失败:${failedCount}`,
|
||||||
);
|
);
|
||||||
|
|
||||||
callback && callback();
|
callback && callback();
|
||||||
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Batch download failed:", err);
|
console.error("Batch download failed:", err);
|
||||||
callback && callback();
|
callback && callback();
|
||||||
@@ -833,10 +891,10 @@
|
|||||||
try {
|
try {
|
||||||
URL.revokeObjectURL(file.url);
|
URL.revokeObjectURL(file.url);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.warn('Failed to revoke object URL:', e);
|
console.warn("Failed to revoke object URL:", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.files = this.files.filter(f => f._id !== file._id);
|
this.files = this.files.filter((f) => f._id !== file._id);
|
||||||
this.selectedFiles.delete(file._id);
|
this.selectedFiles.delete(file._id);
|
||||||
this.updateToolbarState();
|
this.updateToolbarState();
|
||||||
this.renderFileList();
|
this.renderFileList();
|
||||||
@@ -847,7 +905,7 @@
|
|||||||
// =====================
|
// =====================
|
||||||
internalDeleteBatch: function (files) {
|
internalDeleteBatch: function (files) {
|
||||||
if (!files || files.length === 0) return;
|
if (!files || files.length === 0) return;
|
||||||
files.forEach(file => this.internalDelete(file));
|
files.forEach((file) => this.internalDelete(file));
|
||||||
this.updateToolbarState();
|
this.updateToolbarState();
|
||||||
this.renderFileList();
|
this.renderFileList();
|
||||||
},
|
},
|
||||||
@@ -859,16 +917,92 @@
|
|||||||
|
|
||||||
this.options.permissions = permissions;
|
this.options.permissions = permissions;
|
||||||
|
|
||||||
this.$btnSelect?.prop('disabled', !permissions.select);
|
this.$btnSelect?.prop("disabled", !permissions.select);
|
||||||
this.$btnUpload?.prop('disabled', !permissions.upload);
|
this.$btnUpload?.prop("disabled", !permissions.upload);
|
||||||
this.$btnDownload?.prop('disabled', !permissions.download);
|
this.$btnDownload?.prop("disabled", !permissions.download);
|
||||||
this.$btnDelete?.prop('disabled', !permissions.delete);
|
this.$btnDelete?.prop("disabled", !permissions.delete);
|
||||||
|
|
||||||
this.$fileList.find('.file-action.download')
|
this.$fileList
|
||||||
.toggleClass('is-disabled', !permissions.download);
|
.find(".file-action.download")
|
||||||
|
.toggleClass("is-disabled", !permissions.download);
|
||||||
|
|
||||||
this.$fileList.find('.file-action.delete')
|
this.$fileList
|
||||||
.toggleClass('is-disabled', !permissions.delete);
|
.find(".file-action.delete")
|
||||||
|
.toggleClass("is-disabled", !permissions.delete);
|
||||||
|
},
|
||||||
|
|
||||||
|
// =====================
|
||||||
|
// 外部方法:切换文件选中状态
|
||||||
|
// 参数:fileId - 文件ID,isSelected - 是否选中(可选,不传则切换)
|
||||||
|
// =====================
|
||||||
|
toggleSelect: function (fileId, isSelected) {
|
||||||
|
var file = this.files.find((f) => f._id === fileId);
|
||||||
|
if (!file) return;
|
||||||
|
|
||||||
|
var currentSelected = this.selectedFiles.has(fileId);
|
||||||
|
var newSelected =
|
||||||
|
isSelected !== undefined ? isSelected : !currentSelected;
|
||||||
|
|
||||||
|
if (newSelected) {
|
||||||
|
this.selectedFiles.add(fileId);
|
||||||
|
} else {
|
||||||
|
this.selectedFiles.delete(fileId);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.updateToolbarState();
|
||||||
|
this.renderFileList();
|
||||||
|
|
||||||
|
return newSelected;
|
||||||
|
},
|
||||||
|
|
||||||
|
// =====================
|
||||||
|
// 外部方法:获取当前选中的文件列表
|
||||||
|
// =====================
|
||||||
|
getSelectedFiles: function () {
|
||||||
|
return this.files.filter((f) => this.selectedFiles.has(f._id));
|
||||||
|
},
|
||||||
|
|
||||||
|
// =====================
|
||||||
|
// 外部方法:获取当前预览的文件列表
|
||||||
|
// =====================
|
||||||
|
getPreviewFiles: function () {
|
||||||
|
return this.files.filter((f) => this.previewFiles.has(f._id));
|
||||||
|
},
|
||||||
|
|
||||||
|
// =====================
|
||||||
|
// 外部方法:获取所有文件列表
|
||||||
|
// =====================
|
||||||
|
getFiles: function () {
|
||||||
|
return this.files;
|
||||||
|
},
|
||||||
|
|
||||||
|
// =====================
|
||||||
|
// 外部方法:切换文件预览状态
|
||||||
|
// 参数:fileId - 文件ID,isPreview - 是否预览(可选,不传则切换)
|
||||||
|
// =====================
|
||||||
|
togglePreview: function (fileId, isPreview) {
|
||||||
|
var file = this.files.find((f) => f._id === fileId);
|
||||||
|
if (!file) return;
|
||||||
|
|
||||||
|
var currentPreview = this.previewFiles.has(fileId);
|
||||||
|
var newPreview = isPreview !== undefined ? isPreview : !currentPreview;
|
||||||
|
|
||||||
|
if (newPreview) {
|
||||||
|
// 开启预览前,先清除其他文件的预览状态(只允许同时预览一个)
|
||||||
|
this.previewFiles.clear();
|
||||||
|
this.previewFiles.add(fileId);
|
||||||
|
} else {
|
||||||
|
this.previewFiles.delete(fileId);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.renderFileList();
|
||||||
|
|
||||||
|
// 调用外部回调
|
||||||
|
if (typeof this.options.onPreview === "function") {
|
||||||
|
this.options.onPreview(file, newPreview);
|
||||||
|
}
|
||||||
|
|
||||||
|
return newPreview;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -876,33 +1010,33 @@
|
|||||||
// jQuery 插件封装
|
// jQuery 插件封装
|
||||||
// ==========================
|
// ==========================
|
||||||
$.fn.G3File = function (options) {
|
$.fn.G3File = function (options) {
|
||||||
|
|
||||||
var args = Array.prototype.slice.call(arguments, 1);
|
var args = Array.prototype.slice.call(arguments, 1);
|
||||||
var instance;
|
var instance;
|
||||||
|
var methodResult; // 存储方法调用结果
|
||||||
|
|
||||||
this.each(function () {
|
this.each(function () {
|
||||||
var $this = $(this);
|
var $this = $(this);
|
||||||
instance = $this.data('G3File');
|
instance = $this.data("G3File");
|
||||||
|
|
||||||
// ========= 初始化 =========
|
// ========= 初始化 =========
|
||||||
if (!instance) {
|
if (!instance) {
|
||||||
instance = Object.create(G3File);
|
instance = Object.create(G3File);
|
||||||
instance.init(this.id, options);
|
instance.init(this.id, options);
|
||||||
|
|
||||||
$this.data('G3File', instance);
|
$this.data("G3File", instance);
|
||||||
|
|
||||||
// ========= 方法调用 =========
|
// ========= 方法调用 =========
|
||||||
} else if (typeof options === 'string') {
|
} else if (typeof options === "string") {
|
||||||
var method = options;
|
var method = options;
|
||||||
|
|
||||||
if (instance[method]) {
|
if (instance[method]) {
|
||||||
instance[method].apply(instance, args);
|
// 保存方法调用结果
|
||||||
|
methodResult = instance[method].apply(instance, args);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// ⭐关键:返回 instance(支持 file.xxx)
|
// 如果是方法调用,返回方法结果;否则返回实例
|
||||||
return instance || this;
|
return methodResult !== undefined ? methodResult : instance || this;
|
||||||
};
|
};
|
||||||
|
|
||||||
})(jQuery);
|
})(jQuery);
|
||||||
@@ -31,6 +31,10 @@
|
|||||||
<div id="app">
|
<div id="app">
|
||||||
<button id="reloadBtn">重新加载文件</button>
|
<button id="reloadBtn">重新加载文件</button>
|
||||||
<button id="reloadBtn2">重新加载文件2</button>
|
<button id="reloadBtn2">重新加载文件2</button>
|
||||||
|
<button id="toggleSelectBtn">切换第一个文件选中状态</button>
|
||||||
|
<button id="getSelectedBtn">获取选中文件</button>
|
||||||
|
<button id="togglePreviewBtn">切换第一个文件预览状态</button>
|
||||||
|
<button id="getPreviewBtn">获取预览文件</button>
|
||||||
|
|
||||||
<div style="height: 600px;width: 400px;">
|
<div style="height: 600px;width: 400px;">
|
||||||
<div id="fileContainer"></div>
|
<div id="fileContainer"></div>
|
||||||
@@ -54,6 +58,14 @@
|
|||||||
onUpload: function () {
|
onUpload: function () {
|
||||||
console.log('Upload clicked');
|
console.log('Upload clicked');
|
||||||
alert('上传功能');
|
alert('上传功能');
|
||||||
|
},
|
||||||
|
onPreview: function (file, isPreview) {
|
||||||
|
console.log('预览事件:', file.name, '预览状态:', isPreview);
|
||||||
|
if (isPreview) {
|
||||||
|
alert('开始预览文件: ' + file.name);
|
||||||
|
} else {
|
||||||
|
alert('停止预览文件: ' + file.name);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -70,6 +82,45 @@
|
|||||||
{ name: '项目报告2.docx', size: 524288, uploadDate: '2024-02-20T14:45:00', url: '' },
|
{ name: '项目报告2.docx', size: 524288, uploadDate: '2024-02-20T14:45:00', url: '' },
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 测试外部方法:切换选中状态
|
||||||
|
$('#toggleSelectBtn').click(function () {
|
||||||
|
const allFiles = file.getFiles();
|
||||||
|
if (allFiles.length === 0) {
|
||||||
|
alert('请先加载文件');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 获取第一个文件的ID并切换选中状态
|
||||||
|
const firstFileId = allFiles[0]._id;
|
||||||
|
const isSelected = file.toggleSelect(firstFileId);
|
||||||
|
console.log('文件', allFiles[0].name, '选中状态:', isSelected);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 测试外部方法:获取选中文件
|
||||||
|
$('#getSelectedBtn').click(function () {
|
||||||
|
const selectedFiles = file.getSelectedFiles();
|
||||||
|
console.log('选中的文件:', selectedFiles);
|
||||||
|
alert('选中的文件数量: ' + selectedFiles.length + '\n文件列表: ' + selectedFiles.map(f => f.name).join(', '));
|
||||||
|
});
|
||||||
|
|
||||||
|
// 测试外部方法:切换预览状态
|
||||||
|
$('#togglePreviewBtn').click(function () {
|
||||||
|
const allFiles = file.getFiles();
|
||||||
|
if (allFiles.length === 0) {
|
||||||
|
alert('请先加载文件');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const firstFileId = allFiles[0]._id;
|
||||||
|
const isPreview = file.togglePreview(firstFileId);
|
||||||
|
console.log('文件', allFiles[0].name, '预览状态:', isPreview);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 测试外部方法:获取预览文件
|
||||||
|
$('#getPreviewBtn').click(function () {
|
||||||
|
const previewFiles = file.getPreviewFiles();
|
||||||
|
console.log('预览的文件:', previewFiles);
|
||||||
|
alert('预览的文件数量: ' + previewFiles.length + '\n文件列表: ' + previewFiles.map(f => f.name).join(', '));
|
||||||
|
});
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,168 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
|
||||||
|
<html lang="zh-CN">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<title>附件管理示例</title>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
background: #f5f7fa;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
/* 顶部工具栏 */
|
||||||
|
.file-toolbar {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
padding: 10px;
|
||||||
|
background: #fff;
|
||||||
|
border: 1px solid #eee;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-toolbar button {
|
||||||
|
padding: 4px 10px;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
background: #fff;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 列表 */
|
||||||
|
.file-list {
|
||||||
|
list-style: none;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
background: #fff;
|
||||||
|
border: 1px solid #eee;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 10px 12px;
|
||||||
|
border-bottom: 1px solid #f0f0f0;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-item:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* hover */
|
||||||
|
.file-item:hover {
|
||||||
|
background: #f5f7fa;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 模拟选中状态 */
|
||||||
|
.file-item.active {
|
||||||
|
background: #e6f7ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* checkbox */
|
||||||
|
.file-select {
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 文件名 */
|
||||||
|
.file-name {
|
||||||
|
flex: 1;
|
||||||
|
color: #1890ff;
|
||||||
|
cursor: pointer;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-name:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 操作区 */
|
||||||
|
.file-actions {
|
||||||
|
display: none;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-item:hover .file-actions {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-actions button {
|
||||||
|
border: none;
|
||||||
|
background: transparent;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 14px;
|
||||||
|
padding: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-actions button:hover {
|
||||||
|
background: #eee;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<!-- 工具栏 -->
|
||||||
|
|
||||||
|
<div class="file-toolbar">
|
||||||
|
<span>已选 0 个</span>
|
||||||
|
<button>删除</button>
|
||||||
|
<button>下载</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 文件列表 -->
|
||||||
|
|
||||||
|
<ul class="file-list">
|
||||||
|
<li class="file-item active">
|
||||||
|
<label class="file-select">
|
||||||
|
<input type="checkbox" checked />
|
||||||
|
</label>
|
||||||
|
|
||||||
|
```
|
||||||
|
<span class="file-name">合同.pdf</span>
|
||||||
|
|
||||||
|
<div class="file-actions">
|
||||||
|
<button title="预览">👁</button>
|
||||||
|
<button title="下载">⬇</button>
|
||||||
|
<button title="删除">🗑</button>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="file-item">
|
||||||
|
<label class="file-select">
|
||||||
|
<input type="checkbox" />
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<span class="file-name">图片.png</span>
|
||||||
|
|
||||||
|
<div class="file-actions">
|
||||||
|
<button title="预览">👁</button>
|
||||||
|
<button title="下载">⬇</button>
|
||||||
|
<button title="删除">🗑</button>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="file-item">
|
||||||
|
<label class="file-select">
|
||||||
|
<input type="checkbox" />
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<span class="file-name">说明文档.docx</span>
|
||||||
|
|
||||||
|
<div class="file-actions">
|
||||||
|
<button title="预览">👁</button>
|
||||||
|
<button title="下载">⬇</button>
|
||||||
|
<button title="删除">🗑</button>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
```
|
||||||
|
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Reference in new issue
Block a user