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'; interface SurveyChart { survey: Record; responseCount: number; invalidAnswers: number; questions: { id: string; title: string; type: string; options: { text: string; value: string; count: number }[]; otherCount: number }[]; } @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 resettingPassword = 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 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[]>([]); 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[]>([]); readonly updatingDictionary = signal(false); readonly targetGradeCategoryId = signal(0); readonly targetGradeParentId = signal(0); readonly filterGradeCategoryId = signal(0); readonly filterGradeParentId = signal(0); readonly gradeCategories = signal[]>([]); readonly updatingGrade = signal(false); readonly updatingCurrency = signal(false); readonly updatingHoliday = signal(false); readonly filterSearchNavigationType = signal(1); readonly filterSearchNavigationState = signal(-100); readonly filterSearchNavigationElite = signal(-100); readonly searchNavigationOrderStart = signal(1); readonly updatingSearchNavigation = signal(false); readonly filterAdZoneType = signal(''); readonly filterAdStatus = signal(-100); readonly filterAdInfoZoneId = signal(''); readonly targetAdZoneId = signal(''); readonly adZones = signal[]>([]); readonly adPreview = signal[]>([]); readonly adPreviewLoading = signal(false); readonly adPreviewError = signal(''); readonly updatingAdvertisement = signal(false); readonly filterFontShapeTypeId = signal(0); readonly targetFontShapeTypeId = signal(0); readonly fontShapeTypes = signal[]>([]); readonly fontShapePreview = signal(''); readonly updatingFontShape = signal(false); readonly filterSurveyStatus = signal(-100); readonly filterSurveyId = signal(0); readonly surveys = signal[]>([]); readonly surveyOrderStart = signal(1); readonly surveyChart = signal(null); readonly surveyChartLoading = signal(false); readonly surveyChartError = signal(''); readonly updatingSurvey = signal(false); readonly targetServiceSeatUsername = signal(''); readonly updatingService = signal(false); readonly filterStoreStatus = signal(-100); readonly targetStoreStyleId = signal(0); readonly targetStoreUsername = signal(''); readonly storeStyles = signal[]>([]); readonly updatingStore = signal(false); readonly selectedIds = signal([]); 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', 'qtitle', 'sName', 'storeName', 'styleName', 'str1', 'categoryname', 'dicname', 'cateName', 'gradeName', 'cClassName', 'testPoint', 'kName', 'tname', 'username', 'userName', 'realName', 'sRemrk', 'askId', 'zoneId', 'typeId', 'shape', 'qtype', 'required', 'ztype', 'priority', 'zstatus', 'storeState', 'storeCommendState', 'storeStyleId', 'sDefault', 'sIndex', 'str4', 'sdate', 'edate', 'fileUrl', 'type', 'state', 'eliteLevel', 'orderId', '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', 'SysHoliday', 'Search', 'AdZone', 'AdInfo', 'FontPicShape', 'FontPicShapeType', 'DesignAsk', 'DesignQuestion', 'DesignAnswer', 'ServiceSeat', 'Temp', 'StoreApplication', 'StoreStyle'].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); this.filterSearchNavigationType.set(1); this.filterSearchNavigationState.set(-100); this.filterSearchNavigationElite.set(-100); this.searchNavigationOrderStart.set(1); this.filterAdZoneType.set(''); this.filterAdStatus.set(-100); this.filterAdInfoZoneId.set(''); this.resetAdActions(); this.filterFontShapeTypeId.set(0); this.resetFontShapeActions(); this.filterSurveyStatus.set(-100); this.filterSurveyId.set(0); this.resetSurveyActions(); this.resetServiceActions(); this.filterStoreStatus.set(-100); this.resetStoreActions(); void this.load(1, true); }); } async load(pageNumber = this.page()?.page ?? 1, reloadSchema = false): Promise { 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('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])); } if (this.className() === 'AdInfo' && reloadSchema) await this.loadAdZones(); if (this.className() === 'FontPicShape' && reloadSchema) await this.loadFontShapeTypes(); if (['DesignQuestion', 'DesignAnswer'].includes(this.className()) && reloadSchema) { await this.loadSurveys(); if (!this.filterSurveyId() && this.surveys().length) this.filterSurveyId.set(this.surveyIdOf(this.surveys()[0])); } if (this.className() === 'StoreApplication' && reloadSchema) await this.loadStoreStyles(); const pageOperation = this.className() === 'Grade' && this.filterGradeCategoryId() > 0 ? 'gradeOptions' : this.className() === 'Search' ? 'searchNavigations' : this.className() === 'AdZone' ? 'adZones' : this.className() === 'AdInfo' ? 'adInfos' : this.className() === 'FontPicShape' ? 'fontShapes' : this.className() === 'FontPicShapeType' ? 'fontShapeTypes' : this.className() === 'DesignAsk' ? 'surveys' : this.className() === 'DesignQuestion' ? 'surveyQuestions' : this.className() === 'DesignAnswer' ? 'surveyResults' : this.className() === 'ServiceSeat' ? 'serviceSeats' : this.className() === 'Temp' ? 'serviceCodes' : this.className() === 'StoreApplication' ? 'storeApplications' : this.className() === 'StoreStyle' ? 'storeStyles' : 'list'; const pagePayload: Record = { className: this.className(), page: pageNumber, pageSize: 20, search: this.search().trim() }; if (pageOperation === 'gradeOptions') { pagePayload['categoryId'] = this.filterGradeCategoryId(); pagePayload['parentId'] = this.filterGradeParentId(); } if (pageOperation === 'searchNavigations') { pagePayload['type'] = this.filterSearchNavigationType(); pagePayload['state'] = this.filterSearchNavigationState(); pagePayload['elite'] = this.filterSearchNavigationElite(); } if (pageOperation === 'adZones') { pagePayload['type'] = this.filterAdZoneType(); pagePayload['status'] = this.filterAdStatus(); } if (pageOperation === 'adInfos') { pagePayload['zoneId'] = this.filterAdInfoZoneId(); pagePayload['status'] = this.filterAdStatus(); } if (pageOperation === 'fontShapes') pagePayload['typeId'] = this.filterFontShapeTypeId(); if (pageOperation === 'surveys') pagePayload['status'] = this.filterSurveyStatus(); if (pageOperation === 'surveyQuestions' || pageOperation === 'surveyResults') pagePayload['askId'] = this.filterSurveyId(); if (pageOperation === 'storeApplications') pagePayload['status'] = this.filterStoreStatus(); this.page.set(await this.functions.admin(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.resetAdActions(); this.resetFontShapeActions(); this.resetSurveyActions(); this.resetServiceActions(); this.resetStoreActions(); 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.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.resetAdActions(detail); this.resetFontShapeActions(detail); this.resetSurveyActions(); this.resetServiceActions(detail); this.resetStoreActions(detail); this.editorOpen.set(true); if (this.className() === '_User') await this.loadUserGroups(); if (this.className() === 'AdInfo' && !this.adZones().length) await this.loadAdZones(); if (this.className() === 'AdZone') await this.loadAdPreview(); if (this.className() === 'FontPicShape' && !this.fontShapeTypes().length) await this.loadFontShapeTypes(); } 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(); this.resetAdActions(); this.resetFontShapeActions(); this.resetSurveyActions(); this.resetServiceActions(); this.resetStoreActions(); } 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 { 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' : this.className() === 'SysHoliday' ? 'saveHoliday' : this.className() === 'Search' ? 'saveSearchNavigation' : this.className() === 'AdZone' ? 'saveAdZone' : this.className() === 'AdInfo' ? 'saveAdInfo' : this.className() === 'FontPicShape' ? 'saveFontShape' : this.className() === 'FontPicShapeType' ? 'saveFontShapeType' : this.className() === 'DesignAsk' ? 'saveSurvey' : this.className() === 'DesignQuestion' ? 'saveSurveyQuestion' : this.className() === 'ServiceSeat' ? 'saveServiceSeat' : this.className() === 'Temp' ? 'saveServiceCode' : this.className() === 'StoreApplication' ? 'saveStoreApplication' : this.className() === 'StoreStyle' ? 'saveStoreStyle' : 'save'; const payload: Record = { 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(); } if (this.className() === 'AdInfo') payload['zoneId'] = this.targetAdZoneId(); if (this.className() === 'FontPicShape') payload['typeId'] = this.targetFontShapeTypeId(); if (this.className() === 'ServiceSeat') payload['username'] = this.targetServiceSeatUsername(); if (this.className() === 'StoreApplication') { payload['username'] = this.targetStoreUsername(); payload['styleId'] = this.targetStoreStyleId(); } 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 { if (this.className() === '_User' && !this.draftId()) await this.createUser(); else await this.save(); } async createUser(): Promise { 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 = { 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 { 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 { 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[]; 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 { 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[] }>('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 { this.contentActionError.set(''); this.contentActionMessage.set(''); if (this.className() !== 'CommonModel' || !this.draftId()) { this.contentActionError.set('只能管理现有内容'); return; } if (action === 'recycle' && !confirm('确认将该内容移入回收站?')) return; const payload: Record = { 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[] }>('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 { 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 = { 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 { 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 = { 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 { this.nodeActionError.set(''); this.nodeActionMessage.set(''); if (this.className() !== 'Node' || !this.draftId()) { this.nodeActionError.set('只能管理现有栏目'); return; } if (action === 'recycle' && !confirm('确认将该栏目移入回收站?')) return; const payload: Record = { 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[] }>('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 { 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 = { 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 { 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[] }>('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 { 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 { 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[] }>('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 { 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 { 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 { this.barActionError.set(''); this.barActionMessage.set(''); if (this.className() !== 'GuestBar' || !this.draftId()) { this.barActionError.set('只能管理现有帖子'); return; } if (action === 'recycle' && !confirm('确认将该帖子移入回收站?')) return; const payload: Record = { 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[] }>('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 { 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 = { 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 { 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 { 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 { 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 applyBulkHolidayDelete(): Promise { const objectIds = this.selectedIds(); this.bulkError.set(''); this.bulkMessage.set(''); if (this.className() !== 'SysHoliday' || !objectIds.length) { this.bulkError.set('请先选择节假日记录'); return; } if (!confirm(`确认删除选中的 ${objectIds.length} 条节假日记录?`)) return; this.updatingHoliday.set(true); try { const result = await this.functions.admin<{ updated: number }>('holidayBatch', { className: 'SysHoliday', action: 'delete', objectIds }); await this.load(); this.bulkMessage.set(`已删除 ${result.updated} 条节假日记录`); } catch (error) { this.bulkError.set(error instanceof Error ? error.message : '批量删除节假日失败'); } finally { this.updatingHoliday.set(false); } } async applyBulkSearchNavigationAction(action: 'enable' | 'disable' | 'elite' | 'unelite' | 'order' | 'delete'): Promise { const objectIds = this.selectedIds(); this.bulkError.set(''); this.bulkMessage.set(''); if (this.className() !== 'Search' || !objectIds.length) { this.bulkError.set('请先选择快捷入口'); return; } if (action === 'delete' && !confirm(`确认删除选中的 ${objectIds.length} 条快捷入口?`)) return; const payload: Record = { className: 'Search', action, objectIds }; if (action === 'order') { const start = this.searchNavigationOrderStart(); if (!Number.isInteger(start) || start < 1 || start + objectIds.length - 1 > 1000000) { this.bulkError.set('请输入有效的起始排序'); return; } payload['orders'] = objectIds.map((objectId, index) => ({ objectId, orderId: start + index })); } this.updatingSearchNavigation.set(true); try { const result = await this.functions.admin<{ updated: number }>('searchNavigationBatch', payload); await this.load(); this.bulkMessage.set(`已处理 ${result.updated} 条快捷入口`); } catch (error) { this.bulkError.set(error instanceof Error ? error.message : '批量处理快捷入口失败'); } finally { this.updatingSearchNavigation.set(false); } } async applyBulkAdvertisementAction(action: 'active' | 'pause' | 'audit' | 'unaudit' | 'delete'): Promise { const className = this.className(); const objectIds = this.selectedIds(); this.bulkError.set(''); this.bulkMessage.set(''); if (!['AdZone', 'AdInfo'].includes(className) || !objectIds.length) { this.bulkError.set('请先选择广告位或广告内容'); return; } if (action === 'delete' && !confirm(`确认删除选中的 ${objectIds.length} 条${className === 'AdZone' ? '广告位' : '广告内容'}?`)) return; if (className === 'AdZone' && !['active', 'pause', 'delete'].includes(action)) { this.bulkError.set('广告位操作无效'); return; } if (className === 'AdInfo' && !['audit', 'unaudit', 'delete'].includes(action)) { this.bulkError.set('广告内容操作无效'); return; } this.updatingAdvertisement.set(true); try { const result = await this.functions.admin<{ updated: number }>(className === 'AdZone' ? 'adZoneBatch' : 'adInfoBatch', { className, action, objectIds }); await this.load(); this.bulkMessage.set(`已处理 ${result.updated} 条${className === 'AdZone' ? '广告位' : '广告内容'}`); } catch (error) { this.bulkError.set(error instanceof Error ? error.message : '广告批量操作失败'); } finally { this.updatingAdvertisement.set(false); } } async loadAdPreview(): Promise { this.adPreview.set([]); this.adPreviewError.set(''); if (this.className() !== 'AdZone' || !this.draftId()) return; this.adPreviewLoading.set(true); try { const result = await this.functions.admin<{ results: Record[] }>('adPreview', { zoneObjectId: this.draftId() }); this.adPreview.set(result.results || []); } catch (error) { this.adPreviewError.set(error instanceof Error ? error.message : '广告位预览加载失败'); } finally { this.adPreviewLoading.set(false); } } async applyBulkFontShapeDelete(): Promise { const className = this.className(); const objectIds = this.selectedIds(); this.bulkError.set(''); this.bulkMessage.set(''); if (!['FontPicShape', 'FontPicShapeType'].includes(className) || !objectIds.length) { this.bulkError.set('请先选择图形素材或分类'); return; } if (!confirm(`确认删除选中的 ${objectIds.length} 条${className === 'FontPicShape' ? '图形素材' : '图形分类'}?`)) return; this.updatingFontShape.set(true); try { const result = await this.functions.admin<{ updated: number }>(className === 'FontPicShape' ? 'fontShapeBatch' : 'fontShapeTypeBatch', { className, action: 'delete', objectIds }); await this.load(); this.bulkMessage.set(`已删除 ${result.updated} 条${className === 'FontPicShape' ? '图形素材' : '图形分类'}`); } catch (error) { this.bulkError.set(error instanceof Error ? error.message : '图形删除失败'); } finally { this.updatingFontShape.set(false); } } async applyBulkSurveyAction(action: 'start' | 'stop' | 'sort' | 'delete'): Promise { const className = this.className(); const objectIds = this.selectedIds(); this.bulkError.set(''); this.bulkMessage.set(''); const allowed = className === 'DesignAsk' ? ['start', 'stop', 'delete'] : className === 'DesignQuestion' ? ['sort', 'delete'] : className === 'DesignAnswer' ? ['delete'] : []; if (!allowed.includes(action) || !objectIds.length) { this.bulkError.set('请先选择可执行该操作的问卷记录'); return; } if (action === 'delete' && !confirm(`确认删除选中的 ${objectIds.length} 条问卷记录?`)) return; const payload: Record = { className, action, objectIds }; if (action === 'sort') { const start = this.surveyOrderStart(); if (!Number.isInteger(start) || start < 1 || start + objectIds.length - 1 > 1000000) { this.bulkError.set('请输入有效的起始排序'); return; } payload['orders'] = objectIds.map((objectId, index) => ({ objectId, orderId: start + index })); } this.updatingSurvey.set(true); try { const result = await this.functions.admin<{ updated: number }>('surveyBatch', payload); await this.load(); this.bulkMessage.set(`已处理 ${result.updated} 条问卷记录`); } catch (error) { this.bulkError.set(error instanceof Error ? error.message : '问卷批量操作失败'); } finally { this.updatingSurvey.set(false); } } async loadSurveyChart(): Promise { this.surveyChart.set(null); this.surveyChartError.set(''); if (this.className() !== 'DesignAnswer' || !this.filterSurveyId()) return; this.surveyChartLoading.set(true); try { this.surveyChart.set(await this.functions.admin('surveyResultChart', { askId: this.filterSurveyId() })); } catch (error) { this.surveyChartError.set(error instanceof Error ? error.message : '问卷统计加载失败'); } finally { this.surveyChartLoading.set(false); } } async applyBulkServiceDelete(): Promise { const className = this.className(); const objectIds = this.selectedIds(); this.bulkError.set(''); this.bulkMessage.set(''); if (!['ServiceSeat', 'Temp'].includes(className) || !objectIds.length) { this.bulkError.set('请先选择客服席位或欢迎语'); return; } if (!confirm(`确认删除选中的 ${objectIds.length} 条${className === 'ServiceSeat' ? '客服席位' : '欢迎语'}?`)) return; this.updatingService.set(true); try { const result = await this.functions.admin<{ updated: number }>('serviceBatch', { className, action: 'delete', objectIds }); await this.load(); this.bulkMessage.set(`已删除 ${result.updated} 条客服记录`); } catch (error) { this.bulkError.set(error instanceof Error ? error.message : '客服记录删除失败'); } finally { this.updatingService.set(false); } } async applyBulkStoreAction(action: 'audit' | 'unaudit' | 'elite' | 'unelite' | 'delete'): Promise { const className = this.className(); const objectIds = this.selectedIds(); this.bulkError.set(''); this.bulkMessage.set(''); const allowed = className === 'StoreApplication' ? ['audit', 'unaudit', 'elite', 'unelite'] : className === 'StoreStyle' ? ['delete'] : []; if (!allowed.includes(action) || !objectIds.length) { this.bulkError.set(action === 'delete' && className === 'StoreApplication' ? '店铺删除因商品主表缺失而阻塞' : '请先选择可执行该操作的店铺记录'); return; } if (action === 'delete' && !confirm(`确认删除选中的 ${objectIds.length} 条店铺样式?`)) return; this.updatingStore.set(true); try { const result = await this.functions.admin<{ updated: number }>('storeBatch', { className, action, objectIds }); await this.load(); this.bulkMessage.set(`已处理 ${result.updated} 条店铺记录`); } catch (error) { this.bulkError.set(error instanceof Error ? error.message : '店铺批量操作失败'); } finally { this.updatingStore.set(false); } } async remove(row: Record): Promise { const objectId = String(row['objectId'] ?? ''); const verb = ['CommonModel', 'Node', 'Guestbook'].includes(this.className()) ? '移入回收站' : '删除'; if (!objectId || !confirm(`确认${verb} ${this.className()} / ${objectId}?`)) return; try { if (this.className() === 'Search') await this.functions.admin('searchNavigationBatch', { className: 'Search', action: 'delete', objectIds: [objectId] }); else if (this.className() === 'AdZone') await this.functions.admin('adZoneBatch', { className: 'AdZone', action: 'delete', objectIds: [objectId] }); else if (this.className() === 'AdInfo') await this.functions.admin('adInfoBatch', { className: 'AdInfo', action: 'delete', objectIds: [objectId] }); else if (this.className() === 'FontPicShape') await this.functions.admin('fontShapeBatch', { className: 'FontPicShape', action: 'delete', objectIds: [objectId] }); else if (this.className() === 'FontPicShapeType') await this.functions.admin('fontShapeTypeBatch', { className: 'FontPicShapeType', action: 'delete', objectIds: [objectId] }); else if (['DesignAsk', 'DesignQuestion', 'DesignAnswer'].includes(this.className())) await this.functions.admin('surveyBatch', { className: this.className(), action: 'delete', objectIds: [objectId] }); else if (['ServiceSeat', 'Temp'].includes(this.className())) await this.functions.admin('serviceBatch', { className: this.className(), action: 'delete', objectIds: [objectId] }); else if (['StoreApplication', 'StoreStyle'].includes(this.className())) await this.functions.admin('storeBatch', { className: this.className(), action: 'delete', objectIds: [objectId] }); else 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); } groupIdOf(group: Record): number { return Number(group['groupId']) || 0; } groupLabel(group: Record): string { return `${String(group['groupName'] || group['name'] || '未命名组')}(${this.groupIdOf(group)})`; } dictionaryCategoryIdOf(category: Record): number { return Number(category['diccateid']) || 0; } gradeCategoryIdOf(category: Record): number { return Number(category['cateId']) || 0; } adZoneIdOf(zone: Record): string { return String(zone['id'] || ''); } adZoneLabel(zone: Record): string { return `${String(zone['name'] || '未命名广告位')}(${this.adZoneIdOf(zone)})`; } fontShapeTypeIdOf(type: Record): number { return Number(type['id']) || 0; } fontShapeTypeLabel(type: Record): string { return `${String(type['name'] || '未命名分类')}(${this.fontShapeTypeIdOf(type)})`; } surveyIdOf(survey: Record): number { return Number(survey['id']) || 0; } surveyLabel(survey: Record): string { return `${String(survey['title'] || '未命名问卷')}(${this.surveyIdOf(survey)})`; } storeStyleIdOf(style: Record): number { return Number(style['id']) || 0; } storeStyleLabel(style: Record): string { return `${String(style['styleName'] || '未命名样式')}(${this.storeStyleIdOf(style)})`; } contentStatusValue(): number { return Number(this.draft()['status']) || 0; } contentStatusLabel(status: number): string { return ({ [-3]: '草稿', [-2]: '回收站', [-1]: '未通过', 0: '待审核', 99: '已审核' } as Record)[status] || String(status); } nodeStatusValue(): number { return Number(this.draft()['zstatus'] ?? 99); } private async loadUserGroups(): Promise { try { const groups = await this.functions.admin('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 { try { const result = await this.functions.admin<{ results: Record[] }>('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 { try { const result = await this.functions.admin<{ results: Record[] }>('gradeCategories', {}); this.gradeCategories.set(result.results || []); } catch (error) { this.gradeCategories.set([]); this.error.set(error instanceof Error ? error.message : '多级字典分类加载失败'); } } private async loadAdZones(): Promise { try { const result = await this.functions.admin('adZones', { className: 'AdZone', page: 1, pageSize: 1000, search: '', type: '', status: -100 }); this.adZones.set(result.results || []); } catch (error) { this.adZones.set([]); this.error.set(error instanceof Error ? error.message : '广告位选项加载失败'); } } private async loadFontShapeTypes(): Promise { try { const result = await this.functions.admin('fontShapeTypes', { className: 'FontPicShapeType', page: 1, pageSize: 1000, search: '' }); this.fontShapeTypes.set(result.results || []); } catch (error) { this.fontShapeTypes.set([]); this.error.set(error instanceof Error ? error.message : '图形分类选项加载失败'); } } private async loadSurveys(): Promise { try { const result = await this.functions.admin('surveys', { className: 'DesignAsk', page: 1, pageSize: 100, search: '', status: -100 }); this.surveys.set(result.results || []); } catch (error) { this.surveys.set([]); this.error.set(error instanceof Error ? error.message : '问卷选项加载失败'); } } private async loadStoreStyles(): Promise { try { const result = await this.functions.admin('storeStyles', { className: 'StoreStyle', page: 1, pageSize: 100, search: '' }); this.storeStyles.set(result.results || []); } catch (error) { this.storeStyles.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 = {}): void { this.userGroups.set([]); this.selectedUserGroupId.set(Number(detail['legacyGroupId'] ?? (detail['legacyUserData'] as Record | undefined)?.['GroupID']) || 0); this.userActionReason.set(''); this.userActionError.set(''); this.userActionMessage.set(''); } private resetContentActions(detail: Record = {}): 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 = {}): void { this.targetNodeParentId.set(Number(detail['parentId']) || 0); this.nodeActionError.set(''); this.nodeActionMessage.set(''); } private resetSpecialActions(detail: Record = {}): void { this.targetSpecialParentId.set(Number(detail['pid']) || 0); this.specialActionError.set(''); this.specialActionMessage.set(''); } private resetGuestbookActions(detail: Record = {}): void { this.selectedGuestbookStatus.set(Number(detail['status'] ?? 99)); this.guestbookReplyTitle.set(''); this.guestbookReplyContent.set(''); this.guestbookActionError.set(''); this.guestbookActionMessage.set(''); } private resetGuestCategoryActions(detail: Record = {}): void { this.guestCategoryType.set(Number(detail['gtype']) || 0); this.targetGuestCategoryParentId.set(Number(detail['parentId']) || 0); } private resetBarActions(detail: Record = {}): void { this.targetBarCategoryId.set(Number(detail['cateId']) || 0); this.barActionError.set(''); this.barActionMessage.set(''); } private resetExamClassActions(detail: Record = {}): 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 = {}): void { this.targetExamPointParentId.set(Number(detail['tid']) || 0); this.examPointOrder.set(Number(detail['orderBy']) || 0); } private resetKnowledgeActions(detail: Record = {}): 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 = {}): void { this.targetTeacherClassId.set(Number(detail['tclsss']) || 0); } private resetDictionaryActions(detail: Record = {}): void { this.targetDictionaryCategoryId.set(Number(detail['diccate']) || 0); } private resetGradeActions(detail: Record = {}): void { this.targetGradeCategoryId.set(Number(detail['cate']) || this.filterGradeCategoryId()); this.targetGradeParentId.set(Number(detail['parentId']) || this.filterGradeParentId()); } private resetAdActions(detail: Record = {}): void { this.targetAdZoneId.set(String(detail['zoneId'] || this.filterAdInfoZoneId() || '')); this.adPreview.set([]); this.adPreviewError.set(''); this.adPreviewLoading.set(false); } private resetFontShapeActions(detail: Record = {}): void { this.targetFontShapeTypeId.set(Number(detail['typeId']) || this.filterFontShapeTypeId()); this.fontShapePreview.set(String(detail['shape'] || '')); } private resetSurveyActions(): void { this.surveyOrderStart.set(1); this.surveyChart.set(null); this.surveyChartError.set(''); this.surveyChartLoading.set(false); } private resetServiceActions(detail: Record = {}): void { this.targetServiceSeatUsername.set(String(detail['sRemrk'] || '')); } private resetStoreActions(detail: Record = {}): void { this.targetStoreUsername.set(String(detail['userName'] || '')); this.targetStoreStyleId.set(Number(detail['storeStyleId']) || 0); } }