☰
ThinkPHP6+Layui+MySQL开源OA系统:部署、避坑与二次开发指南
2026/10/22 10:13:31 网站建设 项目流程

简介:勾股OA是一套基于ThinkPHP6+Layui+MySql的开源企业办公系统,定位为中小企业快速搭建办公自动化的解决方案,系统简约、开箱即用,集成了系统设置、人事管理、消息管理、审批管理、日常办公、客户管理、合同管理、项目管理、财务管理等模块,也可二次开发为CRM、ERP等业务系统。资源包共997个文件,压缩包大小10.3MB,以329个html页面模板、275个js交互脚本、191个php后端逻辑、84个css样式表为核心,另附sql数据库脚本、字体图标、配置文件及说明文档,覆盖前端展示、前后端交互、业务逻辑与数据存储,便于直接部署或按需改造。目前已有319人学习下载。源码目录结构清晰,适合PHP开发者学习ThinkPHP6与Layui的整合开发,也可直接搭建试用;通过模块划分可快速定位功能代码,理解企业级应用的架构设计,为二次开发或业务扩展提供可靠基础。

1. 开箱即用的开源 OA 办公系统:为什么我最后选了 ThinkPHP6 + Layui + MySQL

在给团队内部做 OA 办公选型的那段时间,我几乎把市面上开源的、闭源的商业系统都翻了一遍,最后反而是一套基于 ThinkPHP6+Layui+MySQL 的开源企业办公系统落地最快。它开箱即用,装完就具备系统设置、人事管理、消息管理、审批管理、日常办公、客户管理、合同管理、项目管理、财务管理等模块,基本覆盖中小企业日常办公场景。对技术团队来说,这套系统更像是一个“业务底座”:既能直接给内部用,也能在此基础上二次开发成 CRM、ERP 或某个垂直行业的业务管理系统,省掉从零搭框架、写权限、做审批流的成本。适合预算不多但又需要完整办公系统的公司,也适合那些需要快速交付企业应用项目的开发者。

2. 技术架构与核心模块:为什么 TP6 + Layui 能撑起中小企业的 OA 场景

有不少人一听到“开源办公系统”就下意识觉得技术栈会很老。这套系统的选择其实非常克制:ThinkPHP6 负责 PHP 业务层,Layui 负责后台界面布局,MySQL 负责数据存储。三个组件都很“低调”,但组合起来之后,恰好踩准了 OA 这类表单密集、权限复杂、流程明确的应用形态。

2.1 技术栈选型:不是每个系统都适合重框架

先看后端。ThinkPHP6 是 PHP 的经典 MVC 框架,PHP 7.2.5 以上即可运行,相比微服务或者 Java 体系,它的部署成本低很多。OA 系统的核心是用户管理、流程审批、消息推送、数据统计,这些业务并不需要极致的并发性能,反而更需要快速地开发页面、快速调整表结构、快速处理权限关系。TP6 自带路由、中间件、ORM、验证器,还能通过composer装各种组件,这决定了二次开发时不会因为框架本身卡住。

再看前端。Layui 不是 React、Vue 那种组件化框架,而是偏向传统服务端渲染的 UI 库。OA 后台大部分页面都是“左侧菜单 + 右侧表单/表格”的经典结构,用 Layui 的 table、form、layer、tree 组件可以非常快地拼出来。Layui 不依赖 Webpack、Vite 这些构建链,改一个 HTML 模板刷新就能看到效果。对要做 CRM、ERP 二次开发的团队来说,这等于省掉了前端工程化的学习成本。

数据库用 MySQL 是理所当然的选择。审批流、合同、财务对事务有硬性要求,InnoDB 保证 ACID,TP6 的模型事务可以直接支持“一个审批动作同时更新申请单状态和消息表”。字符集建议统一使用utf8mb4,因为 OA 消息、备注、审批意见里经常出现中文和 emoji,用utf8mb4_general_ci或者utf8mb4_unicode_ci都能避免乱码。

我一般会在选型时先问一句:这个系统未来三年的最大规模是多少?如果只是几百人以内、模块二十个上下,单体 PHP 应用完全够用。等做到真正高并发 SaaS 再单独拆 API,也不迟。

2.2 目录结构与核心模块划分

