guoziyun преди 9 месеца
родител
ревизия
c574e68937

+ 1 - 0
oms/dist/src/models/messageRecordModel.js

@@ -27,6 +27,7 @@ const messageRecordSchema = new mongoose_1.Schema({
     title: {
         type: String,
         required: true,
+        index: true,
     },
     // 已经确定了具体语言的消息内容,必须
     content: {

+ 1 - 1
oms/public/app/index.html

@@ -9,5 +9,5 @@
   <style>body,html{width:100%;height:100%}*,:after,:before{box-sizing:border-box}html{font-family:sans-serif;line-height:1.15;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%;-ms-overflow-style:scrollbar;-webkit-tap-highlight-color:transparent}@-ms-viewport{width:device-width}body{margin:0;color:#000000d9;font-size:14px;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,"Apple Color Emoji","Segoe UI Emoji",Segoe UI Symbol,"Noto Color Emoji";font-variant:tabular-nums;line-height:1.5715;background-color:#fff;font-feature-settings:"tnum"}html{--antd-wave-shadow-color:#1890ff;--scroll-bar:0}</style><link rel="stylesheet" href="styles-LXBSU6DF.css" media="print" onload="this.media='all'"><noscript><link rel="stylesheet" href="styles-LXBSU6DF.css"></noscript></head>
   <body>
     <app-root></app-root>
-  <script src="polyfills-B6TNHZQ6.js" type="module"></script><script src="main-4VR4ZTQB.js" type="module"></script></body>
+  <script src="polyfills-B6TNHZQ6.js" type="module"></script><script src="main-3P5NBQDT.js" type="module"></script></body>
 </html>

Файловите разлики са ограничени, защото са твърде много
+ 0 - 0
oms/public/app/main-3P5NBQDT.js


+ 0 - 485
omsapp/src/app/layouts/admin-layout.component copy.ts

@@ -1,485 +0,0 @@
-import {
-  Component,
-  OnInit,
-  OnDestroy,
-  ViewChild,
-  ElementRef,
-  AfterViewInit,
-} from '@angular/core';
-import {
-  Router,
-  RouterOutlet,
-  RouterModule,
-  NavigationEnd,
-} from '@angular/router';
-import { CommonModule } from '@angular/common';
-import { filter, Subscription } from 'rxjs';
-
-// 导入 NG-ZORRO 模块
-import { NzLayoutModule } from 'ng-zorro-antd/layout';
-import { NzMenuModule } from 'ng-zorro-antd/menu';
-import { NzIconModule } from 'ng-zorro-antd/icon';
-import { NzButtonModule } from 'ng-zorro-antd/button';
-import { NzTabsModule } from 'ng-zorro-antd/tabs';
-import { AuthService } from '../services/auth.service';
-
-interface TabItem {
-  title: string;
-  path: string;
-}
-
-@Component({
-  selector: 'app-main-layout',
-  template: `
-    <nz-layout class="app-layout">
-      <!-- 侧边栏(保持不变) -->
-      <nz-sider
-        class="menu-sidebar"
-        nzCollapsible
-        nzTheme="dark"
-        [nzCollapsedWidth]="64"
-        nzWidth="216px"
-        [(nzCollapsed)]="isCollapsed"
-        [nzTrigger]="null"
-      >
-        <div class="menu-sidebar-inner">
-          <div class="sidebar-logo">
-            <a href="https://ng.ant.design/" target="_blank">
-              <img src="https://ng.ant.design/assets/img/logo.svg" alt="logo" />
-              <h1>OMS</h1>
-            </a>
-          </div>
-          <ul nz-menu nzTheme="dark" nzMode="inline" class="menu">
-            <li
-              nz-menu-item
-              [routerLink]="['/dashboard']"
-              [nzSelected]="activePath === '/dashboard'"
-            >
-              <span nz-icon nzType="dashboard"></span>
-              <span>Dashboard</span>
-            </li>
-            <li
-              nz-menu-item
-              [routerLink]="['/user']"
-              [nzSelected]="activePath === '/user'"
-            >
-              <span nz-icon nzType="team"></span>
-              <span>用户管理</span>
-            </li>
-            <li nz-submenu nzTitle="消息系统" nzIcon="message">
-              <ul>
-                <li
-                  nz-menu-item
-                  [routerLink]="['/message-activity']"
-                  [nzSelected]="activePath === '/message-activity'"
-                >
-                  <span nz-icon nzType="notification"></span>
-                  <span>消息通知</span>
-                </li>
-                <li
-                  nz-menu-item
-                  [routerLink]="['/message-record']"
-                  [nzSelected]="activePath === '/message-record'"
-                >
-                  <span nz-icon nzType="send"></span>
-                  <span>推送记录</span>
-                </li>
-                <li
-                  nz-menu-item
-                  [routerLink]="['/message-template']"
-                  [nzSelected]="activePath === '/message-template'"
-                >
-                  <span nz-icon nzType="cluster"></span>
-                  <span>消息模板</span>
-                </li>
-              </ul>
-            </li>
-          </ul>
-        </div>
-      </nz-sider>
-
-      <!-- 主内容区域 -->
-      <nz-layout>
-        <!-- 顶部导航栏:核心修复区域 -->
-        <nz-header class="header">
-          <!-- 顶部容器:使用flex确保子元素同行 -->
-          <div class="header-container">
-            <!-- 1. 折叠按钮:固定宽度,不压缩 -->
-            <div class="header-trigger-wrapper">
-              <span class="header-trigger" (click)="isCollapsed = !isCollapsed">
-                <i
-                  class="trigger"
-                  nz-icon
-                  [nzType]="isCollapsed ? 'menu-unfold' : 'menu-fold'"
-                ></i>
-              </span>
-            </div>
-
-            <!-- 2. Tab标签页:占满中间剩余空间,溢出时可滚动 -->
-            <div class="tab-container">
-              <div class="tabs-wrapper" #tabsWrapper>
-                <nz-tabs
-                  [nzSelectedIndex]="getTabIndex(activePath)"
-                  nzType="card"
-                  nzHideAll
-                  nzSize="small"
-                  (nzSelectedIndexChange)="selectTab(openTabs[$event]?.path)"
-                >
-                  <nz-tab
-                    *ngFor="let tab of openTabs; let i = index"
-                    [nzTitle]="titleTemplate"
-                  >
-                    <ng-template #titleTemplate>
-                      <span class="tab-title" (click)="selectTab(tab.path)">{{
-                        tab.title
-                      }}</span>
-                      <i
-                        nz-icon
-                        nzType="close"
-                        class="close-icon"
-                        (click)="
-                          closeTab(tab.path, i); $event.stopPropagation()
-                        "
-                      ></i>
-                    </ng-template>
-                  </nz-tab>
-                </nz-tabs>
-              </div>
-            </div>
-
-            <!-- 3. Admin菜单:固定在右侧,不压缩 -->
-            <div class="user-menu-wrapper">
-              <ul nz-menu nzTheme="light" nzMode="horizontal" class="user-menu">
-                <li nz-submenu nzTitle="Admin" nzIcon="user">
-                  <ul>
-                    <li nz-menu-item>个人设置</li>
-                    <li nz-menu-item (click)="logout()">退出登录</li>
-                  </ul>
-                </li>
-              </ul>
-            </div>
-          </div>
-        </nz-header>
-
-        <!-- 内容区域(保持不变) -->
-        <nz-content>
-          <div class="inner-content" #content>
-            <router-outlet></router-outlet>
-          </div>
-        </nz-content>
-      </nz-layout>
-    </nz-layout>
-  `,
-  styles: [
-    `
-      :host {
-        display: flex;
-        text-rendering: optimizeLegibility;
-        -webkit-font-smoothing: antialiased;
-        -moz-osx-font-smoothing: grayscale;
-      }
-
-      .app-layout {
-        min-height: 100vh;
-        display: flex;
-      }
-
-      /* 侧边栏样式(保持不变) */
-      .menu-sidebar {
-        position: relative;
-        z-index: 1001;
-        min-height: 100vh;
-        box-shadow: 2px 0 6px rgba(0, 21, 41, 0.35);
-        background: #001529;
-      }
-
-      .menu-sidebar-inner {
-        position: sticky;
-        height: 100vh;
-        top: 0;
-        left: 0;
-        display: flex;
-        flex-direction: column;
-        background: #001529;
-      }
-
-      .menu {
-        flex: 1;
-        overflow-y: auto;
-        padding-bottom: 20px;
-      }
-
-      .sidebar-logo {
-        height: 64px;
-        padding-left: 14px;
-        overflow: hidden;
-        line-height: 64px;
-      }
-
-      .sidebar-logo img {
-        display: inline-block;
-        height: 32px;
-        width: 32px;
-        vertical-align: middle;
-      }
-
-      .sidebar-logo h1 {
-        display: inline-block;
-        margin: 0 0 0 20px;
-        color: #fff;
-        font-weight: 600;
-        font-size: 14px;
-        vertical-align: middle;
-      }
-
-      /* 顶部导航栏核心修复样式 */
-      .header {
-        padding: 0;
-        width: 100%;
-        z-index: 1000;
-        /* 固定顶部高度,避免元素挤压换行 */
-        height: 64px;
-      }
-
-      /* 顶部容器:flex布局确保子元素同行 */
-      .header-container {
-        display: flex;
-        align-items: center;
-        justify-content: space-between;
-        height: 100%;
-        padding: 0 8px;
-        background: #fff;
-        box-shadow: 0 1px 4px rgba(0, 21, 41, 0.08);
-      }
-
-      /* 1. 折叠按钮容器:固定宽度,不压缩 */
-      .header-trigger-wrapper {
-        width: 64px; /* 与折叠按钮点击区域匹配 */
-        display: flex;
-        align-items: center;
-        justify-content: center;
-      }
-
-      .header-trigger {
-        height: 100%;
-        width: 100%;
-        display: flex;
-        align-items: center;
-        justify-content: center;
-        cursor: pointer;
-        transition: all 0.3s;
-      }
-
-      .trigger:hover {
-        color: #1890ff;
-      }
-
-      /* 2. Tab标签页容器:占满中间空间,溢出时横向滚动 */
-      .tab-container {
-        flex: 1; /* 关键:占满剩余宽度 */
-        margin: 0 8px;
-        height: 100%;
-        display: flex;
-        align-items: center;
-        overflow: hidden; /* 隐藏超出容器的内容 */
-      }
-
-      .tabs-wrapper {
-        width: 100%;
-        height: 100%;
-        overflow-x: auto; /* 横向滚动 */
-        white-space: nowrap; /* 禁止Tab换行 */
-        /* 隐藏滚动条,保持美观 */
-        scrollbar-width: none;
-        -ms-overflow-style: none;
-      }
-
-      .tabs-wrapper::-webkit-scrollbar {
-        display: none;
-      }
-
-      /* 优化Tab样式,确保垂直居中 */
-      :host ::ng-deep .ant-tabs-card {
-        height: 100%;
-        display: flex;
-        flex-direction: column;
-      }
-
-      :host ::ng-deep .ant-tabs-card .ant-tabs-card-bar {
-        border-bottom: none;
-        flex-shrink: 0; /* 禁止Tab栏被压缩 */
-      }
-
-      :host ::ng-deep .ant-tabs-card .ant-tabs-tab {
-        height: 36px;
-        line-height: 36px;
-        margin-top: 8px; /* 与顶部保持间距 */
-      }
-
-      /* 3. Admin菜单容器:固定宽度,不压缩 */
-      .user-menu-wrapper {
-        width: 120px; /* 与Admin菜单宽度匹配 */
-        display: flex;
-        align-items: center;
-        justify-content: center;
-      }
-
-      .user-menu {
-        width: 100%;
-        display: flex;
-        align-items: center;
-        justify-content: center;
-      }
-
-      /* 内容区域样式(保持不变) */
-      nz-content {
-        margin: 0;
-        background-color: white;
-      }
-
-      .inner-content {
-        padding: 24px;
-        background: #fff;
-        min-height: calc(100vh - 64px);
-      }
-
-      /* Tab标签页细节样式 */
-      .tab-title {
-        cursor: pointer;
-      }
-
-      .close-icon {
-        margin-left: 8px;
-        font-size: 12px;
-        color: rgba(0, 0, 0, 0.45);
-        cursor: pointer;
-      }
-
-      .close-icon:hover {
-        color: #f5222d;
-      }
-    `,
-  ],
-  standalone: true,
-  imports: [
-    CommonModule,
-    RouterOutlet,
-    RouterModule,
-    NzLayoutModule,
-    NzMenuModule,
-    NzIconModule,
-    NzButtonModule,
-    NzTabsModule,
-  ],
-})
-export class MainLayoutComponent implements OnInit, OnDestroy, AfterViewInit {
-  @ViewChild('tabsWrapper') tabsWrapper!: ElementRef<HTMLDivElement>;
-  @ViewChild('content') content!: ElementRef;
-
-  isCollapsed = true;
-  openTabs: TabItem[] = [];
-  activePath: string | null = null;
-  private routerSubscription!: Subscription;
-
-  private routeTitleMap: { [key: string]: string } = {
-    '/dashboard': '数据看板',
-    '/user': '用户管理',
-    '/message-activity': '消息通知',
-    '/message-record': '推送记录',
-    '/message-template': '消息模板',
-  };
-
-  constructor(private router: Router, private authService: AuthService) {}
-
-  ngOnInit(): void {
-    // 监听路由变化,更新标签页
-    this.routerSubscription = this.router.events
-      .pipe(filter((event) => event instanceof NavigationEnd))
-      .subscribe((event: NavigationEnd) => {
-        const path = event.urlAfterRedirects.split('?')[0];
-        this.activePath = path;
-
-        // 新增未打开的标签页
-        if (!this.openTabs.some((tab) => tab.path === path)) {
-          const title = this.routeTitleMap[path] || '新页面';
-          this.openTabs.push({ title, path });
-        }
-      });
-
-    // 初始化当前路由标签页
-    this.activePath = this.router.url.split('?')[0];
-    if (
-      this.activePath &&
-      !this.openTabs.some((tab) => tab.path === this.activePath)
-    ) {
-      const title = this.routeTitleMap[this.activePath] || '新页面';
-      this.openTabs.push({ title, path: this.activePath });
-    }
-  }
-
-  ngAfterViewInit(): void {
-    this.scrollToActiveTab();
-  }
-
-  ngOnDestroy(): void {
-    if (this.routerSubscription) {
-      this.routerSubscription.unsubscribe();
-    }
-  }
-
-  // 获取当前标签页索引
-  getTabIndex(path: string | null): number {
-    return path ? this.openTabs.findIndex((tab) => tab.path === path) : -1;
-  }
-
-  // 切换标签页
-  selectTab(path: string | undefined): void {
-    if (path && this.activePath !== path) {
-      this.router.navigateByUrl(path);
-    }
-  }
-
-  // 关闭标签页
-  closeTab(path: string, index: number): void {
-    if (this.openTabs.length <= 1) return;
-
-    this.openTabs = this.openTabs.filter((tab) => tab.path !== path);
-
-    // 若关闭当前激活标签页,跳转至前一个标签页
-    if (this.activePath === path) {
-      const newIndex = Math.max(0, index - 1);
-      this.router.navigateByUrl(this.openTabs[newIndex].path);
-    }
-  }
-
-  // 退出登录
-  logout(): void {
-    this.authService.logout();
-    this.router.navigate(['/login']);
-  }
-
-  // 滚动标签页(左右滚动)
-  scrollTabs(direction: 'left' | 'right'): void {
-    const wrapper = this.tabsWrapper.nativeElement;
-    wrapper.scrollBy({
-      left: direction === 'left' ? -150 : 150,
-      behavior: 'smooth',
-    });
-  }
-
-  // 滚动到当前激活标签页
-  private scrollToActiveTab(): void {
-    if (!this.tabsWrapper) return;
-
-    const activeTab = this.tabsWrapper.nativeElement.querySelector(
-      '.ant-tabs-tab-active'
-    );
-    if (activeTab) {
-      activeTab.scrollIntoView({
-        behavior: 'smooth',
-        block: 'nearest',
-        inline: 'center',
-      });
-    }
-  }
-}

+ 0 - 909
omsapp/src/app/pages/message-template.component copy.ts

@@ -1,909 +0,0 @@
-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: `
-    <nz-page-header [nzGhost]="false">
-      <nz-page-header-title>消息模板管理</nz-page-header-title>
-      <nz-page-header-extra>
-        <button nz-button nzType="primary" (click)="showCreateModal()">
-          <span nz-icon nzType="plus"></span>新建模板
-        </button>
-      </nz-page-header-extra>
-      <nz-page-header-content>
-        管理多语言消息模板,用于向不同语言的用户发送本地化消息
-      </nz-page-header-content>
-    </nz-page-header>
-
-    <nz-card>
-      <!-- 筛选表单 -->
-      <form nz-form [formGroup]="filterForm" class="filter-form">
-        <div nz-row [nzGutter]="16">
-          <div nz-col [nzSpan]="8">
-            <nz-form-item>
-              <nz-form-control>
-                <nz-input-group nzPrefixIcon="search">
-                  <input
-                    type="text"
-                    nz-input
-                    placeholder="模板名称"
-                    formControlName="templateName"
-                  />
-                </nz-input-group>
-              </nz-form-control>
-            </nz-form-item>
-          </div>
-          <div nz-col [nzSpan]="8">
-            <nz-form-item>
-              <nz-form-control>
-                <nz-select
-                  nzPlaceHolder="模板类型"
-                  formControlName="templateType"
-                  nzAllowClear
-                >
-                  @for (type of templateTypeOptions; track type.value) {
-                  <nz-option
-                    [nzLabel]="type.label"
-                    [nzValue]="type.value"
-                  ></nz-option>
-                  }
-                </nz-select>
-              </nz-form-control>
-            </nz-form-item>
-          </div>
-          <div nz-col [nzSpan]="8" class="button-col">
-            <button nz-button (click)="resetFilters()">重置</button>
-          </div>
-        </div>
-      </form>
-
-      <nz-spin [nzSpinning]="isLoading">
-        <nz-table
-          #templatesTable
-          [nzData]="filteredTemplates"
-          [nzLoading]="isLoading"
-          [nzFrontPagination]="false"
-          [nzBordered]="true"
-          [nzSize]="'small'"
-          [nzShowPagination]="false"
-        >
-          <thead>
-            <tr>
-              <th>模板名称</th>
-              <th>模板类型</th>
-              <th>消息内容</th>
-              <th style="text-align: center;">图片</th>
-              <th style="text-align: center;">Action</th>
-              <th style="text-align: center;">创建时间</th>
-              <th style="text-align: center;">更新时间</th>
-              <th style="text-align: center;">操作</th>
-            </tr>
-          </thead>
-          <tbody>
-            @for (template of templatesTable.data; track template.templateName)
-            {
-            <tr>
-              <td>{{ template.templateName }}</td>
-              <td>
-                <nz-tag
-                  [nzColor]="'blue'"
-                  (click)="filterByType(template.templateType)"
-                  style="cursor: pointer;"
-                >
-                  {{ getTemplateTypeName(template.templateType) }}
-                </nz-tag>
-              </td>
-              <td class="message-content-cell">
-                <div class="message-title">
-                  {{ template.messageTitle['en'] || '-' }}
-                </div>
-                <div class="message-content">
-                  {{ template.messageContent['en'] || '-' }}
-                </div>
-              </td>
-              <td style="text-align: center;">
-                @if (template.image) {
-                <img
-                  [src]="template.image"
-                  style="max-width: 60px; max-height: 60px;"
-                />
-                } @else { - }
-              </td>
-              <td style="text-align: center;">{{ template.action }}</td>
-              <td style="text-align: center;">
-                {{ template.createdAt | date : 'yyyy-MM-dd HH:mm' }}
-              </td>
-              <td style="text-align: center;">
-                {{ template.updatedAt | date : 'yyyy-MM-dd HH:mm' }}
-              </td>
-              <td style="text-align: center;">
-                <a (click)="showEditModal(template)">编辑</a>
-                <nz-divider nzType="vertical"></nz-divider>
-                <a (click)="showDetailModal(template)">详情</a>
-                <nz-divider nzType="vertical"></nz-divider>
-                <a
-                  nz-popconfirm
-                  nzPopconfirmTitle="确定要删除此模板吗?"
-                  nzPopconfirmOkText="确定"
-                  nzPopconfirmCancelText="取消"
-                  (nzOnConfirm)="deleteTemplate(template.templateName)"
-                  >删除
-                </a>
-              </td>
-            </tr>
-            }
-          </tbody>
-        </nz-table>
-
-        @if (filteredTemplates.length === 0 && !isLoading) {
-        <nz-empty nzNotFoundContent="暂无消息模板"></nz-empty>
-        }
-      </nz-spin>
-    </nz-card>
-
-    <!-- 创建/编辑模板模态框 -->
-    <nz-modal
-      [(nzVisible)]="isModalVisible"
-      [nzTitle]="isEditMode ? '编辑消息模板' : '新建消息模板'"
-      [nzOkText]="isEditMode ? '更新' : '创建'"
-      [nzCancelText]="'取消'"
-      (nzOnCancel)="handleCancel()"
-      (nzOnOk)="handleOk()"
-      [nzOkLoading]="isSubmitting"
-      [nzWidth]="800"
-    >
-      <form nz-form [formGroup]="templateForm" *nzModalContent>
-        <nz-form-item>
-          <nz-form-label nzRequired>模板名称</nz-form-label>
-          <nz-form-control>
-            <input
-              nz-input
-              formControlName="templateName"
-              placeholder="输入模板名称(英文,不可重复)"
-            />
-          </nz-form-control>
-        </nz-form-item>
-
-        <nz-form-item>
-          <nz-form-label nzRequired>模板类型</nz-form-label>
-          <nz-form-control>
-            <nz-select
-              formControlName="templateType"
-              nzPlaceHolder="选择模板类型"
-            >
-              @for (type of templateTypeOptions; track type.value) {
-              <nz-option
-                [nzLabel]="type.label"
-                [nzValue]="type.value"
-              ></nz-option>
-              }
-            </nz-select>
-          </nz-form-control>
-        </nz-form-item>
-
-        <!-- 新增字段 -->
-        <nz-form-item>
-          <nz-form-label>图片URL</nz-form-label>
-          <nz-form-control>
-            <input
-              nz-input
-              formControlName="image"
-              placeholder="输入图片URL(可选)"
-            />
-          </nz-form-control>
-        </nz-form-item>
-
-        <nz-form-item>
-          <nz-form-label>允许展开</nz-form-label>
-          <nz-form-control>
-            <nz-switch formControlName="bigger"></nz-switch>
-          </nz-form-control>
-        </nz-form-item>
-
-        <nz-form-item>
-          <nz-form-label>客户端行为</nz-form-label>
-          <nz-form-control>
-            <nz-select formControlName="action" nzPlaceHolder="选择客户端行为">
-              @for (action of getActionOptions(); track action.value) {
-              <nz-option
-                [nzLabel]="action.label"
-                [nzValue]="action.value"
-              ></nz-option>
-              }
-            </nz-select>
-          </nz-form-control>
-        </nz-form-item>
-
-        <nz-form-item>
-          <nz-form-label>参数</nz-form-label>
-          <nz-form-control>
-            <input
-              nz-input
-              formControlName="param"
-              [placeholder]="getParamPlaceholder()"
-            />
-          </nz-form-control>
-        </nz-form-item>
-
-        <nz-form-item>
-          <nz-form-label>扩展参数</nz-form-label>
-          <nz-form-control>
-            <input
-              nz-input
-              formControlName="extend"
-              placeholder="输入扩展参数(可选)"
-            />
-          </nz-form-control>
-        </nz-form-item>
-
-        <nz-form-item>
-          <nz-form-label nzRequired>添加语言</nz-form-label>
-          <nz-form-control>
-            <nz-select
-              nzMode="multiple"
-              nzPlaceHolder="选择要添加的语言"
-              formControlName="selectedLanguages"
-              (ngModelChange)="onLanguageSelectionChange()"
-            >
-              @for (lang of availableLanguages; track lang.code) {
-              <nz-option
-                [nzLabel]="lang.name"
-                [nzValue]="lang.code"
-              ></nz-option>
-              }
-            </nz-select>
-          </nz-form-control>
-        </nz-form-item>
-
-        @if (templateForm.get('selectedLanguages')?.value; as selectedLangs) {
-        @for (lang of selectedLangs; track lang) { @if
-        (templateForm.get('title_' + lang) && templateForm.get('content_' +
-        lang)) {
-        <nz-card
-          [nzTitle]="getLanguageName(lang)"
-          [nzSize]="'small'"
-          style="margin-bottom: 16px;"
-        >
-          <nz-form-item>
-            <nz-form-label nzRequired>标题</nz-form-label>
-            <nz-form-control>
-              <textarea
-                nz-input
-                [formControlName]="'title_' + lang"
-                placeholder="输入消息标题"
-                rows="2"
-              ></textarea>
-            </nz-form-control>
-          </nz-form-item>
-
-          <nz-form-item>
-            <nz-form-label nzRequired>内容</nz-form-label>
-            <nz-form-control>
-              <textarea
-                nz-input
-                [formControlName]="'content_' + lang"
-                placeholder="输入消息内容"
-                rows="4"
-              ></textarea>
-            </nz-form-control>
-          </nz-form-item>
-        </nz-card>
-        } } }
-      </form>
-    </nz-modal>
-
-    <!-- 模板详情模态框 -->
-    <nz-modal
-      [(nzVisible)]="isDetailModalVisible"
-      [nzTitle]="'模板详情 - ' + (selectedTemplate?.templateName || '')"
-      [nzFooter]="null"
-      (nzOnCancel)="isDetailModalVisible = false"
-      [nzWidth]="800"
-    >
-      <ng-container *nzModalContent>
-        <nz-descriptions nzBordered [nzColumn]="1">
-          <nz-descriptions-item nzTitle="模板名称">{{
-            selectedTemplate?.templateName
-          }}</nz-descriptions-item>
-          <nz-descriptions-item nzTitle="模板类型">
-            <nz-tag [nzColor]="'blue'">
-              {{ getTemplateTypeName(selectedTemplate?.templateType || 0) }}
-            </nz-tag>
-          </nz-descriptions-item>
-
-          @if (selectedTemplate?.image) {
-          <nz-descriptions-item nzTitle="图片">
-            <img
-              [src]="selectedTemplate?.image"
-              style="max-width: 100px; max-height: 100px;"
-            />
-          </nz-descriptions-item>
-          }
-          <nz-descriptions-item nzTitle="允许展开">
-            {{ selectedTemplate?.bigger ? '是' : '否' }}
-          </nz-descriptions-item>
-          @if (selectedTemplate?.action) {
-          <nz-descriptions-item nzTitle="动作类型">
-            {{ selectedTemplate?.action }}
-          </nz-descriptions-item>
-          } @if (selectedTemplate?.param) {
-          <nz-descriptions-item nzTitle="参数">
-            {{ selectedTemplate?.param }}
-          </nz-descriptions-item>
-          } @if (selectedTemplate?.extend) {
-          <nz-descriptions-item nzTitle="扩展参数">
-            {{ selectedTemplate?.extend }}
-          </nz-descriptions-item>
-          }
-          <nz-descriptions-item nzTitle="创建时间">{{
-            selectedTemplate?.createdAt | date : 'yyyy-MM-dd HH:mm:ss'
-          }}</nz-descriptions-item>
-          <nz-descriptions-item nzTitle="更新时间">{{
-            selectedTemplate?.updatedAt | date : 'yyyy-MM-dd HH:mm:ss'
-          }}</nz-descriptions-item>
-        </nz-descriptions>
-
-        <nz-tabs>
-          @for (lang of getLanguageKeys(selectedTemplate?.messageTitle || {});
-          track lang) {
-          <nz-tab [nzTitle]="getLanguageName(lang)">
-            <nz-card [nzTitle]="'标题 (' + lang + ')'" [nzSize]="'small'">
-              <p>{{ selectedTemplate?.messageTitle?.[lang] }}</p>
-            </nz-card>
-            <nz-card
-              [nzTitle]="'内容 (' + lang + ')'"
-              [nzSize]="'small'"
-              style="margin-top: 16px;"
-            >
-              <p>{{ selectedTemplate?.messageContent?.[lang] }}</p>
-            </nz-card>
-          </nz-tab>
-          }
-        </nz-tabs>
-      </ng-container>
-    </nz-modal>
-  `,
-  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())
-      .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);
-  }
-
-  // 模板表单初始化
-  private initTemplateForm(): void {
-    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], // 现在会使用 ActionType 枚举
-      param: [null],
-      extend: [null],
-      selectedLanguages: [[], [Validators.required]],
-    });
-  }
-
-  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;
-    this.templateForm.reset({
-      templateType: TemplateType.OTHER,
-    });
-    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 }
-    );
-
-    this.templateForm.get('templateName')?.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 || [];
-    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 titleControl = this.templateForm.get(`title_${lang}`);
-      const contentControl = this.templateForm.get(`content_${lang}`);
-
-      if (titleControl && contentControl) {
-        messageData.messageTitle[lang] = titleControl.value;
-        messageData.messageContent[lang] = 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();
-      },
-      error: (err) => {
-        this.handleApiError(err, '删除');
-      },
-    });
-  }
-
-  handleCancel(): void {
-    this.isModalVisible = false;
-    this.clearDynamicControls();
-  }
-
-  private addLanguageControls(lang: string): void {
-    this.templateForm.addControl(
-      `title_${lang}`,
-      new FormControl('', Validators.required)
-    );
-    this.templateForm.addControl(
-      `content_${lang}`,
-      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_', ''));
-      }
-    });
-    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) : [];
-  }
-}

