| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560 |
- 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, Eye, KeyRound, 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<AdminClassSchema | null>(null);
- readonly page = signal<ResourcePage | null>(null);
- readonly loading = signal(true);
- readonly saving = signal(false);
- readonly resettingPassword = signal(false);
- readonly error = signal('');
- readonly search = signal('');
- readonly editorOpen = signal(false);
- readonly draftId = signal('');
- readonly draft = signal<Record<string, unknown>>({});
- readonly fieldErrors = signal<Record<string, string>>({});
- readonly fieldInputs = signal<Record<string, string>>({});
- readonly newPassword = signal('');
- readonly confirmPassword = signal('');
- readonly passwordMessage = signal('');
- readonly passwordError = signal('');
- readonly createUsername = signal('');
- readonly createPassword = signal('');
- readonly createConfirmPassword = signal('');
- readonly createDisplayName = signal('');
- readonly createMobile = signal('');
- readonly createInviteUsername = signal('');
- readonly createUserError = signal('');
- readonly userGroups = signal<Record<string, unknown>[]>([]);
- readonly selectedUserGroupId = signal(0);
- readonly userActionReason = signal('');
- readonly userActionError = signal('');
- readonly userActionMessage = signal('');
- readonly updatingUser = signal(false);
- readonly selectedContentStatus = signal(0);
- readonly targetContentNodeId = signal(0);
- readonly updatingContent = signal(false);
- readonly contentActionError = signal('');
- readonly contentActionMessage = signal('');
- readonly targetNodeParentId = signal(0);
- readonly updatingNode = signal(false);
- readonly nodeActionError = signal('');
- readonly nodeActionMessage = signal('');
- readonly targetSpecialParentId = signal(0);
- readonly updatingSpecial = signal(false);
- readonly specialActionError = signal('');
- readonly specialActionMessage = signal('');
- readonly selectedGuestbookStatus = signal(99);
- readonly guestbookReplyTitle = signal('');
- readonly guestbookReplyContent = signal('');
- readonly updatingGuestbook = signal(false);
- readonly guestbookActionError = signal('');
- readonly guestbookActionMessage = signal('');
- readonly guestCategoryType = signal(0);
- readonly targetGuestCategoryParentId = signal(0);
- readonly targetBarCategoryId = signal(0);
- readonly updatingBar = signal(false);
- readonly barActionError = signal('');
- readonly barActionMessage = signal('');
- readonly targetExamClassParentId = signal(0);
- readonly examClassType = signal(1);
- readonly examClassOrder = signal(0);
- readonly targetExamPointParentId = signal(0);
- readonly examPointOrder = signal(0);
- readonly targetKnowledgeClassId = signal(0);
- readonly targetKnowledgeParentId = signal(0);
- readonly knowledgeOrder = signal(0);
- readonly targetTeacherClassId = signal(0);
- readonly targetDictionaryCategoryId = signal(0);
- readonly dictionaryCategories = signal<Record<string, unknown>[]>([]);
- readonly updatingDictionary = signal(false);
- readonly targetGradeCategoryId = signal(0);
- readonly targetGradeParentId = signal(0);
- readonly filterGradeCategoryId = signal(0);
- readonly filterGradeParentId = signal(0);
- readonly gradeCategories = signal<Record<string, unknown>[]>([]);
- readonly updatingGrade = signal(false);
- readonly updatingCurrency = signal(false);
- readonly selectedIds = signal<string[]>([]);
- readonly bulkError = signal('');
- readonly bulkMessage = signal('');
- readonly icons = { ChevronLeft, ChevronRight, Eye, KeyRound, LoaderCircle, Pencil, Plus, RefreshCw, Search, Trash2, X };
- readonly columns = computed(() => {
- const fields = this.schema()?.fields ?? [];
- const preferred = ['objectId', 'name', 'title', 'categoryname', 'dicname', 'cateName', 'gradeName', 'cClassName', 'testPoint', 'kName', 'tname', 'username', 'realName', 'mobile', 'sourceKey', 'status', 'createdAt', 'updatedAt'];
- const rank = (name: string): number => { const index = preferred.indexOf(name); return index < 0 ? preferred.length : index; };
- return fields.map((field, index) => ({ field, index })).sort((a, b) => rank(a.field.name) - rank(b.field.name) || a.index - b.index).map(({ field }) => field).slice(0, 7);
- });
- readonly pageCount = computed(() => Math.max(1, Math.ceil((this.page()?.total ?? 0) / (this.page()?.pageSize || 20))));
- readonly bulkEnabled = computed(() => ['_User', 'CommonModel', 'Node', 'Special', 'Guestbook', 'GuestBar', 'Datadiccategory', 'Datadic', 'GradeCate', 'Grade', 'Currency'].includes(this.className()));
- readonly allPageSelected = computed(() => {
- const ids = (this.page()?.results || []).map((row) => String(row['objectId'] || '')).filter(Boolean);
- return Boolean(ids.length) && ids.every((id) => this.selectedIds().includes(id));
- });
- 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('');
- this.filterGradeCategoryId.set(0); this.filterGradeParentId.set(0);
- void this.load(1, true);
- });
- }
- async load(pageNumber = this.page()?.page ?? 1, reloadSchema = false): Promise<void> {
- this.loading.set(true); this.error.set(''); this.selectedIds.set([]); this.bulkError.set(''); this.bulkMessage.set('');
- try {
- if (reloadSchema || !this.schema()) this.schema.set(await this.functions.admin<AdminClassSchema>('schema', { className: this.className() }));
- if (this.className() === 'Grade' && reloadSchema) { await this.loadGradeCategories(); if (!this.filterGradeCategoryId() && this.gradeCategories().length) this.filterGradeCategoryId.set(this.gradeCategoryIdOf(this.gradeCategories()[0])); }
- const pageOperation = this.className() === 'Grade' && this.filterGradeCategoryId() > 0 ? 'gradeOptions' : 'list';
- const pagePayload: Record<string, unknown> = { className: this.className(), page: pageNumber, pageSize: 20, search: this.search().trim() };
- if (pageOperation === 'gradeOptions') { pagePayload['categoryId'] = this.filterGradeCategoryId(); pagePayload['parentId'] = this.filterGradeParentId(); }
- this.page.set(await this.functions.admin<ResourcePage>(pageOperation, pagePayload));
- if (this.className() === '_User' && reloadSchema) await this.loadUserGroups();
- if (this.className() === 'Datadic' && reloadSchema) await this.loadDictionaryCategories();
- } 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.resetPasswordFields(); this.resetCreateUserFields(); this.resetUserActions(); this.resetContentActions(); this.resetNodeActions(); this.resetSpecialActions(); this.resetGuestbookActions(); this.resetGuestCategoryActions(); this.resetBarActions(); this.resetExamClassActions(); this.resetExamPointActions(); this.resetKnowledgeActions(); this.resetTeacherActions(); this.resetDictionaryActions(); this.resetGradeActions(); this.editorOpen.set(true); }
- async openEdit(row: Record<string, unknown>): Promise<void> {
- const objectId = String(row['objectId'] ?? '');
- this.error.set('');
- try {
- const detail = await this.functions.admin<Record<string, unknown>>('get', { className: this.className(), objectId });
- this.draftId.set(objectId); this.draft.set(detail); this.fieldErrors.set({}); this.fieldInputs.set({}); this.resetPasswordFields(); this.resetCreateUserFields(); this.resetUserActions(detail); this.resetContentActions(detail); this.resetNodeActions(detail); this.resetSpecialActions(detail); this.resetGuestbookActions(detail); this.resetGuestCategoryActions(detail); this.resetBarActions(detail); this.resetExamClassActions(detail); this.resetExamPointActions(detail); this.resetKnowledgeActions(detail); this.resetTeacherActions(detail); this.resetDictionaryActions(detail); this.resetGradeActions(detail); this.editorOpen.set(true);
- if (this.className() === '_User') await this.loadUserGroups();
- } 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({}); this.resetPasswordFields(); this.resetCreateUserFields(); this.resetUserActions(); this.resetContentActions(); this.resetNodeActions(); this.resetSpecialActions(); this.resetGuestbookActions(); this.resetGuestCategoryActions(); this.resetBarActions(); this.resetExamClassActions(); this.resetExamPointActions(); this.resetKnowledgeActions(); this.resetTeacherActions(); this.resetDictionaryActions(); this.resetGradeActions(); }
- 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<void> {
- const invalidFields = Object.keys(this.fieldErrors());
- if (invalidFields.length) { this.error.set(`请先修正字段格式:${invalidFields.join('、')}`); return; }
- this.saving.set(true); this.error.set('');
- try {
- const operation = this.className() === 'Group' ? 'saveGroup'
- : this.className() === 'Node' ? 'saveNode'
- : this.className() === 'Special' ? 'saveSpecial'
- : this.className() === 'Model' ? 'saveModelMetadata'
- : this.className() === 'ModelField' ? 'saveModelFieldMetadata'
- : this.className() === 'Guestbook' ? 'saveGuestbook'
- : this.className() === 'Guestcate' ? 'saveGuestCategory'
- : this.className() === 'ExamClass' ? 'saveExamClass'
- : this.className() === 'ExamPoint' ? 'saveExamPoint'
- : this.className() === 'QuestionsKnowledge' ? 'saveKnowledge'
- : this.className() === 'ExTeacher' ? 'saveExamTeacher'
- : this.className() === 'Datadiccategory' ? 'saveDictionaryCategory'
- : this.className() === 'Datadic' ? 'saveDictionaryItem'
- : this.className() === 'GradeCate' ? 'saveGradeCategory'
- : this.className() === 'Grade' ? 'saveGradeOption'
- : this.className() === 'Currency' ? 'saveCurrency' : 'save';
- const payload: Record<string, unknown> = { className: this.className(), objectId: this.draftId() || undefined, fields: this.draft() };
- if (this.className() === 'Node') payload['parentId'] = this.targetNodeParentId();
- if (this.className() === 'Special') payload['pid'] = this.targetSpecialParentId();
- if (this.className() === 'Guestcate') { payload['gtype'] = this.guestCategoryType(); payload['parentId'] = this.targetGuestCategoryParentId(); }
- if (this.className() === 'ExamClass') { payload['parentId'] = this.targetExamClassParentId(); payload['classType'] = this.examClassType(); payload['orderId'] = this.examClassOrder(); }
- if (this.className() === 'ExamPoint') { payload['parentId'] = this.targetExamPointParentId(); payload['orderId'] = this.examPointOrder(); }
- if (this.className() === 'QuestionsKnowledge') { payload['classId'] = this.targetKnowledgeClassId(); payload['parentId'] = this.targetKnowledgeParentId(); payload['orderId'] = this.knowledgeOrder(); }
- if (this.className() === 'ExTeacher') payload['classId'] = this.targetTeacherClassId();
- if (this.className() === 'Datadic') payload['categoryId'] = this.targetDictionaryCategoryId();
- if (this.className() === 'Grade') { payload['categoryId'] = this.targetGradeCategoryId(); payload['parentId'] = this.targetGradeParentId(); }
- await this.functions.admin(operation, payload);
- this.closeEditor(); await this.load();
- } catch (error) { this.error.set(error instanceof Error ? error.message : '保存失败'); }
- finally { this.saving.set(false); }
- }
- async submitEditor(): Promise<void> {
- if (this.className() === '_User' && !this.draftId()) await this.createUser();
- else await this.save();
- }
- async createUser(): Promise<void> {
- const username = this.createUsername().trim();
- const password = this.createPassword();
- this.createUserError.set('');
- if (this.className() !== '_User' || this.draftId()) { this.createUserError.set('只能从用户管理的新建入口开户'); return; }
- if (username.length < 3 || username.length > 64) { this.createUserError.set('账号长度应为 3 至 64 位'); return; }
- if (password.length < 8 || password.length > 72) { this.createUserError.set('密码长度应为 8 至 72 位'); return; }
- if (password !== this.createConfirmPassword()) { this.createUserError.set('两次输入的密码不一致'); return; }
- const payload: Record<string, unknown> = { username, password };
- const displayName = this.createDisplayName().trim();
- const mobile = this.createMobile().trim();
- const inviteUsername = this.createInviteUsername().trim();
- if (displayName) payload['displayName'] = displayName;
- if (mobile) payload['mobile'] = mobile;
- if (inviteUsername) payload['inviteUsername'] = inviteUsername;
- this.saving.set(true); this.error.set('');
- try {
- await this.functions.admin('createUser', payload);
- this.closeEditor(); await this.load();
- } catch (error) { this.createUserError.set(error instanceof Error ? error.message : '用户开户失败'); }
- finally { this.saving.set(false); }
- }
- async resetPassword(): Promise<void> {
- this.passwordError.set(''); this.passwordMessage.set('');
- if (this.className() !== '_User' || !this.draftId()) { this.passwordError.set('只能重置现有用户密码'); return; }
- if (this.newPassword().length < 8) { this.passwordError.set('新密码至少 8 位'); return; }
- if (this.newPassword() !== this.confirmPassword()) { this.passwordError.set('两次输入的密码不一致'); return; }
- this.resettingPassword.set(true);
- try {
- await this.functions.admin('resetPassword', { className: '_User', objectId: this.draftId(), newPassword: this.newPassword() });
- this.newPassword.set(''); this.confirmPassword.set(''); this.passwordMessage.set('密码已安全重置');
- } catch (error) { this.passwordError.set(error instanceof Error ? error.message : '密码重置失败'); }
- finally { this.resettingPassword.set(false); }
- }
- async setUserDisabled(disabled: boolean): Promise<void> {
- this.userActionError.set(''); this.userActionMessage.set('');
- if (this.className() !== '_User' || !this.draftId()) { this.userActionError.set('只能管理现有用户状态'); return; }
- if (disabled && !confirm('确认停用该账号并撤销其全部登录会话?')) return;
- this.updatingUser.set(true);
- try {
- const result = await this.functions.admin<{ results: Record<string, unknown>[]; revokedSessions: number }>('userBatch', {
- className: '_User', action: disabled ? 'lock' : 'unlock', objectIds: [this.draftId()], reason: this.userActionReason().trim(),
- });
- if (result.results?.[0]) this.draft.set(result.results[0]);
- this.userActionMessage.set(disabled ? `账号已停用,撤销 ${result.revokedSessions || 0} 个会话` : '账号已恢复使用');
- await this.load();
- } catch (error) { this.userActionError.set(error instanceof Error ? error.message : '账号状态更新失败'); }
- finally { this.updatingUser.set(false); }
- }
- async moveUserGroup(): Promise<void> {
- this.userActionError.set(''); this.userActionMessage.set('');
- const groupId = this.selectedUserGroupId();
- if (this.className() !== '_User' || !this.draftId() || !Number.isInteger(groupId) || groupId < 1) { this.userActionError.set('请选择有效用户组'); return; }
- this.updatingUser.set(true);
- try {
- const result = await this.functions.admin<{ results: Record<string, unknown>[] }>('userBatch', { className: '_User', action: 'move', objectIds: [this.draftId()], groupId });
- if (result.results?.[0]) this.draft.set(result.results[0]);
- this.userActionMessage.set(`已移动到用户组 ${groupId}`);
- await this.load();
- } catch (error) { this.userActionError.set(error instanceof Error ? error.message : '用户组更新失败'); }
- finally { this.updatingUser.set(false); }
- }
- async applyContentAction(action: 'status' | 'recycle' | 'recover' | 'move'): Promise<void> {
- this.contentActionError.set(''); this.contentActionMessage.set('');
- if (this.className() !== 'CommonModel' || !this.draftId()) { this.contentActionError.set('只能管理现有内容'); return; }
- if (action === 'recycle' && !confirm('确认将该内容移入回收站?')) return;
- const payload: Record<string, unknown> = { className: 'CommonModel', action, objectIds: [this.draftId()] };
- if (action === 'status') {
- const status = this.selectedContentStatus();
- if (![-3, -1, 0, 99].includes(status)) { this.contentActionError.set('请选择有效内容状态'); return; }
- payload['status'] = status;
- }
- if (action === 'move') {
- const nodeId = this.targetContentNodeId();
- if (!Number.isInteger(nodeId) || nodeId < 1) { this.contentActionError.set('请输入有效目标节点 ID'); return; }
- payload['nodeId'] = nodeId;
- }
- this.updatingContent.set(true);
- try {
- const result = await this.functions.admin<{ results: Record<string, unknown>[] }>('contentBatch', payload);
- if (result.results?.[0]) {
- this.draft.set(result.results[0]);
- 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()}` };
- this.contentActionMessage.set(messages[action]);
- await this.load();
- } catch (error) { this.contentActionError.set(error instanceof Error ? error.message : '内容操作失败'); }
- finally { this.updatingContent.set(false); }
- }
- toggleSelection(objectId: string, selected: boolean): void {
- this.selectedIds.update((current) => selected ? [...new Set([...current, objectId])] : current.filter((id) => id !== objectId));
- }
- togglePageSelection(selected: boolean): void {
- const pageIds = (this.page()?.results || []).map((row) => String(row['objectId'] || '')).filter(Boolean);
- this.selectedIds.set(selected ? pageIds : []);
- }
- isSelected(objectId: unknown): boolean { return this.selectedIds().includes(String(objectId || '')); }
- async applyBulkUserAction(action: 'lock' | 'unlock' | 'move'): Promise<void> {
- const objectIds = this.selectedIds(); this.bulkError.set(''); this.bulkMessage.set('');
- if (this.className() !== '_User' || !objectIds.length) { this.bulkError.set('请先选择用户'); return; }
- if (action === 'lock' && !confirm(`确认停用选中的 ${objectIds.length} 个账号并撤销全部会话?`)) return;
- const payload: Record<string, unknown> = { className: '_User', action, objectIds };
- if (action === 'move') {
- const groupId = this.selectedUserGroupId();
- if (!Number.isInteger(groupId) || groupId < 1) { this.bulkError.set('请选择有效用户组'); return; }
- payload['groupId'] = groupId;
- } else payload['reason'] = this.userActionReason().trim();
- this.updatingUser.set(true);
- try {
- const result = await this.functions.admin<{ updated: number; revokedSessions: number }>('userBatch', payload);
- const message = action === 'lock' ? `已停用 ${result.updated} 个账号并撤销 ${result.revokedSessions || 0} 个会话` : action === 'unlock' ? `已解锁 ${result.updated} 个账号` : `已移动 ${result.updated} 个用户`;
- await this.load(); this.bulkMessage.set(message);
- } catch (error) { this.bulkError.set(error instanceof Error ? error.message : '批量用户操作失败'); }
- finally { this.updatingUser.set(false); }
- }
- async applyBulkContentAction(action: 'status' | 'recycle' | 'recover' | 'move'): 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;
- const payload: Record<string, unknown> = { className: 'CommonModel', action, objectIds };
- if (action === 'status') {
- const status = this.selectedContentStatus();
- if (![-3, -1, 0, 99].includes(status)) { this.bulkError.set('请选择有效内容状态'); return; }
- payload['status'] = status;
- }
- if (action === 'move') {
- const nodeId = this.targetContentNodeId();
- if (!Number.isInteger(nodeId) || nodeId < 1) { this.bulkError.set('请输入有效目标节点 ID'); return; }
- payload['nodeId'] = nodeId;
- }
- this.updatingContent.set(true);
- try {
- const result = await this.functions.admin<{ updated: number }>('contentBatch', payload);
- const message = `已处理 ${result.updated} 条内容`; await this.load(); this.bulkMessage.set(message);
- } 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; }
- if (action === 'recycle' && !confirm('确认将该栏目移入回收站?')) return;
- const payload: Record<string, unknown> = { className: 'Node', action, objectIds: [this.draftId()] };
- if (action === 'move') {
- const parentId = this.targetNodeParentId();
- if (!Number.isInteger(parentId) || parentId < 0) { this.nodeActionError.set('请输入有效父节点 ID,顶级栏目填 0'); return; }
- payload['parentId'] = parentId;
- }
- this.updatingNode.set(true);
- try {
- const result = await this.functions.admin<{ results: Record<string, unknown>[] }>('nodeBatch', payload);
- if (result.results?.[0]) { this.draft.set(result.results[0]); this.targetNodeParentId.set(Number(result.results[0]['parentId']) || 0); }
- this.nodeActionMessage.set(action === 'recycle' ? '栏目已移入回收站' : action === 'recover' ? '栏目已恢复' : `栏目已移动到父节点 ${this.targetNodeParentId()}`);
- await this.load();
- } catch (error) { this.nodeActionError.set(error instanceof Error ? error.message : '栏目操作失败'); }
- finally { this.updatingNode.set(false); }
- }
- async applyBulkNodeAction(action: 'recycle' | 'recover' | 'move'): Promise<void> {
- const objectIds = this.selectedIds(); this.bulkError.set(''); this.bulkMessage.set('');
- if (this.className() !== 'Node' || !objectIds.length) { this.bulkError.set('请先选择栏目'); return; }
- if (action === 'recycle' && !confirm(`确认将选中的 ${objectIds.length} 个栏目移入回收站?`)) return;
- const payload: Record<string, unknown> = { className: 'Node', action, objectIds };
- if (action === 'move') {
- const parentId = this.targetNodeParentId();
- if (!Number.isInteger(parentId) || parentId < 0) { this.bulkError.set('请输入有效父节点 ID,顶级栏目填 0'); return; }
- payload['parentId'] = parentId;
- }
- this.updatingNode.set(true);
- try {
- const result = await this.functions.admin<{ updated: number }>('nodeBatch', payload);
- await this.load(); this.bulkMessage.set(`已处理 ${result.updated} 个栏目`);
- } catch (error) { this.bulkError.set(error instanceof Error ? error.message : '批量栏目操作失败'); }
- finally { this.updatingNode.set(false); }
- }
- async moveSpecial(): Promise<void> {
- this.specialActionError.set(''); this.specialActionMessage.set('');
- const pid = this.targetSpecialParentId();
- if (this.className() !== 'Special' || !this.draftId() || !Number.isInteger(pid) || pid < 0) { this.specialActionError.set('请输入有效父专题 ID,顶级专题填 0'); return; }
- this.updatingSpecial.set(true);
- try {
- const result = await this.functions.admin<{ results: Record<string, unknown>[] }>('specialBatch', { className: 'Special', action: 'move', objectIds: [this.draftId()], pid });
- if (result.results?.[0]) { this.draft.set(result.results[0]); this.targetSpecialParentId.set(Number(result.results[0]['pid']) || 0); }
- this.specialActionMessage.set(`专题已移动到父专题 ${pid}`); await this.load();
- } catch (error) { this.specialActionError.set(error instanceof Error ? error.message : '专题移动失败'); }
- finally { this.updatingSpecial.set(false); }
- }
- async applyBulkSpecialMove(): Promise<void> {
- const objectIds = this.selectedIds(); const pid = this.targetSpecialParentId(); this.bulkError.set(''); this.bulkMessage.set('');
- if (this.className() !== 'Special' || !objectIds.length) { this.bulkError.set('请先选择专题'); return; }
- if (!Number.isInteger(pid) || pid < 0) { this.bulkError.set('请输入有效父专题 ID,顶级专题填 0'); return; }
- this.updatingSpecial.set(true);
- try {
- const result = await this.functions.admin<{ updated: number }>('specialBatch', { className: 'Special', action: 'move', objectIds, pid });
- await this.load(); this.bulkMessage.set(`已移动 ${result.updated} 个专题`);
- } catch (error) { this.bulkError.set(error instanceof Error ? error.message : '批量专题移动失败'); }
- finally { this.updatingSpecial.set(false); }
- }
- async applyGuestbookAction(action: 'audit' | 'unaudit' | 'recycle' | 'recover'): Promise<void> {
- this.guestbookActionError.set(''); this.guestbookActionMessage.set('');
- if (this.className() !== 'Guestbook' || !this.draftId()) { this.guestbookActionError.set('只能管理现有留言'); return; }
- if (action === 'recycle' && !confirm('确认将该留言移入回收站?')) return;
- this.updatingGuestbook.set(true);
- try {
- const result = await this.functions.admin<{ results: Record<string, unknown>[] }>('guestbookBatch', { className: 'Guestbook', action, objectIds: [this.draftId()] });
- if (result.results?.[0]) { this.draft.set(result.results[0]); this.selectedGuestbookStatus.set(Number(result.results[0]['status']) || 0); }
- this.guestbookActionMessage.set(action === 'audit' ? '留言已审核' : action === 'unaudit' ? '留言已取消审核' : action === 'recycle' ? '留言已移入回收站' : '留言已恢复'); await this.load();
- } catch (error) { this.guestbookActionError.set(error instanceof Error ? error.message : '留言状态更新失败'); }
- finally { this.updatingGuestbook.set(false); }
- }
- async replyGuestbook(): Promise<void> {
- this.guestbookActionError.set(''); this.guestbookActionMessage.set('');
- const content = this.guestbookReplyContent();
- if (this.className() !== 'Guestbook' || !this.draftId() || !content.trim()) { this.guestbookActionError.set('请输入回复内容'); return; }
- this.updatingGuestbook.set(true);
- try {
- await this.functions.admin('guestbookReply', { className: 'Guestbook', parentObjectId: this.draftId(), title: this.guestbookReplyTitle().trim(), content });
- this.guestbookReplyTitle.set(''); this.guestbookReplyContent.set(''); this.guestbookActionMessage.set('管理员回复已发布');
- } catch (error) { this.guestbookActionError.set(error instanceof Error ? error.message : '管理员回复失败'); }
- finally { this.updatingGuestbook.set(false); }
- }
- async applyBulkGuestbookAction(action: 'audit' | 'unaudit' | 'recycle' | 'recover'): Promise<void> {
- const objectIds = this.selectedIds(); this.bulkError.set(''); this.bulkMessage.set('');
- if (this.className() !== 'Guestbook' || !objectIds.length) { this.bulkError.set('请先选择留言'); return; }
- if (action === 'recycle' && !confirm(`确认将选中的 ${objectIds.length} 条留言移入回收站?`)) return;
- this.updatingGuestbook.set(true);
- try {
- const result = await this.functions.admin<{ updated: number }>('guestbookBatch', { className: 'Guestbook', action, objectIds });
- await this.load(); this.bulkMessage.set(`已处理 ${result.updated} 条留言`);
- } catch (error) { this.bulkError.set(error instanceof Error ? error.message : '批量留言操作失败'); }
- finally { this.updatingGuestbook.set(false); }
- }
- async applyBarAction(action: 'audit' | 'unaudit' | 'recycle' | 'recover' | 'move' | 'top-global' | 'top-section' | 'top-undo' | 'elite' | 'elite-undo' | 'bottom' | 'bottom-undo'): Promise<void> {
- this.barActionError.set(''); this.barActionMessage.set('');
- if (this.className() !== 'GuestBar' || !this.draftId()) { this.barActionError.set('只能管理现有帖子'); return; }
- if (action === 'recycle' && !confirm('确认将该帖子移入回收站?')) return;
- const payload: Record<string, unknown> = { className: 'GuestBar', action, objectIds: [this.draftId()] };
- if (action === 'move') {
- const cateId = this.targetBarCategoryId();
- if (!Number.isInteger(cateId) || cateId < 1) { this.barActionError.set('请输入有效目标贴吧分类 ID'); return; }
- payload['cateId'] = cateId;
- }
- this.updatingBar.set(true);
- try {
- const result = await this.functions.admin<{ results: Record<string, unknown>[] }>('barBatch', payload);
- if (result.results?.[0]) { this.draft.set(result.results[0]); this.targetBarCategoryId.set(Number(result.results[0]['cateId']) || 0); }
- this.barActionMessage.set('帖子状态已更新'); await this.load();
- } catch (error) { this.barActionError.set(error instanceof Error ? error.message : '帖子操作失败'); }
- finally { this.updatingBar.set(false); }
- }
- async applyBulkBarAction(action: 'audit' | 'unaudit' | 'recycle' | 'recover' | 'move' | 'top-global' | 'top-section' | 'top-undo' | 'elite' | 'elite-undo' | 'bottom' | 'bottom-undo'): Promise<void> {
- const objectIds = this.selectedIds(); this.bulkError.set(''); this.bulkMessage.set('');
- if (this.className() !== 'GuestBar' || !objectIds.length) { this.bulkError.set('请先选择帖子'); return; }
- if (action === 'recycle' && !confirm(`确认将选中的 ${objectIds.length} 条帖子移入回收站?`)) return;
- const payload: Record<string, unknown> = { className: 'GuestBar', action, objectIds };
- if (action === 'move') {
- const cateId = this.targetBarCategoryId();
- if (!Number.isInteger(cateId) || cateId < 1) { this.bulkError.set('请输入有效目标贴吧分类 ID'); return; }
- payload['cateId'] = cateId;
- }
- this.updatingBar.set(true);
- try { const result = await this.functions.admin<{ updated: number }>('barBatch', payload); await this.load(); this.bulkMessage.set(`已处理 ${result.updated} 条帖子`); }
- catch (error) { this.bulkError.set(error instanceof Error ? error.message : '批量帖子操作失败'); }
- finally { this.updatingBar.set(false); }
- }
- async applyBulkDictionaryAction(action: 'enable' | 'disable' | 'delete'): Promise<void> {
- const objectIds = this.selectedIds(); this.bulkError.set(''); this.bulkMessage.set('');
- if (!['Datadiccategory', 'Datadic'].includes(this.className()) || !objectIds.length) { this.bulkError.set('请先选择字典记录'); return; }
- if (action === 'delete' && !confirm(`确认删除选中的 ${objectIds.length} 条字典记录?`)) return;
- this.updatingDictionary.set(true);
- try {
- const result = await this.functions.admin<{ updated: number }>('dictionaryBatch', { className: this.className(), action, objectIds });
- await this.load(); this.bulkMessage.set(`已处理 ${result.updated} 条字典记录`);
- } catch (error) { this.bulkError.set(error instanceof Error ? error.message : '批量字典操作失败'); }
- finally { this.updatingDictionary.set(false); }
- }
- async applyBulkGradeDelete(): Promise<void> {
- const objectIds = this.selectedIds(); this.bulkError.set(''); this.bulkMessage.set('');
- if (!['GradeCate', 'Grade'].includes(this.className()) || !objectIds.length) { this.bulkError.set('请先选择多级字典记录'); return; }
- if (!confirm(`确认删除选中的 ${objectIds.length} 条多级字典记录?`)) return;
- this.updatingGrade.set(true);
- try { const result = await this.functions.admin<{ updated: number }>('gradeBatch', { className: this.className(), action: 'delete', objectIds }); await this.load(); this.bulkMessage.set(`已删除 ${result.updated} 条多级字典记录`); }
- catch (error) { this.bulkError.set(error instanceof Error ? error.message : '批量删除多级字典失败'); }
- finally { this.updatingGrade.set(false); }
- }
- async applyBulkCurrencyDelete(): Promise<void> {
- const objectIds = this.selectedIds(); this.bulkError.set(''); this.bulkMessage.set('');
- if (this.className() !== 'Currency' || !objectIds.length) { this.bulkError.set('请先选择货币记录'); return; }
- if (!confirm(`确认删除选中的 ${objectIds.length} 条货币记录?`)) return;
- this.updatingCurrency.set(true);
- try { const result = await this.functions.admin<{ updated: number }>('currencyBatch', { className: 'Currency', action: 'delete', objectIds }); await this.load(); this.bulkMessage.set(`已删除 ${result.updated} 条货币记录`); }
- catch (error) { this.bulkError.set(error instanceof Error ? error.message : '批量删除货币失败'); }
- finally { this.updatingCurrency.set(false); }
- }
- async remove(row: Record<string, unknown>): Promise<void> {
- const objectId = String(row['objectId'] ?? '');
- const verb = ['CommonModel', 'Node', 'Guestbook'].includes(this.className()) ? '移入回收站' : '删除';
- if (!objectId || !confirm(`确认${verb} ${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<string, unknown>;
- return String(data['name'] ?? data['title'] ?? data['username'] ?? data['objectId'] ?? JSON.stringify(value));
- }
- return String(value);
- }
- groupIdOf(group: Record<string, unknown>): number { return Number(group['groupId']) || 0; }
- groupLabel(group: Record<string, unknown>): string { return `${String(group['groupName'] || group['name'] || '未命名组')}(${this.groupIdOf(group)})`; }
- dictionaryCategoryIdOf(category: Record<string, unknown>): number { return Number(category['diccateid']) || 0; }
- gradeCategoryIdOf(category: Record<string, unknown>): number { return Number(category['cateId']) || 0; }
- contentStatusValue(): number { return Number(this.draft()['status']) || 0; }
- contentStatusLabel(status: number): string { return ({ [-3]: '草稿', [-2]: '回收站', [-1]: '未通过', 0: '待审核', 99: '已审核' } as Record<number, string>)[status] || String(status); }
- nodeStatusValue(): number { return Number(this.draft()['zstatus'] ?? 99); }
- private async loadUserGroups(): Promise<void> {
- try {
- const groups = await this.functions.admin<ResourcePage>('list', { className: 'Group', page: 1, pageSize: 100, sort: 'orderId', order: 'asc' });
- this.userGroups.set(groups.results);
- } catch (error) { this.userGroups.set([]); this.userActionError.set(error instanceof Error ? error.message : '用户组加载失败'); }
- }
- private async loadDictionaryCategories(): Promise<void> {
- try {
- const result = await this.functions.admin<{ results: Record<string, unknown>[] }>('dictionaryCategories', { enabledOnly: false });
- this.dictionaryCategories.set(result.results || []);
- } catch (error) { this.dictionaryCategories.set([]); this.error.set(error instanceof Error ? error.message : '字典分类加载失败'); }
- }
- private async loadGradeCategories(): Promise<void> {
- try { const result = await this.functions.admin<{ results: Record<string, unknown>[] }>('gradeCategories', {}); this.gradeCategories.set(result.results || []); }
- catch (error) { this.gradeCategories.set([]); this.error.set(error instanceof Error ? error.message : '多级字典分类加载失败'); }
- }
- 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 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(''); }
- private resetGuestCategoryActions(detail: Record<string, unknown> = {}): void { this.guestCategoryType.set(Number(detail['gtype']) || 0); this.targetGuestCategoryParentId.set(Number(detail['parentId']) || 0); }
- private resetBarActions(detail: Record<string, unknown> = {}): void { this.targetBarCategoryId.set(Number(detail['cateId']) || 0); this.barActionError.set(''); this.barActionMessage.set(''); }
- private resetExamClassActions(detail: Record<string, unknown> = {}): void { this.targetExamClassParentId.set(Number(detail['cClassid']) || 0); this.examClassType.set(Number(detail['cClassType']) || 1); this.examClassOrder.set(Number(detail['cOrderBy']) || 0); }
- private resetExamPointActions(detail: Record<string, unknown> = {}): void { this.targetExamPointParentId.set(Number(detail['tid']) || 0); this.examPointOrder.set(Number(detail['orderBy']) || 0); }
- private resetKnowledgeActions(detail: Record<string, unknown> = {}): void { this.targetKnowledgeClassId.set(Number(detail['kClassId']) || 0); this.targetKnowledgeParentId.set(Number(detail['pid']) || 0); this.knowledgeOrder.set(Number(detail['kOrderBy']) || 0); }
- private resetTeacherActions(detail: Record<string, unknown> = {}): void { this.targetTeacherClassId.set(Number(detail['tclsss']) || 0); }
- private resetDictionaryActions(detail: Record<string, unknown> = {}): void { this.targetDictionaryCategoryId.set(Number(detail['diccate']) || 0); }
- private resetGradeActions(detail: Record<string, unknown> = {}): void { this.targetGradeCategoryId.set(Number(detail['cate']) || this.filterGradeCategoryId()); this.targetGradeParentId.set(Number(detail['parentId']) || this.filterGradeParentId()); }
- }
|