拿到源码包之后,可以先扫一眼静态资源目录。源码包里有layui.css、editormd.css、editormd.min.css、skin.min.css、editormd.preview.css、editormd.preview.min.css、ambiance.css、main.min.css这串样式文件。

这基本透露了两件事:

  • 前端主框架是 Layui,负责后台布局和基础组件。
  • 系统内置了 EditorMD 作为 Markdown 编辑器,editormd.css和editormd.preview.css负责编辑和预览,ambiance.css大概率是代码高亮主题。日常办公模块里写工作日报、项目纪要这类富文本内容时,Markdown 编辑器体验比普通 textarea 要舒服。

入口结构是标准 ThinkPHP6 布局,项目里一般会按下面这样划分:

路径职责
app/admin/controller后台控制器,每个模块对应一个控制器
app/admin/model数据模型层,用 TP6 ORM 操作业务表
app/admin/view后台前端模板,以.html为主
publicWeb 根目录,入口文件和静态资源
config数据库、缓存、路由等配置
extend公共扩展类和工具类
storage日志、缓存、上传文件的写入目录

功能模块在后台菜单上能直接看到:系统设置、人事管理、消息管理、审批管理、日常办公、客户管理、合同管理、项目管理、财务管理。这些模块是关系型业务系统的典型切分方式:系统设置管人员和部门,消息管理负责站内通知,审批管理作为流程引擎,客户、合同、项目、财务则构成业务主线。做 CRM 时可以直接站在“客户管理 + 消息管理 + 审批管理”这三个模块上扩展,做 ERP 时则更依赖“合同管理 + 项目管理 + 财务管理”的联动关系。

2.3 数据库与核心表关系

这类 OA 的表结构不会太复杂,但核心表的关系很典型。常见的基础表大概如下:

数据表类型常见表名核心字段作用
用户权限adminid、username、password、dept_id、status后台登录用户
角色权限roleid、name、rules角色和权限规则绑定
部门组织departmentid、pid、name、leader_id组织架构树
消息通知messageid、from_user_id、to_user_id、content、is_read站内消息
审批申请approvalid、user_id、type、status、apply_time审批主表
客户管理customerid、name、phone、level、owner_id客户资源
合同管理contractid、customer_id、amount、status、start_date合同履约
项目管理projectid、contract_id、name、progress、status项目进度
财务管理financeid、type、amount、contract_id、status收支流水

从关系上看,admin表与role表是多对多,TP6 里用belongsToMany可以关联查询;message表与admin表是一对多关系,用户登录后查询未读消息,直接hasMany闭包条件过滤即可。最关键的业务关系是“客户—合同—项目—财务”这条链:合同挂在客户下,项目挂在合同下,财务流水挂在合同或者项目下,这样才能把“签了多少钱、项目干到哪、款回了多少”串成一条可追踪的线索。这个关系设计得好不好,直接决定你能不能在二次开发时把它扩展成 CRM/ERP。

在 TP6 模型层,建议所有跨表写入都包在事务里,比如审批通过时要同时更新审批状态、生成消息、写操作日志,用Db::transaction()包住,避免出现“审批状态变了但消息没发出去”这种局。

3. 本地部署与初始化:五步跑通一套可登录的 OA 环境

部署这套系统不需要复杂的编排,只要环境符合要求,按步骤操作,十分钟内能打开登录页。先说一个经验:不要一拿到包就直接在根目录跑php start,而是先把环境检查一遍,再把数据导入,这样后面报错的概率会小很多。

3.1 环境准备:先检查 PHP 版本与扩展

ThinkPHP6 对 PHP 版本要求是 7.2.5 以上,我一般直接用 PHP 7.4 或 8.0 开发,部署到服务器时用 PHP 7.4 更稳妥。除了 PHP 本身,必须确认这几个扩展存在:pdo_mysql、mbstring、openssl、fileinfo。其中fileinfo容易被人忽略,文件上传、图片压缩、MIME 类型识别都会用到它;如果缺少,后台传文件时会莫名其妙报错。

建议先跑一段环境检查命令:

php -v php -m | grep -E "pdo_mysql|mbstring|openssl|fileinfo" composer --version

第一行看 PHP 版本,第二行确认扩展,第三行确认 Composer 可用。如果某个扩展没装,Debian/Ubuntu 上常见做法是:

