import { CommonModule } from '@angular/common'; import { ChangeDetectionStrategy, Component, DestroyRef, OnInit, computed, inject, signal } from '@angular/core'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { FormsModule } from '@angular/forms'; import { ActivatedRoute } from '@angular/router'; import { ChevronLeft, ChevronRight, LoaderCircle, LucideAngularModule, Pencil, Plus, RefreshCw, Search, Trash2, X } from 'lucide-angular'; import { AdminClassSchema, AdminFieldSchema, ResourcePage } from '../admin.models'; import { CloudFunctionsService } from '../cloud-functions.service'; @Component({ selector: 'app-admin-resource', standalone: true, imports: [CommonModule, FormsModule, LucideAngularModule], templateUrl: './admin-resource.component.html', styleUrl: './admin-resource.component.scss', changeDetection: ChangeDetectionStrategy.OnPush, }) export class AdminResourceComponent implements OnInit { readonly className = signal(''); readonly schema = signal(null); readonly page = signal(null); readonly loading = signal(true); readonly saving = signal(false); readonly error = signal(''); readonly search = signal(''); readonly editorOpen = signal(false); readonly draftId = signal(''); readonly draft = signal>({}); readonly fieldErrors = signal>({}); readonly fieldInputs = signal>({}); readonly icons = { ChevronLeft, ChevronRight, LoaderCircle, Pencil, Plus, RefreshCw, Search, Trash2, X }; readonly columns = computed(() => { const fields = this.schema()?.fields ?? []; const preferred = ['objectId', 'name', 'title', 'username', 'realName', 'mobile', 'sourceKey', 'status', 'createdAt', 'updatedAt']; return [...fields].sort((a, b) => preferred.indexOf(a.name) - preferred.indexOf(b.name)).slice(0, 7); }); private readonly functions = inject(CloudFunctionsService); private readonly route = inject(ActivatedRoute); private readonly destroyRef = inject(DestroyRef); ngOnInit(): void { this.route.paramMap.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((params) => { this.className.set(params.get('className') ?? ''); this.search.set(''); void this.load(1, true); }); } async load(pageNumber = this.page()?.page ?? 1, reloadSchema = false): Promise { this.loading.set(true); this.error.set(''); try { if (reloadSchema || !this.schema()) this.schema.set(await this.functions.admin('schema', { className: this.className() })); this.page.set(await this.functions.admin('list', { className: this.className(), page: pageNumber, pageSize: 20, search: this.search().trim() })); } catch (error) { this.error.set(error instanceof Error ? error.message : '数据加载失败'); } finally { this.loading.set(false); } } openCreate(): void { this.draftId.set(''); this.draft.set({}); this.fieldErrors.set({}); this.fieldInputs.set({}); this.editorOpen.set(true); } async openEdit(row: Record): Promise { const objectId = String(row['objectId'] ?? ''); this.error.set(''); try { const detail = await this.functions.admin>('get', { className: this.className(), objectId }); this.draftId.set(objectId); this.draft.set(detail); this.fieldErrors.set({}); this.fieldInputs.set({}); this.editorOpen.set(true); } catch (error) { this.error.set(error instanceof Error ? error.message : '详情加载失败'); } } closeEditor(): void { this.editorOpen.set(false); this.draft.set({}); this.draftId.set(''); this.fieldErrors.set({}); this.fieldInputs.set({}); } setField(field: AdminFieldSchema, raw: unknown): void { let value = raw; if (field.type === 'Number') value = raw === '' ? null : Number(raw); if (field.type === 'Pointer') value = raw ? { __type: 'Pointer', className: field.targetClass, objectId: String(raw) } : null; if ((field.type === 'Object' || field.type === 'Array') && typeof raw === 'string') { this.fieldInputs.update((current) => ({ ...current, [field.name]: raw })); try { value = raw.trim() ? JSON.parse(raw) : field.type === 'Array' ? [] : {}; const valid = field.type === 'Array' ? Array.isArray(value) : Boolean(value) && typeof value === 'object' && !Array.isArray(value); if (!valid) throw new Error(); this.fieldErrors.update((current) => { const next = { ...current }; delete next[field.name]; return next; }); } catch { this.fieldErrors.update((current) => ({ ...current, [field.name]: field.type === 'Array' ? '请输入有效的 JSON 数组' : '请输入有效的 JSON 对象' })); return; } } this.draft.update((current) => ({ ...current, [field.name]: value })); } inputValue(field: AdminFieldSchema): string | number { const value = this.draft()[field.name]; if (field.type === 'Pointer') return String((value as { objectId?: string } | null)?.objectId ?? ''); if (field.type === 'Date') return String((value as { iso?: string } | string | null)?.hasOwnProperty?.('iso') ? (value as { iso: string }).iso.slice(0, 16) : String(value ?? '').slice(0, 16)); if (field.type === 'Object' || field.type === 'Array') return this.fieldInputs()[field.name] ?? (value == null ? '' : JSON.stringify(value, null, 2)); return typeof value === 'number' ? value : String(value ?? ''); } async save(): Promise { const invalidFields = Object.keys(this.fieldErrors()); if (invalidFields.length) { this.error.set(`请先修正字段格式:${invalidFields.join('、')}`); return; } this.saving.set(true); this.error.set(''); try { await this.functions.admin('save', { className: this.className(), objectId: this.draftId() || undefined, fields: this.draft() }); this.closeEditor(); await this.load(); } catch (error) { this.error.set(error instanceof Error ? error.message : '保存失败'); } finally { this.saving.set(false); } } async remove(row: Record): Promise { const objectId = String(row['objectId'] ?? ''); if (!objectId || !confirm(`确认删除 ${this.className()} / ${objectId}?`)) return; try { await this.functions.admin('delete', { className: this.className(), objectId }); await this.load(); } catch (error) { this.error.set(error instanceof Error ? error.message : '删除失败'); } } display(value: unknown): string { if (value == null || value === '') return '—'; if (typeof value === 'object') { const data = value as Record; return String(data['name'] ?? data['title'] ?? data['username'] ?? data['objectId'] ?? JSON.stringify(value)); } return String(value); } }