|
|
@@ -58,6 +58,7 @@ export class AdminResourceComponent implements OnInit {
|
|
|
readonly updatingContent = signal(false);
|
|
|
readonly contentActionError = signal('');
|
|
|
readonly contentActionMessage = signal('');
|
|
|
+ readonly duplicateContentTitles = signal<Record<string, unknown>[]>([]);
|
|
|
readonly targetNodeParentId = signal(0);
|
|
|
readonly updatingNode = signal(false);
|
|
|
readonly nodeActionError = signal('');
|
|
|
@@ -367,7 +368,7 @@ export class AdminResourceComponent implements OnInit {
|
|
|
} catch (error) { this.userActionError.set(error instanceof Error ? error.message : '用户组更新失败'); }
|
|
|
finally { this.updatingUser.set(false); }
|
|
|
}
|
|
|
- async applyContentAction(action: 'status' | 'recycle' | 'recover' | 'move'): Promise<void> {
|
|
|
+ async applyContentAction(action: 'status' | 'recycle' | 'recover' | 'move' | 'refresh'): Promise<void> {
|
|
|
this.contentActionError.set(''); this.contentActionMessage.set('');
|
|
|
if (this.className() !== 'CommonModel' || !this.draftId()) { this.contentActionError.set('只能管理现有内容'); return; }
|
|
|
if (action === 'recycle' && !confirm('确认将该内容移入回收站?')) return;
|
|
|
@@ -390,7 +391,7 @@ export class AdminResourceComponent implements OnInit {
|
|
|
this.selectedContentStatus.set(Number(result.results[0]['status']) || 0);
|
|
|
this.targetContentNodeId.set(Number(result.results[0]['nodeId']) || 0);
|
|
|
}
|
|
|
- const messages = { status: `内容状态已更新为${this.contentStatusLabel(this.selectedContentStatus())}`, recycle: '内容已移入回收站', recover: '内容已恢复为待审核', move: `内容已移动到节点 ${this.targetContentNodeId()}` };
|
|
|
+ const messages = { status: `内容状态已更新为${this.contentStatusLabel(this.selectedContentStatus())}`, recycle: '内容已移入回收站', recover: '内容已恢复为待审核', move: `内容已移动到节点 ${this.targetContentNodeId()}`, refresh: '内容创建和修改时间已刷新' };
|
|
|
this.contentActionMessage.set(messages[action]);
|
|
|
await this.load();
|
|
|
} catch (error) { this.contentActionError.set(error instanceof Error ? error.message : '内容操作失败'); }
|
|
|
@@ -422,7 +423,7 @@ export class AdminResourceComponent implements OnInit {
|
|
|
} catch (error) { this.bulkError.set(error instanceof Error ? error.message : '批量用户操作失败'); }
|
|
|
finally { this.updatingUser.set(false); }
|
|
|
}
|
|
|
- async applyBulkContentAction(action: 'status' | 'recycle' | 'recover' | 'move'): Promise<void> {
|
|
|
+ async applyBulkContentAction(action: 'status' | 'recycle' | 'recover' | 'move' | 'refresh'): Promise<void> {
|
|
|
const objectIds = this.selectedIds(); this.bulkError.set(''); this.bulkMessage.set('');
|
|
|
if (this.className() !== 'CommonModel' || !objectIds.length) { this.bulkError.set('请先选择内容'); return; }
|
|
|
if (action === 'recycle' && !confirm(`确认将选中的 ${objectIds.length} 条内容移入回收站?`)) return;
|
|
|
@@ -444,6 +445,31 @@ export class AdminResourceComponent implements OnInit {
|
|
|
} catch (error) { this.bulkError.set(error instanceof Error ? error.message : '批量内容操作失败'); }
|
|
|
finally { this.updatingContent.set(false); }
|
|
|
}
|
|
|
+ async checkDuplicateContentTitle(): Promise<void> {
|
|
|
+ const title = String(this.draft()['title'] || '').trim(); this.contentActionError.set(''); this.contentActionMessage.set(''); this.duplicateContentTitles.set([]);
|
|
|
+ if (this.className() !== 'CommonModel' || !title) { this.contentActionError.set('当前内容没有可检查的标题'); return; }
|
|
|
+ this.updatingContent.set(true);
|
|
|
+ try {
|
|
|
+ const result = await this.functions.admin<{ total: number; results: Record<string, unknown>[] }>('legacyContentUtility', { action: 'duplicate-title', title });
|
|
|
+ this.duplicateContentTitles.set(result.results || []); this.contentActionMessage.set(`找到 ${result.total || 0} 条同名内容(含当前记录)`);
|
|
|
+ } catch (error) { this.contentActionError.set(error instanceof Error ? error.message : '重复标题检查失败'); }
|
|
|
+ finally { this.updatingContent.set(false); }
|
|
|
+ }
|
|
|
+ async exportContent(): Promise<void> {
|
|
|
+ const selected = this.selectedIds(); const pageIds = (this.page()?.results || []).map((row) => String(row['objectId'] || '')).filter(Boolean); const objectIds = selected.length ? selected : pageIds;
|
|
|
+ this.bulkError.set(''); this.bulkMessage.set('');
|
|
|
+ if (this.className() !== 'CommonModel' || !objectIds.length) { this.bulkError.set('当前没有可导出的内容'); return; }
|
|
|
+ this.updatingContent.set(true);
|
|
|
+ try {
|
|
|
+ const result = await this.functions.admin<{ total: number; rows: Record<string, unknown>[] }>('legacyContentUtility', { action: 'export', objectIds });
|
|
|
+ const labels = ['ID', '标题', '录入者', '点击数', '推荐', '状态', '添加时间', '修改时间']; const keys = ['id', 'title', 'inputer', 'hits', 'elite', 'status', 'createTime', 'updateTime'];
|
|
|
+ const csvCell = (value: unknown) => { let text = String(value ?? ''); if (/^[=+\-@]/.test(text)) text = `'${text}`; return `"${text.replace(/"/g, '""')}"`; };
|
|
|
+ const csv = [labels.map(csvCell).join(','), ...(result.rows || []).map((row) => keys.map((key) => csvCell(row[key])).join(','))].join('\r\n');
|
|
|
+ const url = URL.createObjectURL(new Blob(['\ufeff', csv], { type: 'text/csv;charset=utf-8' })); const link = document.createElement('a'); link.href = url; link.download = `${new Date().toISOString().slice(0, 10).replace(/-/g, '')}-内容管理表.csv`; link.click(); URL.revokeObjectURL(url);
|
|
|
+ this.bulkMessage.set(`已导出 ${result.total || 0} 条内容`);
|
|
|
+ } catch (error) { this.bulkError.set(error instanceof Error ? error.message : '内容导出失败'); }
|
|
|
+ finally { this.updatingContent.set(false); }
|
|
|
+ }
|
|
|
async applyNodeAction(action: 'recycle' | 'recover' | 'move'): Promise<void> {
|
|
|
this.nodeActionError.set(''); this.nodeActionMessage.set('');
|
|
|
if (this.className() !== 'Node' || !this.draftId()) { this.nodeActionError.set('只能管理现有栏目'); return; }
|
|
|
@@ -790,7 +816,7 @@ export class AdminResourceComponent implements OnInit {
|
|
|
private resetPasswordFields(): void { this.newPassword.set(''); this.confirmPassword.set(''); this.passwordMessage.set(''); this.passwordError.set(''); }
|
|
|
private resetCreateUserFields(): void { this.createUsername.set(''); this.createPassword.set(''); this.createConfirmPassword.set(''); this.createDisplayName.set(''); this.createMobile.set(''); this.createInviteUsername.set(''); this.createUserError.set(''); }
|
|
|
private resetUserActions(detail: Record<string, unknown> = {}): void { this.userGroups.set([]); this.selectedUserGroupId.set(Number(detail['legacyGroupId'] ?? (detail['legacyUserData'] as Record<string, unknown> | undefined)?.['GroupID']) || 0); this.userActionReason.set(''); this.userActionError.set(''); this.userActionMessage.set(''); }
|
|
|
- private resetContentActions(detail: Record<string, unknown> = {}): void { this.selectedContentStatus.set(Number(detail['status']) || 0); this.targetContentNodeId.set(Number(detail['nodeId']) || 0); this.contentActionError.set(''); this.contentActionMessage.set(''); }
|
|
|
+ private resetContentActions(detail: Record<string, unknown> = {}): void { this.selectedContentStatus.set(Number(detail['status']) || 0); this.targetContentNodeId.set(Number(detail['nodeId']) || 0); this.contentActionError.set(''); this.contentActionMessage.set(''); this.duplicateContentTitles.set([]); }
|
|
|
private resetNodeActions(detail: Record<string, unknown> = {}): void { this.targetNodeParentId.set(Number(detail['parentId']) || 0); this.nodeActionError.set(''); this.nodeActionMessage.set(''); }
|
|
|
private resetSpecialActions(detail: Record<string, unknown> = {}): void { this.targetSpecialParentId.set(Number(detail['pid']) || 0); this.specialActionError.set(''); this.specialActionMessage.set(''); }
|
|
|
private resetGuestbookActions(detail: Record<string, unknown> = {}): void { this.selectedGuestbookStatus.set(Number(detail['status'] ?? 99)); this.guestbookReplyTitle.set(''); this.guestbookReplyContent.set(''); this.guestbookActionError.set(''); this.guestbookActionMessage.set(''); }
|