sudo apt install php7.4-mysql php7.4-mbstring php7.4-openssl php7.4-fileinfo

这里的php7.4要替换成你实际安装的 PHP 版本。装完扩展后重新跑一遍php -m | grep,确认成功再进入下一步。

3.2 初始化安装:数据库导入与环境配置

源码包通常有两种形态,一种是不带vendor目录的,需要先执行composer install把 TP6 的依赖拉下来;另一种是完整包,直接解压就能用。我建议拿到包之后先看根目录有没有vendor,没有就补一步:

cd /path/to/oa-project composer install --no-dev

这个命令会按composer.json安装生产环境依赖,--no-dev表示不装调试工具,线上部署用这个更干净。

接下来配置数据库。ThinkPHP6 的配置在项目根目录的.env文件中,如果源码包没附带.env,可以复制.env.example。核心配置项如下:

APP_DEBUG = true DATABASE_TYPE = mysql DATABASE_HOST = 127.0.0.1 DATABASE_NAME = oa_demo DATABASE_USER = root DATABASE_PASSWORD = your_password DATABASE_PORT = 3306 DATABASE_PREFIX = oa_

参数说明:DATABASE_TYPE一般固定为 mysql;DATABASE_HOST如果是远程数据库,要改成对应 IP;DATABASE_NAME需要提前在 MySQL 里建好;DATABASE_PREFIX是表前缀,源码里的表结构一般统一带前缀,比如oa_admin,如果你改前缀,所有表都要一致,不然后续 ORM 模型会找不到表。

数据库创建和导入一般用这条命令:

mysql -u root -p -e "CREATE DATABASE oa_demo DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;" mysql -u root -p oa_demo < install.sql

install.sql是源码包里的初始化脚本,导入后会自动建表并写入基础菜单和角色。如果安装向导已经提供了在线导入功能,也可以用后台安装页完成,不用手动执行 SQL。但手动执行的好处是不会漏掉表。

3.3 启动入口与管理员账号配置

ThinkPHP6 的入口在public/index.php,Web 服务器必须把站点根目录指到public,而不是项目根目录。这样能避免别人直接访问到config、runtime这些敏感目录。

本地快速验证时,可以用 TP6 自带命令:

cd /path/to/oa-project php think run

默认监听127.0.0.1:8000,浏览器打开http://127.0.0.1:8000就能访问安装向导。安装向导一般要求设置管理邮箱和管理员密码,这里不要随手写admin/admin,因为这是全系统最高权限账号,密码强度至少包含大小写和数字。完成这一步后,就可以进入登录页了。

4. 部署与使用中的避坑:五个高频问题排查记录

这套系统本身不大,但“能用”和“顺利用起来”之间还是隔着一堆小毛病。下面这几个问题是我在本地和服务器上反复遇到过的高频记录,按“现象→原因→解决”列出来,如果你部署翻车,直接对照查。

4.1 数据库连接失败:时区和字符集埋的坑

现象:安装向导第一步填写数据库信息后,点击下一步直接报SQLSTATE[HY000] [2002] Connection refused,或者报 “数据库连接失败”。

原因:最常见的是.env里配的数据库端口不对,MySQL 默认 3306,如果跑了 Docker 或者其他实例,端口会被占用;另一个容易忽略的是 MySQL 8.0 默认缓存认证插件是caching_sha2_password,PHP 7.4 老版本的pdo_mysql可能不支持,导致连接被拒绝。

解决:先把端口确认好,netstat -ltnp | grep 3306看看 MySQL 是否真的在监听。如果是认证插件问题,登录 MySQL 后执行:

ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY 'your_password'; FLUSH PRIVILEGES;

顺便把.env里字符集相关的配置统一成:

DATABASE_CHARSET = utf8mb4 DATABASE_COLLATE = utf8mb4_general_ci

改完以后重启 PHP-FPM 再试。不要忽略字符集,线上 OA 消息里的 emoji 很可能让你第一次同步数据时就变问号。

4.2 静态资源全部丢样式:站点根目录指错了

现象:浏览器能打开登录页,但页面完全没有 CSS 效果,控制台全是404找不到layui.css、main.min.css。

原因:多数情况是 Nginx 的root指到了项目根目录,不是public。TP6 的静态资源都在public/static下,如果 root 指向项目根目录,访问/static/xxx.css时映射到的是项目根目录下的static目录,自然找不到。

