admin-resource.component.ts 79 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944
  1. import { CommonModule } from '@angular/common';
  2. import { ChangeDetectionStrategy, Component, DestroyRef, OnInit, computed, inject, signal } from '@angular/core';
  3. import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
  4. import { FormsModule } from '@angular/forms';
  5. import { ActivatedRoute } from '@angular/router';
  6. import { ChevronLeft, ChevronRight, Eye, KeyRound, LoaderCircle, LucideAngularModule, Pencil, Plus, RefreshCw, Search, Trash2, X } from 'lucide-angular';
  7. import { AdminClassSchema, AdminFieldSchema, ResourcePage } from '../admin.models';
  8. import { CloudFunctionsService } from '../cloud-functions.service';
  9. interface SurveyChart {
  10. survey: Record<string, unknown>;
  11. responseCount: number;
  12. invalidAnswers: number;
  13. questions: { id: string; title: string; type: string; options: { text: string; value: string; count: number }[]; otherCount: number }[];
  14. }
  15. interface PlatformMemberPage {
  16. companyObjectId: string;
  17. compId: number;
  18. creatorUsername: string;
  19. page: number;
  20. pageSize: number;
  21. total: number;
  22. results: Record<string, unknown>[];
  23. }
  24. @Component({
  25. selector: 'app-admin-resource',
  26. standalone: true,
  27. imports: [CommonModule, FormsModule, LucideAngularModule],
  28. templateUrl: './admin-resource.component.html',
  29. styleUrl: './admin-resource.component.scss',
  30. changeDetection: ChangeDetectionStrategy.OnPush,
  31. })
  32. export class AdminResourceComponent implements OnInit {
  33. readonly className = signal('');
  34. readonly schema = signal<AdminClassSchema | null>(null);
  35. readonly page = signal<ResourcePage | null>(null);
  36. readonly loading = signal(true);
  37. readonly saving = signal(false);
  38. readonly resettingPassword = signal(false);
  39. readonly error = signal('');
  40. readonly search = signal('');
  41. readonly editorOpen = signal(false);
  42. readonly draftId = signal('');
  43. readonly draft = signal<Record<string, unknown>>({});
  44. readonly fieldErrors = signal<Record<string, string>>({});
  45. readonly fieldInputs = signal<Record<string, string>>({});
  46. readonly newPassword = signal('');
  47. readonly confirmPassword = signal('');
  48. readonly passwordMessage = signal('');
  49. readonly passwordError = signal('');
  50. readonly createUsername = signal('');
  51. readonly createPassword = signal('');
  52. readonly createConfirmPassword = signal('');
  53. readonly createDisplayName = signal('');
  54. readonly createMobile = signal('');
  55. readonly createInviteUsername = signal('');
  56. readonly createUserError = signal('');
  57. readonly userGroups = signal<Record<string, unknown>[]>([]);
  58. readonly selectedUserGroupId = signal(0);
  59. readonly userActionReason = signal('');
  60. readonly userActionError = signal('');
  61. readonly userActionMessage = signal('');
  62. readonly updatingUser = signal(false);
  63. readonly selectedContentStatus = signal(0);
  64. readonly targetContentNodeId = signal(0);
  65. readonly updatingContent = signal(false);
  66. readonly contentActionError = signal('');
  67. readonly contentActionMessage = signal('');
  68. readonly duplicateContentTitles = signal<Record<string, unknown>[]>([]);
  69. readonly targetNodeParentId = signal(0);
  70. readonly updatingNode = signal(false);
  71. readonly nodeActionError = signal('');
  72. readonly nodeActionMessage = signal('');
  73. readonly targetSpecialParentId = signal(0);
  74. readonly updatingSpecial = signal(false);
  75. readonly specialActionError = signal('');
  76. readonly specialActionMessage = signal('');
  77. readonly selectedGuestbookStatus = signal(99);
  78. readonly guestbookReplyTitle = signal('');
  79. readonly guestbookReplyContent = signal('');
  80. readonly updatingGuestbook = signal(false);
  81. readonly guestbookActionError = signal('');
  82. readonly guestbookActionMessage = signal('');
  83. readonly guestCategoryType = signal(0);
  84. readonly targetGuestCategoryParentId = signal(0);
  85. readonly targetBarCategoryId = signal(0);
  86. readonly updatingBar = signal(false);
  87. readonly barActionError = signal('');
  88. readonly barActionMessage = signal('');
  89. readonly targetExamClassParentId = signal(0);
  90. readonly examClassType = signal(1);
  91. readonly examClassOrder = signal(0);
  92. readonly targetExamPointParentId = signal(0);
  93. readonly examPointOrder = signal(0);
  94. readonly targetKnowledgeClassId = signal(0);
  95. readonly targetKnowledgeParentId = signal(0);
  96. readonly knowledgeOrder = signal(0);
  97. readonly targetTeacherClassId = signal(0);
  98. readonly targetDictionaryCategoryId = signal(0);
  99. readonly dictionaryCategories = signal<Record<string, unknown>[]>([]);
  100. readonly updatingDictionary = signal(false);
  101. readonly targetGradeCategoryId = signal(0);
  102. readonly targetGradeParentId = signal(0);
  103. readonly filterGradeCategoryId = signal(0);
  104. readonly filterGradeParentId = signal(0);
  105. readonly gradeCategories = signal<Record<string, unknown>[]>([]);
  106. readonly updatingGrade = signal(false);
  107. readonly updatingCurrency = signal(false);
  108. readonly updatingHoliday = signal(false);
  109. readonly filterSearchNavigationType = signal(1);
  110. readonly filterSearchNavigationState = signal(-100);
  111. readonly filterSearchNavigationElite = signal(-100);
  112. readonly searchNavigationOrderStart = signal(1);
  113. readonly updatingSearchNavigation = signal(false);
  114. readonly filterAdZoneType = signal('');
  115. readonly filterAdStatus = signal(-100);
  116. readonly filterAdInfoZoneId = signal('');
  117. readonly targetAdZoneId = signal('');
  118. readonly adZones = signal<Record<string, unknown>[]>([]);
  119. readonly adPreview = signal<Record<string, unknown>[]>([]);
  120. readonly adPreviewLoading = signal(false);
  121. readonly adPreviewError = signal('');
  122. readonly updatingAdvertisement = signal(false);
  123. readonly filterFontShapeTypeId = signal(0);
  124. readonly targetFontShapeTypeId = signal(0);
  125. readonly fontShapeTypes = signal<Record<string, unknown>[]>([]);
  126. readonly fontShapePreview = signal('');
  127. readonly updatingFontShape = signal(false);
  128. readonly filterSurveyStatus = signal(-100);
  129. readonly filterSurveyId = signal(0);
  130. readonly surveys = signal<Record<string, unknown>[]>([]);
  131. readonly surveyOrderStart = signal(1);
  132. readonly surveyChart = signal<SurveyChart | null>(null);
  133. readonly surveyChartLoading = signal(false);
  134. readonly surveyChartError = signal('');
  135. readonly updatingSurvey = signal(false);
  136. readonly targetServiceSeatUsername = signal('');
  137. readonly updatingService = signal(false);
  138. readonly filterStoreStatus = signal(-100);
  139. readonly targetStoreStyleId = signal(0);
  140. readonly targetStoreUsername = signal('');
  141. readonly storeStyles = signal<Record<string, unknown>[]>([]);
  142. readonly updatingStore = signal(false);
  143. readonly tagKeyContent = signal('');
  144. readonly filterUserMoneyType = signal(1);
  145. readonly filterUserMoneyStartDate = signal('');
  146. readonly filterUserMoneyEndDate = signal('');
  147. readonly updatingUserLevel = signal(false);
  148. readonly filterBaikeStatus = signal(-100);
  149. readonly filterBaikeElite = signal(-100);
  150. readonly updatingBaike = signal(false);
  151. readonly updatingCrmType = signal(false);
  152. readonly updatingShopFare = signal(false);
  153. readonly filterSystemLogType1 = signal('');
  154. readonly filterSystemLogType2 = signal('');
  155. readonly filterSystemLogStartDate = signal('');
  156. readonly filterSystemLogEndDate = signal('');
  157. readonly updatingMisType = signal(false);
  158. readonly updatingPageStyle = signal(false);
  159. readonly updatingPlatformCompany = signal(false);
  160. readonly platformMembers = signal<PlatformMemberPage | null>(null);
  161. readonly platformMembersLoading = signal(false);
  162. readonly platformMembersError = signal('');
  163. readonly platformMemberUsername = signal('');
  164. readonly platformCreatorUsername = signal('');
  165. readonly selectedIds = signal<string[]>([]);
  166. readonly bulkError = signal('');
  167. readonly bulkMessage = signal('');
  168. readonly icons = { ChevronLeft, ChevronRight, Eye, KeyRound, LoaderCircle, Pencil, Plus, RefreshCw, Search, Trash2, X };
  169. readonly columns = computed(() => {
  170. const fields = this.schema()?.fields ?? [];
  171. const preferred = ['objectId', 'name', 'title', 'typeName', 'pageNodeName', 'compName', 'tlpName', 'pName', 'pTitle', 'questionTitle', 'tName', 'newsName', 'nodeName', 'value', 'remark', 'qtitle', 'sName', 'storeName', 'styleName', 'count', 'content', 'str1', 'categoryname', 'dicname', 'cateName', 'gradeName', 'cClassName', 'testPoint', 'kName', 'tname', 'username', 'userName', 'realName', 'cname', 'type1', 'type2', 'detail', 'cdate', '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'];
  172. const rank = (name: string): number => { const index = preferred.indexOf(name); return index < 0 ? preferred.length : index; };
  173. 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);
  174. });
  175. readonly pageCount = computed(() => Math.max(1, Math.ceil((this.page()?.total ?? 0) / (this.page()?.pageSize || 20))));
  176. 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', 'UserLevel', 'Baike', 'CRMSAttr', 'ShopFareTlp', 'MisType', 'PageStyle', 'PlatComp'].includes(this.className()));
  177. readonly allPageSelected = computed(() => {
  178. const ids = (this.page()?.results || []).map((row) => String(row['objectId'] || '')).filter(Boolean);
  179. return Boolean(ids.length) && ids.every((id) => this.selectedIds().includes(id));
  180. });
  181. private readonly functions = inject(CloudFunctionsService);
  182. private readonly route = inject(ActivatedRoute);
  183. private readonly destroyRef = inject(DestroyRef);
  184. ngOnInit(): void {
  185. this.route.paramMap.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((params) => {
  186. this.className.set(params.get('className') ?? '');
  187. this.search.set('');
  188. this.filterGradeCategoryId.set(0); this.filterGradeParentId.set(0);
  189. this.filterSearchNavigationType.set(1); this.filterSearchNavigationState.set(-100); this.filterSearchNavigationElite.set(-100); this.searchNavigationOrderStart.set(1);
  190. this.filterAdZoneType.set(''); this.filterAdStatus.set(-100); this.filterAdInfoZoneId.set(''); this.resetAdActions();
  191. this.filterFontShapeTypeId.set(0); this.resetFontShapeActions();
  192. this.filterSurveyStatus.set(-100); this.filterSurveyId.set(0); this.resetSurveyActions();
  193. this.resetServiceActions();
  194. this.filterStoreStatus.set(-100); this.resetStoreActions();
  195. this.resetTagKeyActions();
  196. this.filterUserMoneyType.set(1); this.filterUserMoneyStartDate.set(''); this.filterUserMoneyEndDate.set('');
  197. this.filterBaikeStatus.set(-100); this.filterBaikeElite.set(-100);
  198. this.filterSystemLogType1.set(''); this.filterSystemLogType2.set(''); this.filterSystemLogStartDate.set(''); this.filterSystemLogEndDate.set('');
  199. void this.load(1, true);
  200. });
  201. }
  202. async load(pageNumber = this.page()?.page ?? 1, reloadSchema = false): Promise<void> {
  203. this.loading.set(true); this.error.set(''); this.selectedIds.set([]); this.bulkError.set(''); this.bulkMessage.set('');
  204. try {
  205. if (reloadSchema || !this.schema()) this.schema.set(await this.functions.admin<AdminClassSchema>('schema', { className: this.className() }));
  206. if (this.className() === 'Grade' && reloadSchema) { await this.loadGradeCategories(); if (!this.filterGradeCategoryId() && this.gradeCategories().length) this.filterGradeCategoryId.set(this.gradeCategoryIdOf(this.gradeCategories()[0])); }
  207. if (this.className() === 'AdInfo' && reloadSchema) await this.loadAdZones();
  208. if (this.className() === 'FontPicShape' && reloadSchema) await this.loadFontShapeTypes();
  209. if (['DesignQuestion', 'DesignAnswer'].includes(this.className()) && reloadSchema) { await this.loadSurveys(); if (!this.filterSurveyId() && this.surveys().length) this.filterSurveyId.set(this.surveyIdOf(this.surveys()[0])); }
  210. if (this.className() === 'StoreApplication' && reloadSchema) await this.loadStoreStyles();
  211. 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' : this.className() === 'ContentTagKey' ? 'contentTagKeys' : this.className() === 'UserLevel' ? 'userLevels' : this.className() === 'UserMoneyLog' ? 'userMoneyLogs' : this.className() === 'Baike' ? 'baikeEntries' : this.className() === 'CRMSAttr' ? 'crmClientTypes' : this.className() === 'ShopFareTlp' ? 'shopFareTemplates' : this.className() === 'SysLog' ? 'systemLogs' : this.className() === 'MisType' ? 'misTypes' : this.className() === 'PageStyle' ? 'pageStyles' : this.className() === 'PlatComp' ? 'platformCompanies' : 'list';
  212. const pagePayload: Record<string, unknown> = { className: this.className(), page: pageNumber, pageSize: 20, search: this.search().trim() };
  213. if (pageOperation === 'gradeOptions') { pagePayload['categoryId'] = this.filterGradeCategoryId(); pagePayload['parentId'] = this.filterGradeParentId(); }
  214. if (pageOperation === 'searchNavigations') { pagePayload['type'] = this.filterSearchNavigationType(); pagePayload['state'] = this.filterSearchNavigationState(); pagePayload['elite'] = this.filterSearchNavigationElite(); }
  215. if (pageOperation === 'adZones') { pagePayload['type'] = this.filterAdZoneType(); pagePayload['status'] = this.filterAdStatus(); }
  216. if (pageOperation === 'adInfos') { pagePayload['zoneId'] = this.filterAdInfoZoneId(); pagePayload['status'] = this.filterAdStatus(); }
  217. if (pageOperation === 'fontShapes') pagePayload['typeId'] = this.filterFontShapeTypeId();
  218. if (pageOperation === 'surveys') pagePayload['status'] = this.filterSurveyStatus();
  219. if (pageOperation === 'surveyQuestions' || pageOperation === 'surveyResults') pagePayload['askId'] = this.filterSurveyId();
  220. if (pageOperation === 'storeApplications') pagePayload['status'] = this.filterStoreStatus();
  221. if (pageOperation === 'userMoneyLogs') { pagePayload['type'] = this.filterUserMoneyType(); pagePayload['startDate'] = this.filterUserMoneyStartDate(); pagePayload['endDate'] = this.filterUserMoneyEndDate(); }
  222. if (pageOperation === 'baikeEntries') { pagePayload['status'] = this.filterBaikeStatus(); pagePayload['elite'] = this.filterBaikeElite(); }
  223. if (pageOperation === 'systemLogs') { pagePayload['type1'] = this.filterSystemLogType1(); pagePayload['type2'] = this.filterSystemLogType2(); pagePayload['startDate'] = this.filterSystemLogStartDate(); pagePayload['endDate'] = this.filterSystemLogEndDate(); }
  224. this.page.set(await this.functions.admin<ResourcePage>(pageOperation, pagePayload));
  225. if (this.className() === '_User' && reloadSchema) await this.loadUserGroups();
  226. if (this.className() === 'Datadic' && reloadSchema) await this.loadDictionaryCategories();
  227. } catch (error) { this.error.set(error instanceof Error ? error.message : '数据加载失败'); }
  228. finally { this.loading.set(false); }
  229. }
  230. 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.resetTagKeyActions(); this.resetPlatformCompanyActions(); this.editorOpen.set(true); }
  231. async openEdit(row: Record<string, unknown>): Promise<void> {
  232. const objectId = String(row['objectId'] ?? '');
  233. this.error.set('');
  234. try {
  235. const detailOperation = this.className() === 'ContentTagKey' ? 'getContentTagKeys' : this.className() === 'UserMoneyLog' ? 'getUserMoneyLog' : 'get';
  236. const detail = await this.functions.admin<Record<string, unknown>>(detailOperation, { className: this.className(), objectId });
  237. 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.resetTagKeyActions(detail); this.resetPlatformCompanyActions(detail); this.editorOpen.set(true);
  238. if (this.className() === '_User') await this.loadUserGroups();
  239. if (this.className() === 'AdInfo' && !this.adZones().length) await this.loadAdZones();
  240. if (this.className() === 'AdZone') await this.loadAdPreview();
  241. if (this.className() === 'FontPicShape' && !this.fontShapeTypes().length) await this.loadFontShapeTypes();
  242. if (this.className() === 'PlatComp') await this.loadPlatformMembers();
  243. } catch (error) { this.error.set(error instanceof Error ? error.message : '详情加载失败'); }
  244. }
  245. 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(); this.resetTagKeyActions(); this.resetPlatformCompanyActions(); }
  246. setField(field: AdminFieldSchema, raw: unknown): void {
  247. let value = raw;
  248. if (field.type === 'Number') value = raw === '' ? null : Number(raw);
  249. if (field.type === 'Pointer') value = raw ? { __type: 'Pointer', className: field.targetClass, objectId: String(raw) } : null;
  250. if ((field.type === 'Object' || field.type === 'Array') && typeof raw === 'string') {
  251. this.fieldInputs.update((current) => ({ ...current, [field.name]: raw }));
  252. try {
  253. value = raw.trim() ? JSON.parse(raw) : field.type === 'Array' ? [] : {};
  254. const valid = field.type === 'Array' ? Array.isArray(value) : Boolean(value) && typeof value === 'object' && !Array.isArray(value);
  255. if (!valid) throw new Error();
  256. this.fieldErrors.update((current) => { const next = { ...current }; delete next[field.name]; return next; });
  257. } catch {
  258. this.fieldErrors.update((current) => ({ ...current, [field.name]: field.type === 'Array' ? '请输入有效的 JSON 数组' : '请输入有效的 JSON 对象' }));
  259. return;
  260. }
  261. }
  262. this.draft.update((current) => ({ ...current, [field.name]: value }));
  263. }
  264. inputValue(field: AdminFieldSchema): string | number {
  265. const value = this.draft()[field.name];
  266. if (field.type === 'Pointer') return String((value as { objectId?: string } | null)?.objectId ?? '');
  267. 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));
  268. if (field.type === 'Object' || field.type === 'Array') return this.fieldInputs()[field.name] ?? (value == null ? '' : JSON.stringify(value, null, 2));
  269. return typeof value === 'number' ? value : String(value ?? '');
  270. }
  271. async save(): Promise<void> {
  272. const invalidFields = Object.keys(this.fieldErrors());
  273. if (invalidFields.length) { this.error.set(`请先修正字段格式:${invalidFields.join('、')}`); return; }
  274. this.saving.set(true); this.error.set('');
  275. try {
  276. const operation = this.className() === 'Group' ? 'saveGroup'
  277. : this.className() === 'Node' ? 'saveNode'
  278. : this.className() === 'Special' ? 'saveSpecial'
  279. : this.className() === 'Model' ? 'saveModelMetadata'
  280. : this.className() === 'ModelField' ? 'saveModelFieldMetadata'
  281. : this.className() === 'Guestbook' ? 'saveGuestbook'
  282. : this.className() === 'Guestcate' ? 'saveGuestCategory'
  283. : this.className() === 'ExamClass' ? 'saveExamClass'
  284. : this.className() === 'ExamPoint' ? 'saveExamPoint'
  285. : this.className() === 'QuestionsKnowledge' ? 'saveKnowledge'
  286. : this.className() === 'ExTeacher' ? 'saveExamTeacher'
  287. : this.className() === 'Datadiccategory' ? 'saveDictionaryCategory'
  288. : this.className() === 'Datadic' ? 'saveDictionaryItem'
  289. : this.className() === 'GradeCate' ? 'saveGradeCategory'
  290. : this.className() === 'Grade' ? 'saveGradeOption'
  291. : this.className() === 'Currency' ? 'saveCurrency'
  292. : this.className() === 'SysHoliday' ? 'saveHoliday'
  293. : this.className() === 'Search' ? 'saveSearchNavigation'
  294. : this.className() === 'AdZone' ? 'saveAdZone'
  295. : this.className() === 'AdInfo' ? 'saveAdInfo'
  296. : this.className() === 'FontPicShape' ? 'saveFontShape'
  297. : this.className() === 'FontPicShapeType' ? 'saveFontShapeType'
  298. : this.className() === 'DesignAsk' ? 'saveSurvey'
  299. : this.className() === 'DesignQuestion' ? 'saveSurveyQuestion'
  300. : this.className() === 'ServiceSeat' ? 'saveServiceSeat'
  301. : this.className() === 'Temp' ? 'saveServiceCode'
  302. : this.className() === 'StoreApplication' ? 'saveStoreApplication'
  303. : this.className() === 'StoreStyle' ? 'saveStoreStyle'
  304. : this.className() === 'ContentTagKey' ? 'saveContentTagKeys'
  305. : this.className() === 'UserLevel' ? 'saveUserLevel'
  306. : this.className() === 'CRMSAttr' ? 'saveCrmClientType'
  307. : this.className() === 'ShopFareTlp' ? 'saveShopFareTemplate'
  308. : this.className() === 'MisType' ? 'saveMisType'
  309. : this.className() === 'PageStyle' ? 'savePageStyle'
  310. : this.className() === 'PlatComp' ? 'savePlatformCompany' : 'save';
  311. const payload: Record<string, unknown> = { className: this.className(), objectId: this.draftId() || undefined, fields: this.draft() };
  312. if (this.className() === 'Node') payload['parentId'] = this.targetNodeParentId();
  313. if (this.className() === 'Special') payload['pid'] = this.targetSpecialParentId();
  314. if (this.className() === 'Guestcate') { payload['gtype'] = this.guestCategoryType(); payload['parentId'] = this.targetGuestCategoryParentId(); }
  315. if (this.className() === 'ExamClass') { payload['parentId'] = this.targetExamClassParentId(); payload['classType'] = this.examClassType(); payload['orderId'] = this.examClassOrder(); }
  316. if (this.className() === 'ExamPoint') { payload['parentId'] = this.targetExamPointParentId(); payload['orderId'] = this.examPointOrder(); }
  317. if (this.className() === 'QuestionsKnowledge') { payload['classId'] = this.targetKnowledgeClassId(); payload['parentId'] = this.targetKnowledgeParentId(); payload['orderId'] = this.knowledgeOrder(); }
  318. if (this.className() === 'ExTeacher') payload['classId'] = this.targetTeacherClassId();
  319. if (this.className() === 'Datadic') payload['categoryId'] = this.targetDictionaryCategoryId();
  320. if (this.className() === 'Grade') { payload['categoryId'] = this.targetGradeCategoryId(); payload['parentId'] = this.targetGradeParentId(); }
  321. if (this.className() === 'AdInfo') payload['zoneId'] = this.targetAdZoneId();
  322. if (this.className() === 'FontPicShape') payload['typeId'] = this.targetFontShapeTypeId();
  323. if (this.className() === 'ServiceSeat') payload['username'] = this.targetServiceSeatUsername();
  324. if (this.className() === 'StoreApplication') { payload['username'] = this.targetStoreUsername(); payload['styleId'] = this.targetStoreStyleId(); }
  325. if (this.className() === 'ContentTagKey') payload['content'] = this.tagKeyContent();
  326. if (this.className() === 'PlatComp') payload['creatorUsername'] = this.platformCreatorUsername();
  327. await this.functions.admin(operation, payload);
  328. this.closeEditor(); await this.load();
  329. } catch (error) { this.error.set(error instanceof Error ? error.message : '保存失败'); }
  330. finally { this.saving.set(false); }
  331. }
  332. async submitEditor(): Promise<void> {
  333. if (this.className() === '_User' && !this.draftId()) await this.createUser();
  334. else await this.save();
  335. }
  336. async createUser(): Promise<void> {
  337. const username = this.createUsername().trim();
  338. const password = this.createPassword();
  339. this.createUserError.set('');
  340. if (this.className() !== '_User' || this.draftId()) { this.createUserError.set('只能从用户管理的新建入口开户'); return; }
  341. if (username.length < 3 || username.length > 64) { this.createUserError.set('账号长度应为 3 至 64 位'); return; }
  342. if (password.length < 8 || password.length > 72) { this.createUserError.set('密码长度应为 8 至 72 位'); return; }
  343. if (password !== this.createConfirmPassword()) { this.createUserError.set('两次输入的密码不一致'); return; }
  344. const payload: Record<string, unknown> = { username, password };
  345. const displayName = this.createDisplayName().trim();
  346. const mobile = this.createMobile().trim();
  347. const inviteUsername = this.createInviteUsername().trim();
  348. if (displayName) payload['displayName'] = displayName;
  349. if (mobile) payload['mobile'] = mobile;
  350. if (inviteUsername) payload['inviteUsername'] = inviteUsername;
  351. this.saving.set(true); this.error.set('');
  352. try {
  353. await this.functions.admin('createUser', payload);
  354. this.closeEditor(); await this.load();
  355. } catch (error) { this.createUserError.set(error instanceof Error ? error.message : '用户开户失败'); }
  356. finally { this.saving.set(false); }
  357. }
  358. async resetPassword(): Promise<void> {
  359. this.passwordError.set(''); this.passwordMessage.set('');
  360. if (this.className() !== '_User' || !this.draftId()) { this.passwordError.set('只能重置现有用户密码'); return; }
  361. if (this.newPassword().length < 8) { this.passwordError.set('新密码至少 8 位'); return; }
  362. if (this.newPassword() !== this.confirmPassword()) { this.passwordError.set('两次输入的密码不一致'); return; }
  363. this.resettingPassword.set(true);
  364. try {
  365. await this.functions.admin('resetPassword', { className: '_User', objectId: this.draftId(), newPassword: this.newPassword() });
  366. this.newPassword.set(''); this.confirmPassword.set(''); this.passwordMessage.set('密码已安全重置');
  367. } catch (error) { this.passwordError.set(error instanceof Error ? error.message : '密码重置失败'); }
  368. finally { this.resettingPassword.set(false); }
  369. }
  370. async setUserDisabled(disabled: boolean): Promise<void> {
  371. this.userActionError.set(''); this.userActionMessage.set('');
  372. if (this.className() !== '_User' || !this.draftId()) { this.userActionError.set('只能管理现有用户状态'); return; }
  373. if (disabled && !confirm('确认停用该账号并撤销其全部登录会话?')) return;
  374. this.updatingUser.set(true);
  375. try {
  376. const result = await this.functions.admin<{ results: Record<string, unknown>[]; revokedSessions: number }>('userBatch', {
  377. className: '_User', action: disabled ? 'lock' : 'unlock', objectIds: [this.draftId()], reason: this.userActionReason().trim(),
  378. });
  379. if (result.results?.[0]) this.draft.set(result.results[0]);
  380. this.userActionMessage.set(disabled ? `账号已停用,撤销 ${result.revokedSessions || 0} 个会话` : '账号已恢复使用');
  381. await this.load();
  382. } catch (error) { this.userActionError.set(error instanceof Error ? error.message : '账号状态更新失败'); }
  383. finally { this.updatingUser.set(false); }
  384. }
  385. async moveUserGroup(): Promise<void> {
  386. this.userActionError.set(''); this.userActionMessage.set('');
  387. const groupId = this.selectedUserGroupId();
  388. if (this.className() !== '_User' || !this.draftId() || !Number.isInteger(groupId) || groupId < 1) { this.userActionError.set('请选择有效用户组'); return; }
  389. this.updatingUser.set(true);
  390. try {
  391. const result = await this.functions.admin<{ results: Record<string, unknown>[] }>('userBatch', { className: '_User', action: 'move', objectIds: [this.draftId()], groupId });
  392. if (result.results?.[0]) this.draft.set(result.results[0]);
  393. this.userActionMessage.set(`已移动到用户组 ${groupId}`);
  394. await this.load();
  395. } catch (error) { this.userActionError.set(error instanceof Error ? error.message : '用户组更新失败'); }
  396. finally { this.updatingUser.set(false); }
  397. }
  398. async applyContentAction(action: 'status' | 'recycle' | 'recover' | 'move' | 'refresh'): Promise<void> {
  399. this.contentActionError.set(''); this.contentActionMessage.set('');
  400. if (this.className() !== 'CommonModel' || !this.draftId()) { this.contentActionError.set('只能管理现有内容'); return; }
  401. if (action === 'recycle' && !confirm('确认将该内容移入回收站?')) return;
  402. const payload: Record<string, unknown> = { className: 'CommonModel', action, objectIds: [this.draftId()] };
  403. if (action === 'status') {
  404. const status = this.selectedContentStatus();
  405. if (![-3, -1, 0, 99].includes(status)) { this.contentActionError.set('请选择有效内容状态'); return; }
  406. payload['status'] = status;
  407. }
  408. if (action === 'move') {
  409. const nodeId = this.targetContentNodeId();
  410. if (!Number.isInteger(nodeId) || nodeId < 1) { this.contentActionError.set('请输入有效目标节点 ID'); return; }
  411. payload['nodeId'] = nodeId;
  412. }
  413. this.updatingContent.set(true);
  414. try {
  415. const result = await this.functions.admin<{ results: Record<string, unknown>[] }>('contentBatch', payload);
  416. if (result.results?.[0]) {
  417. this.draft.set(result.results[0]);
  418. this.selectedContentStatus.set(Number(result.results[0]['status']) || 0);
  419. this.targetContentNodeId.set(Number(result.results[0]['nodeId']) || 0);
  420. }
  421. const messages = { status: `内容状态已更新为${this.contentStatusLabel(this.selectedContentStatus())}`, recycle: '内容已移入回收站', recover: '内容已恢复为待审核', move: `内容已移动到节点 ${this.targetContentNodeId()}`, refresh: '内容创建和修改时间已刷新' };
  422. this.contentActionMessage.set(messages[action]);
  423. await this.load();
  424. } catch (error) { this.contentActionError.set(error instanceof Error ? error.message : '内容操作失败'); }
  425. finally { this.updatingContent.set(false); }
  426. }
  427. toggleSelection(objectId: string, selected: boolean): void {
  428. this.selectedIds.update((current) => selected ? [...new Set([...current, objectId])] : current.filter((id) => id !== objectId));
  429. }
  430. togglePageSelection(selected: boolean): void {
  431. const pageIds = (this.page()?.results || []).map((row) => String(row['objectId'] || '')).filter(Boolean);
  432. this.selectedIds.set(selected ? pageIds : []);
  433. }
  434. isSelected(objectId: unknown): boolean { return this.selectedIds().includes(String(objectId || '')); }
  435. async applyBulkUserAction(action: 'lock' | 'unlock' | 'move'): Promise<void> {
  436. const objectIds = this.selectedIds(); this.bulkError.set(''); this.bulkMessage.set('');
  437. if (this.className() !== '_User' || !objectIds.length) { this.bulkError.set('请先选择用户'); return; }
  438. if (action === 'lock' && !confirm(`确认停用选中的 ${objectIds.length} 个账号并撤销全部会话?`)) return;
  439. const payload: Record<string, unknown> = { className: '_User', action, objectIds };
  440. if (action === 'move') {
  441. const groupId = this.selectedUserGroupId();
  442. if (!Number.isInteger(groupId) || groupId < 1) { this.bulkError.set('请选择有效用户组'); return; }
  443. payload['groupId'] = groupId;
  444. } else payload['reason'] = this.userActionReason().trim();
  445. this.updatingUser.set(true);
  446. try {
  447. const result = await this.functions.admin<{ updated: number; revokedSessions: number }>('userBatch', payload);
  448. const message = action === 'lock' ? `已停用 ${result.updated} 个账号并撤销 ${result.revokedSessions || 0} 个会话` : action === 'unlock' ? `已解锁 ${result.updated} 个账号` : `已移动 ${result.updated} 个用户`;
  449. await this.load(); this.bulkMessage.set(message);
  450. } catch (error) { this.bulkError.set(error instanceof Error ? error.message : '批量用户操作失败'); }
  451. finally { this.updatingUser.set(false); }
  452. }
  453. async applyBulkContentAction(action: 'status' | 'recycle' | 'recover' | 'move' | 'refresh'): Promise<void> {
  454. const objectIds = this.selectedIds(); this.bulkError.set(''); this.bulkMessage.set('');
  455. if (this.className() !== 'CommonModel' || !objectIds.length) { this.bulkError.set('请先选择内容'); return; }
  456. if (action === 'recycle' && !confirm(`确认将选中的 ${objectIds.length} 条内容移入回收站?`)) return;
  457. const payload: Record<string, unknown> = { className: 'CommonModel', action, objectIds };
  458. if (action === 'status') {
  459. const status = this.selectedContentStatus();
  460. if (![-3, -1, 0, 99].includes(status)) { this.bulkError.set('请选择有效内容状态'); return; }
  461. payload['status'] = status;
  462. }
  463. if (action === 'move') {
  464. const nodeId = this.targetContentNodeId();
  465. if (!Number.isInteger(nodeId) || nodeId < 1) { this.bulkError.set('请输入有效目标节点 ID'); return; }
  466. payload['nodeId'] = nodeId;
  467. }
  468. this.updatingContent.set(true);
  469. try {
  470. const result = await this.functions.admin<{ updated: number }>('contentBatch', payload);
  471. const message = `已处理 ${result.updated} 条内容`; await this.load(); this.bulkMessage.set(message);
  472. } catch (error) { this.bulkError.set(error instanceof Error ? error.message : '批量内容操作失败'); }
  473. finally { this.updatingContent.set(false); }
  474. }
  475. async checkDuplicateContentTitle(): Promise<void> {
  476. const title = String(this.draft()['title'] || '').trim(); this.contentActionError.set(''); this.contentActionMessage.set(''); this.duplicateContentTitles.set([]);
  477. if (this.className() !== 'CommonModel' || !title) { this.contentActionError.set('当前内容没有可检查的标题'); return; }
  478. this.updatingContent.set(true);
  479. try {
  480. const result = await this.functions.admin<{ total: number; results: Record<string, unknown>[] }>('legacyContentUtility', { action: 'duplicate-title', title });
  481. this.duplicateContentTitles.set(result.results || []); this.contentActionMessage.set(`找到 ${result.total || 0} 条同名内容(含当前记录)`);
  482. } catch (error) { this.contentActionError.set(error instanceof Error ? error.message : '重复标题检查失败'); }
  483. finally { this.updatingContent.set(false); }
  484. }
  485. async exportContent(): Promise<void> {
  486. const selected = this.selectedIds(); const pageIds = (this.page()?.results || []).map((row) => String(row['objectId'] || '')).filter(Boolean); const objectIds = selected.length ? selected : pageIds;
  487. this.bulkError.set(''); this.bulkMessage.set('');
  488. if (this.className() !== 'CommonModel' || !objectIds.length) { this.bulkError.set('当前没有可导出的内容'); return; }
  489. this.updatingContent.set(true);
  490. try {
  491. const result = await this.functions.admin<{ total: number; rows: Record<string, unknown>[] }>('legacyContentUtility', { action: 'export', objectIds });
  492. const labels = ['ID', '标题', '录入者', '点击数', '推荐', '状态', '添加时间', '修改时间']; const keys = ['id', 'title', 'inputer', 'hits', 'elite', 'status', 'createTime', 'updateTime'];
  493. const csvCell = (value: unknown) => { let text = String(value ?? ''); if (/^[=+\-@]/.test(text)) text = `'${text}`; return `"${text.replace(/"/g, '""')}"`; };
  494. const csv = [labels.map(csvCell).join(','), ...(result.rows || []).map((row) => keys.map((key) => csvCell(row[key])).join(','))].join('\r\n');
  495. const url = URL.createObjectURL(new Blob(['\ufeff', csv], { type: 'text/csv;charset=utf-8' })); const link = document.createElement('a'); link.href = url; link.download = `${new Date().toISOString().slice(0, 10).replace(/-/g, '')}-内容管理表.csv`; link.click(); URL.revokeObjectURL(url);
  496. this.bulkMessage.set(`已导出 ${result.total || 0} 条内容`);
  497. } catch (error) { this.bulkError.set(error instanceof Error ? error.message : '内容导出失败'); }
  498. finally { this.updatingContent.set(false); }
  499. }
  500. async applyNodeAction(action: 'recycle' | 'recover' | 'move'): Promise<void> {
  501. this.nodeActionError.set(''); this.nodeActionMessage.set('');
  502. if (this.className() !== 'Node' || !this.draftId()) { this.nodeActionError.set('只能管理现有栏目'); return; }
  503. if (action === 'recycle' && !confirm('确认将该栏目移入回收站?')) return;
  504. const payload: Record<string, unknown> = { className: 'Node', action, objectIds: [this.draftId()] };
  505. if (action === 'move') {
  506. const parentId = this.targetNodeParentId();
  507. if (!Number.isInteger(parentId) || parentId < 0) { this.nodeActionError.set('请输入有效父节点 ID,顶级栏目填 0'); return; }
  508. payload['parentId'] = parentId;
  509. }
  510. this.updatingNode.set(true);
  511. try {
  512. const result = await this.functions.admin<{ results: Record<string, unknown>[] }>('nodeBatch', payload);
  513. if (result.results?.[0]) { this.draft.set(result.results[0]); this.targetNodeParentId.set(Number(result.results[0]['parentId']) || 0); }
  514. this.nodeActionMessage.set(action === 'recycle' ? '栏目已移入回收站' : action === 'recover' ? '栏目已恢复' : `栏目已移动到父节点 ${this.targetNodeParentId()}`);
  515. await this.load();
  516. } catch (error) { this.nodeActionError.set(error instanceof Error ? error.message : '栏目操作失败'); }
  517. finally { this.updatingNode.set(false); }
  518. }
  519. async applyBulkNodeAction(action: 'recycle' | 'recover' | 'move'): Promise<void> {
  520. const objectIds = this.selectedIds(); this.bulkError.set(''); this.bulkMessage.set('');
  521. if (this.className() !== 'Node' || !objectIds.length) { this.bulkError.set('请先选择栏目'); return; }
  522. if (action === 'recycle' && !confirm(`确认将选中的 ${objectIds.length} 个栏目移入回收站?`)) return;
  523. const payload: Record<string, unknown> = { className: 'Node', action, objectIds };
  524. if (action === 'move') {
  525. const parentId = this.targetNodeParentId();
  526. if (!Number.isInteger(parentId) || parentId < 0) { this.bulkError.set('请输入有效父节点 ID,顶级栏目填 0'); return; }
  527. payload['parentId'] = parentId;
  528. }
  529. this.updatingNode.set(true);
  530. try {
  531. const result = await this.functions.admin<{ updated: number }>('nodeBatch', payload);
  532. await this.load(); this.bulkMessage.set(`已处理 ${result.updated} 个栏目`);
  533. } catch (error) { this.bulkError.set(error instanceof Error ? error.message : '批量栏目操作失败'); }
  534. finally { this.updatingNode.set(false); }
  535. }
  536. async moveSpecial(): Promise<void> {
  537. this.specialActionError.set(''); this.specialActionMessage.set('');
  538. const pid = this.targetSpecialParentId();
  539. if (this.className() !== 'Special' || !this.draftId() || !Number.isInteger(pid) || pid < 0) { this.specialActionError.set('请输入有效父专题 ID,顶级专题填 0'); return; }
  540. this.updatingSpecial.set(true);
  541. try {
  542. const result = await this.functions.admin<{ results: Record<string, unknown>[] }>('specialBatch', { className: 'Special', action: 'move', objectIds: [this.draftId()], pid });
  543. if (result.results?.[0]) { this.draft.set(result.results[0]); this.targetSpecialParentId.set(Number(result.results[0]['pid']) || 0); }
  544. this.specialActionMessage.set(`专题已移动到父专题 ${pid}`); await this.load();
  545. } catch (error) { this.specialActionError.set(error instanceof Error ? error.message : '专题移动失败'); }
  546. finally { this.updatingSpecial.set(false); }
  547. }
  548. async applyBulkSpecialMove(): Promise<void> {
  549. const objectIds = this.selectedIds(); const pid = this.targetSpecialParentId(); this.bulkError.set(''); this.bulkMessage.set('');
  550. if (this.className() !== 'Special' || !objectIds.length) { this.bulkError.set('请先选择专题'); return; }
  551. if (!Number.isInteger(pid) || pid < 0) { this.bulkError.set('请输入有效父专题 ID,顶级专题填 0'); return; }
  552. this.updatingSpecial.set(true);
  553. try {
  554. const result = await this.functions.admin<{ updated: number }>('specialBatch', { className: 'Special', action: 'move', objectIds, pid });
  555. await this.load(); this.bulkMessage.set(`已移动 ${result.updated} 个专题`);
  556. } catch (error) { this.bulkError.set(error instanceof Error ? error.message : '批量专题移动失败'); }
  557. finally { this.updatingSpecial.set(false); }
  558. }
  559. async applyGuestbookAction(action: 'audit' | 'unaudit' | 'recycle' | 'recover'): Promise<void> {
  560. this.guestbookActionError.set(''); this.guestbookActionMessage.set('');
  561. if (this.className() !== 'Guestbook' || !this.draftId()) { this.guestbookActionError.set('只能管理现有留言'); return; }
  562. if (action === 'recycle' && !confirm('确认将该留言移入回收站?')) return;
  563. this.updatingGuestbook.set(true);
  564. try {
  565. const result = await this.functions.admin<{ results: Record<string, unknown>[] }>('guestbookBatch', { className: 'Guestbook', action, objectIds: [this.draftId()] });
  566. if (result.results?.[0]) { this.draft.set(result.results[0]); this.selectedGuestbookStatus.set(Number(result.results[0]['status']) || 0); }
  567. this.guestbookActionMessage.set(action === 'audit' ? '留言已审核' : action === 'unaudit' ? '留言已取消审核' : action === 'recycle' ? '留言已移入回收站' : '留言已恢复'); await this.load();
  568. } catch (error) { this.guestbookActionError.set(error instanceof Error ? error.message : '留言状态更新失败'); }
  569. finally { this.updatingGuestbook.set(false); }
  570. }
  571. async replyGuestbook(): Promise<void> {
  572. this.guestbookActionError.set(''); this.guestbookActionMessage.set('');
  573. const content = this.guestbookReplyContent();
  574. if (this.className() !== 'Guestbook' || !this.draftId() || !content.trim()) { this.guestbookActionError.set('请输入回复内容'); return; }
  575. this.updatingGuestbook.set(true);
  576. try {
  577. await this.functions.admin('guestbookReply', { className: 'Guestbook', parentObjectId: this.draftId(), title: this.guestbookReplyTitle().trim(), content });
  578. this.guestbookReplyTitle.set(''); this.guestbookReplyContent.set(''); this.guestbookActionMessage.set('管理员回复已发布');
  579. } catch (error) { this.guestbookActionError.set(error instanceof Error ? error.message : '管理员回复失败'); }
  580. finally { this.updatingGuestbook.set(false); }
  581. }
  582. async applyBulkGuestbookAction(action: 'audit' | 'unaudit' | 'recycle' | 'recover'): Promise<void> {
  583. const objectIds = this.selectedIds(); this.bulkError.set(''); this.bulkMessage.set('');
  584. if (this.className() !== 'Guestbook' || !objectIds.length) { this.bulkError.set('请先选择留言'); return; }
  585. if (action === 'recycle' && !confirm(`确认将选中的 ${objectIds.length} 条留言移入回收站?`)) return;
  586. this.updatingGuestbook.set(true);
  587. try {
  588. const result = await this.functions.admin<{ updated: number }>('guestbookBatch', { className: 'Guestbook', action, objectIds });
  589. await this.load(); this.bulkMessage.set(`已处理 ${result.updated} 条留言`);
  590. } catch (error) { this.bulkError.set(error instanceof Error ? error.message : '批量留言操作失败'); }
  591. finally { this.updatingGuestbook.set(false); }
  592. }
  593. async applyBarAction(action: 'audit' | 'unaudit' | 'recycle' | 'recover' | 'move' | 'top-global' | 'top-section' | 'top-undo' | 'elite' | 'elite-undo' | 'bottom' | 'bottom-undo'): Promise<void> {
  594. this.barActionError.set(''); this.barActionMessage.set('');
  595. if (this.className() !== 'GuestBar' || !this.draftId()) { this.barActionError.set('只能管理现有帖子'); return; }
  596. if (action === 'recycle' && !confirm('确认将该帖子移入回收站?')) return;
  597. const payload: Record<string, unknown> = { className: 'GuestBar', action, objectIds: [this.draftId()] };
  598. if (action === 'move') {
  599. const cateId = this.targetBarCategoryId();
  600. if (!Number.isInteger(cateId) || cateId < 1) { this.barActionError.set('请输入有效目标贴吧分类 ID'); return; }
  601. payload['cateId'] = cateId;
  602. }
  603. this.updatingBar.set(true);
  604. try {
  605. const result = await this.functions.admin<{ results: Record<string, unknown>[] }>('barBatch', payload);
  606. if (result.results?.[0]) { this.draft.set(result.results[0]); this.targetBarCategoryId.set(Number(result.results[0]['cateId']) || 0); }
  607. this.barActionMessage.set('帖子状态已更新'); await this.load();
  608. } catch (error) { this.barActionError.set(error instanceof Error ? error.message : '帖子操作失败'); }
  609. finally { this.updatingBar.set(false); }
  610. }
  611. async applyBulkBarAction(action: 'audit' | 'unaudit' | 'recycle' | 'recover' | 'move' | 'top-global' | 'top-section' | 'top-undo' | 'elite' | 'elite-undo' | 'bottom' | 'bottom-undo'): Promise<void> {
  612. const objectIds = this.selectedIds(); this.bulkError.set(''); this.bulkMessage.set('');
  613. if (this.className() !== 'GuestBar' || !objectIds.length) { this.bulkError.set('请先选择帖子'); return; }
  614. if (action === 'recycle' && !confirm(`确认将选中的 ${objectIds.length} 条帖子移入回收站?`)) return;
  615. const payload: Record<string, unknown> = { className: 'GuestBar', action, objectIds };
  616. if (action === 'move') {
  617. const cateId = this.targetBarCategoryId();
  618. if (!Number.isInteger(cateId) || cateId < 1) { this.bulkError.set('请输入有效目标贴吧分类 ID'); return; }
  619. payload['cateId'] = cateId;
  620. }
  621. this.updatingBar.set(true);
  622. try { const result = await this.functions.admin<{ updated: number }>('barBatch', payload); await this.load(); this.bulkMessage.set(`已处理 ${result.updated} 条帖子`); }
  623. catch (error) { this.bulkError.set(error instanceof Error ? error.message : '批量帖子操作失败'); }
  624. finally { this.updatingBar.set(false); }
  625. }
  626. async applyBulkDictionaryAction(action: 'enable' | 'disable' | 'delete'): Promise<void> {
  627. const objectIds = this.selectedIds(); this.bulkError.set(''); this.bulkMessage.set('');
  628. if (!['Datadiccategory', 'Datadic'].includes(this.className()) || !objectIds.length) { this.bulkError.set('请先选择字典记录'); return; }
  629. if (action === 'delete' && !confirm(`确认删除选中的 ${objectIds.length} 条字典记录?`)) return;
  630. this.updatingDictionary.set(true);
  631. try {
  632. const result = await this.functions.admin<{ updated: number }>('dictionaryBatch', { className: this.className(), action, objectIds });
  633. await this.load(); this.bulkMessage.set(`已处理 ${result.updated} 条字典记录`);
  634. } catch (error) { this.bulkError.set(error instanceof Error ? error.message : '批量字典操作失败'); }
  635. finally { this.updatingDictionary.set(false); }
  636. }
  637. async applyBulkGradeDelete(): Promise<void> {
  638. const objectIds = this.selectedIds(); this.bulkError.set(''); this.bulkMessage.set('');
  639. if (!['GradeCate', 'Grade'].includes(this.className()) || !objectIds.length) { this.bulkError.set('请先选择多级字典记录'); return; }
  640. if (!confirm(`确认删除选中的 ${objectIds.length} 条多级字典记录?`)) return;
  641. this.updatingGrade.set(true);
  642. try { const result = await this.functions.admin<{ updated: number }>('gradeBatch', { className: this.className(), action: 'delete', objectIds }); await this.load(); this.bulkMessage.set(`已删除 ${result.updated} 条多级字典记录`); }
  643. catch (error) { this.bulkError.set(error instanceof Error ? error.message : '批量删除多级字典失败'); }
  644. finally { this.updatingGrade.set(false); }
  645. }
  646. async applyBulkCurrencyDelete(): Promise<void> {
  647. const objectIds = this.selectedIds(); this.bulkError.set(''); this.bulkMessage.set('');
  648. if (this.className() !== 'Currency' || !objectIds.length) { this.bulkError.set('请先选择货币记录'); return; }
  649. if (!confirm(`确认删除选中的 ${objectIds.length} 条货币记录?`)) return;
  650. this.updatingCurrency.set(true);
  651. try { const result = await this.functions.admin<{ updated: number }>('currencyBatch', { className: 'Currency', action: 'delete', objectIds }); await this.load(); this.bulkMessage.set(`已删除 ${result.updated} 条货币记录`); }
  652. catch (error) { this.bulkError.set(error instanceof Error ? error.message : '批量删除货币失败'); }
  653. finally { this.updatingCurrency.set(false); }
  654. }
  655. async applyBulkHolidayDelete(): Promise<void> {
  656. const objectIds = this.selectedIds(); this.bulkError.set(''); this.bulkMessage.set('');
  657. if (this.className() !== 'SysHoliday' || !objectIds.length) { this.bulkError.set('请先选择节假日记录'); return; }
  658. if (!confirm(`确认删除选中的 ${objectIds.length} 条节假日记录?`)) return;
  659. this.updatingHoliday.set(true);
  660. try { const result = await this.functions.admin<{ updated: number }>('holidayBatch', { className: 'SysHoliday', action: 'delete', objectIds }); await this.load(); this.bulkMessage.set(`已删除 ${result.updated} 条节假日记录`); }
  661. catch (error) { this.bulkError.set(error instanceof Error ? error.message : '批量删除节假日失败'); }
  662. finally { this.updatingHoliday.set(false); }
  663. }
  664. async applyBulkSearchNavigationAction(action: 'enable' | 'disable' | 'elite' | 'unelite' | 'order' | 'delete'): Promise<void> {
  665. const objectIds = this.selectedIds(); this.bulkError.set(''); this.bulkMessage.set('');
  666. if (this.className() !== 'Search' || !objectIds.length) { this.bulkError.set('请先选择快捷入口'); return; }
  667. if (action === 'delete' && !confirm(`确认删除选中的 ${objectIds.length} 条快捷入口?`)) return;
  668. const payload: Record<string, unknown> = { className: 'Search', action, objectIds };
  669. if (action === 'order') {
  670. const start = this.searchNavigationOrderStart();
  671. if (!Number.isInteger(start) || start < 1 || start + objectIds.length - 1 > 1000000) { this.bulkError.set('请输入有效的起始排序'); return; }
  672. payload['orders'] = objectIds.map((objectId, index) => ({ objectId, orderId: start + index }));
  673. }
  674. this.updatingSearchNavigation.set(true);
  675. try { const result = await this.functions.admin<{ updated: number }>('searchNavigationBatch', payload); await this.load(); this.bulkMessage.set(`已处理 ${result.updated} 条快捷入口`); }
  676. catch (error) { this.bulkError.set(error instanceof Error ? error.message : '批量处理快捷入口失败'); }
  677. finally { this.updatingSearchNavigation.set(false); }
  678. }
  679. async applyBulkAdvertisementAction(action: 'active' | 'pause' | 'audit' | 'unaudit' | 'delete'): Promise<void> {
  680. const className = this.className(); const objectIds = this.selectedIds(); this.bulkError.set(''); this.bulkMessage.set('');
  681. if (!['AdZone', 'AdInfo'].includes(className) || !objectIds.length) { this.bulkError.set('请先选择广告位或广告内容'); return; }
  682. if (action === 'delete' && !confirm(`确认删除选中的 ${objectIds.length} 条${className === 'AdZone' ? '广告位' : '广告内容'}?`)) return;
  683. if (className === 'AdZone' && !['active', 'pause', 'delete'].includes(action)) { this.bulkError.set('广告位操作无效'); return; }
  684. if (className === 'AdInfo' && !['audit', 'unaudit', 'delete'].includes(action)) { this.bulkError.set('广告内容操作无效'); return; }
  685. this.updatingAdvertisement.set(true);
  686. try {
  687. const result = await this.functions.admin<{ updated: number }>(className === 'AdZone' ? 'adZoneBatch' : 'adInfoBatch', { className, action, objectIds });
  688. await this.load(); this.bulkMessage.set(`已处理 ${result.updated} 条${className === 'AdZone' ? '广告位' : '广告内容'}`);
  689. } catch (error) { this.bulkError.set(error instanceof Error ? error.message : '广告批量操作失败'); }
  690. finally { this.updatingAdvertisement.set(false); }
  691. }
  692. async loadAdPreview(): Promise<void> {
  693. this.adPreview.set([]); this.adPreviewError.set('');
  694. if (this.className() !== 'AdZone' || !this.draftId()) return;
  695. this.adPreviewLoading.set(true);
  696. try { const result = await this.functions.admin<{ results: Record<string, unknown>[] }>('adPreview', { zoneObjectId: this.draftId() }); this.adPreview.set(result.results || []); }
  697. catch (error) { this.adPreviewError.set(error instanceof Error ? error.message : '广告位预览加载失败'); }
  698. finally { this.adPreviewLoading.set(false); }
  699. }
  700. async applyBulkFontShapeDelete(): Promise<void> {
  701. const className = this.className(); const objectIds = this.selectedIds(); this.bulkError.set(''); this.bulkMessage.set('');
  702. if (!['FontPicShape', 'FontPicShapeType'].includes(className) || !objectIds.length) { this.bulkError.set('请先选择图形素材或分类'); return; }
  703. if (!confirm(`确认删除选中的 ${objectIds.length} 条${className === 'FontPicShape' ? '图形素材' : '图形分类'}?`)) return;
  704. this.updatingFontShape.set(true);
  705. 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' ? '图形素材' : '图形分类'}`); }
  706. catch (error) { this.bulkError.set(error instanceof Error ? error.message : '图形删除失败'); }
  707. finally { this.updatingFontShape.set(false); }
  708. }
  709. async applyBulkSurveyAction(action: 'start' | 'stop' | 'sort' | 'delete'): Promise<void> {
  710. const className = this.className(); const objectIds = this.selectedIds(); this.bulkError.set(''); this.bulkMessage.set('');
  711. const allowed = className === 'DesignAsk' ? ['start', 'stop', 'delete'] : className === 'DesignQuestion' ? ['sort', 'delete'] : className === 'DesignAnswer' ? ['delete'] : [];
  712. if (!allowed.includes(action) || !objectIds.length) { this.bulkError.set('请先选择可执行该操作的问卷记录'); return; }
  713. if (action === 'delete' && !confirm(`确认删除选中的 ${objectIds.length} 条问卷记录?`)) return;
  714. const payload: Record<string, unknown> = { className, action, objectIds };
  715. if (action === 'sort') {
  716. const start = this.surveyOrderStart();
  717. if (!Number.isInteger(start) || start < 1 || start + objectIds.length - 1 > 1000000) { this.bulkError.set('请输入有效的起始排序'); return; }
  718. payload['orders'] = objectIds.map((objectId, index) => ({ objectId, orderId: start + index }));
  719. }
  720. this.updatingSurvey.set(true);
  721. try { const result = await this.functions.admin<{ updated: number }>('surveyBatch', payload); await this.load(); this.bulkMessage.set(`已处理 ${result.updated} 条问卷记录`); }
  722. catch (error) { this.bulkError.set(error instanceof Error ? error.message : '问卷批量操作失败'); }
  723. finally { this.updatingSurvey.set(false); }
  724. }
  725. async loadSurveyChart(): Promise<void> {
  726. this.surveyChart.set(null); this.surveyChartError.set('');
  727. if (this.className() !== 'DesignAnswer' || !this.filterSurveyId()) return;
  728. this.surveyChartLoading.set(true);
  729. try { this.surveyChart.set(await this.functions.admin<SurveyChart>('surveyResultChart', { askId: this.filterSurveyId() })); }
  730. catch (error) { this.surveyChartError.set(error instanceof Error ? error.message : '问卷统计加载失败'); }
  731. finally { this.surveyChartLoading.set(false); }
  732. }
  733. async applyBulkServiceDelete(): Promise<void> {
  734. const className = this.className(); const objectIds = this.selectedIds(); this.bulkError.set(''); this.bulkMessage.set('');
  735. if (!['ServiceSeat', 'Temp'].includes(className) || !objectIds.length) { this.bulkError.set('请先选择客服席位或欢迎语'); return; }
  736. if (!confirm(`确认删除选中的 ${objectIds.length} 条${className === 'ServiceSeat' ? '客服席位' : '欢迎语'}?`)) return;
  737. this.updatingService.set(true);
  738. try { const result = await this.functions.admin<{ updated: number }>('serviceBatch', { className, action: 'delete', objectIds }); await this.load(); this.bulkMessage.set(`已删除 ${result.updated} 条客服记录`); }
  739. catch (error) { this.bulkError.set(error instanceof Error ? error.message : '客服记录删除失败'); }
  740. finally { this.updatingService.set(false); }
  741. }
  742. async applyBulkStoreAction(action: 'audit' | 'unaudit' | 'elite' | 'unelite' | 'delete'): Promise<void> {
  743. const className = this.className(); const objectIds = this.selectedIds(); this.bulkError.set(''); this.bulkMessage.set('');
  744. const allowed = className === 'StoreApplication' ? ['audit', 'unaudit', 'elite', 'unelite'] : className === 'StoreStyle' ? ['delete'] : [];
  745. if (!allowed.includes(action) || !objectIds.length) { this.bulkError.set(action === 'delete' && className === 'StoreApplication' ? '店铺删除因商品主表缺失而阻塞' : '请先选择可执行该操作的店铺记录'); return; }
  746. if (action === 'delete' && !confirm(`确认删除选中的 ${objectIds.length} 条店铺样式?`)) return;
  747. this.updatingStore.set(true);
  748. try { const result = await this.functions.admin<{ updated: number }>('storeBatch', { className, action, objectIds }); await this.load(); this.bulkMessage.set(`已处理 ${result.updated} 条店铺记录`); }
  749. catch (error) { this.bulkError.set(error instanceof Error ? error.message : '店铺批量操作失败'); }
  750. finally { this.updatingStore.set(false); }
  751. }
  752. async applyBulkUserLevelDelete(): Promise<void> {
  753. const objectIds = this.selectedIds(); this.bulkError.set(''); this.bulkMessage.set('');
  754. if (this.className() !== 'UserLevel' || !objectIds.length) { this.bulkError.set('请先选择积分等级'); return; }
  755. if (!confirm(`确认删除选中的 ${objectIds.length} 个积分等级?`)) return;
  756. this.updatingUserLevel.set(true);
  757. try { const result = await this.functions.admin<{ updated: number }>('userLevelBatch', { className: 'UserLevel', action: 'delete', objectIds }); await this.load(); this.bulkMessage.set(`已删除 ${result.updated} 个积分等级`); }
  758. catch (error) { this.bulkError.set(error instanceof Error ? error.message : '积分等级删除失败'); }
  759. finally { this.updatingUserLevel.set(false); }
  760. }
  761. async applyBulkCrmClientTypeDelete(): Promise<void> {
  762. const objectIds = this.selectedIds(); this.bulkError.set(''); this.bulkMessage.set('');
  763. if (this.className() !== 'CRMSAttr' || !objectIds.length) { this.bulkError.set('请先选择 CRM 客户类型'); return; }
  764. if (!confirm(`确认删除选中的 ${objectIds.length} 个客户类型?`)) return;
  765. this.updatingCrmType.set(true);
  766. try { const result = await this.functions.admin<{ updated: number }>('crmClientTypeBatch', { className: 'CRMSAttr', action: 'delete', objectIds }); await this.load(); this.bulkMessage.set(`已删除 ${result.updated} 个客户类型`); }
  767. catch (error) { this.bulkError.set(error instanceof Error ? error.message : '客户类型删除失败'); }
  768. finally { this.updatingCrmType.set(false); }
  769. }
  770. async applyBulkShopFareDelete(): Promise<void> {
  771. const objectIds = this.selectedIds(); this.bulkError.set(''); this.bulkMessage.set('');
  772. if (this.className() !== 'ShopFareTlp' || !objectIds.length) { this.bulkError.set('请先选择商城运费模板'); return; }
  773. if (!confirm(`确认删除选中的 ${objectIds.length} 个运费模板?`)) return;
  774. this.updatingShopFare.set(true);
  775. try { const result = await this.functions.admin<{ updated: number }>('shopFareTemplateBatch', { className: 'ShopFareTlp', action: 'delete', objectIds }); await this.load(); this.bulkMessage.set(`已删除 ${result.updated} 个运费模板`); }
  776. catch (error) { this.bulkError.set(error instanceof Error ? error.message : '运费模板删除失败'); }
  777. finally { this.updatingShopFare.set(false); }
  778. }
  779. async applyBulkMisTypeDelete(): Promise<void> {
  780. const objectIds = this.selectedIds(); this.bulkError.set(''); this.bulkMessage.set('');
  781. if (this.className() !== 'MisType' || !objectIds.length) { this.bulkError.set('请先选择 OA 流程类型'); return; }
  782. if (!confirm(`确认删除选中的 ${objectIds.length} 个 OA 流程类型?`)) return;
  783. this.updatingMisType.set(true);
  784. try { const result = await this.functions.admin<{ updated: number }>('misTypeBatch', { className: 'MisType', action: 'delete', objectIds }); await this.load(); this.bulkMessage.set(`已删除 ${result.updated} 个 OA 流程类型`); }
  785. catch (error) { this.bulkError.set(error instanceof Error ? error.message : 'OA 流程类型删除失败'); }
  786. finally { this.updatingMisType.set(false); }
  787. }
  788. async applyBulkPageStyleDelete(): Promise<void> {
  789. const objectIds = this.selectedIds(); this.bulkError.set(''); this.bulkMessage.set('');
  790. if (this.className() !== 'PageStyle' || !objectIds.length) { this.bulkError.set('请先选择黄页样式'); return; }
  791. if (!confirm(`确认删除选中的 ${objectIds.length} 个黄页样式?`)) return;
  792. this.updatingPageStyle.set(true);
  793. try { const result = await this.functions.admin<{ updated: number }>('pageStyleBatch', { className: 'PageStyle', action: 'delete', objectIds }); await this.load(); this.bulkMessage.set(`已删除 ${result.updated} 个黄页样式`); }
  794. catch (error) { this.bulkError.set(error instanceof Error ? error.message : '黄页样式删除失败'); }
  795. finally { this.updatingPageStyle.set(false); }
  796. }
  797. async loadPlatformMembers(): Promise<void> {
  798. const companyObjectId = this.draftId(); this.platformMembersError.set(''); this.platformMembers.set(null);
  799. if (this.className() !== 'PlatComp' || !companyObjectId) return;
  800. this.platformMembersLoading.set(true);
  801. try { const result = await this.functions.admin<PlatformMemberPage>('platformCompanyMembers', { companyObjectId, page: 1, pageSize: 100, search: '' }); this.platformMembers.set(result); if (result.creatorUsername) this.platformCreatorUsername.set(result.creatorUsername); }
  802. catch (error) { this.platformMembersError.set(error instanceof Error ? error.message : '企业成员加载失败'); }
  803. finally { this.platformMembersLoading.set(false); }
  804. }
  805. async addPlatformMember(): Promise<void> {
  806. const username = this.platformMemberUsername().trim(); this.platformMembersError.set('');
  807. if (this.className() !== 'PlatComp' || !this.draftId() || !username) { this.platformMembersError.set('请输入要加入的成员账号'); return; }
  808. this.updatingPlatformCompany.set(true);
  809. try { await this.functions.admin('platformCompanyMemberBatch', { companyObjectId: this.draftId(), action: 'add', usernames: [username] }); this.platformMemberUsername.set(''); await this.loadPlatformMembers(); }
  810. catch (error) { this.platformMembersError.set(error instanceof Error ? error.message : '企业成员添加失败'); }
  811. finally { this.updatingPlatformCompany.set(false); }
  812. }
  813. async removePlatformMember(member: Record<string, unknown>): Promise<void> {
  814. const objectId = String(member['objectId'] || ''); if (!objectId || !confirm(`确认将 ${String(member['username'] || objectId)} 移出该企业?`)) return;
  815. this.platformMembersError.set(''); this.updatingPlatformCompany.set(true);
  816. try { await this.functions.admin('platformCompanyMemberBatch', { companyObjectId: this.draftId(), action: 'remove', objectIds: [objectId] }); await this.loadPlatformMembers(); }
  817. catch (error) { this.platformMembersError.set(error instanceof Error ? error.message : '企业成员移除失败'); }
  818. finally { this.updatingPlatformCompany.set(false); }
  819. }
  820. async applyBulkPlatformCompanyDelete(): Promise<void> {
  821. const objectIds = this.selectedIds(); this.bulkError.set(''); this.bulkMessage.set('');
  822. if (this.className() !== 'PlatComp' || !objectIds.length) { this.bulkError.set('请先选择协同办公企业'); return; }
  823. if (!confirm(`确认删除选中的 ${objectIds.length} 个协同办公企业?仍有成员或角色时会拒绝。`)) return;
  824. this.updatingPlatformCompany.set(true);
  825. try { const result = await this.functions.admin<{ updated: number }>('platformCompanyBatch', { className: 'PlatComp', action: 'delete', objectIds }); await this.load(); this.bulkMessage.set(`已删除 ${result.updated} 个协同办公企业`); }
  826. catch (error) { this.bulkError.set(error instanceof Error ? error.message : '协同办公企业删除失败'); }
  827. finally { this.updatingPlatformCompany.set(false); }
  828. }
  829. async applyBulkBaikeAction(action: 'audit' | 'unaudit' | 'elite' | 'unelite'): Promise<void> {
  830. const objectIds = this.selectedIds(); this.bulkError.set(''); this.bulkMessage.set('');
  831. if (this.className() !== 'Baike' || !objectIds.length) { this.bulkError.set('请先选择百科词条'); return; }
  832. this.updatingBaike.set(true);
  833. try { const result = await this.functions.admin<{ updated: number }>('baikeBatch', { className: 'Baike', action, objectIds }); await this.load(); this.bulkMessage.set(`已处理 ${result.updated} 个百科词条`); }
  834. catch (error) { this.bulkError.set(error instanceof Error ? error.message : '百科状态更新失败'); }
  835. finally { this.updatingBaike.set(false); }
  836. }
  837. async remove(row: Record<string, unknown>): Promise<void> {
  838. const objectId = String(row['objectId'] ?? '');
  839. const verb = ['CommonModel', 'Node', 'Guestbook'].includes(this.className()) ? '移入回收站' : '删除';
  840. if (!objectId || !confirm(`确认${verb} ${this.className()} / ${objectId}?`)) return;
  841. try {
  842. if (this.className() === 'Search') await this.functions.admin('searchNavigationBatch', { className: 'Search', action: 'delete', objectIds: [objectId] });
  843. else if (this.className() === 'AdZone') await this.functions.admin('adZoneBatch', { className: 'AdZone', action: 'delete', objectIds: [objectId] });
  844. else if (this.className() === 'AdInfo') await this.functions.admin('adInfoBatch', { className: 'AdInfo', action: 'delete', objectIds: [objectId] });
  845. else if (this.className() === 'FontPicShape') await this.functions.admin('fontShapeBatch', { className: 'FontPicShape', action: 'delete', objectIds: [objectId] });
  846. else if (this.className() === 'FontPicShapeType') await this.functions.admin('fontShapeTypeBatch', { className: 'FontPicShapeType', action: 'delete', objectIds: [objectId] });
  847. else if (['DesignAsk', 'DesignQuestion', 'DesignAnswer'].includes(this.className())) await this.functions.admin('surveyBatch', { className: this.className(), action: 'delete', objectIds: [objectId] });
  848. else if (['ServiceSeat', 'Temp'].includes(this.className())) await this.functions.admin('serviceBatch', { className: this.className(), action: 'delete', objectIds: [objectId] });
  849. else if (['StoreApplication', 'StoreStyle'].includes(this.className())) await this.functions.admin('storeBatch', { className: this.className(), action: 'delete', objectIds: [objectId] });
  850. else if (this.className() === 'UserLevel') await this.functions.admin('userLevelBatch', { className: 'UserLevel', action: 'delete', objectIds: [objectId] });
  851. else if (this.className() === 'CRMSAttr') await this.functions.admin('crmClientTypeBatch', { className: 'CRMSAttr', action: 'delete', objectIds: [objectId] });
  852. else if (this.className() === 'ShopFareTlp') await this.functions.admin('shopFareTemplateBatch', { className: 'ShopFareTlp', action: 'delete', objectIds: [objectId] });
  853. else if (this.className() === 'MisType') await this.functions.admin('misTypeBatch', { className: 'MisType', action: 'delete', objectIds: [objectId] });
  854. else if (this.className() === 'PageStyle') await this.functions.admin('pageStyleBatch', { className: 'PageStyle', action: 'delete', objectIds: [objectId] });
  855. else if (this.className() === 'PlatComp') await this.functions.admin('platformCompanyBatch', { className: 'PlatComp', action: 'delete', objectIds: [objectId] });
  856. else await this.functions.admin('delete', { className: this.className(), objectId });
  857. await this.load();
  858. }
  859. catch (error) { this.error.set(error instanceof Error ? error.message : '删除失败'); }
  860. }
  861. display(value: unknown): string {
  862. if (value == null || value === '') return '—';
  863. if (typeof value === 'object') {
  864. const data = value as Record<string, unknown>;
  865. return String(data['name'] ?? data['title'] ?? data['username'] ?? data['objectId'] ?? JSON.stringify(value));
  866. }
  867. return String(value);
  868. }
  869. groupIdOf(group: Record<string, unknown>): number { return Number(group['groupId']) || 0; }
  870. groupLabel(group: Record<string, unknown>): string { return `${String(group['groupName'] || group['name'] || '未命名组')}(${this.groupIdOf(group)})`; }
  871. dictionaryCategoryIdOf(category: Record<string, unknown>): number { return Number(category['diccateid']) || 0; }
  872. gradeCategoryIdOf(category: Record<string, unknown>): number { return Number(category['cateId']) || 0; }
  873. adZoneIdOf(zone: Record<string, unknown>): string { return String(zone['id'] || ''); }
  874. adZoneLabel(zone: Record<string, unknown>): string { return `${String(zone['name'] || '未命名广告位')}(${this.adZoneIdOf(zone)})`; }
  875. fontShapeTypeIdOf(type: Record<string, unknown>): number { return Number(type['id']) || 0; }
  876. fontShapeTypeLabel(type: Record<string, unknown>): string { return `${String(type['name'] || '未命名分类')}(${this.fontShapeTypeIdOf(type)})`; }
  877. surveyIdOf(survey: Record<string, unknown>): number { return Number(survey['id']) || 0; }
  878. surveyLabel(survey: Record<string, unknown>): string { return `${String(survey['title'] || '未命名问卷')}(${this.surveyIdOf(survey)})`; }
  879. storeStyleIdOf(style: Record<string, unknown>): number { return Number(style['id']) || 0; }
  880. storeStyleLabel(style: Record<string, unknown>): string { return `${String(style['styleName'] || '未命名样式')}(${this.storeStyleIdOf(style)})`; }
  881. contentStatusValue(): number { return Number(this.draft()['status']) || 0; }
  882. contentStatusLabel(status: number): string { return ({ [-3]: '草稿', [-2]: '回收站', [-1]: '未通过', 0: '待审核', 99: '已审核' } as Record<number, string>)[status] || String(status); }
  883. nodeStatusValue(): number { return Number(this.draft()['zstatus'] ?? 99); }
  884. private async loadUserGroups(): Promise<void> {
  885. try {
  886. const groups = await this.functions.admin<ResourcePage>('list', { className: 'Group', page: 1, pageSize: 100, sort: 'orderId', order: 'asc' });
  887. this.userGroups.set(groups.results);
  888. } catch (error) { this.userGroups.set([]); this.userActionError.set(error instanceof Error ? error.message : '用户组加载失败'); }
  889. }
  890. private async loadDictionaryCategories(): Promise<void> {
  891. try {
  892. const result = await this.functions.admin<{ results: Record<string, unknown>[] }>('dictionaryCategories', { enabledOnly: false });
  893. this.dictionaryCategories.set(result.results || []);
  894. } catch (error) { this.dictionaryCategories.set([]); this.error.set(error instanceof Error ? error.message : '字典分类加载失败'); }
  895. }
  896. private async loadGradeCategories(): Promise<void> {
  897. try { const result = await this.functions.admin<{ results: Record<string, unknown>[] }>('gradeCategories', {}); this.gradeCategories.set(result.results || []); }
  898. catch (error) { this.gradeCategories.set([]); this.error.set(error instanceof Error ? error.message : '多级字典分类加载失败'); }
  899. }
  900. private async loadAdZones(): Promise<void> {
  901. try { const result = await this.functions.admin<ResourcePage>('adZones', { className: 'AdZone', page: 1, pageSize: 1000, search: '', type: '', status: -100 }); this.adZones.set(result.results || []); }
  902. catch (error) { this.adZones.set([]); this.error.set(error instanceof Error ? error.message : '广告位选项加载失败'); }
  903. }
  904. private async loadFontShapeTypes(): Promise<void> {
  905. try { const result = await this.functions.admin<ResourcePage>('fontShapeTypes', { className: 'FontPicShapeType', page: 1, pageSize: 1000, search: '' }); this.fontShapeTypes.set(result.results || []); }
  906. catch (error) { this.fontShapeTypes.set([]); this.error.set(error instanceof Error ? error.message : '图形分类选项加载失败'); }
  907. }
  908. private async loadSurveys(): Promise<void> {
  909. try { const result = await this.functions.admin<ResourcePage>('surveys', { className: 'DesignAsk', page: 1, pageSize: 100, search: '', status: -100 }); this.surveys.set(result.results || []); }
  910. catch (error) { this.surveys.set([]); this.error.set(error instanceof Error ? error.message : '问卷选项加载失败'); }
  911. }
  912. private async loadStoreStyles(): Promise<void> {
  913. try { const result = await this.functions.admin<ResourcePage>('storeStyles', { className: 'StoreStyle', page: 1, pageSize: 100, search: '' }); this.storeStyles.set(result.results || []); }
  914. catch (error) { this.storeStyles.set([]); this.error.set(error instanceof Error ? error.message : '店铺样式选项加载失败'); }
  915. }
  916. private resetPasswordFields(): void { this.newPassword.set(''); this.confirmPassword.set(''); this.passwordMessage.set(''); this.passwordError.set(''); }
  917. private resetCreateUserFields(): void { this.createUsername.set(''); this.createPassword.set(''); this.createConfirmPassword.set(''); this.createDisplayName.set(''); this.createMobile.set(''); this.createInviteUsername.set(''); this.createUserError.set(''); }
  918. 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(''); }
  919. private resetContentActions(detail: Record<string, unknown> = {}): void { this.selectedContentStatus.set(Number(detail['status']) || 0); this.targetContentNodeId.set(Number(detail['nodeId']) || 0); this.contentActionError.set(''); this.contentActionMessage.set(''); this.duplicateContentTitles.set([]); }
  920. private resetNodeActions(detail: Record<string, unknown> = {}): void { this.targetNodeParentId.set(Number(detail['parentId']) || 0); this.nodeActionError.set(''); this.nodeActionMessage.set(''); }
  921. private resetSpecialActions(detail: Record<string, unknown> = {}): void { this.targetSpecialParentId.set(Number(detail['pid']) || 0); this.specialActionError.set(''); this.specialActionMessage.set(''); }
  922. 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(''); }
  923. private resetGuestCategoryActions(detail: Record<string, unknown> = {}): void { this.guestCategoryType.set(Number(detail['gtype']) || 0); this.targetGuestCategoryParentId.set(Number(detail['parentId']) || 0); }
  924. private resetBarActions(detail: Record<string, unknown> = {}): void { this.targetBarCategoryId.set(Number(detail['cateId']) || 0); this.barActionError.set(''); this.barActionMessage.set(''); }
  925. 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); }
  926. private resetExamPointActions(detail: Record<string, unknown> = {}): void { this.targetExamPointParentId.set(Number(detail['tid']) || 0); this.examPointOrder.set(Number(detail['orderBy']) || 0); }
  927. 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); }
  928. private resetTeacherActions(detail: Record<string, unknown> = {}): void { this.targetTeacherClassId.set(Number(detail['tclsss']) || 0); }
  929. private resetDictionaryActions(detail: Record<string, unknown> = {}): void { this.targetDictionaryCategoryId.set(Number(detail['diccate']) || 0); }
  930. private resetGradeActions(detail: Record<string, unknown> = {}): void { this.targetGradeCategoryId.set(Number(detail['cate']) || this.filterGradeCategoryId()); this.targetGradeParentId.set(Number(detail['parentId']) || this.filterGradeParentId()); }
  931. private resetAdActions(detail: Record<string, unknown> = {}): void { this.targetAdZoneId.set(String(detail['zoneId'] || this.filterAdInfoZoneId() || '')); this.adPreview.set([]); this.adPreviewError.set(''); this.adPreviewLoading.set(false); }
  932. private resetFontShapeActions(detail: Record<string, unknown> = {}): void { this.targetFontShapeTypeId.set(Number(detail['typeId']) || this.filterFontShapeTypeId()); this.fontShapePreview.set(String(detail['shape'] || '')); }
  933. private resetSurveyActions(): void { this.surveyOrderStart.set(1); this.surveyChart.set(null); this.surveyChartError.set(''); this.surveyChartLoading.set(false); }
  934. private resetServiceActions(detail: Record<string, unknown> = {}): void { this.targetServiceSeatUsername.set(String(detail['sRemrk'] || '')); }
  935. private resetStoreActions(detail: Record<string, unknown> = {}): void { this.targetStoreUsername.set(String(detail['userName'] || '')); this.targetStoreStyleId.set(Number(detail['storeStyleId']) || 0); }
  936. private resetTagKeyActions(detail: Record<string, unknown> = {}): void { this.tagKeyContent.set(String(detail['content'] || '')); }
  937. private resetPlatformCompanyActions(detail: Record<string, unknown> = {}): void { this.platformMembers.set(null); this.platformMembersLoading.set(false); this.platformMembersError.set(''); this.platformMemberUsername.set(''); this.platformCreatorUsername.set(String(detail['creatorUsername'] || '')); }
  938. }