通达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. 敏感信息:不输出敏感信息到页面
相关推荐
聂二AI落地内参1 小时前
LLM 数据增强任务卡 4 天:upsert 少传 id 后发生了什么
后端
RainCity2 小时前
Java Swing 自定义组件库分享(十三)
java·笔记·后端
livemetee2 小时前
【关于Spring声明式事务】
java·后端·spring
techdashen4 小时前
Arborium:把 tree-sitter 语法高亮打包成 Rust 文档生态的基础设施
开发语言·后端·rust
Profile排查笔记4 小时前
指纹浏览器环境异常排查:Fingerprint、Profile、Proxy、Session 和 Task Log 怎么看
前端·人工智能·后端·自动化
小强库计算机毕业设计4 小时前
源码分享Spring Boot + Vue3 的校园社团管理系统
java·spring boot·后端·计算机毕业设计
阿新聊ai5 小时前
从 Prompt 到 Loop:AI 编程 Agent 四代循环的演进全景
人工智能·后端
im_lanny5 小时前
从 Function Calling 到 MCP:Agent 工具调用的三层境界与生产级安全护栏
后端
agent8975 小时前
Spring Boot 接口超时治理:从连接池、线程池到熔断限流的完整排查思路
java·spring boot·后端