通达OA模块开发实战

一、引言

模块开发是通达OA二次开发的核心内容。本文将通过一个完整的实战案例,详细介绍如何创建一个功能完善的OA模块,包括目录结构、数据库设计、代码编写、菜单注册和权限分配等环节。

二、开发前准备

2.1 需求分析

假设我们需要开发一个"项目管理"模块,包含以下功能:

2.2 数据库设计

项目表(project)

字段名 类型 说明
project_id int 项目ID(主键,自增)
project_name varchar(100) 项目名称
description text 项目描述
start_date date 开始日期
end_date date 结束日期
status tinyint 状态(0=进行中,1=已完成)
creator_uid int 创建人ID
create_time datetime 创建时间
update_time datetime 更新时间

数据库关系图

三、模块开发步骤

3.1 步骤一:建立模块目录

MYOA\webroot\general 目录下创建模块目录:

php 复制代码
New-Item -ItemType Directory -Path "MYOA\webroot\general\project"

目录结构规划

csharp 复制代码
project/
├── index.php          # 项目列表页
├── add.php            # 添加项目页
├── edit.php           # 编辑项目页
├── detail.php         # 项目详情页
├── delete.php         # 删除项目接口
├── ajax.php           # AJAX接口
└── style.css          # 模块样式文件

3.2 步骤二:创建数据库表

使用phpMyAdmin或SQL语句创建表:

