Browse Source

feat(公告表单): 重构公告表单组件为全屏模式并优化类型定义

yz 1 month ago
parent
commit
6b38e7180d

+ 47 - 10
src/components/announcement/announcement-form-mixin.js

@@ -77,14 +77,17 @@ const dataConverter = {
       title: formModel.title,
       content: formModel.content,
       categoryId: formModel.categoryId,
+      categoryName: formModel.categoryName,
       orgId: Number(formModel.orgId),
       orgCode: formModel.orgCode,
       orgName: formModel.orgName,
-      visibleRoles: this.calculateRolesMask(formModel.visibleRoles),
+      /** @type {string} */
+      visibleRoles: String(this.calculateRolesMask(formModel.visibleRoles)),
       brandScope: this.stringifyBrandScope(formModel.brandScope),
       customerBlacklist: this.stringifyCustomerBlacklist(formModel.customerBlacklist),
       remark: formModel.remark,
-      status: formModel.status
+      /** @type {import('@/api/types/announcement').AnnouncementStatus} */
+      status: /** @type {import('@/api/types/announcement').AnnouncementStatus} */ (formModel.status)
     };
   },
 
@@ -96,7 +99,9 @@ const dataConverter = {
   parseVisibleRoles(rolesMask) {
     if (!rolesMask) return [];
     const mask = typeof rolesMask === 'string' ? parseInt(rolesMask, 10) : rolesMask;
-    return parseRolesMask(mask);
+    const roleOptions = parseRolesMask(mask);
+    // 转换RoleOption[]为RoleType[]
+    return roleOptions.map(option => option.value);
   },
 
   /**
@@ -181,6 +186,7 @@ export function createInitialFormData() {
     title: '',
     content: '',
     categoryId: '',
+    categoryName: '',
     orgId: 0,
     orgCode: '',
     orgName: '',
@@ -301,11 +307,16 @@ export default {
     /**
      * 监听表单显示状态变化
      * @param {boolean} newVal 新值
-     * @this {AnnouncementFormMixinComponent & Vue}
      */
     visible: {
+      /**
+       * @this {AnnouncementFormMixinComponent & Vue}
+       * @param {boolean} newVal
+       */
       handler(newVal) {
         if (newVal) {
+          // 初始化表单配置,确保表单列正确渲染
+          this.initFormOption();
           // 初始化表单数据
           this.formData = createInitialFormData();
           // 加载分类选项
@@ -322,10 +333,13 @@ export default {
 
     /**
      * 监听编辑数据变化
-     * @param {NoticeRecord|null} newVal 新值
-     * @this {AnnouncementFormMixinComponent & Vue}
+     * @param {import('@/api/types/announcement').NoticeRecord|null} newVal 新值
      */
     editData: {
+      /**
+       * @this {AnnouncementFormMixinComponent & Vue}
+       * @param {import('@/api/types/announcement').NoticeRecord|null} newVal
+       */
       handler(newVal) {
         if (newVal && this.visible) {
           this.formData = {
@@ -349,6 +363,23 @@ export default {
       },
       deep: true,
       immediate: true
+    },
+
+    /**
+     * 监听分类ID变化,自动设置分类名称
+     */
+    'formData.categoryId': {
+      handler(newVal) {
+        if (newVal && this.categoryOptions && this.categoryOptions.length > 0) {
+          const category = this.categoryOptions.find(item => item.value === newVal);
+          if (category) {
+            this.formData.categoryName = category.name;
+          }
+        } else {
+          this.formData.categoryName = '';
+        }
+      },
+      immediate: true
     }
   },
 
@@ -533,10 +564,16 @@ export default {
           {
             prop: 'content',
             label: '公告内容',
-            type: 'textarea',
+            component: 'AvueUeditor',
+            options: {
+              action: '/api/blade-resource/oss/endpoint/put-file',
+              props: {
+                res: 'data',
+                url: 'link',
+              }
+            },
             span: 24,
-            minRows: 4,
-            maxRows: 8,
+            minRows: 6,
             rules: this.formRules.content
           },
           {
@@ -667,7 +704,7 @@ export default {
      */
     async submitAnnouncementData(submitData) {
       if (this.isEdit) {
-        return await updateAnnouncement(this.announcementId, submitData);
+        return await updateAnnouncement({ ...submitData, id: this.announcementId });
       } else {
         return await addAnnouncement(submitData);
       }

+ 1 - 0
src/components/announcement/form-option.js

@@ -270,6 +270,7 @@ export function getFormOption(options = {}) {
 
 /**
  * 表单验证规则
+ * @type {import('./types').AnnouncementFormRules}
  */
 export const formValidationRules = {
   // 标题验证

+ 3 - 2
src/components/announcement/index.js

@@ -42,11 +42,12 @@ const DataConverter = {
        orgId: Number(formData.orgId) || 0,
        orgCode: formData.orgCode,
        orgName: formData.orgName,
-       visibleRoles: 0, // 需要根据实际逻辑计算
+       visibleRoles: '0', // 需要根据实际逻辑计算
        brandScope: '',
        customerBlacklist: '',
        remark: formData.remark,
-       status: Number(formData.status) || 0
+       /** @type {import('@/api/types/announcement').AnnouncementStatus} */
+       status: /** @type {import('@/api/types/announcement').AnnouncementStatus} */ (Number(formData.status) || 0)
      };
    }
 };

+ 246 - 75
src/components/announcement/index.scss

@@ -22,49 +22,166 @@ $text-color-placeholder: #c0c4cc;
 $background-color-base: #f5f7fa;
 $background-color-light: #fafafa;
 
+// 公告表单容器样式(仿照订单表单)
+.announcement-form-container {
+  position: relative;
+  width: 100%;
+  height: calc(100vh - 200px);
+  min-height: 600px;
+  background-color: #f5f7fa;
+  display: flex;
+  flex-direction: column;
+  overflow: hidden;
+  border-radius: 6px;
+  box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
+
+  // 动画效果
+  animation: fadeIn 0.3s ease-out;
+
+  @keyframes fadeIn {
+    from {
+      opacity: 0;
+      transform: translateY(20px);
+    }
+    to {
+      opacity: 1;
+      transform: translateY(0);
+    }
+  }
+}
+
+// 表单头部导航条(仿照订单表单)
+.announcement-form-header {
+  height: 60px;
+  background-color: #ffffff;
+  border-bottom: 1px solid #e4e7ed;
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  padding: 0 24px;
+  box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
+  position: relative;
+  z-index: 10;
+
+  .header-left {
+    display: flex;
+    align-items: center;
+
+    .back-btn {
+      font-size: 16px;
+      color: #409eff;
+      margin-right: 16px;
+      padding: 8px 12px;
+      transition: all 0.3s;
+
+      &:hover {
+        background-color: rgba(64, 158, 255, 0.1);
+        color: darken(#409eff, 10%);
+      }
+
+      .el-icon-arrow-left {
+        margin-right: 4px;
+      }
+    }
+
+    .form-title {
+      font-size: 18px;
+      font-weight: 600;
+      color: #303133;
+      margin: 0;
+    }
+  }
+
+  .header-right {
+    .el-button {
+      padding: 10px 20px;
+      font-size: 14px;
+      border-radius: 6px;
+      transition: all 0.3s;
+
+      &.el-button--primary {
+        background-color: #409eff;
+        border-color: #409eff;
+
+        &:hover {
+          background-color: darken(#409eff, 10%);
+          border-color: darken(#409eff, 10%);
+        }
+      }
+    }
+  }
+}
+
+// 表单内容区域(仿照订单表单)
+.announcement-form-content {
+  flex: 1;
+  overflow-y: auto;
+  padding: 24px;
+  background-color: #ffffff;
+
+  // 自定义滚动条
+  &::-webkit-scrollbar {
+    width: 6px;
+  }
+
+  &::-webkit-scrollbar-track {
+    background: #f1f1f1;
+    border-radius: 3px;
+  }
+
+  &::-webkit-scrollbar-thumb {
+    background: #c1c1c1;
+    border-radius: 3px;
+
+    &:hover {
+      background: #a8a8a8;
+    }
+  }
+}
+
 // 公告表单主容器
 .announcement-form {
   font-family: 'Helvetica Neue', Helvetica, 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', '微软雅黑', Arial, sans-serif;
-  
+
   // 对话框样式
   .announcement-form-dialog {
     .el-dialog {
       border-radius: 8px;
       box-shadow: 0 8px 32px rgba(0, 0, 0, 0.12);
-      
+
       .el-dialog__header {
         background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
         border-radius: 8px 8px 0 0;
         padding: 20px 24px;
         border-bottom: 1px solid $border-color-light;
-        
+
         .el-dialog__title {
           font-size: 18px;
           font-weight: 600;
           color: $text-color-primary;
           line-height: 1.4;
         }
-        
+
         .el-dialog__headerbtn {
           top: 20px;
           right: 20px;
-          
+
           .el-dialog__close {
             color: $text-color-secondary;
             font-size: 18px;
-            
+
             &:hover {
               color: $primary-color;
             }
           }
         }
       }
-      
+
       .el-dialog__body {
         padding: 24px;
         background-color: #fff;
       }
-      
+
       .el-dialog__footer {
         padding: 16px 24px 24px;
         background-color: $background-color-light;
@@ -72,42 +189,42 @@ $background-color-light: #fafafa;
         border-radius: 0 0 8px 8px;
       }
     }
-    
+
     // 表单容器
     .form-container {
       max-height: 70vh;
       overflow-y: auto;
-      
+
       // 自定义滚动条
       &::-webkit-scrollbar {
         width: 6px;
       }
-      
+
       &::-webkit-scrollbar-track {
         background: $background-color-base;
         border-radius: 3px;
       }
-      
+
       &::-webkit-scrollbar-thumb {
         background: $border-color-base;
         border-radius: 3px;
-        
+
         &:hover {
           background: $text-color-placeholder;
         }
       }
     }
-    
+
     // 富文本编辑器容器
     .rich-editor-container {
       width: 100%;
-      
+
       .el-textarea {
         .el-textarea__inner {
           border-radius: 6px;
           border: 1px solid $border-color-base;
           transition: border-color 0.2s ease;
-          
+
           &:focus {
             border-color: $primary-color;
             box-shadow: 0 0 0 2px rgba(64, 158, 255, 0.2);
@@ -115,37 +232,37 @@ $background-color-light: #fafafa;
         }
       }
     }
-    
+
     // 对话框底部按钮
     .dialog-footer {
       text-align: right;
-      
+
       .el-button {
         margin-left: 12px;
         padding: 10px 20px;
         border-radius: 6px;
         font-weight: 500;
         transition: all 0.2s ease;
-        
+
         &:first-child {
           margin-left: 0;
         }
-        
+
         &.el-button--primary {
           background: linear-gradient(135deg, $primary-color 0%, #66b1ff 100%);
           border: none;
-          
+
           &:hover {
             background: linear-gradient(135deg, #66b1ff 0%, $primary-color 100%);
             transform: translateY(-1px);
             box-shadow: 0 4px 12px rgba(64, 158, 255, 0.3);
           }
-          
+
           &:active {
             transform: translateY(0);
           }
         }
-        
+
         &.el-button--default {
           &:hover {
             color: $primary-color;
@@ -162,63 +279,63 @@ $background-color-light: #fafafa;
 .avue-form {
   .el-form-item {
     margin-bottom: 24px;
-    
+
     .el-form-item__label {
       font-weight: 500;
       color: $text-color-primary;
       line-height: 1.6;
       padding-bottom: 8px;
-      
+
       &::before {
         margin-right: 4px;
       }
     }
-    
+
     .el-form-item__content {
       line-height: 1.6;
     }
   }
-  
+
   // 输入框样式
   .el-input {
     .el-input__inner {
       border-radius: 6px;
       border: 1px solid $border-color-base;
       transition: all 0.2s ease;
-      
+
       &:focus {
         border-color: $primary-color;
         box-shadow: 0 0 0 2px rgba(64, 158, 255, 0.2);
       }
-      
+
       &:hover {
         border-color: $text-color-placeholder;
       }
     }
   }
-  
+
   // 选择器样式
   .el-select {
     width: 100%;
-    
+
     .el-input__inner {
       border-radius: 6px;
     }
-    
+
     .el-select__tags {
       max-height: 120px;
       overflow-y: auto;
-      
+
       .el-tag {
         margin: 2px 4px 2px 0;
         border-radius: 4px;
         background-color: rgba(64, 158, 255, 0.1);
         border-color: rgba(64, 158, 255, 0.2);
         color: $primary-color;
-        
+
         .el-tag__close {
           color: $primary-color;
-          
+
           &:hover {
             background-color: $primary-color;
             color: #fff;
@@ -227,22 +344,22 @@ $background-color-light: #fafafa;
       }
     }
   }
-  
+
   // 多选框组样式
   .el-checkbox-group {
     display: flex;
     flex-wrap: wrap;
     gap: 12px;
-    
+
     .el-checkbox {
       margin-right: 0;
       margin-bottom: 8px;
-      
+
       .el-checkbox__label {
         font-weight: 400;
         color: $text-color-regular;
       }
-      
+
       &.is-checked {
         .el-checkbox__label {
           color: $primary-color;
@@ -251,7 +368,7 @@ $background-color-light: #fafafa;
       }
     }
   }
-  
+
   // 文本域样式
   .el-textarea {
     .el-textarea__inner {
@@ -259,22 +376,22 @@ $background-color-light: #fafafa;
       border: 1px solid $border-color-base;
       transition: all 0.2s ease;
       font-family: inherit;
-      
+
       &:focus {
         border-color: $primary-color;
         box-shadow: 0 0 0 2px rgba(64, 158, 255, 0.2);
       }
-      
+
       &:hover {
         border-color: $text-color-placeholder;
       }
     }
   }
-  
+
   // 数字输入框样式
   .el-input-number {
     width: 100%;
-    
+
     .el-input__inner {
       text-align: left;
     }
@@ -285,14 +402,14 @@ $background-color-light: #fafafa;
 .el-select-dropdown {
   border-radius: 6px;
   box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1);
-  
+
   .el-select-dropdown__item {
     padding: 8px 16px;
-    
+
     &:hover {
       background-color: rgba(64, 158, 255, 0.05);
     }
-    
+
     &.selected {
       background-color: rgba(64, 158, 255, 0.1);
       color: $primary-color;
@@ -301,8 +418,14 @@ $background-color-light: #fafafa;
   }
 }
 
-// 响应式设计
+// 响应式设计(仿照订单表单)
 @media (max-width: 1200px) {
+  .announcement-form-container {
+    .announcement-form-content {
+      padding: 16px;
+    }
+  }
+
   .announcement-form {
     .announcement-form-dialog {
       .el-dialog {
@@ -314,33 +437,54 @@ $background-color-light: #fafafa;
 }
 
 @media (max-width: 768px) {
+  .announcement-form-header {
+    padding: 0 16px;
+
+    .header-left {
+      .form-title {
+        font-size: 16px;
+      }
+    }
+
+    .header-right {
+      .el-button {
+        padding: 8px 16px;
+        font-size: 13px;
+      }
+    }
+  }
+
+  .announcement-form-content {
+    padding: 16px;
+  }
+
   .announcement-form {
     .announcement-form-dialog {
       .el-dialog {
         width: 95% !important;
         margin: 0 auto;
-        
+
         .el-dialog__header {
           padding: 16px 20px;
-          
+
           .el-dialog__title {
             font-size: 16px;
           }
         }
-        
+
         .el-dialog__body {
           padding: 16px 20px;
         }
-        
+
         .el-dialog__footer {
           padding: 12px 20px 20px;
         }
       }
-      
+
       .form-container {
         max-height: 60vh;
       }
-      
+
       .dialog-footer {
         .el-button {
           margin-left: 8px;
@@ -349,23 +493,23 @@ $background-color-light: #fafafa;
         }
       }
     }
-    
+
     .avue-form {
       .el-form-item {
         margin-bottom: 20px;
-        
+
         .el-form-item__label {
           font-size: 14px;
           padding-bottom: 6px;
         }
       }
-      
+
       .el-checkbox-group {
         gap: 8px;
-        
+
         .el-checkbox {
           margin-bottom: 6px;
-          
+
           .el-checkbox__label {
             font-size: 14px;
           }
@@ -376,39 +520,66 @@ $background-color-light: #fafafa;
 }
 
 @media (max-width: 480px) {
+  .announcement-form-header {
+    height: 50px;
+
+    .header-left {
+      .back-btn {
+        padding: 6px 8px;
+        font-size: 14px;
+        margin-right: 8px;
+      }
+
+      .form-title {
+        font-size: 14px;
+      }
+    }
+
+    .header-right {
+      .el-button {
+        padding: 6px 12px;
+        font-size: 12px;
+      }
+    }
+  }
+
+  .announcement-form-content {
+    padding: 12px;
+  }
+
   .announcement-form {
     .announcement-form-dialog {
       .el-dialog {
         width: 98% !important;
-        
+
         .el-dialog__header {
           padding: 12px 16px;
-          
+
           .el-dialog__title {
             font-size: 15px;
           }
         }
-        
+
         .el-dialog__body {
           padding: 12px 16px;
         }
-        
+
         .el-dialog__footer {
           padding: 8px 16px 16px;
         }
       }
-      
+
       .form-container {
         max-height: 55vh;
       }
-      
+
       .dialog-footer {
         text-align: center;
-        
+
         .el-button {
           margin: 4px;
           width: calc(50% - 8px);
-          
+
           &:first-child {
             margin-left: 4px;
           }
@@ -421,7 +592,7 @@ $background-color-light: #fafafa;
 // 加载状态样式
 .el-loading-mask {
   border-radius: 6px;
-  
+
   .el-loading-spinner {
     .el-loading-text {
       color: $text-color-regular;
@@ -435,7 +606,7 @@ $background-color-light: #fafafa;
   .el-input__inner,
   .el-textarea__inner {
     border-color: $danger-color;
-    
+
     &:focus {
       box-shadow: 0 0 0 2px rgba(245, 108, 108, 0.2);
     }
@@ -447,7 +618,7 @@ $background-color-light: #fafafa;
   .el-input__inner,
   .el-textarea__inner {
     border-color: $success-color;
-    
+
     &:focus {
       box-shadow: 0 0 0 2px rgba(103, 194, 58, 0.2);
     }
@@ -460,20 +631,20 @@ $background-color-light: #fafafa;
     .announcement-form-dialog {
       .el-dialog {
         background-color: #2d3748;
-        
+
         .el-dialog__header {
           background: linear-gradient(135deg, #2d3748 0%, #4a5568 100%);
           border-bottom-color: #4a5568;
-          
+
           .el-dialog__title {
             color: #e2e8f0;
           }
         }
-        
+
         .el-dialog__body {
           background-color: #2d3748;
         }
-        
+
         .el-dialog__footer {
           background-color: #1a202c;
           border-top-color: #4a5568;
@@ -481,4 +652,4 @@ $background-color-light: #fafafa;
       }
     }
   }
-}
+}

+ 156 - 198
src/components/announcement/index.vue

@@ -1,113 +1,97 @@
 <template>
-  <div class="announcement-form">
-    <!-- 表单对话框 -->
-    <el-dialog
-      :title="formTitle"
-      :visible.sync="dialogVisible"
-      :close-on-click-modal="false"
-      :close-on-press-escape="false"
-      width="80%"
-      top="5vh"
-      class="announcement-form-dialog"
-      @close="handleCancel"
-    >
-      <!-- 表单内容 -->
-      <div v-loading="formLoading" class="form-container">
-        <avue-form
-          ref="form"
-          v-model="formData"
-          :option="formOption"
-          :rules="formRules"
-          @submit="handleSubmit"
-          @reset-change="handleReset"
+  <div v-if="visible" class="announcement-form announcement-form-container basic-container">
+    <!-- 顶部栏:返回与保存 -->
+    <div class="announcement-form-header">
+      <div class="header-left">
+        <el-button
+          type="text"
+          icon="el-icon-arrow-left"
+          size="small"
+          class="back-btn"
+          @click="handleBack"
         >
-          <!-- 自定义插槽:品牌范围 -->
-          <template #brandScope="{ value, column }">
-            <el-select
-              v-model="formData.brandScope"
-              multiple
-              filterable
-              remote
-              reserve-keyword
-              placeholder="请搜索并选择品牌"
-              :remote-method="remoteSearchBrands"
-              :loading="brandLoading"
-              style="width: 100%"
-              @change="handleBrandScopeChange"
-              @clear="clearBrandOptions"
-            >
-              <el-option
-                v-for="item in brandOptions"
-                :key="item.value"
-                :label="item.label"
-                :value="item.value"
-              />
-            </el-select>
-          </template>
-
-          <!-- 自定义插槽:客户黑名单 -->
-          <template #customerBlacklist="{ value, column }">
-            <el-select
-              v-model="formData.customerBlacklist"
-              multiple
-              filterable
-              remote
-              reserve-keyword
-              placeholder="请搜索并选择客户"
-              :remote-method="remoteSearchCustomers"
-              :loading="customerLoading"
-              style="width: 100%"
-              @change="handleCustomerBlacklistChange"
-              @clear="clearCustomerOptions"
-            >
-              <el-option
-                v-for="item in customerOptions"
-                :key="item.value"
-                :label="item.label"
-                :value="item.value"
-              />
-            </el-select>
-          </template>
-
-          <!-- 自定义插槽:富文本编辑器 -->
-          <template #content="{ value, column }">
-            <div class="rich-editor-container">
-              <el-input
-                v-model="formData.content"
-                type="textarea"
-                :rows="6"
-                placeholder="请输入公告内容"
-                maxlength="5000"
-                show-word-limit
-              />
-            </div>
-          </template>
-        </avue-form>
-      </div>
-
-      <!-- 对话框底部按钮 -->
-      <div slot="footer" class="dialog-footer">
-        <el-button @click="handleCancel">
-          取消
-        </el-button>
-        <el-button @click="handleReset">
-          重置
+          返回列表
         </el-button>
+        <span class="form-title">{{ formTitle }}</span>
+      </div>
+      <div class="header-right">
         <el-button
           type="primary"
+          icon="el-icon-check"
+          size="small"
           :loading="saveLoading"
           :disabled="!canEdit"
-          @click="handleSubmit"
+          @click="handleSave"
         >
-          {{ isEdit ? '更新' : '保存' }}
+          保存
         </el-button>
       </div>
-    </el-dialog>
+    </div>
+
+    <!-- 表单内容区域 -->
+    <div class="announcement-form-content">
+      <avue-form
+        ref="form"
+        v-model="formData"
+        :option="formOption"
+        :rules="formRules"
+        @submit="handleSubmit"
+        @reset-change="handleReset"
+      >
+        <!-- 自定义插槽:品牌范围 -->
+        <template #brandScope="{ value, column }">
+          <el-select
+            v-model="formData.brandScope"
+            multiple
+            filterable
+            remote
+            reserve-keyword
+            placeholder="请搜索并选择品牌"
+            :remote-method="remoteSearchBrands"
+            :loading="brandLoading"
+            style="width: 100%"
+            @change="handleBrandScopeChange"
+            @clear="clearBrandOptions"
+          >
+            <el-option
+              v-for="item in brandOptions"
+              :key="item.value"
+              :label="item.label"
+              :value="item.value"
+            />
+          </el-select>
+        </template>
+
+        <!-- 自定义插槽:客户黑名单 -->
+        <template #customerBlacklist="{ value, column }">
+          <el-select
+            v-model="formData.customerBlacklist"
+            multiple
+            filterable
+            remote
+            reserve-keyword
+            placeholder="请搜索并选择客户"
+            :remote-method="remoteSearchCustomers"
+            :loading="customerLoading"
+            style="width: 100%"
+            @change="handleCustomerBlacklistChange"
+            @clear="clearCustomerOptions"
+          >
+            <el-option
+              v-for="item in customerOptions"
+              :key="item.value"
+              :label="item.label"
+              :value="item.value"
+            />
+          </el-select>
+        </template>
+      </avue-form>
+    </div>
   </div>
 </template>
 
 <script>
-import announcementFormMixin from './announcement-form-mixin';
+import announcementFormMixin from './announcement-form-mixin.js';
 
 /**
  * 公告表单组件
@@ -115,11 +99,11 @@ import announcementFormMixin from './announcement-form-mixin';
  */
 export default {
   name: 'AnnouncementForm',
-  
+
   mixins: [announcementFormMixin],
 
   props: {
-    /** 是否显示表单对话框 */
+    /** 是否显示表单 */
     visible: {
       type: Boolean,
       default: false
@@ -146,120 +130,58 @@ export default {
     }
   },
 
-  data() {
-    return {
-      /** 对话框显示状态(内部控制) */
-      dialogVisible: false
-    };
-  },
-
   watch: {
     visible: {
-      handler(newVal) {
-        this.dialogVisible = newVal;
+      handler(newVal, oldVal) {
+        if (newVal && !oldVal) {
+          // 初始化表单选项与数据
+          this.initFormOption();
+          this.initFormData();
+        }
       },
       immediate: true
     },
-
-    dialogVisible(newVal) {
-      if (!newVal) {
-        this.$emit('update:visible', false);
-      }
+    editData: {
+      handler(newVal) {
+        if (newVal && this.visible) {
+          this.formData = this.apiToFormModel(newVal);
+        }
+      },
+      deep: true
     }
   },
 
   methods: {
-    /**
-     * 处理品牌范围变化
-     * @param {Array} selectedValues 选中的品牌ID数组
-     */
-    handleBrandScopeChange(selectedValues) {
-      // 根据选中的值更新formData中的brandScope
-      const selectedBrands = this.brandOptions.filter(option => 
-        selectedValues.includes(option.value)
-      );
-      
-      this.formData.brandScope = selectedBrands.map(brand => ({
-        id: brand.value,
-        name: brand.name || brand.label.split(' (')[0],
-        code: brand.code || brand.label.split('(')[1]?.replace(')', '') || ''
-      }));
-    },
-
-    /**
-     * 处理客户黑名单变化
-     * @param {Array} selectedValues 选中的客户ID数组
-     */
-    handleCustomerBlacklistChange(selectedValues) {
-      // 根据选中的值更新formData中的customerBlacklist
-      const selectedCustomers = this.customerOptions.filter(option => 
-        selectedValues.includes(option.value)
-      );
-      
-      this.formData.customerBlacklist = selectedCustomers.map(customer => ({
-        id: customer.value,
-        Customer_NAME: customer.Customer_NAME || customer.label.split(' (')[0],
-        Customer_CODE: customer.Customer_CODE || customer.label.split('(')[1]?.replace(')', '') || ''
-      }));
+    // 返回列表
+    handleBack() {
+      this.$emit('back');
+      this.$emit('update:visible', false);
     },
-
-    /**
-     * 处理表单提交
-     */
-    async handleSubmit() {
-      try {
-        // 调用混入中的提交方法
-        await this.$parent.handleSubmit.call(this);
-      } catch (error) {
-        console.error('表单提交失败:', error);
-      }
+    // 顶栏保存按钮
+    async handleSave() {
+      await this.handleSubmit();
     },
-
-    /**
-     * 处理表单重置
-     */
+    // 表单重置
     handleReset() {
-      // 调用混入中的重置方法
-      this.$parent.handleReset.call(this);
-      
-      // 重置表单引用
-      if (this.$refs.form) {
+      // 直接调用混入中的重置
+      this.$options.mixins[0].methods.handleReset.call(this);
+      if (this.$refs.form && typeof this.$refs.form.resetFields === 'function') {
         this.$refs.form.resetFields();
       }
-    },
-
-    /**
-     * 处理取消操作
-     */
-    handleCancel() {
-      // 调用混入中的取消方法
-      this.$parent.handleCancel.call(this);
-      
-      // 关闭对话框
-      this.dialogVisible = false;
-    },
-
-    /**
-     * 验证表单
-     * @returns {Promise<boolean>} 验证结果
-     */
-    async validateForm() {
-      try {
-        if (this.$refs.form) {
-          await this.$refs.form.validate();
-          return true;
-        }
-        return false;
-      } catch (error) {
-        console.warn('表单验证失败:', error);
-        return false;
-      }
     }
   }
 };
 </script>
 
 <style lang="scss" scoped>
+
+ .el-card__body {
+    padding-top: 0;
+ }
+ .announcement-form-header {
+    background-color: #fff;
+    border: 0;
+ }
 .announcement-form {
   .announcement-form-dialog {
     .form-container {
@@ -270,7 +192,7 @@ export default {
 
     .rich-editor-container {
       width: 100%;
-      
+
       .el-textarea {
         width: 100%;
       }
@@ -280,7 +202,7 @@ export default {
       text-align: right;
       padding: 20px 0 0;
       border-top: 1px solid #e4e7ed;
-      
+
       .el-button {
         margin-left: 10px;
       }
@@ -309,7 +231,7 @@ export default {
       display: flex;
       flex-wrap: wrap;
       gap: 10px;
-      
+
       .el-checkbox {
         margin-right: 0;
         margin-bottom: 8px;
@@ -330,7 +252,7 @@ export default {
     .announcement-form-dialog {
       width: 95% !important;
       margin: 0 auto;
-      
+
       .form-container {
         padding: 0 10px;
         max-height: 60vh;
@@ -344,7 +266,7 @@ export default {
   .el-dialog__header {
     background-color: #f5f7fa;
     border-bottom: 1px solid #e4e7ed;
-    
+
     .el-dialog__title {
       font-size: 18px;
       font-weight: 600;
@@ -362,4 +284,40 @@ export default {
     border-top: 1px solid #e4e7ed;
   }
 }
-</style>
+
+// 新增:整页公告表单顶部与内容区域样式
+.announcement-form-container {
+  display: flex;
+  flex-direction: column;
+  gap: 12px;
+
+  .announcement-form-header {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    padding: 12px 16px;
+    background-color: #f5f7fa;
+    border: 1px solid #e4e7ed;
+    border-radius: 6px;
+
+    .header-left {
+      display: flex;
+      align-items: center;
+      gap: 8px;
+
+      .form-title {
+        font-size: 16px;
+        font-weight: 600;
+        color: #303133;
+      }
+    }
+  }
+
+  .announcement-form-content {
+    background: #fff;
+    border: 1px solid #ebeef5;
+    border-radius: 6px;
+    padding: 16px;
+  }
+}
+</style>

+ 14 - 0
src/views/announcement/index.vue

@@ -1,6 +1,16 @@
 <template>
     <basic-container>
+        <!-- 公告表单组件 -->
+        <announcement-form
+            :visible.sync="announcementFormVisible"
+            :is-edit="isEditMode"
+            :announcement-id="editAnnouncementId"
+            @save-success="handleFormSaveSuccess"
+        />
+        
+        <!-- 公告列表 -->
         <avue-crud
+            v-if="!announcementFormVisible"
             :option="option"
             :data="data"
             ref="crud"
@@ -122,6 +132,7 @@
 
 <script>
 import AnnouncementIndexMixin from './mixins/announcementIndex.js';
+import AnnouncementForm from '@/components/announcement/index.vue';
 
 /**
  * 公告管理组件
@@ -129,6 +140,9 @@ import AnnouncementIndexMixin from './mixins/announcementIndex.js';
  */
 export default {
     name: 'NoticeIndex',
+    components: {
+        AnnouncementForm
+    },
     mixins: [AnnouncementIndexMixin]
 };
 </script>

+ 55 - 174
src/views/announcement/mixins/announcementIndex.js

@@ -159,6 +159,12 @@ export default {
             currentCustomerBlacklist: [],
             /** @type {boolean} 客户选项加载状态 */
             customerOptionsLoading: false,
+            /** @type {boolean} 公告表单组件显示状态 */
+            announcementFormVisible: false,
+            /** @type {boolean} 是否为编辑模式 */
+            isEditMode: false,
+            /** @type {string|null} 编辑的公告ID */
+            editAnnouncementId: null,
             /** @type {TableOption} 表格配置选项 */
             option: {
                 height: 'auto',
@@ -191,16 +197,17 @@ export default {
                     },
                     {
                         label: "分类",
-                        prop: "categoryId",
+                        prop: "categoryName",
                         type: "select",
                         dicData: [],
                         props: {
                             label: "name",
-                            value: "id"
+                            value: "name"
                         },
                         slot: true,
                         search: true,
                         span: 12,
+                        searchProp: "categoryId",
                         rules: [{
                             required: true,
                             message: "请选择分类",
@@ -408,7 +415,7 @@ export default {
                     .map(item => ({
                         id: item.id,
                         name: item.name,
-                        value: item.id,
+                        value: item.name,
                         label: item.name,
                         orgId: item.orgId,
                         orgName: item.orgName,
@@ -417,7 +424,7 @@ export default {
                     .sort((a, b) => a.sortOrder - b.sortOrder);
 
                 // 更新表格列配置中的字典数据
-                const categoryColumn = this.option.column.find((/** @type {TableColumn} */ col) => col.prop === 'categoryId');
+                const categoryColumn = this.option.column.find((/** @type {TableColumn} */ col) => col.prop === 'categoryName');
                 if (categoryColumn) {
                     categoryColumn.dicData = this.categoryOptions;
                 }
@@ -428,11 +435,11 @@ export default {
                 // 使用默认分类选项
                 /** @type {Array<CategoryOption>} */
                 this.categoryOptions = [
-                    { id: 1, name: '系统公告', value: 1, label: '系统公告', sortOrder: 0 },
-                    { id: 2, name: '部门公告', value: 2, label: '部门公告', sortOrder: 1 }
+                    { id: 1, name: '系统公告', value: '系统公告', label: '系统公告', sortOrder: 0 },
+                    { id: 2, name: '部门公告', value: '部门公告', label: '部门公告', sortOrder: 1 }
                 ];
 
-                const categoryColumn = this.option.column.find((/** @type {TableColumn} */ col) => col.prop === 'categoryId');
+                const categoryColumn = this.option.column.find((/** @type {TableColumn} */ col) => col.prop === 'categoryName');
                 if (categoryColumn) {
                     categoryColumn.dicData = this.categoryOptions;
                 }
@@ -474,76 +481,9 @@ export default {
          * @throws {Error} 当保存失败时抛出错误
          */
         async rowSave(row, done, loading) {
-            try {
-                // 参数验证
-                if (!row.title?.trim()) {
-                    this.$message.error('公告标题不能为空');
-                    loading();
-                    return;
-                }
-
-                if (!row.content?.trim()) {
-                    this.$message.error('公告内容不能为空');
-                    loading();
-                    return;
-                }
-
-                // 计算角色掩码值
-                /** @type {VisibleRolesMask} */
-                let rolesMask;
-                if (Array.isArray(row.visibleRoles)) {
-                    // 如果是数组,需要检查是RoleType[]还是RoleOption[]
-                    const roleValues = row.visibleRoles.map(role =>
-                        typeof role === 'object' ? role.value : role
-                    );
-                    rolesMask = this.calculateRolesMask(roleValues);
-                } else {
-                    rolesMask = row.visibleRoles ? parseInt(row.visibleRoles) : 0;
-                }
-
-                // 处理客户黑名单数据
-                const filteredCustomerBlacklist = this.currentCustomerBlacklist.filter((/** @type {CustomerBlacklistOption} */ customer) => {
-                    const selectedIds = (row.customerBlacklist || []).map((/** @type {CustomerBlacklistOption} */ item) => item.id);
-                    return selectedIds.includes(customer.id);
-                });
-
-                // 构建表单数据
-                /** @type {import('@/api/announcement').NoticeFormData} */
-                const formData = {
-                    ...row,
-                    orgId: this.userInfo?.orgId || row.orgId,
-                    orgCode: this.userInfo?.orgCode || row.orgCode,
-                    orgName: this.userInfo?.orgName || row.orgName,
-                    brandScope: row.brandScope || undefined,
-                    customerBlacklist: JSON.stringify(filteredCustomerBlacklist.map(customer => ({
-                        ...customer,
-                        ID: customer.id,
-                        CODE: customer.Customer_CODE,
-                        NAME: customer.Customer_NAME
-                    }))),
-                    remark: row.remark || '',
-                    visibleRoles: rolesMask.toString(),
-                    status: row.status !== undefined ? row.status : ANNOUNCEMENT_STATUS.DRAFT,
-                };
-
-                // 设置分类名称
-                const selectedCategory = this.categoryOptions.find((/** @type {CategoryOption} */ cat) => String(cat.id) == row.categoryId);
-                if (selectedCategory) {
-                    formData.categoryName = selectedCategory.name;
-                }
-
-                await add(formData);
-                await this.onLoad(this.page);
-                this.$message({
-                    type: "success",
-                    message: "新增公告成功!"
-                });
-                done();
-            } catch (error) {
-                console.error('保存失败:', error);
-                this.$message.error(`保存失败: ${error.message || '未知错误'}`);
-                loading();
-            }
+            // 新增操作现在由表单组件处理,此方法已废弃
+            console.warn('rowSave方法已废弃,请使用表单组件进行新增操作');
+            done();
         },
 
         /**
@@ -558,86 +498,9 @@ export default {
          * @throws {Error} 当更新失败时抛出错误
          */
         async rowUpdate(row, index, done, loading) {
-            try {
-                // 参数验证
-                if (!row.id) {
-                    this.$message.error('缺少公告ID,无法更新');
-                    loading();
-                    return;
-                }
-
-                if (!row.title?.trim()) {
-                    this.$message.error('公告标题不能为空');
-                    loading();
-                    return;
-                }
-
-                if (!row.content?.trim()) {
-                    this.$message.error('公告内容不能为空');
-                    loading();
-                    return;
-                }
-
-                // 计算角色掩码值
-                /** @type {VisibleRolesMask} */
-                let rolesMask;
-                if (Array.isArray(row.visibleRoles)) {
-                    // 如果是数组,需要检查是RoleType[]还是RoleOption[]
-                    const roleValues = row.visibleRoles.map(role =>
-                        typeof role === 'object' ? role.value : role
-                    );
-                    rolesMask = this.calculateRolesMask(roleValues);
-                } else {
-                    rolesMask = row.visibleRoles;
-                }
-
-                // 设置分类名称
-                const selectedCategory = this.categoryOptions.find((/** @type {CategoryOption} */ cat) => String(cat.id) == row.categoryId);
-                if (selectedCategory) {
-                    row.categoryName = selectedCategory.name;
-                }
-
-                // 处理客户黑名单数据
-
-                console.log('aaa', this.currentCustomerBlacklist)
-                console.log('aaa', row.customerBlacklist)
-                const filteredCustomerBlacklist = this.currentCustomerBlacklist.filter((/** @type {CustomerBlacklistOption} */ customer) => {
-                    const selectedIds = (row.customerBlacklist || []).map((/** @type {CustomerBlacklistOption} */ item) => item.id);
-                    return selectedIds.includes(customer.id);
-                });
-                const uniqFilterCustomerBlacklist = filteredCustomerBlacklist.filter((item, index, self) =>
-                    index === self.findIndex((t) => (
-                        t.id === item.id
-                    ))
-                );
-
-                // 构建更新数据
-                /** @type {import('@/api/announcement').NoticeFormData} */
-                const formData = {
-                    ...row,
-                    brandScope: row.brandScope || undefined,
-                    customerBlacklist: JSON.stringify(uniqFilterCustomerBlacklist.map(customer => ({
-                        ID: customer.id,
-                        CODE: customer.Customer_CODE,
-                        NAME: customer.Customer_NAME
-                    }))),
-                    remark: row.remark || '',
-                    visibleRoles: rolesMask.toString()
-                };
-
-                console.log(formData)
-                await update(formData);
-                await this.onLoad(this.page);
-                this.$message({
-                    type: "success",
-                    message: "更新公告成功!"
-                });
-                done();
-            } catch (error) {
-                console.error('更新失败:', error);
-                this.$message.error(`更新失败: ${error.message || '未知错误'}`);
-                loading();
-            }
+            // 编辑操作现在由表单组件处理,此方法已废弃
+            console.warn('rowUpdate方法已废弃,请使用表单组件进行编辑操作');
+            done();
         },
 
         /**
@@ -713,7 +576,23 @@ export default {
          * @throws {Error} 当获取详情失败时抛出错误
          */
         async beforeOpen(done, type) {
-            if (["edit", "view"].includes(type)) {
+            // 对于新增和编辑操作,使用表单组件
+            if (type === "add") {
+                this.isEditMode = false;
+                this.editAnnouncementId = null;
+                this.announcementFormVisible = true;
+                done();
+                return;
+            } else if (type === "edit") {
+                this.isEditMode = true;
+                this.editAnnouncementId = this.form?.id || null;
+                this.announcementFormVisible = true;
+                done();
+                return;
+            }
+            
+            // 对于查看操作,保持原有逻辑
+            if (type === "view") {
                 try {
                     if (!this.form?.id) {
                         this.$message.error('缺少公告ID,无法获取详情');
@@ -775,21 +654,6 @@ export default {
                     console.error('获取详情失败:', error);
                     this.$message.error('获取详情失败,请稍后重试');
                 }
-            } else if (type === "add") {
-                // 新增时设置默认值
-                this.form = {
-                    orgId: this.userInfo?.orgId || '',
-                    orgCode: this.userInfo?.orgCode || '',
-                    orgName: this.userInfo?.orgName || '',
-                    visibleRoles: [ROLE_TYPES.DEALER], // 默认经销商
-                    brandScope: [],
-                    customerBlacklist: [],
-                    remark: '',
-                    status: ANNOUNCEMENT_STATUS.DRAFT
-                };
-                // 清空客户选项
-                /** @type {Array<CustomerBlacklistOption>} */
-                this.customerBlacklistOptions = [];
             }
             done();
         },
@@ -927,9 +791,26 @@ export default {
          */
         clearCustomerOptions() {
             this.customerBlacklistOptions = [];
+        },
 
-            this.currentCustomerBlacklist = [];
+        /**
+         * 处理表单组件返回事件
+         */
+        handleFormBack() {
+            this.announcementFormVisible = false;
+            this.isEditMode = false;
+            this.editAnnouncementId = null;
         },
 
+        /**
+         * 处理表单保存成功事件
+         */
+        handleFormSaveSuccess() {
+            this.announcementFormVisible = false;
+            this.isEditMode = false;
+            this.editAnnouncementId = null;
+            // 刷新列表数据
+            this.onLoad(this.page);
+        }
     }
 };