解决:在 Nginx 站点配置里,把root指向public目录。常见配置片段如下:

server { listen 80; server_name oa.example.com; root /var/www/oa/public; index index.php index.html; location / { try_files $uri $uri/ /index.php?s=$uri&$args; } location ~ \.php$ { include snippets/fastcgi-php.conf; fastcgi_pass unix:/run/php/php7.4-fpm.sock; } }

修改后执行nginx -t && nginx -s reload,再刷新页面。如果用的是 Apache,则加一条DocumentRoot指向public。

4.3 登录后跳转正常,但列表页报错缺少模板

现象:登录成功,点“系统设置”或者“员工管理”时,报错Template not found: xxx/index.html,但控制器方法确实存在。

原因:TP6 的模板默认从app/admin/view/控制器名/方法名.html读取,如果源码包里模板文件名是admin/员工管理.html,而控制器里return view('index'),就会找不到。这类问题常见于源码包经过二次修改、目录结构不完整的情况。

解决:先打开控制器看return view()的参数,再对照view目录下的模板文件名。如果模板文件确实缺失,最简单的做法是重新下载一份完整源码包,把view目录整体补齐。我一般会在改代码之前先备份整个view目录,因为模板丢失往往发生在你自己误删之后。

4.4 保存客户数据后翻页丢了筛选条件

现象:客户列表按“等级”筛选后,翻到第二页,筛选条件失效,全部客户又显示出来了。

原因:TP6 自带的paginate生成分页地址时,默认只处理当前查询参数,手动拼接的where条件不会自动携带到链接里。常见解决思路是手动把条件拼到分页查询参数中。

解决:在控制器里先收集筛选条件,再传给分页次数的第三参数,比如:

$params = request()->param(); $list = Customer::where('level', $params['level']) ->paginate(10, false, ['query' => $params]);

这样分页链接里就会带上level参数。如果你在二次开发中遇到筛选翻页后条件丢失,基本就是这个原因。

4.5 上传附件超过 2MB 后报 500

现象:上传合同附件时,文件超过 2MB 直接 500,小文件没问题,日志里没有任何明显的业务错误。

原因:PHP 默认upload_max_filesize和post_max_size都是 2M,超过这个限制,请求体直接没进到框架层就被丢弃了,所以日志看不到框架错误。

解决:修改 PHP 生产环境的配置,常见位置是/etc/php/7.4/fpm/php.ini:

post_max_size = 64M upload_max_filesize = 64M max_file_uploads = 20

改完重载 PHP-FPM:

sudo systemctl reload php7.4-fpm

如果用了 Nginx,client_max_body_size也要加大,例如在 server 块里加client_max_body_size 64m;。这个坑几乎每个做过 OA 的人都踩过,看似简单,但排查起来容易先怀疑代码。

5. 向 CRM/ERP 二次开发:模块拆分与业务扩展的三个落点

这套 OA 最大的价值不是开箱即用,而是它把办公类系统最通用的“组织、权限、审批、消息”四件套都搭好了,二次开发时真正要写的是业务逻辑。下面讲三个我实际落地过的扩展方向。

5.1 先看懂模块边界:OA 模块如何映射 CRM 和 ERP

建议先不要急着写代码,把模块边界盘清楚。OA 里的“客户管理”可以当作 CRM 的「客户档案」基础;“合同管理”和“财务管理”合起来就是 ERP 的订单与收付款闭环;“项目管理”则承担交付跟踪的角色。映射关系大致如下:

OA 现有模块CRM 落点ERP 落点
客户管理客户池、跟进记录客户主数据
合同管理商机转合同销售订单
项目管理交付进度生产/实施任务
财务管理回款记录应收应付台账
审批管理折扣审批采购/付款审批

这个映射的价值在于:你能复用原来的部门权限,而不是把 CRM 做成一个没有权限边界的“大客户表”。例如,销售只能看到自己名下客户,财务只能看到回款流水,这两个需求在 OA 原本的dept_id和owner_id上都能实现。

5.2 新增业务模块:Controller-Model-View 三层扩展

以新增一个“销售线索”模块为例,后台典型的做法是在app/admin下加一套 MVC。控制器代码类似:

