Prechádzať zdrojové kódy

feat(forecast): 新增预测管理模块的路由、API和页面

yz 1 mesiac pred
rodič
commit
c63b3cceae

+ 46 - 0
src/api/forecast/index.js

@@ -0,0 +1,46 @@
+import request from '@/router/axios';
+
+/**
+ * 获取预测提报列表
+ * @param {number} current 当前页码
+ * @param {number} size 每页大小
+ * @param {object} params 查询参数
+ * @returns {Promise} 返回预测提报列表数据
+ */
+export const getForecastList = (current, size, params) => {
+  return request({
+    url: '/api/blade-forecast/list',
+    method: 'get',
+    params: {
+      ...params,
+      current,
+      size,
+    }
+  });
+};
+
+/**
+ * 获取品牌列表
+ * @returns {Promise} 返回品牌选项数据
+ */
+export const getBrandList = () => {
+  return request({
+    url: '/api/blade-forecast/brands',
+    method: 'get'
+  });
+};
+
+/**
+ * 获取预测提报详情
+ * @param {string|number} id 预测提报ID
+ * @returns {Promise} 返回预测提报详情数据
+ */
+export const getForecastDetail = (id) => {
+  return request({
+    url: '/api/blade-forecast/detail',
+    method: 'get',
+    params: {
+      id
+    }
+  });
+};

+ 13 - 1
src/router/views/index.js

