forked from wangziqi/gongxue-base
feat: notify tenants about public bank syncs
This commit is contained in:
@@ -13,11 +13,13 @@ import {
|
||||
loadImportTemplate,
|
||||
loadPublicQuestionBankConflicts,
|
||||
loadPublicQuestionBanks,
|
||||
loadTenantContentNotifications,
|
||||
previewContentImport,
|
||||
resolvePublicQuestionBankConflict,
|
||||
resolvePublicQuestionBankConflicts,
|
||||
runImportPostCheck,
|
||||
syncPublicQuestionBank,
|
||||
updateTenantContentNotificationStatus,
|
||||
type ContentEntryAdminItem,
|
||||
type ImportFieldMapping,
|
||||
type ImportJobDetail,
|
||||
@@ -30,6 +32,7 @@ import {
|
||||
type PublicQuestionBankConflictItem,
|
||||
type PublicQuestionBankConflictsResult,
|
||||
type PublicQuestionBankItem,
|
||||
type TenantContentNotificationItem,
|
||||
} from '@/services/tenantAdmin';
|
||||
import '../admin.css';
|
||||
|
||||
@@ -137,6 +140,13 @@ function conflictItems(value: PublicQuestionBankConflictsResult | null) {
|
||||
return Array.isArray(value?.conflicts) ? value.conflicts : [];
|
||||
}
|
||||
|
||||
function notificationStatusText(item: TenantContentNotificationItem) {
|
||||
if (item.status === 'resolved') return '已处理';
|
||||
if (item.status === 'dismissed') return '已忽略';
|
||||
if (item.status === 'read') return '已读';
|
||||
return '未读';
|
||||
}
|
||||
|
||||
function objectRecord(value: unknown): Record<string, unknown> {
|
||||
return value && typeof value === 'object' && !Array.isArray(value) ? value as Record<string, unknown> : {};
|
||||
}
|
||||
@@ -162,6 +172,7 @@ export default function TenantContentPage() {
|
||||
const [entries, setEntries] = useState<ContentEntryAdminItem[]>([]);
|
||||
const [jobs, setJobs] = useState<ImportJobItem[]>([]);
|
||||
const [publicBanks, setPublicBanks] = useState<PublicQuestionBankItem[]>([]);
|
||||
const [notifications, setNotifications] = useState<TenantContentNotificationItem[]>([]);
|
||||
const [selectedJobId, setSelectedJobId] = useState('');
|
||||
const [selectedImportType, setSelectedImportType] = useState<ImportType>('questions');
|
||||
const [sourceFormat, setSourceFormat] = useState<ImportSourceFormat>('json');
|
||||
@@ -187,10 +198,12 @@ export default function TenantContentPage() {
|
||||
loadContentEntriesAdmin().catch(() => ({ items: [] })),
|
||||
loadImportJobs(20).catch(() => ({ items: [] })),
|
||||
loadPublicQuestionBanks().catch(() => ({ items: [] })),
|
||||
]).then(([entryPayload, jobPayload, bankPayload]) => {
|
||||
loadTenantContentNotifications({ limit: 10 }).catch(() => ({ items: [] })),
|
||||
]).then(([entryPayload, jobPayload, bankPayload, notificationPayload]) => {
|
||||
setEntries(entryPayload.items || []);
|
||||
setJobs(jobPayload.items || []);
|
||||
setPublicBanks(bankPayload.items || []);
|
||||
setNotifications(notificationPayload.items || []);
|
||||
}).catch(nextError => setError(nextError instanceof Error ? nextError.message : '内容数据加载失败'));
|
||||
}
|
||||
|
||||
@@ -461,6 +474,8 @@ export default function TenantContentPage() {
|
||||
copyLimit: Math.max(1, Math.min(Number(copyLimit || 1000), 1000)),
|
||||
});
|
||||
setConflicts(payload.sync || null);
|
||||
const notificationPayload = await loadTenantContentNotifications({ limit: 10 });
|
||||
setNotifications(notificationPayload.items || []);
|
||||
Taro.showToast({ title: '同步完成', icon: 'success' });
|
||||
reload();
|
||||
} catch (nextError) {
|
||||
@@ -470,6 +485,21 @@ export default function TenantContentPage() {
|
||||
}
|
||||
}
|
||||
|
||||
async function updateNotificationStatus(item: TenantContentNotificationItem, status: 'read' | 'dismissed') {
|
||||
if (!item.id) return;
|
||||
setBusy(`notification:${item.id}:${status}`);
|
||||
setError('');
|
||||
try {
|
||||
await updateTenantContentNotificationStatus({ notificationIds: [item.id], status });
|
||||
const payload = await loadTenantContentNotifications({ limit: 10 });
|
||||
setNotifications(payload.items || []);
|
||||
} catch (nextError) {
|
||||
setError(nextError instanceof Error ? nextError.message : '通知状态更新失败');
|
||||
} finally {
|
||||
setBusy('');
|
||||
}
|
||||
}
|
||||
|
||||
async function showConflicts(item: PublicQuestionBankItem) {
|
||||
const adoptionId = adoptionIdOf(item);
|
||||
if (!adoptionId) {
|
||||
@@ -508,6 +538,8 @@ export default function TenantContentPage() {
|
||||
});
|
||||
const payload = await loadPublicQuestionBankConflicts(adoptionId);
|
||||
setConflicts(payload.item || null);
|
||||
const notificationPayload = await loadTenantContentNotifications({ limit: 10 });
|
||||
setNotifications(notificationPayload.items || []);
|
||||
Taro.showToast({ title: '已处理', icon: 'success' });
|
||||
reload();
|
||||
} catch (nextError) {
|
||||
@@ -545,6 +577,8 @@ export default function TenantContentPage() {
|
||||
});
|
||||
const payload = await loadPublicQuestionBankConflicts(adoptionId);
|
||||
setConflicts(payload.item || null);
|
||||
const notificationPayload = await loadTenantContentNotifications({ limit: 10 });
|
||||
setNotifications(notificationPayload.items || []);
|
||||
Taro.showToast({ title: '批量处理完成', icon: 'success' });
|
||||
reload();
|
||||
} catch (nextError) {
|
||||
@@ -722,6 +756,27 @@ export default function TenantContentPage() {
|
||||
) : null}
|
||||
</View>
|
||||
|
||||
<View className='admin-section'>
|
||||
<Text className='admin-section-title'>同步通知</Text>
|
||||
<View className='admin-list'>
|
||||
{notifications.map(item => (
|
||||
<View className='admin-row' key={item.id}>
|
||||
<Text className='admin-row-main'>{item.title || '内容通知'} · {notificationStatusText(item)}</Text>
|
||||
<Text className='admin-row-meta'>{item.message || '-'} · {item.createdAt ? String(item.createdAt).slice(0, 16).replace('T', ' ') : ''}</Text>
|
||||
<View className='admin-row-actions'>
|
||||
{item.status === 'unread' ? (
|
||||
<Button className='admin-mini-button primary' loading={busy === `notification:${item.id}:read`} onClick={() => updateNotificationStatus(item, 'read')}>标记已读</Button>
|
||||
) : null}
|
||||
{item.status !== 'dismissed' && item.status !== 'resolved' ? (
|
||||
<Button className='admin-mini-button' loading={busy === `notification:${item.id}:dismissed`} onClick={() => updateNotificationStatus(item, 'dismissed')}>忽略</Button>
|
||||
) : null}
|
||||
</View>
|
||||
</View>
|
||||
))}
|
||||
</View>
|
||||
{!notifications.length ? <View className='admin-empty'>暂无公共题库同步通知。</View> : null}
|
||||
</View>
|
||||
|
||||
<View className='admin-section'>
|
||||
<Text className='admin-section-title'>公共题库</Text>
|
||||
<View className='admin-list'>
|
||||
|
||||
Reference in New Issue
Block a user