使用node开发前后端分离的进销存管理系统学习项目

0 阅读3分钟

今天给大家分享一个使用node开发的一个前后端分离 进销存系统。

后端框架:Express

前端:Vue 2 + Element UI

数据库:mysql8

这个系统主要实现了以下菜单功能:

系统管理、菜单管理、角色管理、账号管理、基础资料、商品档案、客户档案、供应商档案、仓库信息、员工管理、部门管理、报表分析、销售报表、采购报表、库存管理、出入库记录、采购管理、采购订单、采购入库、采购退货、销售管理、销售订单、销售出库、销售退货、首页、登录

个别页面的截图:

首页:

image.png

角色管理:

image.png

采购报表:

image.png

代码目录:

image.png

个别页面的代码实现:

<template>
  <div class="home-container">
    <div class="statistics-cards">
      <el-card class="stat-card" shadow="hover">
        <div class="stat-content">
          <div class="stat-icon sales-icon">
            <i class="el-icon-s-order"></i>
          </div>
          <div class="stat-info">
            <div class="stat-value">{{ statistics.salesOrderCount || 0 }}</div>
            <div class="stat-label">销售订单</div>
          </div>
        </div>
      </el-card>
      <el-card class="stat-card" shadow="hover">
        <div class="stat-content">
          <div class="stat-icon purchase-icon">
            <i class="el-icon-shopping-cart-2"></i>
          </div>
          <div class="stat-info">
            <div class="stat-value">{{ statistics.purchaseOrderCount || 0 }}</div>
            <div class="stat-label">采购订单</div>
          </div>
        </div>
      </el-card>
      <el-card class="stat-card" shadow="hover">
        <div class="stat-content">
          <div class="stat-icon product-icon">
            <i class="el-icon-goods"></i>
          </div>
          <div class="stat-info">
            <div class="stat-value">{{ statistics.productCount || 0 }}</div>
            <div class="stat-label">商品数量</div>
          </div>
        </div>
      </el-card>
    </div>

    <div class="charts-container">
      <el-card class="chart-card" shadow="hover">
        <div slot="header" class="chart-header">
          <span>销售与采购金额统计</span>
        </div>
        <div ref="amountChart" class="chart"></div>
      </el-card>
      <el-card class="chart-card" shadow="hover">
        <div slot="header" class="chart-header">
          <span>订单数量统计</span>
        </div>
        <div ref="orderChart" class="chart"></div>
      </el-card>
    </div>
  </div>
</template>

<script>
import * as echarts from 'echarts'
import { getHomeStatistics } from '@/api/homeStatistics'

export default {
  name: 'Home',
  data() {
    return {
      statistics: {
        salesOrderCount: 0,
        purchaseOrderCount: 0,
        productCount: 0,
        salesAmount: 0,
        purchaseAmount: 0
      },
      amountChart: null,
      orderChart: null
    }
  },
  mounted() {
    this.loadStatistics()
    window.addEventListener('resize', this.handleResize)
  },
  beforeDestroy() {
    window.removeEventListener('resize', this.handleResize)
    if (this.amountChart) {
      this.amountChart.dispose()
    }
    if (this.orderChart) {
      this.orderChart.dispose()
    }
  },
  methods: {
    loadStatistics() {
      getHomeStatistics().then(res => {
        if (res.code === 1000) {
          this.statistics = res.data || {}
          this.$nextTick(() => {
            this.initAmountChart()
            this.initOrderChart()
          })
        }
      })
    },
    initAmountChart() {
      if (!this.$refs.amountChart) return
      
      this.amountChart = echarts.init(this.$refs.amountChart)
      const option = {
        tooltip: {
          trigger: 'axis',
          formatter: '{b}<br/>{a}: ¥{c}'
        },
        grid: {
          left: '3%',
          right: '4%',
          bottom: '3%',
          containLabel: true
        },
        xAxis: {
          type: 'category',
          data: ['销售金额', '采购金额']
        },
        yAxis: {
          type: 'value'
        },
        series: [
          {
            name: '金额',
            type: 'bar',
            data: [
              this.statistics.salesAmount || 0,
              this.statistics.purchaseAmount || 0
            ],
            itemStyle: {
              color: function(params) {
                const colors = ['#409EFF', '#67C23A']
                return colors[params.dataIndex]
              }
            },
            barWidth: '50%'
          }
        ]
      }
      this.amountChart.setOption(option)
    },
    initOrderChart() {
      if (!this.$refs.orderChart) return
      
      this.orderChart = echarts.init(this.$refs.orderChart)
      const option = {
        tooltip: {
          trigger: 'item',
          formatter: '{a} <br/>{b}: {c} ({d}%)'
        },
        legend: {
          orient: 'vertical',
          left: 'left'
        },
        series: [
          {
            name: '订单数量',
            type: 'pie',
            radius: '50%',
            data: [
              { value: this.statistics.salesOrderCount || 0, name: '销售订单' },
              { value: this.statistics.purchaseOrderCount || 0, name: '采购订单' }
            ],
            emphasis: {
              itemStyle: {
                shadowBlur: 10,
                shadowOffsetX: 0,
                shadowColor: 'rgba(0, 0, 0, 0.5)'
              }
            }
          }
        ]
      }
      this.orderChart.setOption(option)
    },
    handleResize() {
      if (this.amountChart) {
        this.amountChart.resize()
      }
      if (this.orderChart) {
        this.orderChart.resize()
      }
    }
  }
}
</script>

