我会从 “为什么需要复制”、“如何正确复制”、“复制后必须做的修改” 以及 “最佳实践” 四个方面来讲解,并提供一个完整的示例。

为什么需要复制模板?
在开发中,我们经常遇到这样的场景:
- 创建一个类似“新闻列表”的“产品列表”页面,它们的布局、分页逻辑、数据查询方式都非常相似。
- 创建一个类似“添加文章”的“添加产品”页面,表单结构、验证规则大同小异。
直接从头写一遍代码效率低下,容易出错,最直接的方法就是复制一个已有的、功能完善的控制器和视图文件,然后进行修改,以快速实现新功能。
如何正确复制?(以复制一个列表页为例)
假设我们已经有了一个 News 模块(或控制器),现在想创建一个功能类似的 Product 模块。
场景:
- 源控制器:
app/controller/News.php - 源视图:
app/view/news/index.html(新闻列表页) - 目标控制器:
app/controller/Product.php - 目标视图:
app/view/product/index.html(产品列表页)
复制控制器文件
- 在
app/controller/目录下,找到News.php文件。 - 复制该文件,并重命名为
Product.php。
修改控制器文件
打开新创建的 app/controller/Product.php 文件,进行以下关键修改:

修改命名空间 命名空间必须与文件所在的目录结构一致。
// 原来的 News.php namespace app\controller; // 修改后的 Product.php namespace app\controller; // 如果目录结构没变,这里可以不变,但养成好习惯,检查一下
修改类名 类名必须与文件名保持一致(大小写敏感)。
// 原来的 News.php class News extends \think\Controller // 修改后的 Product.php class Product extends \think\Controller
修改模型关联
这是最关键的一步,控制器中的所有模型操作都需要从 News 模型改为 Product 模型。
// 原来的 News.php
use app\model\News as NewsModel;
class News extends \think\Controller
{
public function index()
{
// 查询新闻数据
$list = NewsModel::where('status', 1)
->order('id', 'desc')
->paginate(10);
// ... 其他逻辑
}
}
// 修改后的 Product.php
use app\model\Product as ProductModel; // 1. 引入 Product 模型
class Product extends \think\Controller
{
public function index()
{
// 2. 将所有 NewsModel:: 改为 ProductModel::
$list = ProductModel::where('status', 1)
->order('id', 'desc')
->paginate(10);
// ... 其他逻辑
}
}
修改模板赋值
assign() 方法传递给视图的变量名通常可以保持不变,但为了清晰,最好也一并修改。

