forked from wangziqi/gongxue-base
feat: resolve public bank conflicts
This commit is contained in:
@@ -13,6 +13,7 @@ import {
|
||||
loadPublicQuestionBankConflicts,
|
||||
loadPublicQuestionBanks,
|
||||
previewContentImport,
|
||||
resolvePublicQuestionBankConflict,
|
||||
runImportPostCheck,
|
||||
syncPublicQuestionBank,
|
||||
type ContentEntryAdminItem,
|
||||
@@ -23,6 +24,8 @@ import {
|
||||
type ImportTemplateItem,
|
||||
type ImportType,
|
||||
type ImportSourceFormat,
|
||||
type PublicQuestionBankConflictItem,
|
||||
type PublicQuestionBankConflictsResult,
|
||||
type PublicQuestionBankItem,
|
||||
} from '@/services/tenantAdmin';
|
||||
import '../admin.css';
|
||||
@@ -100,6 +103,10 @@ function safeJsonPreview(value: unknown, maxLength = 360) {
|
||||
}
|
||||
}
|
||||
|
||||
function conflictItems(value: PublicQuestionBankConflictsResult | null) {
|
||||
return Array.isArray(value?.conflicts) ? value.conflicts : [];
|
||||
}
|
||||
|
||||
function objectRecord(value: unknown): Record<string, unknown> {
|
||||
return value && typeof value === 'object' && !Array.isArray(value) ? value as Record<string, unknown> : {};
|
||||
}
|
||||
@@ -131,7 +138,7 @@ export default function TenantContentPage() {
|
||||
const [template, setTemplate] = useState<ImportTemplateItem | null>(null);
|
||||
const [previewResult, setPreviewResult] = useState<ImportPreviewResult | null>(null);
|
||||
const [postCheck, setPostCheck] = useState<Record<string, unknown> | null>(null);
|
||||
const [conflicts, setConflicts] = useState<Record<string, unknown> | null>(null);
|
||||
const [conflicts, setConflicts] = useState<PublicQuestionBankConflictsResult | null>(null);
|
||||
const [busy, setBusy] = useState('');
|
||||
const [error, setError] = useState('');
|
||||
|
||||
@@ -393,6 +400,38 @@ export default function TenantContentPage() {
|
||||
}
|
||||
}
|
||||
|
||||
async function resolveConflict(item: PublicQuestionBankConflictItem, resolution: 'accept_platform' | 'keep_local') {
|
||||
const adoptionId = conflicts?.adoptionId;
|
||||
if (!adoptionId || !item.sourceQuestionId) {
|
||||
setError('缺少冲突上下文,无法处理。');
|
||||
return;
|
||||
}
|
||||
const ok = await confirm(
|
||||
resolution === 'accept_platform' ? '采纳平台版本' : '保留租户版本',
|
||||
resolution === 'accept_platform'
|
||||
? '确认用平台公共题库的新版本覆盖该租户副本?系统会写入新题目版本并记录审计。'
|
||||
: '确认保留租户本地改写版本?系统会记录该冲突已处理,后续同步不再反复提示同一版本。',
|
||||
);
|
||||
if (!ok) return;
|
||||
setBusy(`resolve:${item.sourceQuestionId}:${resolution}`);
|
||||
setError('');
|
||||
try {
|
||||
await resolvePublicQuestionBankConflict({
|
||||
adoptionId,
|
||||
sourceQuestionId: item.sourceQuestionId,
|
||||
resolution,
|
||||
});
|
||||
const payload = await loadPublicQuestionBankConflicts(adoptionId);
|
||||
setConflicts(payload.item || null);
|
||||
Taro.showToast({ title: '已处理', icon: 'success' });
|
||||
reload();
|
||||
} catch (nextError) {
|
||||
setError(nextError instanceof Error ? nextError.message : '冲突处理失败');
|
||||
} finally {
|
||||
setBusy('');
|
||||
}
|
||||
}
|
||||
|
||||
const postCheckSummary = postCheck?.importPostCheck && typeof postCheck.importPostCheck === 'object'
|
||||
? postCheck.importPostCheck as Record<string, unknown>
|
||||
: postCheck;
|
||||
@@ -564,10 +603,24 @@ export default function TenantContentPage() {
|
||||
<View className='admin-section'>
|
||||
<Text className='admin-section-title'>同步冲突</Text>
|
||||
{conflicts ? (
|
||||
<View className='admin-row'>
|
||||
<Text className='admin-row-main'>状态 {String(conflicts.status || conflicts.syncStatus || '-')}</Text>
|
||||
<Text className='admin-row-meta'>冲突数 {String(conflicts.conflictCount || 0)} · 最近同步 {String(conflicts.lastSyncedAt || '')}</Text>
|
||||
<Text className='admin-row-meta'>{JSON.stringify(conflicts).slice(0, 300)}</Text>
|
||||
<View className='admin-list'>
|
||||
<View className='admin-row'>
|
||||
<Text className='admin-row-main'>状态 {String(conflicts.status || conflicts.syncStatus || '-')}</Text>
|
||||
<Text className='admin-row-meta'>冲突数 {String(conflicts.conflictCount || 0)} · 最近同步 {String(conflicts.lastSyncedAt || '')}</Text>
|
||||
<Text className='admin-row-meta'>采纳平台会覆盖为平台新版;保留本地会记录本次冲突已处理。</Text>
|
||||
</View>
|
||||
{conflictItems(conflicts).slice(0, 20).map((item, index) => (
|
||||
<View className='admin-row' key={`${item.sourceQuestionId || index}-${item.targetQuestionId || ''}`}>
|
||||
<Text className='admin-row-main'>题目冲突 {String(item.sourceQuestionId || '').slice(0, 8)}</Text>
|
||||
<Text className='admin-row-meta'>目标 {String(item.targetQuestionId || '-').slice(0, 8)} · 平台 {String(item.sourceHash || '').slice(0, 10)} · 本地 {String(item.targetHash || '').slice(0, 10)}</Text>
|
||||
<Text className='admin-row-meta'>上次平台 {String(item.previousSourceHash || '-').slice(0, 10)}</Text>
|
||||
<View className='admin-row-actions'>
|
||||
<Button className='admin-mini-button primary' loading={busy === `resolve:${item.sourceQuestionId}:accept_platform`} onClick={() => resolveConflict(item, 'accept_platform')}>采纳平台</Button>
|
||||
<Button className='admin-mini-button' loading={busy === `resolve:${item.sourceQuestionId}:keep_local`} onClick={() => resolveConflict(item, 'keep_local')}>保留本地</Button>
|
||||
</View>
|
||||
</View>
|
||||
))}
|
||||
{!conflictItems(conflicts).length ? <View className='admin-empty'>当前没有待处理冲突。</View> : null}
|
||||
</View>
|
||||
) : <View className='admin-empty'>同步后或点击“冲突”可查看最近冲突摘要。</View>}
|
||||
</View>
|
||||
|
||||
Reference in New Issue
Block a user