📝 快捷表单助手

FormHelper 是 ThinkAdmin 提供的快捷表单助手,能够根据提交的表单数据自动处理保存与更新操作,大幅提升开发效率。

🚀 主要功能

  • 自动处理: 根据表单数据自动处理保存与更新
  • 简化操作: 减少手动编写代码的工作量
  • 错误减少: 降低因手动编写代码而产生的错误
  • 提高效率: 显著提升数据操作的处理效率
  • 统一接口: 提供统一的表单处理接口
  • 灵活配置: 支持多种参数配置和自定义

📋 使用场景

  • 数据保存: 表单数据的自动保存
  • 数据更新: 现有数据的自动更新
  • 批量操作: 批量表单数据处理
  • 数据验证: 表单数据验证和处理
  • 模板渲染: 表单模板的自动渲染

⚙️ 使用要求

模型继承

  • 模型必须继承自 \think\admin\Model
  • 确保模型具备与 FormHelper 交互的能力
  • 支持自动处理表单数据的保存与更新

控制器继承

  • 控制器必须继承自 \think\admin\Controller
  • 方便调用 FormHelper 提供的功能
  • 简化数据处理的流程

🔧 工作原理

自动处理流程

  1. 接收数据: 接收表单提交的数据
  2. 数据验证: 自动进行数据验证
  3. 保存更新: 根据条件自动保存或更新
  4. 结果返回: 返回处理结果和状态
  5. 回调处理: 执行前置和后置回调函数

参数配置

  • 模板文件: 指定表单模板文件
  • 主键字段: 设置主键字段名称
  • 查询条件: 定义查询条件
  • 额外数据: 传递额外的数据参数
  • 验证规则: 支持自定义验证规则
  • 事务支持: 支持数据库事务操作

数据处理机制

  • 智能判断: 根据主键自动判断新增或更新
  • 数据过滤: 支持数据过滤和预处理
  • 错误处理: 完善的错误处理机制
  • 日志记录: 自动记录操作日志

调用快捷表单

// 1.0 模型用法
// 参数 $template 模板文件
// 参数 $field 主键字段
// 参数 $where 查询条件
// 参数 $data 额外数据
MyModel::mForm($template, $field, $where, $data);

// 1.1 模型通用更新
MyModel::mForm();

// 2.0 控制器用法
// 参数 $dbQuery 为模型名称
// 参数 $template 模板文件
// 参数 $field 主键字段
// 参数 $where 查询条件
// 参数 $data 额外数据
$this->_form($dbQuery, $template, $field, $where, $data);

// 2.1 通用修改器
$this->_form('MyModel');

系统进一步简化了控制器中的数据添加与更新操作。现在,控制器仅需通过一行代码 $this->_form('模板名称'); 即可实现数据的快速添加或更新。这一行代码会自动根据提交的表单数据判断是执行添加操作还是更新操作。如果表单数据中包含了指定的主键,那么将执行更新操作;否则,将执行添加记录的操作。

使用了 HttpResponseException 来直接输出响应,这意味着在使用 $this->_form('模板名称'); 时,控制器无需返回任何内容,系统会自动处理输出。 为了满足更多场景的需求,系统还允许在控制器中给模板额外赋值。通过在控制器中设置 $this->username = '变量值';,你可以将变量值传递给模板,并在模板中直接使用 $username 变量。 为了增强数据的灵活性和安全性,系统引入了 callback 操作(参数使用引用)。开发者可以定义 protected function _form_filter(&$data) 方法,在数据添加到数据库之前对数据进行自定义处理或验证。这一功能使得数据在入库前能够进行额外的过滤和校验,提高了数据的质量和安全性。

数据回调处理

对于表单操作,Controller 内置了两个回调方法,如:

// 表单前置操作,允许使用引用更改 data 值
// 参数 $data :会返回待处理的数组,分显示模型和保存模型,也就是 get|post 请求
[_ACTION]_form_filter(array &$data)

// 表单后置操作,返回的 data 为提交的数据
// 参数 $result :为返回保存结果,成功为 true,失败为 false
// 参数 $data :为返回后的数据,默认为带上数据ID,也就是主键,这个与模型定义有关
[_ACTION]_form_result(bool $result, array $data)

以上案例回调函数如果返回 false 时,Controller 默认行为将不会再执行。

