message-template.component.ts 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937
  1. import { ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core';
  2. import { CommonModule, DatePipe } from '@angular/common';
  3. import {
  4. FormsModule,
  5. ReactiveFormsModule,
  6. FormBuilder,
  7. FormGroup,
  8. FormControl,
  9. Validators,
  10. } from '@angular/forms';
  11. import { Router, ActivatedRoute } from '@angular/router';
  12. import { Subscription } from 'rxjs';
  13. import { debounceTime, distinctUntilChanged } from 'rxjs/operators';
  14. // NG-ZORRO 组件
  15. import { NzTableModule } from 'ng-zorro-antd/table';
  16. import { NzDividerModule } from 'ng-zorro-antd/divider';
  17. import { NzButtonModule } from 'ng-zorro-antd/button';
  18. import { NzIconModule } from 'ng-zorro-antd/icon';
  19. import { NzFormModule } from 'ng-zorro-antd/form';
  20. import { NzInputModule } from 'ng-zorro-antd/input';
  21. import { NzModalModule, NzModalService } from 'ng-zorro-antd/modal';
  22. import { NzSelectModule } from 'ng-zorro-antd/select';
  23. import { NzPageHeaderModule } from 'ng-zorro-antd/page-header';
  24. import { NzCardModule } from 'ng-zorro-antd/card';
  25. import { NzPopconfirmModule } from 'ng-zorro-antd/popconfirm';
  26. import { NzTagModule } from 'ng-zorro-antd/tag';
  27. import { NzTabsModule } from 'ng-zorro-antd/tabs';
  28. import { NzDescriptionsModule } from 'ng-zorro-antd/descriptions';
  29. import { NzMessageService } from 'ng-zorro-antd/message';
  30. import { NzEmptyModule } from 'ng-zorro-antd/empty';
  31. import { NzSpinModule } from 'ng-zorro-antd/spin';
  32. import { NzSwitchModule } from 'ng-zorro-antd/switch';
  33. // 服务
  34. import {
  35. MessageService,
  36. IMessageTemplate,
  37. TemplateType,
  38. TEMPLATE_TYPE_MAP,
  39. ActionType,
  40. ACTION_TYPE_MAP,
  41. ACTION_PARAM_PLACEHOLDERS,
  42. } from '../services/message.service';
  43. @Component({
  44. selector: 'app-message-template',
  45. standalone: true,
  46. imports: [
  47. CommonModule,
  48. FormsModule,
  49. ReactiveFormsModule,
  50. NzTableModule,
  51. NzDividerModule,
  52. NzButtonModule,
  53. NzIconModule,
  54. NzFormModule,
  55. NzInputModule,
  56. NzModalModule,
  57. NzSelectModule,
  58. NzPageHeaderModule,
  59. NzCardModule,
  60. NzPopconfirmModule,
  61. NzTagModule,
  62. NzTabsModule,
  63. NzDescriptionsModule,
  64. NzEmptyModule,
  65. NzSpinModule,
  66. NzSwitchModule,
  67. DatePipe,
  68. ],
  69. template: `
  70. <nz-page-header [nzGhost]="false">
  71. <nz-page-header-title>消息模板管理</nz-page-header-title>
  72. <nz-page-header-extra>
  73. <button nz-button nzType="primary" (click)="showCreateModal()">
  74. <span nz-icon nzType="plus"></span>新建模板
  75. </button>
  76. </nz-page-header-extra>
  77. <nz-page-header-content>
  78. 管理多语言消息模板,用于向不同语言的用户发送本地化消息
  79. </nz-page-header-content>
  80. </nz-page-header>
  81. <nz-card>
  82. <!-- 筛选表单 -->
  83. <form nz-form [formGroup]="filterForm" class="filter-form">
  84. <div nz-row [nzGutter]="16">
  85. <div nz-col [nzSpan]="8">
  86. <nz-form-item>
  87. <nz-form-control>
  88. <nz-input-group nzPrefixIcon="search">
  89. <input
  90. type="text"
  91. nz-input
  92. placeholder="模板名称"
  93. formControlName="templateName"
  94. />
  95. </nz-input-group>
  96. </nz-form-control>
  97. </nz-form-item>
  98. </div>
  99. <div nz-col [nzSpan]="8">
  100. <nz-form-item>
  101. <nz-form-control>
  102. <nz-select
  103. nzPlaceHolder="模板类型"
  104. formControlName="templateType"
  105. nzAllowClear
  106. >
  107. @for (type of templateTypeOptions; track type.value) {
  108. <nz-option
  109. [nzLabel]="type.label"
  110. [nzValue]="type.value"
  111. ></nz-option>
  112. }
  113. </nz-select>
  114. </nz-form-control>
  115. </nz-form-item>
  116. </div>
  117. <div nz-col [nzSpan]="8" class="button-col">
  118. <button nz-button (click)="resetFilters()">重置</button>
  119. </div>
  120. </div>
  121. </form>
  122. <nz-spin [nzSpinning]="isLoading">
  123. <nz-table
  124. #templatesTable
  125. [nzData]="filteredTemplates"
  126. [nzLoading]="isLoading"
  127. [nzFrontPagination]="false"
  128. [nzBordered]="true"
  129. [nzSize]="'small'"
  130. [nzShowPagination]="false"
  131. >
  132. <thead>
  133. <tr>
  134. <th>模板名称</th>
  135. <th>模板类型</th>
  136. <th>消息内容</th>
  137. <th style="text-align: center;">图片</th>
  138. <th style="text-align: center;">Action</th>
  139. <th style="text-align: center;">创建时间</th>
  140. <th style="text-align: center;">更新时间</th>
  141. <th style="text-align: center;">操作</th>
  142. </tr>
  143. </thead>
  144. <tbody>
  145. @for (template of templatesTable.data; track template.templateName)
  146. {
  147. <tr>
  148. <td>{{ template.templateName }}</td>
  149. <td>
  150. <nz-tag
  151. [nzColor]="'blue'"
  152. (click)="filterByType(template.templateType)"
  153. style="cursor: pointer;"
  154. >
  155. {{ getTemplateTypeName(template.templateType) }}
  156. </nz-tag>
  157. </td>
  158. <td class="message-content-cell">
  159. <div class="message-title">
  160. {{ template.messageTitle['en'] || '-' }}
  161. </div>
  162. <div class="message-content">
  163. {{ template.messageContent['en'] || '-' }}
  164. </div>
  165. </td>
  166. <td style="text-align: center;">
  167. @if (template.image) {
  168. <img
  169. [src]="template.image"
  170. style="max-width: 60px; max-height: 60px;"
  171. />
  172. } @else { - }
  173. </td>
  174. <td style="text-align: center;">{{ template.action }}</td>
  175. <td style="text-align: center;">
  176. {{ template.createdAt | date : 'yyyy-MM-dd HH:mm' }}
  177. </td>
  178. <td style="text-align: center;">
  179. {{ template.updatedAt | date : 'yyyy-MM-dd HH:mm' }}
  180. </td>
  181. <td style="text-align: center;">
  182. <a (click)="showEditModal(template)">编辑</a>
  183. <nz-divider nzType="vertical"></nz-divider>
  184. <a (click)="showDetailModal(template)">详情</a>
  185. <nz-divider nzType="vertical"></nz-divider>
  186. <a
  187. nz-popconfirm
  188. nzPopconfirmTitle="确定要删除此模板吗?"
  189. nzPopconfirmOkText="确定"
  190. nzPopconfirmCancelText="取消"
  191. (nzOnConfirm)="deleteTemplate(template.templateName)"
  192. >删除
  193. </a>
  194. </td>
  195. </tr>
  196. }
  197. </tbody>
  198. </nz-table>
  199. @if (filteredTemplates.length === 0 && !isLoading) {
  200. <nz-empty nzNotFoundContent="暂无消息模板"></nz-empty>
  201. }
  202. </nz-spin>
  203. </nz-card>
  204. <!-- 创建/编辑模板模态框 -->
  205. <nz-modal
  206. [(nzVisible)]="isModalVisible"
  207. [nzTitle]="isEditMode ? '编辑消息模板' : '新建消息模板'"
  208. [nzOkText]="isEditMode ? '更新' : '创建'"
  209. [nzCancelText]="'取消'"
  210. (nzOnCancel)="handleCancel()"
  211. (nzOnOk)="handleOk()"
  212. [nzOkLoading]="isSubmitting"
  213. [nzWidth]="800"
  214. >
  215. <form nz-form [formGroup]="templateForm" *nzModalContent>
  216. <nz-form-item>
  217. <nz-form-label nzRequired>模板名称</nz-form-label>
  218. <nz-form-control>
  219. <input
  220. nz-input
  221. formControlName="templateName"
  222. placeholder="输入模板名称(英文,不可重复)"
  223. />
  224. </nz-form-control>
  225. </nz-form-item>
  226. <nz-form-item>
  227. <nz-form-label nzRequired>模板类型</nz-form-label>
  228. <nz-form-control>
  229. <nz-select
  230. formControlName="templateType"
  231. nzPlaceHolder="选择模板类型"
  232. >
  233. @for (type of templateTypeOptions; track type.value) {
  234. <nz-option
  235. [nzLabel]="type.label"
  236. [nzValue]="type.value"
  237. ></nz-option>
  238. }
  239. </nz-select>
  240. </nz-form-control>
  241. </nz-form-item>
  242. <!-- 新增字段 -->
  243. <nz-form-item>
  244. <nz-form-label>图片URL</nz-form-label>
  245. <nz-form-control>
  246. <input
  247. nz-input
  248. formControlName="image"
  249. placeholder="输入图片URL(可选)"
  250. />
  251. </nz-form-control>
  252. </nz-form-item>
  253. <nz-form-item>
  254. <nz-form-label>允许展开</nz-form-label>
  255. <nz-form-control>
  256. <nz-switch formControlName="bigger"></nz-switch>
  257. </nz-form-control>
  258. </nz-form-item>
  259. <nz-form-item>
  260. <nz-form-label>客户端行为</nz-form-label>
  261. <nz-form-control>
  262. <nz-select formControlName="action" nzPlaceHolder="选择客户端行为">
  263. @for (action of getActionOptions(); track action.value) {
  264. <nz-option
  265. [nzLabel]="action.label"
  266. [nzValue]="action.value"
  267. ></nz-option>
  268. }
  269. </nz-select>
  270. </nz-form-control>
  271. </nz-form-item>
  272. <nz-form-item>
  273. <nz-form-label>参数</nz-form-label>
  274. <nz-form-control>
  275. <input
  276. nz-input
  277. formControlName="param"
  278. [placeholder]="getParamPlaceholder()"
  279. />
  280. </nz-form-control>
  281. </nz-form-item>
  282. <nz-form-item>
  283. <nz-form-label>扩展参数</nz-form-label>
  284. <nz-form-control>
  285. <input
  286. nz-input
  287. formControlName="extend"
  288. placeholder="输入扩展参数(可选)"
  289. />
  290. </nz-form-control>
  291. </nz-form-item>
  292. <nz-form-item>
  293. <nz-form-label nzRequired>添加语言</nz-form-label>
  294. <nz-form-control>
  295. <nz-select
  296. nzMode="multiple"
  297. nzPlaceHolder="选择要添加的语言"
  298. formControlName="selectedLanguages"
  299. (ngModelChange)="onLanguageSelectionChange()"
  300. >
  301. @for (lang of availableLanguages; track lang.code) {
  302. <nz-option
  303. [nzLabel]="lang.name"
  304. [nzValue]="lang.code"
  305. ></nz-option>
  306. }
  307. </nz-select>
  308. </nz-form-control>
  309. </nz-form-item>
  310. @if (templateForm.get('selectedLanguages')?.value; as selectedLangs) {
  311. @for (lang of selectedLangs; track lang) { @if
  312. (templateForm.get('title_' + lang) && templateForm.get('content_' +
  313. lang)) {
  314. <nz-card
  315. [nzTitle]="getLanguageName(lang)"
  316. [nzSize]="'small'"
  317. style="margin-bottom: 16px;"
  318. >
  319. <nz-form-item>
  320. <nz-form-label nzRequired>标题</nz-form-label>
  321. <nz-form-control>
  322. <textarea
  323. nz-input
  324. [formControlName]="'title_' + lang"
  325. placeholder="输入消息标题"
  326. rows="2"
  327. ></textarea>
  328. </nz-form-control>
  329. </nz-form-item>
  330. <nz-form-item>
  331. <nz-form-label nzRequired>内容</nz-form-label>
  332. <nz-form-control>
  333. <textarea
  334. nz-input
  335. [formControlName]="'content_' + lang"
  336. placeholder="输入消息内容"
  337. rows="4"
  338. ></textarea>
  339. </nz-form-control>
  340. </nz-form-item>
  341. </nz-card>
  342. } } }
  343. </form>
  344. </nz-modal>
  345. <!-- 模板详情模态框 -->
  346. <nz-modal
  347. [(nzVisible)]="isDetailModalVisible"
  348. [nzTitle]="'模板详情 - ' + (selectedTemplate?.templateName || '')"
  349. [nzFooter]="null"
  350. (nzOnCancel)="isDetailModalVisible = false"
  351. [nzWidth]="800"
  352. >
  353. <ng-container *nzModalContent>
  354. <nz-descriptions nzBordered [nzColumn]="1">
  355. <nz-descriptions-item nzTitle="模板名称">{{
  356. selectedTemplate?.templateName
  357. }}</nz-descriptions-item>
  358. <nz-descriptions-item nzTitle="模板类型">
  359. <nz-tag [nzColor]="'blue'">
  360. {{ getTemplateTypeName(selectedTemplate?.templateType || 0) }}
  361. </nz-tag>
  362. </nz-descriptions-item>
  363. @if (selectedTemplate?.image) {
  364. <nz-descriptions-item nzTitle="图片">
  365. <img
  366. [src]="selectedTemplate?.image"
  367. style="max-width: 100px; max-height: 100px;"
  368. />
  369. </nz-descriptions-item>
  370. }
  371. <nz-descriptions-item nzTitle="允许展开">
  372. {{ selectedTemplate?.bigger ? '是' : '否' }}
  373. </nz-descriptions-item>
  374. @if (selectedTemplate?.action) {
  375. <nz-descriptions-item nzTitle="动作类型">
  376. {{ selectedTemplate?.action }}
  377. </nz-descriptions-item>
  378. } @if (selectedTemplate?.param) {
  379. <nz-descriptions-item nzTitle="参数">
  380. {{ selectedTemplate?.param }}
  381. </nz-descriptions-item>
  382. } @if (selectedTemplate?.extend) {
  383. <nz-descriptions-item nzTitle="扩展参数">
  384. {{ selectedTemplate?.extend }}
  385. </nz-descriptions-item>
  386. }
  387. <nz-descriptions-item nzTitle="创建时间">{{
  388. selectedTemplate?.createdAt | date : 'yyyy-MM-dd HH:mm:ss'
  389. }}</nz-descriptions-item>
  390. <nz-descriptions-item nzTitle="更新时间">{{
  391. selectedTemplate?.updatedAt | date : 'yyyy-MM-dd HH:mm:ss'
  392. }}</nz-descriptions-item>
  393. </nz-descriptions>
  394. <nz-tabs>
  395. @for (lang of getLanguageKeys(selectedTemplate?.messageTitle || {});
  396. track lang) {
  397. <nz-tab [nzTitle]="getLanguageName(lang)">
  398. <nz-card [nzTitle]="'标题 (' + lang + ')'" [nzSize]="'small'">
  399. <p>{{ selectedTemplate?.messageTitle?.[lang] }}</p>
  400. </nz-card>
  401. <nz-card
  402. [nzTitle]="'内容 (' + lang + ')'"
  403. [nzSize]="'small'"
  404. style="margin-top: 16px;"
  405. >
  406. <p>{{ selectedTemplate?.messageContent?.[lang] }}</p>
  407. </nz-card>
  408. </nz-tab>
  409. }
  410. </nz-tabs>
  411. </ng-container>
  412. </nz-modal>
  413. `,
  414. styles: [
  415. `
  416. .filter-form {
  417. margin-bottom: 16px;
  418. }
  419. .button-col {
  420. display: flex;
  421. justify-content: flex-end;
  422. }
  423. nz-card {
  424. margin-bottom: 16px;
  425. }
  426. .ant-tag {
  427. margin-bottom: 4px;
  428. }
  429. .ant-form-item {
  430. margin-bottom: 16px;
  431. }
  432. textarea.ant-input {
  433. resize: none;
  434. }
  435. .ant-empty {
  436. margin: 40px 0;
  437. }
  438. .ant-table {
  439. margin-top: 16px;
  440. }
  441. /* 消息内容单元格样式 */
  442. .message-content-cell {
  443. max-width: 300px;
  444. min-width: 200px;
  445. }
  446. /* 消息标题样式 */
  447. .message-title {
  448. font-weight: bold;
  449. white-space: nowrap;
  450. overflow: hidden;
  451. text-overflow: ellipsis;
  452. margin-bottom: 4px;
  453. }
  454. /* 消息内容样式 */
  455. .message-content {
  456. display: -webkit-box;
  457. -webkit-line-clamp: 2;
  458. -webkit-box-orient: vertical;
  459. overflow: hidden;
  460. text-overflow: ellipsis;
  461. line-height: 1.4;
  462. color: #666;
  463. }
  464. /* 可点击的标签样式 */
  465. nz-tag[style*='cursor: pointer']:hover {
  466. opacity: 0.8;
  467. box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
  468. }
  469. img {
  470. max-width: 60px;
  471. max-height: 60px;
  472. border-radius: 4px;
  473. }
  474. `,
  475. ],
  476. })
  477. export class MessageTemplateComponent implements OnInit, OnDestroy {
  478. templates: IMessageTemplate[] = [];
  479. filteredTemplates: IMessageTemplate[] = [];
  480. isLoading = false;
  481. isModalVisible = false;
  482. isDetailModalVisible = false;
  483. isEditMode = false;
  484. isSubmitting = false;
  485. selectedTemplate: IMessageTemplate | null = null;
  486. availableLanguages: { code: string; name: string }[] = [];
  487. templateTypeOptions: { value: TemplateType; label: string }[] = [];
  488. // 筛选表单
  489. filterForm: FormGroup;
  490. // 模板表单
  491. templateForm: FormGroup;
  492. private queryParamsSubscription!: Subscription;
  493. constructor(
  494. private messageService: MessageService,
  495. private fb: FormBuilder,
  496. private message: NzMessageService,
  497. private modal: NzModalService,
  498. private cdr: ChangeDetectorRef,
  499. private router: Router,
  500. private route: ActivatedRoute
  501. ) {
  502. // 初始化筛选表单
  503. this.filterForm = this.fb.group({
  504. templateName: [''],
  505. templateType: [null],
  506. });
  507. // 初始化模板表单
  508. this.templateForm = this.fb.group({
  509. templateName: [
  510. '',
  511. [Validators.required, Validators.pattern(/^[a-zA-Z0-9_-]+$/)],
  512. ],
  513. templateType: [TemplateType.OTHER, Validators.required],
  514. image: [null],
  515. bigger: [false],
  516. action: [null],
  517. param: [null],
  518. extend: [null],
  519. selectedLanguages: [[], [Validators.required]],
  520. });
  521. }
  522. ngOnInit(): void {
  523. this.loadTemplates();
  524. this.availableLanguages = this.messageService.getSupportedLanguages();
  525. this.templateTypeOptions = this.messageService.getTemplateTypeOptions();
  526. // 监听筛选表单变化
  527. this.filterForm.valueChanges
  528. .pipe(
  529. debounceTime(300),
  530. // 自定义比较逻辑,确保值真正变化时才触发
  531. distinctUntilChanged((prev, curr) => {
  532. return (
  533. prev.templateName === curr.templateName &&
  534. prev.templateType === curr.templateType
  535. );
  536. })
  537. )
  538. .subscribe(() => {
  539. this.applyFilters();
  540. this.updateUrl();
  541. });
  542. // 订阅URL参数变化
  543. this.queryParamsSubscription = this.route.queryParams.subscribe(
  544. (params) => {
  545. this.filterForm.patchValue(
  546. {
  547. templateName: params['templateName'] || '',
  548. templateType: params['templateType']
  549. ? (parseInt(params['templateType'], 10) as TemplateType)
  550. : null,
  551. },
  552. { emitEvent: false }
  553. );
  554. this.loadTemplates();
  555. }
  556. );
  557. }
  558. ngOnDestroy(): void {
  559. if (this.queryParamsSubscription) {
  560. this.queryParamsSubscription.unsubscribe();
  561. }
  562. }
  563. //替换原来的方法
  564. getActionOptions(): { value: string; label: string }[] {
  565. return this.messageService.getActionOptions();
  566. }
  567. // 替换原来的方法
  568. getParamPlaceholder(): string {
  569. const action = this.templateForm.get('action')?.value as ActionType;
  570. return this.messageService.getActionParamPlaceholder(action);
  571. }
  572. loadTemplates(): void {
  573. this.isLoading = true;
  574. this.messageService.getAllTemplates().subscribe({
  575. next: (templates) => {
  576. this.templates = templates;
  577. this.applyFilters();
  578. this.isLoading = false;
  579. },
  580. error: (err) => {
  581. this.message.error('加载模板失败: ' + err.message);
  582. this.isLoading = false;
  583. },
  584. });
  585. }
  586. // 应用筛选条件
  587. applyFilters(): void {
  588. const filterValue = this.filterForm.value;
  589. this.filteredTemplates = this.templates.filter((template) => {
  590. const nameMatch = filterValue.templateName
  591. ? template.templateName
  592. .toLowerCase()
  593. .includes(filterValue.templateName.toLowerCase())
  594. : true;
  595. const typeMatch =
  596. filterValue.templateType !== null
  597. ? template.templateType === filterValue.templateType
  598. : true;
  599. return nameMatch && typeMatch;
  600. });
  601. }
  602. // 更新URL参数
  603. private updateUrl(): void {
  604. const queryParams: any = {};
  605. const filterValue = this.filterForm.value;
  606. if (filterValue.templateName) {
  607. queryParams.templateName = filterValue.templateName;
  608. }
  609. if (filterValue.templateType !== null) {
  610. queryParams.templateType = filterValue.templateType;
  611. }
  612. this.router.navigate([], {
  613. relativeTo: this.route,
  614. queryParams,
  615. queryParamsHandling: 'merge',
  616. });
  617. }
  618. // 点击类型标签筛选
  619. filterByType(type: TemplateType): void {
  620. this.filterForm.patchValue({
  621. templateType: type,
  622. });
  623. }
  624. // 重置筛选条件
  625. resetFilters(): void {
  626. this.filterForm.reset({
  627. templateName: '',
  628. templateType: null,
  629. });
  630. }
  631. getTemplateTypeName(type: TemplateType): string {
  632. return TEMPLATE_TYPE_MAP[type] || '未知类型';
  633. }
  634. showCreateModal(): void {
  635. this.isEditMode = false;
  636. // 关键修复:确保新建时模板名称控件可编辑
  637. const templateNameControl = this.templateForm.get('templateName');
  638. if (templateNameControl) {
  639. templateNameControl.enable();
  640. }
  641. // 重置表单默认值
  642. this.templateForm.reset({
  643. templateType: TemplateType.OTHER,
  644. bigger: false,
  645. });
  646. this.clearDynamicControls();
  647. // 设置默认语言
  648. const defaultLanguages = ['en', 'zh-cn'];
  649. this.templateForm.get('selectedLanguages')?.setValue(defaultLanguages);
  650. this.onLanguageSelectionChange();
  651. this.isModalVisible = true;
  652. }
  653. showEditModal(template: IMessageTemplate): void {
  654. this.isEditMode = true;
  655. this.selectedTemplate = template;
  656. this.templateForm.reset(
  657. {
  658. templateName: template.templateName,
  659. templateType: template.templateType,
  660. image: template.image || null,
  661. bigger: template.bigger || false,
  662. action: template.action || null,
  663. param: template.param || null,
  664. extend: template.extend || null,
  665. selectedLanguages: Object.keys(template.messageTitle),
  666. },
  667. { emitEvent: false }
  668. );
  669. // 编辑时禁用模板名称
  670. const templateNameControl = this.templateForm.get('templateName');
  671. if (templateNameControl) {
  672. templateNameControl.disable();
  673. }
  674. this.clearDynamicControls();
  675. // 加载多语言内容
  676. Object.keys(template.messageTitle).forEach((lang) => {
  677. this.templateForm.addControl(
  678. `title_${lang}`,
  679. new FormControl(template.messageTitle[lang], Validators.required)
  680. );
  681. this.templateForm.addControl(
  682. `content_${lang}`,
  683. new FormControl(template.messageContent[lang], Validators.required)
  684. );
  685. });
  686. this.cdr.detectChanges();
  687. this.isModalVisible = true;
  688. }
  689. showDetailModal(template: IMessageTemplate): void {
  690. this.selectedTemplate = template;
  691. this.isDetailModalVisible = true;
  692. }
  693. onLanguageSelectionChange(): void {
  694. // 统一语言代码为小写,避免大小写不一致导致的问题
  695. const selectedLanguages = (
  696. this.templateForm.get('selectedLanguages')?.value || []
  697. ).map((lang: string) => lang.toLowerCase());
  698. const currentLanguages = this.getCurrentLanguageControls();
  699. // 添加新选择的语言控件
  700. selectedLanguages.forEach((lang: string) => {
  701. if (!currentLanguages.includes(lang)) {
  702. this.addLanguageControls(lang);
  703. }
  704. });
  705. // 移除取消选择的语言控件
  706. currentLanguages.forEach((lang) => {
  707. if (!selectedLanguages.includes(lang)) {
  708. this.templateForm.removeControl(`title_${lang}`);
  709. this.templateForm.removeControl(`content_${lang}`);
  710. }
  711. });
  712. }
  713. handleOk(): void {
  714. if (this.templateForm.invalid) {
  715. this.markFormControlsAsDirty();
  716. return;
  717. }
  718. this.isSubmitting = true;
  719. const formValue = this.templateForm.getRawValue();
  720. const selectedLanguages = formValue.selectedLanguages;
  721. const messageData = {
  722. templateName: formValue.templateName,
  723. templateType: formValue.templateType,
  724. image: formValue.image,
  725. bigger: formValue.bigger,
  726. action: formValue.action,
  727. param: formValue.param,
  728. extend: formValue.extend,
  729. messageTitle: {} as { [key: string]: string },
  730. messageContent: {} as { [key: string]: string },
  731. };
  732. selectedLanguages.forEach((lang: string) => {
  733. const normalizedLang = lang.toLowerCase();
  734. const titleControl = this.templateForm.get(`title_${normalizedLang}`);
  735. const contentControl = this.templateForm.get(`content_${normalizedLang}`);
  736. if (titleControl && contentControl) {
  737. messageData.messageTitle[normalizedLang] = titleControl.value;
  738. messageData.messageContent[normalizedLang] = contentControl.value;
  739. }
  740. });
  741. if (this.isEditMode && this.selectedTemplate) {
  742. this.updateTemplate(messageData);
  743. } else {
  744. this.createTemplate(messageData);
  745. }
  746. }
  747. createTemplate(templateData: any): void {
  748. this.messageService.createTemplate(templateData).subscribe({
  749. next: () => {
  750. this.message.success('模板创建成功');
  751. this.loadTemplates();
  752. this.isModalVisible = false;
  753. this.isSubmitting = false;
  754. },
  755. error: (err) => {
  756. this.handleApiError(err, '创建');
  757. this.isSubmitting = false;
  758. },
  759. });
  760. }
  761. updateTemplate(templateData: any): void {
  762. if (!this.selectedTemplate) return;
  763. this.messageService
  764. .updateTemplate(this.selectedTemplate.templateName, templateData)
  765. .subscribe({
  766. next: () => {
  767. this.message.success('模板更新成功');
  768. this.loadTemplates();
  769. this.isModalVisible = false;
  770. this.isSubmitting = false;
  771. },
  772. error: (err) => {
  773. this.handleApiError(err, '更新');
  774. this.isSubmitting = false;
  775. },
  776. });
  777. }
  778. deleteTemplate(templateName: string): void {
  779. this.messageService.deleteTemplate(templateName).subscribe({
  780. next: () => {
  781. this.message.success('模板删除成功');
  782. this.loadTemplates();
  783. // 如果删除的是当前筛选的模板,重置筛选条件
  784. const currentFilterName = this.filterForm.get('templateName')?.value;
  785. if (
  786. currentFilterName &&
  787. currentFilterName.toLowerCase() === templateName.toLowerCase()
  788. ) {
  789. this.resetFilters();
  790. }
  791. },
  792. error: (err) => {
  793. this.handleApiError(err, '删除');
  794. },
  795. });
  796. }
  797. handleCancel(): void {
  798. this.isModalVisible = false;
  799. this.clearDynamicControls();
  800. // 重置表单状态,避免下次打开时显示错误提示
  801. Object.values(this.templateForm.controls).forEach((control) => {
  802. control.markAsPristine();
  803. control.markAsUntouched();
  804. });
  805. // 确保下次打开新建时模板名称可编辑
  806. const templateNameControl = this.templateForm.get('templateName');
  807. if (templateNameControl) {
  808. templateNameControl.enable();
  809. }
  810. }
  811. private addLanguageControls(lang: string): void {
  812. const normalizedLang = lang.toLowerCase();
  813. this.templateForm.addControl(
  814. `title_${normalizedLang}`,
  815. new FormControl('', Validators.required)
  816. );
  817. this.templateForm.addControl(
  818. `content_${normalizedLang}`,
  819. new FormControl('', Validators.required)
  820. );
  821. }
  822. private clearDynamicControls(): void {
  823. Object.keys(this.templateForm.controls).forEach((key) => {
  824. if (key.startsWith('title_') || key.startsWith('content_')) {
  825. this.templateForm.removeControl(key);
  826. }
  827. });
  828. }
  829. private getCurrentLanguageControls(): string[] {
  830. const languages: string[] = [];
  831. Object.keys(this.templateForm.controls).forEach((key) => {
  832. if (key.startsWith('title_')) {
  833. languages.push(key.replace('title_', '').toLowerCase());
  834. }
  835. });
  836. return languages;
  837. }
  838. private markFormControlsAsDirty(): void {
  839. Object.values(this.templateForm.controls).forEach((control) => {
  840. if (control instanceof FormControl) {
  841. control.markAsDirty();
  842. control.updateValueAndValidity();
  843. }
  844. });
  845. this.message.error('请填写所有必填字段');
  846. }
  847. private handleApiError(err: any, action: string): void {
  848. if (err.status === 409) {
  849. this.message.error('模板名称已存在');
  850. } else if (err.status === 404) {
  851. this.message.error('未找到指定的模板');
  852. } else if (err.status === 400) {
  853. this.message.error(
  854. '验证错误: ' + (err.error?.message || '请检查输入数据')
  855. );
  856. } else {
  857. this.message.error(`${action}模板失败: ${err.message}`);
  858. }
  859. }
  860. getLanguageName(code: string): string {
  861. return this.messageService.getLanguageName(code);
  862. }
  863. getLanguageKeys(obj: { [key: string]: any }): string[] {
  864. return obj ? Object.keys(obj) : [];
  865. }
  866. }