|
@@ -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 - 搜索参数
|