实际应用案例

案例1:简单的用户管理表单

<?php
namespace app\admin\controller;

use think\admin\Controller;
use think\admin\model\SystemUser;

class UserController extends Controller
{
    /**
     * 添加用户
     * @auth true
     * @menu true
     */
    public function add()
    {
        $this->title = '添加用户';
        $this->_form('SystemUser', 'form');
    }

    /**
     * 编辑用户
     * @auth true
     * @menu true
     */
    public function edit()
    {
        $this->title = '编辑用户';
        $this->_form('SystemUser', 'form');
    }

    /**
     * 表单数据处理
     */
    protected function _form_filter(&$data)
    {
        if ($this->request->isPost()) {
            // 验证必填字段
            if (empty($data['username'])) {
                $this->error('用户名不能为空');
            }
            if (empty($data['password']) && empty($data['id'])) {
                $this->error('密码不能为空');
            }
            
            // 检查用户名是否重复
            $map = [['username', '=', $data['username']]];
            if (!empty($data['id'])) {
                $map[] = ['id', '<>', $data['id']];
            }
            if ($this->app->db->name('SystemUser')->where($map)->count() > 0) {
                $this->error('用户名已存在');
            }
            
            // 密码加密处理
            if (!empty($data['password'])) {
                $data['password'] = md5($data['password']);
            } else {
                unset($data['password']); // 编辑时不修改密码
            }
            
            // 设置默认值
            $data['status'] = $data['status'] ?? 1;
            $data['create_at'] = date('Y-m-d H:i:s');
        }
    }

    /**
     * 表单结果处理
     */
    protected function _form_result(bool $result, array $data)
    {
        if ($result && $this->request->isPost()) {
            $this->success('用户保存成功!', 'javascript:history.back()');
        }
    }
}

案例2:复杂的商品管理表单(带关联数据)

<?php
namespace app\admin\controller;

use think\admin\Controller;
use think\facade\Db;

class GoodsController extends Controller
{
    /**
     * 添加商品
     * @auth true
     * @menu true
     */
    public function add()
    {
        $this->title = '添加商品';
        $this->_form('StoreGoods', 'form');
    }

    /**
     * 编辑商品
     * @auth true
     * @menu true
     */
    public function edit()
    {
        $this->title = '编辑商品';
        $this->_form('StoreGoods', 'form');
    }

    /**
     * 商品表单数据处理
     */
    protected function _form_filter(&$data)
    {
        if ($this->request->isGet()) {
            // GET请求:准备表单数据
            if (!empty($data['id'])) {
                // 编辑模式:加载商品详情
                $data['goods'] = Db::name('StoreGoods')->where('id', $data['id'])->find();
                $data['images'] = Db::name('StoreGoodsImages')->where('goods_id', $data['id'])->select();
                $data['specs'] = Db::name('StoreGoodsSpecs')->where('goods_id', $data['id'])->select();
            } else {
                // 新增模式:设置默认值
                $data['status'] = 1;
                $data['sort'] = 0;
                $data['images'] = [];
                $data['specs'] = [];
            }
        } else {
            // POST请求:处理表单提交
            // 验证必填字段
            if (empty($data['title'])) {
                $this->error('商品标题不能为空');
            }
            if (empty($data['price'])) {
                $this->error('商品价格不能为空');
            }
            if (empty($data['images'])) {
                $this->error('请上传商品图片');
            }
            
            // 生成商品编号
            if (empty($data['code'])) {
                $data['code'] = 'G' . date('YmdHis') . rand(1000, 9999);
            }
            
            // 处理商品图片
            $images = $data['images'] ?? [];
            $data['image'] = $images[0] ?? ''; // 主图
            unset($data['images']); // 移除临时字段
            
            // 处理商品规格
            $specs = $data['specs'] ?? [];
            unset($data['specs']); // 移除临时字段
            
            // 使用事务处理复杂数据
            try {
                $this->app->db->transaction(function () use ($data, $images, $specs) {
                    // 保存商品基本信息
                    if (empty($data['id'])) {
                        $goodsId = $this->app->db->name('StoreGoods')->insertGetId($data);
                    } else {
                        $goodsId = $data['id'];
                        $this->app->db->name('StoreGoods')->where('id', $goodsId)->update($data);
                    }
                    
                    // 保存商品图片
                    $this->app->db->name('StoreGoodsImages')->where('goods_id', $goodsId)->delete();
                    if (!empty($images)) {
                        $imageData = [];
                        foreach ($images as $index => $image) {
                            $imageData[] = [
                                'goods_id' => $goodsId,
                                'image_url' => $image,
                                'sort' => $index,
                                'create_at' => date('Y-m-d H:i:s')
                            ];
                        }
                        $this->app->db->name('StoreGoodsImages')->insertAll($imageData);
                    }
                    
                    // 保存商品规格
                    $this->app->db->name('StoreGoodsSpecs')->where('goods_id', $goodsId)->delete();
                    if (!empty($specs)) {
                        $specData = [];
                        foreach ($specs as $spec) {
                            if (!empty($spec['name']) && !empty($spec['value'])) {
                                $specData[] = [
                                    'goods_id' => $goodsId,
                                    'spec_name' => $spec['name'],
                                    'spec_value' => $spec['value'],
                                    'price' => $spec['price'] ?? $data['price'],
                                    'stock' => $spec['stock'] ?? 0,
                                    'create_at' => date('Y-m-d H:i:s')
                                ];
                            }
                        }
                        if (!empty($specData)) {
                            $this->app->db->name('StoreGoodsSpecs')->insertAll($specData);
                        }
                    }
                });
            } catch (\Exception $e) {
                $this->error("商品保存失败:{$e->getMessage()}");
            }
        }
    }

