修复问题,通过浏览器插件发布过的笔记后,在web页面的笔记的状态不会更改,还有一个问题,如果上传失败以后,笔记的状态就变成了在未审核页面的改成了失败,应该是在已审核页面中笔记列表的“使用状态”变成失败,可以批量将使用失败的笔记改为待发布的状态,已经发布成功的状态改为已使用。分析一下现在的流程,感觉有点混乱
This commit is contained in:
parent
2a3eac2d49
commit
320e130ea5
@ -58,9 +58,9 @@ app.get('/api/stats', (req, res) => {
|
||||
const shopCount = db.prepare('SELECT COUNT(*) as cnt FROM shops').get().cnt;
|
||||
const productCount = db.prepare('SELECT COUNT(*) as cnt FROM products').get().cnt;
|
||||
const noteCount = db.prepare('SELECT COUNT(*) as cnt FROM notes').get().cnt;
|
||||
const publishedCount = db.prepare("SELECT COUNT(*) as cnt FROM notes WHERE status = 'published'").get().cnt;
|
||||
const publishedCount = db.prepare("SELECT COUNT(*) as cnt FROM notes WHERE use_status = 'used'").get().cnt;
|
||||
const pendingReviewCount = db.prepare("SELECT COUNT(*) as cnt FROM notes WHERE status = 'generated'").get().cnt;
|
||||
const failedCount = db.prepare("SELECT COUNT(*) as cnt FROM notes WHERE status = 'failed'").get().cnt;
|
||||
const failedCount = db.prepare("SELECT COUNT(*) as cnt FROM notes WHERE status = 'failed' OR use_status = 'failed'").get().cnt;
|
||||
res.json({ shops: shopCount, products: productCount, notes: noteCount, published: publishedCount, pendingReview: pendingReviewCount, failed: failedCount });
|
||||
});
|
||||
|
||||
@ -78,7 +78,7 @@ app.use((req, res, next) => {
|
||||
setInterval(() => {
|
||||
try {
|
||||
const stale = db.prepare(
|
||||
"UPDATE notes SET status = 'approved', worker_id = NULL, claimed_at = NULL, updated_at = CURRENT_TIMESTAMP WHERE status = 'publishing' AND claimed_at < datetime('now', '-10 minutes')"
|
||||
"UPDATE notes SET status = 'approved', use_status = 'pending', worker_id = NULL, claimed_at = NULL, updated_at = CURRENT_TIMESTAMP WHERE status = 'publishing' AND claimed_at < datetime('now', '-10 minutes')"
|
||||
).run();
|
||||
if (stale.changes > 0) {
|
||||
console.log(`[定时任务] 释放了 ${stale.changes} 条超时的 publishing 笔记`);
|
||||
|
||||
@ -37,6 +37,7 @@ function initDatabase() {
|
||||
topics TEXT DEFAULT '[]',
|
||||
image_paths TEXT DEFAULT '[]',
|
||||
status TEXT DEFAULT 'pending' CHECK(status IN ('pending', 'generating', 'generated', 'draft', 'approved', 'publishing', 'published', 'failed', 'rejected')),
|
||||
use_status TEXT DEFAULT 'pending',
|
||||
error_message TEXT DEFAULT '',
|
||||
publish_result TEXT DEFAULT NULL,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
@ -67,12 +68,30 @@ function initDatabase() {
|
||||
FOREIGN KEY (shop_id) REFERENCES shops(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS generation_jobs (
|
||||
id TEXT PRIMARY KEY,
|
||||
shop_id TEXT NOT NULL,
|
||||
status TEXT DEFAULT 'pending' CHECK(status IN ('pending', 'running', 'completed', 'failed')),
|
||||
total_products INTEGER DEFAULT 0,
|
||||
total_notes INTEGER DEFAULT 0,
|
||||
completed_notes INTEGER DEFAULT 0,
|
||||
failed_notes INTEGER DEFAULT 0,
|
||||
current_product_id TEXT DEFAULT NULL,
|
||||
current_product_title TEXT DEFAULT '',
|
||||
message TEXT DEFAULT '',
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (shop_id) REFERENCES shops(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_products_shop_id ON products(shop_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_notes_product_id ON notes(product_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_notes_shop_id ON notes(shop_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_notes_status ON notes(status);
|
||||
CREATE INDEX IF NOT EXISTS idx_publish_tasks_status ON publish_tasks(status);
|
||||
CREATE INDEX IF NOT EXISTS idx_publish_tasks_shop_id ON publish_tasks(shop_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_generation_jobs_status ON generation_jobs(status);
|
||||
CREATE INDEX IF NOT EXISTS idx_generation_jobs_shop_id ON generation_jobs(shop_id);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS image_hashes (
|
||||
id TEXT PRIMARY KEY,
|
||||
@ -90,12 +109,33 @@ function initDatabase() {
|
||||
"ALTER TABLE notes ADD COLUMN image_prompt TEXT DEFAULT ''",
|
||||
"ALTER TABLE notes ADD COLUMN worker_id TEXT DEFAULT NULL",
|
||||
"ALTER TABLE notes ADD COLUMN claimed_at DATETIME DEFAULT NULL",
|
||||
"ALTER TABLE notes ADD COLUMN use_status TEXT DEFAULT 'pending'",
|
||||
"ALTER TABLE products ADD COLUMN reference_image TEXT DEFAULT ''",
|
||||
];
|
||||
for (const sql of migrate) {
|
||||
try { db.exec(sql); } catch (e) { /* column already exists */ }
|
||||
}
|
||||
|
||||
db.exec(`
|
||||
UPDATE notes
|
||||
SET use_status = 'used', status = 'approved'
|
||||
WHERE status = 'published';
|
||||
|
||||
UPDATE notes
|
||||
SET use_status = 'failed', status = 'approved'
|
||||
WHERE status = 'failed'
|
||||
AND EXISTS (
|
||||
SELECT 1 FROM publish_tasks
|
||||
WHERE publish_tasks.note_id = notes.id
|
||||
AND publish_tasks.status = 'failed'
|
||||
);
|
||||
|
||||
UPDATE notes
|
||||
SET use_status = 'pending'
|
||||
WHERE status IN ('approved', 'publishing')
|
||||
AND (use_status IS NULL OR use_status = '');
|
||||
`);
|
||||
|
||||
|
||||
// Insert default configs
|
||||
const insertConfig = db.prepare(`
|
||||
|
||||
@ -122,7 +122,7 @@ function createNoteRoutes(db) {
|
||||
return res.status(400).json({ error: 'ids array required' });
|
||||
}
|
||||
const placeholders = ids.map(() => '?').join(',');
|
||||
db.prepare('UPDATE notes SET status = ?, updated_at = CURRENT_TIMESTAMP WHERE id IN (' + placeholders + ')').run('approved', ...ids);
|
||||
db.prepare('UPDATE notes SET status = ?, use_status = ?, updated_at = CURRENT_TIMESTAMP WHERE id IN (' + placeholders + ')').run('approved', 'pending', ...ids);
|
||||
const batchNotes = db.prepare('SELECT * FROM notes WHERE id IN (' + placeholders + ')').all(...ids);
|
||||
for (const note of batchNotes) {
|
||||
createPublishTask(note);
|
||||
@ -136,21 +136,47 @@ function createNoteRoutes(db) {
|
||||
return res.status(400).json({ error: 'ids array required' });
|
||||
}
|
||||
const placeholders = ids.map(() => '?').join(',');
|
||||
db.prepare('UPDATE notes SET status = ?, updated_at = CURRENT_TIMESTAMP WHERE id IN (' + placeholders + ')').run('pending', ...ids);
|
||||
db.prepare('UPDATE notes SET status = ?, use_status = ?, updated_at = CURRENT_TIMESTAMP WHERE id IN (' + placeholders + ')').run('pending', 'pending', ...ids);
|
||||
res.json({ updated: ids.length });
|
||||
});
|
||||
|
||||
router.post('/batch/publish-reset', (req, res) => {
|
||||
const { ids } = req.body;
|
||||
if (!Array.isArray(ids) || ids.length === 0) {
|
||||
return res.status(400).json({ error: 'ids array required' });
|
||||
}
|
||||
const placeholders = ids.map(() => '?').join(',');
|
||||
const resetNotes = db.prepare(
|
||||
'SELECT * FROM notes WHERE id IN (' + placeholders + ") AND status = 'approved' AND use_status = 'failed'"
|
||||
).all(...ids);
|
||||
|
||||
const reset = db.transaction(() => {
|
||||
const result = db.prepare(
|
||||
'UPDATE notes SET use_status = ?, error_message = ?, updated_at = CURRENT_TIMESTAMP WHERE id IN (' + placeholders + ") AND status = 'approved' AND use_status = 'failed'"
|
||||
).run('pending', '', ...ids);
|
||||
for (const note of resetNotes) {
|
||||
createPublishTask({ ...note, use_status: 'pending' });
|
||||
}
|
||||
return result.changes;
|
||||
});
|
||||
|
||||
res.json({ updated: reset() });
|
||||
});
|
||||
|
||||
router.put('/:id', (req, res) => {
|
||||
const note = db.prepare('SELECT * FROM notes WHERE id = ?').get(req.params.id);
|
||||
if (!note) return res.status(404).json({ error: 'Note not found' });
|
||||
const { title, content, topics, image_paths, status, image_prompt, worker_id } = req.body;
|
||||
db.prepare(`UPDATE notes SET title=?, content=?, topics=?, image_paths=?, status=?, image_prompt=?, worker_id=?, updated_at=CURRENT_TIMESTAMP WHERE id=?`)
|
||||
const nextStatus = status || note.status;
|
||||
const nextUseStatus = nextStatus === 'approved' && note.status !== 'approved' ? 'pending' : note.use_status;
|
||||
db.prepare(`UPDATE notes SET title=?, content=?, topics=?, image_paths=?, status=?, use_status=?, image_prompt=?, worker_id=?, updated_at=CURRENT_TIMESTAMP WHERE id=?`)
|
||||
.run(
|
||||
title || note.title,
|
||||
content !== undefined ? content : note.content,
|
||||
topics ? JSON.stringify(topics) : note.topics,
|
||||
image_paths ? JSON.stringify(image_paths) : note.image_paths,
|
||||
status || note.status,
|
||||
nextStatus,
|
||||
nextUseStatus,
|
||||
image_prompt !== undefined ? image_prompt : note.image_prompt,
|
||||
worker_id !== undefined ? worker_id : note.worker_id,
|
||||
req.params.id
|
||||
@ -177,7 +203,7 @@ function createNoteRoutes(db) {
|
||||
const product = db.prepare('SELECT * FROM products WHERE id = ?').get(note.product_id);
|
||||
const productTitle = product?.title || note.title;
|
||||
|
||||
db.prepare("UPDATE notes SET status='generating', updated_at=CURRENT_TIMESTAMP WHERE id=?").run(req.params.id);
|
||||
db.prepare("UPDATE notes SET status='generating', use_status='pending', updated_at=CURRENT_TIMESTAMP WHERE id=?").run(req.params.id);
|
||||
// Cancel any pending publish tasks for this note
|
||||
db.prepare("UPDATE publish_tasks SET status='failed', updated_at=CURRENT_TIMESTAMP WHERE note_id = ? AND status IN ('pending', 'claimed', 'executing')").run(req.params.id);
|
||||
|
||||
@ -225,7 +251,7 @@ function createNoteRoutes(db) {
|
||||
router.post('/:id/complete', (req, res) => {
|
||||
const note = db.prepare('SELECT * FROM notes WHERE id = ?').get(req.params.id);
|
||||
if (!note) return res.status(404).json({ error: 'Note not found' });
|
||||
db.prepare("UPDATE notes SET status='published', publish_result=?, published_at=CURRENT_TIMESTAMP, updated_at=CURRENT_TIMESTAMP WHERE id=?")
|
||||
db.prepare("UPDATE notes SET status='approved', use_status='used', publish_result=?, error_message='', published_at=CURRENT_TIMESTAMP, updated_at=CURRENT_TIMESTAMP WHERE id=?")
|
||||
.run(JSON.stringify(req.body.result || {}), req.params.id);
|
||||
db.prepare("UPDATE publish_tasks SET status='completed', updated_at=CURRENT_TIMESTAMP WHERE note_id=? AND status != 'completed'")
|
||||
.run(req.params.id);
|
||||
@ -236,7 +262,7 @@ function createNoteRoutes(db) {
|
||||
const note = db.prepare('SELECT * FROM notes WHERE id = ?').get(req.params.id);
|
||||
if (!note) return res.status(404).json({ error: 'Note not found' });
|
||||
const { error_message } = req.body;
|
||||
db.prepare("UPDATE notes SET status='failed', error_message=?, updated_at=CURRENT_TIMESTAMP WHERE id=?")
|
||||
db.prepare("UPDATE notes SET status='approved', use_status='failed', error_message=?, updated_at=CURRENT_TIMESTAMP WHERE id=?")
|
||||
.run(error_message || 'Unknown error', req.params.id);
|
||||
db.prepare("UPDATE publish_tasks SET status='failed', error_message=?, updated_at=CURRENT_TIMESTAMP WHERE note_id=? AND status != 'completed'")
|
||||
.run(error_message || 'Unknown error', req.params.id);
|
||||
|
||||
@ -60,7 +60,7 @@ function createTaskRoutes(db) {
|
||||
"UPDATE publish_tasks SET status = 'pending', claimed_by = NULL, claimed_at = NULL, updated_at = CURRENT_TIMESTAMP WHERE shop_id = ? AND status IN ('claimed', 'executing') AND claimed_at < datetime('now', '-10 minutes')"
|
||||
).run(shop_id);
|
||||
db.prepare(
|
||||
"UPDATE notes SET status = 'approved', worker_id = NULL, claimed_at = NULL, updated_at = CURRENT_TIMESTAMP WHERE shop_id = ? AND status = 'publishing' AND claimed_at < datetime('now', '-10 minutes')"
|
||||
"UPDATE notes SET status = 'approved', use_status = 'pending', worker_id = NULL, claimed_at = NULL, updated_at = CURRENT_TIMESTAMP WHERE shop_id = ? AND status = 'publishing' AND claimed_at < datetime('now', '-10 minutes')"
|
||||
).run(shop_id);
|
||||
|
||||
const claim = db.transaction(() => {
|
||||
@ -72,7 +72,7 @@ function createTaskRoutes(db) {
|
||||
"UPDATE publish_tasks SET status = 'claimed', claimed_by = ?, claimed_at = CURRENT_TIMESTAMP, updated_at = CURRENT_TIMESTAMP WHERE id = ? AND status = 'pending'"
|
||||
).run(claimed_by, task.id);
|
||||
if (result.changes === 0) return null;
|
||||
db.prepare("UPDATE notes SET status = 'publishing', worker_id = ?, claimed_at = CURRENT_TIMESTAMP, updated_at = CURRENT_TIMESTAMP WHERE id = ?")
|
||||
db.prepare("UPDATE notes SET status = 'publishing', use_status = 'publishing', worker_id = ?, claimed_at = CURRENT_TIMESTAMP, updated_at = CURRENT_TIMESTAMP WHERE id = ?")
|
||||
.run(claimed_by, task.note_id);
|
||||
return task;
|
||||
});
|
||||
@ -112,7 +112,7 @@ function createTaskRoutes(db) {
|
||||
).run(req.params.id);
|
||||
if (result.changes === 0) return false;
|
||||
db.prepare(
|
||||
"UPDATE notes SET status = 'published', publish_result = ?, published_at = CURRENT_TIMESTAMP, updated_at = CURRENT_TIMESTAMP WHERE id = ?"
|
||||
"UPDATE notes SET status = 'approved', use_status = 'used', publish_result = ?, error_message = '', published_at = CURRENT_TIMESTAMP, worker_id = NULL, claimed_at = NULL, updated_at = CURRENT_TIMESTAMP WHERE id = ?"
|
||||
).run(JSON.stringify(req.body.result || {}), task.note_id);
|
||||
return true;
|
||||
});
|
||||
@ -142,7 +142,7 @@ function createTaskRoutes(db) {
|
||||
).run(errorMessage, req.params.id);
|
||||
if (result.changes === 0) return false;
|
||||
db.prepare(
|
||||
"UPDATE notes SET status = 'failed', error_message = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?"
|
||||
"UPDATE notes SET status = 'approved', use_status = 'failed', error_message = ?, worker_id = NULL, claimed_at = NULL, updated_at = CURRENT_TIMESTAMP WHERE id = ?"
|
||||
).run(errorMessage, task.note_id);
|
||||
return true;
|
||||
});
|
||||
@ -157,7 +157,7 @@ function createTaskRoutes(db) {
|
||||
if (!shop_id) return res.status(400).json({ error: 'shop_id required' });
|
||||
|
||||
const approvedNotes = db.prepare(
|
||||
"SELECT id, shop_id FROM notes WHERE shop_id = ? AND status = 'approved'"
|
||||
"SELECT id, shop_id FROM notes WHERE shop_id = ? AND status = 'approved' AND use_status = 'pending'"
|
||||
).all(shop_id);
|
||||
|
||||
if (approvedNotes.length === 0) {
|
||||
|
||||
@ -46,6 +46,7 @@ export const notes = {
|
||||
batchDelete: (ids) => request("/api/notes/batch-delete", { method: "POST", body: JSON.stringify({ ids }) }),
|
||||
batchApprove: (ids) => request("/api/notes/batch/approve", { method: "POST", body: JSON.stringify({ ids }) }),
|
||||
batchReject: (ids) => request("/api/notes/batch/reject", { method: "POST", body: JSON.stringify({ ids }) }),
|
||||
batchResetPublish: (ids) => request("/api/notes/batch/publish-reset", { method: "POST", body: JSON.stringify({ ids }) }),
|
||||
rewrite: (id) => request(`/api/notes/${id}/rewrite`, { method: "POST" }),
|
||||
};
|
||||
|
||||
@ -64,6 +65,7 @@ export const generate = {
|
||||
note: (data) => request("/api/generate/note", { method: "POST", body: JSON.stringify(data) }),
|
||||
batch: (data) => request("/api/generate/batch", { method: "POST", body: JSON.stringify(data) }),
|
||||
image: (data) => request("/api/generate/image", { method: "POST", body: JSON.stringify(data) }),
|
||||
jobs: (shopId) => request(`/api/generate/jobs${shopId ? "?shop_id=" + shopId : ""}`),
|
||||
};
|
||||
|
||||
export const stats = {
|
||||
|
||||
@ -19,8 +19,27 @@ const STATUS_LABEL = {
|
||||
};
|
||||
|
||||
const APPROVED_STATUSES = ["draft", "approved", "publishing", "published"];
|
||||
const USE_STATUS_MAP = {
|
||||
pending: "bg-gray-100 text-gray-600",
|
||||
publishing: "bg-orange-100 text-orange-700",
|
||||
used: "bg-emerald-100 text-emerald-800",
|
||||
failed: "bg-red-100 text-red-700",
|
||||
};
|
||||
const USE_STATUS_LABEL = {
|
||||
pending: "待发布",
|
||||
publishing: "发布中",
|
||||
used: "已使用",
|
||||
failed: "使用失败",
|
||||
};
|
||||
const PAGE_SIZE = 10;
|
||||
|
||||
function getUseStatus(note) {
|
||||
if (note.use_status) return note.use_status;
|
||||
if (note.status === "published") return "used";
|
||||
if (note.status === "publishing") return "publishing";
|
||||
return "pending";
|
||||
}
|
||||
|
||||
export default function NotesApproved() {
|
||||
const [list, setList] = useState([]);
|
||||
const [shopList, setShopList] = useState([]);
|
||||
@ -57,7 +76,7 @@ export default function NotesApproved() {
|
||||
const filteredList = useMemo(() => {
|
||||
let result = list;
|
||||
if (filterStatus) {
|
||||
result = result.filter((n) => n.status === filterStatus);
|
||||
result = result.filter((n) => getUseStatus(n) === filterStatus);
|
||||
}
|
||||
if (search.trim()) {
|
||||
const q = search.trim().toLowerCase();
|
||||
@ -107,12 +126,24 @@ export default function NotesApproved() {
|
||||
} catch (e) { toast(e.message, "error"); }
|
||||
};
|
||||
|
||||
const batchResetPublish = async () => {
|
||||
if (selected.size === 0) { toast("请先选择笔记", "error"); return; }
|
||||
if (!window.confirm("确定将选中的使用失败笔记重置为待发布吗?")) return;
|
||||
try {
|
||||
const result = await notes.batchResetPublish([...selected]);
|
||||
toast(`已重置 ${result.updated || 0} 条使用失败笔记`, "success");
|
||||
setSelected(new Set());
|
||||
load();
|
||||
} catch (e) { toast(e.message, "error"); }
|
||||
};
|
||||
|
||||
const handleExport = () => {
|
||||
const data = filteredList.map((n) => ({
|
||||
红薯名称: shopList.find((s) => s.id === n.shop_id)?.shop_name || "",
|
||||
标题: n.title,
|
||||
正文内容: n.content,
|
||||
热推话题: Array.isArray(n.topics) ? n.topics.join(", ") : n.topics,
|
||||
使用状态: USE_STATUS_LABEL[getUseStatus(n)] || getUseStatus(n),
|
||||
状态: STATUS_LABEL[n.status] || n.status,
|
||||
创建时间: n.created_at,
|
||||
更新时间: n.updated_at,
|
||||
@ -152,11 +183,11 @@ export default function NotesApproved() {
|
||||
</select>
|
||||
<input className="border rounded-lg px-3 py-2 text-sm w-48" placeholder="商品标题" value={search} onChange={(e) => setSearch(e.target.value)} />
|
||||
<select className="border rounded-lg px-3 py-2 text-sm" value={filterStatus} onChange={(e) => setFilterStatus(e.target.value)}>
|
||||
<option value="">全部状态</option>
|
||||
<option value="generated">待审核</option>
|
||||
<option value="approved">已通过</option>
|
||||
<option value="published">已发布</option>
|
||||
<option value="failed">失败</option>
|
||||
<option value="">全部使用状态</option>
|
||||
<option value="pending">待发布</option>
|
||||
<option value="publishing">发布中</option>
|
||||
<option value="used">已使用</option>
|
||||
<option value="failed">使用失败</option>
|
||||
</select>
|
||||
<select className="border rounded-lg px-3 py-2 text-sm" value={sortMode} onChange={(e) => setSortMode(e.target.value)}>
|
||||
<option value="">默认排序</option>
|
||||
@ -165,6 +196,7 @@ export default function NotesApproved() {
|
||||
</div>
|
||||
<div className="flex items-center gap-2 mt-3">
|
||||
<button onClick={batchDelete} disabled={selected.size === 0} className="px-3 py-1.5 bg-red-500 text-white text-sm rounded-lg hover:bg-red-600 disabled:opacity-40">删除</button>
|
||||
<button onClick={batchResetPublish} disabled={selected.size === 0} className="px-3 py-1.5 bg-orange-500 text-white text-sm rounded-lg hover:bg-orange-600 disabled:opacity-40">使用失败改待发布</button>
|
||||
<button onClick={batchRewrite} disabled={selected.size === 0} className="px-3 py-1.5 bg-blue-500 text-white text-sm rounded-lg hover:bg-blue-600 disabled:opacity-40">+ 一键重新生成笔记</button>
|
||||
<button onClick={handleExport} className="px-3 py-1.5 bg-green-500 text-white text-sm rounded-lg hover:bg-green-600">导出</button>
|
||||
</div>
|
||||
@ -206,8 +238,16 @@ export default function NotesApproved() {
|
||||
<button onClick={() => setImagePreview(n)} className="text-blue-600 hover:underline text-xs">查看图片({n.image_paths.length})</button>
|
||||
) : <span className="text-gray-400 text-xs">暂无图片</span>}
|
||||
</td>
|
||||
<td className="px-3 py-3"><span className="text-xs text-gray-500">未使用</span></td>
|
||||
<td className="px-3 py-3"><span className="text-xs text-green-600">正常</span></td>
|
||||
<td className="px-3 py-3">
|
||||
<span className={`px-2 py-0.5 rounded-full text-xs font-medium ${USE_STATUS_MAP[getUseStatus(n)] || "bg-gray-100 text-gray-600"}`}>
|
||||
{USE_STATUS_LABEL[getUseStatus(n)] || getUseStatus(n)}
|
||||
</span>
|
||||
</td>
|
||||
<td className="px-3 py-3">
|
||||
<span className={`text-xs ${getUseStatus(n) === "failed" ? "text-red-600" : "text-green-600"}`}>
|
||||
{getUseStatus(n) === "failed" ? "失败" : "正常"}
|
||||
</span>
|
||||
</td>
|
||||
<td className="px-3 py-3 text-gray-400 text-xs whitespace-nowrap">{n.created_at?.slice(0, 19).replace("T", " ")}</td>
|
||||
<td className="px-3 py-3 text-gray-400 text-xs whitespace-nowrap">{n.updated_at?.slice(0, 19).replace("T", " ")}</td>
|
||||
<td className="px-3 py-3 space-x-2 whitespace-nowrap">
|
||||
|
||||
Loading…
Reference in New Issue
Block a user