sql 复制代码
CREATE TABLE IF NOT EXISTS `project` (
    `project_id` int(11) NOT NULL AUTO_INCREMENT,
    `project_name` varchar(100) NOT NULL,
    `description` text,
    `start_date` date DEFAULT NULL,
    `end_date` date DEFAULT NULL,
    `status` tinyint(1) DEFAULT '0',
    `creator_uid` int(11) NOT NULL,
    `create_time` datetime DEFAULT CURRENT_TIMESTAMP,
    `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    PRIMARY KEY (`project_id`),
    KEY `creator_uid` (`creator_uid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

3.3 步骤三:编写核心代码

3.3.1 项目列表页(index.php)

php 复制代码
<?php
$HTML_PAGE_TITLE = _("项目管理");
include_once("inc/auth.inc.php");
include_once("inc/header.inc.php");

$td = new TD();

// 分页参数
$page = isset($_GET['page']) ? intval($_GET['page']) : 1;
$page_size = 10;
$offset = ($page - 1) * $page_size;

// 查询项目列表
$sql = "SELECT p.*, u.name AS creator_name 
        FROM project p 
        LEFT JOIN user u ON p.creator_uid = u.uid 
        ORDER BY p.create_time DESC 
        LIMIT $offset, $page_size";
$result = $td->query($sql);

// 查询总数
$count_sql = "SELECT COUNT(*) AS total FROM project";
$count_result = $td->query($count_sql);
$count_row = $td->fetch_array($count_result);
$total = $count_row['total'];
$total_pages = ceil($total / $page_size);
?>

<div class="main-content">
    <h2><?php echo _("项目列表"); ?></h2>
    
    <a href="add.php" class="btn btn-primary">
        <?php echo _("添加项目"); ?>
    </a>
    
    <table class="table">
        <thead>
            <tr>
                <th><?php echo _("项目名称"); ?></th>
                <th><?php echo _("描述"); ?></th>
                <th><?php echo _("时间范围"); ?></th>
                <th><?php echo _("状态"); ?></th>
                <th><?php echo _("创建人"); ?></th>
                <th><?php echo _("操作"); ?></th>
            </tr>
        </thead>
        <tbody>
            <?php while ($row = $td->fetch_array($result)) { ?>
            <tr>
                <td><?php echo $row['project_name']; ?></td>
                <td><?php echo csubstr($row['description'], 0, 50); ?></td>
                <td><?php echo $row['start_date'] . ' ~ ' . $row['end_date']; ?></td>
                <td><?php echo $row['status'] ? _('已完成') : _('进行中'); ?></td>
                <td><?php echo $row['creator_name']; ?></td>
                <td>
                    <a href="detail.php?id=<?php echo $row['project_id']; ?>">
                        <?php echo _("查看"); ?>
                    </a>
                    <a href="edit.php?id=<?php echo $row['project_id']; ?>">
                        <?php echo _("编辑"); ?>
                    </a>
                    <a href="delete.php?id=<?php echo $row['project_id']; ?>" 
                       onclick="return confirm('<?php echo _('确定删除?'); ?>')">
                        <?php echo _("删除"); ?>
                    </a>
                </td>
            </tr>
            <?php } ?>
        </tbody>
    </table>
    
    <?php // 分页代码省略 ?>
</div>

<?php
include_once("inc/footer.inc.php");
?>

3.3.2 添加项目页(add.php)

ini 复制代码
<?php
$HTML_PAGE_TITLE = _("添加项目");
include_once("inc/auth.inc.php");
include_once("inc/header.inc.php");

if ($_POST) {
    $td = new TD();
    
    $project_name = $td->escape($_POST['project_name']);
    $description = $td->escape($_POST['description']);
    $start_date = $_POST['start_date'];
    $end_date = $_POST['end_date'];
    $creator_uid = $_SESSION['uid'];
    
    $sql = "INSERT INTO project (project_name, description, start_date, end_date, creator_uid) 
            VALUES ('$project_name', '$description', '$start_date', '$end_date', $creator_uid)";
    
    if ($td->query($sql)) {
        Message(_("添加成功"), "index.php");
        exit;
    } else {
        Message(_("添加失败"), "add.php");
        exit;
    }
}
?>

<div class="main-content">
    <h2><?php echo _("添加项目"); ?></h2>
    
    <form method="post" action="add.php">
        <div class="form-group">
            <label><?php echo _("项目名称"); ?>:</label>
            <input type="text" name="project_name" required class="form-control">
        </div>
        
        <div class="form-group">
            <label><?php echo _("项目描述"); ?>:</label>
            <textarea name="description" class="form-control"></textarea>
        </div>
        
        <div class="form-group">
            <label><?php echo _("开始日期"); ?>:</label>
            <input type="date" name="start_date" required class="form-control">
        </div>
        
        <div class="form-group">
            <label><?php echo _("结束日期"); ?>:</label>
            <input type="date" name="end_date" required class="form-control">
        </div>
        
        <button type="submit" class="btn btn-primary">
            <?php echo _("提交"); ?>
        </button>
        <a href="index.php" class="btn btn-default">
            <?php echo _("返回"); ?>
        </a>
    </form>
</div>

<?php
include_once("inc/footer.inc.php");
?>

3.3.3 删除项目(delete.php)

php 复制代码
<?php
include_once("inc/auth.inc.php");

$td = new TD();
$project_id = isset($_GET['id']) ? intval($_GET['id']) : 0;

if ($project_id > 0) {
    $sql = "DELETE FROM project WHERE project_id = $project_id";
    if ($td->query($sql)) {
        Message(_("删除成功"), "index.php");
    } else {
        Message(_("删除失败"), "index.php");
    }
} else {
    Message(_("参数错误"), "index.php");
}
?>

3.4 步骤四:注册菜单

在通达OA系统管理中注册菜单:

lua 复制代码
Mermaid 渲染失败: Lexical error on line 9. Unrecognized text.
...al/project/index.php]    E -->|父菜单| E3[
-----------------------^

菜单注册信息

字段
菜单名称 项目管理
菜单地址 /general/project/index.php
父菜单 工作台(或其他合适的父菜单)
菜单图标 选择合适的图标
排序号 根据需要设置

3.5 步骤五:分配权限

权限分配步骤

  1. 进入系统管理 → 角色管理
  2. 选择需要分配权限的角色
  3. 点击"编辑权限"
  4. 在权限列表中找到"项目管理"并勾选
  5. 保存权限设置

四、高级功能开发

4.1 附件上传功能

使用OA内置的文件上传函数:

php 复制代码
<?php
include_once("inc/utility_file.php");

// 处理文件上传
if ($_FILES['attachment']['name']) {
    $attach = upload('attachment', 0, 0, 'project');
    if ($attach['error'] == 0) {
        $attach_id = $attach['id'];
        // 保存附件ID到数据库
    }
}
?>

4.2 人员选择组件

使用OA的人员选择组件:

xml 复制代码
<?php
include_once("inc/header.inc.php");
?>

<!-- 人员多选组件 -->
<input type="hidden" name="user_ids" id="user_ids">
<script type="text/javascript">
    // 调用人员选择组件
    selectUser('user_ids', 'multi');
</script>

4.3 AJAX交互

php 复制代码
<?php
// ajax.php
include_once("inc/auth.inc.php");

$action = isset($_GET['action']) ? $_GET['action'] : '';

if ($action == 'search') {
    $keyword = $_GET['keyword'];
    $td = new TD();
    $keyword = $td->escape($keyword);
    
    $sql = "SELECT * FROM project WHERE project_name LIKE '%$keyword%'";
    $result = $td->query($sql);
    
    $data = array();
    while ($row = $td->fetch_array($result)) {
        $data[] = $row;
    }
    
    echo json_encode($data);
    exit;
}
?>

五、调试与测试

5.1 调试方法

调试工具

工具 用途
php_errors.log PHP错误日志
浏览器开发者工具 前端调试
phpMyAdmin 数据库查询
var_dump() 变量输出

5.2 测试用例

功能测试

测试项 测试步骤 预期结果
添加项目 填写表单提交 项目成功添加
编辑项目 修改信息提交 项目信息更新
删除项目 点击删除确认 项目成功删除
列表显示 访问列表页 显示所有项目
权限控制 无权限用户访问 提示无权限

六、模块部署与发布

6.1 部署流程

6.2 发布清单

项目 说明
代码文件 所有PHP文件
样式文件 CSS文件
数据库脚本 SQL文件
配置文件 菜单配置
权限配置 角色权限

七、最佳实践总结

7.1 代码规范

编码规范

规范项 要求
变量命名 驼峰式命名
文件命名 小写字母,下划线分隔
数据库表 小写字母,下划线分隔
SQL语句 使用TD类的escape方法转义

7.2 安全注意事项

  1. 输入验证:对所有用户输入进行验证
  2. SQL注入:使用参数化查询或escape方法
  3. 文件上传:验证文件类型和大小
  4. 权限控制:使用auth.inc.php进行权限验证
  5. 敏感信息:不输出敏感信息到页面
相关推荐
星栈13 小时前
MCP 从 stdio 迁到 SSE,踩了 5 个传输层坑
人工智能·后端·架构
Conan在掘金13 小时前
鸿蒙报错速查:struct 里嵌 enum 声明就炸,根因 + 真解法
后端
回家路上绕了弯14 小时前
Java双数据源实战全指南:Spring Boot多数据源配置、原理与踩坑避坑
后端
神奇小汤圆14 小时前
Redis主从切换后,旧主恢复竟触发全量同步?——我猜错了,原因比想象中更严谨
后端
JoyT14 小时前
滚动MAD与孤立森林在时序异常检测中的协同应用
后端
程序员cxuan14 小时前
白嫖 Claude Max 20x 漏洞完整事件始末
人工智能·后端·程序员
颜进强14 小时前
LangChain 从入门到实践:用最小案例理解 RAG 的 5 个核心抽象
前端·后端·ai编程
颜进强14 小时前
MCP 从入门到实践:用 TypeScript 实现第一个 MCP Server
前端·后端·ai编程
Undoom14 小时前
用搜索引擎 API 搭一个 SEO 关键词监控工具:定时追踪排名、广告和相关搜索
后端
程序大爆炸14 小时前
gcsfuse的TypeCache
后端