Files
hz4th_coder 3a6d4fc683 FreeCut 自由剪辑 v1.0.0: 免费开源视频编辑器(Web)
- 多轨道时间线: 视频/图片/音频/文字, 拖拽/修剪/分割/复制/删除
- 12款滤镜特效 + 4种转场 + 4种文字动画
- 6款程序化原创配乐(Web Audio 实时合成, 零版权风险)
- Canvas 合成 + MediaRecorder 实时导出 WebM(VP9+Opus)
- 账号体系 + 每日导出额度 + 企业/AI短剧付费升级 + 管理后台
- 项目自动保存/缩略图, 移动端自适应
- 个人免费每天30次导出, 企业¥299/月 不限量, AI短剧¥499/月 不限量
2026-08-26 17:37:56 +08:00

103 lines
5.6 KiB
HTML

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>FreeCut · 管理后台</title>
<link rel="stylesheet" href="/css/style.css">
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>⚙️</text></svg>">
<style>
table { width: 100%; border-collapse: collapse; background: var(--bg2); border-radius: 10px; overflow: hidden; }
th, td { padding: 10px 12px; text-align: left; border-bottom: 1px solid var(--line); font-size: 13px; }
th { background: var(--bg3); color: var(--muted); font-weight: 600; }
.stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px,1fr)); gap: 14px; margin: 20px 0; }
.stat { background: var(--bg2); border: 1px solid var(--line); border-radius: 12px; padding: 16px; }
.stat .n { font-size: 28px; font-weight: 800; background: var(--grad); -webkit-background-clip: text; background-clip: text; color: transparent; }
.stat .l { color: var(--muted); font-size: 13px; }
select, button { margin-right: 6px; }
.wrap { max-width: 1100px; margin: 0 auto; padding: 20px 16px 60px; }
.bar-mini { height: 10px; background: var(--bg3); border-radius: 5px; overflow: hidden; display: inline-block; width: 120px; vertical-align: middle; }
.bar-mini i { display: block; height: 100%; background: var(--grad); }
</style>
</head>
<body>
<nav class="land-nav">
<span class="logo">⚙️ FreeCut 管理后台</span>
<span class="spacer"></span>
<a class="btn small ghost" href="/">← 返回首页</a>
</nav>
<div class="wrap">
<div class="stats" id="stats"></div>
<h3 style="margin:24px 0 10px">📋 升级申请</h3>
<div id="req-box"></div>
<h3 style="margin:24px 0 10px">👥 用户管理</h3>
<table>
<thead><tr><th>ID</th><th>用户名</th><th>昵称</th><th>等级</th><th>付费到期</th><th>今日导出</th><th>操作</th></tr></thead>
<tbody id="user-tbody"></tbody>
</table>
</div>
<script src="/js/common.js"></script>
<script>
const $ = s => document.querySelector(s);
async function load() {
try {
const s = await FC.api('/api/admin/stats');
$('#stats').innerHTML = `
<div class="stat"><div class="n">${s.users}</div><div class="l">注册用户</div></div>
<div class="stat"><div class="n">${s.projects}</div><div class="l">作品数</div></div>
<div class="stat"><div class="n">${s.exports}</div><div class="l">累计导出</div></div>
<div class="stat"><div class="n">近7天导出</div><div class="l">${s.days.map(d => `${d.date.slice(5)} <span class="bar-mini"><i style="width:${Math.max(4, Math.min(100, d.count * 12))}%"></i></span> ${d.count}`).join(' · ')}</div></div>`;
} catch (e) { FC.toast(e.message, 'err'); }
// 申请
try {
const r = await FC.api('/api/admin/requests');
const list = r.requests;
$('#req-box').innerHTML = !list.length ? '<p class="muted">暂无申请</p>' : `
<table><thead><tr><th>ID</th><th>用户</th><th>申请版本</th><th>联系方式</th><th>状态</th><th>时间</th><th>操作</th></tr></thead><tbody>
${list.map(x => `<tr>
<td>${x.id}</td><td>${FC.esc(x.username || x.user_id)}</td>
<td>${x.target_tier === 'business' ? '🏢 企业版' : '🎬 AI短剧版'}</td>
<td>${FC.esc(x.contact || '-')}</td>
<td>${x.status === 'pending' ? '<span class="tag" style="background:rgba(243,156,18,.15);color:var(--warn)">待审核</span>' : x.status === 'approved' ? '<span class="tag free">已开通</span>' : '<span class="tag">已拒绝</span>'}</td>
<td>${new Date(x.created_at * 1000).toLocaleString()}</td>
<td>${x.status === 'pending' ? `<button class="btn small" onclick="handleReq(${x.id},'approve')">✅ 开通(付费1年)</button> <button class="btn small danger" onclick="handleReq(${x.id},'reject')">✕ 拒绝</button>` : '-'}</td>
</tr>`).join('')}</tbody></table>`;
} catch (e) {}
// 用户
try {
const r = await FC.api('/api/admin/users');
$('#user-tbody').innerHTML = r.users.map(u => `<tr>
<td>${u.id}</td><td>${FC.esc(u.username)}</td><td>${FC.esc(u.nickname)}</td>
<td><select onchange="setTier(${u.id},this.value)">
${Object.entries({free:'个人免费',pro:'个人专业',business:'企业',ai_short:'AI短剧'}).map(([k,v]) => `<option value="${k}" ${u.tier===k?'selected':''}>${v}</option>`).join('')}
</select></td>
<td>${u.paid_expire ? new Date(u.paid_expire*1000).toLocaleDateString() : '—'}</td>
<td>${u.today_exports}</td>
<td><button class="btn small ghost" onclick="setPaid(${u.id},${u.paid_expire?'0':now()+31536000},this)">${u.paid_expire?'取消付费':'开通付费'}</button></td>
</tr>`).join('');
} catch (e) { FC.toast(e.message, 'err'); }
}
window.handleReq = async (id, action) => {
try { await FC.api('/api/admin/request', { method: 'POST', body: { id, action } }); FC.toast('已处理', 'ok'); load(); }
catch (e) { FC.toast(e.message, 'err'); }
};
window.setTier = async (id, tier) => {
try { await FC.api('/api/admin/user', { method: 'POST', body: { id, tier } }); FC.toast('已更新等级', 'ok'); }
catch (e) { FC.toast(e.message, 'err'); }
};
window.setPaid = async (id, expire, btn) => {
try {
const r = await FC.api('/api/admin/users');
const u = r.users.find(x => x.id === id);
await FC.api('/api/admin/user', { method: 'POST', body: { id, tier: u.tier, paid_expire: expire } });
FC.toast('已更新付费状态', 'ok'); load();
} catch (e) { FC.toast(e.message, 'err'); }
};
window.now = () => Math.floor(Date.now()/1000);
document.addEventListener('DOMContentLoaded', load);
</script>
</body>
</html>