iCMS 应用开发流程

概述

iCMS v8 提供了完整的应用开发框架,允许开发者创建功能丰富的第三方应用。本文档基于 article(文章应用) 的实际代码编写,详细介绍了应用的开发流程、目录结构和各个组件的作用。

⚠️ 重要提示

本文档所有示例代码均参考 app/article/ 实际代码,确保了写法的准确性。关键要点

类继承关系(必须遵循)

  • TestAdmincp → 继承 AdmincpCommon + 使用 AdmincpCommonTrait 这部分可以 去查看 iCMS 后台应用开发说明
  • TestApp → 继承 AppsBase + 使用 AppsAppTrait
  • TestUserApp → 继承 UserContentApp
  • TestEvent → 继承 DBEvent
  • TestFunc → 继承 AppsFuncCommon + 实现 AppsFuncInterface
  • TestCategoryAdmincp → 继承 NodeAdmincp(构造函数中传入 APPID)

方法命名规范

  • 后台管理(Admincp):使用 do_xxx() 前缀
  • 用户中心(UserApp):使用 api_xxx()done_xxx() 前缀
  • 前台应用(App):使用 do_xxx() 前缀
  • 模板标签(Func):使用 public static function xxx($vars)

Model 事件系统

  • ✅ 在 Model 中使用 $events 数组定义事件
  • ✅ 事件方法签名:public static function 事件名($response, $Builder, $event, $isMulti)
  • ✅ 使用 $casts 定义字段类型转换
  • ✅ 使用 $callback 定义数据库错误回调

配置文件格式

  • ✅ 菜单 href 不包含 admincp.php,直接写 /test/index
  • ✅ 批处理配置使用键值对形式,键为操作标识
  • ✅ 使用 caption 而不是 name 作为菜单标题(在 admincp.json 中)

快速开始

