Browse Source

feat(forecast-summary): 添加按年月导出销售预测汇总功能

yz 1 month ago
parent
commit
85e39d6a41

+ 31 - 0
src/views/forecast-summary/index.vue

@@ -82,6 +82,37 @@
         </div>
       </template>
     </avue-crud>
+
+    <!-- 导出选择层(按年月导出) -->
+    <el-dialog
+      title="按年月导出"
+      :visible.sync="exportDialogVisible"
+      width="420px"
+      :close-on-click-modal="false"
+      append-to-body
+    >
+      <el-form :model="exportForm" :rules="exportFormRules" ref="exportFormRef" label-width="80px">
+        <el-form-item label="年份" prop="year">
+          <el-date-picker
+            v-model="exportForm.year"
+            type="year"
+            placeholder="请选择年份"
+            value-format="yyyy"
+            format="yyyy"
+            style="width: 100%;"
+          />
+        </el-form-item>
+        <el-form-item label="月份" prop="month">
+          <el-select v-model="exportForm.month" placeholder="请选择月份" style="width: 100%;">
+            <el-option v-for="m in monthOptions" :key="m.value" :label="m.label" :value="m.value" />
+          </el-select>
+        </el-form-item>
+      </el-form>
+      <span slot="footer" class="dialog-footer">
+        <el-button @click="cancelExportDialog">取 消</el-button>
+        <el-button type="primary" :loading="exportLoading" @click="confirmExportByMonth">确 定</el-button>
+      </span>
+    </el-dialog>
   </basic-container>
 </template>
 

+ 70 - 34
src/views/forecast-summary/summaryIndex.js

@@ -7,7 +7,7 @@
  * @typedef {import('./types').ForecastSummaryComponent} ForecastSummaryComponent
  */
 
-import { getSalesForecastMainList, getForecastSummaryDetail, exportSalesForecastTemplate } from '@/api/forecast/forecast-summary'
+import { getSalesForecastMainList, getForecastSummaryDetail, exportSalesForecastTemplate, exportSalesForecastByMonth } from '@/api/forecast/forecast-summary'
 import {
   APPROVAL_STATUS,
   APPROVAL_STATUS_CONFIG,
@@ -199,7 +199,25 @@ import { mapGetters } from 'vuex'
            { label: '品牌名称', prop: 'brandName', minWidth: 120, overHidden: true },
            { label: '预测数量', prop: 'forecastQuantity', minWidth: 120, align: 'right', slot: true }
          ]
-       }
+       },
+
+       // —— 导出选择层 ——
+       /** 弹层可见性 */
+       exportDialogVisible: false,
+       /** 导出表单 */
+       exportForm: {
+         /** @type {string|number|null} */ year: new Date().getFullYear().toString(),
+         /** @type {number|null} */ month: new Date().getMonth() + 1
+       },
+       /** 导出中加载状态 */
+       exportLoading: false,
+       /** 导出表单校验规则 */
+       exportFormRules: {
+         year: [ { required: true, message: '请选择年份', trigger: 'change' } ],
+         month: [ { required: true, message: '请选择月份', trigger: 'change' } ]
+       },
+       /** 月份选项(用于模板渲染) */
+       monthOptions: MONTH_OPTIONS
      }
    },
 
@@ -284,48 +302,56 @@ import { mapGetters } from 'vuex'
      },
 
      /**
-      * 导出“销售预测汇总”模板(使用当前查询条件)
+      * 打开导出选择层(选择年份与月份)
+      * 保持方法名以兼容现有模板绑定
+      * @this {ForecastSummaryComponent & Vue}
+      * @returns {void}
+      */
+     onExportTemplate() {
+       // 以当前查询条件作为默认值(若有)
+       const q = this.query || {}
+       const now = new Date()
+       const defaultYear = (q.year && String(q.year)) || String(now.getFullYear())
+       const defaultMonth = (q.month && Number(q.month)) || (now.getMonth() + 1)
+       this.exportForm = { year: defaultYear, month: defaultMonth }
+       this.exportDialogVisible = true
+     },
+
+     /**
+      * 确认导出(按年月)
       * @this {ForecastSummaryComponent & Vue}
       * @returns {Promise<void>}
       */
