{{wikiTitle}}
添加页面说明
复制链接
编辑文档
CRMEB PRO 添加页面说明
概述
本文档说明如何在 CRMEB PRO 后台管理系统中添加新页面,包括控制器、服务层、DAO、视图的完整实现步骤。
添加页面完整流程
流程图
1. 创建数据库表
↓
2. 创建Model(模型)
↓
3. 创建DAO(数据访问层)
↓
4. 创建Services(服务层)
↓
5. 创建Controller(控制器)
↓
6. 配置路由
↓
7. 添加后台菜单
↓
8. 前端页面开发
示例:添加”公告管理”功能
Step 1: 创建数据库表
CREATE TABLE `eb_system_notice` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT '公告ID',
`title` varchar(128) NOT NULL DEFAULT '' COMMENT '公告标题',
`content` text COMMENT '公告内容',
`type` tinyint(1) unsigned DEFAULT '1' COMMENT '公告类型 1:系统公告 2:活动公告',
`status` tinyint(1) unsigned DEFAULT '1' COMMENT '状态 0:禁用 1:启用',
`sort` int(10) unsigned DEFAULT '0' COMMENT '排序',
`is_del` tinyint(1) unsigned DEFAULT '0' COMMENT '是否删除',
`add_time` int(10) unsigned DEFAULT '0' COMMENT '添加时间',
`update_time` int(10) unsigned DEFAULT '0' COMMENT '更新时间',
PRIMARY KEY (`id`),
KEY `status` (`status`),
KEY `type` (`type`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='系统公告表';
Step 2: 创建Model
<?php
// app/model/system/SystemNotice.php
declare(strict_types=1);
namespace app\model\system;
use crmeb\basic\BaseModel;
use crmeb\traits\ModelTrait;
use think\Model;
/**
* 系统公告模型
* Class SystemNotice
* @package app\model\system
*/
class SystemNotice extends BaseModel
{
use ModelTrait;
/**
* 数据表主键
* @var string
*/
protected $pk = 'id';
/**
* 模型名称
* @var string
*/
protected $name = 'system_notice';
/**
* 自动时间戳
* @var bool
*/
protected $autoWriteTimestamp = 'int';
/**
* 创建时间字段
* @var string
*/
protected $createTime = 'add_time';
/**
* 更新时间字段
* @var string
*/
protected $updateTime = 'update_time';
/**
* 类型搜索器
* @param Model $query
* @param $value
*/
public function searchTypeAttr($query, $value)
{
if ($value !== '') {
$query->where('type', $value);
}
}
/**
* 状态搜索器
* @param Model $query
* @param $value
*/
public function searchStatusAttr($query, $value)
{
if ($value !== '') {
$query->where('status', $value);
}
}
/**
* 关键词搜索器
* @param Model $query
* @param $value
*/
public function searchKeywordAttr($query, $value)
{
if ($value !== '') {
$query->whereLike('title', '%' . $value . '%');
}
}
/**
* 是否删除搜索器
* @param Model $query
* @param $value
*/
public function searchIsDelAttr($query, $value)
{
$query->where('is_del', $value ?? 0);
}
}
Step 3: 创建DAO
<?php
// app/dao/system/SystemNoticeDao.php
declare(strict_types=1);
namespace app\dao\system;
use app\dao\BaseDao;
use app\model\system\SystemNotice;
/**
* 系统公告DAO
* Class SystemNoticeDao
* @package app\dao\system
*/
class SystemNoticeDao extends BaseDao
{
/**
* 设置模型
* @return string
*/
protected function setModel(): string
{
return SystemNotice::class;
}
/**
* 获取列表
* @param array $where
* @param int $page
* @param int $limit
* @param string $field
* @param string $order
* @return array
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function getList(array $where, int $page = 0, int $limit = 0, string $field = '*', string $order = 'sort desc, id desc'): array
{
return $this->search($where)
->field($field)
->when($page && $limit, function ($query) use ($page, $limit) {
$query->page($page, $limit);
})
->order($order)
->select()
->toArray();
}
/**
* 获取数量
* @param array $where
* @return int
*/
public function getCount(array $where): int
{
return $this->search($where)->count();
}
}
Step 4: 创建Services
<?php
// app/services/system/SystemNoticeServices.php
declare(strict_types=1);
namespace app\services\system;
use app\dao\system\SystemNoticeDao;
use app\services\BaseServices;
use crmeb\exceptions\AdminException;
use crmeb\services\FormBuilder as Form;
use think\facade\Route as Url;
/**
* 系统公告服务
* Class SystemNoticeServices
* @package app\services\system
*/
class SystemNoticeServices extends BaseServices
{
/**
* 构造方法
* SystemNoticeServices constructor.
* @param SystemNoticeDao $dao
*/
public function __construct(SystemNoticeDao $dao)
{
$this->dao = $dao;
}
/**
* 获取列表
* @param array $where
* @return array
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function getList(array $where): array
{
[$page, $limit] = $this->getPageValue();
$where['is_del'] = 0;
$list = $this->dao->getList($where, $page, $limit);
$count = $this->dao->getCount($where);
return compact('list', 'count');
}
/**
* 获取详情
* @param int $id
* @return array
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function getInfo(int $id): array
{
$info = $this->dao->get($id);
if (!$info) {
throw new AdminException('公告不存在');
}
return $info->toArray();
}
/**
* 创建表单
* @return array
* @throws \FormBuilder\Exception\FormBuilderException
*/
public function createForm(): array
{
return create_form('添加公告', $this->form(), Url::buildUrl('/system/notice'), 'POST');
}
/**
* 编辑表单
* @param int $id
* @return array
* @throws \FormBuilder\Exception\FormBuilderException
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function editForm(int $id): array
{
$info = $this->getInfo($id);
return create_form('编辑公告', $this->form($info), Url::buildUrl('/system/notice/' . $id), 'PUT');
}
/**
* 表单字段
* @param array $info
* @return array
* @throws \FormBuilder\Exception\FormBuilderException
*/
protected function form(array $info = []): array
{
return [
Form::input('title', '公告标题', $info['title'] ?? '')->required(),
Form::select('type', '公告类型', $info['type'] ?? 1)->options([
['value' => 1, 'label' => '系统公告'],
['value' => 2, 'label' => '活动公告'],
])->required(),
Form::textarea('content', '公告内容', $info['content'] ?? '')->required(),
Form::number('sort', '排序', $info['sort'] ?? 0)->min(0),
Form::radio('status', '状态', $info['status'] ?? 1)->options([
['value' => 1, 'label' => '启用'],
['value' => 0, 'label' => '禁用'],
]),
];
}
/**
* 保存公告
* @param array $data
* @return bool
*/
public function save(array $data): bool
{
$data['add_time'] = time();
return $this->dao->save($data) ? true : false;
}
/**
* 更新公告
* @param int $id
* @param array $data
* @return bool
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function update(int $id, array $data): bool
{
$info = $this->dao->get($id);
if (!$info) {
throw new AdminException('公告不存在');
}
$data['update_time'] = time();
return $this->dao->update($id, $data) ? true : false;
}
/**
* 删除公告
* @param int $id
* @return bool
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function delete(int $id): bool
{
$info = $this->dao->get($id);
if (!$info) {
throw new AdminException('公告不存在');
}
return $this->dao->update($id, ['is_del' => 1]) ? true : false;
}
/**
* 修改状态
* @param int $id
* @param int $status
* @return bool
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function setStatus(int $id, int $status): bool
{
$info = $this->dao->get($id);
if (!$info) {
throw new AdminException('公告不存在');
}
return $this->dao->update($id, ['status' => $status]) ? true : false;
}
}
Step 5: 创建Controller
<?php
// app/controller/admin/v1/system/SystemNotice.php
declare(strict_types=1);
namespace app\controller\admin\v1\system;
use app\controller\admin\AuthController;
use app\services\system\SystemNoticeServices;
use think\facade\App;
/**
* 系统公告控制器
* Class SystemNotice
* @package app\controller\admin\v1\system
*/
class SystemNotice extends AuthController
{
/**
* 服务
* @var SystemNoticeServices
*/
protected SystemNoticeServices $services;
/**
* 构造方法
* SystemNotice constructor.
* @param App $app
* @param SystemNoticeServices $services
*/
public function __construct(App $app, SystemNoticeServices $services)
{
parent::__construct($app);
$this->services = $services;
}
/**
* 公告列表
* @return mixed
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function index()
{
$where = $this->request->getMore([
['keyword', ''],
['type', ''],
['status', ''],
]);
return app('json')->success($this->services->getList($where));
}
/**
* 公告详情
* @param int $id
* @return mixed
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function read(int $id)
{
return app('json')->success($this->services->getInfo($id));
}
/**
* 创建表单
* @return mixed
* @throws \FormBuilder\Exception\FormBuilderException
*/
public function create()
{
return app('json')->success($this->services->createForm());
}
/**
* 保存公告
* @return mixed
*/
public function save()
{
$data = $this->request->postMore([
['title', ''],
['type', 1],
['content', ''],
['sort', 0],
['status', 1],
]);
if (empty($data['title'])) {
return app('json')->fail('请输入公告标题');
}
$this->services->save($data);
return app('json')->success('添加成功');
}
/**
* 编辑表单
* @param int $id
* @return mixed
* @throws \FormBuilder\Exception\FormBuilderException
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function edit(int $id)
{
return app('json')->success($this->services->editForm($id));
}
/**
* 更新公告
* @param int $id
* @return mixed
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function update(int $id)
{
$data = $this->request->postMore([
['title', ''],
['type', 1],
['content', ''],
['sort', 0],
['status', 1],
]);
if (empty($data['title'])) {
return app('json')->fail('请输入公告标题');
}
$this->services->update($id, $data);
return app('json')->success('修改成功');
}
/**
* 删除公告
* @param int $id
* @return mixed
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function delete(int $id)
{
$this->services->delete($id);
return app('json')->success('删除成功');
}
/**
* 修改状态
* @param int $id
* @param int $status
* @return mixed
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function set_status(int $id, int $status)
{
$this->services->setStatus($id, $status);
return app('json')->success('修改成功');
}
}
Step 6: 配置路由
// route/admin.php 中添加
/**
* 系统公告管理
*/
Route::group('system', function () {
// 修改公告状态
Route::put('notice/set_status/:id/:status', 'v1.system.SystemNotice/set_status')
->name('NoticeSetStatus')
->option(['real_name' => '修改公告状态']);
// 公告资源路由
Route::resource('notice', 'v1.system.SystemNotice')
->name('NoticeResource')
->option(['real_name' => [
'index' => '获取公告列表',
'read' => '获取公告详情',
'create' => '获取创建公告表单',
'save' => '保存公告',
'edit' => '获取编辑公告表单',
'update' => '更新公告',
'delete' => '删除公告'
]]);
})->middleware([
\app\http\middleware\AllowOriginMiddleware::class,
\app\http\middleware\admin\AdminAuthTokenMiddleware::class,
\app\http\middleware\admin\AdminCkeckRoleMiddleware::class,
\app\http\middleware\admin\AdminLogMiddleware::class
]);
Step 7: 添加后台菜单
通过后台管理界面添加:
- 进入 设置 → 权限管理 → 菜单管理
- 点击 添加菜单
- 填写以下信息:
- 菜单名称:公告管理
- 上级菜单:系统设置
- 路由路径:/system/notice
- 权限标识:admin-system-notice
- 是否显示:是
Step 8: 前端页面开发
在后台前端项目中创建页面:
<!-- view/admin/src/pages/system/notice/index.vue -->
<template>
<div class="notice-page">
<!-- 搜索表单 -->
<Card :bordered="false" dis-hover class="ivu-mt">
<Form ref="searchForm" :model="searchForm" inline>
<FormItem label="关键词">
<Input v-model="searchForm.keyword" placeholder="请输入标题" clearable />
</FormItem>
<FormItem label="类型">
<Select v-model="searchForm.type" placeholder="请选择" clearable style="width: 120px">
<Option :value="1">系统公告</Option>
<Option :value="2">活动公告</Option>
</Select>
</FormItem>
<FormItem label="状态">
<Select v-model="searchForm.status" placeholder="请选择" clearable style="width: 120px">
<Option :value="1">启用</Option>
<Option :value="0">禁用</Option>
</Select>
</FormItem>
<FormItem>
<Button type="primary" @click="handleSearch">搜索</Button>
<Button @click="handleReset">重置</Button>
</FormItem>
</Form>
</Card>
<!-- 数据表格 -->
<Card :bordered="false" dis-hover class="ivu-mt">
<Button type="primary" @click="handleAdd">添加公告</Button>
<Table :columns="columns" :data="tableData" :loading="loading" class="ivu-mt">
<template slot-scope="{ row }" slot="type">
<Tag :color="row.type === 1 ? 'blue' : 'green'">
{{ row.type === 1 ? '系统公告' : '活动公告' }}
</Tag>
</template>
<template slot-scope="{ row }" slot="status">
<i-switch
v-model="row.status"
:true-value="1"
:false-value="0"
@on-change="handleStatusChange(row)"
/>
</template>
<template slot-scope="{ row }" slot="action">
<Button type="primary" size="small" @click="handleEdit(row.id)">编辑</Button>
<Button type="error" size="small" @click="handleDelete(row.id)">删除</Button>
</template>
</Table>
<Page
:total="total"
:current="searchForm.page"
:page-size="searchForm.limit"
@on-change="handlePageChange"
show-total
class="ivu-mt"
/>
</Card>
</div>
</template>
<script>
import { getNoticeList, deleteNotice, setNoticeStatus } from '@/api/system'
export default {
name: 'SystemNotice',
data() {
return {
searchForm: {
keyword: '',
type: '',
status: '',
page: 1,
limit: 10
},
tableData: [],
total: 0,
loading: false,
columns: [
{ title: 'ID', key: 'id', width: 80 },
{ title: '标题', key: 'title' },
{ title: '类型', slot: 'type', width: 120 },
{ title: '排序', key: 'sort', width: 80 },
{ title: '状态', slot: 'status', width: 100 },
{ title: '创建时间', key: 'add_time', width: 180 },
{ title: '操作', slot: 'action', width: 180 }
]
}
},
mounted() {
this.getList()
},
methods: {
async getList() {
this.loading = true
try {
const res = await getNoticeList(this.searchForm)
this.tableData = res.data.list
this.total = res.data.count
} finally {
this.loading = false
}
},
handleSearch() {
this.searchForm.page = 1
this.getList()
},
handleReset() {
this.searchForm = {
keyword: '',
type: '',
status: '',
page: 1,
limit: 10
}
this.getList()
},
handlePageChange(page) {
this.searchForm.page = page
this.getList()
},
handleAdd() {
this.$modalForm(createNotice()).then(() => this.getList())
},
handleEdit(id) {
this.$modalForm(editNotice(id)).then(() => this.getList())
},
async handleDelete(id) {
this.$Modal.confirm({
title: '提示',
content: '确定要删除该公告吗?',
onOk: async () => {
await deleteNotice(id)
this.$Message.success('删除成功')
this.getList()
}
})
},
async handleStatusChange(row) {
await setNoticeStatus(row.id, row.status)
this.$Message.success('修改成功')
}
}
}
</script>
使用命令行快速生成
CRMEB PRO 提供了命令行工具快速生成 DAO 和 Services:
# 生成DAO
php think make:dao system/SystemNotice
# 生成Services
php think make:services admin@system/SystemNotice
表单构建器
CRMEB PRO 使用 form-builder 快速构建表单:
use crmeb\services\FormBuilder as Form;
// 文本输入框
Form::input('name', '名称', '默认值')->required();
// 下拉选择
Form::select('type', '类型', 1)->options([
['value' => 1, 'label' => '选项1'],
['value' => 2, 'label' => '选项2'],
]);
// 单选框
Form::radio('status', '状态', 1)->options([
['value' => 1, 'label' => '启用'],
['value' => 0, 'label' => '禁用'],
]);
// 多选框
Form::checkbox('tags', '标签', [1, 2])->options([...]);
// 日期选择
Form::date('date', '日期', '');
// 图片上传
Form::frameImage('image', '图片', Url::buildUrl('admin/widget.images/index', ['fodder' => 'image']));
// 富文本
Form::ueditor('content', '内容', '');
最佳实践
- 分层清晰: Controller → Services → DAO → Model
- 统一响应: 使用
app('json')->success()/app('json')->fail() - 参数验证: 在控制器中验证参数,在服务层处理业务
- 异常处理: 使用 AdminException 抛出业务异常
- 表单构建: 使用 form-builder 构建表单,前端统一渲染
注意事项
- 命名规范统一:数据表、Model、DAO、Services、Controller 的命名要保持一致,便于维护
- 分层职责明确:Controller 只做参数接收和响应返回,业务逻辑全部放在 Services 层
- 菜单权限配置:新增页面后需要在后台管理「系统设置-权限管理-菜单」中添加对应菜单
- 路由权限标识:路由的
unique_auth要与菜单配置的权限标识对应,否则无法访问 - 前端路由同步:后端添加路由后,前端也需要同步添加路由配置和页面文件
- 表单构建器限制:form-builder 适合常规表单,复杂交互的表单建议自定义前端组件
- 数据验证位置:简单验证在控制器使用 Validate,复杂业务验证在 Services 层处理
- 事务使用:涉及多表操作时使用
Db::transaction()包裹,确保数据一致性
常见问题
Q: 新增页面后访问显示 404?
A: 检查以下几点:1) 后端路由是否正确配置;2) 前端路由是否添加;3) 菜单是否配置且有访问权限;4) 控制器和方法是否存在Q: 权限验证一直失败?
A: 确认路由的unique_auth标识与后台菜单配置的「权限标识」完全一致,并且当前角色已分配该菜单权限Q: 表单构建器生成的表单不显示?
A: 检查接口返回的数据格式是否正确,前端 Dialog 组件是否正确接收表单数据Q: 如何在页面中使用分页?
A: 后端在 DAO 层使用getList方法返回分页数据,前端使用el-pagination组件配合@current-change事件处理Q: Services 层如何调用其他模块的方法?
A: 通过app()->make(OtherServices::class)获取其他服务实例,避免循环依赖Q: 命令行生成的文件放在哪里?
A:make:dao生成到app/dao/目录,make:services根据参数前缀生成到对应控制器类型的 services 目录
评论({{cateWiki.comment_num}})
最新
最早
{{cateWiki.page_view_num}}人看过该文档
评论(0)
最新
最早
230人看过
登录/注册
即可发表评论
{{item.user ? item.user.nickname : ''}}
(自评)
{{item.content}}
搜索结果
为您找到{{wikiCount}}条结果
{{item.page_view_num}}
{{item.like ? item.like.like_num : 0}}
{{item.comment ? item.comment.comment_num : 0}}
位置:
{{path.name}}
{{(i+1) == getCataloguePathData(item.catalogue).length ? '':'/'}}