@@ -35,11 +35,23 @@ export default [{
         name: '公告管理',
         meta: {
             keepAlive: true,
-            isAuth: false
         },
         component: () => import( /* webpackChunkName: "views" */ '@/views/announcement/index')
     }]
 }, {
+    path: '/forecast',
+    component: Layout,
+    redirect: '/forecast/index',
+    children: [{
+        path: 'index',
+        name: '预测管理',
+        meta: {
+            keepAlive: true,
+            isAuth: false
+        },
+        component: () => import( /* webpackChunkName: "views" */ '@/views/forecast/index')
+    }]
+}, {
     path: '/dict-horizontal',
     component: Layout,
     redirect: '/dict-horizontal/index',

+ 510 - 0
src/views/forecast/index.vue

@@ -0,0 +1,510 @@
+<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"
+      @row-click="handleRowClick">
+
+      <!-- 自定义审核状态显示 -->
+      <template slot-scope="{row}" slot="auditStatus">
+        <el-tag
+          :type="getAuditStatusType(row.auditStatus)"
+          size="small">
+          {{ getAuditStatusText(row.auditStatus) }}
+        </el-tag>
+      </template>
+
+      <!-- 自定义品牌显示 -->
+      <template slot-scope="{row}" slot="brand">
+        <el-tag size="small">{{ row.brandName }}</el-tag>
+      </template>
+
+      <!-- 自定义物料价格显示 -->
+      <template slot-scope="{row}" slot="materialPrice">
+        <span class="price-text">¥{{ formatPrice(row.materialPrice) }}</span>
+      </template>
+    </avue-crud>
+
+    <!-- 详情查看对话框 -->
+    <el-dialog
+      title="预测提报详情"
+      :visible.sync="detailVisible"
+      width="80%"
+      append-to-body
+      :close-on-click-modal="false">
+      <div class="forecast-detail" v-if="currentDetail">
+        <el-row :gutter="20">
+          <el-col :span="12">
+            <div class="detail-item">
+              <label>年月:</label>
+              <span>{{ currentDetail.yearMonth }}</span>
+            </div>
+            <div class="detail-item">
+              <label>品牌:</label>
+              <span>{{ currentDetail.brandName }}</span>
+            </div>
+            <div class="detail-item">
+              <label>规格:</label>
+              <span>{{ currentDetail.specification }}</span>
+            </div>
+            <div class="detail-item">
+              <label>花纹:</label>
+              <span>{{ currentDetail.pattern }}</span>
+            </div>
+            <div class="detail-item">
+              <label>物料编号:</label>
+              <span>{{ currentDetail.materialCode }}</span>
+            </div>
+          </el-col>
+          <el-col :span="12">
+            <div class="detail-item">
+              <label>物料描述:</label>
+              <span>{{ currentDetail.materialDescription }}</span>
+            </div>
+            <div class="detail-item">
+              <label>物料价格:</label>
+              <span class="price-text">¥{{ formatPrice(currentDetail.materialPrice) }}</span>
+            </div>
+            <div class="detail-item">
+              <label>月初需求量:</label>
+              <span>{{ formatQuantity(currentDetail.earlyMonthDemand) }}</span>
+            </div>
+            <div class="detail-item">
+              <label>月中需求量:</label>
+              <span>{{ formatQuantity(currentDetail.midMonthDemand) }}</span>
+            </div>
+            <div class="detail-item">
+              <label>月末需求量:</label>
+              <span>{{ formatQuantity(currentDetail.endMonthDemand) }}</span>
+            </div>
+          </el-col>
+        </el-row>
+        <div class="detail-item full-width">
+          <label>审核状态:</label>
+          <el-tag
+            :type="getAuditStatusType(currentDetail.auditStatus)"
+            size="medium">
+            {{ getAuditStatusText(currentDetail.auditStatus) }}
+          </el-tag>
+        </div>
+      </div>
+      <span slot="footer" class="dialog-footer">
+        <el-button @click="detailVisible = false">关 闭</el-button>
+      </span>
+    </el-dialog>
+  </basic-container>
+</template>
+
+<script>
+import { getForecastList, getBrandList, getForecastDetail } from '@/api/forecast';
+import { mapGetters } from 'vuex';
+
+/**
+ * 预测提报管理页面
+ * 提供预测提报数据的查询、搜索、详情查看功能
+ * 支持按年月、品牌进行筛选
+ */
+export default {
+  name: 'ForecastIndex',
+  data() {
+    return {
+      form: {},
+      query: {},
+      loading: true,
+      detailVisible: false,
+      currentDetail: null,
+      page: {
+        pageSize: 10,
+        currentPage: 1,
+        total: 0
+      },
+      brandOptions: [],
+      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,
+        column: [
+          {
+            label: '年月',
+            prop: 'yearMonth',
+            type: 'month',
+            format: 'yyyy-MM',
+            valueFormat: 'yyyy-MM',
+            search: true,
+            searchSpan: 6,
+            width: 120,
+            sortable: true,
+            rules: [{
+              required: true,
+              message: '请选择年月',
+              trigger: 'change'
+            }]
+          },
+          {
+            label: '品牌',
+            prop: 'brandId',
+            type: 'select',
+            dicData: [],
+            props: {
+              label: 'brandName',
+              value: 'id'
+            },
+            slot: true,
+            search: true,
+            searchSpan: 6,
+            width: 120,
+            overHidden: true
+          },
+          {
+            label: '规格',
+            prop: 'specification',
+            search: false,
+            width: 150,
+            overHidden: true
+          },
+          {
+            label: '花纹',
+            prop: 'pattern',
+            search: false,
+            width: 120,
+            overHidden: true
+          },
+          {
+            label: '物料描述',
+            prop: 'materialDescription',
+            search: false,
+            width: 200,
+            overHidden: true
+          },
+          {
+            label: '物料价格',
+            prop: 'materialPrice',
+            type: 'number',
+            slot: true,
+            search: false,
+            width: 120,
+            align: 'right'
+          },
+          {
+            label: '月末需求量',
+            prop: 'endMonthDemand',
+            type: 'number',
+            search: false,
+            width: 120,
+            align: 'right'
+          },
+          {
+            label: '月中需求量',
+            prop: 'midMonthDemand',
+            type: 'number',
+            search: false,
+            width: 120,
+            align: 'right'
+          },
+          {
+            label: '月初需求量',
+            prop: 'earlyMonthDemand',
+            type: 'number',
+            search: false,
+            width: 120,
+            align: 'right'
+          },
+          {
+            label: '物料编号',
+            prop: 'materialCode',
+            search: false,
+            width: 150,
+            overHidden: true
+          },
+          {
+            label: '审核状态',
+            prop: 'auditStatus',
+            type: 'select',
+            dicData: [
+              { label: '待审核', value: 'pending' },
+              { label: '审核中', value: 'reviewing' },
+              { label: '已通过', value: 'approved' },
+              { label: '已拒绝', value: 'rejected' }
+            ],
+            slot: true,
+            search: false,
+            width: 120
+          }
+        ]
+      },
+      data: []
+    };
+  },
+  computed: {
+    ...mapGetters(['permission']),
+    /**
+     * 权限配置
+     * 预测提报页面只提供查看功能,不提供增删改操作
+     */
+    permissionList() {
+      return {
+        addBtn: false,
+        viewBtn: true,
+        delBtn: false,
+        editBtn: false
+      };
+    }
+  },
+  created() {
+    this.loadBrandOptions();
+  },
+  methods: {
+    /**
+     * 加载品牌选项数据
+     * 异步获取品牌列表并更新到对应的列配置中
+     */
+    async loadBrandOptions() {
+      try {
+        const res = await getBrandList();
+        this.brandOptions = res.data.data || [];
+        const brandColumn = this.option.column.find(col => col.prop === 'brandId');
+        if (brandColumn) {
+          brandColumn.dicData = this.brandOptions;
+        }
+      } catch (error) {
+        console.error('加载品牌选项失败:', error);
+        // 提供默认的品牌选项
+        this.brandOptions = [
+          { id: 1, brandName: '品牌A' },
+          { id: 2, brandName: '品牌B' },
+          { id: 3, brandName: '品牌C' }
+        ];
+        const brandColumn = this.option.column.find(col => col.prop === 'brandId');
+        if (brandColumn) {
+          brandColumn.dicData = this.brandOptions;
+        }
+      }
+    },
+
+    /**
+     * 处理行点击事件
+     * @param {Object} row 行数据
+     * @param {Object} event 事件对象
+     * @param {Object} column 列配置
+     */
+    async handleRowClick(row, event, column) {
+      if (column && column.property === 'auditStatus') {
+        return; // 点击状态列不触发详情查看
+      }
+      await this.viewDetail(row);
+    },
+
+    /**
+     * 查看详情
+     * @param {Object} row 行数据
+     */
+    async viewDetail(row) {
+      try {
+        const res = await getForecastDetail(row.id);
+        this.currentDetail = res.data.data;
+        this.detailVisible = true;
+      } catch (error) {
+        console.error('获取详情失败:', error);
+        this.$message.error('获取详情失败,请稍后重试');
+      }
+    },
+
+    /**
+     * 搜索重置
+     */
+    searchReset() {
+      this.query = {};
+      this.onLoad(this.page);
+    },
+
+    /**
+     * 搜索条件变化
+     * @param {Object} params 搜索参数
+     * @param {Function} done 完成回调
+     */
+    searchChange(params, done) {
+      this.query = params;
+      this.page.currentPage = 1;
+      this.onLoad(this.page, params);
+      done();
+    },
+
+    /**
+     * 页码变化
+     * @param {number} currentPage 当前页码
+     */
+    currentChange(currentPage) {
+      this.page.currentPage = currentPage;
+    },
+
+    /**
+     * 页大小变化
+     * @param {number} pageSize 页大小
+     */
+    sizeChange(pageSize) {
+      this.page.pageSize = pageSize;
+    },
+
+    /**
+     * 刷新数据
+     */
+    refreshChange() {
+      this.onLoad(this.page, this.query);
+    },
+
+    /**
+     * 加载数据
+     * @param {Object} page 分页参数
+     * @param {Object} params 查询参数
+     */
+    async onLoad(page, params = {}) {
+      this.loading = true;
+      try {
+        const res = await getForecastList(page.currentPage, page.pageSize, {
+          ...params,
+          ...this.query
+        });
+        const data = res.data.data;
+        this.page.total = data.total;
+        this.data = data.records;
+      } catch (error) {
+        console.error('加载数据失败:', error);
+        this.$message.error('加载数据失败,请稍后重试');
+        this.data = [];
+        this.page.total = 0;
+      } finally {
+        this.loading = false;
+      }
+    },
+
+    /**
+     * 获取审核状态对应的标签类型
+     * @param {string} status 审核状态
+     * @returns {string} 标签类型
+     */
+    getAuditStatusType(status) {
+      const statusMap = {
+        pending: 'warning',
+        reviewing: 'primary',
+        approved: 'success',
+        rejected: 'danger'
+      };
+      return statusMap[status] || 'info';
+    },
+
+    /**
+     * 获取审核状态显示文本
+     * @param {string} status 审核状态
+     * @returns {string} 显示文本
+     */
+    getAuditStatusText(status) {
+      const statusMap = {
+        pending: '待审核',
+        reviewing: '审核中',
+        approved: '已通过',
+        rejected: '已拒绝'
+      };
+      return statusMap[status] || '未知状态';
+    },
+
+    /**
+     * 格式化价格显示
+     * @param {number} price 价格
+     * @returns {string} 格式化后的价格
+     */
+    formatPrice(price) {
+      if (price == null || price === '') return '0.00';
+      return Number(price).toFixed(2).replace(/\B(?=(\d{3})+(?!\d))/g, ',');
+    },
+
+    /**
+     * 格式化数量显示
+     * @param {number} quantity 数量
+     * @returns {string} 格式化后的数量
+     */
+    formatQuantity(quantity) {
+      if (quantity == null || quantity === '') return '0';
+      return Number(quantity).toLocaleString();
+    }
+  }
+};
+</script>
+
+<style scoped>
+.forecast-detail {
+  padding: 20px;
+}
+
+.detail-item {
+  margin-bottom: 15px;
+  display: flex;
+  align-items: center;
+}
+
+.detail-item.full-width {
+  width: 100%;
+  margin-top: 20px;
+  padding-top: 20px;
+  border-top: 1px solid #e4e7ed;
+}
+
+.detail-item label {
+  font-weight: bold;
+  color: #606266;
+  min-width: 100px;
+  margin-right: 10px;
+}
+
+.detail-item span {
+  color: #303133;
+  flex: 1;
+}
+
+.price-text {
+  color: #e6a23c;
+  font-weight: bold;
+}
+
+.dialog-footer {
+  text-align: center;
+}
+
+/* 表格行悬停效果 */
+::v-deep .el-table__row:hover {
+  cursor: pointer;
+}
+
+/* 搜索表单样式优化 */
+::v-deep .avue-crud__search .el-form-item {
+  margin-bottom: 10px;
+}
+
+/* 状态标签样式 */
+::v-deep .el-tag {
+  border-radius: 4px;
+}
+</style>