+ 70 - 31
omsapp/src/app/pages/message-template.component.ts

@@ -251,13 +251,17 @@ import {
         <!-- 新增字段 -->
         <nz-form-item>
           <nz-form-label>图片URL</nz-form-label>
-          <nz-form-control>
+          <nz-form-control [nzErrorTip]="imageErrorTpl">
             <input
               nz-input
               formControlName="image"
-              placeholder="输入图片URL(可选)"
+              placeholder="输入图片URL(可选,支持HTTP/HTTPS)"
             />
           </nz-form-control>
+          <ng-template #imageErrorTpl let-control>
+            @if (control.hasError('pattern')) {
+            请输入有效的图片URL(支持HTTP/HTTPS协议) }
+          </ng-template>
         </nz-form-item>
 
         <nz-form-item>
@@ -535,7 +539,14 @@ export class MessageTemplateComponent implements OnInit, OnDestroy {
         [Validators.required, Validators.pattern(/^[a-zA-Z0-9_-]+$/)],
       ],
       templateType: [TemplateType.OTHER, Validators.required],
-      image: [null],
+      image: [
+        null,
+        [
+          Validators.pattern(
+            /^(https?:\/\/)?([\da-z.-]+)\.([a-z.]{2,6})([\/\w .-]*)*\/?$/
+          ),
+        ],
+      ],
       bigger: [false],
       action: [null],
       param: [null],
@@ -551,7 +562,16 @@ export class MessageTemplateComponent implements OnInit, OnDestroy {
 
     // 监听筛选表单变化
     this.filterForm.valueChanges
-      .pipe(debounceTime(300), distinctUntilChanged())
+      .pipe(
+        debounceTime(300),
+        // 自定义比较逻辑,确保值真正变化时才触发
+        distinctUntilChanged((prev, curr) => {
+          return (
+            prev.templateName === curr.templateName &&
+            prev.templateType === curr.templateType
+          );
+        })
+      )
       .subscribe(() => {
         this.applyFilters();
         this.updateUrl();
@@ -592,23 +612,6 @@ export class MessageTemplateComponent implements OnInit, OnDestroy {
     return this.messageService.getActionParamPlaceholder(action);
   }
 
-  // 模板表单初始化
-  private initTemplateForm(): void {
-    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], // 现在会使用 ActionType 枚举
-      param: [null],
-      extend: [null],
-      selectedLanguages: [[], [Validators.required]],
-    });
-  }
-
   loadTemplates(): void {
     this.isLoading = true;
     this.messageService.getAllTemplates().subscribe({
@@ -684,10 +687,18 @@ export class MessageTemplateComponent implements OnInit, OnDestroy {
 
   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();
@@ -712,9 +723,15 @@ export class MessageTemplateComponent implements OnInit, OnDestroy {
       { emitEvent: false }
     );
 
-    this.templateForm.get('templateName')?.disable();
+    // 编辑时禁用模板名称
+    const templateNameControl = this.templateForm.get('templateName');
+    if (templateNameControl) {
+      templateNameControl.disable();
+    }
+
     this.clearDynamicControls();
 
+    // 加载多语言内容
     Object.keys(template.messageTitle).forEach((lang) => {
       this.templateForm.addControl(
         `title_${lang}`,
@@ -736,8 +753,10 @@ export class MessageTemplateComponent implements OnInit, OnDestroy {
   }
 
   onLanguageSelectionChange(): void {
-    const selectedLanguages =
-      this.templateForm.get('selectedLanguages')?.value || [];
+    // 统一语言代码为小写,避免大小写不一致导致的问题
+    const selectedLanguages = (
+      this.templateForm.get('selectedLanguages')?.value || []
+    ).map((lang: string) => lang.toLowerCase());
     const currentLanguages = this.getCurrentLanguageControls();
 
     // 添加新选择的语言控件
@@ -779,12 +798,13 @@ export class MessageTemplateComponent implements OnInit, OnDestroy {
     };
 
     selectedLanguages.forEach((lang: string) => {
-      const titleControl = this.templateForm.get(`title_${lang}`);
-      const contentControl = this.templateForm.get(`content_${lang}`);
+      const normalizedLang = lang.toLowerCase();
+      const titleControl = this.templateForm.get(`title_${normalizedLang}`);
+      const contentControl = this.templateForm.get(`content_${normalizedLang}`);
 
       if (titleControl && contentControl) {
-        messageData.messageTitle[lang] = titleControl.value;
-        messageData.messageContent[lang] = contentControl.value;
+        messageData.messageTitle[normalizedLang] = titleControl.value;
+        messageData.messageContent[normalizedLang] = contentControl.value;
       }
     });
 
@@ -834,6 +854,14 @@ export class MessageTemplateComponent implements OnInit, OnDestroy {
       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, '删除');
@@ -844,15 +872,26 @@ export class MessageTemplateComponent implements OnInit, OnDestroy {
   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_${lang}`,
+      `title_${normalizedLang}`,
       new FormControl('', Validators.required)
     );
     this.templateForm.addControl(
-      `content_${lang}`,
+      `content_${normalizedLang}`,
       new FormControl('', Validators.required)
     );
   }
@@ -869,7 +908,7 @@ export class MessageTemplateComponent implements OnInit, OnDestroy {
     const languages: string[] = [];
     Object.keys(this.templateForm.controls).forEach((key) => {
       if (key.startsWith('title_')) {
-        languages.push(key.replace('title_', ''));
+        languages.push(key.replace('title_', '').toLowerCase());
       }
     });
     return languages;

Някои файлове не бяха показани, защото твърде много файлове са промени