import { ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core'; import { CommonModule, DatePipe } from '@angular/common'; import { FormsModule, ReactiveFormsModule, FormBuilder, FormGroup, FormControl, Validators, } from '@angular/forms'; import { Router, ActivatedRoute } from '@angular/router'; import { Subscription } from 'rxjs'; import { debounceTime, distinctUntilChanged } from 'rxjs/operators'; // NG-ZORRO 组件 import { NzTableModule } from 'ng-zorro-antd/table'; import { NzDividerModule } from 'ng-zorro-antd/divider'; import { NzButtonModule } from 'ng-zorro-antd/button'; import { NzIconModule } from 'ng-zorro-antd/icon'; import { NzFormModule } from 'ng-zorro-antd/form'; import { NzInputModule } from 'ng-zorro-antd/input'; import { NzModalModule, NzModalService } from 'ng-zorro-antd/modal'; import { NzSelectModule } from 'ng-zorro-antd/select'; import { NzPageHeaderModule } from 'ng-zorro-antd/page-header'; import { NzCardModule } from 'ng-zorro-antd/card'; import { NzPopconfirmModule } from 'ng-zorro-antd/popconfirm'; import { NzTagModule } from 'ng-zorro-antd/tag'; import { NzTabsModule } from 'ng-zorro-antd/tabs'; import { NzDescriptionsModule } from 'ng-zorro-antd/descriptions'; import { NzMessageService } from 'ng-zorro-antd/message'; import { NzEmptyModule } from 'ng-zorro-antd/empty'; import { NzSpinModule } from 'ng-zorro-antd/spin'; import { NzSwitchModule } from 'ng-zorro-antd/switch'; // 服务 import { MessageService, IMessageTemplate, TemplateType, TEMPLATE_TYPE_MAP, ActionType, ACTION_TYPE_MAP, ACTION_PARAM_PLACEHOLDERS, } from '../services/message.service'; @Component({ selector: 'app-message-template', standalone: true, imports: [ CommonModule, FormsModule, ReactiveFormsModule, NzTableModule, NzDividerModule, NzButtonModule, NzIconModule, NzFormModule, NzInputModule, NzModalModule, NzSelectModule, NzPageHeaderModule, NzCardModule, NzPopconfirmModule, NzTagModule, NzTabsModule, NzDescriptionsModule, NzEmptyModule, NzSpinModule, NzSwitchModule, DatePipe, ], template: ` 消息模板管理 管理多语言消息模板,用于向不同语言的用户发送本地化消息
@for (type of templateTypeOptions; track type.value) { }
模板名称 模板类型 消息内容 图片 Action 创建时间 更新时间 操作 @for (template of templatesTable.data; track template.templateName) { {{ template.templateName }} {{ getTemplateTypeName(template.templateType) }}
{{ template.messageTitle['en'] || '-' }}
{{ template.messageContent['en'] || '-' }}
@if (template.image) { } @else { - } {{ template.action }} {{ template.createdAt | date : 'yyyy-MM-dd HH:mm' }} {{ template.updatedAt | date : 'yyyy-MM-dd HH:mm' }} 编辑 详情 删除 }
@if (filteredTemplates.length === 0 && !isLoading) { }
模板名称 模板类型 @for (type of templateTypeOptions; track type.value) { } 图片URL 允许展开 客户端行为 @for (action of getActionOptions(); track action.value) { } 参数 扩展参数 添加语言 @for (lang of availableLanguages; track lang.code) { } @if (templateForm.get('selectedLanguages')?.value; as selectedLangs) { @for (lang of selectedLangs; track lang) { @if (templateForm.get('title_' + lang) && templateForm.get('content_' + lang)) { 标题 内容 } } }
{{ selectedTemplate?.templateName }} {{ getTemplateTypeName(selectedTemplate?.templateType || 0) }} @if (selectedTemplate?.image) { } {{ selectedTemplate?.bigger ? '是' : '否' }} @if (selectedTemplate?.action) { {{ selectedTemplate?.action }} } @if (selectedTemplate?.param) { {{ selectedTemplate?.param }} } @if (selectedTemplate?.extend) { {{ selectedTemplate?.extend }} } {{ selectedTemplate?.createdAt | date : 'yyyy-MM-dd HH:mm:ss' }} {{ selectedTemplate?.updatedAt | date : 'yyyy-MM-dd HH:mm:ss' }} @for (lang of getLanguageKeys(selectedTemplate?.messageTitle || {}); track lang) {

{{ selectedTemplate?.messageTitle?.[lang] }}

{{ selectedTemplate?.messageContent?.[lang] }}

}
`, styles: [ ` .filter-form { margin-bottom: 16px; } .button-col { display: flex; justify-content: flex-end; } nz-card { margin-bottom: 16px; } .ant-tag { margin-bottom: 4px; } .ant-form-item { margin-bottom: 16px; } textarea.ant-input { resize: none; } .ant-empty { margin: 40px 0; } .ant-table { margin-top: 16px; } /* 消息内容单元格样式 */ .message-content-cell { max-width: 300px; min-width: 200px; } /* 消息标题样式 */ .message-title { font-weight: bold; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; margin-bottom: 4px; } /* 消息内容样式 */ .message-content { display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; text-overflow: ellipsis; line-height: 1.4; color: #666; } /* 可点击的标签样式 */ nz-tag[style*='cursor: pointer']:hover { opacity: 0.8; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); } img { max-width: 60px; max-height: 60px; border-radius: 4px; } `, ], }) export class MessageTemplateComponent implements OnInit, OnDestroy { templates: IMessageTemplate[] = []; filteredTemplates: IMessageTemplate[] = []; isLoading = false; isModalVisible = false; isDetailModalVisible = false; isEditMode = false; isSubmitting = false; selectedTemplate: IMessageTemplate | null = null; availableLanguages: { code: string; name: string }[] = []; templateTypeOptions: { value: TemplateType; label: string }[] = []; // 筛选表单 filterForm: FormGroup; // 模板表单 templateForm: FormGroup; private queryParamsSubscription!: Subscription; constructor( private messageService: MessageService, private fb: FormBuilder, private message: NzMessageService, private modal: NzModalService, private cdr: ChangeDetectorRef, private router: Router, private route: ActivatedRoute ) { // 初始化筛选表单 this.filterForm = this.fb.group({ templateName: [''], templateType: [null], }); // 初始化模板表单 this.templateForm = this.fb.group({ templateName: [ '', [Validators.required, Validators.pattern(/^[a-zA-Z0-9_-]+$/)], ], templateType: [TemplateType.OTHER, Validators.required], image: [null], bigger: [false], action: [null], param: [null], extend: [null], selectedLanguages: [[], [Validators.required]], }); } ngOnInit(): void { this.loadTemplates(); this.availableLanguages = this.messageService.getSupportedLanguages(); this.templateTypeOptions = this.messageService.getTemplateTypeOptions(); // 监听筛选表单变化 this.filterForm.valueChanges .pipe( debounceTime(300), // 自定义比较逻辑,确保值真正变化时才触发 distinctUntilChanged((prev, curr) => { return ( prev.templateName === curr.templateName && prev.templateType === curr.templateType ); }) ) .subscribe(() => { this.applyFilters(); this.updateUrl(); }); // 订阅URL参数变化 this.queryParamsSubscription = this.route.queryParams.subscribe( (params) => { this.filterForm.patchValue( { templateName: params['templateName'] || '', templateType: params['templateType'] ? (parseInt(params['templateType'], 10) as TemplateType) : null, }, { emitEvent: false } ); this.loadTemplates(); } ); } ngOnDestroy(): void { if (this.queryParamsSubscription) { this.queryParamsSubscription.unsubscribe(); } } //替换原来的方法 getActionOptions(): { value: string; label: string }[] { return this.messageService.getActionOptions(); } // 替换原来的方法 getParamPlaceholder(): string { const action = this.templateForm.get('action')?.value as ActionType; return this.messageService.getActionParamPlaceholder(action); } loadTemplates(): void { this.isLoading = true; this.messageService.getAllTemplates().subscribe({ next: (templates) => { this.templates = templates; this.applyFilters(); this.isLoading = false; }, error: (err) => { this.message.error('加载模板失败: ' + err.message); this.isLoading = false; }, }); } // 应用筛选条件 applyFilters(): void { const filterValue = this.filterForm.value; this.filteredTemplates = this.templates.filter((template) => { const nameMatch = filterValue.templateName ? template.templateName .toLowerCase() .includes(filterValue.templateName.toLowerCase()) : true; const typeMatch = filterValue.templateType !== null ? template.templateType === filterValue.templateType : true; return nameMatch && typeMatch; }); } // 更新URL参数 private updateUrl(): void { const queryParams: any = {}; const filterValue = this.filterForm.value; if (filterValue.templateName) { queryParams.templateName = filterValue.templateName; } if (filterValue.templateType !== null) { queryParams.templateType = filterValue.templateType; } this.router.navigate([], { relativeTo: this.route, queryParams, queryParamsHandling: 'merge', }); } // 点击类型标签筛选 filterByType(type: TemplateType): void { this.filterForm.patchValue({ templateType: type, }); } // 重置筛选条件 resetFilters(): void { this.filterForm.reset({ templateName: '', templateType: null, }); } getTemplateTypeName(type: TemplateType): string { return TEMPLATE_TYPE_MAP[type] || '未知类型'; } showCreateModal(): void { this.isEditMode = false; // 关键修复:确保新建时模板名称控件可编辑 const templateNameControl = this.templateForm.get('templateName'); if (templateNameControl) { templateNameControl.enable(); } // 重置表单默认值 this.templateForm.reset({ templateType: TemplateType.OTHER, bigger: false, }); this.clearDynamicControls(); // 设置默认语言 const defaultLanguages = ['en', 'zh-cn']; this.templateForm.get('selectedLanguages')?.setValue(defaultLanguages); this.onLanguageSelectionChange(); this.isModalVisible = true; } showEditModal(template: IMessageTemplate): void { this.isEditMode = true; this.selectedTemplate = template; this.templateForm.reset( { templateName: template.templateName, templateType: template.templateType, image: template.image || null, bigger: template.bigger || false, action: template.action || null, param: template.param || null, extend: template.extend || null, selectedLanguages: Object.keys(template.messageTitle), }, { emitEvent: false } ); // 编辑时禁用模板名称 const templateNameControl = this.templateForm.get('templateName'); if (templateNameControl) { templateNameControl.disable(); } this.clearDynamicControls(); // 加载多语言内容 Object.keys(template.messageTitle).forEach((lang) => { this.templateForm.addControl( `title_${lang}`, new FormControl(template.messageTitle[lang], Validators.required) ); this.templateForm.addControl( `content_${lang}`, new FormControl(template.messageContent[lang], Validators.required) ); }); this.cdr.detectChanges(); this.isModalVisible = true; } showDetailModal(template: IMessageTemplate): void { this.selectedTemplate = template; this.isDetailModalVisible = true; } onLanguageSelectionChange(): void { // 统一语言代码为小写,避免大小写不一致导致的问题 const selectedLanguages = ( this.templateForm.get('selectedLanguages')?.value || [] ).map((lang: string) => lang.toLowerCase()); const currentLanguages = this.getCurrentLanguageControls(); // 添加新选择的语言控件 selectedLanguages.forEach((lang: string) => { if (!currentLanguages.includes(lang)) { this.addLanguageControls(lang); } }); // 移除取消选择的语言控件 currentLanguages.forEach((lang) => { if (!selectedLanguages.includes(lang)) { this.templateForm.removeControl(`title_${lang}`); this.templateForm.removeControl(`content_${lang}`); } }); } handleOk(): void { if (this.templateForm.invalid) { this.markFormControlsAsDirty(); return; } this.isSubmitting = true; const formValue = this.templateForm.getRawValue(); const selectedLanguages = formValue.selectedLanguages; const messageData = { templateName: formValue.templateName, templateType: formValue.templateType, image: formValue.image, bigger: formValue.bigger, action: formValue.action, param: formValue.param, extend: formValue.extend, messageTitle: {} as { [key: string]: string }, messageContent: {} as { [key: string]: string }, }; selectedLanguages.forEach((lang: string) => { const normalizedLang = lang.toLowerCase(); const titleControl = this.templateForm.get(`title_${normalizedLang}`); const contentControl = this.templateForm.get(`content_${normalizedLang}`); if (titleControl && contentControl) { messageData.messageTitle[normalizedLang] = titleControl.value; messageData.messageContent[normalizedLang] = contentControl.value; } }); if (this.isEditMode && this.selectedTemplate) { this.updateTemplate(messageData); } else { this.createTemplate(messageData); } } createTemplate(templateData: any): void { this.messageService.createTemplate(templateData).subscribe({ next: () => { this.message.success('模板创建成功'); this.loadTemplates(); this.isModalVisible = false; this.isSubmitting = false; }, error: (err) => { this.handleApiError(err, '创建'); this.isSubmitting = false; }, }); } updateTemplate(templateData: any): void { if (!this.selectedTemplate) return; this.messageService .updateTemplate(this.selectedTemplate.templateName, templateData) .subscribe({ next: () => { this.message.success('模板更新成功'); this.loadTemplates(); this.isModalVisible = false; this.isSubmitting = false; }, error: (err) => { this.handleApiError(err, '更新'); this.isSubmitting = false; }, }); } deleteTemplate(templateName: string): void { this.messageService.deleteTemplate(templateName).subscribe({ next: () => { this.message.success('模板删除成功'); this.loadTemplates(); // 如果删除的是当前筛选的模板,重置筛选条件 const currentFilterName = this.filterForm.get('templateName')?.value; if ( currentFilterName && currentFilterName.toLowerCase() === templateName.toLowerCase() ) { this.resetFilters(); } }, error: (err) => { this.handleApiError(err, '删除'); }, }); } handleCancel(): void { this.isModalVisible = false; this.clearDynamicControls(); // 重置表单状态,避免下次打开时显示错误提示 Object.values(this.templateForm.controls).forEach((control) => { control.markAsPristine(); control.markAsUntouched(); }); // 确保下次打开新建时模板名称可编辑 const templateNameControl = this.templateForm.get('templateName'); if (templateNameControl) { templateNameControl.enable(); } } private addLanguageControls(lang: string): void { const normalizedLang = lang.toLowerCase(); this.templateForm.addControl( `title_${normalizedLang}`, new FormControl('', Validators.required) ); this.templateForm.addControl( `content_${normalizedLang}`, new FormControl('', Validators.required) ); } private clearDynamicControls(): void { Object.keys(this.templateForm.controls).forEach((key) => { if (key.startsWith('title_') || key.startsWith('content_')) { this.templateForm.removeControl(key); } }); } private getCurrentLanguageControls(): string[] { const languages: string[] = []; Object.keys(this.templateForm.controls).forEach((key) => { if (key.startsWith('title_')) { languages.push(key.replace('title_', '').toLowerCase()); } }); return languages; } private markFormControlsAsDirty(): void { Object.values(this.templateForm.controls).forEach((control) => { if (control instanceof FormControl) { control.markAsDirty(); control.updateValueAndValidity(); } }); this.message.error('请填写所有必填字段'); } private handleApiError(err: any, action: string): void { if (err.status === 409) { this.message.error('模板名称已存在'); } else if (err.status === 404) { this.message.error('未找到指定的模板'); } else if (err.status === 400) { this.message.error( '验证错误: ' + (err.error?.message || '请检查输入数据') ); } else { this.message.error(`${action}模板失败: ${err.message}`); } } getLanguageName(code: string): string { return this.messageService.getLanguageName(code); } getLanguageKeys(obj: { [key: string]: any }): string[] { return obj ? Object.keys(obj) : []; } }