feat(webui): totally convert to new webui

This commit is contained in:
2026-05-04 20:33:22 +08:00
parent 741d328430
commit fa07b340e7
129 changed files with 1938 additions and 17824 deletions
+92
View File
@@ -0,0 +1,92 @@
import type { ApiError } from '@/api/client'
export function formatDateTime(value?: string | null) {
if (!value) return '未记录'
const date = new Date(value)
if (Number.isNaN(date.getTime())) return value
return new Intl.DateTimeFormat('zh-CN', {
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
}).format(date)
}
export function formatFullDateTime(value?: string | null) {
if (!value) return '未记录'
const date = new Date(value)
if (Number.isNaN(date.getTime())) return value
return new Intl.DateTimeFormat('zh-CN', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
}).format(date)
}
export function statusLabel(status?: string | null) {
const labels: Record<string, string> = {
success: '成功',
failure: '失败',
failed: '失败',
pending: '进行中',
running: '进行中',
already_submitted: '已提交',
out_of_time: '超出时间',
unknown: '未知',
manual: '手动',
scheduler: '定时',
scheduled: '定时',
admin: '管理员',
}
return labels[status ?? ''] ?? status ?? '未知'
}
export function statusTone(status?: string | null) {
if (status === 'success' || status === 'already_submitted') return 'success'
if (status === 'pending' || status === 'running') return 'warning'
if (status === 'failure' || status === 'failed' || status === 'out_of_time') return 'danger'
return 'neutral'
}
export function cronLabel(value?: string | null) {
if (!value) return '未启用定时'
if (value === '0 20 * * *') return '每天 20:00'
return value
}
export function parseJson<T>(value?: string | null, fallback: T = {} as T) {
if (!value) return fallback
try {
return JSON.parse(value) as T
} catch {
return fallback
}
}
export function stringifyJson(value: unknown) {
try {
return JSON.stringify(value, null, 2)
} catch {
return String(value)
}
}
export function extractErrorMessage(error: unknown) {
if (typeof error === 'object' && error && 'message' in error) {
return String((error as ApiError).message)
}
if (error instanceof Error) return error.message
return '操作失败,请稍后重试'
}
export function boolLabel(value?: boolean | null) {
return value ? '是' : '否'
}
export function clampText(value?: string | null, fallback = '未命名') {
const trimmed = value?.trim()
return trimmed || fallback
}
-145
View File
@@ -1,145 +0,0 @@
/**
* 格式化日期时间
* @param {string|Date} date - 日期
* @param {boolean} includeTime - 是否包含时间
* @returns {string}
*/
export function formatDateTime(date, includeTime = true) {
if (!date) return '-';
const d = new Date(date);
if (isNaN(d.getTime())) return '-';
const year = d.getFullYear();
const month = String(d.getMonth() + 1).padStart(2, '0');
const day = String(d.getDate()).padStart(2, '0');
if (!includeTime) {
return `${year}-${month}-${day}`;
}
const hours = String(d.getHours()).padStart(2, '0');
const minutes = String(d.getMinutes()).padStart(2, '0');
const seconds = String(d.getSeconds()).padStart(2, '0');
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
}
/**
* 格式化相对时间(多久之前)
* @param {string|Date} date - 日期
* @returns {string}
*/
export function formatRelativeTime(date) {
if (!date) return '-';
const d = new Date(date);
if (isNaN(d.getTime())) return '-';
const now = new Date();
const diff = now - d; // 毫秒差
const seconds = Math.floor(diff / 1000);
const minutes = Math.floor(seconds / 60);
const hours = Math.floor(minutes / 60);
const days = Math.floor(hours / 24);
if (seconds < 60) return '刚刚';
if (minutes < 60) return `${minutes} 分钟前`;
if (hours < 24) return `${hours} 小时前`;
if (days < 7) return `${days} 天前`;
return formatDateTime(date, false);
}
/**
* 格式化文件大小
* @param {number} bytes - 字节数
* @returns {string}
*/
export function formatFileSize(bytes) {
if (!bytes || bytes === 0) return '0 B';
const k = 1024;
const sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return `${(bytes / Math.pow(k, i)).toFixed(2)} ${sizes[i]}`;
}
/**
* 防抖函数
* @param {Function} fn - 要防抖的函数
* @param {number} delay - 延迟时间(毫秒)
* @returns {Function}
*/
export function debounce(fn, delay = 300) {
let timer = null;
return function (...args) {
if (timer) clearTimeout(timer);
timer = setTimeout(() => {
fn.apply(this, args);
}, delay);
};
}
/**
* 节流函数
* @param {Function} fn - 要节流的函数
* @param {number} delay - 延迟时间(毫秒)
* @returns {Function}
*/
export function throttle(fn, delay = 300) {
let timer = null;
let lastTime = 0;
return function (...args) {
const now = Date.now();
if (now - lastTime < delay) {
if (timer) clearTimeout(timer);
timer = setTimeout(() => {
lastTime = now;
fn.apply(this, args);
}, delay);
} else {
lastTime = now;
fn.apply(this, args);
}
};
}
/**
* 复制文本到剪贴板
* @param {string} text - 要复制的文本
* @returns {Promise<boolean>}
*/
export async function copyToClipboard(text) {
try {
if (navigator.clipboard && window.isSecureContext) {
await navigator.clipboard.writeText(text);
return true;
} else {
// 降级方案
const textArea = document.createElement('textarea');
textArea.value = text;
textArea.style.position = 'fixed';
textArea.style.left = '-999999px';
document.body.appendChild(textArea);
textArea.focus();
textArea.select();
try {
document.execCommand('copy');
textArea.remove();
return true;
} catch (error) {
console.error('复制失败', error);
textArea.remove();
return false;
}
}
} catch (error) {
console.error('复制失败', error);
return false;
}
}