大家好,我是Java1234_小锋老师,分享一套锋哥原创的LLM大模型 基于LangChain的RAG AI智能办公OA系统(FastAPI+Vue3) 。

项目介绍
企业日常办公中,请假、报销、公告和制度咨询仍然大量依赖人工传递。传统办公自动化系统能够完成单据流转,却难以回答“年假还剩几天”“这张报销单到了哪一个节点”这类需要同时理解自然语言和业务数据的问题。把通用对话模型直接接入办公系统,又容易出现编造余额、越权查询他人单据和无法给出制度出处等问题。针对上述不足,本文设计并实现了基于LangChain的AI智能办公OA系统。
系统采用前后端分离结构。表现层使用Vue3、Vue Router、Pinia和Element Plus构建管理界面,并用ECharts按角色展示工作台统计。接口层使用Python语言和FastAPI框架提供REST接口,使用JWT完成登录认证,使用SQLAlchemy访问MySQL数据库db_ai_oa。智能层以LangChain组织对话循环:知识库文档经切分后写入Chroma向量库,问答时先做相似度检索,再按需调用假期余额、请假进度、报销进度和员工信息等工具。工具在服务端绑定当前登录用户,模型不能自行指定他人编号。请假超过3天、报销超过5000元时,审批由部门经理流转至总经理。
论文完成了需求分析、总体设计、数据库设计和主要功能实现。数据库给出用户、部门、假期余额、请假单、报销单、审批记录、公告、知识文档、会话和消息十个实体的属性图、联系图以及全部字段表。测试用例覆盖登录、越权访问、分级审批、智能填单和知识库拒答等分支。结果表明,该系统能够把制度问答、个人数据查询和审批流放在同一套办公入口中,并保证回答所依据的余额和进度来自数据库而非模型臆测。
源码下载
链接: https://pan.baidu.com/s/1EAvPYzaBK5NWg0364CoWTA?pwd=1234 提取码: 1234
系统展示 






核心代码
"""登录与个人中心接口。"""
from fastapi import APIRouter, Depends, File, UploadFile
from sqlalchemy.orm import Session
from app.database import get_db
from app.deps import current_user
from app.files import save_upload
from app.models import LoginUser
from app.response import ok
from app.services import auth_service
router = APIRouter()
@router.post("/api/auth/login")
def login(body: dict, db: Session = Depends(get_db)):
"""用户名密码登录。"""
return ok(auth_service.login(db, body.get("username"), body.get("password")))
@router.get("/api/auth/info")
def info(user: LoginUser = Depends(current_user), db: Session = Depends(get_db)):
"""当前登录用户详情。"""
return ok(auth_service.current_info(db, user.id))
@router.put("/api/profile")
def update_profile(body: dict, user: LoginUser = Depends(current_user), db: Session = Depends(get_db)):
"""修改个人资料。"""
auth_service.update_profile(db, user.id, body)
return ok()
@router.put("/api/profile/password")
def update_password(body: dict, user: LoginUser = Depends(current_user), db: Session = Depends(get_db)):
"""修改密码。"""
auth_service.update_password(db, user.id, body)
return ok()
@router.post("/api/profile/avatar")
async def upload_avatar(
file: UploadFile = File(…),
user: LoginUser = Depends(current_user),
db: Session = Depends(get_db),
):
"""上传头像。"""
content = await file.read()
url = save_upload(file, content, "avatar")
return ok(auth_service.save_avatar(db, user.id, url))
<template>
<div class="page-card">
<div class="toolbar">
<div class="search-form">
<el-input v-model="keyword" placeholder="部门名称" clearable style="width: 220px;" @keyup.enter="load" />
<el-button type="primary" @click="load">查询</el-button>
</div>
<el-button type="primary" @click="open()">新增部门</el-button>
</div>
<div class="table-wrap">
<el-table :data="table.records" stripe>
<el-table-column prop="deptName" label="部门名称" min-width="140" />
<el-table-column prop="deptCode" label="编码" min-width="100" />
<el-table-column prop="managerName" label="部门经理" min-width="120" />
<el-table-column prop="employeeCount" label="人数" min-width="80" />
<el-table-column prop="remark" label="备注" min-width="180" />
<el-table-column label="创建时间" min-width="170">
<template #default="{ row }">{{ formatDateTime(row.createTime) }}</template>
</el-table-column>
<el-table-column label="操作" min-width="140" fixed="right">
<template #default="{ row }">
<el-button link type="primary" @click="open(row)">编辑</el-button>
<el-button link type="danger" @click="remove(row)">删除</el-button>
</template>
</el-table-column>
</el-table>
</div>
<div class="pager"><el-pagination background layout="total, prev, pager, next" :total="table.total" v-model:current-page="pageNum" :page-size="pageSize" @current-change="load" /></div>
<el-dialog v-model="visible" :title="form.id ? '编辑部门' : '新增部门'" width="520px">
<el-form :model="form" label-width="90px">
<el-form-item label="名称"><el-input v-model="form.deptName" /></el-form-item>
<el-form-item label="编码"><el-input v-model="form.deptCode" /></el-form-item>
<el-form-item label="经理">
<el-select v-model="form.managerId" clearable filterable style="width: 100%;">
<el-option v-for="u in managers" :key="u.id" :label="u.realName" :value="u.id" />
</el-select>
</el-form-item>
<el-form-item label="排序"><el-input-number v-model="form.sortNum" /></el-form-item>
<el-form-item label="备注"><el-input v-model="form.remark" type="textarea" /></el-form-item>
</el-form>
<template #footer>
<el-button @click="visible = false">取消</el-button>
<el-button type="primary" @click="save">保存</el-button>
</template>
</el-dialog>
</div>
</template>
<script setup>
import { onMounted, reactive, ref } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import request from '@/utils/request'
import { formatDateTime } from '@/utils/format'
const keyword = ref('')
const pageNum = ref(1)
const pageSize = 10
const table = reactive({ total: 0, records: [] })
const visible = ref(false)
const form = reactive({})
const managers = ref([])
const load = async () => {
const res = await request.get('/api/dept/page', { params: { pageNum: pageNum.value, pageSize, keyword: keyword.value } })
table.total = res.data.total
table.records = res.data.records
}
const open = (row) => {
Object.keys(form).forEach((k) => delete form[k]) // 把弹窗表单 form 里已有的字段全部删掉,先清空再填新数据。
Object.assign(form, row ? { …row } : { sortNum: 1, parentId: 0 })
visible.value = true
}
const save = async () => {
if (form.id) await request.put('/api/dept', form)
else await request.post('/api/dept', form)
visible.value = false
ElMessage.success('保存成功')
load()
}
const remove = async (row) => {
await ElMessageBox.confirm('确认删除该部门?')
await request.delete(`/api/dept/${row.id}`)
ElMessage.success('已删除')
load()
}
onMounted(async () => {
const res = await request.get('/api/employee/managers')
managers.value = res.data || []
load()
})
</script>