// 原来的 News.php
$this->assign('list', $list);
$this->assign('page', $list->render());
// 修改后的 Product.php (推荐修改变量名,更清晰)
$this->assign('productList', $list);
$this->assign('productPage', $list->render());
复制视图文件
- 在
app/view/目录下,找到news/index.html文件。 - 复制
news文件夹,并重命名为product。 - 在新的
product文件夹中,打开index.html文件。
修改视图文件
打开 app/view/product/index.html,进行以下修改:
修改模板继承 如果使用了模板继承,需要修改继承的父模板路径。
<!-- 原来的 news/index.html -->
{extend name="public/layout" /}
<!-- 修改后的 product/index.html (如果布局一样,可以不变) -->
{extend name="public/layout" /}
修改变量名
确保变量名与控制器中 assign() 的变量名一致。
<!-- 原来的 news/index.html -->
{volist name="list" id="news"}
<h3>{$news.title}</h3>
<p>{$news.create_time}</p>
{/volist}
{$page}
<!-- 修改后的 product/index.html -->
{volist name="productList" id="product"} <!-- 1. 修改变量名 -->
<h3>{$product.title}</h3>
<p>{$product.price}</p> <!-- 2. 修改字段名,符合 product 表结构 -->
<p>{$product.create_time}</p>
{/volist}
{$productPage} <!-- 3. 修改分页变量名 -->
修改链接和资源路径
所有指向 news 模块的链接、图片、CSS、JS 路径都需要修改。
<!-- 原来的链接 -->
<a href="{:url('news/detail', ['id'=>$news.id])}">查看详情</a>
<!-- 修改后的链接 -->
<a href="{:url('product/detail', ['id'=>$product.id])}">查看详情</a>
<!-- 如果有静态资源 -->
<link rel="stylesheet" href="/static/css/news.css">
<!-- 修改为 -->
<link rel="stylesheet" href="/static/css/product.css">
复制后必须做的检查清单
完成上述修改后,请务必检查以下几点,这是新手最容易出错的地方:
| 检查项 | 说明 | 示例 |
|---|---|---|
| 控制器类名 | 类名必须和文件名完全一致(包括大小写)。 | 文件 Product.php,类名必须是 class Product。 |
| 命名空间 | 命名空间必须与文件在 app 目录下的相对路径一致。 |
文件 app/controller/Product.php,命名空间是 app\controller。 |
| 模型引用 | 确保引入了正确的模型,并且代码中所有模型调用都已更新。 | use app\model\Product; 和 ProductModel::where(...)。 |
| 模板变量 | 检查视图文件中使用的变量是否与控制器 assign() 的变量名匹配。 |
控制器 $this->assign('list', $data);,视图中 {volist name="list" ...}。 |
| URL 路由 | 所有硬编码的 URL(如 href="/news/index")都已修改为新模块的路径(如 href="/product/index")。 |
使用 {:url()} 助手函数是最佳实践,只需修改控制器名即可。 |
| 数据库表前缀 | 如果新模块使用了不同的数据表,请检查模型中的 $table 属性是否正确。 |
protected $table = 'tp_product'; |
| 路由配置 | 如果在 route/app.php 中定义了路由,需要为新模块添加对应的路由规则。 |
Route::get('product', 'product/index'); |
最佳实践与建议
虽然复制可以快速开发,但长期来看,过度复制会导致代码冗余、难以维护,这里有一些更好的建议:
使用继承和公共布局
- 模板继承:将所有页面共同的头部、底部、导航栏抽取到一个
public/layout.html基础模板中,其他页面模板{extend name="public/layout" /}即可,避免重复代码。 - 公共 JS/CSS:将公共的 JS 和 CSS 文件放在
public/static目录下,在基础模板中统一引入。
创建通用控制器基类
如果多个控制器有大量相同的方法(如 index, add, edit, delete),可以创建一个 BaseController。
// app/controller/BaseController.php
namespace app\controller;
use think\Controller;
use app\model\Common as CommonModel; // 假设有一个通用模型
class BaseController extends Controller
{
// 一个通用的列表方法
public function commonIndex($modelClass)
{
$model = new $modelClass();
$list = $model->paginate(15);
$this->assign('list', $list);
return $this->fetch();
}
}
// app/controller/Product.php
namespace app\controller;
use app\BaseController; // 引入基类
use app\model\Product;
class Product extends BaseController
{
public function index()
{
// 直接调用基类的通用方法,传入模型类名
return $this->commonIndex(Product::class);
}
}
谨慎使用“复制粘贴”
在复制之前,先思考一下:这部分逻辑真的不能复用吗?是否能通过重构代码,让一个函数或方法服务于多个场景?这能从根本上减少代码量。
利用 ThinkPHP 的命令行工具
ThinkPHP 提供了命令行工具来生成控制器和模型,虽然不能直接“复制”,但可以基于模板快速生成新模块的骨架。
# 在项目根目录下执行 php think make:controller Product php think make:model Product
这会帮你创建好基础的文件和类结构,你只需要填充业务逻辑即可,比手动复制后修改更规范。
ThinkPHP 复制模板的核心流程是 “复制文件 -> 修改控制器 -> 修改视图”,关键在于 命名空间、类名、模型关联、模板变量和 URL 路径 这五点。
虽然复制是高效的启动方式,但为了项目的长期健康,应积极拥抱 模板继承、公共基类和代码重构 等最佳实践,避免陷入“复制-粘贴-修改-再复制”的恶性循环。
