Переглянути джерело

feat(forecast-audit): 添加子表展开功能并优化审批流程

yz 1 тиждень тому
батько
коміт
4c177d9e0d

+ 112 - 107
src/views/forecast-audit/auditIndex.js

@@ -1,4 +1,5 @@
 import { getForecastList, updateForecast } from '@/api/forecast'
+import { approveForecastSummary } from '@/api/forecast/forecast-summary'
 import { mapGetters } from 'vuex'
 import {
   APPROVAL_STATUS,
@@ -74,16 +75,12 @@ export default {
         dialogClickModal: false,
         menu: true,
         menuWidth: 280,
+        // 行展开配置,展示子表明细
+        expand: true,
+        expandRowKeys: [],
+        defaultExpandAll: false,
         column: [
           {
-            label: '预测编码',
-            prop: 'forecastCode',
-            search: true,
-            searchSpan: 6,
-            width: 150,
-            overHidden: true
-          },
-          {
             label: '年份',
             prop: 'year',
             type: 'year',
@@ -115,67 +112,22 @@ export default {
             width: 100
           },
           {
-            label: '客户名称',
-            prop: 'customerName',
-            search: true,
-            searchSpan: 6,
-            width: 180,
-            overHidden: true
-          },
-          {
             label: '客户编码',
             prop: 'customerCode',
-            search: false,
-            width: 150,
-            overHidden: true
-          },
-          {
-            label: '品牌名称',
-            prop: 'brandName',
-            search: false,
-            width: 120,
-            overHidden: true
-          },
-          {
-            label: '物料名称',
-            prop: 'itemName',
-            search: false,
-            width: 200,
-            overHidden: true
-          },
-          {
-            label: '物料编码',
-            prop: 'itemCode',
-            search: false,
+            search: true,
+            searchSpan: 6,
             width: 150,
             overHidden: true
           },
           {
-            label: '规格',
-            prop: 'specs',
-            search: false,
-            width: 150,
+            label: '客户名称',
+            prop: 'customerName',
+            search: true,
+            searchSpan: 6,
+            width: 180,
             overHidden: true
           },
           {
-            label: '预测数量',
-            prop: 'forecastQuantity',
-            type: 'number',
-            precision: 4,
-            search: false,
-            width: 120,
-            align: 'right'
-          },
-          {
-            label: '当前库存',
-            prop: 'currentInventory',
-            type: 'number',
-            precision: 4,
-            search: false,
-            width: 120,
-            align: 'right'
-          },
-          {
             label: '审批状态',
             prop: 'approvalStatus',
             type: 'select',
@@ -209,6 +161,31 @@ export default {
             overHidden: true
           }
         ]
+      },
+      // 子表(展开区)配置,展示明细列表
+      childOption: {
+        height: 'auto',
+        calcHeight: 0,
+        tip: false,
+        searchShow: false,
+        border: true,
+        index: true,
+        viewBtn: false,
+        editBtn: false,
+        delBtn: false,
+        addBtn: false,
+        selection: false,
+        menu: true,
+        menuWidth: 160,
+        expand: false,
+        column: [
+          { label: '商品编码', prop: 'itemCode', minWidth: 120 },
+          { label: '商品名称', prop: 'itemName', minWidth: 180, overHidden: true },
+          { label: '规格型号', prop: 'specs', minWidth: 140, overHidden: true },
+          { label: '花型/图案', prop: 'pattern', minWidth: 120, overHidden: true },
+          { label: '品牌名称', prop: 'brandName', minWidth: 120, overHidden: true },
+          { label: '预测数量', prop: 'forecastQuantity', minWidth: 120, align: 'right', slot: true }
+        ]
       }
     }
   },
@@ -270,7 +247,7 @@ export default {
     /**
      * 判断是否可以审批通过
      * @this {Vue & ForecastAuditComponent}
-     * @param {ForecastRecord} row - 数据行对象
+     * @param {ForecastRecord | {approvalStatus: number}} row - 数据行对象
      * @returns {boolean} 是否可以审批通过
      */
     canApprove(row) {
@@ -280,7 +257,7 @@ export default {
     /**
      * 判断是否可以审批拒绝
      * @this {Vue & ForecastAuditComponent}
-     * @param {ForecastRecord} row - 数据行对象
+     * @param {ForecastRecord | {approvalStatus: number}} row - 数据行对象
      * @returns {boolean} 是否可以审批拒绝
      */
     canReject(row) {
@@ -310,7 +287,7 @@ export default {
     },
 
     /**
-     * 审批通过记录
+     * 审批通过记录(主表)
      * @this {Vue & ForecastAuditComponent}
      * @param {ForecastRecord} row - 数据行对象
      * @param {number} index - 行索引
@@ -329,7 +306,7 @@ export default {
     },
 
     /**
-     * 审批拒绝记录
+     * 审批拒绝记录(主表)
      * @this {Vue & ForecastAuditComponent}
      * @param {ForecastRecord} row - 数据行对象
      * @param {number} index - 行索引
@@ -348,6 +325,58 @@ export default {
     },
 
     /**
+     * 子表明细审批通过
+     * @param {{ id: string|number, approvalStatus: number }} sub - 子项记录
+     */
+    approveChildRecord(sub) {
+      this.$confirm('确认通过此明细吗?', '审批确认', {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'success'
+      }).then(() => {
+        this.submitChildApproval(sub, true)
+      }).catch(() => {})
+    },
+
+    /**
+     * 子表明细审批拒绝
+     * @param {{ id: string|number, approvalStatus: number }} sub - 子项记录
+     */
+    rejectChildRecord(sub) {
+      this.$confirm('确认拒绝此明细吗?', '审批确认', {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning'
+      }).then(() => {
+        this.submitChildApproval(sub, false)
+      }).catch(() => {})
+    },
+
+    /**
+     * 执行子表明细审批
+     * @param {{ id: string|number }} sub - 子项记录
+     * @param {boolean} isApprove - 是否通过
+     */
+    submitChildApproval(sub, isApprove) {
+      this.submitting = true
+      const payload = {
+        id: sub.id,
+        approvalStatus: isApprove ? APPROVAL_STATUS.APPROVED : APPROVAL_STATUS.REJECTED
+      }
+      approveForecastSummary(payload)
+        .then(() => {
+          this.$message.success('明细审批成功')
+          this.onLoad(this.page, this.query)
+        })
+        .catch(() => {
+          this.$message.error('明细审批失败')
+        })
+        .finally(() => {
+          this.submitting = false
+        })
+    },
+
+    /**
      * 从详情页面审批通过
      * @this {Vue & ForecastAuditComponent}
      * @returns {void}
@@ -384,7 +413,7 @@ export default {
     },
 
     /**
-     * 执行审批操作
+     * 执行审批操作(主表)
      * @this {Vue & ForecastAuditComponent}
      * @param {ForecastRecord} record - 要审批的记录
      * @param {boolean} isApprove - 是否为通过审批
@@ -399,7 +428,7 @@ export default {
     },
 
     /**
-     * 提交审批
+     * 提交审批(主表)
      * @this {Vue & ForecastAuditComponent}
      * @returns {void}
      */
@@ -469,60 +498,36 @@ export default {
     },
 
     /**
-     * 加载数据
+     * 加载数据(使用销售预测主表分页 main-list)
      * @this {Vue & ForecastAuditComponent}
-     * @param {PageConfig} page - 分页参数
-     * @param {Record<string, any>} [params={}] - 查询参数
-     * @returns {Promise<void>}
-     * @throws {Error} 当数据加载失败时抛出错误
      */
     async onLoad(page, params = {}) {
       try {
         this.loading = true
 
         const queryParams = {
-          ...params,
-          // 只显示需要审核的数据(可以根据业务需求调整)
-          // approvalStatus: APPROVAL_STATUS.PENDING
+          year: params.year ?? this.query.year,
+          month: params.month ?? this.query.month,
+          customerName: params.customerName ?? this.query.customerName
         }
 
-        const res = await getForecastList(page.currentPage, page.pageSize, queryParams)
-
-        if (res.data && res.data.success) {
-          const { records, total } = res.data.data
-          this.data = records || []
-          this.page.total = total || 0
-        } else {
-          throw new Error(res.data?.msg || '获取数据失败')
-        }
+        // 调用主表分页接口
+        const res = await getForecastList({
+          current: page.currentPage,
+          size: page.pageSize,
+          ...queryParams
+        })
+        const { records = [], total = 0, current = 1, size = 10 } = (res.data && res.data.data) || {}
+        this.data = records
+        this.page.total = total
+        this.page.currentPage = current
+        this.page.pageSize = size
       } catch (error) {
-        console.error('加载数据失败:', error)
-        this.$message.error(error.message || '加载数据失败')
-        this.data = []
-        this.page.total = 0
+        // eslint-disable-next-line no-console
+        console.error('加载列表失败', error)
       } finally {
         this.loading = false
       }
-    },
-
-    /**
-     * 页码变化
-     * @this {Vue & ForecastAuditComponent}
-     * @param {number} currentPage - 当前页码
-     * @returns {void}
-     */
-    currentChange(currentPage) {
-      this.page.currentPage = currentPage
-    },
-
-    /**
-     * 页大小变化
-     * @this {Vue & ForecastAuditComponent}
-     * @param {number} pageSize - 页大小
-     * @returns {void}
-     */
-    sizeChange(pageSize) {
-      this.page.pageSize = pageSize
     }
   }
 }

+ 69 - 56
src/views/forecast-audit/index.vue

@@ -26,7 +26,7 @@
         </el-tag>
       </template>
 
-      <!-- 自定义操作按钮 -->
+      <!-- 自定义操作按钮(主表):仅保留查看详情 -->
       <template slot-scope="{row, index}" slot="menu">
         <el-button
           type="text"
@@ -36,28 +36,50 @@
         >
           查看详情
         </el-button>
-        <el-button
-          v-if="canApprove(row)"
-          type="text"
-          size="small"
-          icon="el-icon-check"
-          @click="approveRecord(row, index)"
-          :loading="submitting"
-          style="color: #67c23a;"
-        >
-          通过
-        </el-button>
-        <el-button
-          v-if="canReject(row)"
-          type="text"
-          size="small"
-          icon="el-icon-close"
-          @click="rejectRecord(row, index)"
-          :loading="submitting"
-          style="color: #f56c6c;"
-        >
-          拒绝
-        </el-button>
+      </template>
+
+      <!-- 行展开:展示汇总行的明细列表,与汇总页保持一致 -->
+      <template slot="expand" slot-scope="{row}">
+        <div class="order-expand-content">
+          <div class="expand-header">
+            <h4>明细</h4>
+          </div>
+          <div class="expand-body">
+            <avue-crud
+              :option="childOption"
+              :data="row.pcBladeSalesForecastSummaryList || []"
+              :table-loading="false"
+            >
+              <template slot="forecastQuantity" slot-scope="{row: sub}">
+                <span>{{ formatNumber(sub.forecastQuantity) }}</span>
+              </template>
+              <template slot="menu" slot-scope="{row: sub}">
+                <el-button
+                  v-if="canApprove(sub)"
+                  type="text"
+                  size="small"
+                  icon="el-icon-check"
+                  @click="approveChildRecord(sub)"
+                  :loading="submitting"
+                  style="color: #67c23a;"
+                >
+                  通过
+                </el-button>
+                <el-button
+                  v-if="canReject(sub)"
+                  type="text"
+                  size="small"
+                  icon="el-icon-close"
+                  @click="rejectChildRecord(sub)"
+                  :loading="submitting"
+                  style="color: #f56c6c;"
+                >
+                  拒绝
+                </el-button>
+              </template>
+            </avue-crud>
+          </div>
+        </div>
       </template>
     </avue-crud>
 
@@ -200,67 +222,58 @@
           v-if="canApprove(detailForm)"
           type="success"
           @click="approveFromDetail"
-          :loading="submitting"
         >
-          通过审批
+          通过
         </el-button>
         <el-button
           v-if="canReject(detailForm)"
           type="danger"
           @click="rejectFromDetail"
-          :loading="submitting"
         >
-          拒绝审批
+          拒绝
         </el-button>
       </span>
     </el-dialog>
 
-    <!-- 审批弹窗 -->
+    <!-- 审批确认弹窗(主表) -->
     <el-dialog
       :title="approvalDialogTitle"
       :visible.sync="approvalDialogVisible"
-      width="600px"
+      width="420px"
       :close-on-click-modal="false"
       append-to-body
     >
-      <div class="approval-info">
-        <p v-if="approvalForm.isApprove">
-          <i class="el-icon-success" style="color: #67C23A; margin-right: 8px;"></i>
-          确认通过此预测申报?
-        </p>
-        <p v-else>
-          <i class="el-icon-error" style="color: #F56C6C; margin-right: 8px;"></i>
-          确认拒绝此预测申报?
-        </p>
-      </div>
-      
+      <div style="padding: 10px 0;">请确认是否{{ approvalForm.isApprove ? '通过' : '拒绝' }}该记录。</div>
       <span slot="footer" class="dialog-footer">
         <el-button @click="cancelApproval">取 消</el-button>
-        <el-button
-          type="primary"
-          @click="submitApproval"
-          :loading="submitting"
-        >
-          确 定
-        </el-button>
+        <el-button type="primary" :loading="submitting" @click="submitApproval">确 定</el-button>
       </span>
     </el-dialog>
   </basic-container>
 </template>
 
 <script>
-import auditMixin from './auditIndex.js'
-
-/**
- * 销售预测审核页面
- * @description 提供预测申报的审核功能,包括查看详情、审批通过、审批拒绝等操作
- */
+import mixin from './auditIndex'
 export default {
   name: 'ForecastAudit',
-  mixins: [auditMixin]
+  mixins: [mixin]
 }
 </script>
 
-<style lang="scss">
-@import './index.scss';
+<style scoped>
+.order-expand-content {
+  padding: 8px 0 16px 0;
+}
+.expand-header {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  margin-bottom: 6px;
+}
+.expand-body {
+  background: #fafafa;
+  border: 1px solid #ebeef5;
+  border-radius: 4px;
+  padding: 8px;
+}
 </style>

+ 2 - 0
src/views/forecast-audit/types.d.ts

@@ -111,6 +111,8 @@ export interface ForecastAuditComponentData {
   detailForm: Record<string, any>
   /** 表格配置 */
   option: AvueCrudOption
+  /** 子表(展开区)配置 */
+  childOption: AvueCrudOption
   /** 审批表单数据 */
   approvalForm: ApprovalFormData
 }