<style lang="scss" scoped>
@use '@/styles/index.scss' as *;

.home-container {
  min-height: 100%;
  display: flex;
  flex-direction: column;
  background-color: #f5f5f5;
  padding: 20px;

  .statistics-cards {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
    gap: 20px;
    margin-bottom: 20px;

    .stat-card {
      border-radius: 8px;
      border: none;
      box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);

      ::v-deep .el-card__body {
        padding: 20px;
      }

      .stat-content {
        display: flex;
        align-items: center;
        gap: 20px;

        .stat-icon {
          width: 60px;
          height: 60px;
          border-radius: 12px;
          display: flex;
          align-items: center;
          justify-content: center;
          font-size: 28px;
          color: white;
          opacity: 0.9;

          &.sales-icon {
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
          }

          &.purchase-icon {
            background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
          }

          &.product-icon {
            background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
          }
        }

        .stat-info {
          flex: 1;

          .stat-value {
            font-size: 32px;
            font-weight: 700;
            color: #262626;
            margin-bottom: 8px;
            line-height: 1;
          }

          .stat-label {
            font-size: 14px;
            color: #8c8c8c;
            font-weight: 500;
          }
        }
      }
    }
  }

  .charts-container {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(500px, 1fr));
    gap: 20px;

    .chart-card {
      border-radius: 8px;
      border: none;
      box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);

      ::v-deep .el-card__header {
        padding: 16px 20px;
        border-bottom: 1px solid #f0f0f0;
      }

      ::v-deep .el-card__body {
        padding: 20px;
      }

      .chart-header {
        font-size: 16px;
        font-weight: 600;
        color: #262626;
      }

      .chart {
        width: 100%;
        height: 400px;
      }
    }
  }
}
</style>

const DepartmentService = require('../services/department.service')
const Response = require('../utils/response')
const asyncHandler = require('../utils/asyncHandler')

class DepartmentController {
  /**
   * 获取部门列表(分页查询)
   */
  pageList = asyncHandler(async (req, res) => {
    const result = await DepartmentService.pageList(req.query)
    res.json(Response.success(result))
  })

  /**
   * 添加部门
   */
  add = asyncHandler(async (req, res) => {
    const result = await DepartmentService.add(req.body)
    if (result) {
      res.json(Response.success(null, '新增成功'))
    } else {
      res.json(Response.error('新增失败'))
    }
  })

  /**
   * 更新部门信息
   */
  updateDepartment = asyncHandler(async (req, res) => {
    const result = await DepartmentService.updateDepartment(req.body)
    if (result) {
      res.json(Response.success(null, '更新成功'))
    } else {
      res.json(Response.error('更新失败'))
    }
  })

  /**
   * 删除部门
   */
  deleteDepartment = asyncHandler(async (req, res) => {
    const result = await DepartmentService.deleteDepartment(req.query.id)
    if (result) {
      res.json(Response.success(null, '删除成功'))
    } else {
      res.json(Response.error('删除失败'))
    }
  })
}

module.exports = new DepartmentController()

如果你最近在学习node开发,希望这个项目能帮助到你~项目搭建了演示站,有兴趣的小伙伴可以去看看,也可以自己尝试照着 写一写,在刚刚开始学习编程的时候,需要写一些完整的项目来巩固我们的技术。 演示站地址: test.wwwoop.com/?s=jin-xiao…