<?php namespace app\admin\controller; use app\common\model\Lead; use think\facade\Db; class Lead extends Base { public function index() { $list = Lead::where('status', 1) ->order('id desc') ->paginate(10); $this->assign('list', $list); return $this->fetch(); } public function add() { if ($this->request->isPost()) { $data = $this->request->post(); Db::transaction(function () use ($data) { Lead::create($data); // 写入一条消息通知主管 // messageService->send(主管ID, '新线索待分配'); }); return json(['code' => 1, 'msg' => '添加成功']); } return $this->fetch(); } }

说明几个关键点:

  • Base是后台基类控制器,里面会做登录校验和权限校验,新模块如果不同的地方,直接继承它,权限继承不会少。
  • Lead::create($data)是 TP6 模型的新增方法,会自动完成字段过滤和时间戳写入。
  • 事务包住业务写入和消息通知,能防止发生“线索加了但主管不知道”的中间状态。
  • 模板Lead/index.html和Lead/add.html放在app/admin/view/lead目录下,表单沿用 Layui 的form组件即可。

新增后记得在系统设置里把菜单权限注册到角色上,否则其他用户登录后看不到入口。这是新手最容易漏的环节。

5.3 把审批流通用化:把 OA 的审批逻辑变成业务引擎

OA 里审批模块通常不是写死在某个特定类型上的,而是支持多类型:请假、报销、合同审核。做 CRM/ERP 时,建议不要把审批业务复制一份,而是把审批动作抽成公共服务。

常见做法是维护一张approval_process流程表,字段包含process_name、approver_ids、condition_field、status。某条业务记录发起审批时,统一调用公共方法:

public function submitApproval($type, $businessId, $businessData) { $dialog = ApprovalService::create($type, $businessId); $dialog->setApprovers($businessData->leader_id) ->setFields($businessData); return $dialog->start(); }

这样客户价格变更、项目立项、付款申请都能复用同一条审批链路,不用每加一个业务就复制一张自定义审批表。审批状态回调时,在同一个事务里更新业务表状态和审批表状态即可。

如果你打算把这套系统直接改成行业业务管理系统,建议优先把审批模块、消息模块、权限模块这三个“公共能力”研究透,因为它们才是你后续所有业务模块的地基。真正写业务时,反而像填空题。

6. 进阶技巧:写一条验收脚本,把部署结果一次验完

无论部署到本地还是服务器,我都不建议只看到登录页就认为环境没问题。更稳妥的做法是写一条验收脚本,把 PHP 扩展、目录权限、路由响应、上传目录一次性检查完。下面是一个可以挂在 CI 或者手动执行的 Bash 脚本:

#!/bin/bash URL="http://127.0.0.1:8000" PHP_BIN="php" PROJECT_DIR="/var/www/oa" # 1. 检查 PHP 和关键扩展 $PHP_BIN -v >/dev/null 2>&1 || { echo "[FAIL] PHP 不可用"; exit 1; } for EXT in pdo_mysql mbstring openssl fileinfo; do if ! $PHP_BIN -m | grep -q "$EXT"; then echo "[WARN] 缺少扩展 $EXT" fi done # 2. 检查目录权限 for DIR in runtime storage public/upload; do if [ ! -w "$PROJECT_DIR/$DIR" ]; then echo "[FAIL] $DIR 不可写,请执行 chmod -R 755" fi done # 3. 检查登录页响应 HTTP_CODE=$(curl -o /dev/null -s -w "%{http_code}" "$URL") [ "$HTTP_CODE" -eq 200 ] && echo "[OK] 登录页返回 200" || echo "[FAIL] 登录页异常: $HTTP_CODE"

这段脚本的逻辑很简单:PHP 不存在直接退出;扩展缺了只提醒,因为部分业务可能用不到;目录不可写则明确报出;最后用curl验证 Web 入口响应码。部署后跑一遍,再手动登录一次,基本就能排除 80% 的环境问题。

以前我上线这套系统时,从不验证目录权限,结果用户传合同附件时突然报 500,最后查下来是storage目录没写权限,来回折腾了半小时。从那以后我每次部署完都强制走一遍这个检查脚本,甚至还会把脚本放到 CI 的构建产物里,每次发布前自动执行一次。如果你也是拿这套 OA 快速交付项目,建议把这一步固化下来,能少踩很多环境相关的坑。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询