Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 799afd1ea5 | |||
| 04d68d0b2c |
@@ -455,6 +455,26 @@ export function createAdminApp(services) {
|
||||
}
|
||||
});
|
||||
|
||||
adminApi.get('/wechat/news-morning-draft/today', requireAdmin, async (_req, res) => {
|
||||
if (!wechatNewsMorningDraftService) {
|
||||
return res.status(503).json({ message: '新闻早报草稿推送未启用' });
|
||||
}
|
||||
return res.json(await wechatNewsMorningDraftService.getTodayStatus());
|
||||
});
|
||||
|
||||
adminApi.get('/wechat/news-morning-draft/preview/today', requireAdmin, async (_req, res) => {
|
||||
if (!wechatNewsMorningDraftService) {
|
||||
return res.status(503).json({ message: '新闻早报草稿推送未启用' });
|
||||
}
|
||||
try {
|
||||
return res.json(await wechatNewsMorningDraftService.previewToday());
|
||||
} catch (error) {
|
||||
return res.status(400).json({
|
||||
message: error instanceof Error ? error.message : '预览失败',
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
adminApi.post('/wechat/news-morning-draft/push', requireAdmin, async (req, res) => {
|
||||
if (!wechatNewsMorningDraftService) {
|
||||
return res.status(503).json({ message: '新闻早报草稿推送未启用' });
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
getWechatIntentRouterConfig,
|
||||
getWechatIntentRouterRuntime,
|
||||
getWechatNewsMorningDraftConfig,
|
||||
getWechatNewsMorningDraftTodayStatus,
|
||||
listAdminUsers,
|
||||
listLlmProviderKeys,
|
||||
listWechatBindings,
|
||||
@@ -18,7 +19,7 @@ import {
|
||||
listWechatWebNotifications,
|
||||
patchWechatIntentRouterConfig,
|
||||
patchWechatNewsMorningDraftConfig,
|
||||
previewWechatNewsMorningDraft,
|
||||
previewWechatNewsMorningDraftToday,
|
||||
pushWechatNewsMorningDraft,
|
||||
resumeWechatDigest,
|
||||
updateWechatScheduleLlmConfig,
|
||||
@@ -36,6 +37,7 @@ import type {
|
||||
WechatNewsMorningDraftPreview,
|
||||
WechatNewsMorningDraftRun,
|
||||
WechatNewsMorningDraftTemplate,
|
||||
WechatNewsMorningDraftTodayStatus,
|
||||
WechatWebNotification,
|
||||
} from '../../types';
|
||||
import { formatTime } from '../utils/format';
|
||||
@@ -188,6 +190,7 @@ export function WechatPage() {
|
||||
const [savedNewsDraftForm, setSavedNewsDraftForm] = useState<NewsMorningDraftForm | null>(null);
|
||||
const [newsDraftTemplate, setNewsDraftTemplate] = useState<WechatNewsMorningDraftTemplate | null>(null);
|
||||
const [newsDraftPreview, setNewsDraftPreview] = useState<WechatNewsMorningDraftPreview | null>(null);
|
||||
const [newsDraftTodayStatus, setNewsDraftTodayStatus] = useState<WechatNewsMorningDraftTodayStatus | null>(null);
|
||||
const [newsDraftRuns, setNewsDraftRuns] = useState<WechatNewsMorningDraftRun[]>([]);
|
||||
const [newsDraftSaving, setNewsDraftSaving] = useState(false);
|
||||
const [newsDraftWorking, setNewsDraftWorking] = useState(false);
|
||||
@@ -274,10 +277,40 @@ export function WechatPage() {
|
||||
}
|
||||
}, [deliveryStatus, digestStatus, messageStatus, notificationStatus, search]);
|
||||
|
||||
const refreshTodayNewsMorningDraft = useCallback(async (sourceUserId?: string) => {
|
||||
if (!sourceUserId?.trim()) {
|
||||
setNewsDraftTodayStatus(null);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const status = await getWechatNewsMorningDraftTodayStatus();
|
||||
setNewsDraftTodayStatus(status);
|
||||
if (status.todayPageReady) {
|
||||
setNewsDraftPreview(await previewWechatNewsMorningDraftToday());
|
||||
} else {
|
||||
setNewsDraftPreview(null);
|
||||
}
|
||||
} catch (err) {
|
||||
setNewsDraftTodayStatus(null);
|
||||
if (!(err instanceof Error && /404|503/u.test(err.message))) {
|
||||
setError(err instanceof Error ? err.message : '加载今日早报失败');
|
||||
}
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
void load();
|
||||
}, [load]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!newsDraftForm?.sourceUserId.trim()) {
|
||||
setNewsDraftTodayStatus(null);
|
||||
setNewsDraftPreview(null);
|
||||
return;
|
||||
}
|
||||
void refreshTodayNewsMorningDraft(newsDraftForm.sourceUserId);
|
||||
}, [newsDraftForm?.sourceUserId, refreshTodayNewsMorningDraft]);
|
||||
|
||||
const runAction = async (action: () => Promise<unknown>) => {
|
||||
setBusy(true);
|
||||
setError(null);
|
||||
@@ -405,7 +438,14 @@ export function WechatPage() {
|
||||
setNewsDraftWorking(true);
|
||||
setError(null);
|
||||
try {
|
||||
setNewsDraftPreview(await previewWechatNewsMorningDraft());
|
||||
const status = await getWechatNewsMorningDraftTodayStatus();
|
||||
setNewsDraftTodayStatus(status);
|
||||
if (status.todayPageReady) {
|
||||
setNewsDraftPreview(await previewWechatNewsMorningDraftToday());
|
||||
} else {
|
||||
setNewsDraftPreview(null);
|
||||
setError(status.message ?? '今日新闻早报尚未生成');
|
||||
}
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : '预览失败');
|
||||
} finally {
|
||||
@@ -442,6 +482,7 @@ export function WechatPage() {
|
||||
setNotice(`草稿已写入微信后台,media_id:${result.draftMediaId ?? '—'}`);
|
||||
setNewsDraftPreview(result.preview ?? null);
|
||||
setNewsDraftRuns(await listWechatNewsMorningDraftRuns(10));
|
||||
await refreshTodayNewsMorningDraft(newsDraftForm.sourceUserId);
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : '推送失败');
|
||||
setNotice(null);
|
||||
@@ -579,6 +620,72 @@ export function WechatPage() {
|
||||
{newsDraftSaving ? '保存中…' : newsDraftDirty ? '保存配置' : '已保存'}
|
||||
</button>
|
||||
</div>
|
||||
{newsDraftTodayStatus ? (
|
||||
<div
|
||||
className="admin-card"
|
||||
style={{ marginTop: 16, background: 'var(--panel-2, rgba(255,255,255,.03))' }}
|
||||
>
|
||||
<div className="admin-card-head">
|
||||
<div>
|
||||
<h3 style={{ marginBottom: 4 }}>今日早报 · {newsDraftTodayStatus.dateKey}</h3>
|
||||
<p className="muted" style={{ marginTop: 0 }}>
|
||||
期望文件名:<code>{newsDraftTodayStatus.expectedSlug}.html</code>
|
||||
</p>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
className="ghost-btn"
|
||||
onClick={() => void refreshTodayNewsMorningDraft(newsDraftForm.sourceUserId)}
|
||||
disabled={busy || newsDraftWorking || !newsDraftForm.sourceUserId.trim()}
|
||||
>
|
||||
刷新
|
||||
</button>
|
||||
</div>
|
||||
<dl className="admin-dl">
|
||||
<div>
|
||||
<dt>生成状态</dt>
|
||||
<dd className={newsDraftTodayStatus.todayPageReady ? 'wechat-ok' : 'text-error'}>
|
||||
{newsDraftTodayStatus.todayPageReady ? '今日页面已生成' : newsDraftTodayStatus.message ?? '待生成'}
|
||||
</dd>
|
||||
</div>
|
||||
{newsDraftTodayStatus.page ? (
|
||||
<>
|
||||
<div>
|
||||
<dt>页面</dt>
|
||||
<dd>
|
||||
<a href={newsDraftTodayStatus.page.publicUrl} target="_blank" rel="noreferrer">
|
||||
{newsDraftTodayStatus.page.slug}
|
||||
</a>
|
||||
</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>更新时间</dt>
|
||||
<dd>{dateLabel(newsDraftTodayStatus.page.modifiedAt)}</dd>
|
||||
</div>
|
||||
</>
|
||||
) : newsDraftTodayStatus.latestPageSlug ? (
|
||||
<div>
|
||||
<dt>最近页面</dt>
|
||||
<dd className="muted">{newsDraftTodayStatus.latestPageSlug}(非今日)</dd>
|
||||
</div>
|
||||
) : null}
|
||||
{newsDraftTodayStatus.autoPushEnabled ? (
|
||||
<div>
|
||||
<dt>自动推送</dt>
|
||||
<dd>
|
||||
计划 {String(newsDraftTodayStatus.pushHour ?? 6).padStart(2, '0')}:
|
||||
{String(newsDraftTodayStatus.pushMinute ?? 0).padStart(2, '0')}
|
||||
{newsDraftTodayStatus.runs.push?.status === 'success'
|
||||
? ' · 今日已推送草稿'
|
||||
: newsDraftTodayStatus.runs.push?.status === 'failed'
|
||||
? ` · 推送失败:${newsDraftTodayStatus.runs.push.errorMessage ?? '—'}`
|
||||
: ' · 待推送'}
|
||||
</dd>
|
||||
</div>
|
||||
) : null}
|
||||
</dl>
|
||||
</div>
|
||||
) : null}
|
||||
<div className="admin-form" style={{ marginTop: 16 }}>
|
||||
<label>
|
||||
<input
|
||||
@@ -600,7 +707,7 @@ export function WechatPage() {
|
||||
setNewsDraftForm((current) => current && { ...current, autoPushEnabled: event.target.checked })
|
||||
}
|
||||
/>{' '}
|
||||
定时自动推送(需 Portal 侧调度读取本配置)
|
||||
定时自动推送(Portal 在推送时间前约 1 小时自动生成当日页面,到点写入草稿箱)
|
||||
</label>
|
||||
<label>
|
||||
推送时间(北京时间)
|
||||
@@ -709,7 +816,7 @@ export function WechatPage() {
|
||||
) : null}
|
||||
{newsDraftPreview ? (
|
||||
<div className="admin-card" style={{ marginTop: 16, background: 'var(--panel-2, rgba(255,255,255,.03))' }}>
|
||||
<h3 style={{ marginBottom: 8 }}>预览</h3>
|
||||
<h3 style={{ marginBottom: 8 }}>草稿预览</h3>
|
||||
<dl className="admin-dl">
|
||||
<div>
|
||||
<dt>页面</dt>
|
||||
|
||||
@@ -59,6 +59,7 @@ import type {
|
||||
WechatNewsMorningDraftPreview,
|
||||
WechatNewsMorningDraftRun,
|
||||
WechatNewsMorningDraftTemplate,
|
||||
WechatNewsMorningDraftTodayStatus,
|
||||
WechatIntentRouterAdminConfig,
|
||||
WechatIntentRouterConfigState,
|
||||
WechatIntentRouterRuntimeState,
|
||||
@@ -486,6 +487,14 @@ export async function previewWechatNewsMorningDraft(): Promise<WechatNewsMorning
|
||||
return portalFetch<WechatNewsMorningDraftPreview>('/admin-api/wechat/news-morning-draft/preview');
|
||||
}
|
||||
|
||||
export async function getWechatNewsMorningDraftTodayStatus(): Promise<WechatNewsMorningDraftTodayStatus> {
|
||||
return portalFetch<WechatNewsMorningDraftTodayStatus>('/admin-api/wechat/news-morning-draft/today');
|
||||
}
|
||||
|
||||
export async function previewWechatNewsMorningDraftToday(): Promise<WechatNewsMorningDraftPreview> {
|
||||
return portalFetch<WechatNewsMorningDraftPreview>('/admin-api/wechat/news-morning-draft/preview/today');
|
||||
}
|
||||
|
||||
export async function pushWechatNewsMorningDraft(options?: { dryRun?: boolean }): Promise<{
|
||||
ok?: boolean;
|
||||
dryRun?: boolean;
|
||||
|
||||
@@ -357,6 +357,9 @@ export type WechatNewsMorningDraftPreview = {
|
||||
publicUrl: string;
|
||||
modifiedAt: number;
|
||||
hasThumb: boolean;
|
||||
isTodayPage?: boolean;
|
||||
dateKey?: string;
|
||||
expectedSlug?: string;
|
||||
};
|
||||
article: {
|
||||
title: string;
|
||||
@@ -384,6 +387,25 @@ export type WechatNewsMorningDraftRun = {
|
||||
createdAt: number;
|
||||
};
|
||||
|
||||
export type WechatNewsMorningDraftTodayStatus = {
|
||||
configured: boolean;
|
||||
dateKey: string;
|
||||
expectedSlug: string;
|
||||
todayPageReady: boolean;
|
||||
page: WechatNewsMorningDraftPreview['page'] | null;
|
||||
latestPageSlug?: string | null;
|
||||
autoGenerateEnabled?: boolean;
|
||||
autoPushEnabled?: boolean;
|
||||
pushHour?: number;
|
||||
pushMinute?: number;
|
||||
generateLeadMinutes?: number;
|
||||
message?: string | null;
|
||||
runs: {
|
||||
generate: WechatNewsMorningDraftRun | null;
|
||||
push: WechatNewsMorningDraftRun | null;
|
||||
};
|
||||
};
|
||||
|
||||
export type WechatIntentRouterAdminConfig = {
|
||||
enabled: boolean;
|
||||
shadowMode: boolean;
|
||||
|
||||
Reference in New Issue
Block a user