今天给大家分享一个使用node开发的一个前后端分离 进销存系统。
后端框架:Express
前端:Vue 2 + Element UI
数据库:mysql8
这个系统主要实现了以下菜单功能:
系统管理、菜单管理、角色管理、账号管理、基础资料、商品档案、客户档案、供应商档案、仓库信息、员工管理、部门管理、报表分析、销售报表、采购报表、库存管理、出入库记录、采购管理、采购订单、采购入库、采购退货、销售管理、销售订单、销售出库、销售退货、首页、登录
个别页面的截图:
首页:
角色管理:
采购报表:
代码目录:
个别页面的代码实现:
<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…