大家好,我是Java1234_小锋老师,分享一套锋哥原创的基于Python的设备管理系统(FastAPI+Vue3)

项目介绍

随着信息技术的快速发展和企事业单位资产规模的不断扩大,设备数量日益增多、种类日趋复杂,传统依靠人工登记台账、纸质单据流转的设备管理方式已难以满足高效、准确、可追溯的管理需求,普遍存在信息更新滞后、借还记录混乱、维修保养不及时、报废流程不规范以及数据统计困难等问题。为解决上述问题,本文设计并实现了一套基于Python的设备管理系统,采用前后端分离的架构模式,后端使用FastAPI框架构建高性能RESTful接口,前端使用Vue3框架搭建响应式单页面应用,数据持久化采用MySQL关系型数据库。

论文首先分析了系统的研究背景与意义,调研了国内外设备管理系统的研究现状;随后重点介绍了Python语言、FastAPI框架、Vue3框架及MySQL等关键技术;在此基础上对系统进行了可行性分析和需求分析,完成了系统总体架构设计、功能模块设计、数据库设计,并绘制了系统功能结构图、时序图和E-R图;接着按照功能模块对系统进行编码实现,涵盖登录认证、系统管理、基础信息管理、设备信息管理、设备借用归还、设备维修保养、设备报废以及统计报表等模块,并给出了各模块的核心代码;最后对系统进行了功能测试。

测试结果表明,该系统运行稳定、操作便捷、功能完善,能够有效提高设备管理的规范化与信息化水平,具有较好的实用价值和推广意义。

源码下载 

链接: https://pan.baidu.com/s/1OCGf5tPid9sUkqxJThTYcg?pwd=1234
提取码: 1234

系统展示

核心代码

"""
部门管理业务服务
"""
from sqlalchemy.orm import Session

from models.dept import Dept
from models.equipment import Equipment
from models.user import User
from schemas.dept import DeptSaveRequest, DeptVO
from utils.datetime_util import format_datetime


def dept_to_vo(dept: Dept) -> dict:
    """将部门 ORM 转为视图字典"""
    vo = DeptVO(
        id=dept.id,
        dept_no=dept.dept_no,
        dept_name=dept.dept_name,
        leader=dept.leader,
        phone=dept.phone,
        create_time=format_datetime(dept.create_time),
    )
    return vo.model_dump(by_alias=True)


def get_dept_page(db: Session, page: int, size: int, dept_name: str | None = None) -> tuple[list, int]:
    """分页查询部门"""
    query = db.query(Dept)
    if dept_name:
        query = query.filter(Dept.dept_name.like(f"%{dept_name}%"))

    total = query.count()
    depts = query.order_by(Dept.id.asc()).offset((page - 1) * size).limit(size).all()
    records = [dept_to_vo(d) for d in depts]
    return records, total


def get_dept_list(db: Session) -> list:
    """获取全部部门列表(下拉用)"""
    depts = db.query(Dept).order_by(Dept.id.asc()).all()
    return [dept_to_vo(d) for d in depts]


def create_dept(db: Session, req: DeptSaveRequest) -> tuple[bool, str]:
    """创建部门"""
    dept = Dept(
        dept_no=req.dept_no,
        dept_name=req.dept_name,
        leader=req.leader,
        phone=req.phone,
    )
    db.add(dept)
    db.commit()
    return True, "创建成功"


def update_dept(db: Session, req: DeptSaveRequest) -> tuple[bool, str]:
    """更新部门"""
    dept = db.query(Dept).filter(Dept.id == req.id).first()
    if not dept:
        return False, "部门不存在"

    dept.dept_no = req.dept_no
    dept.dept_name = req.dept_name
    dept.leader = req.leader
    dept.phone = req.phone
    db.commit()
    return True, "更新成功"


def delete_dept(db: Session, dept_id: int) -> tuple[bool, str]:
    """删除部门"""
    dept = db.query(Dept).filter(Dept.id == dept_id).first()
    if not dept:
        return False, "部门不存在"

    user_count = db.query(User).filter(User.dept_id == dept_id).count()
    if user_count > 0:
        return False, "该部门下存在用户,无法删除"

    equip_count = db.query(Equipment).filter(Equipment.dept_id == dept_id).count()
    if equip_count > 0:
        return False, "该部门下存在设备,无法删除"

    db.delete(dept)
    db.commit()
    return True, "删除成功"