1. 创建应用

  1. 在后台 应用管理 中添加一个应用,选择 第三方应用
  2. app 目录下创建与应用同名的目录(例如:app/test
  3. 按照标准目录结构创建相应的文件

2. 应用命名规范

  • 应用目录名:小写字母,如 test
  • 类文件名:首字母大写驼峰命名,如 TestApp.php
  • 类名:与文件名一致,如 class TestApp

目录结构总览

app/test/
├── TestAdmincp.php              # 后台管理程序
├── TestApp.php                  # 前台程序
├── TestNodeAdmincp.php          # 分类管理
├── TestDataModel.php            # 数据表模型(icms_test_data)
├── TestModel.php                # 主表模型(icms_test)
├── TestEvent.php                # 事件处理类
├── TestFunc.php                 # 模板标签类
├── TestFuncSearch.php           # 搜索模板标签类
├── TestHook.php                 # 系统钩子定义
├── Test.php                     # 应用主类
├── TestUserApp.php              # 用户中心程序
├── TestAaaAdmincp.php           # 其它模块后台管理
├── TestAaaModel.php             # 其它模块数据模型
├── TestAaa.php                  # 其它模块主类
├── assets/                      # 静态资源目录
│   ├── add.css                  # 样式文件
│   └── js/
│       ├── add.js               # 添加页面脚本
│       ├── edit.js              # 编辑页面脚本
│       ├── index.js             # 列表页面脚本
│       ├── user.js              # 用户页面脚本
│       └── vEditor.js           # 自定义脚本
├── etc/                         # 配置文件目录
│   ├── admincp/
│   │   ├── Test.index.batch.json    # 批处理配置
│   │   ├── home.quick.json          # 快捷链接配置
│   │   └── home.stats.json          # 统计配置
│   ├── menu/
│   │   ├── admincp.cache.json       # 缓存菜单配置
│   │   ├── admincp.json             # 后台主菜单配置
│   │   └── usercp.content.json      # 用户中心菜单配置
│   └── route/
│       ├── node.json                # 分类路由配置
│       └── route.json               # 应用路由配置
└── views/                       # 视图模板目录
    ├── aaa/                     # 子模块模板目录
    │   ├── bbb.html             # aaa 模块的 bbb 页面
    │   ├── ccc.html             # aaa 模块的 ccc 页面
    │   └── index.html           # aaa 模块主页
    ├── app/
    │   ├── config/
    │   │   ├── Test.html        # 应用配置页面
    │   │   └── Test.sphinx.html # 应用扩展配置页面
    │   └── node/
    │       └── add.html         # 自定义分类添加页面
    ├── batch.html               # 批处理 UI 模板
    ├── add.html                 # 添加内容页面
    ├── edit.html                # 编辑内容页面
    └── index.html               # 管理列表页面

核心文件详解

1. 后台管理程序 (TestAdmincp.php)

作用:处理应用的后台管理逻辑,包括增删改查等操作。

功能特点

  • 继承 AdmincpCommon 并使用 AdmincpCommonTrait
  • 实现后台管理的各种方法(增、删、改、查)
  • 处理表单提交和数据验证
  • 权限控制和访问验证

常用方法

  • do_index() - 列表页面
  • do_add() - 添加/编辑页面
  • do_save() - 保存数据
  • do_delete() - 删除数据
  • do_batch() - 批量操作

示例(参考 ArticleAdmincp.php):

<?php
defined('iPHP') or exit('What are you doing?');
defined('APP_URL') or define('APP_URL', '/admincp.php/test');

class TestAdmincp extends AdmincpCommon
{
    use AdmincpCommonTrait;

    public static $orderBy = [
        'id'      => 'ID',
        'hits'    => '点击',
        'postime' => '时间',
    ];

    /**
     * 添加/编辑内容
     */
    public function do_add()
    {
        $rs = [];
        // 如果有ID则获取数据(编辑模式)
        if ($this->id) {
            $rs = Test::get($this->id);
        }

        // 获取分类
        $rs['cid'] = $rs['cid'] ?: (int) Request::get('cid');
        $Node = $rs['cid'] ? Node::get($rs['cid']) : [];

        // 新增时的默认值
        if (empty($this->id)) {
            $rs['status'] = '1';
            $rs['postype'] = '1';
            $rs['editor'] = Admin::$nickname;
            $rs['userid'] = Admin::$user_id;
        }

        include self::view();
    }

    /**
     * 保存数据
     */
    public function do_save()
    {
        // 处理数据...
        $data = $this->data();

        if ($this->id) {
            Test::update($data, ['id' => $this->id]);
        } else {
            $this->id = Test::create($data);
        }

        return [true,'保存成功'];
    }
}

访问路径admincp.php/test/indexadmincp.php/test/add


2. 前台程序 (TestApp.php)

作用:处理应用的前台逻辑,包括页面渲染和 API 接口。

功能特点

  • 继承 AppsBase 并使用 AppsAppTrait
  • do_xxx() 方法:渲染模板页面
  • api_xxx() 方法:提供 API 接口(返回 JSON)
  • 静态方法:工具方法,供模板标签等调用
  • display() 方法:通用的数据展示方法

方法类型说明

1. do_ 前缀方法 - 页面渲染(有模板)

  • 用于渲染 HTML 页面
  • 使用 self::render() 返回视图
  • 访问路径:api.php/test/方法名

2. api_ 前缀方法 - API 接口(无模板)

  • 用于提供数据接口
  • 返回数组或 JSON 格式数据
  • 通常供前端 Ajax 调用

3. display() 方法 - 通用展示

  • 标准的数据展示方法
  • 调用 getData() 获取数据
  • 调用 values() 处理数据
  • 调用 render() 渲染模板

4. 静态方法 - 工具方法

  • 供其他地方调用的工具方法
  • values(), data(), resource()

示例代码(参考 VideoApp.php 和 XxxxApp.php)

<?php
class TestApp extends AppsBase
{
    use AppsAppTrait;
    protected static $DATA = [];

    /**
     * do_ 前缀:页面渲染方法(有模板)
     * 访问:api.php/test/detail&id=1
     */
    public function do_detail($id = null, $tpl = null)
    {
        $id === null && $id = (int)Request::get('id');

        // 获取数据
        $test = TestModel::where([
            'id'     => $id,
            'status' => 1
        ])->find();

        empty($test) && self::alert(['errors:not_found', [self::$app, 'id', $id]], 10001);

        // 处理数据
        $vars = ['tag' => true, 'user' => true];
        self::values($test, $vars, $tpl);

        // 渲染模板
        $tpl === null && $tpl = self::$DATA['node']['template']['test:detail'];
        return self::render($test, $tpl);
    }

    /**
     * api_ 前缀:API 接口方法(无模板,返回JSON)
     * 访问:api.php/test/get_list
     */
    public function api_get_list()
    {
        $page = Request::get('page', 1);
        $pageSize = Request::get('pageSize', 10);

        $_GET['page'] = $page;
        $obj = TestModel::where(['status' => 1]);
        $obj->orderby('id', 'desc');
        $rows = $obj->paging($pageSize);

        return [
            'list'      => $rows,
            'total'     => Paging::$total,
            'page'      => $page,
            'pageSize'  => $pageSize
        ];
    }

    /**
     * api_ 前缀:API 接口 - 提交反馈
     */
    public function api_feedback()
    {
        $data = [
            'userid'    => User::$id,
            'test_id'   => Request::post('test_id'),
            'content'   => Request::post('content'),
            'type'      => Request::post('type'),
            'ip'        => Request::ip(),
            'created'   => $_SERVER['REQUEST_TIME'],
        ];

        TestFeedbackModel::create($data);
        return [true, '非常感谢您的反馈!'];
    }

    /**
     * display() 方法:通用展示方法
     * 供路由或其他地方调用
     */
    public function display($value, $field = 'id', $tpl = true)
    {
        $vars = ['tag' => true, 'user' => true];

        // 获取主表数据
        $test = $this->getData($value, $field, $tpl);
        if ($test === false) {
            return false;
        }

        // 获取扩展表数据
        $testData = TestDataModel::field('body, images')
                                 ->where('test_id', $test['id'])
                                 ->first();

        // 处理数据
        self::values($test, $testData, $vars, $tpl);
        self::getCustomData($test, $vars);

        // 渲染
        return self::render($test, $tpl);
    }

    /**
     * 静态方法:数据处理
     * @param array $test 主表数据
     * @param array $data 扩展数据
     * @param array $vars 变量配置
     * @param mixed $tpl 模板
     */
    public static function values(&$test, $data = null, $vars = [], $tpl = false)
    {
        self::initialize($test, $tpl);

        // 处理扩展数据
        if ($data) {
            $test['images'] = FilesPic::findImgUrl($data['images']);
            $test['body'] = $data['body'];
        }

        // 处理标签
        $vars['tag'] && TagApp::getArray($test, $test['node']['name'], 'tags');

        // 使用 AppsCommon 处理通用数据
        AppsCommon::init($test, $vars)
            ->link()        // 生成链接
            ->user()        // 用户信息
            ->comment()     // 评论数
            ->pic()         // 图片处理
            ->hits()        // 点击数
            ->params();     // 参数处理

        return $test;
    }

    /**
     * 静态方法:获取扩展数据
     * @param array $idArray ID数组
     * @param string $fields 字段
     */
    public static function data($idArray = [], $fields = '*')
    {
        $data = TestDataModel::field($fields)->where([
            'test_id' => $idArray,
        ])->select();

        // 转换为以 test_id 为键的数组
        $data = array_column($data, null, 'test_id');
        return $data;
    }

    /**
     * 静态方法:处理资源数据
     */
    public static function resource($value)
    {
        if (is_array($value)) {
            // 生成URL
            $value['iurl'] = (array) Route::get('test:resource', [$value]);
            $value['url'] = $value['iurl']['url'];

            // 生成链接
            $value['link'] = sprintf(
                '<a href="%s" class="test_resource_link" target="_blank">%s</a>',
                $value['url'],
                $value['title']
            );
        }
        return $value;
    }
}

访问路径说明

do_ 方法

  • api.php/test/detail?id=1do_detail()
  • api.php/test/listdo_list()

api_ 方法

  • api.php/test/get_listapi_get_list()
  • api.php/test/feedbackapi_feedback()

display() 方法

  • 通常由路由系统调用
  • 或在其他代码中调用:(new TestApp())->display($id)

核心方法详解

1. getData() - 获取数据(继承自 AppsAppTrait)
$test = $this->getData($value, $field, $tpl);

参数

  • $value: 值(如 ID)
  • $field: 字段名(默认 'id')
  • $tpl: 模板(true/false/模板路径)

返回:数组或 false

2. self::render() - 渲染模板
return self::render($data, $tpl);
return self::render($data, $tpl, 'varName'); // 自定义变量名
return self::render($data, $tpl, 'varName', 'prefix'); // 自定义前缀

参数

  • $data: 数据数组
  • $tpl: 模板路径
  • $varName: 模板变量名(可选)
  • $prefix: 变量前缀(可选)
3. AppsCommon::init() - 通用数据处理
AppsCommon::init($test, $vars)
    ->link()        // 生成 URL 和 link
    ->user()        // 处理用户信息
    ->comment()     // 处理评论数
    ->pic()         // 处理图片
    ->hits()        // 处理点击数
    ->params()      // 处理参数
    ->text2link();  // 文本转链接(可选)

链式调用:可根据需要选择调用的方法

完整示例:视频应用(参考 VideoApp.php)

class VideoApp extends AppsBase
{
    use AppsAppTrait;
    protected static $DATA = [];

    /**
     * 播放页面
     */
    public function do_play($id = null, $vid = null, $tpl = null)
    {
        $id === null && $id = (int)Request::get('id');
        $vid === null && $vid = (int)Request::get('vid');

        $resource = VideoResourceModel::where([
            'id'      => $id,
            'status'  => 1
        ])->find();

        empty($resource) && self::alert(['errors:not_found', [self::$app, 'id', $id]], 10001);

        $resource = self::resource($resource);
        $tpl === null && $tpl = self::$DATA['node']['template']['video:play'];

        // HTTPS 处理
        if (iPHP_REQUEST_SCHEME == 'https') {
            $resource['src'] = str_replace('http://', 'https://', $resource['src']);
        }

        $vars = [];
        AppsCommon::init($resource, $vars)->hits();

        $resource['params'] = [
            'id'       => $resource['id'],
            'video_id' => $resource['video_id'],
            'src'      => $resource['src'],
        ];

        return self::render($resource, $tpl, 'player', 'video');
    }

    /**
     * 获取资源(API接口)
     */
    public function api_get_res()
    {
        View::assign('vrs_count', Request::get('count'));
        View::assign('vrs_video_id', Request::get('video_id'));
        View::assign('vrs_source', Request::get('source'));
        View::display('iCMS://video.ajax.htm');
    }
}

重要提示

  1. 方法命名do_ 用于页面,api_ 用于接口
  2. 参数处理:使用 Request::get() / Request::post()
  3. 数据验证:空数据使用 self::alert() 返回错误
  4. 模板渲染:使用 self::render() 而不是 $this->view()
  5. API返回:返回数组,系统自动转 JSON
  6. 静态方法:供模板标签或其他地方调用
  7. 链式调用:使用 AppsCommon::init()->link()->user()->...

3. 分类管理 (TestCategoryAdmincp.php)

作用:管理应用的分类体系,继承自 NodeAdmincp

功能特点

  • 继承系统分类管理功能
  • 可自定义分类字段和验证规则
  • 支持分类树形结构
  • 可扩展分类特殊属性

使用场景

  • 文章分类
  • 产品分类
  • 视频分类
  • 任何需要分类管理的场景

示例(参考 ArticleCategoryAdmincp.php):

<?php
defined('iPHP') or exit('What are you doing?');

/**
 * 测试应用分类管理
 */
class TestCategoryAdmincp extends NodeAdmincp
{
    public function __construct()
    {
        parent::__construct(iCMS_APP_TEST); // 传入应用ID
        $this->app     = 'test';
        $this->title   = '测试';
        $this->primary = 'cid';

        $this->CONTENT_MODEL = new TestModel();
        $this->NODE_NAME = "栏目";

        /**
         * URL规则选项(可选)
         */
        // $this->setRule();
    }
}

注意事项

  • 必须在构造函数中调用 parent::__construct(应用ID)
  • $this->CONTENT_MODEL 指定内容模型
  • $this->NODE_NAME 定义分类名称

4. 数据模型 (TestModel.php / TestDataModel.php)

TestModel.php - 主表模型

作用:对应数据库主表 icms_test,处理主要业务数据。

功能特点

  • 继承 Model 基类
  • 使用 $casts 定义字段类型转换
  • 使用 $events 数组定义事件监听
  • 数据验证和过滤

示例(参考 ArticleModel.php):

<?php
defined('iPHP') or exit('What are you doing?');

class TestModel extends Model
{
    /**
     * 状态映射
     */
    public static $statusMap = [
        '0' => '草稿',
        '1' => '正常',
        '2' => '回收站',
        '3' => '待审核',
    ];

    /**
     * 字段类型转换
     */
    protected $casts = [
        'picdata' => 'array',      // 自动转换为数组
        'nodeAttrs' => 'array',
    ];

    /**
     * 事件监听
     */
    protected $events = [
        'delete'  => ['TestEvent', 'delete'],   // 删除前
        'changed' => ['TestEvent', 'changed'],  // 创建或更新后
    ];
}

重要属性说明

  • $casts - 字段类型自动转换(如 JSON 转数组)
  • $events - 模型事件绑定到 Event 类的方法
  • $statusMap - 状态值的文本映射

TestDataModel.php - 数据表模型

作用:对应扩展数据表 icms_test_data,存储更多字段信息。

使用场景

  • 主表存储核心字段(标题、分类、状态等)
  • 数据表存储扩展字段(详细内容、自定义字段等)
  • 实现表分离,提高查询效率
  • 支持分表存储(sharding)

示例(参考 ArticleDataModel.php):

<?php
defined('iPHP') or exit('What are you doing?');

class TestDataModel extends Model
{
    protected $casts = [
        // 'body' => 'html',
    ];

    /**
     * 数据库错误回调
     */
    protected $callback = [
        'SQLSTATE:42S02' => [__CLASS__, 'createTable'], // 表不存在自动创建
    ];

    public static $appid = iCMS_APP_TEST;

    /**
     * 分表策略
     * @param int $test_id 主表ID
     * @return TestDataModel
     */
    public static function sharding($test_id)
    {
        $model = self::getInstance();
        $model->sharding = (int)$test_id % 10; // 按ID取模分10张表
        return $model;
    }

    /**
     * 自动创建表
     */
    public static function createTable()
    {
        try {
            $target = self::getTableName();
            $source = self::table(__CLASS__);
            DB::copy($source, $target);
            return true;
        } catch (sException $ex) {
            $state = $ex->getState();
            if ($state === '42000') { // 无创建表权限
                throw $ex;
            }
            return false;
        }
    }
}

分表使用示例

// 使用分表查询
$dataModel = TestDataModel::sharding($test_id);
$data = $dataModel->where('test_id', $test_id)->get();

5. 事件处理类 (TestEvent.php)

作用:处理模型事件,如数据创建、更新、删除时的关联操作。

功能特点

  • 继承 DBEvent 基类
  • 监听模型事件(created、updated、deleted等)
  • 处理关联数据更新(分类、标签、属性等)
  • 文件和缓存管理

事件方法签名
所有事件方法必须遵循以下签名:

public static function 事件名($response, $Builder, $event, $isMulti)

常用事件

  • changed - 创建或更新后触发(包含 created 和 updated)
  • delete - 删除前触发
  • deleted - 删除后触发
  • created - 创建后触发
  • updated - 更新后触发

示例(参考 ArticleEvent.php):

<?php
defined('iPHP') or exit('What are you doing?');

class TestEvent extends DBEvent
{
    public static $appid = iCMS_APP_TEST;

    /**
     * 创建或更新后的事件
     * @param array $response 保存的数据
     * @param object $Builder 查询构建器
     * @param string $event 事件名 (created/updated)
     * @param bool $isMulti 是否批量操作
     */
    public static function changed($response, $Builder, $event, $isMulti)
    {
        // 获取受影响的ID列表
        if ($event == 'updated') {
            $idArr = $Builder->field('id')->pluck();
        } else {
            $idArr = [$response['id']];
        }

        array_map(function ($id) use ($response, $Builder, $event, $isMulti) {
            if (!$id) {
                return;
            }

            // 处理分类变化
            if (isset($response['cid'])) {
                Node::change('cid', $response['cid'], $event, $id, self::$appid);
            }

            // 处理副分类变化
            if (isset($response['scid'])) {
                AppsMap::change('scid', self::$appid, $response['scid'], $event, $id, 'Node');
            }

            // 处理属性变化
            if (isset($response['pid'])) {
                AppsMap::change('pid', self::$appid, $response['pid'], $event, $id, 'Prop');
            }

            // 处理标签变化
            if (isset($response['tags'])) {
                Tag::$APPID = self::$appid;
                if (!isset($response['tags']['raw'])) {
                    Tag::change('tags', $response['tags'], $event, $id, self::$appid, $Builder);
                }
            }

            // 处理缩略图变化
            if (isset($response['pic']) || isset($response['bpic'])) {
                FilesPic::change($response, self::$appid, $event, $id);
            }

            // 内容归档
            if ($event == 'updated') {
                Archive::update(self::$appid, $id, $response);
            } else {
                Archive::save(self::$appid, $id, $response);
            }
        }, $idArr);
    }

    /**
     * 删除前的事件
     */
    public static function delete($response, $Builder, $event, $isMulti)
    {
        $idArr = $Builder->field('id')->pluck();
        array_map(function ($id) {
            if (!$id) {
                return;
            }

            // 删除应用映射(分类、标签、属性)
            AppsMap::delete(self::$appid, $id, 'Node');
            AppsMap::delete(self::$appid, $id, 'Tag');
            AppsMap::delete(self::$appid, $id, 'Prop');

            // 删除关联文件
            Files::delete(self::$appid, $id);

            // 删除评论
            Comment::delete(self::$appid, $id);

            // 删除归档
            Archive::delete(self::$appid, $id);
        }, $idArr);
    }
}

重要说明

  1. 事件在 Model 的 $events 数组中定义
  2. 事件方法必须是 public static
  3. 所有事件方法签名必须一致
  4. $response 包含保存的数据
  5. $Builder 可用于查询当前操作的记录

6. 模板标签类 (TestFunc.php)

作用:为模板提供数据标签,在模板中使用 <!--{iCMS:test:方法名}--> 调用。

功能特点

  • 继承 AppsFuncCommon 并实现 AppsFuncInterface
  • 所有方法必须是 public static
  • 使用基类提供的辅助方法(nodes、tags、props、orderby等)
  • 返回资源数组供模板渲染

模板调用方式

<!--{iCMS:test:list}-->           # 调用 TestFunc::lists() 注:list=>lists() 比较特殊系统处理过的,其它都是对应的方法
<!--{iCMS:test:list row="10"}-->  # 传递参数
<!--{iCMS:test:data id="1"}-->     # 获取详情

示例(参考 ArticleFunc.php):

<?php
defined('iPHP') or exit('What are you doing?');

class TestFunc extends AppsFuncCommon implements AppsFuncInterface
{
    /**
     * 获取列表
     */
    public static function lists($vars)
    {
        $resource  = array();
        $whereNot  = array();
        $model     = TestModel::field('id');
        $status    = isset($vars['status']) ? $vars['status'] : 1;
        $where     = [['status', $status]];

        // 条件筛选
        $vars['call'] == 'user'  && $where[] = ['postype', 0];
        $vars['call'] == 'admin' && $where[] = ['postype', 1];
        isset($vars['userid'])   && $where[] = ['userid', $vars['userid']];
        isset($vars['pic'])      && $where[] = ['haspic', '1'];
        isset($vars['nopic'])    && $where[] = ['haspic', '0'];

        // 日期范围
        if (isset($vars['startdate'])) {
            $where[] = ['pubdate', '>=', str2time($vars['startdate'])];
        }
        if (isset($vars['enddate'])) {
            $where[] = ['pubdate', '<=', str2time($vars['enddate'])];
        }

        // 使用基类方法
        self::inited($vars, $model, $where, $whereNot);
        self::nodes('cid');        // 处理分类
        self::tags();              // 处理标签
        self::props();             // 处理属性
        self::keywords();          // 处理关键词

        // 排序映射
        self::orderby([
            'hot'   => 'hits',
            'today' => 'hits_today',
            'week'  => 'hits_week',
            'month' => 'hits_month'
        ]);

        self::where();
        return self::getResource(__METHOD__);
    }

    /**
     * 获取资源详情(由 getResource 自动调用)
     */
    public static function resource($idsArray = null)
    {
        $vars = self::$vars;
        $resource = TestModel::field('*')
            ->where($idsArray)
            ->orderBy('id', $idsArray)
            ->select();
        $resource = self::many($vars, $resource);
        return $resource;
    }

    /**
     * 获取单条数据
     */
    public static function data($vars)
    {
        $id = $vars['id'] ?: $vars['test_id'];
        $id or self::msg(['func_param', ['iCMS:test:data', 'id']]);

        $where = ['id' => $id];
        $model = TestModel::where($where);

        $hash = md5($model->getSql());
        self::paging($hash, __METHOD__);

        $resource = self::getCache(self::$cacheName);
        if (empty($resource)) {
            $resource = $model->field('*')->get();
            $cacheTime = isset($vars['time']) ? (int) $vars['time'] : -1;
            $vars['cache'] && Cache::set(self::$cacheName, $resource, $cacheTime);
        }

        return $resource;
    }

    /**
     * 上一篇
     */
    public static function prev($vars)
    {
        $vars['order'] = 'p';
        return self::next($vars);
    }

    /**
     * 下一篇
     */
    public static function next($vars)
    {
        // 实现上下篇逻辑
        // ...
    }
}

模板使用示例

<!-- 获取列表 -->
<ul>
<!--{iCMS:test:list row="10" cid="1" order="hits"}-->
    <li><a href="<!--{$test_list.url}-->"><!--{$test_list.title}--></a></li>
<!--{/iCMS}-->
</ul>
<!-- 获取详情 -->
<!--{iCMS:test:data id="$id"}-->
<h1><!--{$test_data.title}--></h1>
<div><!--{$test_data.content}--></div>

重要说明

  1. list() 返回列表,模板中变量为 $test_list
  2. data() 返回单条,模板中变量为 $test_data
  3. 使用基类方法可以自动处理分类、标签、分页等

7. 搜索模板标签类 (TestFuncSearch.php)

作用:专门处理搜索相关的模板标签 <!--{iCMS:test:search}-->

功能特点

  • 实现高级搜索功能
  • 支持多条件筛选
  • 支持分页
  • 支持排序

示例

class TestFuncSearch implements AppsFuncInterface
{
    public static function search($vars)
    {
        $keyword = $vars['keyword'] ?? '';
        $cid = $vars['cid'] ?? 0;
        $page = $vars['page'] ?? 1;
        $pagesize = $vars['pagesize'] ?? 20;

        $query = TestModel::query();

        // 关键词搜索
        if ($keyword) {
            $query->where('title', 'like', "%{$keyword}%")
                  ->orWhere('content', 'like', "%{$keyword}%");
        }

        // 分类筛选
        if ($cid) {
            $query->where('cid', $cid);
        }

        // 分页
        $total = $query->count();
        $list = $query->offset(($page - 1) * $pagesize)
                      ->limit($pagesize)
                      ->get();

        return [
            'list' => $list,
            'total' => $total,
            'page' => $page,
            'pagesize' => $pagesize,
        ];
    }
}

8. 系统钩子 (TestHook.php)

作用:定义和处理系统级别的钩子,在特定事件触发时执行。

功能特点

  • 监听系统事件
  • 扩展系统功能
  • 不修改核心代码的情况下添加功能
  • 实现插件化开发

常用钩子场景

  • 用户注册/登录时
  • 内容发布时
  • 系统初始化时
  • 缓存更新时

示例

class TestHook
{
    // 注册钩子
    public static function hooks()
    {
        return [
            'user.register.after' => 'onUserRegister',
            'content.publish.after' => 'onContentPublish',
            'system.init' => 'onSystemInit',
        ];
    }

    // 用户注册后的钩子
    public static function onUserRegister($user)
    {
        // 为新用户创建默认数据
        TestModel::create([
            'uid' => $user['id'],
            'title' => '欢迎 ' . $user['nickname'],
        ]);
    }

    // 内容发布后的钩子
    public static function onContentPublish($content)
    {
        // 更新统计
        Test::updateStats($content);
    }
}

9. 应用主类 (Test.php)

作用:定义应用的公用方法和常量,提供工具函数。

功能特点

  • 定义应用常量(APP、APPID、状态映射等)
  • 提供数据操作的封装方法
  • 提供静态工具方法
  • 简化常用操作

示例(参考 Article.php):

<?php
class Test
{
    const APP = 'test';
    const APPID = iCMS_APP_TEST;

    /**
     * 状态映射
     */
    public static $stypeMap = array(
        'inbox'   => '0', // 草稿
        'normal'  => '1', // 正常
        'trash'   => '2', // 回收站
        'examine' => '3', // 待审核
        'off'     => '4', // 未通过
        'delete'  => '5', // 删除
        'lock'    => '6', // 锁定
        'hidden'  => '7', // 隐藏
    );

    /**
     * 检查字段值是否存在
     * @param mixed $value 要检查的值
     * @param int $id 排除的ID
     * @param string $field 字段名
     * @return mixed
     */
    public static function check($value, $id = 0, $field = 'title')
    {
        $where = array($field => $value);
        $id && $where['id'] = array('<>', $id);
        return TestModel::field('id')->where($where)->value();
    }

    /**
     * 获取单个字段值
     * @param string $field 字段名
     * @param int $id ID
     * @return mixed
     */
    public static function value($field = 'id', $id = 0)
    {
        if (empty($id)) {
            return '';
        }
        return TestModel::field($field)->where($id)->value();
    }

    /**
     * 获取单条记录
     * @param int $id ID
     * @param string $field 字段
     * @param array $where 条件
     * @return array
     */
    public static function get($id = 0, $field = '*', $where = array())
    {
        $where['id'] = $id;
        return TestModel::field($field)->where($where)->get();
    }

    /**
     * 获取数据(包含扩展表)
     * @param int $id 主表ID
     * @param int $dataId 数据表ID
     * @param int $userid 用户ID
     * @return array [主表数据, 扩展数据, 其他信息]
     */
    public static function data($id = 0, $dataId = 0, $userid = 0)
    {
        $userid && $where['userid'] = $userid;
        $test = TestModel::where($where)->get($id);
        $data = array();

        if ($test) {
            $DataModel = TestDataModel::sharding($test['id']);
            try {
                $DataModel = $DataModel->where('test_id', $test['id']);
                $dataId && $DataModel = $DataModel->where('id', $dataId);
                $data = $DataModel->get();
            } catch (\sException $ex) {
                $state = $ex->getState();
                if ($state == '42S02') { // 表不存在
                    // TestDataModel::createTable();
                }
            }
        }

        return array($test, $data, []);
    }

    /**
     * 创建记录
     * @param array $data 数据
     * @return int 新增ID
     */
    public static function create($data)
    {
        return TestModel::create($data, true);
    }

    /**
     * 更新记录
     * @param array $data 数据
     * @param array $where 条件
     * @return int 影响行数
     */
    public static function update($data, $where)
    {
        return TestModel::update($data, $where);
    }

    /**
     * 删除记录
     * @param int $id ID
     * @return int 影响行数
     */
    public static function delete($id)
    {
        return TestModel::delete($id);
    }
}

重要说明

  1. 常量 APPAPPID 必须定义
  2. 主类提供对 Model 的封装,简化调用
  3. data() 方法用于获取主表+扩展表数据
  4. 使用静态方法,方便全局调用

10. 用户中心程序 (TestUserApp.php)

作用:处理用户中心相关的功能,如用户投稿、内容管理等。

功能特点

  • 继承 UserContentApp 基类
  • 实现用户权限验证
  • 处理用户个人数据
  • 前台用户交互

常用方法

  • api_manage() - 我的内容管理
  • api_publish() - 发布/编辑页面
  • api_delete() - 删除内容
  • done_save() - 保存数据

示例(参考 ArticleUserApp.php):

<?php
class TestUserApp extends UserContentApp
{
    /**
     * 内容管理页面
     */
    public function api_manage()
    {
        return $this->display();
    }

    /**
     * 发布/编辑页面
     */
    public function api_publish()
    {
        $config = User::$config['post'];
        $id = (int)Request::get('id');

        // 获取数据(编辑模式)
        if ($id) {
            $data = Test::get($id);
            // 验证权限
            if ($data['userid'] != User::$id) {
                AppsBase::alert('无权操作');
            }
        }

        View::assign('test', $data);
        return $this->display();
    }

    /**
     * 删除内容
     */
    public function api_delete()
    {
        $id = (int)Request::post('id');
        $id or AppsBase::alert('errors:empty:id');

        // 验证权限并软删除(状态改为2)
        $where = ['id' => $id, 'userid' => User::$id];
        return (bool)Test::update(['status' => 2], $where);
    }

    /**
     * 保存数据
     */
    public function done_save()
    {
        $config = User::$config['post'];

        // 验证码检查
        if ($config['captcha']) {
            Captcha::check();
        }

        // 发布间隔检查
        if ($config['interval']) {
            $last_postime = TestModel::where(['userid' => User::$id])
                                    ->max('postime');
            if ((int)$_SERVER['REQUEST_TIME'] - (int)$last_postime < (int)$config['interval']) {
                AppsBase::alert('user:publish:interval');
            }
        }

        $data = Request::post();

        // 数据过滤和验证
        $data = array_filter_keys($data, 'id,cid,title,body,source,author,pic,tags,description');

        $data['userid'] = User::$id;
        $data['author'] = $data['author'] ?: User::$nickname;
        $data['editor'] = User::$nickname;

        // 验证必填项
        empty($data['title']) && iJson::error('user:publish:empty:title');
        empty($data['cid']) && iJson::error('user:publish:empty:cid');
        empty($data['body']) && iJson::error('user:publish:empty:body');

        // 内容过滤
        array_walk_recursive($data, function (&$value, $key) {
            $fwd = iPHP::callback('Filter::run', [&$value], false);
            $fwd && iJson::error('user:publish:filter_' . $key);
        });

        $data['pubdate'] = time();
        $data['postype'] = '0'; // 用户发布

        // 检查权限,决定是否需要审核
        $node = NodeCache::getId($data['cid']);
        $roleArray = $node['config']['role'];
        $data['status'] = UserCenter::checkRole($roleArray['examine']) ? 3 : 1;

        // 保存数据
        if ($data['id']) {
            Test::update($data, ['id' => $data['id'], 'userid' => User::$id]);
        } else {
            $data['postime'] = time();
            $data['id'] = Test::create($data);
        }

        $msgMap = [
            '1' => 'user:publish:success',
            '3' => 'user:publish:examine',
        ];
        $url = Route::create('TestUser/manage');
        return [true, $msgMap[$data['status']], $url];
    }
}

访问路径api.php/TestUser/manageapi.php/TestUser/publish

重要说明

  1. 方法前缀使用 api_ 而不是 do_
  2. 保存方法使用 done_save() 前缀
  3. 必须验证用户权限(userid)
  4. 用户发布的内容 postype0
  5. 根据分类权限决定是否需要审核

11. 其它模块开发

应用可以包含多个子模块,每个模块遵循类似的命名规范。

TestAaaAdmincp.php - 子模块后台管理

作用:管理应用的子模块(如 aaa 模块)。

示例:测试应用(test)的问答模块(qa)

  • TestQaAdmincp.php - 问答后台管理
  • TestQaModel.php - 问答数据模型(icms_test_qa 表)
  • TestQa.php - 问答主类

TestAaaModel.php - 子模块数据模型

作用:对应子模块的数据表 icms_test_aaa

TestAaa.php - 子模块主类

作用:提供子模块的公共方法和工具函数。


静态资源目录 (assets/)

目录结构

assets/
├── add.css          # 样式文件
└── js/
    ├── add.js       # 添加页面脚本
    ├── edit.js      # 编辑页面脚本
    ├── index.js     # 列表页面脚本
    ├── user.js      # 用户页面脚本
    └── vEditor.js   # 自定义脚本

JavaScript 文件命名规则

脚本文件名与后台方法对应:

  • add.jsadmincp.php/test/add 页面自动加载
  • edit.jsadmincp.php/test/edit 页面自动加载
  • index.jsadmincp.php/test/index 页面自动加载
  • user.jsadmincp.php/test/user 页面自动加载

add.js 示例

define(["iCMS", "vue"], function (iCMS, Vue) {
    // 添加一个虚假的历史记录项
    window.history.pushState(null, null, window.location.href);

    // 监听浏览器的返回按钮事件
    window.addEventListener('popstate', function (event) {
        if (confirm('您确定要离开此页面吗?')) {
            // 如果用户确认,则允许跳转
            // 如果需要真正禁用,这里不执行任何操作即可
        } else {
            // 用户取消,重新推入当前状态
            window.history.pushState(null, null, window.location.href);
        }
    });
    // window.addEventListener("beforeunload", function (e) {
    //     const confirmationMessage = "您确定要离开此页面吗?";
    //     e.returnValue = confirmationMessage; // 兼容旧浏览器
    //     return confirmationMessage;          // 兼容新浏览器
    // });
    const factory = {
        add_success: function (json) {
            console.log(json);
        },
        edit: function () {

        },
        add: function () {
            $(".cms-editor").on('change', function () {
                console.log('内容改变了');
            });

        },
        storage: function () {

        },

    };

    return factory;
});

自定义脚本引用

vEditor.js,可以在其它脚本手动引入:

define(["iCMS", "vue", "./vEditor.js"], function (iCMS, Vue, vEd) {
  vEd.aaa();//调整vEditor.js内部方法

CSS 文件

可以创建任意 CSS 文件,在模板中引入:

<link rel="stylesheet" href="{{APP_URL}}/test/assets/add.css">

配置文件目录 (etc/)

1. admincp/ - 后台配置

Test.index.batch.json - 批处理配置

作用:定义 admincp.php/test/index 页面的批量操作。

示例(参考 Article.index.batch.json):

{
    "pubdate=now": {
        "name": "更新发布时间",
        "icon": "clock",
        "sort": 1,
        "call": "default"
    },
    "status=1&pubdate=now": {
        "name": "发布并更新时间",
        "icon": "clock",
        "sort": 2,
        "call": "default",
        "show": {
            "stype": ["!=", "normal"]
        }
    },
    "status=0": {
        "name": "转为草稿",
        "icon": "inbox",
        "sort": 3,
        "call": "default"
    },
    "status=2": {
        "name": "移入回收站",
        "icon": "trash-alt",
        "sort": 5,
        "call": "default"
    },
    "move": {
        "name": "移动栏目",
        "icon": "fighter-jet",
        "sort": 10,
        "call": "move"
    },
    "prop": {
        "name": "设置属性",
        "icon": "puzzle-piece",
        "sort": 12,
        "call": "prop"
    },
    "tag": {
        "name": "设置标签",
        "icon": "tags",
        "sort": 17,
        "call": "tag"
    },
    "divider1": {
        "name": "divider",
        "sort": 19
    },
    "dels": {
        "name": "删除",
        "icon": "trash-alt",
        "sort": 21,
        "call": "dels"
    }
}

字段说明

  • name: 操作名称
  • icon: 图标类名(Font Awesome)
  • sort: 排序
  • call: 调用的方法名(default 为默认更新,其他为自定义方法)
  • show: 显示条件(可选)
  • divider: 分隔线(name 为 "divider")

home.quick.json - 快捷链接配置

作用:在管理首页添加快捷操作链接。

示例

{
  "quick": [
    {
      "name": "添加内容",
      "icon": "fa fa-plus",
      "url": "admincp.php/test/add",
      "class": "btn-primary"
    },
    {
      "name": "内容管理",
      "icon": "fa fa-list",
      "url": "admincp.php/test/index"
    }
  ]
}

2. menu/ - 菜单配置

admincp.json - 后台主菜单

作用:定义应用在后台的菜单结构。

示例(参考 article/etc/menu/admincp.json):

[
    {
        "id": "test",
        "sort": "10",
        "caption": "测试应用",
        "icon": "si si-grid",
        "children": [
            {
                "caption": "应用配置",
                "href": "/test/config",
                "icon": "cog",
                "sort": 103,
                "access": "/test/config",
                "id": "menu_test_config"
            },
            {
                "caption": "添加内容",
                "href": "/test/add",
                "icon": "edit",
                "sort": 104,
                "access": "/test/add",
                "id": "menu_test_add"
            },
            {
                "caption": "栏目管理",
                "href": "/testCategory/tree",
                "icon": "sitemap",
                "sort": 105,
                "access": "/testCategory/tree",
                "id": "menu_test_category",
                "children": [
                    {
                        "caption": "添加栏目",
                        "href": "/testCategory/add",
                        "icon": "edit",
                        "sort": 106,
                        "access": "/testCategory/add",
                        "id": "menu_test_category_add"
                    },
                    {
                        "caption": "更新缓存",
                        "href": "/testCategory/cache",
                        "icon": "sync",
                        "ui": "cms:ui:ajax",
                        "sort": 107,
                        "access": "/testCategory/cache",
                        "id": "menu_test_category_cache"
                    }
                ],
                "expanded": true
            },
            {
                "caption": "-",
                "sort": 108,
                "access": "test-divider-108",
                "id": "menu_test_divider1"
            },
            {
                "caption": "内容管理",
                "href": "/test/index",
                "icon": "list",
                "sort": 109,
                "access": "/test/index",
                "id": "menu_test_index",
                "children": [
                    {
                        "caption": "草稿箱",
                        "href": "/test/index/inbox",
                        "icon": "inbox",
                        "sort": 110,
                        "access": "/test/index/inbox",
                        "id": "menu_test_inbox"
                    },
                    {
                        "caption": "回收站",
                        "href": "/test/index/trash",
                        "icon": "trash-alt",
                        "sort": 111,
                        "access": "/test/index/trash",
                        "id": "menu_test_trash"
                    }
                ],
                "expanded": true
            }
        ],
        "access": "test",
        "expanded": false
    }
]

字段说明

  • id: 菜单唯一标识
  • caption: 菜单标题
  • icon: 图标类名(Font Awesome 或 Simple Icons)
  • href: 链接地址(不含 admincp.php,如 /test/index
  • sort: 排序(数字越小越靠前)
  • access: 权限标识
  • children: 子菜单数组
  • expanded: 是否展开
  • ui: UI 交互方式(如 cms:ui:ajax 表示 Ajax 请求)
  • caption: "-": 分隔线

admincp.cache.json - 缓存菜单配置

作用:在缓存工具下注入文章相关的缓存清理工具。

示例

[{
    "id": "tools",
    "children": [{
        "id": "cache",
        "children": [{
            "caption": "更新文章栏目缓存",
            "href": "/articleCategory/cache",
            "icon": "sync",
            "ui": "cms:ui:ajax"
        }, {
            "caption": "更新文章栏目统计",
            "href": "/articleCategory/recount",
            "icon": "sync",
            "ui": "cms:ui:ajax"
        }]
    }]
}]

usercp.content.json - 用户中心菜单

作用:定义用户中心的应用菜单。

示例

[
    {
        "id": "ArticleUser",
        "sort": 5,
        "caption": "我的文章",
        "icon": "fa fa-cog",
        "children": [{
                "id": "ArticleUserManage",
                "sort": 5,
                "caption": "文章管理",
                "url": "ArticleUser/manage",
                "icon": "fa fa-cog",
                "app": "article",
                "template": "iCMS://user/article/manage.htm"
            },
            {
                "id": "ArticleUserPublish",
                "sort": 7,
                "caption": "撰写文章",
                "url": "ArticleUser/publish",
                "icon": "fa fa-cog",
                "app": "article",
                "template": "iCMS://user/article/publish.htm"
            }
        ]
    }
]

3. route/ - 路由配置

route.json - 应用路由配置

作用:定义应用的前台路由规则。

示例

{
    "user": "app=user",
    "user/home": "app=user&do=home",
    "user/manage/category": "app=user&do=manage&s=category",
    "{userid}": "app=user&do=home&userid={userid}",
    "{userid}/home": "app=user&do=home&userid={userid}",
    "{userid}/follower": "app=user&do=follower&userid={userid}",
    "{userid}/{cid}": "app=user&do=home&userid={userid}&cid={cid}",
    "{userid}/favorite/{id}": "app=user&do=favorite&userid={userid}&id={id}",
}

字段说明

  • "URL 路径":"转发目标"
  • "{参数1}/aaa/bbb":"转发目标{参数1}"
  • "/aaa/ccc/{参数1}/{参数2}":"转发目标{参数1}/{参数2}"

路由效果

  • /testapi.php/test/index
  • /test/123api.php/test/detail?id=123

node.json - 分类路由配置

作用:定义分类中应用的配置。

示例

{
    "test": {
        "label": "test",
        "template": "{iTPL}\/test.htm",
        "rule": "\/{CDIR}\/{YYYY}\/{MM}{DD}\/{ID}{EXT}",
        "tips": "{ID},{0xID},{LINK},{HASH@ID},{HASH@0xID}"
    }
}

其它示例

{
    "index": {
        "label": "首页",
        "template": "{iTPL}\/video.index.htm",
        "rule": "\/{CDIR}\/",
        "tips": "{CID},{0xCID},{CDIR},{HASH@CID},{HASH@0xCID}",
        "info": "[NODE@NAME]的首页模板(可制作用于频道封面、单页等)"
    },
    "list": {
        "label": "列表",
        "template": "{iTPL}\/video.list.htm",
        "rule": "\/{CDIR}\/index_{P}{EXT}",
        "tips": "{CID},{0xCID},{CDIR},{HASH@CID},{HASH@0xCID}",
        "info": "当[NODE@NAME]有分页且当前页号大于1时使用该模板"
    },
    "video": {
        "label": "视频",
        "template": "{iTPL}\/video.htm",
        "rule": "\/video\/{ID}{EXT}",
        "tips": "{ID},{0xID},{LINK},{HASH@ID},{HASH@0xID}"
    },
    "video:play": {
        "label": "播放",
        "template": "{iTPL}\/video.play.htm",
        "rule": "\/play\/{VIDEO:ID}\/{ID}{EXT}",
        "tips": "{VIDEO:ID},{ID},{0xID},{HASH@ID},{HASH@0xID}"
    },
    "video:down": {
        "label": "下载",
        "template": "{iTPL}\/video.down.htm",
        "rule": "\/down\/{VIDEO:ID}\/{ID}{EXT}",
        "tips": "{VIDEO:ID},{ID},{0xID},{HASH@ID},{HASH@0xID}"
    },
    "video:story": {
        "label": "剧情",
        "template": "{iTPL}\/video.story.htm",
        "rule": "\/story\/{VIDEO:ID}\/{ID}{EXT}",
        "tips": "{VIDEO:ID},{ID},{0xID},{HASH@ID},{HASH@0xID}"
    },
    "tag": {
        "label": "标签",
        "template": "{iTPL}\/video.tag.htm",
        "rule": "\/video\/{TKEY}{EXT}",
        "tips": "{ID},{0xID},{TKEY},{NAME},{ZH_CN},{HASH@ID},{HASH@0xID}"
    }
}

视图模板目录 (views/)

重要说明

iCMS v8 的视图模板使用 PHP 混合模式,不是传统的 Smarty 模板。主要特点:

  1. ✅ 使用 self::head()self::foot() 输出头尾
  2. ✅ 使用 <?php ... ?> 嵌入 PHP 代码
  3. ✅ 使用 {{变量名}} 输出变量(双花括号)
  4. ✅ 使用 i="cms:ui:xxx" 定义 UI 组件
  5. ✅ 列表页的表格数据由 JavaScript 动态加载
  6. ✅ 表单使用 i="cms:ui:form" 提交

1. 主要模板文件

index.html - 管理列表页

作用:后台内容列表页面。

访问路径admincp.php/test/index

功能

  • 搜索表单
  • 数据表格(由 JS 动态加载)
  • 工具栏(添加、编辑、删除、批量操作)

完整示例(参考 article/views/index.html):

<?php
defined('iPHP') or exit('What are you doing?');
self::head();
?>
<div class="content" id="{{APP_MAINID}}">

    <!-- 搜索区域 -->
    <div class="block block-rounded">
        <div class="block-header block-header-default">
            <h3 class="block-title">搜索</h3>
            <div class="block-options">
                <button type="button" class="btn-block-option" data-toggle="block-option" data-action="content_toggle">
                    <i class="si si-arrow-up"></i>
                </button>
            </div>
        </div>
        <div class="block-content block-content-full">
            <form action="{{APP_DOURL}}" method="get" class="table-search">
                <div class="row">
                    <!-- 状态筛选 -->
                    <div class="col-md-6 col-lg-4 mt-1">
                        <div class="input-group input-group-sm">
                            <label class="input-group-text">状态</label>
                            <input type="text" data-source='statusMap' class="form-control" 
                                   i="cms:ui:selectpicker" name="status" value="">
                        </div>
                    </div>

                    <!-- 分类筛选 -->
                    <div class="col-md-6 col-lg-4 mt-1">
                        <div class="input-group input-group-sm">
                            <label class="input-group-text">栏目</label>
                            <input type="text" class="form-control" i="cms:ui:node" 
                                   data-node-access="cm" name="cid" value="">
                        </div>
                    </div>

                    <!-- 日期范围 -->
                    <div class="col-md-6 col-lg-4 mt-1">
                        <div class="input-group input-group-sm">
                            <span class="input-group-text">发布时间</span>
                            <input type="text" class="form-control" i="cms:ui:daterangepicker" 
                                   name="pubdate" value="<?php echo $_GET['pubdate']; ?>" 
                                   placeholder="开始时间 - 结束时间" />
                        </div>
                    </div>

                    <!-- 排序和每页数量 -->
                    <div class="col-md-6 col-lg-4 mt-1">
                        <div class="input-group input-group-sm">
                            <label class="input-group-text">排序</label>
                            <select data-source='./orderby' class="form-control" 
                                    i="cms:ui:bs-selectpicker" name="orderby">
                                <option value="">默认排序</option>
                            </select>
                            <label class="input-group-text">每页</label>
                            <input class="form-control" style="max-width:56px;" type="text" 
                                   name="pageSize" value="<?php echo Paging::$pageSize; ?>" />
                            <label class="input-group-text">条</label>
                        </div>
                    </div>

                    <!-- 关键词搜索 -->
                    <div class="col-md-6 col-lg-4 mt-1">
                        <div class="input-group input-group-sm">
                            <label class="input-group-text">查找</label>
                            <select name="st" class="form-control">
                                <option value="title" selected>标题</option>
                                <option value="tag">标签</option>
                                <option value="id">ID</option>
                            </select>
                            <input type="text" name="keywords" class="form-control" 
                                   value="<?php echo $_GET['keywords']; ?>" />
                        </div>
                    </div>

                    <!-- 搜索按钮 -->
                    <div class="col-sm-12 d-flex justify-content-end mt-2">
                        <button class="btn btn-sm btn-alt-primary table-btn-search me-2" type="submit">
                            <i class="fa fa-fw fa-search"></i> 搜 索
                        </button>
                        <button class="btn btn-sm btn-alt-secondary table-btn-reset" type="reset">
                            <i class="fa fa-fw fa-repeat"></i> 重 置
                        </button>
                    </div>
                </div>
            </form>
        </div>
    </div>

    <!-- 数据表格区域 -->
    <div class="block">
        <div class="block-content p-0">
            <!-- 工具栏 -->
            <div id="toolbar" class="toolbar row">
                <div class="input-group input-group-sm" role="group">
                    <a href="{{test/add}}" id="add" class="btn btn-alt-primary">
                        <i class="fa fa-fw fa-add"></i> {{#Lang('Add')}}
                    </a>
                    <a href="{{test/edit}}" id="edit" class="btn btn-alt-primary table-btn-edit disabled" disabled>
                        <i class="fa fa-fw fa-edit"></i> {{#Lang('Edit')}}
                    </a>
                    <a href="{{test/delete}}" id="delete" class="btn btn-alt-primary table-btn-delete disabled" disabled>
                        <i class="fa fa-fw fa-trash"></i> {{#Lang('Delete')}}
                    </a>
                    <?php AdmincpBatch::group(); ?>
                </div>
            </div>

            <!-- 数据表格(由 JS 动态加载数据) -->
            <div class="table-responsive">
                <table id="table" width="100%"></table>
            </div>
        </div>
    </div>

</div>
<?php self::foot(); ?>

关键点说明

  1. i="cms:ui:xxx" - UI 组件声明

    • i="cms:ui:selectpicker" - 下拉选择器
    • i="cms:ui:node" - 分类选择器
    • i="cms:ui:daterangepicker" - 日期范围选择器
    • i="cms:ui:bs-selectpicker" - Bootstrap 选择器
  2. {{变量名}} - 变量输出

    • {{APP_MAINID}} - 应用主ID
    • {{APP_DOURL}} - 应用处理URL
    • {{test/add}} - 生成URL
    • {{#Lang('Add')}} - 语言包函数
  3. <table id="table"></table> - 空表格,数据由对应的 index.js 动态加载

  4. <?php AdmincpBatch::group(); ?> - 批量操作按钮组


add.html - 添加/编辑内容页

作用:后台添加和编辑内容页面(同一个页面)。

访问路径

  • 添加:admincp.php/test/add
  • 编辑:admincp.php/test/add?id=1

完整示例(参考 article/views/add.html):

<?php
defined('iPHP') or exit('What are you doing?');
self::head();
?>
<div class="content" id="{{APP_MAINID}}">
    <div class="block">
        <!-- 选项卡导航 -->
        <ul i="cms:ui:tabs" class="nav nav-tabs nav-tabs-block" data-toggle="tabs" role="tablist">
            <li class="nav-item">
                <a class="nav-link active" href="#test-base">
                    <i class="fa fa-fw fa-info-circle"></i> 基本信息
                </a>
            </li>
            <li class="nav-item">
                <a class="nav-link" href="#test-publish">
                    <i class="fa fa-fw fa-rocket"></i> 发布设置
                </a>
            </li>
            <li class="nav-item">
                <a class="nav-link" href="#apps-meta">
                    <i class="fa fa-fw fa-sitemap"></i> 动态属性
                </a>
            </li>
        </ul>

        <div class="block-content">
            <!-- 表单 -->
            <form action="{{APP_DOURL}}" method="POST" i="cms:ui:form" data-success="cms:test:add:success">
                <!-- 隐藏字段 -->
                <input name="rs[id]" type="hidden" value="<?php echo $this->id; ?>" />
                <input name="rs[userid]" type="hidden" value="<?php echo $rs['userid']; ?>" />
                <input name="REFERER" type="hidden" value="<?php echo iPHP_REFERER; ?>" />

                <div id="test-add" class="tab-content">
                    <!-- 基本信息选项卡 -->
                    <div id="test-base" class="tab-pane active">
                        <div class="row">
                            <div class="col-sm-9 border-end">
                                <!-- 标题 -->
                                <div class="form-floating mb-3">
                                    <input type="text" name="rs[title]" 
                                           class="form-control form-control-lg fw-bold fs-4" 
                                           id="title" value="<?php echo $rs['title']; ?>" 
                                           data-rule="required" />
                                    <label for="title">标题</label>
                                </div>

                                <!-- 分类的动态属性 -->
                                <?=NodeWidget::attrs($rs['nodeAttrs'],'rs[nodeAttrs]','#cid');?>

                                <!-- 编辑器区域 -->
                                <div class="form-group">
                                    <label>内容</label>
                                    <textarea name="rs[body]" class="form-control cms-editor" 
                                              i="cms:ui:editor" rows="20"><?php echo $rs['body']; ?></textarea>
                                </div>

                                <!-- 提交按钮 -->
                                <div class="form-group">
                                    <button class="btn btn-primary" type="submit" 
                                            data-loading-text="提交中,请稍候...">
                                        <i class="fa fa-fw fa-check"></i> 提交
                                    </button>
                                </div>
                            </div>

                            <div class="col-sm-3">
                                <!-- 分类选择 -->
                                <div class="form-floating mb-3">
                                    <input type="text" name="rs[cid]" class="form-control" 
                                           id="cid" i="cms:ui:node" data-node-access="cm" 
                                           value="<?php echo $rs['cid']; ?>" data-rule="required" />
                                    <label for="cid">栏目</label>
                                </div>

                                <!-- 副栏目 -->
                                <div class="form-floating mb-3">
                                    <input type="text" name="rs[scid]" class="form-control" 
                                           id="scid" i="cms:ui:node" data-max="5" multiple="multiple" 
                                           value="<?php echo $rs['scid']; ?>" />
                                    <label for="scid">副栏目</label>
                                </div>

                                <!-- 缩略图 -->
                                <div class="form-floating mb-3">
                                    <input type="text" name="rs[pic]" class="form-control" 
                                           id="pic" i="cms:ui:pic" 
                                           value="<?php echo $rs['pic']; ?>" />
                                    <label for="pic">缩略图</label>
                                </div>

                                <!-- 标签 -->
                                <div class="form-floating mb-3">
                                    <input type="text" name="rs[tags]" class="form-control" 
                                           id="tags" i="cms:ui:tags" 
                                           value="<?php echo $rs['tags']; ?>" />
                                    <label for="tags">标签</label>
                                </div>

                                <!-- 描述 -->
                                <div class="form-floating mb-3">
                                    <textarea name="rs[description]" class="form-control" 
                                              id="description" rows="4"><?php echo $rs['description']; ?></textarea>
                                    <label for="description">描述</label>
                                </div>
                            </div>
                        </div>
                    </div>

                    <!-- 发布设置选项卡 -->
                    <div id="test-publish" class="tab-pane">
                        <div class="row">
                            <!-- 状态 -->
                            <div class="col-md-6 mb-3">
                                <div class="form-floating">
                                    <select name="rs[status]" class="form-control" id="status" 
                                            i="cms:ui:bs-selectpicker" data-source="statusMap">
                                        <option value="<?php echo $rs['status']; ?>" selected></option>
                                    </select>
                                    <label for="status">状态</label>
                                </div>
                            </div>

                            <!-- 发布时间 -->
                            <div class="col-md-6 mb-3">
                                <div class="form-floating">
                                    <input type="text" name="rs[pubdate]" class="form-control" 
                                           id="pubdate" i="cms:ui:datetimepicker" 
                                           value="<?php echo $rs['pubdate'] ? date('Y-m-d H:i:s', $rs['pubdate']) : ''; ?>" />
                                    <label for="pubdate">发布时间</label>
                                </div>
                            </div>

                            <!-- 来源 -->
                            <div class="col-md-6 mb-3">
                                <div class="form-floating">
                                    <input type="text" name="rs[source]" class="form-control" 
                                           id="source" value="<?php echo $rs['source']; ?>" />
                                    <label for="source">来源</label>
                                </div>
                            </div>

                            <!-- 作者 -->
                            <div class="col-md-6 mb-3">
                                <div class="form-floating">
                                    <input type="text" name="rs[author]" class="form-control" 
                                           id="author" value="<?php echo $rs['author']; ?>" />
                                    <label for="author">作者</label>
                                </div>
                            </div>
                        </div>
                    </div>

                    <!-- 动态属性选项卡 -->
                    <div id="apps-meta" class="tab-pane">
                        <?php AppsWidget::metadata($rs['id'], $rs['metadata']); ?>
                    </div>
                </div>
            </form>
        </div>
    </div>
</div>
<?php self::foot(); ?>

关键点说明

  1. i="cms:ui:form" - 表单组件,自动处理提交

    • data-success="cms:test:add:success" - 成功后的回调
  2. UI 组件

    • i="cms:ui:editor" - 编辑器(UEditor/Markdown)
    • i="cms:ui:node" - 分类选择器
    • i="cms:ui:pic" - 图片上传
    • i="cms:ui:tags" - 标签输入
    • i="cms:ui:datetimepicker" - 日期时间选择器
    • i="cms:ui:bs-selectpicker" - Bootstrap 下拉选择
  3. 数据绑定

    • 使用 name="rs[字段名]" 格式
    • 使用 <?php echo $rs['字段名']; ?> 输出值
    • data-rule="required" 标记必填项
  4. 小部件

    • <?=NodeWidget::attrs(...)?> - 分类动态属性
    • <?php AppsWidget::metadata(...); ?> - 动态属性管理

edit.html - 编辑内容页

作用:复用添加页面。

访问路径admincp.php/test/edit?id=1

示例(参考 article/views/edit.html):

<?php include self::view('add'); ?>

说明

  • 编辑页面直接复用 add.html
  • 通过 $this->id 判断是添加还是编辑模式
  • 编辑时自动填充 $rs 数组中的数据

batch.html - 批处理 UI 模板

作用:定义批量操作的表单 UI。

访问路径:系统自动调用

示例(参考 article/views/batch.html):

<!-- 设置副栏目 -->
<div batch="scid">
    <div class="input-group input-group-sm">
        <label class="input-group-text">副栏目</label>
        <input name="BatchData[scid]" class="form-control" 
               i="cms:ui:node" data-max="5" multiple="multiple" 
               placeholder="请选择副栏目(可多选)..." value="">
    </div>
</div>

<!-- 设置发布类型 -->
<div batch="postype">
    <div class="input-group input-group-sm">
        <label class="input-group-text">发布类型</label>
        <input name="BatchData[postype]" type="text" 
               data-source='postypeMap' class="form-control" 
               i="cms:ui:selectpicker" value="">
    </div>
</div>

<!-- 设置动态属性 -->
<div batch="meta">
    <?php AppsWidget::metadata(0); ?>
</div>

关键点说明

  1. batch="操作标识" - 与 Test.index.batch.json 中的 key 对应
  2. name="BatchData[字段名]" - 批量数据格式
  3. 可以使用所有 i="cms:ui:xxx" UI 组件

2. 常用 UI 组件列表

iCMS v8 提供了丰富的 UI 组件,通过 i="cms:ui:xxx" 属性使用:

组件 说明 示例
cms:ui:form 表单提交 ``
cms:ui:editor 富文本编辑器 ``
cms:ui:node 分类选择器 ``
cms:ui:pic 图片上传 ``
cms:ui:tags 标签输入 ``
cms:ui:selectpicker 下拉选择 ``
cms:ui:bs-selectpicker Bootstrap选择 ``
cms:ui:datepicker 日期选择 ``
cms:ui:datetimepicker 日期时间选择 ``
cms:ui:daterangepicker 日期范围选择 ``
cms:ui:tabs 选项卡 ``
cms:ui:checked 复选框状态 ``

常用属性

  • data-source='statusMap' - 数据源(从 Model 的 $statusMap)
  • data-source='./orderby' - 数据源(从 API)
  • data-rule="required" - 验证规则(必填)
  • data-max="5" - 最大选择数量
  • multiple="multiple" - 多选
  • data-success="cms:test:add:success" - 成功回调

3. 应用配置模板 (views/app/config/)

配置模板使用纯 PHP/HTML,系统会自动处理保存。

Test.html - 基本配置

访问路径admincp.php/apps/config?app=test

示例

id="pagesize" value="" />
            每页显示数量







                   class="form-check-input" id="enable_audit" value="1" 
                   >
            启用审核







                >UEditor
                >Vditor(Markdown)

            编辑器

Test.sphinx.html - 扩展配置

作用:高级或扩展配置。


4. 分类自定义模板 (views/app/node/)

add.html - 分类额外字段

作用:在分类添加/编辑页面添加自定义字段。

示例

id="meta_icon" value="" />
    分类图标





           id="meta_color" value="" />
    分类颜色

5. 模板变量说明

常用系统变量

变量 说明
{{APP_MAINID}} 应用主容器ID
{{APP_DOURL}} 当前处理URL
{{APP_URL}} 应用URL
{{#Lang('key')}} 语言包函数
id; ?> 当前编辑的ID
`` 数据字段
`` 来源URL
`` 管理员昵称
`` 分页大小

PHP 代码规范

// 1. 安全检查(必须)
defined('iPHP') or exit('What are you doing?');

// 2. 输出头部
self::head();
?>




// 3. 输出尾部
self::foot(); 
?>

6. 实际开发建议

  1. 复制现有模板

    • app/article/views/ 复制对应文件
    • 替换应用名称和字段
  2. 最小化模板

    views/
    ├── index.html    # 必需:列表页
    ├── add.html      # 必需:添加/编辑页
    ├── edit.html     # 必需:复用 add.html
    └── batch.html    # 可选:批量操作
  3. 模板复用

    // 复用 add.html
  4. 调试技巧

    // 查看数据
      // 查看配置

数据库设计规范

1. 主表设计 (icms_test)

基本字段

CREATE TABLE `icms_test` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT 'ID',
  `cid` int(11) unsigned DEFAULT '0' COMMENT '分类ID',
  `uid` int(11) unsigned DEFAULT '0' COMMENT '用户ID',
  `title` varchar(255) NOT NULL COMMENT '标题',
  `description` varchar(500) DEFAULT NULL COMMENT '描述',
  `tags` varchar(255) DEFAULT NULL COMMENT '标签',
  `status` tinyint(1) DEFAULT '1' COMMENT '状态:0草稿,1发布,2待审核',
  `hits` int(11) unsigned DEFAULT '0' COMMENT '点击数',
  `ordernum` int(11) DEFAULT '0' COMMENT '排序',
  `created_at` int(11) unsigned DEFAULT '0' COMMENT '创建时间',
  `updated_at` int(11) unsigned DEFAULT '0' COMMENT '更新时间',
  PRIMARY KEY (`id`),
  KEY `cid` (`cid`),
  KEY `uid` (`uid`),
  KEY `status` (`status`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='测试应用主表';

2. 数据表设计 (icms_test_data)

扩展字段

CREATE TABLE `icms_test_data` (
  `id` int(11) unsigned NOT NULL COMMENT 'ID,关联主表',
  `body` longtext COMMENT '正文内容',
  `template` varchar(100) DEFAULT NULL COMMENT '模板',
  `metadata` text COMMENT '元数据(JSON)',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='测试应用数据表';

3. 子模块表设计 (icmstest*)

子模块表命名:icms_应用名_模块名

例如:icms_test_qa(测试应用的问答模块)


开发流程详解

第一步:创建应用

  1. 在后台 应用管理添加应用
  2. 选择 第三方应用
  3. 填写应用信息:
    • 应用标识:test(英文小写)
    • 应用名称:测试应用
    • 应用描述:这是一个测试应用
  4. 记录应用 ID(假设为 iCMS_APP_TEST,值为 8)

第二步:创建目录结构

mkdir app/test
mkdir app/test/assets
mkdir app/test/assets/js
mkdir app/test/etc
mkdir app/test/etc/admincp
mkdir app/test/etc/menu
mkdir app/test/etc/route
mkdir app/test/views
mkdir app/test/views/app
mkdir app/test/views/app/config

第三步:创建核心文件

1. 创建 Test.php(应用主类)

参考Article.php

/**
 * Test 应用主类
 */
defined('iPHP') or exit('What are you doing?');

class Test
{
    const APP = 'test';
    const APPID = iCMS_APP_TEST;  // 应用ID常量

    /**
     * 状态映射
     */
    public static $stypeMap = array(
        'inbox'   => '0',  // 草稿
        'normal'  => '1',  // 正常
        'trash'   => '2',  // 回收站
        'examine' => '3',  // 待审核
    );

    /**
     * 检查字段值是否存在
     */
    public static function check($value, $id = 0, $field = 'title')
    {
        $where = array($field => $value);
        $id && $where['id'] = array('<>', $id);
        return TestModel::field('id')->where($where)->value();
    }

    /**
     * 获取单个字段值
     */
    public static function value($field = 'id', $id = 0)
    {
        if (empty($id)) {
            return '';
        }
        return TestModel::field($field)->where($id)->value();
    }

    /**
     * 获取单条记录
     */
    public static function get($id = 0, $field = '*', $where = array())
    {
        $where['id'] = $id;
        return TestModel::field($field)->where($where)->get();
    }

    /**
     * 获取数据(包含扩展表)
     */
    public static function data($id = 0, $dataId = 0, $userid = 0)
    {
        $where = array();
        $userid && $where['userid'] = $userid;
        $test = TestModel::where($where)->get($id);
        $data = array();

        if ($test) {
            $DataModel = TestDataModel::sharding($test['id']);
            try {
                $DataModel = $DataModel->where('test_id', $test['id']);
                $dataId && $DataModel = $DataModel->where('id', $dataId);
                $data = $DataModel->get();
            } catch (\sException $ex) {
                // 处理表不存在等异常
            }
        }

        return array($test, $data, []);
    }

    /**
     * 创建记录
     */
    public static function create($data)
    {
        return TestModel::create($data, true);
    }

    /**
     * 更新记录
     */
    public static function update($data, $where)
    {
        return TestModel::update($data, $where);
    }

    /**
     * 删除记录
     */
    public static function delete($id)
    {
        return TestModel::delete($id);
    }
}

2. 创建 TestModel.php(主表模型)

参考ArticleModel.php

/**
 * Test 主表模型
 */
defined('iPHP') or exit('What are you doing?');

class TestModel extends Model
{
    /**
     * 状态映射
     */
    public static $statusMap = [
        '0' => '草稿',
        '1' => '正常',
        '2' => '回收站',
        '3' => '待审核',
    ];

    /**
     * 字段类型转换
     */
    protected $casts = [
        'picdata'    => 'array',      // 图片数据
        'nodeAttrs'  => 'array',      // 分类动态属性
        'metadata'   => 'array',      // 元数据
    ];

    /**
     * 事件监听
     */
    protected $events = [
        'delete'  => ['TestEvent', 'delete'],   // 删除前
        'changed' => ['TestEvent', 'changed'],  // 创建或更新后
    ];
}

3. 创建 TestDataModel.php(扩展表模型)

参考ArticleDataModel.php

/**
 * Test 扩展数据表模型
 */
defined('iPHP') or exit('What are you doing?');

class TestDataModel extends Model
{
    protected $casts = [];

    /**
     * 数据库错误回调
     */
    protected $callback = [
        'SQLSTATE:42S02' => [__CLASS__, 'createTable'], // 表不存在时自动创建
    ];

    public static $appid = iCMS_APP_TEST;

    /**
     * 分表策略
     */
    public static function sharding($test_id)
    {
        $model = self::getInstance();
        $model->sharding = (int)$test_id % 10; // 按ID取模分10张表
        return $model;
    }

    /**
     * 自动创建表
     */
    public static function createTable()
    {
        try {
            $target = self::getTableName();
            $source = self::table(__CLASS__);
            DB::copy($source, $target);
            return true;
        } catch (sException $ex) {
            $state = $ex->getState();
            if ($state === '42000') { // 无创建表权限
                throw $ex;
            }
            return false;
        }
    }
}

4. 创建 TestEvent.php(事件处理)

参考ArticleEvent.php

/**
 * Test 事件处理类
 */
defined('iPHP') or exit('What are you doing?');

class TestEvent extends DBEvent
{
    public static $appid = iCMS_APP_TEST;

    /**
     * 创建或更新后的事件
     */
    public static function changed($response, $Builder, $event, $isMulti)
    {
        // 获取受影响的ID列表
        if ($event == 'updated') {
            $idArr = $Builder->field('id')->pluck();
        } else {
            $idArr = [$response['id']];
        }

        array_map(function ($id) use ($response, $Builder, $event, $isMulti) {
            if (!$id) {
                return;
            }

            // 处理分类变化
            if (isset($response['cid'])) {
                Node::change('cid', $response['cid'], $event, $id, self::$appid);
            }

            // 处理副分类变化
            if (isset($response['scid'])) {
                AppsMap::change('scid', self::$appid, $response['scid'], $event, $id, 'Node');
            }

            // 处理标签变化
            if (isset($response['tags'])) {
                Tag::$APPID = self::$appid;
                if (!isset($response['tags']['raw'])) {
                    Tag::change('tags', $response['tags'], $event, $id, self::$appid, $Builder);
                }
            }

            // 处理缩略图变化
            if (isset($response['pic'])) {
                FilesPic::change($response, self::$appid, $event, $id);
            }

            // 内容归档
            if ($event == 'updated') {
                Archive::update(self::$appid, $id, $response);
            } else {
                Archive::save(self::$appid, $id, $response);
            }
        }, $idArr);
    }

    /**
     * 删除前的事件
     */
    public static function delete($response, $Builder, $event, $isMulti)
    {
        $idArr = $Builder->field('id')->pluck();
        array_map(function ($id) {
            if (!$id) {
                return;
            }

            // 删除应用映射(分类、标签、属性)
            AppsMap::delete(self::$appid, $id, 'Node');
            AppsMap::delete(self::$appid, $id, 'Tag');
            AppsMap::delete(self::$appid, $id, 'Prop');

            // 删除关联文件
            Files::delete(self::$appid, $id);

            // 删除评论
            Comment::delete(self::$appid, $id);

            // 删除归档
            Archive::delete(self::$appid, $id);
        }, $idArr);
    }
}

5. 创建 TestAdmincp.php(后台管理)

参考ArticleAdmincp.php

/**
 * Test 后台管理
 */
defined('iPHP') or exit('What are you doing?');
defined('APP_URL') or define('APP_URL', '/admincp.php/test');

class TestAdmincp extends AdmincpCommon
{
    use AdmincpCommonTrait;

    /**
     * 排序字段映射
     */
    public static $orderBy = [
        'id'      => 'ID',
        'hits'    => '点击',
        'postime' => '时间',
    ];

    /**
     * 添加/编辑内容
     */
    public function do_add()
    {
        $rs = [];
        // 如果有ID则获取数据(编辑模式)
        if ($this->id) {
            $rs = Test::get($this->id);
        }

        // 获取分类
        $rs['cid'] = $rs['cid'] ?: (int) Request::get('cid');
        $Node = $rs['cid'] ? Node::get($rs['cid']) : [];

        // 新增时的默认值
        if (empty($this->id)) {
            $rs['status']  = '1';
            $rs['postype'] = '1';
            $rs['editor']  = Admin::$nickname;
            $rs['userid']  = Admin::$user_id;
        }

        include self::view();
    }

    /**
     * 保存数据
     */
    public function do_save()
    {
        // 获取提交的数据
        $data = $this->data();

        // 数据验证
        empty($data['title']) && self::json(['code' => 0, 'msg' => '标题不能为空']);
        empty($data['cid']) && self::json(['code' => 0, 'msg' => '请选择栏目']);

        // 保存主表数据
        if ($this->id) {
            Test::update($data, ['id' => $this->id]);
        } else {
            $data['postime'] = time();
            $this->id = Test::create($data);
        }

        // 保存扩展表数据(如果有)
        if (isset($data['body'])) {
            $dataModel = TestDataModel::sharding($this->id);
            $extData = [
                'test_id' => $this->id,
                'body'    => $data['body'],
            ];
            $dataModel->replace($extData);
        }

        return [true, '保存成功'];
    }

    /**
     * 删除
     */
    public function do_delete()
    {
        $id = (int)Request::get('id');
        $id or self::json(['code' => 0, 'msg' => 'ID不能为空']);

        Test::delete($id);

        // 删除扩展表数据
        $dataModel = TestDataModel::sharding($id);
        $dataModel->where('test_id', $id)->delete();

        return [true,'删除成功'];
    }
}

6. 创建 TestCategoryAdmincp.php(分类管理)

参考ArticleCategoryAdmincp.php

/**
 * Test 分类管理
 */
defined('iPHP') or exit('What are you doing?');

class TestCategoryAdmincp extends NodeAdmincp
{
    public function __construct()
    {
        parent::__construct(iCMS_APP_TEST); // 传入应用ID
        $this->app     = 'test';
        $this->title   = '测试';
        $this->primary = 'cid';

        $this->CONTENT_MODEL = new TestModel();
        $this->NODE_NAME = "栏目";
    }
}

7. 创建 TestFunc.php(模板标签)

参考ArticleFunc.php

/**
 * Test 模板标签类
 */
defined('iPHP') or exit('What are you doing?');

class TestFunc extends AppsFuncCommon implements AppsFuncInterface
{
    /**
     * 获取列表
     */
    public static function lists($vars)
    {
        $resource  = array();
        $whereNot  = array();
        $model     = TestModel::field('id');
        $status    = isset($vars['status']) ? $vars['status'] : 1;
        $where     = [['status', $status]];

        // 条件筛选
        isset($vars['userid']) && $where[] = ['userid', $vars['userid']];
        isset($vars['pic'])    && $where[] = ['haspic', '1'];

        // 使用基类方法
        self::inited($vars, $model, $where, $whereNot);
        self::nodes('cid');        // 处理分类
        self::tags();              // 处理标签
        self::keywords();          // 处理关键词

        // 排序映射
        self::orderby([
            'hot'   => 'hits',
            'week'  => 'hits_week',
            'month' => 'hits_month'
        ]);

        self::where();
        return self::getResource(__METHOD__);
    }

    /**
     * 获取资源详情(由 getResource 自动调用)
     */
    public static function resource($idsArray = null)
    {
        $vars = self::$vars;
        $resource = TestModel::field('*')
            ->where($idsArray)
            ->orderBy('id', $idsArray)
            ->select();
        $resource = self::many($vars, $resource);
        return $resource;
    }

    /**
     * 获取单条数据
     */
    public static function data($vars)
    {
        $id = $vars['id'] ?: $vars['test_id'];
        $id or self::msg(['func_param', ['iCMS:test:data', 'id']]);

        $where = ['id' => $id];
        $model = TestModel::where($where);

        $hash = md5($model->getSql());
        self::paging($hash, __METHOD__);

        $resource = self::getCache(self::$cacheName);
        if (empty($resource)) {
            $resource = $model->field('*')->get();
            $cacheTime = isset($vars['time']) ? (int) $vars['time'] : -1;
            $vars['cache'] && Cache::set(self::$cacheName, $resource, $cacheTime);
        }

        return $resource;
    }
}

第四步:创建视图模板

1. 创建 views/index.html(列表页)

defined('iPHP') or exit('What are you doing?');
self::head();
?>




            搜索






                            栏目

                                   name="cid" value="">




                            关键词

                                   value="" />




                             搜索




</span></span>            </form><span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>        </div><span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>    </div><span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>    <span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>    <!-- 数据表格 --><span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>    <div class="block"><span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>        <div class="block-content p-0"><span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>            <div id="toolbar" class="toolbar row"><span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                <div class="input-group input-group-sm"><span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                    <a href="{{test/add}}" class="btn btn-alt-primary"><span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                        <i class="fa fa-add"></i> 添加<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                    </a><span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                    <a href="{{test/edit}}" id="edit" class="btn btn-alt-primary table-btn-edit disabled" disabled><span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                        <i class="fa fa-edit"></i> 编辑<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                    </a><span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                    <a href="{{test/delete}}" id="delete" class="btn btn-alt-primary table-btn-delete disabled" disabled><span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                        <i class="fa fa-trash"></i> 删除<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                    </a><span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                    <?php AdmincpBatch::group(); ?><span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                </div><span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>            </div><span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>            <div class="table-responsive"><span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                <table id="table" width="100%"></table><span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>            </div><span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>        </div><span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>    </div><span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span></div><span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span><?php self::foot(); ?></span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="code-block-close-marker" class="vditor-sv__marker">```</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="newline"><br /><span style="display: none">
</span></span></div><div data-block="0"><span class="vditor-sv__marker--heading h4" data-type="heading-marker">#### </span><span data-type="text" class="h4">2. 创建 views/add.html(添加/编辑页)</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="newline"><br /><span style="display: none">
</span></span></div><div data-block="0"><span data-type="code-block-open-marker" class="vditor-sv__marker">```</span><span class="vditor-sv__marker--info" data-type="code-block-info">php</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="text"><?php<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>defined('iPHP') or exit('What are you doing?');<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>self::head();<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>?><span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span><div class="content" id="{{APP_MAINID}}"><span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>    <div class="block"><span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>        <div class="block-content"><span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>            <form action="{{APP_DOURL}}" method="POST" i="cms:ui:form"><span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                <input name="rs[id]" type="hidden" value="<?php echo $this->id; ?>" /><span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                <input name="REFERER" type="hidden" value="<?php echo iPHP_REFERER; ?>" /><span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                <span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                <div class="row"><span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                    <div class="col-sm-9 border-end"><span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                        <!-- 标题 --><span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                        <div class="form-floating mb-3"><span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                            <input type="text" name="rs[title]" class="form-control form-control-lg" <span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                                   id="title" value="<?php echo $rs['title']; ?>" data-rule="required" /><span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                            <label for="title">标题</label><span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                        </div><span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                        <span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                        <!-- 内容 --><span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                        <div class="form-group mb-3"><span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                            <label>内容</label><span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                            <textarea name="rs[body]" class="form-control" <span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                                      i="cms:ui:editor" rows="20"><?php echo $rs['body']; ?></textarea><span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                        </div><span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                        <span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                        <!-- 提交按钮 --><span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                        <div class="form-group"><span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                            <button class="btn btn-primary" type="submit"><span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                                <i class="fa fa-check"></i> 提交<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                            </button><span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                        </div><span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                    </div><span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                    <span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                    <div class="col-sm-3"><span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                        <!-- 分类 --><span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                        <div class="form-floating mb-3"><span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                            <input type="text" name="rs[cid]" class="form-control" <span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                                   id="cid" i="cms:ui:node" <span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                                   value="<?php echo $rs['cid']; ?>" data-rule="required" /><span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                            <label for="cid">栏目</label><span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                        </div><span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                        <span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                        <!-- 缩略图 --><span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                        <div class="form-floating mb-3"><span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                            <input type="text" name="rs[pic]" class="form-control" <span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                                   id="pic" i="cms:ui:pic" <span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                                   value="<?php echo $rs['pic']; ?>" /><span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                            <label for="pic">缩略图</label><span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                        </div><span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                        <span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                        <!-- 标签 --><span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                        <div class="form-floating mb-3"><span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                            <input type="text" name="rs[tags]" class="form-control" <span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                                   id="tags" i="cms:ui:tags" <span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                                   value="<?php echo $rs['tags']; ?>" /><span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                            <label for="tags">标签</label><span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                        </div><span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                        <span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                        <!-- 状态 --><span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                        <div class="form-floating mb-3"><span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                            <select name="rs[status]" class="form-control" id="status" <span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                                    i="cms:ui:bs-selectpicker" data-source="statusMap"><span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                                <option value="<?php echo $rs['status']; ?>" selected></option><span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                            </select><span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                            <label for="status">状态</label><span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                        </div><span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                    </div><span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                </div><span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>            </form><span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>        </div><span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>    </div><span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span></div><span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span><?php self::foot(); ?></span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="code-block-close-marker" class="vditor-sv__marker">```</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="newline"><br /><span style="display: none">
</span></span></div><div data-block="0"><span class="vditor-sv__marker--heading h4" data-type="heading-marker">#### </span><span data-type="text" class="h4">3. 创建 views/edit.html(编辑页)</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="newline"><br /><span style="display: none">
</span></span></div><div data-block="0"><span data-type="code-block-open-marker" class="vditor-sv__marker">```</span><span class="vditor-sv__marker--info" data-type="code-block-info">php</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="text"><?php include self::view('add'); ?></span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="code-block-close-marker" class="vditor-sv__marker">```</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="newline"><br /><span style="display: none">
</span></span></div><div data-block="0"><span class="vditor-sv__marker--heading h3" data-type="heading-marker">### </span><span data-type="text" class="h3">第五步:创建配置文件</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="newline"><br /><span style="display: none">
</span></span></div><div data-block="0"><span class="vditor-sv__marker--heading h4" data-type="heading-marker">#### </span><span data-type="text" class="h4">1. 创建 etc/menu/admincp.json(后台菜单)</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="newline"><br /><span style="display: none">
</span></span></div><div data-block="0"><span data-type="code-block-open-marker" class="vditor-sv__marker">```</span><span class="vditor-sv__marker--info" data-type="code-block-info">json</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="text">[{<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>    "id": "test",<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>    "sort": "10",<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>    "caption": "测试应用",<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>    "icon": "si si-grid",<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>    "children": [<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>        {<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>            "caption": "添加内容",<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>            "href": "/test/add",<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>            "icon": "edit",<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>            "sort": 101,<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>            "access": "/test/add",<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>            "id": "menu_test_add"<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>        },<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>        {<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>            "caption": "栏目管理",<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>            "href": "/testCategory/tree",<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>            "icon": "sitemap",<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>            "sort": 102,<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>            "access": "/testCategory/tree",<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>            "id": "menu_test_category"<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>        },<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>        {<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>            "caption": "内容管理",<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>            "href": "/test/index",<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>            "icon": "list",<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>            "sort": 103,<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>            "access": "/test/index",<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>            "id": "menu_test_index"<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>        }<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>    ],<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>    "access": "test",<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>    "expanded": false<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>}]</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="code-block-close-marker" class="vditor-sv__marker">```</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="newline"><br /><span style="display: none">
</span></span></div><div data-block="0"><span class="vditor-sv__marker--heading h4" data-type="heading-marker">#### </span><span data-type="text" class="h4">2. 创建 etc/admincp/Test.index.batch.json(批量操作)</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="newline"><br /><span style="display: none">
</span></span></div><div data-block="0"><span data-type="code-block-open-marker" class="vditor-sv__marker">```</span><span class="vditor-sv__marker--info" data-type="code-block-info">json</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="text">{<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>    "status=1": {<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>        "name": "发布",<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>        "icon": "check",<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>        "sort": 1,<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>        "call": "default"<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>    },<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>    "status=0": {<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>        "name": "转为草稿",<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>        "icon": "inbox",<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>        "sort": 2,<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>        "call": "default"<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>    },<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>    "status=2": {<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>        "name": "移入回收站",<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>        "icon": "trash-alt",<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>        "sort": 3,<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>        "call": "default"<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>    },<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>    "move": {<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>        "name": "移动栏目",<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>        "icon": "fighter-jet",<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>        "sort": 10,<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>        "call": "move"<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>    },<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>    "dels": {<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>        "name": "删除",<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>        "icon": "trash-alt",<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>        "sort": 20,<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>        "call": "dels"<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>    }<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>}</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="code-block-close-marker" class="vditor-sv__marker">```</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="newline"><br /><span style="display: none">
</span></span></div><div data-block="0"><span class="vditor-sv__marker--heading h4" data-type="heading-marker">#### </span><span data-type="text" class="h4">3. 创建 etc/route/node.json(分类路由配置)</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="newline"><br /><span style="display: none">
</span></span></div><div data-block="0"><span data-type="code-block-open-marker" class="vditor-sv__marker">```</span><span class="vditor-sv__marker--info" data-type="code-block-info">json</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="text">{<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>    "test": {<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>        "label": "test",<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>        "template": "{iTPL}/test.htm",<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>        "rule": "/{CDIR}/{YYYY}/{MM}{DD}/{ID}{EXT}",<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>        "tips": "{ID},{0xID},{LINK},{HASH@ID},{HASH@0xID}"<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>    }<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>}</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="code-block-close-marker" class="vditor-sv__marker">```</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="newline"><br /><span style="display: none">
</span></span></div><div data-block="0"><span class="vditor-sv__marker--heading h3" data-type="heading-marker">### </span><span data-type="text" class="h3">第六步:创建 JavaScript 文件</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="newline"><br /><span style="display: none">
</span></span></div><div data-block="0"><span class="vditor-sv__marker--heading h4" data-type="heading-marker">#### </span><span data-type="text" class="h4">assets/js/index.js(列表页脚本)</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="newline"><br /><span style="display: none">
</span></span></div><div data-block="0"><span data-type="code-block-open-marker" class="vditor-sv__marker">```</span><span class="vditor-sv__marker--info" data-type="code-block-info">javascript</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="text">define(["admincp", "table"], function (admincp, Table) {<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>    const factory ={<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>        user: function () {<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>            return factory.index()<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>        },<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>        index: function () {<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>            var $table = $table || $('#table');<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>            Table.init($table, {<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                app: $APP.name,<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                urls: {<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                    "index": window.location.href,<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                //     "add": "article/add",<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                //     "edit": "article/edit",<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                //     "detail": "article/detail",<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                //     "publish": "article/update",<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                //     "trash": "article/trash",<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                //     "import": "article/import",<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                //     "delete": "article/delete"<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                },<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                // search:true,<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                columns: [{<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                    field: 'id',<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                    title: 'fields:id',<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                    width: "90",<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                    sortable: true<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                },<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                {<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                    field: 'cid',<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                    title: 'fields:nodeName',<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                    setting: { map: 'node/mapper' },<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                    search: "=",<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                    formatter: Table.format.search,<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                },<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                {<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                    field: 'title',<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                    title: 'fields:title',<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                    class: "text-nowrap w-25",<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                    formatter: function (value, row, index) {<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                        var html = [Table.format.link.apply(this, arguments)];<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                        if (row['postype'] == 0) {<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                            html.push('<i class="fa fa-fw fa-user text-primary-light"></i>');<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                        }<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                        if (row['pic']) {<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                            html.push('<i class="fa fa-fw fa-image text-success"></i>');<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                        }<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                        if (row['title_extra']) {<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                            html.push(`<br /><span class="py-1 px-3 rounded-pill bg-warning-light text-warning">${row['title_extra']}</span>`);<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                        }<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                        // console.log(html);<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                        return html.join(' ');<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                    },<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                },<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                {<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                    field: 'pid',<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                    title: 'fields:propName',<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                    setting: { map: "prop/mapper" },<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                    formatter: Table.format.flag,<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                },<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                {<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                    field: 'pubdate',<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                    title: 'fields:pubdate',<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                    // formatter: Table.format.datetime,<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                    formatter: function (value, row, index) {<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                        var format = 'YYYY-MM-DD HH:mm';<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                        return (row['pubdate'] > 0 ? iCMS.utils.moment.unix(row['pubdate']).format(format) : '-') +<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                            '<br />' +<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                            (row['postime'] > 0 ? iCMS.utils.moment.unix(row['postime']).format(format) : '-')<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                    },<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                },<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                {<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                    field: 'hits',<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                    title: 'fields:hits_text',<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                    formatter: function (value, row, index) {<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                        var s = {<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                            href: "javascript:;",<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                            text: value,<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                            format: '<a class="btn btn-sm btn-light" href="{{href}}" title="{{title}}" {{attr}}>{{text}}</a>',<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                        }<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                        s.attr = [<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                            'i="cms:ui:popover"',<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                            'data-html="true"',<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                            'data-placement="top"',<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                            'data-content="' + [<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                                '总点击数: ' + row.hits,<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                                '当天点击数: ' + row.hits_today,<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                                '昨天点击数: ' + row.hits_yday,<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                                '周点击数: ' + row.hits_week,<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                                '月点击数: ' + row.hits_month,<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                                '---------------',<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                                '赞: ' + row.good,<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                                '踩: ' + row.bad,<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                                '评论数: ' + row.comment,<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                                '收藏数: ' + row.favorite,<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                            ].join('<br/>') + '"'<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                        ].join(" ");<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                        return iCMS.utils.display(s.format, s);<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                    }<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                },<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                {<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                    field: 'status',<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                    title: 'fields:status',<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                    formatter: Table.format.status,<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                }, {<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                    field: 'operate',<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                    buttons: [{<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                        title: Lang('subApp'),<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                        id: 'dropdown-default-primary',<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                        class: 'btn btn-alt-primary dropdown-toggle',<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                        icon: 'fa fa-fw fa-bars',<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                        attr: {<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                            "data-bs-toggle": "dropdown",<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                            "aria-haspopup": "true",<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                            "aria-expanded": "false",<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                        },<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                        visible: function(){<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                            if(isArray($APP.context.subApp) && $APP.context.subApp.length > 0){<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                                return true;<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                            }<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                            return false;<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                        },<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                        dropdown: $APP.context.subApp<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                        // dropdown: [<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                        //     {<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                        //         'href': './dfg',<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                        //         'text': 'fgh',<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                        //         'class': 'btn-modal'<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                        //     },<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                        //     {<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                        //         'href': './dfg',<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                        //         'text': 'fgh',<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                        //         'class': 'btn-modal'<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                        //     },<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                        // ]<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                    }],<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                    formatter: Table.format.operate,<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                }<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>                ]<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>            });<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>            // $("#test").on("click",function (e) {<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>            //     e.preventDefault();<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>            //     var row = Table.method.refresh({<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>            //         'sortName': 'id',<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>            //         'sortOrder': 'asc'<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>            //     });<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>            //     // alert(JSON.stringify(row));<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>            //     // var that = this;<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>            //     // var href = $(this).attr("href");<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>            //     // console.log(href);<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>            //     // $.each(Table.ids, function (idx, value) {<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>            //     //     var url = iCMS.api.makeUrl(href, { id: value });<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>            //     //     console.log(url);<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>            //     //     iCMS.ui.modal(that, { url });<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>            //     // });<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>            //     // alert(this.href)<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>            // });<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>        }<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>    }<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>    return factory;<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>});</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="code-block-close-marker" class="vditor-sv__marker">```</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="newline"><br /><span style="display: none">
</span></span></div><div data-block="0"><span class="vditor-sv__marker--heading h4" data-type="heading-marker">#### </span><span data-type="text" class="h4">assets/js/add.js(添加页脚本)</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="newline"><br /><span style="display: none">
</span></span></div><div data-block="0"><span data-type="code-block-open-marker" class="vditor-sv__marker">```</span><span class="vditor-sv__marker--info" data-type="code-block-info">javascript</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="text">define(["iCMS"], function (iCMS) {<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>    const factory = {<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>        add_success: function (json) {<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>            console.log('保存成功', json);<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>        }<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>    };<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>    return factory;<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>});</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="code-block-close-marker" class="vditor-sv__marker">```</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="newline"><br /><span style="display: none">
</span></span></div><div data-block="0"><span class="vditor-sv__marker--heading h4" data-type="heading-marker">#### </span><span data-type="text" class="h4">assets/js/edit.js(编辑页脚本)</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="newline"><br /><span style="display: none">
</span></span></div><div data-block="0"><span data-type="code-block-open-marker" class="vditor-sv__marker">```</span><span class="vditor-sv__marker--info" data-type="code-block-info">javascript</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="text">define(["./add"], function (add) {<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>    return add;<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>});</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="code-block-close-marker" class="vditor-sv__marker">```</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="newline"><br /><span style="display: none">
</span></span></div><div data-block="0"><span class="vditor-sv__marker--heading h3" data-type="heading-marker">### </span><span data-type="text" class="h3">第七步:清除缓存并测试</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="newline"><br /><span style="display: none">
</span></span></div><div data-block="0"><span data-type="li-marker" class="vditor-sv__marker">1. </span><span class="vditor-sv__marker--bi strong">**</span><span data-type="text" class="strong">清除缓存</span><span class="vditor-sv__marker--bi strong">**</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="padding">   </span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="padding">   </span><span data-type="li-marker" class="vditor-sv__marker">- </span><span data-type="text">进入后台:</span><span class="vditor-sv__marker--bi strong">**</span><span data-type="text" class="strong">系统管理</span><span class="vditor-sv__marker--bi strong">**</span><span data-type="text"> → </span><span class="vditor-sv__marker--bi strong">**</span><span data-type="text" class="strong">缓存管理</span><span class="vditor-sv__marker--bi strong">**</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="padding">   </span><span data-type="li-marker" class="vditor-sv__marker">- </span><span data-type="text">点击:</span><span class="vditor-sv__marker--bi strong">**</span><span data-type="text" class="strong">清除应用缓存</span><span class="vditor-sv__marker--bi strong">**</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="padding">   </span><span data-type="li-marker" class="vditor-sv__marker">- </span><span data-type="text">点击:</span><span class="vditor-sv__marker--bi strong">**</span><span data-type="text" class="strong">更新菜单缓存</span><span class="vditor-sv__marker--bi strong">**</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="li-marker" class="vditor-sv__marker">2. </span><span class="vditor-sv__marker--bi strong">**</span><span data-type="text" class="strong">访问测试</span><span class="vditor-sv__marker--bi strong">**</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="padding">   </span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="padding">   </span><span data-type="li-marker" class="vditor-sv__marker">- </span><span data-type="text">后台列表:</span><span class="vditor-sv__marker">`</span><span>admincp.php/test/index</span><span class="vditor-sv__marker">`</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="padding">   </span><span data-type="li-marker" class="vditor-sv__marker">- </span><span data-type="text">后台添加:</span><span class="vditor-sv__marker">`</span><span>admincp.php/test/add</span><span class="vditor-sv__marker">`</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="padding">   </span><span data-type="li-marker" class="vditor-sv__marker">- </span><span data-type="text">分类管理:</span><span class="vditor-sv__marker">`</span><span>admincp.php/testCategory/tree</span><span class="vditor-sv__marker">`</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="padding">   </span><span data-type="li-marker" class="vditor-sv__marker">- </span><span data-type="text">前台访问:</span><span class="vditor-sv__marker">`</span><span>api.php/test/index</span><span class="vditor-sv__marker">`</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="newline"><br /><span style="display: none">
</span></span></div><div data-block="0"><span class="vditor-sv__marker--heading h3" data-type="heading-marker">### </span><span data-type="text" class="h3">第八步:常见问题处理</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="newline"><br /><span style="display: none">
</span></span></div><div data-block="0"><span class="vditor-sv__marker--bi strong">**</span><span data-type="text" class="strong">问题1:菜单不显示</span><span class="vditor-sv__marker--bi strong">**</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="newline"><br /><span style="display: none">
</span></span></div><div data-block="0"><span data-type="li-marker" class="vditor-sv__marker">- </span><span data-type="text">检查 </span><span class="vditor-sv__marker">`</span><span>etc/menu/admincp.json</span><span class="vditor-sv__marker">`</span><span data-type="text"> 格式是否正确</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="li-marker" class="vditor-sv__marker">- </span><span data-type="text">清除菜单缓存</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="li-marker" class="vditor-sv__marker">- </span><span data-type="text">检查 </span><span class="vditor-sv__marker">`</span><span>caption</span><span class="vditor-sv__marker">`</span><span data-type="text">(不是 </span><span class="vditor-sv__marker">`</span><span>name</span><span class="vditor-sv__marker">`</span><span data-type="text">),</span><span class="vditor-sv__marker">`</span><span>href</span><span class="vditor-sv__marker">`</span><span data-type="text">(不是 </span><span class="vditor-sv__marker">`</span><span>url</span><span class="vditor-sv__marker">`</span><span data-type="text">)</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="newline"><br /><span style="display: none">
</span></span></div><div data-block="0"><span class="vditor-sv__marker--bi strong">**</span><span data-type="text" class="strong">问题2:列表页空白</span><span class="vditor-sv__marker--bi strong">**</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="newline"><br /><span style="display: none">
</span></span></div><div data-block="0"><span data-type="li-marker" class="vditor-sv__marker">- </span><span data-type="text">检查 </span><span class="vditor-sv__marker">`</span><span>assets/js/index.js</span><span class="vditor-sv__marker">`</span><span data-type="text"> 是否存在</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="li-marker" class="vditor-sv__marker">- </span><span data-type="text">浏览器控制台查看 JS 错误</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="li-marker" class="vditor-sv__marker">- </span><span data-type="text">确认 </span><span class="vditor-sv__marker">`</span><span><table id="table"></table></span><span class="vditor-sv__marker">`</span><span data-type="text"> 存在</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="newline"><br /><span style="display: none">
</span></span></div><div data-block="0"><span class="vditor-sv__marker--bi strong">**</span><span data-type="text" class="strong">问题3:保存失败</span><span class="vditor-sv__marker--bi strong">**</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="newline"><br /><span style="display: none">
</span></span></div><div data-block="0"><span data-type="li-marker" class="vditor-sv__marker">- </span><span data-type="text">检查 </span><span class="vditor-sv__marker">`</span><span>do_save()</span><span class="vditor-sv__marker">`</span><span data-type="text"> 方法的返回格式:</span><span class="vditor-sv__marker">`</span><span>return [true, '消息'];</span><span class="vditor-sv__marker">`</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="li-marker" class="vditor-sv__marker">- </span><span data-type="text">检查表单 </span><span class="vditor-sv__marker">`</span><span>name="rs[字段名]"</span><span class="vditor-sv__marker">`</span><span data-type="text"> 格式</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="li-marker" class="vditor-sv__marker">- </span><span data-type="text">检查 Model 的 </span><span class="vditor-sv__marker">`</span><span>$events</span><span class="vditor-sv__marker">`</span><span data-type="text"> 事件处理</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="newline"><br /><span style="display: none">
</span></span></div><div data-block="0"><span class="vditor-sv__marker--bi strong">**</span><span data-type="text" class="strong">问题4:事件不触发</span><span class="vditor-sv__marker--bi strong">**</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="newline"><br /><span style="display: none">
</span></span></div><div data-block="0"><span data-type="li-marker" class="vditor-sv__marker">- </span><span data-type="text">检查 Model 的 </span><span class="vditor-sv__marker">`</span><span>$events</span><span class="vditor-sv__marker">`</span><span data-type="text"> 数组是否定义</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="li-marker" class="vditor-sv__marker">- </span><span data-type="text">检查 Event 类的方法签名是否正确</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="li-marker" class="vditor-sv__marker">- </span><span data-type="text">检查 Event 类是否继承 </span><span class="vditor-sv__marker">`</span><span>DBEvent</span><span class="vditor-sv__marker">`</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="newline"><br /><span style="display: none">
</span></span></div><div data-block="0"><span class="vditor-sv__marker">---</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="newline"><br /><span style="display: none">
</span></span></div><div data-block="0"><span class="vditor-sv__marker--heading h2" data-type="heading-marker">## </span><span data-type="text" class="h2">最佳实践</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="newline"><br /><span style="display: none">
</span></span></div><div data-block="0"><span class="vditor-sv__marker--heading h3" data-type="heading-marker">### </span><span data-type="text" class="h3">1. 代码规范</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="newline"><br /><span style="display: none">
</span></span></div><div data-block="0"><span data-type="li-marker" class="vditor-sv__marker">- </span><span data-type="text">遵循 PSR-4 自动加载规范</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="li-marker" class="vditor-sv__marker">- </span><span data-type="text">类名使用 PascalCase(首字母大写驼峰)</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="li-marker" class="vditor-sv__marker">- </span><span data-type="text">方法名使用 camelCase(小驼峰)</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="li-marker" class="vditor-sv__marker">- </span><span data-type="text">常量使用 UPPER_CASE(全大写下划线)</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="newline"><br /><span style="display: none">
</span></span></div><div data-block="0"><span class="vditor-sv__marker--heading h3" data-type="heading-marker">### </span><span data-type="text" class="h3">2. 安全建议</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="newline"><br /><span style="display: none">
</span></span></div><div data-block="0"><span data-type="li-marker" class="vditor-sv__marker">- </span><span data-type="text">所有用户输入必须验证和过滤</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="li-marker" class="vditor-sv__marker">- </span><span data-type="text">使用参数化查询防止 SQL 注入</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="li-marker" class="vditor-sv__marker">- </span><span data-type="text">对敏感操作进行权限验证</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="li-marker" class="vditor-sv__marker">- </span><span data-type="text">XSS 防护:输出时进行 HTML 转义</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="newline"><br /><span style="display: none">
</span></span></div><div data-block="0"><span class="vditor-sv__marker--heading h3" data-type="heading-marker">### </span><span data-type="text" class="h3">3. 性能优化</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="newline"><br /><span style="display: none">
</span></span></div><div data-block="0"><span data-type="li-marker" class="vditor-sv__marker">- </span><span data-type="text">合理使用缓存</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="li-marker" class="vditor-sv__marker">- </span><span data-type="text">避免 N+1 查询问题</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="li-marker" class="vditor-sv__marker">- </span><span data-type="text">分表存储大字段内容</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="li-marker" class="vditor-sv__marker">- </span><span data-type="text">使用索引优化查询</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="newline"><br /><span style="display: none">
</span></span></div><div data-block="0"><span class="vditor-sv__marker--heading h3" data-type="heading-marker">### </span><span data-type="text" class="h3">4. 可维护性</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="newline"><br /><span style="display: none">
</span></span></div><div data-block="0"><span data-type="li-marker" class="vditor-sv__marker">- </span><span data-type="text">代码注释清晰</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="li-marker" class="vditor-sv__marker">- </span><span data-type="text">功能模块化</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="li-marker" class="vditor-sv__marker">- </span><span data-type="text">遵循单一职责原则</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="li-marker" class="vditor-sv__marker">- </span><span data-type="text">使用事件解耦业务逻辑</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="newline"><br /><span style="display: none">
</span></span></div><div data-block="0"><span class="vditor-sv__marker">---</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="newline"><br /><span style="display: none">
</span></span></div><div data-block="0"><span class="vditor-sv__marker--heading h2" data-type="heading-marker">## </span><span data-type="text" class="h2">常见问题</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="newline"><br /><span style="display: none">
</span></span></div><div data-block="0"><span class="vditor-sv__marker--heading h3" data-type="heading-marker">### </span><span data-type="text" class="h3">Q1: 如何在模板中调用应用数据?</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="newline"><br /><span style="display: none">
</span></span></div><div data-block="0"><span class="vditor-sv__marker--bi strong">**</span><span data-type="text" class="strong">A</span><span class="vditor-sv__marker--bi strong">**</span><span data-type="text">: 使用模板标签,在 </span><span class="vditor-sv__marker">`</span><span>TestFunc.php</span><span class="vditor-sv__marker">`</span><span data-type="text"> 中定义方法:</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="newline"><br /><span style="display: none">
</span></span></div><div data-block="0"><span data-type="code-block-open-marker" class="vditor-sv__marker">```</span><span class="vditor-sv__marker--info" data-type="code-block-info">php</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="text">public static function list($vars) {<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>    return TestModel::all();<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>}</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="code-block-close-marker" class="vditor-sv__marker">```</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="newline"><br /><span style="display: none">
</span></span></div><div data-block="0"><span data-type="text">模板中调用:</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="newline"><br /><span style="display: none">
</span></span></div><div data-block="0"><span data-type="code-block-open-marker" class="vditor-sv__marker">```</span><span class="vditor-sv__marker--info" data-type="code-block-info">html</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="text"><!--{iCMS:test:list loop="true"}--><span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>    {{$test_list.title}}<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span><!--{/iCMS}--></span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="code-block-close-marker" class="vditor-sv__marker">```</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="newline"><br /><span style="display: none">
</span></span></div><div data-block="0"><span class="vditor-sv__marker--heading h3" data-type="heading-marker">### </span><span data-type="text" class="h3">Q2: 如何自定义 URL 路由?</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="newline"><br /><span style="display: none">
</span></span></div><div data-block="0"><span class="vditor-sv__marker--bi strong">**</span><span data-type="text" class="strong">A</span><span class="vditor-sv__marker--bi strong">**</span><span data-type="text">: 在 </span><span class="vditor-sv__marker">`</span><span>etc/route/route.json</span><span class="vditor-sv__marker">`</span><span data-type="text"> 中配置:</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="newline"><br /><span style="display: none">
</span></span></div><div data-block="0"><span data-type="code-block-open-marker" class="vditor-sv__marker">```</span><span class="vditor-sv__marker--info" data-type="code-block-info">json</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="text">{<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>    "/test/{id}":"api.php/test/detail?id={id}"<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>}</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="code-block-close-marker" class="vditor-sv__marker">```</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="newline"><br /><span style="display: none">
</span></span></div><div data-block="0"><span class="vditor-sv__marker--heading h3" data-type="heading-marker">### </span><span data-type="text" class="h3">Q3: 如何添加应用配置项?</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="newline"><br /><span style="display: none">
</span></span></div><div data-block="0"><span class="vditor-sv__marker--bi strong">**</span><span data-type="text" class="strong">A</span><span class="vditor-sv__marker--bi strong">**</span><span data-type="text">:</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="newline"><br /><span style="display: none">
</span></span></div><div data-block="0"><span data-type="li-marker" class="vditor-sv__marker">1. </span><span data-type="text">在 </span><span class="vditor-sv__marker">`</span><span>views/app/config/Test.html</span><span class="vditor-sv__marker">`</span><span data-type="text"> 创建配置表单</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="li-marker" class="vditor-sv__marker">2. </span><span data-type="text">使用 </span><span class="vditor-sv__marker">`</span><span>Test::config('key')</span><span class="vditor-sv__marker">`</span><span data-type="text"> 获取配置值</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="newline"><br /><span style="display: none">
</span></span></div><div data-block="0"><span class="vditor-sv__marker--heading h3" data-type="heading-marker">### </span><span data-type="text" class="h3">Q4: 如何实现数据关联?</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="newline"><br /><span style="display: none">
</span></span></div><div data-block="0"><span class="vditor-sv__marker--bi strong">**</span><span data-type="text" class="strong">A</span><span class="vditor-sv__marker--bi strong">**</span><span data-type="text">: 在 Model 中定义关联关系:</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="newline"><br /><span style="display: none">
</span></span></div><div data-block="0"><span data-type="code-block-open-marker" class="vditor-sv__marker">```</span><span class="vditor-sv__marker--info" data-type="code-block-info">php</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="text">class TestModel extends Model<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>{<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>    public function category()<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>    {<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>        return $this->belongsTo(CategoryModel::class, 'cid');<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>    }<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>    <span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>    public function user()<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>    {<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>        return $this->belongsTo(UserModel::class, 'uid');<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>    }<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>}</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="code-block-close-marker" class="vditor-sv__marker">```</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="newline"><br /><span style="display: none">
</span></span></div><div data-block="0"><span class="vditor-sv__marker--heading h3" data-type="heading-marker">### </span><span data-type="text" class="h3">Q5: 如何处理文件上传?</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="newline"><br /><span style="display: none">
</span></span></div><div data-block="0"><span class="vditor-sv__marker--bi strong">**</span><span data-type="text" class="strong">A</span><span class="vditor-sv__marker--bi strong">**</span><span data-type="text">: 使用系统的文件上传组件:</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="newline"><br /><span style="display: none">
</span></span></div><div data-block="0"><span data-type="code-block-open-marker" class="vditor-sv__marker">```</span><span class="vditor-sv__marker--info" data-type="code-block-info">php</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="text">public function do_upload()<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>{<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>    $file = Files::upload('image', [<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>        'dir' => 'test',<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>        'ext' => 'jpg,png,gif',<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>        'size' => 2 * 1024 * 1024 // 2MB<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>    ]);<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>    return ['url' => $file['url']];<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>}</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="code-block-close-marker" class="vditor-sv__marker">```</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="newline"><br /><span style="display: none">
</span></span></div><div data-block="0"><span class="vditor-sv__marker">---</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="newline"><br /><span style="display: none">
</span></span></div><div data-block="0"><span class="vditor-sv__marker--heading h2" data-type="heading-marker">## </span><span data-type="text" class="h2">附录</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="newline"><br /><span style="display: none">
</span></span></div><div data-block="0"><span class="vditor-sv__marker--heading h3" data-type="heading-marker">### </span><span data-type="text" class="h3">常用方法参考</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="newline"><br /><span style="display: none">
</span></span></div><div data-block="0"><span class="vditor-sv__marker--heading h4" data-type="heading-marker">#### </span><span data-type="text" class="h4">AdmincpBase 类方法</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="newline"><br /><span style="display: none">
</span></span></div><div data-block="0"><span data-type="li-marker" class="vditor-sv__marker">- </span><span class="vditor-sv__marker">`</span><span>view($template, $data)</span><span class="vditor-sv__marker">`</span><span data-type="text"> - 渲染视图</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="li-marker" class="vditor-sv__marker">- </span><span class="vditor-sv__marker">`</span><span>success($msg, $url)</span><span class="vditor-sv__marker">`</span><span data-type="text"> - 成功提示</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="li-marker" class="vditor-sv__marker">- </span><span class="vditor-sv__marker">`</span><span>error($msg, $url)</span><span class="vditor-sv__marker">`</span><span data-type="text"> - 错误提示</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="li-marker" class="vditor-sv__marker">- </span><span class="vditor-sv__marker">`</span><span>json($data)</span><span class="vditor-sv__marker">`</span><span data-type="text"> - 返回 JSON</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="newline"><br /><span style="display: none">
</span></span></div><div data-block="0"><span class="vditor-sv__marker--heading h4" data-type="heading-marker">#### </span><span data-type="text" class="h4">Model 类方法</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="newline"><br /><span style="display: none">
</span></span></div><div data-block="0"><span data-type="li-marker" class="vditor-sv__marker">- </span><span class="vditor-sv__marker">`</span><span>all()</span><span class="vditor-sv__marker">`</span><span data-type="text"> - 获取所有记录</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="li-marker" class="vditor-sv__marker">- </span><span class="vditor-sv__marker">`</span><span>find($id)</span><span class="vditor-sv__marker">`</span><span data-type="text"> - 根据 ID 查找</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="li-marker" class="vditor-sv__marker">- </span><span class="vditor-sv__marker">`</span><span>where($field, $value)</span><span class="vditor-sv__marker">`</span><span data-type="text"> - 条件查询</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="li-marker" class="vditor-sv__marker">- </span><span class="vditor-sv__marker">`</span><span>create($data)</span><span class="vditor-sv__marker">`</span><span data-type="text"> - 创建记录</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="li-marker" class="vditor-sv__marker">- </span><span class="vditor-sv__marker">`</span><span>update($data)</span><span class="vditor-sv__marker">`</span><span data-type="text"> - 更新记录</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="li-marker" class="vditor-sv__marker">- </span><span class="vditor-sv__marker">`</span><span>delete()</span><span class="vditor-sv__marker">`</span><span data-type="text"> - 删除记录</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="li-marker" class="vditor-sv__marker">- </span><span class="vditor-sv__marker">`</span><span>paginate($perPage)</span><span class="vditor-sv__marker">`</span><span data-type="text"> - 分页</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="newline"><br /><span style="display: none">
</span></span></div><div data-block="0"><span class="vditor-sv__marker--heading h4" data-type="heading-marker">#### </span><span data-type="text" class="h4">Request 类方法</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="newline"><br /><span style="display: none">
</span></span></div><div data-block="0"><span data-type="li-marker" class="vditor-sv__marker">- </span><span class="vditor-sv__marker">`</span><span>Request::get($key)</span><span class="vditor-sv__marker">`</span><span data-type="text"> - 获取 GET 参数</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="li-marker" class="vditor-sv__marker">- </span><span class="vditor-sv__marker">`</span><span>Request::post($key)</span><span class="vditor-sv__marker">`</span><span data-type="text"> - 获取 POST 参数</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="li-marker" class="vditor-sv__marker">- </span><span class="vditor-sv__marker">`</span><span>Request::input($key)</span><span class="vditor-sv__marker">`</span><span data-type="text"> - 获取请求参数</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="li-marker" class="vditor-sv__marker">- </span><span class="vditor-sv__marker">`</span><span>Request::isPost()</span><span class="vditor-sv__marker">`</span><span data-type="text"> - 判断是否 POST 请求</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="li-marker" class="vditor-sv__marker">- </span><span class="vditor-sv__marker">`</span><span>Request::ip()</span><span class="vditor-sv__marker">`</span><span data-type="text"> - 获取客户端 IP</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="newline"><br /><span style="display: none">
</span></span></div><div data-block="0"><span class="vditor-sv__marker">---</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="newline"><br /><span style="display: none">
</span></span></div><div data-block="0"><span class="vditor-sv__marker--heading h2" data-type="heading-marker">## </span><span data-type="text" class="h2">Article 应用完整分析</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="newline"><br /><span style="display: none">
</span></span></div><div data-block="0"><span data-type="text">为了更好地理解 iCMS v8 的应用开发,下面我们分析 </span><span class="vditor-sv__marker">`</span><span>app/article/</span><span class="vditor-sv__marker">`</span><span data-type="text"> 文章应用的实际结构:</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="newline"><br /><span style="display: none">
</span></span></div><div data-block="0"><span class="vditor-sv__marker--heading h3" data-type="heading-marker">### </span><span data-type="text" class="h3">文件列表</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="newline"><br /><span style="display: none">
</span></span></div><div data-block="0"><span data-type="code-block-open-marker" class="vditor-sv__marker">```</span><span class="vditor-sv__marker--info" data-type="code-block-info"></span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="text">app/article/<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>├── Article.php                    # 主类<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>├── ArticleModel.php               # 主表模型<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>├── ArticleDataModel.php           # 数据表模型(分表)<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>├── ArticleAdmincp.php            # 后台管理<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>├── ArticleApp.php                # 前台应用<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>├── ArticleUserApp.php            # 用户中心<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>├── ArticleCategoryAdmincp.php    # 分类管理<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>├── ArticleEvent.php              # 事件处理<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>├── ArticleFunc.php               # 模板标签<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>├── ArticleFuncSearch.php         # 搜索标签<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>├── ArticleHook.php               # 系统钩子<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>├── ArticleWidgetAdmincp.php      # 后台小部件<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>├── ArticleShellApp.php           # Shell 命令行应用<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>├── assets/                       # 静态资源<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>│   └── js/<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>│       ├── add.js<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>│       ├── edit.js<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>│       └── index.js<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>├── etc/                          # 配置文件<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>│   ├── admincp/<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>│   │   ├── Article.index.batch.json<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>│   │   ├── home.quick.json<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>│   │   └── home.stats.json<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>│   ├── menu/<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>│   │   ├── admincp.json<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>│   │   ├── admincp.cache.json<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>│   │   └── usercp.content.json<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>│   └── route/<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>│       ├── node.json<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>│       └── route.ArticleUser.json<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>└── views/                        # 视图模板<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>    ├── add.html<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>    ├── edit.html<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>    ├── index.html<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>    ├── batch.html<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>    └── app/<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>        ├── config/<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>        │   ├── article.html<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>        │   └── article.sphinx.html<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>        └── node/<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>            └── add.html</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="code-block-close-marker" class="vditor-sv__marker">```</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="newline"><br /><span style="display: none">
</span></span></div><div data-block="0"><span class="vditor-sv__marker--heading h3" data-type="heading-marker">### </span><span data-type="text" class="h3">核心类分析</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="newline"><br /><span style="display: none">
</span></span></div><div data-block="0"><span class="vditor-sv__marker--heading h4" data-type="heading-marker">#### </span><span data-type="text" class="h4">1. Article.php - 应用主类</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="newline"><br /><span style="display: none">
</span></span></div><div data-block="0"><span class="vditor-sv__marker--bi strong">**</span><span data-type="text" class="strong">关键代码</span><span class="vditor-sv__marker--bi strong">**</span><span data-type="text">:</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="newline"><br /><span style="display: none">
</span></span></div><div data-block="0"><span data-type="code-block-open-marker" class="vditor-sv__marker">```</span><span class="vditor-sv__marker--info" data-type="code-block-info">php</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="text">class Article<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>{<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>    const APP = 'article';<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>    const APPID = iCMS_APP_ARTICLE;<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>    <span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>    public static $stypeMap = array(<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>        'inbox'   => '0',<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>        'normal'  => '1',<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>        'trash'   => '2',<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>        'examine' => '3',<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>        // ...<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>    );<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>    <span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>    public static function check($value, $id = 0, $field = 'title') { }<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>    public static function value($field = 'id', $id = 0) { }<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>    public static function get($id = 0, $field = '*', $where = array()) { }<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>    public static function data($id = 0, $dataId = 0, $userid = 0) { }<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>    public static function create($data) { }<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>    public static function update($data, $where) { }<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>    public static function delete($id) { }<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>}</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="code-block-close-marker" class="vditor-sv__marker">```</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="newline"><br /><span style="display: none">
</span></span></div><div data-block="0"><span class="vditor-sv__marker--bi strong">**</span><span data-type="text" class="strong">特点</span><span class="vditor-sv__marker--bi strong">**</span><span data-type="text">:</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="newline"><br /><span style="display: none">
</span></span></div><div data-block="0"><span data-type="li-marker" class="vditor-sv__marker">- </span><span data-type="text">定义应用常量和状态映射</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="li-marker" class="vditor-sv__marker">- </span><span data-type="text">提供静态工具方法</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="li-marker" class="vditor-sv__marker">- </span><span data-type="text">封装 Model 操作</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="newline"><br /><span style="display: none">
</span></span></div><div data-block="0"><span class="vditor-sv__marker--heading h4" data-type="heading-marker">#### </span><span data-type="text" class="h4">2. ArticleModel.php - 主表模型</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="newline"><br /><span style="display: none">
</span></span></div><div data-block="0"><span class="vditor-sv__marker--bi strong">**</span><span data-type="text" class="strong">关键代码</span><span class="vditor-sv__marker--bi strong">**</span><span data-type="text">:</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="newline"><br /><span style="display: none">
</span></span></div><div data-block="0"><span data-type="code-block-open-marker" class="vditor-sv__marker">```</span><span class="vditor-sv__marker--info" data-type="code-block-info">php</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="text">class ArticleModel extends Model<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>{<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>    public static $statusMap = [<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>        '0' => '草稿',<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>        '1' => '正常',<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>        // ...<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>    ];<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>    <span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>    protected $casts = [<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>        'picdata' => 'array',<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>        'nodeAttrs' => 'array',<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>    ];<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>    <span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>    protected $events = [<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>        'delete'  => ['ArticleEvent', 'delete'],<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>        'changed' => ['ArticleEvent', 'changed'],<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>    ];<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>}</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="code-block-close-marker" class="vditor-sv__marker">```</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="newline"><br /><span style="display: none">
</span></span></div><div data-block="0"><span class="vditor-sv__marker--bi strong">**</span><span data-type="text" class="strong">特点</span><span class="vditor-sv__marker--bi strong">**</span><span data-type="text">:</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="newline"><br /><span style="display: none">
</span></span></div><div data-block="0"><span data-type="li-marker" class="vditor-sv__marker">- </span><span data-type="text">使用 </span><span class="vditor-sv__marker">`</span><span>$casts</span><span class="vditor-sv__marker">`</span><span data-type="text"> 自动转换 JSON 为数组</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="li-marker" class="vditor-sv__marker">- </span><span data-type="text">使用 </span><span class="vditor-sv__marker">`</span><span>$events</span><span class="vditor-sv__marker">`</span><span data-type="text"> 绑定事件到 ArticleEvent</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="li-marker" class="vditor-sv__marker">- </span><span data-type="text">定义状态映射数组</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="newline"><br /><span style="display: none">
</span></span></div><div data-block="0"><span class="vditor-sv__marker--heading h4" data-type="heading-marker">#### </span><span data-type="text" class="h4">3. ArticleAdmincp.php - 后台管理</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="newline"><br /><span style="display: none">
</span></span></div><div data-block="0"><span class="vditor-sv__marker--bi strong">**</span><span data-type="text" class="strong">关键代码</span><span class="vditor-sv__marker--bi strong">**</span><span data-type="text">:</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="newline"><br /><span style="display: none">
</span></span></div><div data-block="0"><span data-type="code-block-open-marker" class="vditor-sv__marker">```</span><span class="vditor-sv__marker--info" data-type="code-block-info">php</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="text">class ArticleAdmincp extends AdmincpCommon<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>{<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>    use AdmincpCommonTrait;<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>    <span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>    public static $orderBy = [<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>        'id'         => 'ID',<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>        'hits'       => '点击',<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>        'postime'    => '时间',<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>    ];<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>    <span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>    public function do_add() {<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>        // 添加/编辑页面<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>        include self::view();<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>    }<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>    <span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>    public function do_save() {<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>        // 保存数据<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>        return [true, '保存成功'];<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>    }<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>}</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="code-block-close-marker" class="vditor-sv__marker">```</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="newline"><br /><span style="display: none">
</span></span></div><div data-block="0"><span class="vditor-sv__marker--bi strong">**</span><span data-type="text" class="strong">特点</span><span class="vditor-sv__marker--bi strong">**</span><span data-type="text">:</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="newline"><br /><span style="display: none">
</span></span></div><div data-block="0"><span data-type="li-marker" class="vditor-sv__marker">- </span><span data-type="text">继承 </span><span class="vditor-sv__marker">`</span><span>AdmincpCommon</span><span class="vditor-sv__marker">`</span><span data-type="text"> + </span><span class="vditor-sv__marker">`</span><span>AdmincpCommonTrait</span><span class="vditor-sv__marker">`</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="li-marker" class="vditor-sv__marker">- </span><span data-type="text">使用 </span><span class="vditor-sv__marker">`</span><span>do_xxx()</span><span class="vditor-sv__marker">`</span><span data-type="text"> 方法</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="li-marker" class="vditor-sv__marker">- </span><span data-type="text">使用 </span><span class="vditor-sv__marker">`</span><span>include self::view()</span><span class="vditor-sv__marker">`</span><span data-type="text"> 加载视图</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="newline"><br /><span style="display: none">
</span></span></div><div data-block="0"><span class="vditor-sv__marker--heading h4" data-type="heading-marker">#### </span><span data-type="text" class="h4">4. ArticleEvent.php - 事件处理</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="newline"><br /><span style="display: none">
</span></span></div><div data-block="0"><span class="vditor-sv__marker--bi strong">**</span><span data-type="text" class="strong">关键代码</span><span class="vditor-sv__marker--bi strong">**</span><span data-type="text">:</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="newline"><br /><span style="display: none">
</span></span></div><div data-block="0"><span data-type="code-block-open-marker" class="vditor-sv__marker">```</span><span class="vditor-sv__marker--info" data-type="code-block-info">php</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="text">class ArticleEvent extends DBEvent<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>{<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>    public static $appid = iCMS_APP_ARTICLE;<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>    <span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>    public static function changed($response, $Builder, $event, $isMulti)<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>    {<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>        // 处理分类、标签、属性、文件等变化<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>        if (isset($response['cid'])) {<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>            Node::change('cid', $response['cid'], $event, $id, self::$appid);<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>        }<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>        if (isset($response['tags'])) {<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>            Tag::change('tags', $response['tags'], $event, $id, self::$appid, $Builder);<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>        }<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>        // ...<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>    }<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>    <span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>    public static function delete($response, $Builder, $event, $isMulti)<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>    {<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>        // 删除关联数据<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>        AppsMap::delete(self::$appid, $id, 'Node');<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>        Files::delete(self::$appid, $id);<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>        // ...<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>    }<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>}</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="code-block-close-marker" class="vditor-sv__marker">```</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="newline"><br /><span style="display: none">
</span></span></div><div data-block="0"><span class="vditor-sv__marker--bi strong">**</span><span data-type="text" class="strong">特点</span><span class="vditor-sv__marker--bi strong">**</span><span data-type="text">:</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="newline"><br /><span style="display: none">
</span></span></div><div data-block="0"><span data-type="li-marker" class="vditor-sv__marker">- </span><span data-type="text">固定的方法签名</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="li-marker" class="vditor-sv__marker">- </span><span data-type="text">处理分类、标签、文件等关联</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="li-marker" class="vditor-sv__marker">- </span><span data-type="text">使用系统提供的工具类</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="newline"><br /><span style="display: none">
</span></span></div><div data-block="0"><span class="vditor-sv__marker--heading h4" data-type="heading-marker">#### </span><span data-type="text" class="h4">5. ArticleFunc.php - 模板标签</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="newline"><br /><span style="display: none">
</span></span></div><div data-block="0"><span class="vditor-sv__marker--bi strong">**</span><span data-type="text" class="strong">关键代码</span><span class="vditor-sv__marker--bi strong">**</span><span data-type="text">:</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="newline"><br /><span style="display: none">
</span></span></div><div data-block="0"><span data-type="code-block-open-marker" class="vditor-sv__marker">```</span><span class="vditor-sv__marker--info" data-type="code-block-info">php</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="text">class ArticleFunc extends AppsFuncCommon implements AppsFuncInterface<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>{<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>    public static function lists($vars)<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>    {<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>        $model = ArticleModel::field('id');<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>        $where = [['status', 1]];<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>        <span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>        self::inited($vars, $model, $where, $whereNot);<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>        self::nodes('cid');<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>        self::tags();<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>        self::orderby([<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>            'hot' => 'hits',<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>            'week' => 'hits_week',<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>        ]);<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>        self::where();<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>        return self::getResource(__METHOD__);<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>    }<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>    <span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>    public static function resource($idsArray = null)<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>    {<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>        $resource = ArticleModel::field('*')<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>            ->where($idsArray)<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>            ->orderBy('id', $idsArray)<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>            ->select();<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>        return self::many($vars, $resource);<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>    }<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>}</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="code-block-close-marker" class="vditor-sv__marker">```</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="newline"><br /><span style="display: none">
</span></span></div><div data-block="0"><span class="vditor-sv__marker--bi strong">**</span><span data-type="text" class="strong">特点</span><span class="vditor-sv__marker--bi strong">**</span><span data-type="text">:</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="newline"><br /><span style="display: none">
</span></span></div><div data-block="0"><span data-type="li-marker" class="vditor-sv__marker">- </span><span data-type="text">使用基类的辅助方法(nodes、tags、orderby)</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="li-marker" class="vditor-sv__marker">- </span><span class="vditor-sv__marker">`</span><span>lists()</span><span class="vditor-sv__marker">`</span><span data-type="text"> 方法配合 </span><span class="vditor-sv__marker">`</span><span>resource()</span><span class="vditor-sv__marker">`</span><span data-type="text"> 方法</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="li-marker" class="vditor-sv__marker">- </span><span data-type="text">返回资源数组供模板使用</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="newline"><br /><span style="display: none">
</span></span></div><div data-block="0"><span class="vditor-sv__marker--heading h3" data-type="heading-marker">### </span><span data-type="text" class="h3">关键配置文件</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="newline"><br /><span style="display: none">
</span></span></div><div data-block="0"><span class="vditor-sv__marker--heading h4" data-type="heading-marker">#### </span><span data-type="text" class="h4">etc/menu/admincp.json</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="newline"><br /><span style="display: none">
</span></span></div><div data-block="0"><span data-type="code-block-open-marker" class="vditor-sv__marker">```</span><span class="vditor-sv__marker--info" data-type="code-block-info">json</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="text">[{<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>    "id": "article",<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>    "caption": "文章",<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>    "icon": "si si-grid",<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>    "href": "/article/index",<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>    "children": [...]<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>}]</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="code-block-close-marker" class="vditor-sv__marker">```</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="newline"><br /><span style="display: none">
</span></span></div><div data-block="0"><span class="vditor-sv__marker--heading h4" data-type="heading-marker">#### </span><span data-type="text" class="h4">etc/admincp/Article.index.batch.json</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="newline"><br /><span style="display: none">
</span></span></div><div data-block="0"><span data-type="code-block-open-marker" class="vditor-sv__marker">```</span><span class="vditor-sv__marker--info" data-type="code-block-info">json</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="text">{<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>    "status=1": {<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>        "name": "发布",<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>        "icon": "check",<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>        "call": "default"<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>    },<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>    "move": {<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>        "name": "移动栏目",<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>        "call": "move"<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>    }<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>}</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="code-block-close-marker" class="vditor-sv__marker">```</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="newline"><br /><span style="display: none">
</span></span></div><div data-block="0"><span class="vditor-sv__marker--heading h3" data-type="heading-marker">### </span><span data-type="text" class="h3">数据表设计</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="newline"><br /><span style="display: none">
</span></span></div><div data-block="0"><span class="vditor-sv__marker--heading h4" data-type="heading-marker">#### </span><span data-type="text" class="h4">icms_article(主表)</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="newline"><br /><span style="display: none">
</span></span></div><div data-block="0"><span data-type="li-marker" class="vditor-sv__marker">- </span><span data-type="text">id, cid, uid, title, description, tags, status, hits, postime, pubdate, etc.</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="newline"><br /><span style="display: none">
</span></span></div><div data-block="0"><span class="vditor-sv__marker--heading h4" data-type="heading-marker">#### </span><span data-type="text" class="h4">icms_article_data_0 ~ icms_article_data_9(分表)</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="newline"><br /><span style="display: none">
</span></span></div><div data-block="0"><span data-type="li-marker" class="vditor-sv__marker">- </span><span data-type="text">id, article_id, subtitle, body</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="newline"><br /><span style="display: none">
</span></span></div><div data-block="0"><span class="vditor-sv__marker">---</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="newline"><br /><span style="display: none">
</span></span></div><div data-block="0"><span class="vditor-sv__marker--heading h2" data-type="heading-marker">## </span><span data-type="text" class="h2">开发建议</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="newline"><br /><span style="display: none">
</span></span></div><div data-block="0"><span class="vditor-sv__marker--heading h3" data-type="heading-marker">### </span><span data-type="text" class="h3">1. 从 Article 应用学习</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="newline"><br /><span style="display: none">
</span></span></div><div data-block="0"><span data-type="text">Article 应用是最完整的参考示例,包含了:</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="newline"><br /><span style="display: none">
</span></span></div><div data-block="0"><span data-type="li-marker" class="vditor-sv__marker">- </span><span data-type="text">✅ 完整的 CRUD 操作</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="li-marker" class="vditor-sv__marker">- </span><span data-type="text">✅ 分类、标签、属性管理</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="li-marker" class="vditor-sv__marker">- </span><span data-type="text">✅ 用户投稿功能</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="li-marker" class="vditor-sv__marker">- </span><span data-type="text">✅ 事件处理机制</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="li-marker" class="vditor-sv__marker">- </span><span data-type="text">✅ 数据分表策略</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="li-marker" class="vditor-sv__marker">- </span><span data-type="text">✅ 模板标签系统</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="newline"><br /><span style="display: none">
</span></span></div><div data-block="0"><span class="vditor-sv__marker--heading h3" data-type="heading-marker">### </span><span data-type="text" class="h3">2. 复制改造法</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="newline"><br /><span style="display: none">
</span></span></div><div data-block="0"><span data-type="text">最快的开发方式:</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="newline"><br /><span style="display: none">
</span></span></div><div data-block="0"><span data-type="li-marker" class="vditor-sv__marker">1. </span><span data-type="text">复制整个 </span><span class="vditor-sv__marker">`</span><span>article</span><span class="vditor-sv__marker">`</span><span data-type="text"> 目录为 </span><span class="vditor-sv__marker">`</span><span>yourapp</span><span class="vditor-sv__marker">`</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="li-marker" class="vditor-sv__marker">2. </span><span data-type="text">批量替换所有 </span><span class="vditor-sv__marker">`</span><span>Article</span><span class="vditor-sv__marker">`</span><span data-type="text"> 为 </span><span class="vditor-sv__marker">`</span><span>Yourapp</span><span class="vditor-sv__marker">`</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="li-marker" class="vditor-sv__marker">3. </span><span data-type="text">批量替换所有 </span><span class="vditor-sv__marker">`</span><span>article</span><span class="vditor-sv__marker">`</span><span data-type="text"> 为 </span><span class="vditor-sv__marker">`</span><span>yourapp</span><span class="vditor-sv__marker">`</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="li-marker" class="vditor-sv__marker">4. </span><span data-type="text">修改配置文件中的菜单、路由等</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="li-marker" class="vditor-sv__marker">5. </span><span data-type="text">根据需求调整字段和逻辑</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="newline"><br /><span style="display: none">
</span></span></div><div data-block="0"><span class="vditor-sv__marker--heading h3" data-type="heading-marker">### </span><span data-type="text" class="h3">3. 必须定义的文件</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="newline"><br /><span style="display: none">
</span></span></div><div data-block="0"><span class="vditor-sv__marker--bi strong">**</span><span data-type="text" class="strong">最小化应用</span><span class="vditor-sv__marker--bi strong">**</span><span data-type="text">至少需要:</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="newline"><br /><span style="display: none">
</span></span></div><div data-block="0"><span data-type="li-marker" class="vditor-sv__marker">- </span><span class="vditor-sv__marker">`</span><span>Yourapp.php</span><span class="vditor-sv__marker">`</span><span data-type="text"> - 主类(定义 APP 和 APPID 常量)</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="li-marker" class="vditor-sv__marker">- </span><span class="vditor-sv__marker">`</span><span>YourappModel.php</span><span class="vditor-sv__marker">`</span><span data-type="text"> - 数据模型</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="li-marker" class="vditor-sv__marker">- </span><span class="vditor-sv__marker">`</span><span>YourappAdmincp.php</span><span class="vditor-sv__marker">`</span><span data-type="text"> - 后台管理</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="li-marker" class="vditor-sv__marker">- </span><span class="vditor-sv__marker">`</span><span>etc/menu/admincp.json</span><span class="vditor-sv__marker">`</span><span data-type="text"> - 后台菜单</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="li-marker" class="vditor-sv__marker">- </span><span class="vditor-sv__marker">`</span><span>views/add.html</span><span class="vditor-sv__marker">`</span><span data-type="text"> - 添加页面</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="li-marker" class="vditor-sv__marker">- </span><span class="vditor-sv__marker">`</span><span>views/index.html</span><span class="vditor-sv__marker">`</span><span data-type="text"> - 列表页面</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="newline"><br /><span style="display: none">
</span></span></div><div data-block="0"><span class="vditor-sv__marker--bi strong">**</span><span data-type="text" class="strong">完整应用</span><span class="vditor-sv__marker--bi strong">**</span><span data-type="text">还应包括:</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="newline"><br /><span style="display: none">
</span></span></div><div data-block="0"><span data-type="li-marker" class="vditor-sv__marker">- </span><span class="vditor-sv__marker">`</span><span>YourappApp.php</span><span class="vditor-sv__marker">`</span><span data-type="text"> - 前台应用</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="li-marker" class="vditor-sv__marker">- </span><span class="vditor-sv__marker">`</span><span>YourappFunc.php</span><span class="vditor-sv__marker">`</span><span data-type="text"> - 模板标签</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="li-marker" class="vditor-sv__marker">- </span><span class="vditor-sv__marker">`</span><span>YourappEvent.php</span><span class="vditor-sv__marker">`</span><span data-type="text"> - 事件处理</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="li-marker" class="vditor-sv__marker">- </span><span class="vditor-sv__marker">`</span><span>YourappUserApp.php</span><span class="vditor-sv__marker">`</span><span data-type="text"> - 用户中心(如需用户投稿)</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="li-marker" class="vditor-sv__marker">- </span><span class="vditor-sv__marker">`</span><span>YourappCategoryAdmincp.php</span><span class="vditor-sv__marker">`</span><span data-type="text"> - 分类管理(如需分类)</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="newline"><br /><span style="display: none">
</span></span></div><div data-block="0"><span class="vditor-sv__marker--heading h3" data-type="heading-marker">### </span><span data-type="text" class="h3">4. 常见陷阱</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="newline"><br /><span style="display: none">
</span></span></div><div data-block="0"><span data-type="text">❌ </span><span class="vditor-sv__marker--bi strong">**</span><span data-type="text" class="strong">错误</span><span class="vditor-sv__marker--bi strong">**</span><span data-type="text">:</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="newline"><br /><span style="display: none">
</span></span></div><div data-block="0"><span data-type="code-block-open-marker" class="vditor-sv__marker">```</span><span class="vditor-sv__marker--info" data-type="code-block-info">php</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="text">class TestAdmincp extends AdmincpBase  // 错误的基类<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>{<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>    public function index() { }  // 缺少 do_ 前缀<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>}</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="code-block-close-marker" class="vditor-sv__marker">```</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="newline"><br /><span style="display: none">
</span></span></div><div data-block="0"><span data-type="text">✅ </span><span class="vditor-sv__marker--bi strong">**</span><span data-type="text" class="strong">正确</span><span class="vditor-sv__marker--bi strong">**</span><span data-type="text">:</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="newline"><br /><span style="display: none">
</span></span></div><div data-block="0"><span data-type="code-block-open-marker" class="vditor-sv__marker">```</span><span class="vditor-sv__marker--info" data-type="code-block-info">php</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="text">class TestAdmincp extends AdmincpCommon<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>{<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>    use AdmincpCommonTrait;<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>    <span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>    public function do_index() { }<span data-type="padding"></span><span data-type="newline"><br /><span style="display: none">
</span></span>}</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="code-block-close-marker" class="vditor-sv__marker">```</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="newline"><br /><span style="display: none">
</span></span></div><div data-block="0"><span class="vditor-sv__marker">---</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="newline"><br /><span style="display: none">
</span></span></div><div data-block="0"><span class="vditor-sv__marker--heading h2" data-type="heading-marker">## </span><span data-type="text" class="h2">总结</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="newline"><br /><span style="display: none">
</span></span></div><div data-block="0"><span data-type="text">iCMS v8 提供了完整而灵活的应用开发框架,遵循 MVC 设计模式,通过规范的目录结构和命名约定,让开发者能够快速构建功能强大的应用。</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="newline"><br /><span style="display: none">
</span></span></div><div data-block="0"><span class="vditor-sv__marker--bi strong">**</span><span data-type="text" class="strong">核心要点</span><span class="vditor-sv__marker--bi strong">**</span><span data-type="text">:</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="newline"><br /><span style="display: none">
</span></span></div><div data-block="0"><span data-type="li-marker" class="vditor-sv__marker">1. </span><span data-type="text">✅ </span><span class="vditor-sv__marker--bi strong">**</span><span data-type="text" class="strong">参考 Article</span><span class="vditor-sv__marker--bi strong">**</span><span data-type="text">:以 article 应用为标准参考</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="li-marker" class="vditor-sv__marker">2. </span><span data-type="text">✅ </span><span class="vditor-sv__marker--bi strong">**</span><span data-type="text" class="strong">继承关系</span><span class="vditor-sv__marker--bi strong">**</span><span data-type="text">:严格遵循正确的类继承关系</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="li-marker" class="vditor-sv__marker">3. </span><span data-type="text">✅ </span><span class="vditor-sv__marker--bi strong">**</span><span data-type="text" class="strong">方法前缀</span><span class="vditor-sv__marker--bi strong">**</span><span data-type="text">:Admincp 用 </span><span class="vditor-sv__marker">`</span><span>do_</span><span class="vditor-sv__marker">`</span><span data-type="text">,UserApp 用 </span><span class="vditor-sv__marker">`</span><span>api_/done_</span><span class="vditor-sv__marker">`</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="li-marker" class="vditor-sv__marker">4. </span><span data-type="text">✅ </span><span class="vditor-sv__marker--bi strong">**</span><span data-type="text" class="strong">事件系统</span><span class="vditor-sv__marker--bi strong">**</span><span data-type="text">:使用 </span><span class="vditor-sv__marker">`</span><span>$events</span><span class="vditor-sv__marker">`</span><span data-type="text"> 数组 + 固定方法签名</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="li-marker" class="vditor-sv__marker">5. </span><span data-type="text">✅ </span><span class="vditor-sv__marker--bi strong">**</span><span data-type="text" class="strong">配置格式</span><span class="vditor-sv__marker--bi strong">**</span><span data-type="text">:参考实际 JSON 配置文件格式</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="li-marker" class="vditor-sv__marker">6. </span><span data-type="text">✅ </span><span class="vditor-sv__marker--bi strong">**</span><span data-type="text" class="strong">模板标签</span><span class="vditor-sv__marker--bi strong">**</span><span data-type="text">:继承 AppsFuncCommon,使用辅助方法</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="li-marker" class="vditor-sv__marker">7. </span><span data-type="text">✅ </span><span class="vditor-sv__marker--bi strong">**</span><span data-type="text" class="strong">分表策略</span><span class="vditor-sv__marker--bi strong">**</span><span data-type="text">:大数据量使用 sharding 方法</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="li-marker" class="vditor-sv__marker">8. </span><span data-type="text">✅ </span><span class="vditor-sv__marker--bi strong">**</span><span data-type="text" class="strong">安全验证</span><span class="vditor-sv__marker--bi strong">**</span><span data-type="text">:数据验证、权限控制、防注入</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="newline"><br /><span style="display: none">
</span></span></div><div data-block="0"><span class="vditor-sv__marker--bi strong">**</span><span data-type="text" class="strong">推荐开发流程</span><span class="vditor-sv__marker--bi strong">**</span><span data-type="text">:</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="newline"><br /><span style="display: none">
</span></span></div><div data-block="0"><span data-type="li-marker" class="vditor-sv__marker">1. </span><span data-type="text">复制 article 应用为模板</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="li-marker" class="vditor-sv__marker">2. </span><span data-type="text">批量替换类名和文件名</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="li-marker" class="vditor-sv__marker">3. </span><span data-type="text">修改数据表结构</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="li-marker" class="vditor-sv__marker">4. </span><span data-type="text">调整业务逻辑</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="li-marker" class="vditor-sv__marker">5. </span><span data-type="text">测试功能</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="li-marker" class="vditor-sv__marker">6. </span><span data-type="text">清除缓存</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="newline"><br /><span style="display: none">
</span></span></div><div data-block="0"><span data-type="text">祝您开发顺利!如有问题,请参考 </span><span class="vditor-sv__marker">`</span><span>app/article/</span><span class="vditor-sv__marker">`</span><span data-type="text"> 目录中的实际代码。</span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="newline"><br /><span style="display: none">
</span></span></div><div data-block="0"><span data-type="text"><wbr></span><span data-type="newline"><br /><span style="display: none">
</span></span><span data-type="newline"><br /><span style="display: none">
</span></span></div>