Quellcode durchsuchen

feat(forecast): 新增预测审批管理功能模块

yz vor 5 Monaten
Ursprung
Commit
5591b603fa

+ 192 - 7
src/constants/forecast.js

@@ -52,7 +52,7 @@ export const APPROVAL_STATUS_OPTIONS = [
 ]
 
 /**
- * 获取审批状态标签文本
+ * 获取审批状态标签
  * @param {number} status - 审批状态值
  * @returns {string} 状态标签文本
  */
@@ -61,9 +61,9 @@ export function getApprovalStatusLabel(status) {
 }
 
 /**
- * 获取审批状态标签类型
+ * 获取审批状态类型
  * @param {number} status - 审批状态值
- * @returns {string} ElementUI标签类型
+ * @returns {string} 状态类型
  */
 export function getApprovalStatusType(status) {
   return APPROVAL_STATUS_CONFIG[status]?.type || 'info'
@@ -72,7 +72,7 @@ export function getApprovalStatusType(status) {
 /**
  * 获取审批状态颜色
  * @param {number} status - 审批状态值
- * @returns {string} 状态颜色
+ * @returns {string} 状态颜色
  */
 export function getApprovalStatusColor(status) {
   return APPROVAL_STATUS_CONFIG[status]?.color || '#909399'
@@ -88,7 +88,7 @@ export function isValidApprovalStatus(status) {
 }
 
 /**
- * 检查是否可以编辑
+ * 判断是否可以编辑
  * @param {number} approvalStatus - 审批状态
  * @returns {boolean} 是否可以编辑
  */
@@ -97,14 +97,115 @@ export function canEdit(approvalStatus) {
 }
 
 /**
+ * 判断是否可以审批
+ * @param {number} approvalStatus - 审批状态
+ * @returns {boolean} 是否可以审批
+ */
+export function canApprove(approvalStatus) {
+  return approvalStatus === APPROVAL_STATUS.PENDING
+}
+
+/**
+ * 判断是否可以拒绝
+ * @param {number} approvalStatus - 审批状态
+ * @returns {boolean} 是否可以拒绝
+ */
+export function canReject(approvalStatus) {
+  return approvalStatus === APPROVAL_STATUS.PENDING
+}
+
+/**
+ * 判断是否已审批完成
+ * @param {number} approvalStatus - 审批状态
+ * @returns {boolean} 是否已审批完成
+ */
+export function isApprovalCompleted(approvalStatus) {
+  return approvalStatus === APPROVAL_STATUS.APPROVED || approvalStatus === APPROVAL_STATUS.REJECTED
+}
+
+/**
+ * 判断是否审批通过
+ * @param {number} approvalStatus - 审批状态
+ * @returns {boolean} 是否审批通过
+ */
+export function isApproved(approvalStatus) {
+  return approvalStatus === APPROVAL_STATUS.APPROVED
+}
+
+/**
+ * 判断是否审批拒绝
+ * @param {number} approvalStatus - 审批状态
+ * @returns {boolean} 是否审批拒绝
+ */
+export function isRejected(approvalStatus) {
+  return approvalStatus === APPROVAL_STATUS.REJECTED
+}
+
+/**
  * 获取所有审批状态值
- * @returns {number[]} 所有状态值数组
+ * @returns {Array<number>} 所有审批状态值数组
  */
 export function getAllApprovalStatusValues() {
   return Object.values(APPROVAL_STATUS)
 }
 
 /**
+ * 审核操作类型枚举
+ * @readonly
+ * @enum {string}
+ */
+export const AUDIT_ACTION_TYPE = {
+  /** 审批通过 */
+  APPROVE: 'approve',
+  /** 审批拒绝 */
+  REJECT: 'reject',
+  /** 查看详情 */
+  VIEW: 'view'
+}
+
+/**
+ * 审核操作配置映射
+ * @readonly
+ * @type {Record<string, {label: string, type: string, icon: string}>}
+ */
+export const AUDIT_ACTION_CONFIG = {
+  [AUDIT_ACTION_TYPE.APPROVE]: {
+    label: '通过',
+    type: 'success',
+    icon: 'el-icon-check'
+  },
+  [AUDIT_ACTION_TYPE.REJECT]: {
+    label: '拒绝',
+    type: 'danger',
+    icon: 'el-icon-close'
+  },
+  [AUDIT_ACTION_TYPE.VIEW]: {
+    label: '查看详情',
+    type: 'primary',
+    icon: 'el-icon-view'
+  }
+}
+
+/**
+ * 获取可执行的审核操作
+ * @param {number} approvalStatus - 审批状态
+ * @returns {Array<string>} 可执行的操作类型数组
+ */
+export function getAvailableAuditActions(approvalStatus) {
+  const actions = [AUDIT_ACTION_TYPE.VIEW]
+  
+  if (canApprove(approvalStatus)) {
+    actions.push(AUDIT_ACTION_TYPE.APPROVE)
+  }
+  
+  if (canReject(approvalStatus)) {
+    actions.push(AUDIT_ACTION_TYPE.REJECT)
+  }
+  
+  return actions
+}
+
+/**
  * 表单验证规则
  * @readonly
  */
@@ -148,12 +249,36 @@ export const FORECAST_FORM_RULES = {
 }
 
 /**
+ * 审批表单验证规则
+ * @readonly
+ */
+export const APPROVAL_FORM_RULES = {
+  /** 审批意见验证规则 */
+  approvalComment: [
+    {
+      validator: (rule, value, callback, source, options) => {
+        // 这里的 this 在运行时会指向 Vue 组件实例
+        const isReject = options.isReject || false
+        if (isReject && (!value || value.trim() === '')) {
+          callback(new Error('拒绝审批时必须填写拒绝原因'))
+        } else if (value && value.length > 500) {
+          callback(new Error('审批意见不能超过500个字符'))
+        } else {
+          callback()
+        }
+      },
+      trigger: 'blur'
+    }
+  ]
+}
+
+/**
  * 默认表单数据
  * @readonly
  */
 export const DEFAULT_FORECAST_FORM = {
   forecastCode: '',
-  year: new Date().getFullYear().toString(), // 改为字符串类型
+  year: new Date().getFullYear().toString(),
   month: new Date().getMonth() + 1,
   customerId: null,
   customerCode: '',
@@ -169,3 +294,63 @@ export const DEFAULT_FORECAST_FORM = {
   currentInventory: null,
   approvalStatus: APPROVAL_STATUS.PENDING
 }
+
+/**
+ * 默认审批表单数据
+ * @readonly
+ */
+export const DEFAULT_APPROVAL_FORM = {
+  approvalComment: '',
+  isApprove: true,
+  currentRecord: null
+}
+
+/**
+ * 数字格式化选项
+ * @readonly
+ */
+export const NUMBER_FORMAT_OPTIONS = {
+  /** 数量格式化选项 */
+  quantity: {
+    minimumFractionDigits: 0,
+    maximumFractionDigits: 4,
+    useGrouping: true
+  },
+  /** 金额格式化选项 */
+  currency: {
+    minimumFractionDigits: 2,
+    maximumFractionDigits: 2,
+    useGrouping: true
+  }
+}
+
+/**
+ * 格式化数字显示
+ * @param {number|null|undefined} value - 数值
+ * @param {Object} options - 格式化选项
+ * @returns {string} 格式化后的字符串
+ */
+export function formatNumber(value, options = NUMBER_FORMAT_OPTIONS.quantity) {
+  if (value === null || value === undefined || isNaN(value)) {
+    return '-'
+  }
+  return Number(value).toLocaleString('zh-CN', options)
+}
+
+/**
+ * 格式化数量显示
+ * @param {number|null|undefined} value - 数量值
+ * @returns {string} 格式化后的字符串
+ */
+export function formatQuantity(value) {
+  return formatNumber(value, NUMBER_FORMAT_OPTIONS.quantity)
+}
+
+/**
+ * 格式化金额显示
+ * @param {number|null|undefined} value - 金额值
+ * @returns {string} 格式化后的字符串
+ */
+export function formatCurrency(value) {
+  return formatNumber(value, NUMBER_FORMAT_OPTIONS.currency)
+}

+ 26 - 0
src/router/views/index.js

@@ -323,6 +323,32 @@ export default [
         ]
     },
     {
+        path: "/forecast-audit",
+        component: Layout,
+        redirect: "/forecast-audit/index",
+        meta: {
+            icon: "el-icon-document-checked",
+            title: "预测审批管理",
+            keepAlive: true,
+            isAuth: true
+        },
+        children: [
+            {
+                path: "index",
+                name: "预测审批",
+                meta: {
+                    keepAlive: true,
+                    isAuth: true,
+                    title: "预测审批"
+                },
+                component: () =>
+                    import(
+                        /* webpackChunkName: "forecast-audit" */ "@/views/forecast-audit/index"
+                    )
+            }
+        ]
+    },
+    {
         path: "/dict-horizontal",
         component: Layout,
         redirect: "/dict-horizontal/index",

+ 563 - 0
src/views/forecast-audit/auditIndex.js

@@ -0,0 +1,563 @@
+import { getForecastList, updateForecast } from '@/api/forecast'
+import {
+  APPROVAL_STATUS,
+  APPROVAL_STATUS_OPTIONS,
+  getApprovalStatusLabel,
+  getApprovalStatusType,
+  canApprove,
+  canReject,
+  formatNumber
+} from '@/constants/forecast'
+import { mapGetters } from 'vuex'
+
+/**
+ * 预测申报数据项类型定义
+ * @typedef {Object} ForecastItem
+ * @property {string} id - 预测申报ID
+ * @property {string} createUser - 创建用户ID
+ * @property {string} createDept - 创建部门ID
+ * @property {string} createTime - 创建时间
+ * @property {string} updateUser - 更新用户ID
+ * @property {string} updateTime - 更新时间
+ * @property {number} status - 状态
+ * @property {number} isDeleted - 是否删除
+ * @property {string} forecastCode - 预测编码
+ * @property {number} year - 年份
+ * @property {number} month - 月份
+ * @property {number} customerId - 客户ID
+ * @property {string} customerCode - 客户编码
+ * @property {string} customerName - 客户名称
+ * @property {number} brandId - 品牌ID
+ * @property {string} brandCode - 品牌编码
+ * @property {string} brandName - 品牌名称
+ * @property {number} itemId - 物料ID
+ * @property {string} itemCode - 物料编码
+ * @property {string} itemName - 物料名称
+ * @property {string} specs - 规格
+ * @property {number} forecastQuantity - 预测数量
+ * @property {number} currentInventory - 当前库存
+ * @property {number} approvalStatus - 审批状态
+ * @property {number|null} approvedBy - 审批人ID
+ * @property {string|null} approvedName - 审批人姓名
+ * @property {string|null} approvedTime - 审批时间
+ * @property {string|null} approvalComment - 审批意见
+ */
+
+/**
+ * 分页配置类型定义
+ * @typedef {Object} PageConfig
+ * @property {number} pageSize - 每页数量
+ * @property {number} currentPage - 当前页码
+ * @property {number} total - 总数量
+ */
+
+/**
+ * 审批表单数据类型定义
+ * @typedef {Object} ApprovalFormData
+ * @property {string} approvalComment - 审批意见
+ * @property {boolean} isApprove - 是否为通过审批
+ * @property {ForecastItem|null} currentRecord - 当前审批的记录
+ */
+
+/**
+ * 权限配置类型定义
+ * @typedef {Object} PermissionConfig
+ * @property {boolean} addBtn - 添加按钮权限
+ * @property {boolean} viewBtn - 查看按钮权限
+ * @property {boolean} editBtn - 编辑按钮权限
+ * @property {boolean} delBtn - 删除按钮权限
+ */
+
+/**
+ * 销售预测审核页面业务逻辑混入
+ * @description 提供预测申报的审核功能,包括列表展示、详情查看、审批操作等
+ */
+export default {
+  data() {
+    return {
+      /** @type {Object} 表单数据 */
+      form: {},
+      /** @type {Object} 查询参数 */
+      query: {},
+      /** @type {boolean} 表格加载状态 */
+      loading: true,
+      /** @type {boolean} 表单提交状态 */
+      submitting: false,
+      /** @type {boolean} 审批中状态 */
+      approving: false,
+      /** @type {boolean} 拒绝中状态 */
+      rejecting: false,
+      /** @type {boolean} 详情弹窗显示状态 */
+      detailDialogVisible: false,
+      /** @type {boolean} 审批确认弹窗显示状态 */
+      approvalDialogVisible: false,
+      /** @type {PageConfig} 分页配置 */
+      page: {
+        pageSize: 10,
+        currentPage: 1,
+        total: 0
+      },
+      /** @type {Array<ForecastItem>} 表格数据 */
+      data: [],
+      /** @type {ForecastItem} 详情表单数据 */
+      detailForm: {},
+      /** @type {ApprovalFormData} 审批表单数据 */
+      approvalForm: {
+        /** @type {string} 审批意见 */
+        approvalComment: '',
+        /** @type {boolean} 是否为通过审批 */
+        isApprove: true,
+        /** @type {ForecastItem|null} 当前审批的记录 */
+        currentRecord: null
+      },
+      /** @type {Object} avue表格配置 */
+      option: {
+        height: 'auto',
+        calcHeight: 30,
+        tip: false,
+        searchShow: true,
+        searchMenuSpan: 6,
+        border: true,
+        index: true,
+        viewBtn: false,
+        selection: false,
+        addBtn: false,
+        editBtn: false,
+        delBtn: false,
+        excelBtn: false,
+        columnBtn: false,
+        refreshBtn: true,
+        dialogClickModal: false,
+        menu: true,
+        menuWidth: 280,
+        column: [
+          {
+            label: '预测编码',
+            prop: 'forecastCode',
+            search: true,
+            searchSpan: 6,
+            width: 150,
+            overHidden: true
+          },
+          {
+            label: '年份',
+            prop: 'year',
+            type: 'year',
+            valueFormat: 'yyyy',
+            search: true,
+            searchSpan: 6,
+            width: 100
+          },
+          {
+            label: '月份',
+            prop: 'month',
+            type: 'select',
+            dicData: [
+              { label: '1月', value: 1 },
+              { label: '2月', value: 2 },
+              { label: '3月', value: 3 },
+              { label: '4月', value: 4 },
+              { label: '5月', value: 5 },
+              { label: '6月', value: 6 },
+              { label: '7月', value: 7 },
+              { label: '8月', value: 8 },
+              { label: '9月', value: 9 },
+              { label: '10月', value: 10 },
+              { label: '11月', value: 11 },
+              { label: '12月', value: 12 }
+            ],
+            search: true,
+            searchSpan: 6,
+            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,
+            width: 150,
+            overHidden: true
+          },
+          {
+            label: '规格',
+            prop: 'specs',
+            search: false,
+            width: 150,
+            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',
+            dicData: APPROVAL_STATUS_OPTIONS,
+            search: true,
+            searchSpan: 6,
+            width: 100,
+            slot: true
+          },
+          {
+            label: '审批人',
+            prop: 'approvedName',
+            search: false,
+            width: 120,
+            overHidden: true
+          },
+          {
+            label: '审批时间',
+            prop: 'approvedTime',
+            type: 'datetime',
+            search: false,
+            width: 160,
+            overHidden: true
+          },
+          {
+            label: '创建时间',
+            prop: 'createTime',
+            type: 'datetime',
+            search: false,
+            width: 160,
+            overHidden: true
+          }
+        ]
+      },
+      /** @type {Object} 审批表单验证规则 */
+      approvalFormRules: {
+        approvalComment: [
+          {
+            validator: (rule, value, callback) => {
+              if (!this.approvalForm.isApprove && (!value || value.trim() === '')) {
+                callback(new Error('拒绝审批时必须填写拒绝原因'))
+              } else if (value && value.length > 500) {
+                callback(new Error('审批意见不能超过500个字符'))
+              } else {
+                callback()
+              }
+            },
+            trigger: 'blur'
+          }
+        ]
+      }
+    }
+  },
+
+  computed: {
+    ...mapGetters(['permission']),
+
+    /**
+     * 权限列表
+     * @returns {PermissionConfig} 权限配置对象
+     */
+    permissionList() {
+      return {
+        // addBtn: this.vaildData(this.permission.forecast_audit_add, false),
+        // viewBtn: this.vaildData(this.permission.forecast_audit_view, false),
+        // editBtn: this.vaildData(this.permission.forecast_audit_edit, false),
+        // delBtn: this.vaildData(this.permission.forecast_audit_delete, false)
+        addBtn: false,
+        viewBtn: false,
+        editBtn: false,
+        delBtn: false
+      }
+    },
+
+    /**
+     * 审批弹窗标题
+     * @returns {string} 弹窗标题文本
+     */
+    approvalDialogTitle() {
+      return this.approvalForm.isApprove ? '审批通过确认' : '审批拒绝确认'
+    }
+  },
+
+  created() {
+    // 初始化时不需要加载额外数据,只等待表格自动加载
+  },
+
+  methods: {
+    /**
+     * 获取审批状态标签
+     * @param {number} status - 审批状态值
+     * @returns {string} 状态标签文本
+     */
+    getApprovalStatusLabel(status) {
+      return getApprovalStatusLabel(status)
+    },
+
+    /**
+     * 获取审批状态类型
+     * @param {number} status - 审批状态值
+     * @returns {string} 状态类型
+     */
+    getApprovalStatusType(status) {
+      return getApprovalStatusType(status)
+    },
+
+    /**
+     * 判断是否可以审批通过
+     * @param {ForecastItem} row - 数据行对象
+     * @returns {boolean} 是否可以审批通过
+     */
+    canApprove(row) {
+      return canApprove(row.approvalStatus)
+    },
+
+    /**
+     * 判断是否可以审批拒绝
+     * @param {ForecastItem} row - 数据行对象
+     * @returns {boolean} 是否可以审批拒绝
+     */
+    canReject(row) {
+      return canReject(row.approvalStatus)
+    },
+
+    /**
+     * 格式化数字显示
+     * @param {number|null|undefined} value - 数值
+     * @returns {string} 格式化后的字符串
+     */
+    formatNumber(value) {
+      return formatNumber(value)
+    },
+
+    /**
+     * 查看详情
+     * @param {ForecastItem} row - 数据行对象
+     * @param {number} index - 行索引
+     * @returns {void}
+     */
+    viewDetail(row, index) {
+      this.detailForm = { ...row }
+      this.detailDialogVisible = true
+    },
+
+    /**
+     * 审批通过记录
+     * @param {ForecastItem} row - 数据行对象
+     * @param {number} index - 行索引
+     * @returns {void}
+     */
+    approveRecord(row, index) {
+      this.approvalForm = {
+        approvalComment: '',
+        isApprove: true,
+        currentRecord: { ...row }
+      }
+      this.approvalDialogVisible = true
+    },
+
+    /**
+     * 审批拒绝记录
+     * @param {ForecastItem} row - 数据行对象
+     * @param {number} index - 行索引
+     * @returns {void}
+     */
+    rejectRecord(row, index) {
+      this.approvalForm = {
+        approvalComment: '',
+        isApprove: false,
+        currentRecord: { ...row }
+      }
+      this.approvalDialogVisible = true
+    },
+
+    /**
+     * 从详情页面审批通过
+     * @returns {void}
+     */
+    approveFromDetail() {
+      this.approvalForm = {
+        approvalComment: '',
+        isApprove: true,
+        currentRecord: { ...this.detailForm }
+      }
+      this.detailDialogVisible = false
+      this.approvalDialogVisible = true
+    },
+
+    /**
+     * 从详情页面审批拒绝
+     * @returns {void}
+     */
+    rejectFromDetail() {
+      this.approvalForm = {
+        approvalComment: '',
+        isApprove: false,
+        currentRecord: { ...this.detailForm }
+      }
+      this.detailDialogVisible = false
+      this.approvalDialogVisible = true
+    },
+
+    /**
+     * 确认审批操作
+     * @returns {Promise<void>}
+     * @throws {Error} 当审批操作失败时抛出错误
+     */
+    async confirmApproval() {
+      try {
+        // 验证表单
+        await this.$refs.approvalForm.validate()
+
+        this.submitting = true
+
+        const { currentRecord, isApprove, approvalComment } = this.approvalForm
+
+        // 构建更新数据
+        const updateData = {
+          ...currentRecord,
+          approvalStatus: isApprove ? APPROVAL_STATUS.APPROVED : APPROVAL_STATUS.REJECTED,
+          approvalComment: approvalComment || null,
+          approvedBy: this.$store.getters.userInfo.user_id,
+          approvedName: this.$store.getters.userInfo.real_name,
+          approvedTime: new Date().toISOString().replace('T', ' ').substring(0, 19)
+        }
+
+        const res = await updateForecast(updateData)
+
+        if (res.data && res.data.success) {
+          this.$message.success(`审批${isApprove ? '通过' : '拒绝'}成功`)
+          this.approvalDialogVisible = false
+          this.onLoad(this.page) // 重新加载数据
+        } else {
+          throw new Error(res.data?.msg || '审批操作失败')
+        }
+      } catch (error) {
+        console.error('审批操作失败:', error)
+        if (error.message && error.message !== 'validation failed') {
+          this.$message.error(error.message)
+        }
+      } finally {
+        this.submitting = false
+      }
+    },
+
+    /**
+     * 搜索变化事件
+     * @param {Object} params - 搜索参数
+     * @param {Function} done - 完成回调
+     * @returns {void}
+     */
+    searchChange(params, done) {
+      this.query = params
+      this.onLoad(this.page, params)
+      done()
+    },
+
+    /**
+     * 搜索重置事件
+     * @returns {void}
+     */
+    searchReset() {
+      this.query = {}
+      this.onLoad(this.page)
+    },
+
+    /**
+     * 刷新事件
+     * @returns {void}
+     */
+    refreshChange() {
+      this.onLoad(this.page, this.query)
+    },
+
+    /**
+     * 加载数据
+     * @param {PageConfig} page - 分页参数
+     * @param {Object} [params={}] - 查询参数
+     * @returns {Promise<void>}
+     * @throws {Error} 当数据加载失败时抛出错误
+     */
+    async onLoad(page, params = {}) {
+      try {
+        this.loading = true
+
+        const queryParams = {
+          ...params,
+          // 只显示需要审核的数据(可以根据业务需求调整)
+          // approvalStatus: APPROVAL_STATUS.PENDING
+        }
+
+        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 || '获取数据失败')
+        }
+      } catch (error) {
+        console.error('加载数据失败:', error)
+        this.$message.error(error.message || '加载数据失败')
+        this.data = []
+        this.page.total = 0
+      } finally {
+        this.loading = false
+      }
+    },
+
+    /**
+     * 页码变化
+     * @param {number} currentPage - 当前页码
+     * @returns {void}
+     */
+    currentChange(currentPage) {
+      this.page.currentPage = currentPage
+    },
+
+    /**
+     * 页大小变化
+     * @param {number} pageSize - 页大小
+     * @returns {void}
+     */
+    sizeChange(pageSize) {
+      this.page.pageSize = pageSize
+    }
+  }
+}

+ 330 - 0
src/views/forecast-audit/index.scss

@@ -0,0 +1,330 @@
+/**
+ * 销售预测审核页面样式
+ */
+
+// 详情表单样式
+.forecast-detail-form {
+  .el-form-item {
+    margin-bottom: 18px;
+  }
+
+  .el-form-item__label {
+    font-weight: 500;
+    color: #606266;
+  }
+
+  .el-form-item__content {
+    span {
+      color: #303133;
+      font-size: 14px;
+      line-height: 1.5;
+      
+      &:empty::after {
+        content: '-';
+        color: #c0c4cc;
+      }
+    }
+  }
+}
+
+// 表格样式优化
+.avue-crud {
+  .el-table {
+    .el-table__row {
+      &:hover {
+        background-color: #f5f7fa;
+      }
+    }
+
+    // 操作列按钮样式
+    .el-button {
+      margin-right: 8px;
+      
+      &:last-child {
+        margin-right: 0;
+      }
+      
+      &.el-button--small {
+        padding: 5px 8px;
+        font-size: 12px;
+      }
+    }
+  }
+
+  // 搜索表单样式
+  .avue-crud__search {
+    .el-form-item {
+      margin-bottom: 10px;
+    }
+  }
+
+  // 工具栏样式
+  .avue-crud__menu {
+    padding: 10px 0;
+    
+    .el-button {
+      margin-right: 10px;
+    }
+  }
+}
+
+// 状态标签样式
+.el-tag {
+  border-radius: 4px;
+  font-weight: 500;
+  
+  &.el-tag--success {
+    background-color: #f0f9ff;
+    border-color: #67c23a;
+    color: #67c23a;
+  }
+  
+  &.el-tag--warning {
+    background-color: #fdf6ec;
+    border-color: #e6a23c;
+    color: #e6a23c;
+  }
+  
+  &.el-tag--danger {
+    background-color: #fef0f0;
+    border-color: #f56c6c;
+    color: #f56c6c;
+  }
+
+  &.el-tag--info {
+    background-color: #f4f4f5;
+    border-color: #909399;
+    color: #909399;
+  }
+}
+
+// 弹窗样式
+.el-dialog {
+  .el-dialog__header {
+    padding: 20px 20px 10px;
+    border-bottom: 1px solid #e4e7ed;
+    
+    .el-dialog__title {
+      font-size: 16px;
+      font-weight: 600;
+      color: #303133;
+    }
+  }
+  
+  .el-dialog__body {
+    padding: 20px;
+    max-height: 70vh;
+    overflow-y: auto;
+  }
+  
+  .el-dialog__footer {
+    padding: 10px 20px 20px;
+    border-top: 1px solid #e4e7ed;
+    
+    .el-button {
+      margin-left: 10px;
+      
+      &:first-child {
+        margin-left: 0;
+      }
+    }
+  }
+}
+
+// 审批确认弹窗特殊样式
+.el-dialog {
+  &[aria-label*="审批"] {
+    .el-dialog__body {
+      padding: 30px 20px;
+    }
+    
+    .el-form-item__label {
+      font-weight: 600;
+      color: #303133;
+    }
+    
+    .el-textarea__inner {
+      border-radius: 6px;
+      border: 1px solid #dcdfe6;
+      transition: border-color 0.2s cubic-bezier(0.645, 0.045, 0.355, 1);
+      
+      &:focus {
+        border-color: #409eff;
+        box-shadow: 0 0 0 2px rgba(64, 158, 255, 0.2);
+      }
+    }
+  }
+}
+
+// 响应式设计
+@media (max-width: 768px) {
+  .el-dialog {
+    width: 95% !important;
+    margin: 0 auto;
+    
+    .el-dialog__body {
+      padding: 15px;
+    }
+  }
+  
+  .forecast-detail-form {
+    .el-row {
+      .el-col {
+        margin-bottom: 10px;
+      }
+    }
+  }
+  
+  .avue-crud {
+    .el-table {
+      .el-button {
+        margin-bottom: 5px;
+        
+        &.el-button--small {
+          padding: 3px 6px;
+          font-size: 11px;
+        }
+      }
+    }
+  }
+}
+
+// 加载状态样式
+.el-loading-mask {
+  background-color: rgba(255, 255, 255, 0.8);
+  
+  .el-loading-spinner {
+    .el-loading-text {
+      color: #409eff;
+      font-size: 14px;
+    }
+  }
+}
+
+// 空数据状态样式
+.el-table__empty-block {
+  .el-table__empty-text {
+    color: #909399;
+    font-size: 14px;
+  }
+}
+
+// 数字格式化显示样式
+.number-display {
+  font-family: 'Consolas', 'Monaco', 'Courier New', monospace;
+  font-weight: 500;
+  color: #303133;
+  
+  &.positive {
+    color: #67c23a;
+  }
+  
+  &.negative {
+    color: #f56c6c;
+  }
+  
+  &.zero {
+    color: #909399;
+  }
+}
+
+// 审批操作按钮组样式
+.approval-buttons {
+  display: flex;
+  gap: 8px;
+  flex-wrap: wrap;
+  
+  .el-button {
+    flex: 1;
+    min-width: 80px;
+    
+    &.el-button--success {
+      background-color: #67c23a;
+      border-color: #67c23a;
+      
+      &:hover {
+        background-color: #85ce61;
+        border-color: #85ce61;
+      }
+    }
+    
+    &.el-button--danger {
+      background-color: #f56c6c;
+      border-color: #f56c6c;
+      
+      &:hover {
+        background-color: #f78989;
+        border-color: #f78989;
+      }
+    }
+  }
+}
+
+// 详情信息展示样式
+.detail-info {
+  .info-row {
+    display: flex;
+    margin-bottom: 12px;
+    
+    .info-label {
+      min-width: 100px;
+      font-weight: 500;
+      color: #606266;
+      margin-right: 12px;
+    }
+    
+    .info-value {
+      flex: 1;
+      color: #303133;
+      
+      &.highlight {
+        color: #409eff;
+        font-weight: 500;
+      }
+    }
+  }
+}
+
+// 审批历史样式(如果需要)
+.approval-history {
+  margin-top: 20px;
+  padding-top: 20px;
+  border-top: 1px solid #e4e7ed;
+  
+  .history-title {
+    font-size: 16px;
+    font-weight: 600;
+    color: #303133;
+    margin-bottom: 15px;
+  }
+  
+  .history-item {
+    padding: 12px;
+    background-color: #f8f9fa;
+    border-radius: 6px;
+    margin-bottom: 10px;
+    
+    .history-header {
+      display: flex;
+      justify-content: space-between;
+      align-items: center;
+      margin-bottom: 8px;
+      
+      .history-user {
+        font-weight: 500;
+        color: #303133;
+      }
+      
+      .history-time {
+        font-size: 12px;
+        color: #909399;
+      }
+    }
+    
+    .history-content {
+      color: #606266;
+      font-size: 14px;
+      line-height: 1.5;
+    }
+  }
+}

+ 270 - 0
src/views/forecast-audit/index.vue

@@ -0,0 +1,270 @@
+<template>
+  <basic-container>
+    <!-- 数据表格 -->
+    <avue-crud
+      :option="option"
+      :table-loading="loading"
+      :data="data"
+      :page.sync="page"
+      ref="crud"
+      v-model="form"
+      :permission="permissionList"
+      @search-change="searchChange"
+      @search-reset="searchReset"
+      @current-change="currentChange"
+      @size-change="sizeChange"
+      @refresh-change="refreshChange"
+      @on-load="onLoad"
+    >
+      <!-- 自定义审批状态显示 -->
+      <template slot-scope="{row}" slot="approvalStatus">
+        <el-tag
+          :type="getApprovalStatusType(row.approvalStatus)"
+          size="small"
+        >
+          {{ getApprovalStatusLabel(row.approvalStatus) }}
+        </el-tag>
+      </template>
+
+      <!-- 自定义操作按钮 -->
+      <template slot-scope="{row, index}" slot="menu">
+        <el-button
+          type="primary"
+          size="small"
+          icon="el-icon-view"
+          @click="viewDetail(row, index)"
+        >
+          查看详情
+        </el-button>
+        <el-button
+          v-if="canApprove(row)"
+          type="success"
+          size="small"
+          icon="el-icon-check"
+          @click="approveRecord(row, index)"
+        >
+          通过
+        </el-button>
+        <el-button
+          v-if="canReject(row)"
+          type="danger"
+          size="small"
+          icon="el-icon-close"
+          @click="rejectRecord(row, index)"
+        >
+          拒绝
+        </el-button>
+      </template>
+    </avue-crud>
+
+    <!-- 详情查看弹窗 -->
+    <el-dialog
+      title="预测申报详情"
+      :visible.sync="detailDialogVisible"
+      width="1200px"
+      :close-on-click-modal="false"
+      append-to-body
+    >
+      <el-form
+        :model="detailForm"
+        label-width="120px"
+        class="forecast-detail-form"
+      >
+        <el-row :gutter="20">
+          <el-col :span="12">
+            <el-form-item label="预测编码">
+              <span>{{ detailForm.forecastCode }}</span>
+            </el-form-item>
+          </el-col>
+          <el-col :span="6">
+            <el-form-item label="年份">
+              <span>{{ detailForm.year }}</span>
+            </el-form-item>
+          </el-col>
+          <el-col :span="6">
+            <el-form-item label="月份">
+              <span>{{ detailForm.month }}月</span>
+            </el-form-item>
+          </el-col>
+        </el-row>
+
+        <el-row :gutter="20">
+          <el-col :span="12">
+            <el-form-item label="客户名称">
+              <span>{{ detailForm.customerName }}</span>
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="客户编码">
+              <span>{{ detailForm.customerCode }}</span>
+            </el-form-item>
+          </el-col>
+        </el-row>
+
+        <el-row :gutter="20">
+          <el-col :span="8">
+            <el-form-item label="品牌名称">
+              <span>{{ detailForm.brandName }}</span>
+            </el-form-item>
+          </el-col>
+          <el-col :span="8">
+            <el-form-item label="品牌编码">
+              <span>{{ detailForm.brandCode }}</span>
+            </el-form-item>
+          </el-col>
+          <el-col :span="8">
+            <el-form-item label="品牌ID">
+              <span>{{ detailForm.brandId }}</span>
+            </el-form-item>
+          </el-col>
+        </el-row>
+
+        <el-row :gutter="20">
+          <el-col :span="12">
+            <el-form-item label="物料名称">
+              <span>{{ detailForm.itemName }}</span>
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="物料编码">
+              <span>{{ detailForm.itemCode }}</span>
+            </el-form-item>
+          </el-col>
+        </el-row>
+
+        <el-row :gutter="20">
+          <el-col :span="24">
+            <el-form-item label="规格">
+              <span>{{ detailForm.specs }}</span>
+            </el-form-item>
+          </el-col>
+        </el-row>
+
+        <el-row :gutter="20">
+          <el-col :span="12">
+            <el-form-item label="预测数量">
+              <span>{{ formatNumber(detailForm.forecastQuantity) }}</span>
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="当前库存">
+              <span>{{ formatNumber(detailForm.currentInventory) }}</span>
+            </el-form-item>
+          </el-col>
+        </el-row>
+
+        <el-row :gutter="20">
+          <el-col :span="8">
+            <el-form-item label="审批状态">
+              <el-tag
+                :type="getApprovalStatusType(detailForm.approvalStatus)"
+                size="medium"
+              >
+                {{ getApprovalStatusLabel(detailForm.approvalStatus) }}
+              </el-tag>
+            </el-form-item>
+          </el-col>
+          <el-col :span="8" v-if="detailForm.approvedName">
+            <el-form-item label="审批人">
+              <span>{{ detailForm.approvedName }}</span>
+            </el-form-item>
+          </el-col>
+          <el-col :span="8" v-if="detailForm.approvedTime">
+            <el-form-item label="审批时间">
+              <span>{{ detailForm.approvedTime }}</span>
+            </el-form-item>
+          </el-col>
+        </el-row>
+
+        <el-row :gutter="20">
+          <el-col :span="12">
+            <el-form-item label="创建时间">
+              <span>{{ detailForm.createTime }}</span>
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="更新时间">
+              <span>{{ detailForm.updateTime }}</span>
+            </el-form-item>
+          </el-col>
+        </el-row>
+      </el-form>
+
+      <span slot="footer" class="dialog-footer">
+        <el-button @click="detailDialogVisible = false">关 闭</el-button>
+        <el-button
+          v-if="canApprove(detailForm)"
+          type="success"
+          @click="approveFromDetail"
+          :loading="approving"
+        >
+          通过审批
+        </el-button>
+        <el-button
+          v-if="canReject(detailForm)"
+          type="danger"
+          @click="rejectFromDetail"
+          :loading="rejecting"
+        >
+          拒绝审批
+        </el-button>
+      </span>
+    </el-dialog>
+
+    <!-- 审批确认弹窗 -->
+    <el-dialog
+      :title="approvalDialogTitle"
+      :visible.sync="approvalDialogVisible"
+      width="500px"
+      :close-on-click-modal="false"
+      append-to-body
+    >
+      <el-form
+        ref="approvalForm"
+        :model="approvalForm"
+        :rules="approvalFormRules"
+        label-width="100px"
+      >
+        <el-form-item label="审批意见" prop="approvalComment">
+          <el-input
+            v-model="approvalForm.approvalComment"
+            type="textarea"
+            :rows="4"
+            :placeholder="approvalForm.isApprove ? '请输入通过意见(可选)' : '请输入拒绝原因'"
+            maxlength="500"
+            show-word-limit
+          >
+          </el-input>
+        </el-form-item>
+      </el-form>
+
+      <span slot="footer" class="dialog-footer">
+        <el-button @click="approvalDialogVisible = false">取 消</el-button>
+        <el-button
+          :type="approvalForm.isApprove ? 'success' : 'danger'"
+          @click="confirmApproval"
+          :loading="submitting"
+        >
+          确认{{ approvalForm.isApprove ? '通过' : '拒绝' }}
+        </el-button>
+      </span>
+    </el-dialog>
+  </basic-container>
+</template>
+
+<script>
+import auditMixin from './auditIndex.js'
+
+/**
+ * 销售预测审核页面
+ * @description 提供预测申报的审核功能,包括查看详情、审批通过、审批拒绝等操作
+ */
+export default {
+  name: 'ForecastAudit',
+  mixins: [auditMixin]
+}
+</script>
+
+<style lang="scss">
+@import './index.scss';
+</style>