<template>
  <div class="page-container">
    <div class="search-bar">
      <el-form :inline="true" :model="query">
        <el-form-item label="状态">
          <el-select v-model="query.status" placeholder="请选择" clearable style="width: 140px">
            <el-option label="待审核" value="待审核" />
            <el-option label="已驳回" value="已驳回" />
            <el-option label="借用中" value="借用中" />
            <el-option label="已归还" value="已归还" />
            <el-option label="逾期" value="逾期" />
          </el-select>
        </el-form-item>
        <el-form-item>
          <el-button type="primary" :icon="Search" @click="loadData">查询</el-button>
          <el-button :icon="Refresh" @click="resetQuery">重置</el-button>
        </el-form-item>
      </el-form>
    </div>

    <div class="table-card">
      <div style="margin-bottom: 16px">
        <el-button type="primary" :icon="Plus" @click="handleApply">申请借用</el-button>
      </div>
      <el-table :data="tableData" stripe border style="width: 100%">
        <el-table-column prop="equipmentNo" label="设备编号" min-width="120" />
        <el-table-column prop="equipmentName" label="设备名称" min-width="140" show-overflow-tooltip />
        <el-table-column prop="userName" label="借用人" min-width="100" />
        <el-table-column prop="createTime" label="申请时间" min-width="170">
          <template #default="{ row }">{{ formatDateTime(row.createTime) }}</template>
        </el-table-column>
        <el-table-column prop="borrowTime" label="借用时间" min-width="170">
          <template #default="{ row }">{{ formatDateTime(row.borrowTime) }}</template>
        </el-table-column>
        <el-table-column prop="expectReturnTime" label="预计归还" min-width="170">
          <template #default="{ row }">{{ formatDateTime(row.expectReturnTime) }}</template>
        </el-table-column>
        <el-table-column prop="actualReturnTime" label="实际归还" min-width="170">
          <template #default="{ row }">{{ formatDateTime(row.actualReturnTime) }}</template>
        </el-table-column>
        <el-table-column prop="status" label="状态" min-width="90">
          <template #default="{ row }">
            <el-tag :type="borrowTagType(row.status)" size="small">{{ row.status }}</el-tag>
          </template>
        </el-table-column>
        <el-table-column prop="auditUserName" label="审核人" min-width="100" />
        <el-table-column prop="rejectReason" label="驳回原因" min-width="120" show-overflow-tooltip />
        <el-table-column prop="remark" label="备注" min-width="120" show-overflow-tooltip />
        <el-table-column label="操作" width="220" fixed="right">
          <template #default="{ row }">
            <el-button v-if="row.status === '待审核' && isAdmin" type="primary" link @click="handleApprove(row)">通过</el-button>
            <el-button v-if="row.status === '待审核' && isAdmin" type="warning" link @click="handleReject(row)">驳回</el-button>
            <el-button v-if="row.status === '借用中'" type="success" link @click="handleReturn(row)">归还</el-button>
            <el-button v-if="isAdmin" type="danger" link @click="handleDelete(row)">删除</el-button>
          </template>
        </el-table-column>
      </el-table>
      <div class="pagination-wrap">
        <el-pagination v-model:current-page="query.page" v-model:page-size="query.size"
          :total="total" :page-sizes="[10, 20, 50]" layout="total, sizes, prev, pager, next" @change="loadData" />
      </div>
    </div>

    <!-- 申请借用对话框 -->
    <el-dialog v-model="dialogVisible" title="申请借用设备" width="500px" destroy-on-close>
      <el-form ref="formRef" :model="form" :rules="rules" label-width="110px">
        <el-form-item label="选择设备" prop="equipmentId">
          <el-select v-model="form.equipmentId" placeholder="请选择可借用设备" style="width: 100%" filterable>
            <el-option v-for="e in availableList" :key="e.id"
              :label="`${e.equipmentNo} - ${e.equipmentName}`" :value="e.id" />
          </el-select>
        </el-form-item>
        <el-form-item label="预计归还时间" prop="expectReturnTime">
          <el-date-picker v-model="form.expectReturnTime" type="datetime"
            value-format="YYYY-MM-DD HH:mm:ss" placeholder="选择预计归还时间" style="width: 100%" />
        </el-form-item>
        <el-form-item label="备注">
          <el-input v-model="form.remark" type="textarea" :rows="2" />
        </el-form-item>
      </el-form>
      <template #footer>
        <el-button @click="dialogVisible = false">取消</el-button>
        <el-button type="primary" @click="handleSubmit">提交申请</el-button>
      </template>
    </el-dialog>

    <!-- 驳回对话框 -->
    <el-dialog v-model="rejectDialogVisible" title="驳回借用申请" width="450px" destroy-on-close>
      <el-form ref="rejectFormRef" :model="rejectForm" :rules="rejectRules" label-width="90px">
        <el-form-item label="驳回原因" prop="rejectReason">
          <el-input v-model="rejectForm.rejectReason" type="textarea" :rows="3" placeholder="请填写驳回原因" />
        </el-form-item>
      </el-form>
      <template #footer>
        <el-button @click="rejectDialogVisible = false">取消</el-button>
        <el-button type="warning" @click="confirmReject">确认驳回</el-button>
      </template>
    </el-dialog>
  </div>
