40760aa884
- server:Go 网关(WS 流式识别中继/计费配额/微信登录支付 mock/反馈/埋点),gummy provider 已真实联调 - desktop:Tauri 2(全局快捷键 push-to-talk/浮层/托盘/设置/登录购买/反馈/首启引导) - android:Compose 主 App + IME(键盘内录音直传) - ios:App + 键盘扩展(1A spike 实证键盘内不可录音,走 deep link 听写) - design/design-pipeline:设计系统 + token 导出 iOS/Android 主题 - doc:前后端设计文档(HTML);web:官网宣传页;todo:任务看板 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
482 lines
20 KiB
HTML
482 lines
20 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>server — 项目 TODO</title>
|
|
<style>
|
|
* { box-sizing: border-box; }
|
|
body {
|
|
margin: 0; padding: 0 0 80px;
|
|
font-family: "Noto Sans SC", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
|
color: #1f2933; background: #f7f9fb; line-height: 1.6;
|
|
-webkit-font-smoothing: antialiased;
|
|
}
|
|
header {
|
|
background: linear-gradient(135deg, #0A1F3B 0%, #15407D 100%);
|
|
color: #fff; padding: 36px 32px 28px;
|
|
}
|
|
header h1 { margin: 0 0 6px; font-size: 24px; font-weight: 700; }
|
|
header .header-meta { color: #ADC9EA; font-size: 13px; margin-top: 4px; }
|
|
.stats { display: flex; gap: 16px; margin-top: 14px; flex-wrap: wrap; }
|
|
.stat-pill {
|
|
background: rgba(255,255,255,0.12); border-radius: 20px;
|
|
padding: 4px 14px; font-size: 13px; color: #fff;
|
|
}
|
|
.stat-pill strong { font-size: 18px; font-weight: 700; margin-right: 2px; }
|
|
|
|
/* ── 筛选栏 ── */
|
|
.filter-bar {
|
|
position: sticky; top: 0; z-index: 10;
|
|
background: #fff; border-bottom: 1px solid #e4e9ef;
|
|
padding: 10px 28px; display: flex; flex-wrap: wrap; gap: 10px; align-items: center;
|
|
}
|
|
.filter-group { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
|
|
.filter-label { font-size: 12px; color: #7b8794; white-space: nowrap; }
|
|
.filter-chip, .filter-btn {
|
|
padding: 3px 12px; border-radius: 14px; border: 1.5px solid #d1d9e6;
|
|
background: #f7f9fb; color: #52606d; font-size: 12.5px; cursor: pointer;
|
|
transition: all .15s; white-space: nowrap;
|
|
}
|
|
.filter-chip:hover, .filter-btn:hover { border-color: #2563AC; color: #2563AC; }
|
|
.filter-chip.active, .filter-btn.active {
|
|
background: #2563AC; border-color: #2563AC; color: #fff;
|
|
}
|
|
.filter-sep { width: 1px; height: 20px; background: #e4e9ef; margin: 0 4px; }
|
|
|
|
/* ── 内容区 ── */
|
|
.wrap { max-width: 980px; margin: 0 auto; padding: 0 24px; }
|
|
.section-block { margin-bottom: 8px; }
|
|
.section-title {
|
|
font-size: 15px; font-weight: 700;
|
|
padding: 10px 14px; border-radius: 8px;
|
|
display: flex; align-items: center; gap: 8px; cursor: pointer;
|
|
margin: 20px 0 8px; user-select: none;
|
|
}
|
|
.section-title .s-count {
|
|
font-size: 12px; font-weight: 500; padding: 1px 8px; border-radius: 10px;
|
|
background: rgba(0,0,0,0.08);
|
|
}
|
|
.section-title .s-arrow { margin-left: auto; font-size: 12px; color: inherit; opacity: .6; }
|
|
|
|
/* 状态主题色 */
|
|
.st-open { background: #eff6ff; color: #1e40af; border-left: 4px solid #3b82f6; }
|
|
.st-doing { background: #fff7ed; color: #9a3412; border-left: 4px solid #f97316; }
|
|
.st-done { background: #fefce8; color: #854d0e; border-left: 4px solid #eab308; }
|
|
.st-accepted{ background: #f0fdf4; color: #166534; border-left: 4px solid #22c55e; }
|
|
|
|
ul.todo-list { list-style: none; margin: 0; padding: 0; }
|
|
.todo-card {
|
|
background: #fff; border: 1px solid #e4e9ef; border-radius: 10px;
|
|
padding: 14px 18px; margin-bottom: 10px;
|
|
border-left-width: 4px;
|
|
}
|
|
.todo-card.s-open { border-left-color: #3b82f6; }
|
|
.todo-card.s-doing { border-left-color: #f97316; }
|
|
.todo-card.s-done { border-left-color: #eab308; }
|
|
.todo-card.s-accepted { border-left-color: #22c55e; background: #f0fdf4; opacity: .8; }
|
|
|
|
.card-header { display: flex; align-items: flex-start; gap: 10px; flex-wrap: wrap; }
|
|
.item-title { font-weight: 600; font-size: 15px; flex: 1; }
|
|
.todo-card.s-accepted .item-title { text-decoration: line-through; color: #52606d; }
|
|
.card-badges { display: flex; gap: 6px; flex-wrap: wrap; align-items: center; }
|
|
.item-desc { font-size: 13.5px; color: #52606d; margin-top: 6px; }
|
|
.item-desc code {
|
|
background: #f0f2f5; padding: 1px 5px; border-radius: 3px;
|
|
font-family: "JetBrains Mono", monospace; font-size: 12.5px; color: #b91c1c;
|
|
}
|
|
.card-footer { display: flex; align-items: center; gap: 10px; margin-top: 10px; flex-wrap: wrap; }
|
|
.tag-row { display: flex; flex-wrap: wrap; gap: 6px; flex: 1; }
|
|
.item-meta { display: flex; flex-wrap: wrap; gap: 8px; font-size: 12px; color: #8aa3c4; }
|
|
.meta-date { white-space: nowrap; }
|
|
|
|
/* ── 标签样式 ── */
|
|
.tag { display: inline-block; padding: 2px 9px; border-radius: 10px; font-size: 12px; font-weight: 600; white-space: nowrap; }
|
|
.t-block { background: #fde8e8; color: #b91c1c; }
|
|
.t-high { background: #fef0d8; color: #9a6700; }
|
|
.t-low { background: #e6f0fb; color: #1d4ed8; }
|
|
.t-tag { background: #efefef; color: #555; cursor: pointer; transition: .12s; }
|
|
.t-tag:hover { background: #d9e8fb; color: #1d4ed8; }
|
|
.ver-badge { font-weight: 700; color: #1f7a44; }
|
|
.sub-progress-badge { background: #f0f4ff; color: #3730a3; }
|
|
|
|
/* 状态徽章 */
|
|
.status-badge { font-size: 11.5px; }
|
|
.s-open { background: #dbeafe; color: #1e40af; }
|
|
.s-doing { background: #ffedd5; color: #9a3412; }
|
|
.s-done { background: #fef9c3; color: #854d0e; }
|
|
.s-accepted { background: #dcfce7; color: #166534; }
|
|
|
|
/* ── 折叠 ── */
|
|
.section-list-wrap.collapsed { display: none; }
|
|
.empty-tip { font-size: 14px; color: #8aa3c4; margin: 8px 0 24px; }
|
|
.hidden { display: none !important; }
|
|
|
|
/* ── 改动等级徽章 ── */
|
|
.tier-1 { background: #ede9fe; color: #6d28d9; }
|
|
.tier-2 { background: #ccfbf1; color: #0f766e; }
|
|
.tier-3 { background: #f1f5f9; color: #475569; }
|
|
|
|
/* ── 拒绝按钮 & 拒绝原因 ── */
|
|
.reject-btn {
|
|
padding: 3px 10px; border-radius: 6px; border: 1.5px solid #dc2626;
|
|
background: #fff; color: #dc2626; font-size: 12px; font-weight: 600;
|
|
cursor: pointer; transition: all .15s; white-space: nowrap;
|
|
}
|
|
.reject-btn:hover { background: #dc2626; color: #fff; }
|
|
.reject-note {
|
|
margin: 8px 0 4px; padding: 7px 12px; border-radius: 6px;
|
|
background: #fef2f2; border: 1px solid #fecaca;
|
|
font-size: 13px; color: #b91c1c; line-height: 1.5;
|
|
}
|
|
.reject-date { color: #ef9999; font-size: 12px; }
|
|
|
|
/* ── 子任务区 ── */
|
|
.subtask-block {
|
|
margin-top: 12px; padding: 10px 14px;
|
|
background: #f8fafc; border: 1px solid #e4e9ef; border-radius: 8px;
|
|
}
|
|
.subtask-header {
|
|
display: flex; align-items: center; gap: 8px; margin-bottom: 8px; flex-wrap: wrap;
|
|
}
|
|
.subtask-label {
|
|
font-size: 11px; font-weight: 700; color: #7b8794;
|
|
text-transform: uppercase; letter-spacing: .6px;
|
|
}
|
|
.subtask-progress-text { font-size: 12px; color: #52606d; }
|
|
.subtask-progress-bar {
|
|
flex: 1; height: 5px; background: #e4e9ef; border-radius: 3px; min-width: 60px;
|
|
}
|
|
.subtask-progress-fill { height: 100%; background: #22c55e; border-radius: 3px; transition: width .4s; }
|
|
.subtask-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 5px; }
|
|
.subtask-item {
|
|
display: flex; align-items: center; gap: 7px; flex-wrap: wrap;
|
|
padding: 6px 10px; border-radius: 6px; font-size: 13px;
|
|
background: #fff; border: 1px solid #e8edf2;
|
|
}
|
|
.subtask-item.s-done { background: #f0fdf4; border-color: #bbf7d0; }
|
|
.subtask-item.s-doing { background: #fff7ed; border-color: #fed7aa; }
|
|
.subtask-item.s-accepted { background: #f0fdf4; border-color: #bbf7d0; opacity: .75; }
|
|
.sub-icon {
|
|
width: 16px; text-align: center; font-size: 13px; font-weight: 700; flex-shrink: 0;
|
|
}
|
|
.sub-icon.s-done { color: #16a34a; }
|
|
.sub-icon.s-doing { color: #ea580c; }
|
|
.sub-icon.s-open { color: #93aec8; }
|
|
.sub-icon.s-accepted { color: #16a34a; }
|
|
.sub-sid {
|
|
font-family: "JetBrains Mono", monospace; font-size: 11.5px;
|
|
color: #52606d; font-weight: 700; min-width: 32px; flex-shrink: 0;
|
|
}
|
|
.sub-title { flex: 1; font-size: 13px; min-width: 120px; }
|
|
.subtask-item.s-done .sub-title,
|
|
.subtask-item.s-accepted .sub-title { text-decoration: line-through; color: #52606d; }
|
|
.sub-badges { display: flex; gap: 4px; flex-wrap: wrap; align-items: center; }
|
|
.dep-section { display: flex; align-items: center; gap: 3px; }
|
|
.dep-label { font-size: 11px; color: #8aa3c4; margin-right: 2px; }
|
|
.dep-badge {
|
|
padding: 1px 6px; border-radius: 8px; font-size: 11px;
|
|
font-weight: 700; font-family: monospace; cursor: default;
|
|
}
|
|
.dep-done { background: #dcfce7; color: #166534; }
|
|
.dep-pending { background: #fee2e2; color: #b91c1c; }
|
|
|
|
/* ── 拒绝 Modal ── */
|
|
.modal-overlay {
|
|
position: fixed; inset: 0; background: rgba(0,0,0,.45);
|
|
display: flex; align-items: center; justify-content: center; z-index: 999;
|
|
}
|
|
.modal-box {
|
|
background: #fff; border-radius: 12px; padding: 28px 32px;
|
|
width: min(480px, 94vw); box-shadow: 0 20px 60px rgba(0,0,0,.2);
|
|
}
|
|
.modal-box h3 { margin: 0 0 4px; font-size: 17px; color: #1f2933; }
|
|
.modal-subtitle { font-size: 13px; color: #52606d; margin: 0 0 18px; }
|
|
.modal-label { font-size: 13px; font-weight: 600; color: #374151; margin-bottom: 6px; display: block; }
|
|
.modal-label .req { color: #dc2626; margin-left: 2px; }
|
|
.modal-textarea {
|
|
width: 100%; border: 1.5px solid #d1d9e6; border-radius: 8px;
|
|
padding: 10px 12px; font-size: 14px; font-family: inherit; resize: vertical;
|
|
min-height: 88px; outline: none; transition: border-color .15s;
|
|
}
|
|
.modal-textarea:focus { border-color: #dc2626; }
|
|
.modal-textarea.error { border-color: #dc2626; background: #fff8f8; }
|
|
.modal-err { font-size: 12px; color: #dc2626; margin-top: 4px; display: none; }
|
|
.modal-err.show { display: block; }
|
|
.modal-actions { display: flex; justify-content: flex-end; gap: 10px; margin-top: 20px; }
|
|
.modal-btn { padding: 8px 20px; border-radius: 8px; font-size: 14px; font-weight: 600; cursor: pointer; border: none; transition: all .15s; }
|
|
.modal-btn-cancel { background: #f3f4f6; color: #374151; }
|
|
.modal-btn-cancel:hover { background: #e5e7eb; }
|
|
.modal-btn-reject { background: #dc2626; color: #fff; }
|
|
.modal-btn-reject:hover { background: #b91c1c; }
|
|
.modal-cmd-wrap { margin-top: 16px; padding: 12px 14px; background: #1e293b; border-radius: 8px; }
|
|
.modal-cmd-label { font-size: 12px; color: #94a3b8; margin-bottom: 8px; }
|
|
.modal-cmd-code { font-family: "JetBrains Mono", "Fira Code", monospace; font-size: 13px; color: #86efac; word-break: break-all; display: block; }
|
|
.modal-copy-btn { margin-top: 10px; padding: 5px 14px; border-radius: 6px; background: #334155; color: #e2e8f0; border: none; font-size: 12px; cursor: pointer; }
|
|
.modal-copy-btn:hover { background: #475569; }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
|
|
<header>
|
|
<div class="wrap">
|
|
<h1>server — 项目 TODO</h1>
|
|
<div class="header-meta">生成于 2026-06-11 · 真相源 todo/todo.json</div>
|
|
<div class="stats">
|
|
<div class="stat-pill"><strong>0</strong>全部</div>
|
|
<div class="stat-pill"><strong>0</strong>待开始</div>
|
|
<div class="stat-pill"><strong>0</strong>开发中</div>
|
|
<div class="stat-pill"><strong>0</strong>待验收</div>
|
|
<div class="stat-pill"><strong>0</strong>已验收</div>
|
|
</div>
|
|
</div>
|
|
</header>
|
|
|
|
<div class="filter-bar" id="filterBar">
|
|
<div class="filter-group">
|
|
<span class="filter-label">重要度</span>
|
|
<button class="filter-btn active" data-filter-level="all">全部</button>
|
|
<button class="filter-btn" data-filter-level="high">高优</button>
|
|
<button class="filter-btn" data-filter-level="mid">重要</button>
|
|
<button class="filter-btn" data-filter-level="low">一般</button>
|
|
</div>
|
|
<div class="filter-sep"></div>
|
|
<div class="filter-group">
|
|
<span class="filter-label">状态</span>
|
|
<button class="filter-btn active" data-filter-status="all">全部</button>
|
|
<button class="filter-btn" data-filter-status="open">待开始</button>
|
|
<button class="filter-btn" data-filter-status="doing">开发中</button>
|
|
<button class="filter-btn" data-filter-status="done">待验收</button>
|
|
<button class="filter-btn" data-filter-status="accepted">已验收</button>
|
|
</div>
|
|
<div class="filter-sep"></div>
|
|
<div class="filter-group">
|
|
<span class="filter-label">改动等级</span>
|
|
<button class="filter-btn active" data-filter-tier="all">全部</button>
|
|
<button class="filter-btn" data-filter-tier="1">一级</button>
|
|
<button class="filter-btn" data-filter-tier="2">二级</button>
|
|
<button class="filter-btn" data-filter-tier="3">三级</button>
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div class="wrap">
|
|
|
|
|
|
<div class="section-block" id="section-open">
|
|
<div class="section-title st-open" data-toggle="open">
|
|
📋 待开始 <span class="s-count">0</span>
|
|
<span class="s-arrow">▴ 收起</span>
|
|
</div>
|
|
<div class="section-list-wrap " id="list-wrap-open">
|
|
<ul class="todo-list" id="list-open">
|
|
<p class="empty-tip">暂无条目</p>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
<div class="section-block" id="section-doing">
|
|
<div class="section-title st-doing" data-toggle="doing">
|
|
🔨 开发中 <span class="s-count">0</span>
|
|
<span class="s-arrow">▴ 收起</span>
|
|
</div>
|
|
<div class="section-list-wrap " id="list-wrap-doing">
|
|
<ul class="todo-list" id="list-doing">
|
|
<p class="empty-tip">暂无条目</p>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
<div class="section-block" id="section-done">
|
|
<div class="section-title st-done" data-toggle="done">
|
|
🔍 待验收 <span class="s-count">0</span>
|
|
<span class="s-arrow">▴ 收起</span>
|
|
</div>
|
|
<div class="section-list-wrap " id="list-wrap-done">
|
|
<ul class="todo-list" id="list-done">
|
|
<p class="empty-tip">暂无条目</p>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
<div class="section-block" id="section-accepted">
|
|
<div class="section-title st-accepted" data-toggle="accepted">
|
|
✅ 已验收 <span class="s-count">0</span>
|
|
<span class="s-arrow">▾ 展开</span>
|
|
</div>
|
|
<div class="section-list-wrap collapsed" id="list-wrap-accepted">
|
|
<ul class="todo-list" id="list-accepted">
|
|
<p class="empty-tip">暂无条目</p>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<!-- 拒绝验收 Modal -->
|
|
<div id="reject-modal" class="modal-overlay" style="display:none">
|
|
<div class="modal-box">
|
|
<h3>拒绝验收</h3>
|
|
<p class="modal-subtitle" id="reject-modal-subtitle"></p>
|
|
<label class="modal-label">拒绝原因<span class="req">*</span></label>
|
|
<textarea class="modal-textarea" id="reject-reason-input" rows="3" placeholder="请说明具体问题,Claude 将以此为依据修复…"></textarea>
|
|
<div class="modal-err" id="reject-reason-err">请填写拒绝原因</div>
|
|
<div id="reject-cmd-wrap" class="modal-cmd-wrap" style="display:none">
|
|
<div class="modal-cmd-label">在终端运行以下命令:</div>
|
|
<code class="modal-cmd-code" id="reject-cmd-text"></code>
|
|
<button class="modal-copy-btn" id="reject-copy-btn">复制命令</button>
|
|
</div>
|
|
<div class="modal-actions">
|
|
<button class="modal-btn modal-btn-cancel" id="reject-cancel-btn">取消</button>
|
|
<button class="modal-btn modal-btn-reject" id="reject-confirm-btn">确认拒绝</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
(function() {
|
|
let curLevel = 'all';
|
|
let curStatus = 'all';
|
|
let curTier = 'all';
|
|
let curTags = new Set();
|
|
|
|
function applyFilter() {
|
|
document.querySelectorAll('.todo-card').forEach(card => {
|
|
const lvl = card.dataset.level;
|
|
const status = card.dataset.status;
|
|
const tier = card.dataset.tier;
|
|
const tags = card.dataset.tags ? card.dataset.tags.split(',') : [];
|
|
|
|
let show = true;
|
|
if (curLevel !== 'all' && lvl !== curLevel) show = false;
|
|
if (curStatus !== 'all' && status !== curStatus) show = false;
|
|
if (curTier !== 'all' && String(tier) !== curTier) show = false;
|
|
if (curTags.size > 0 && ![...curTags].some(t => tags.includes(t))) show = false;
|
|
|
|
card.classList.toggle('hidden', !show);
|
|
});
|
|
|
|
if (curStatus !== 'all') {
|
|
const wrap = document.getElementById('list-wrap-' + curStatus);
|
|
if (wrap) {
|
|
wrap.classList.remove('collapsed');
|
|
const title = document.querySelector('[data-toggle="' + curStatus + '"] .s-arrow');
|
|
if (title) title.textContent = '▴ 收起';
|
|
}
|
|
}
|
|
}
|
|
|
|
document.querySelectorAll('[data-filter-level]').forEach(btn => {
|
|
btn.addEventListener('click', () => {
|
|
curLevel = btn.dataset.filterLevel;
|
|
document.querySelectorAll('[data-filter-level]').forEach(b => b.classList.toggle('active', b === btn));
|
|
applyFilter();
|
|
});
|
|
});
|
|
|
|
document.querySelectorAll('[data-filter-tier]').forEach(btn => {
|
|
btn.addEventListener('click', () => {
|
|
curTier = btn.dataset.filterTier;
|
|
document.querySelectorAll('[data-filter-tier]').forEach(b => b.classList.toggle('active', b === btn));
|
|
applyFilter();
|
|
});
|
|
});
|
|
|
|
document.querySelectorAll('[data-filter-status]').forEach(btn => {
|
|
btn.addEventListener('click', () => {
|
|
curStatus = btn.dataset.filterStatus;
|
|
document.querySelectorAll('[data-filter-status]').forEach(b => b.classList.toggle('active', b === btn));
|
|
applyFilter();
|
|
});
|
|
});
|
|
|
|
document.querySelectorAll('[data-filter-tag]').forEach(btn => {
|
|
btn.addEventListener('click', () => {
|
|
const t = btn.dataset.filterTag;
|
|
if (curTags.has(t)) { curTags.delete(t); btn.classList.remove('active'); }
|
|
else { curTags.add(t); btn.classList.add('active'); }
|
|
applyFilter();
|
|
});
|
|
});
|
|
|
|
document.querySelectorAll('.t-tag').forEach(tag => {
|
|
tag.addEventListener('click', e => {
|
|
e.stopPropagation();
|
|
const t = tag.dataset.tag;
|
|
const chip = document.querySelector('[data-filter-tag="'+t+'"]');
|
|
if (chip) chip.click();
|
|
});
|
|
});
|
|
|
|
document.querySelectorAll('.section-title[data-toggle]').forEach(title => {
|
|
title.addEventListener('click', () => {
|
|
const st = title.dataset.toggle;
|
|
const wrap = document.getElementById('list-wrap-' + st);
|
|
const arrow = title.querySelector('.s-arrow');
|
|
if (!wrap) return;
|
|
const collapsed = wrap.classList.toggle('collapsed');
|
|
if (arrow) arrow.textContent = collapsed ? '▾ 展开' : '▴ 收起';
|
|
});
|
|
});
|
|
|
|
// ── 拒绝验收 Modal ──
|
|
const modal = document.getElementById('reject-modal');
|
|
const reasonInput = document.getElementById('reject-reason-input');
|
|
const reasonErr = document.getElementById('reject-reason-err');
|
|
const cmdWrap = document.getElementById('reject-cmd-wrap');
|
|
const cmdText = document.getElementById('reject-cmd-text');
|
|
const copyBtn = document.getElementById('reject-copy-btn');
|
|
let rejectId = null;
|
|
|
|
document.querySelectorAll('.reject-btn').forEach(btn => {
|
|
btn.addEventListener('click', e => {
|
|
e.stopPropagation();
|
|
rejectId = btn.dataset.id;
|
|
document.getElementById('reject-modal-subtitle').textContent = '#' + rejectId + ' ' + btn.dataset.title;
|
|
reasonInput.value = '';
|
|
reasonInput.classList.remove('error');
|
|
reasonErr.classList.remove('show');
|
|
cmdWrap.style.display = 'none';
|
|
document.getElementById('reject-confirm-btn').style.display = '';
|
|
copyBtn.textContent = '复制命令';
|
|
modal.style.display = 'flex';
|
|
setTimeout(() => reasonInput.focus(), 80);
|
|
});
|
|
});
|
|
|
|
document.getElementById('reject-cancel-btn').addEventListener('click', () => {
|
|
modal.style.display = 'none';
|
|
});
|
|
|
|
modal.addEventListener('click', e => {
|
|
if (e.target === modal) modal.style.display = 'none';
|
|
});
|
|
|
|
document.getElementById('reject-confirm-btn').addEventListener('click', () => {
|
|
const reason = reasonInput.value.trim();
|
|
if (!reason) {
|
|
reasonInput.classList.add('error');
|
|
reasonErr.classList.add('show');
|
|
reasonInput.focus();
|
|
return;
|
|
}
|
|
const escaped = reason.replace(/\/g, '\\').replace(/"/g, '\"');
|
|
const cmd = '/todo reject ' + rejectId + ' ' + escaped;
|
|
cmdText.textContent = cmd;
|
|
cmdWrap.style.display = 'block';
|
|
document.getElementById('reject-confirm-btn').style.display = 'none';
|
|
});
|
|
|
|
reasonInput.addEventListener('input', () => {
|
|
if (reasonInput.value.trim()) {
|
|
reasonInput.classList.remove('error');
|
|
reasonErr.classList.remove('show');
|
|
}
|
|
});
|
|
|
|
copyBtn.addEventListener('click', () => {
|
|
navigator.clipboard.writeText(cmdText.textContent).then(() => {
|
|
copyBtn.textContent = '已复制 ✓';
|
|
});
|
|
});
|
|
})();
|
|
</script>
|
|
</body>
|
|
</html> |