-     async onExportTemplate() {
+     async confirmExportByMonth() {
        try {
-         this.loading = true
-         const page = this.page || { currentPage: 1, pageSize: 10 }
-         const merged = { ...this.query }
-         const safeParams = {
-           year: merged.year,
-           month: merged.month,
-           customerName: merged.customerName
+         // 表单校验
+         const formRef = /** @type {any} */ (this.$refs.exportFormRef)
+         if (formRef && typeof formRef.validate === 'function') {
+           const valid = await new Promise((resolve) => formRef.validate((ok) => resolve(ok)))
+           if (!valid) return
+         } else {
+           if (!this.exportForm.year || !this.exportForm.month) {
+             this.$message.warning('请选择年份和月份')
+             return
+           }
          }
-         const res = await exportSalesForecastTemplate(
-           page.currentPage || 1,
-           page.pageSize || 10,
-           safeParams
-         )
+
+         const year = this.exportForm.year
+         const month = this.exportForm.month
+         this.exportLoading = true
+
+         const res = await exportSalesForecastByMonth(year, month)
+
          // 处理文件名
          const disposition = res?.headers?.['content-disposition'] || res?.headers?.['Content-Disposition']
-         let filename = '销售预测汇总模板.xlsx'
-         // attachment; filename*=UTF-8''xxxx.xlsx 或 filename="xxxx.xlsx"
-         const plainMatch = /filename="?([^;\n"]+)"?/i.exec(disposition)
+         let filename = `销售预测汇总_${year}-${String(month).padStart(2, '0')}.xlsx`
          if (disposition) {
-           // attachment; filename*=UTF-8''xxxx.xlsx 或 filename="xxxx.xlsx"
            const utf8Match = /filename\*=UTF-8''([^;\n]+)/i.exec(disposition)
+           const plainMatch = /filename="?([^;\n"]+)"?/i.exec(disposition)
            const raw = utf8Match ? decodeURIComponent(utf8Match[1]) : (plainMatch ? plainMatch[1] : '')
            if (raw) filename = raw
-           // 将下载日期追加到文件名(YYYYMMDD),便于区分下载时间
-           const now = new Date()
-           const y = now.getFullYear()
-           const m = String(now.getMonth() + 1).padStart(2, '0')
-           const d = String(now.getDate()).padStart(2, '0')
-           const ymd = `${y}${m}${d}`
-           const dotIndex = filename.lastIndexOf('.')
-           if (dotIndex > 0) {
-             filename = `${filename.slice(0, dotIndex)}_${ymd}${filename.slice(dotIndex)}`
-           } else {
-             filename = `${filename}_${ymd}`
-           }
          }
+
          const blob = new Blob([res.data], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' })
          const url = window.URL.createObjectURL(blob)
          const a = document.createElement('a')
@@ -335,15 +361,25 @@ import { mapGetters } from 'vuex'
          a.click()
          document.body.removeChild(a)
          window.URL.revokeObjectURL(url)
+
+         this.exportDialogVisible = false
        } catch (e) {
-         console.error('导出模板失败:', e)
+         console.error('按年月导出失败:', e)
          this.$message.error('导出失败,请稍后重试')
        } finally {
-         this.loading = false
+         this.exportLoading = false
        }
      },
 
      /**
+      * 取消导出
+      * @returns {void}
+      */
+     cancelExportDialog() {
+       this.exportDialogVisible = false
+     },
+
+     /**
       * 搜索条件变化处理
       * @this {ForecastSummaryComponent & Vue}
       * @param {Record<string, any>} params - 搜索参数

+ 13 - 0
src/views/forecast-summary/types.d.ts

@@ -18,6 +18,15 @@ export interface ForecastSummaryComponentData {
   option: AvueCrudOption;
   // 新增:子表格(展开区)配置
   childOption: AvueCrudOption;
+  // —— 导出选择层 ——
+  exportDialogVisible: boolean;
+  exportForm: {
+    year: string | number | null;
+    month: number | null;
+  };
+  exportLoading: boolean;
+  exportFormRules: Record<string, any>;
+  monthOptions: Array<{ label: string; value: number }>;
 }
 
 export interface ForecastSummaryComponent extends ForecastSummaryComponentData {
@@ -37,4 +46,8 @@ export interface ForecastSummaryComponent extends ForecastSummaryComponentData {
   formatNumber: (value: string | number) => string;
   formatDateTime: (dateTime: string) => string;
   formatYearMonth: (year: number, month: number) => string;
+  // —— 导出相关 ——
+  onExportTemplate: () => void;
+  confirmExportByMonth: () => Promise<void>;
+  cancelExportDialog: () => void;
 }