</template>

<script setup>
import { ref, reactive, computed, onMounted } from 'vue'
import { Search, Refresh, Plus } from '@element-plus/icons-vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { getBorrowPage, applyBorrow, returnEquipment, deleteBorrow, approveBorrow, rejectBorrow } from '@/api/borrow'
import { getAvailableEquipment } from '@/api/equipment'
import { formatDateTime } from '@/utils/format'
import { useUserStore } from '@/store/user'

const userStore = useUserStore()
const isAdmin = computed(() => userStore.isAdmin())

const query = reactive({ page: 1, size: 10, status: '' })
const tableData = ref([])
const total = ref(0)
const availableList = ref([])
const dialogVisible = ref(false)
const rejectDialogVisible = ref(false)
const formRef = ref(null)
const rejectFormRef = ref(null)
const currentRejectId = ref(null)
const form = reactive({ equipmentId: null, expectReturnTime: '', remark: '' })
const rejectForm = reactive({ rejectReason: '' })
const rules = {
  equipmentId: [{ required: true, message: '请选择设备', trigger: 'change' }],
  expectReturnTime: [{ required: true, message: '请选择预计归还时间', trigger: 'change' }]
}
const rejectRules = {
  rejectReason: [{ required: true, message: '请填写驳回原因', trigger: 'blur' }]
}

function borrowTagType(status) {
  const map = { '待审核': 'info', '已驳回': 'danger', '借用中': 'warning', '已归还': 'success', '逾期': 'danger' }
  return map[status] || 'info'
}

async function loadData() {
  const res = await getBorrowPage(query)
  tableData.value = res.data.records
  total.value = res.data.total
}

function resetQuery() {
  Object.assign(query, { page: 1, status: '' })
  loadData()
}

async function handleApply() {
  const res = await getAvailableEquipment()
  availableList.value = res.data
  Object.assign(form, { equipmentId: null, expectReturnTime: '', remark: '' })
  dialogVisible.value = true
}

async function handleApprove(row) {
  await ElMessageBox.confirm(`确定通过「${row.equipmentName}」的借用申请吗?`, '审核通过', { type: 'info' })
  await approveBorrow(row.id)
  ElMessage.success('审核通过,设备已借出')
  loadData()
}

function handleReject(row) {
  currentRejectId.value = row.id
  Object.assign(rejectForm, { rejectReason: '' })
  rejectDialogVisible.value = true
}

async function confirmReject() {
  await rejectFormRef.value.validate()
  await rejectBorrow(currentRejectId.value, { rejectReason: rejectForm.rejectReason })
  ElMessage.success('已驳回该借用申请')
  rejectDialogVisible.value = false
  loadData()
}

async function handleReturn(row) {
  await ElMessageBox.confirm('确定归还该设备吗?', '提示', { type: 'info' })
  await returnEquipment(row.id)
  ElMessage.success('归还成功')
  loadData()
}

async function handleDelete(row) {
  await ElMessageBox.confirm('确定删除该记录吗?', '提示', { type: 'warning' })
  await deleteBorrow(row.id)
  ElMessage.success('删除成功')
  loadData()
}

async function handleSubmit() {
  await formRef.value.validate()
  await applyBorrow(form)
  ElMessage.success('申请已提交,等待管理员审核')
  dialogVisible.value = false
  loadData()
}

onMounted(() => loadData())
</script>

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