Files
vps-manager/static/js/api.js
T
gouki cbb32ea3b0 fix(expiry): 已过期资产不再从到期统计中消失,总览页改为到期日历
get_expiring/get_overview 去掉 expiry_date>=today 下界(仅排除已注销)并新增 overdue_count;renewal_service 续费提醒纳入已过期、文案区分已过期N天/今天到期/N天后;dashboard 用到期日历逐条显示具体到期日与倒计时;api.js 新增 Fmt.countdownText 统一文案。
2026-09-18 08:41:21 +00:00

138 lines
7.6 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* API 客户端与格式化工具 */
window.VpsApi = (function () {
const CFG = window.APP_CONFIG || { appName: 'VPS 资产管理系统' };
function getApiKey() { return localStorage.getItem('vps_api_key') || ''; }
function setApiKey(k) { localStorage.setItem('vps_api_key', k || ''); }
async function request(path, options = {}) {
const headers = { 'Content-Type': 'application/json' };
const key = getApiKey();
if (key) headers['X-API-Key'] = key;
const res = await fetch('/api' + path, { ...options, headers: { ...headers, ...(options.headers || {}) } });
if (!res.ok) {
let msg = res.statusText;
try { const e = await res.json(); msg = (typeof e.detail === 'string' ? e.detail : JSON.stringify(e.detail)) || msg; } catch (_) {}
throw new Error(msg);
}
if (res.status === 204) return null;
return res.json();
}
return {
CFG, getApiKey, setApiKey,
get: (p) => request(p),
post: (p, body) => request(p, { method: 'POST', body: JSON.stringify(body) }),
put: (p, body) => request(p, { method: 'PUT', body: JSON.stringify(body) }),
del: (p) => request(p, { method: 'DELETE' }),
};
})();
/* 授权登录来源预设:key = 入库值(oauth_provider,统一英文 slug),label = 展示名,
* group = 表单分组顺序。国内三方登录排在最前(支付宝 / 淘宝 / 微博 等),
* 手填的自定义值由 store.oauthCustom 追加成候选,不需要动后端字典。 */
const OAUTH_PRESETS = [
{ key: 'alipay', label: '支付宝', group: '国内' },
{ key: 'taobao', label: '淘宝', group: '国内' },
{ key: 'wechat', label: '微信', group: '国内' },
{ key: 'qq', label: 'QQ', group: '国内' },
{ key: 'weibo', label: '微博', group: '国内' },
{ key: 'douyin', label: '抖音', group: '国内' },
{ key: 'baidu', label: '百度', group: '国内' },
{ key: 'jd', label: '京东', group: '国内' },
{ key: 'xiaohongshu', label: '小红书', group: '国内' },
{ key: 'bilibili', label: 'B站', group: '国内' },
{ key: 'meituan', label: '美团', group: '国内' },
{ key: 'dingtalk', label: '钉钉', group: '国内' },
{ key: 'feishu', label: '飞书', group: '国内' },
{ key: 'huawei', label: '华为', group: '国内' },
{ key: 'mi', label: '小米', group: '国内' },
{ key: 'google', label: 'Google', group: '国际' },
{ key: 'apple', label: 'Apple', group: '国际' },
{ key: 'microsoft', label: 'Microsoft', group: '国际' },
{ key: 'facebook', label: 'Facebook', group: '国际' },
{ key: 'x', label: 'X (Twitter)', group: '国际' },
{ key: 'yahoo', label: 'Yahoo', group: '国际' },
{ key: 'line', label: 'LINE', group: '国际' },
{ key: 'kakao', label: 'Kakao', group: '国际' },
{ key: 'naver', label: 'NAVER', group: '国际' },
{ key: 'amazon', label: 'Amazon', group: '国际' },
{ key: 'paypal', label: 'PayPal', group: '国际' },
{ key: 'discord', label: 'Discord', group: '国际' },
{ key: 'telegram', label: 'Telegram', group: '国际' },
{ key: 'reddit', label: 'Reddit', group: '国际' },
{ key: 'linkedin', label: 'LinkedIn', group: '国际' },
{ key: 'steam', label: 'Steam', group: '国际' },
{ key: 'epic', label: 'Epic Games', group: '国际' },
{ key: 'github', label: 'GitHub', group: '开发者' },
{ key: 'gitlab', label: 'GitLab', group: '开发者' },
{ key: 'bitbucket', label: 'Bitbucket', group: '开发者' },
{ key: 'cloudflare', label: 'Cloudflare', group: '开发者' },
{ key: 'huggingface', label: 'Hugging Face', group: '开发者' },
];
window.VpsFmt = {
TYPE_LABELS: { vps: 'VPS', domain: '域名', ai_agent: 'AI账号', cloudflare: 'Cloudflare', other: '其他' },
STATUS_LABELS: { active: '使用中', expired: '已过期', stopped: '已停止', cancelled: '已注销', unknown: '未知' },
CATEGORY_LABELS: { vps: 'VPS', domain: '域名', ai_agent: 'AI账号', cloudflare: 'Cloudflare', other: '其他' },
// 平台可提供的服务(综合平台多标签):字段对应 Provider.services 逗号分隔列表
SERVICES_LABELS: { vps: '云服务器', domain: '域名', ai_agent: 'AI服务', cloudflare: 'Cloudflare', ssl_cert: 'SSL证书', cdn: 'CDN', dns: 'DNS', other: '其他' },
CYCLE_LABELS: { monthly: '月付', quarterly: '季付', yearly: '年付' },
// 凭据库登录方式:字段对应 Credential.login_type
LOGIN_TYPE_LABELS: { password: '密码登录', oauth: '授权登录', other: '其他' },
/* 授权登录来源候选(表单 chips + datalist 建议)
*
* 清单在上面 OAUTH_PRESETS;这里只暴露给视图使用。
*/
OAUTH_PRESETS,
// 短展示名:命中预设给中文,否则原样返回(自定义值不加工)
oauthLabel(k) {
const p = OAUTH_PRESETS.find(x => x.key === (k || '').trim().toLowerCase());
return p ? p.label : (k || '');
},
// 详情展示:「支付宝(alipay)」,自定义值只显示原文
oauthDetail(k) {
const p = OAUTH_PRESETS.find(x => x.key === (k || '').trim().toLowerCase());
return p ? p.label + '(' + p.key + ')' : (k || '');
},
// 归一化:允许输入中文名或大小写不一致的 slug,落到规范 key 上,避免同来源存成多种写法
normalizeOauth(raw) {
const v = (raw || '').trim();
if (!v) return '';
const lower = v.toLowerCase();
const p = OAUTH_PRESETS.find(x => x.key === lower || x.label.toLowerCase() === lower);
return p ? p.key : v;
},
loginTypeBadge(t) {
return { password: 'bg-slate-500/10 text-slate-600 dark:text-slate-400', oauth: 'bg-emerald-500/10 text-emerald-600 dark:text-emerald-400', other: 'bg-amber-500/10 text-amber-600 dark:text-amber-400' }[t] || 'bg-slate-500/10 text-slate-500';
},
typeBadge(t) {
return { vps: 'bg-blue-500/10 text-blue-600 dark:text-blue-400', domain: 'bg-emerald-500/10 text-emerald-600 dark:text-emerald-400', ai_agent: 'bg-violet-500/10 text-violet-600 dark:text-violet-400', cloudflare: 'bg-orange-500/10 text-orange-600 dark:text-orange-400', other: 'bg-slate-500/10 text-slate-600 dark:text-slate-400' }[t] || 'bg-slate-500/10 text-slate-500';
},
statusBadge(s) {
return { active: 'bg-emerald-500/10 text-emerald-600 dark:text-emerald-400', expired: 'bg-red-500/10 text-red-600 dark:text-red-400', stopped: 'bg-slate-500/10 text-slate-500', cancelled: 'bg-slate-500/10 text-slate-400', unknown: 'bg-amber-500/10 text-amber-600 dark:text-amber-400' }[s] || 'bg-slate-500/10 text-slate-500';
},
expiryText(d) {
if (d === null || d === undefined) return 'text-slate-400';
if (d < 0) return 'text-red-600 dark:text-red-400 font-semibold';
if (d <= 7) return 'text-red-600 dark:text-red-400 font-semibold';
if (d <= 30) return 'text-amber-600 dark:text-amber-400';
return 'text-slate-500 dark:text-slate-400';
},
expiryBadge(d) {
if (d !== null && d !== undefined && d <= 7) return 'bg-red-500/10 text-red-600 dark:text-red-400';
if (d !== null && d !== undefined && d <= 30) return 'bg-amber-500/10 text-amber-600 dark:text-amber-400';
return 'bg-slate-500/10 text-slate-500';
},
/* 倒计时文案:统一「距到期还有多久」的表述,负数归一为「已过期 N 天」,避免直出 "-3 天" */
countdownText(d) {
if (d === null || d === undefined) return '未设到期日';
if (d < 0) return '已过期 ' + Math.abs(d) + ' 天';
if (d === 0) return '今天到期';
return d + ' 天后';
},
checkBadge(s) {
return { pass: 'bg-emerald-500/10 text-emerald-600 dark:text-emerald-400', warn: 'bg-amber-500/10 text-amber-600 dark:text-amber-400', fail: 'bg-red-500/10 text-red-600 dark:text-red-400', unknown: 'bg-slate-500/10 text-slate-500' }[s] || 'bg-slate-500/10 text-slate-500';
},
};