feat: add independent direct-link sharing flow
This commit is contained in:
127
frontend/app.js
127
frontend/app.js
@@ -92,6 +92,8 @@ createApp({
|
||||
|
||||
// 分享管理
|
||||
shares: [],
|
||||
directLinks: [],
|
||||
directLinksLoading: false,
|
||||
showShareFileModal: false,
|
||||
creatingShare: false, // 创建分享中状态
|
||||
shareFileForm: {
|
||||
@@ -104,6 +106,15 @@ createApp({
|
||||
customDays: 7
|
||||
},
|
||||
shareResult: null,
|
||||
showDirectLinkModal: false,
|
||||
creatingDirectLink: false,
|
||||
directLinkForm: {
|
||||
fileName: '',
|
||||
filePath: '',
|
||||
expiryType: 'never',
|
||||
customDays: 7
|
||||
},
|
||||
directLinkResult: null,
|
||||
shareFilters: {
|
||||
keyword: '',
|
||||
type: 'all', // all/file/directory/all_files
|
||||
@@ -580,6 +591,28 @@ createApp({
|
||||
return list;
|
||||
},
|
||||
|
||||
filteredDirectLinks() {
|
||||
let list = [...this.directLinks];
|
||||
const keyword = this.shareFilters.keyword.trim().toLowerCase();
|
||||
|
||||
if (keyword) {
|
||||
list = list.filter((link) =>
|
||||
(link.file_path || '').toLowerCase().includes(keyword)
|
||||
|| (link.file_name || '').toLowerCase().includes(keyword)
|
||||
|| (link.link_code || '').toLowerCase().includes(keyword)
|
||||
|| (link.direct_url || '').toLowerCase().includes(keyword)
|
||||
);
|
||||
}
|
||||
|
||||
list.sort((a, b) => {
|
||||
const ta = a.created_at ? new Date(a.created_at).getTime() : 0;
|
||||
const tb = b.created_at ? new Date(b.created_at).getTime() : 0;
|
||||
return tb - ta;
|
||||
});
|
||||
|
||||
return list;
|
||||
},
|
||||
|
||||
adminUsersFilteredCount() {
|
||||
return Math.max(0, Number(this.adminUsersTotalCount) || 0);
|
||||
},
|
||||
@@ -1940,6 +1973,9 @@ handleDragLeave(e) {
|
||||
case 'share':
|
||||
this.openShareFileModal(this.contextMenuFile);
|
||||
break;
|
||||
case 'direct_link':
|
||||
this.openDirectLinkModal(this.contextMenuFile);
|
||||
break;
|
||||
case 'delete':
|
||||
this.confirmDeleteFile(this.contextMenuFile);
|
||||
break;
|
||||
@@ -2293,6 +2329,55 @@ handleDragLeave(e) {
|
||||
}
|
||||
},
|
||||
|
||||
openDirectLinkModal(file) {
|
||||
if (!file || file.isDirectory) {
|
||||
this.showToast('warning', '提示', '目录不支持生成直链,请选择文件');
|
||||
return;
|
||||
}
|
||||
|
||||
this.directLinkForm.fileName = file.name;
|
||||
this.directLinkForm.filePath = this.currentPath === '/'
|
||||
? `/${file.name}`
|
||||
: `${this.currentPath}/${file.name}`;
|
||||
this.directLinkForm.expiryType = 'never';
|
||||
this.directLinkForm.customDays = 7;
|
||||
this.directLinkResult = null;
|
||||
this.showDirectLinkModal = true;
|
||||
},
|
||||
|
||||
async createDirectLink() {
|
||||
if (this.creatingDirectLink) return;
|
||||
this.creatingDirectLink = true;
|
||||
|
||||
try {
|
||||
const expiryCheck = this.resolveShareExpiry(this.directLinkForm.expiryType, this.directLinkForm.customDays);
|
||||
if (!expiryCheck.valid) {
|
||||
this.showToast('warning', '提示', expiryCheck.message);
|
||||
return;
|
||||
}
|
||||
|
||||
const response = await axios.post(`${this.apiBase}/api/direct-link/create`, {
|
||||
file_path: this.directLinkForm.filePath,
|
||||
file_name: this.directLinkForm.fileName,
|
||||
expiry_days: expiryCheck.value
|
||||
});
|
||||
|
||||
if (response.data?.success) {
|
||||
this.directLinkResult = {
|
||||
...response.data,
|
||||
target_name: this.directLinkForm.fileName
|
||||
};
|
||||
this.showToast('success', '成功', '直链已创建');
|
||||
this.loadDirectLinks();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('创建直链失败:', error);
|
||||
this.showToast('error', '错误', error.response?.data?.message || '创建直链失败');
|
||||
} finally {
|
||||
this.creatingDirectLink = false;
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
// ===== 文件上传 =====
|
||||
|
||||
@@ -2485,6 +2570,40 @@ handleDragLeave(e) {
|
||||
}
|
||||
},
|
||||
|
||||
async loadDirectLinks() {
|
||||
this.directLinksLoading = true;
|
||||
try {
|
||||
const response = await axios.get(`${this.apiBase}/api/direct-link/my`);
|
||||
if (response.data?.success) {
|
||||
this.directLinks = response.data.links || [];
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载直链列表失败:', error);
|
||||
this.showToast('error', '加载失败', error.response?.data?.message || '加载直链列表失败');
|
||||
} finally {
|
||||
this.directLinksLoading = false;
|
||||
}
|
||||
},
|
||||
|
||||
async deleteDirectLink(id) {
|
||||
if (!confirm('确定要删除这个直链吗?')) return;
|
||||
|
||||
try {
|
||||
const response = await axios.delete(`${this.apiBase}/api/direct-link/${id}`);
|
||||
if (response.data?.success) {
|
||||
this.showToast('success', '成功', '直链已删除');
|
||||
this.loadDirectLinks();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('删除直链失败:', error);
|
||||
this.showToast('error', '删除失败', error.response?.data?.message || '删除直链失败');
|
||||
}
|
||||
},
|
||||
|
||||
async refreshShareResources() {
|
||||
await Promise.all([this.loadShares(), this.loadDirectLinks()]);
|
||||
},
|
||||
|
||||
async createShare() {
|
||||
this.shareForm.path = this.currentPath;
|
||||
|
||||
@@ -2728,6 +2847,10 @@ handleDragLeave(e) {
|
||||
this.copyTextToClipboard(url, '分享链接已复制到剪贴板');
|
||||
},
|
||||
|
||||
copyDirectLink(url) {
|
||||
this.copyTextToClipboard(url, '直链已复制到剪贴板');
|
||||
},
|
||||
|
||||
copySharePassword(password) {
|
||||
this.copyTextToClipboard(password, '访问密码已复制到剪贴板');
|
||||
},
|
||||
@@ -3286,7 +3409,7 @@ handleDragLeave(e) {
|
||||
break;
|
||||
case 'shares':
|
||||
// 切换到分享视图时,重新加载分享列表
|
||||
this.loadShares();
|
||||
this.refreshShareResources();
|
||||
break;
|
||||
case 'admin':
|
||||
// 切换到管理后台时,重新加载用户列表、健康检测和系统日志
|
||||
@@ -4110,7 +4233,7 @@ handleDragLeave(e) {
|
||||
watch: {
|
||||
currentView(newView) {
|
||||
if (newView === 'shares') {
|
||||
this.loadShares();
|
||||
this.refreshShareResources();
|
||||
} else if (newView === 'admin' && this.user?.is_admin) {
|
||||
this.loadUsers();
|
||||
this.loadSystemSettings();
|
||||
|
||||
Reference in New Issue
Block a user