    /**
     * 表单结果处理
     */
    protected function _form_result(bool $result, array $data)
    {
        if ($result && $this->request->isPost()) {
            $this->success('商品保存成功!', 'javascript:history.back()');
        }
    }
}

案例3:表单验证和数据处理

/**
 * 表单数据处理 - 通用验证规则
 */
protected function _form_filter(&$data)
{
    if ($this->request->isPost()) {
        // 使用 _vali 方法进行数据验证
        $this->_vali([
            'username.require' => '用户名不能为空',
            'username.length' => '用户名长度为3-20个字符',
            'email.require' => '邮箱不能为空',
            'email.email' => '邮箱格式不正确',
            'phone.mobile' => '手机号格式不正确',
            'password.require' => '密码不能为空',
            'password.length' => '密码长度为6-20个字符'
        ]);
        
        // 自定义业务验证
        if (!empty($data['username'])) {
            $map = [['username', '=', $data['username']]];
            if (!empty($data['id'])) {
                $map[] = ['id', '<>', $data['id']];
            }
            if ($this->app->db->name('SystemUser')->where($map)->count() > 0) {
                $this->error('用户名已存在');
            }
        }
        
        // 数据预处理
        $data['username'] = trim($data['username']);
        $data['email'] = strtolower(trim($data['email']));
        $data['phone'] = preg_replace('/[^0-9]/', '', $data['phone']);
        
        // 密码加密
        if (!empty($data['password'])) {
            $data['password'] = md5($data['password']);
        } else {
            unset($data['password']);
        }
        
        // 设置默认值
        $data['status'] = $data['status'] ?? 1;
        $data['create_at'] = date('Y-m-d H:i:s');
        $data['update_at'] = date('Y-m-d H:i:s');
    }
}

如果是在 ThinkAdmin 后台基于 admin.js 的情况下,可使用 form[data-auto] 来与 $this->_form 配合使用。

🔧 前端配合使用

自动表单提交

<!-- 使用 data-auto 属性自动提交表单 -->
<form data-auto="{:url('form')}" method="post">
    <input type="text" name="name" placeholder="名称">
    <input type="email" name="email" placeholder="邮箱">
    <button type="submit">提交</button>
</form>

手动表单处理

<!-- 手动处理表单提交 -->
<form id="myForm" method="post">
    <input type="text" name="name" placeholder="名称">
    <input type="email" name="email" placeholder="邮箱">
    <button type="button" onclick="submitForm()">提交</button>
</form>

<script>
function submitForm() {
    $.post('{:url("form")}', $('#myForm').serialize(), function(res) {
        if (res.code === 1) {
            layer.msg(res.info);
            // 处理成功后的逻辑
        } else {
            layer.msg(res.info);
        }
    });
}
</script>
最近更新:
Contributors: 邹景立