// 链接管理工具函数 // 格式化链接状态 export function formatLinkStatus(status) { const statusMap = { 'ACTIVE': '启用', 'INACTIVE': '禁用', 'EXPIRED': '已过期' } return statusMap[status] || status } // 获取链接状态标签类型 export function getLinkStatusType(status) { const typeMap = { 'ACTIVE': 'success', 'INACTIVE': 'danger', 'EXPIRED': 'warning' } return typeMap[status] || 'info' } // 检查链接是否过期 export function isLinkExpired(expiredAt) { if (!expiredAt) return false return new Date(expiredAt) < new Date() } // 计算剩余时间 export function getRemainingTime(expiredAt) { if (!expiredAt) return null const now = new Date() const expired = new Date(expiredAt) const diff = expired - now if (diff <= 0) return '已过期' const days = Math.floor(diff / (1000 * 60 * 60 * 24)) const hours = Math.floor((diff % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60)) const minutes = Math.floor((diff % (1000 * 60 * 60)) / (1000 * 60)) if (days > 0) return `${days}天${hours}小时` if (hours > 0) return `${hours}小时${minutes}分钟` return `${minutes}分钟` } // 生成二维码URL(使用在线服务) export function generateQRCodeUrl(data, size = 200) { return `https://api.qrserver.com/v1/create-qr-code/?size=${size}x${size}&data=${encodeURIComponent(data)}` } // 下载图片 export function downloadImage(url, filename) { const link = document.createElement('a') link.href = url link.download = filename document.body.appendChild(link) link.click() document.body.removeChild(link) } // 复制到剪贴板 export async function copyToClipboard(text) { try { await navigator.clipboard.writeText(text) return true } catch (error) { console.error('复制失败:', error) return false } } // 计算列宽(中文按双字节处理) function calcWch(val) { const s = (val ?? '').toString() let w = 0 for (const ch of s) w += ch.charCodeAt(0) > 255 ? 2 : 1 return Math.min(Math.max(w, 8), 60) // 8~60 字符宽 } // 简单日期格式化(如果字段是 Date 或 ISO 字符串) function fmtDate(v) { const d = v instanceof Date ? v : (typeof v === 'string' && !isNaN(Date.parse(v)) ? new Date(v) : null) if (!d) return v const pad = n => (n < 10 ? '0' + n : '' + n) return `${d.getFullYear()}-${pad(d.getMonth()+1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}` } // 真实 .xlsx 导出(中文不会乱码) export function exportToExcel(data, headers, filename = '导出.xlsx', sheetName = 'Sheet1') { console.log('xlsx导出数据:', data) // 动态导入 xlsx 库 import('xlsx').then(({ utils: XLSXUtils, writeFile: XLSXWriteFile }) => { // 1) 规范化数据:按 headers 顺序映射到"显示列名 -> 值" const headerLabels = headers.map(h => h.label) const rows = (data || []).map(row => { const obj = {} headers.forEach(h => { let v = row?.[h.key] // 识别并格式化日期 v = fmtDate(v) // 复杂对象转字符串 if (v && typeof v === 'object' && !(v instanceof Date)) { try { v = JSON.stringify(v) } catch { v = String(v) } } // 公式注入防护(以 = + - @ 开头的文本加前导单引号) if (typeof v === 'string' && /^[=+\-@]/.test(v)) v = "'" + v // null/undefined 转为空串,保留 0/false if (v === null || v === undefined) v = '' obj[h.label] = v }) return obj }) // 2) 生成工作表 const ws = XLSXUtils.json_to_sheet(rows, { header: headerLabels }) // 3) 列宽:根据表头和数据计算 const cols = headers.map(h => { const headW = calcWch(h.label) const dataW = rows.reduce((mx, r) => Math.max(mx, calcWch(r[h.label])), 0) return { wch: Math.max(headW, dataW) } }) ws['!cols'] = cols // 4) 冻结首行、开启筛选 if (ws['!ref']) { ws['!freeze'] = { xSplit: 0, ySplit: 1 } ws['!autofilter'] = { ref: ws['!ref'] } } // 5) 生成工作簿并下载 const wb = XLSXUtils.book_new() XLSXUtils.book_append_sheet(wb, ws, sheetName) const safeName = filename.endsWith('.xlsx') ? filename : `${filename}.xlsx` XLSXWriteFile(wb, safeName) }).catch(error => { console.error('Failed to load xlsx library:', error) // 可以在这里添加错误提示 }) }