0.主要链接
-
数据表规划一定要做好,省的做的时候很乱,一会要改一下,就特别麻烦
-
在线命令生成crud的时候一定不要填写自定义控制器名,要让他自己生成,否则后面你要修改东西还需要再找.默认的永远能知道在哪里
-
在线命令生成的时候,可以试着删除一下(不会成功),但是能看到他实际上修改了那些文件,你修改内容的时候就知道应该去哪个文件里面去修改了
-
数据表名称,建议都用fa开头然后一级菜单,然后表的语义名称,比如 fa_dataproduct,后面就完全连接起来的小写,这样自动生成也能生成成功,要不然老是会有问题
-
crud的时候关联外键只关联一对一的值,其他值如果显示数据量太大,同时也没法实现
-
thinkphp 外键关联,belongsTo 或者其他的,使用with查询时,要注意查询时传入的参数与model里面的方法名字一致
`class ConversationModel extends Model
{protected $table = ‘fa_conversation’;
public function toUser()
{
return $this->belongsTo(‘UserModel’, ‘to_id’, ‘id’)->field(‘id,mobile,nickname,avatar’);
}
}
/**
*
*查询当前用户所有的聊天会话列表
*/
public function list()
{
$this->success('查询成功',
ConversationModel::with('toUser')->where('from_id', $this->auth->getUser()->id)
->order('updatetime desc')
->limit(200)->select());
}
`
6. 专家和厂家都是用户的不同组,另外一个表bind里面同时有expert_id和enterprise_id,这种复杂显示
数据查询的时候我们要把专家和厂家都关联到,但是crud 只能关联一次user表,所以需要我们另外添加一个关联查询,with里面多加了一个expert关联
public function index()
{
//当前是否为关联查询
$this->relationSearch = true;
//设置过滤方法
$this->request->filter(['strip_tags', 'trim']);
if ($this->request->isAjax()) {
//如果发送的来源是Selectpage,则转发到Selectpage
if ($this->request->request('keyField')) {
return $this->selectpage();
}
list($where, $sort, $order, $offset, $limit) = $this->buildparams();
$list = $this->model
->with(['user','expert'])
->where($where)
->order($sort, $order)
->paginate($limit);
foreach ($list as $row) {
$row->getRelation('user')->visible(['mobile', 'nickname']);
$row->getRelation('expert')->visible(['mobile', 'nickname']);
}
$result = array("total" => $list->total(), "rows" => $list->items());
return json($result);
}
return $this->view->fetch();
}
这个关联要求在model里面添加一个方法,如下,user是自动生成的,expert我们也照着写一个
public function user()
{
return $this->belongsTo('User', 'enterprise_id', 'id', [], 'LEFT')->setEagerlyType(0);
}
public function expert()
{
return $this->belongsTo('User', 'expert_id', 'id', [], 'LEFT')->setEagerlyType(0);
}
到此,查出的数据已经满足了
然后列表的字段如下:field里面添加上我们自己跌的expert.mobil和expert.nickname,当然在lang文件夹下面把多语言适配的部分加上去
// 初始化表格
table.bootstrapTable({
url: $.fn.bootstrapTable.defaults.extend.index_url,
pk: 'id',
sortName: 'id',
columns: [
[
{checkbox: true},
{field: 'id', title: __('Id')},
{field: 'user.mobile', title: __('User.mobile'), operate: 'LIKE'},
{field: 'user.nickname', title: __('User.nickname'), operate: 'LIKE'},
{field: 'expert.mobile', title: __('Expert.mobile')},
{field: 'expert.nickname', title: __('Expert.nickname')},
{field: 'operate', title: __('Operate'), table: table, events: Table.api.events.operate, formatter: Table.api.formatter.operate}
]
]
});
这样列表显示就没有问题了
然后需要让添加和编辑页面也能用,先说添加,首先改装一下添加页面,然后再controller里面自己定义一个方法,给这个页面里面的用户选择弹出框和专家选择弹出框,返回专门的数据即可
add.html中改装如下:
<form id="add-form" class="form-horizontal" role="form" data-toggle="validator" method="POST" action="">
<div class="form-group">
<label class="control-label col-xs-12 col-sm-2">{:__('Enterprise_id')}:</label>
<div class="col-xs-12 col-sm-8">
<input id="c-enterprise_id" data-rule="required" data-source="bind/enterprises" data-show-field="name" class="form-control selectpage" name="row[enterprise_id]" type="text" value="">
</div>
</div>
<div class="form-group">
<label class="control-label col-xs-12 col-sm-2">{:__('Expert_id')}:</label>
<div class="col-xs-12 col-sm-8">
<input id="c-expert_id" data-rule="required" data-source="bind/experts" data-show-field="name" class="form-control selectpage" name="row[expert_id]" type="text" value="">
</div>
</div>
<div class="form-group layer-footer">
<label class="control-label col-xs-12 col-sm-2"></label>
<div class="col-xs-12 col-sm-8">
<button type="submit" class="btn btn-primary btn-embossed disabled">{:__('OK')}</button>
<button type="reset" class="btn btn-default btn-embossed">{:__('Reset')}</button>
</div>
</div>
</form>
这个时候发现add页面中获取专家和企业的列表时,他是在data-sourse这个字段中指定请求的接口的.所以我们改成自己的接口,然后controller里面添加一个方法,对应这个接口,返回我们需要的数据,代码如下,这样多久得到了我们想要的数据了,注意一下上面的data-show-field指定的字段,我们的接口中必须存在.这里是用concat连接出来的一个值
function experts(int $pageNumber, int $pageSize,$q_word=[]): Json
{
$list = User::field(['id','concat(nickname,"|",mobile)'=>'name'])->where('nickname|mobile','like','%'.$q_word[0].'%')->where('group_id', 2)->paginate($pageSize,false,['page'=>$pageNumber]);
$result = array("total" => $list->total(), "rows" => $list->items());
return \json($result);
}
function enterprises(int $pageNumber, int $pageSize,$q_word=[]): Json
{
$list = User::field(['id','concat(nickname,"|",mobile)'=>'name'])->where('nickname|mobile','like','%'.$q_word[0].'%')->where('group_id',4 )->paginate($pageSize,false,['page'=>$pageNumber]);
$result = array("total" => $list->total(), "rows" => $list->items());
return \json($result);
}
同理,再编辑页面也用同样的方法改装一下,打开页面请求的时候报错,不要慌,看看他请求的接口,我们不存在所以需要加上,同时他的参数我们可以看到主要的是searchKey searchValue KeyField keyValue,而且其实这两个请求都是请求的同一个数据表user,所以我们实际只需要定义一个接口就可以满足他的请求了.
在bind里面我们定一个一个方法如下发:当然在edit.html里面data-sourse绑定的接口也换成我们定义的bind/user
function user(int $keyValue): Json
{
$row = User::field(['id','concat(nickname,"|",mobile)'=>'name'])->find($keyValue);
return \json($row);
}
但是这个返回是否正确?不一定,我们不知道前端需要什么数据,写了先试试,不合适再改
对比官方的返回数据为这样的,我们也改装成这样的给他返回就可以了,我的修改过的接口如下
function user(int $keyValue): array
{
$list = User::field(['id', 'concat(nickname,"|",mobile)' => 'name'])->where('id', $keyValue)->limit(1)->select();
return ['list' => $list, 'total' => 1];
}
,结果如下,完全可以了
1.hasOne和belongsTo
这两种方法都可以应用在一对一关联上,但是他们也是有区别的:
hasOne(‘关联模型’,‘外键’,‘主键’);
belongsTo(‘关联模型’,‘外键’,‘关联主键’);
最主要的区别就在于:谁是主,谁是从:
比如有A和B两张表
A表字段:id name B_id
B表字段:id name
这样A表有B表的外键字段B_id,当在A表所对应的模型就应该用belongsTo去关联B表,A表就是从属于B。反之B表则用hasOne ,B为主,里面有一个A
2.Backend比较好用的几个东西,非常爽
/**
* 快速搜索时执行查找的字段
* 不同的子类里面要搜索不同的数据,就可以修改这个值,比如改为id,name,mobile就可以同时搜索这三个
*/
protected $searchFields = 'id';
/**
* 是否是关联查询
*/
protected $relationSearch = false;
/**
* 是否开启数据限制
* 支持auth/personal
* 表示按权限判断/仅限个人
* 默认为禁用,若启用请务必保证表中存在admin_id字段
*/
protected $dataLimit = true;
/**
* 数据限制字段
* my 数据限制字段为jigou_id这样开启之后所有的用户就只能看到自己机构的数据,而不是全部数据,加上这个之后所有的子类都会有这个规则,这就很牛了.当然如果你不想让某一个表里面用这个规则,可以重新这个字段,甚至把$dataLimit=false关闭这个功能
*/
protected $dataLimitField = 'jigou_id';
3.table里面的列表查询的时候filter筛选某一类
queryParams是请求之前的回调,可在这修改请求的参数,这样每一个请求都会加上这个参数,当然其他参数也可以在这里加上
table.bootstrapTable({
url: $.fn.bootstrapTable.defaults.extend.index_url,
pk: 'id',
sortName: 'user.id',
search:false,
queryParams: (params) => {
console.log(params);//打印出来看到底有些啥参数呢
params.filter = JSON.parse(params.filter);
params.filter.group_id=4;
params.filter=JSON.stringify(params.filter);
return params;
},
...
}
4. 自定义selectpage请求的参数
给selectpage的input加上一个特殊的data-params=‘{“custom[group_id]”:“1”}’ ,这样selectpage请求数据列表的时候就会把这个过滤条件加上,只请求到对应的数据,而不是全部数据
<div class="form-group">
<label class="control-label col-xs-12 col-sm-2">{:__('User_id')}:</label>
<div class="col-xs-12 col-sm-8">
<input id="c-user_id" data-rule="required" data-source="user/user/index" data-field="nickname" data-params='{"custom[group_id]":"1"}' class="form-control selectpage" name="row[user_id]" type="text" value="">
</div>
</div>
5.时间最好都用bigint否则需要配置如下
在对应的model里面添加
protected $dateFormat = 'Y-m-d H:i:s';
// 自动写入时间戳字段
protected $autoWriteTimestamp = 'datetime';
6. 菜单一键生成做了啥,权限怎么控制的?
菜单一键生成是根据controller里面的public方法生成的auth_rule(记录就保存在这个表里面),每一个public方法都对应一个rule,然后这个方法的权限就可以给对应的用户组配置,只用经过这个权限检查的方法才能被对应的用户调用,否则权限不足
其中方法的注释内容就是权限里的名称,当然可以自己去auth_rule表里面修改名称
同时如果自己手写的文件,也可以通过菜单生成来实现权限控制
7.FormBuilder表单生成器
用php代码可直接渲染form表单,说明如下
一张图解析FastAdmin中的FormBuilder表单生成器
所以,我们可以在接口中先得到自己想要渲染的数据,然后$this->assign一下,绑定到view中,然后再使用php的Form::xxx方法去渲染即可
8.自定义接口关联view页面和js文件
自定义一个接口比如 /bind/my
一般来说bind就对应一张表,也是一个controller,里面必然有index,add,edit等方法(可能在父类中),这是框架提供的,但如果我们自己定义一个方法呢?
- 首先自己定义一个my方法
- 然后对应的view里面需要添加my.html
- 第3.在asset/js里面的bind.js里面要添加一个my:function(){
Form.api.bindevent($(“form[role=form]”));},这里面的bindevnet不一定要用form的,也可以是其他的,根据自己自定义页面元素而定 最后你需要一键生成菜单,让他读出你的方法,这样就可以使用权限控制了,参考 第6条菜单一键生成做了啥,权限怎么控制的? - 最后,如果my.html页面是一个form表单,那么就需要实现my方法的post请求,直接提交数据,省的再开发一个接口了
9. table中改变字段的值,button改变url值
在对应的js文件中,找到column,在这里面修改,例如
columns: [
[
{checkbox: true},
{field: 'id', title: __('Id')},
{field: 'admin_id', title: __('Admin_id')},
{field: 'clazz_id', title: __('Clazz_id')},
{field: 'createtime', title: __('Createtime'), operate:'RANGE', addclass:'datetimerange', autocomplete:false},
{field: 'hours', title: __('Hours'), operate:'BETWEEN'},
{field: 'jigou_id', title: __('Jigou_id')},
{field: 'note', title: __('Note'), operate: 'LIKE'},
{field: 'clazz.name', title: __('Clazz.name'), operate: 'LIKE'},
{field: 'admin.nickname', title: __('Admin.nickname'), operate: 'LIKE'},
{field: 'operate', title: __('Operate'), table: table, events: Table.api.events.operate, formatter: Table.api.formatter.operate,
buttons: [
{
name: 'mybutton',
text:(row)=>row.student_ids,
icon: 'fa fa-check',
classname: 'btn btn-xs btn-success btn-dialog',
url: 'attend/mybutton?id={id}',//这里可以直接嵌入字段内容
visible: function (row, index) {
return true;
},
extend: 'data-toggle="tooltip"'
}
]
}
]
]
});
其中buttons下面的text就可以控制显示的文字,icon控制图标,等等,不一而足
10. icon 支持https://fontawesome.dashgame.com/ 所有图标
11. 表格怎么强制筛选
index_url: ‘course/index/type/1’ + location.search,这里面的type 1就会传入到请求的参数中,后端需要根据这个type进行返回对应的数据即可
// 初始化表格参数配置
Table.api.init({
extend: {
index_url: 'course/index/type/1' + location.search,
add_url: 'course/add',
edit_url: 'course/edit',
del_url: 'course/del',
multi_url: 'course/multi',
import_url: 'course/import',
table: 'course',
}
});
更高级的方法,filter其实是最好的实现方式,filter的优点是只需要改前端,后端不需要改(如果是已有的列里面筛选,有其他高级条件的话还是需要改后端的)
示例如下:
queryParams里面我们获取到filter和op,强行改变他的值.这样就能保证获取到的数据一定是我们筛选过的了,这个方法只需要改前端,不需要改后端,实在是上上之选啊
// 初始化表格
table.bootstrapTable({
url: $.fn.bootstrapTable.defaults.extend.index_url,
pk: 'id',
sortName: 'id',
queryParams: (params) => {
let filter = JSON.parse(params.filter);
filter.des = '机器';
params.filter = JSON.stringify(filter);
let op = JSON.parse(params.op);
op.des = 'LIKE';
params.op = JSON.stringify(op);
return params;
},
12.★★★★★关联信息查询最佳实现
一张表一个controller,一个js文件,多个view,controller里面多个方法,对应js里面的每个方法,同时对应各个view
如果主表格里面有多个其他记录,就增加按钮,点击打开查看关联列表,
关联列表里面可以关联增加其中关联的列表和关联增加页面需要controller和js要对应提供方法,view要对应增加
这样不管是多对多还是什么,都可以适配,自由度也比价高
fastadmin对于一些简单的关联查询还是可以的.稍微高级一点的就比较捉急了,而且如果引用了别的界面上的js或者view,当那个界面发生调整的时候就需要适配我这边,问题无群无尽.
可以有比较偷懒的办法就是多对多关联的时候可以生成中间表的crud.然后把他生成的内容复制到对应的地方就可以了.能省点功夫.ok,这确实是最佳实现方案,其他的方案都太辣鸡了
如果有特殊情况,比如关联查询的信息比较多.使用fastadmin或者thinkphp并不能解决的情况下,自己创建一个接口,单独实现,原有的index接口不要动人家的
综上所述:
1.一张表一个controller,一个js文件,有特殊情况需要多个页面的,自己另外定义方法,js文件里面添加controller和js里面添加,同时view和language也要有所修改(一般发生在关联表中,不同主表需要不同的操作)
2.特殊情况自己创建接口,不要占用系统提供的index,edit,add等接口
3.中间变生成的记录往往可以给多个主表使用,js的url中可以加上 /student_id/1这样中间表生成的table就会筛选出对应的学生的记录,而不是显示全部
13 多对多成功查询案列,不知道怎么指定中间表字段
class Student extends Model
{
// 表名
protected $table = 'student';
// 定义和班级的多对多关联关系
public function classes()
{
return $this->belongsToMany(Clazz::class, 'studentclazz', 'student_id', 'clazz_id');//这里可以用field指定student表的字段,但是不能指定中间表字段
}
}
//查询时
$students = Student::with(['classes' => function ($query) {
$query->select('clazz.id', 'clazz.name', 'studentclazz.created_at');
}])->select();
return json_encode($students);
//或者直接
$students = Student::with(['classes'])->select();
// 指定字段
$students = Student::field('id')->with(['classes'])->select();
//关联查询数量
$students = Student::field('id')->withCount(['classes'=>'num'])->select();
14. 一对多成功案列,支持指定字段
class Clazz extends Model
{
// 表名
protected $table = 'clazz';
public function teachers(){
return $this->hasMany(Teacher::class)->field('id,clazz_id');
}
}
//查询
$clazzs= Clazz::with(['teachers'])->select();
15.belongsto成功案例
class Teacher extends Model
{
// 表名
protected $table = 'fa_teacher';
public function clazz()
{
return $this->belongsTo(Clazz::class)->field('id');//id必须有,否则没法关联查询
}
}
//查询
$teachers= Teacher::with(['clazz'])->select();
16.★★★外键id要加索引(数据较多时)
尤其多对多,其实数据量一般会比较大.都应该加索引
一对多可能有时候数据不多,但大部分情况下也应该加索引
17.with查询要排除字段用feild
注意feild一定要放在with语句之前,最好放在最前面,否则有bug,一直报错
18.联合模型查询配置方法
controller里面一定要开启联合查询
js文件中配置field如下
{field: 'branch.name', title: __('分所名'), operate: 'LIKE %...%', placeholder: '请输入'},
19.controller里面关联搜索
关联搜索不开启的情况下where语句或者其他查询语句中很容易出现in where clause is ambiguous问题
这是因为默认不会关联搜索,如果多个表中有两个相同的字段就会混淆,只需要开启关联搜素即可,系统会自动加入表名
class Admin extends Backend
{
protected $relationSearch=true;
......
20.view里面关联选择,然后搜索
在view里面添加一段代码
<script id="categorytpl" type="text/html">
<div class="row">
<div class="col-xs-12">
<div class="form-inline" data-toggle="cxselect" data-selects="group,admin">
<select class="group form-control" name="branch_id" data-url="auth/admin/cxselect?type=branch"></select>
<!--这里data-query-name会自动获取branch_id所在的select的选中值,挺牛的哈哈-->
<select class="admin form-control" name="department_id" data-url="auth/admin/cxselect?type=department"
data-query-name="branch_id"></select>
<!--加入这两个input之后会在提交时自动带上operate选项,非常棒-->
<input type="hidden" class="operate" data-name="branch_id" value="="/>
<input type="hidden" class="operate" data-name="department_id" value="="/>
</div>
</div>
</div>
</script>
在js里面的table配置里面添加一个field,并且指定template为view里面的id="categorytpl"的script内容
field: 'department', title: "分所|部门", searchList: function (column) {
return Template('categorytpl', {});
}, formatter: function (value, row, index) {
return '无';
}, visible: false
},
21.关联查询完整示例
一对多,一对多的关联写入看thinkphp5.1的教程即可
//在主表里面,建立一对多关联关系.注意topet,管理的外键topet_id需要指定,如果正常关联这个外键可以自动生成,外键名称不是标明_id时,就需要特别指定才可以,这个表达方式需要注意
class Msg extends \think\Model
{
protected $table = 'fa_msg';
public function pet()
{
return $this->belongsTo(Pet::class,'pet_id');
}
public function topet()
{
return $this->belongsTo(Pet::class,'topet_id');
}
}
//return $q->withField('id,avatar,name');过滤出需要的字段,用这个方法
$list = \app\api\model\Msg::with(['pet' => function ($q) {
return $q->withField('id,name,avatar');
}])->with(['topet' => function ($q) {
return $q->withField('id,name,avatar');
}])
->where("(pet_id=$pet_id and topet_id=$topet_id) or (pet_id=$topet_id and topet_id=$pet_id)")
->page($page, 20)
->order('id desc')
->select();
注意,withField里面必须有id(关联的主键),否则无法完成关联查询
下面是多对多示例,第一个参数是对应的model,第二个参数是中间表名称(中间表不需要有model类),中间表要求有主表(post)的post_id,还要有次表的tag_id,这种标准命名方法,后面的参数就可以省略掉,
class Post extends \think\Model
{
protected $table = 'fa_post';
public function tags()
{
return $this->belongsToMany(Tag::class, 'posttag');
}
}
//多对多查询时,with直接查询即可,不需要特殊处理了
$list = \app\api\model\Post::
with(['tags'])
->order('likenum desc')
->page($page, 20)
->select();
关联查询时,子表中条件查询,直接在子查询里面增加条件
//user的model里面有如下多对多关联
public function offices()
{
return $this->belongsToMany(Office::class,'useroffice');
}
//查询时,我们对中间表的条件筛选放在with预载入的子查询里面即可
\app\api\model\User::with(
['offices' => function ($q) use ($office_id) {
return $q->where('office_id', $office_id ? '=' : '>', $office_id);
}])
->select();
22.对于时间字段要用bigint,并且createtime,updatetime,自动管理
23 开发的时候直接上服务器
开发一点就提交一点,要不然一旦服务器上有啥问题,很难找到,最后会特别麻烦,即使配置的环境完全一致,还是很难找到问题,
24 用户表分两个用户组,要分别看不同的组数据,表格和菜单都用两套
controller复制一份改名字,内容不变
view复制一份改名字,根据需求显示字段
js复制一份改名字,根据需要改字段
js里面添加内容(修改queryParams的过滤条件即可)
// 初始化表格
table.bootstrapTable({
url: $.fn.bootstrapTable.defaults.extend.index_url,
pk: 'id',
sortName: 'user.id',
queryParams: function (params) {
let filter = JSON.parse(params.filter);
filter.group_id = 2;
params.filter = JSON.stringify(filter);
let op = JSON.parse(params.op);
op.group_id = '=';
params.op = JSON.stringify(op);
return params;
}
.....
25.自己新建菜单注意事项
首先如果是一个页面,就选择菜单,如果是菜单里面的子功能,比如添加,删除,就选择不是菜单,这样下面不需要选择tab页或者dialog等
如果选择菜单,显示在tab页就要选择tab页
另外重要的一点是:写规则路径时,前面没有/,比如user/doctor/index,有/就错了
26.新建数据表的时候,尽量把不同功能的表都单独放,不要聚合使用,比如收藏和点赞,逻辑基本完全一致,但是不同的功能,这种就不要放一起,后台管理系统也容易做,一行代码生成即可,否则后期改起来后患无群啊,除非不得已.否则坚决不能混在一张表里面,这次吃了大亏了
27.对于判断是与否的问题,比如评论表里面,条目可能是评论也可能是回复,这种情况下,不要更加他的父id是否存在来判断,应该多加一个字段,表示是否为回复,更加直观,逻辑更加清楚,数据量虽然增加了,但是逻辑不会搞混乱.否则可能后续功能升级的时候,逻辑会越来越乱,最后完全理不清楚了.宁可增加数据库的体积也不要把思路搞混乱了.
逻辑应该越简单越好,不要为了节约体积而导致逻辑的复杂性
28.同一个model/数据表生成多个菜单
同一个表可以生成多个curd菜单,只需要改一下自定义控制器名称即可,适合于同一个表在不同逻辑使用的情况,
同时可以选择列表中显示的字段
可以设置忽略的字段,忽略后在edit和add页面也不会显示这个字段,非常的方便
add和edit里面如果要隐藏一个字段,并且给他设置默认值,直接把生成的那个字段项注释掉,自己写一个同样name的input,设置value为默认值即可,太牛叉了
29.前期开发的时候数据可以直接同步到线上
但是上线之后只能同步数据结构,任何数据都不要同步,而是手动在线上再操作一次,否则一旦造成问题,那是灾难性的.实际上控制权限时,应该给程序员的数据表权限设置的小一点.只有管理者才能做这个同步数据库结构的操作.
30.自己写的弹框,提交之后如何自动关闭窗口
如下自己写了几个按钮,点击后打开弹框,但是提交后不会自动关闭和刷新
{
field: 'buttons',
width: "120px",
title: __('按钮组'),
table: table,
events: Table.api.events.operate,
buttons: [
{
name: 'reducehours',
text: __('课时扣减'),
title: __('课时扣减'),
classname: 'btn btn-xs btn-primary btn-dialog',
icon: 'fa fa-list',
url: 'student/reducehours',
callback: function (data) {
//关闭弹窗
Layer.alert("接收到回传数据:" + JSON.stringify(data), {title: "回传数据"});
}
},
{
name: 'addhours',
text: __('课时增加'),
title: __('课时增加'),
classname: 'btn btn-xs btn-primary btn-dialog',
icon: 'fa fa-list',
url: 'student/addhours',
callback: function (data) {
Layer.alert("接收到回传数据:" + JSON.stringify(data), {title: "回传数据"});
}
}, {
name: 'list',
text: __('增减记录'),
title: __('增减记录'),
classname: 'btn btn-xs btn-primary btn-dialog',
icon: 'fa fa-list',
url: 'hourslog/index?student_id={id}'
},
],
formatter: Table.api.formatter.buttons
},
实际上只需要在对应的js文件里面添加如下代码:
在var Controller = { 这个里面添加即可
reducehours:function () {
Controller.api.bindevent();
},
addhours:function () {
Controller.api.bindevent();
},
31.改装添加弹出框
需要实现的功能是:1.添加时需要带上班级(grade_id),否则不允许添加,也就是原来的添加逻辑就变了2.get请求时,需要把本班级的人员都显示查出来,供用户勾选,同时提交时,把勾选的所有学员id上传,要对这些学员的数据做修改
controller里面修改如下
public function add()
{
$grade_id = $this->request->param('grade_id');
if ($grade_id) {
//有班级id,则获取班级下的学生
$stuGrades = \app\admin\model\StuGrade::with('stu')->where('grade_id', $grade_id)->select();
if ($this->request->isGet()) {
$this->view->assign('stuGrades', $stuGrades);
return parent::add();
} else {
//这里需要自己做批量添加上课记录的操作,同时要给每一个学员扣除掉1个课时
$stu_ids = $this->request->param('row.stu_id/a');
Db::startTrans();
$success = 0;
try {
foreach ($stu_ids as $id) {
$data = $this->request->post();
$data['row']['stu_id'] = $id;
\app\admin\model\StudyRecord::create($data['row']);
\app\admin\model\Stu::update(['class_num' => ['dec', 1]], ['id' => $id]);
}
Db::commit();
$success = 1;
} catch (\Exception $e) {
Db::rollback();
$this->error($e->getMessage());
}
$success ? $this->success('添加成功') : $this->error('添加失败');
}
}
$this->error('请在班级管理中添加');
}
在view里面修改如下
关键点在于判断如果存在stuGrades变量,就是有学员列表,把学员列表显示出来(用下拉选择框),否则就显示单个学员的添加功能
{if condition="isset($stuGrades)"}
<div class="form-group">
<label class="control-label col-xs-12 col-sm-2">{:__('学员列表')}:</label>
<div class="col-xs-12 col-sm-8">
<select id="c-type_list" class="form-control selectpicker" multiple="" name="row[stu_id][]">
{foreach name="stuGrades" item="vo"}
<option value="{$vo.stu_id}" {in name="key" value="" }selected{/in}>{$vo.stu.name}</option>
{/foreach}
</select>
</div>
</div>
{else/}
<div class="form-group">
<label class="control-label col-xs-12 col-sm-2">{:__('Stu_id')}:</label>
<div class="col-xs-12 col-sm-8">
<input id="c-stu_id" data-rule="required" data-source="stu/index" class="form-control selectpage"
name="row[stu_id]" type="text" value="">
</div>
</div>
{/if}
最重要的在于js文件中的修改
在add方法下添加的js代码都会在add.html里面进行操作,当然也可以在add.html里面去修改,但是可能会有点乱,就都写在这里了
注意在init里面,我们吧add_url 也加上location.search,这样添加请求的时候,就会把之前带的参数也携带上,这样才能传到controller里面,这一步非常关键
Table.api.init({
extend: {
index_url: 'study_record/index' + location.search,
add_url: 'study_record/add' + location.search,
edit_url: 'study_record/edit',
del_url: 'study_record/del',
multi_url: 'study_record/multi',
import_url: 'study_record/import',
table: 'study_record',
}
});
//其他代码省略...
add: function () {
function getQueryParam(param) {
var searchParams = new URLSearchParams(window.location.search);
return searchParams.get(param);
}
var grade_id = getQueryParam('grade_id');
//在form里面插入一个input,name为grade_id,value为grade_id
if (grade_id) document.getElementById('add-form').append("<input type='hidden' name='grade_id' value='" + grade_id + "'>" );
Controller.api.bindevent();
}
32.弹出框里面只显示当前学员的缴费记录列表,添加时默认选中当前学员id
1.再stu.js里面添加按钮,点击弹出一个列表框
{
field: 'buttons',
width: "120px",
title: __('按钮组'),
table: table,
events: Table.api.events.operate,
buttons: [
{
name: 'charge',
text: __('缴费记录'),
title: __('缴费记录'),
classname: 'btn btn-xs btn-primary btn-dialog',
icon: 'fa fa-list',
url: 'charge/index?stu_id={id}',
}
],
formatter: Table.api.formatter.buttons
}
2.在charge.js里面,修改add 的url,同时对add方法进行修改
在init里面修改addurl
Table.api.init({
extend: {
index_url: 'stu_grade/index' + location.search,
add_url: 'stu_grade/add'+location.search,
edit_url: 'stu_grade/edit',
del_url: 'stu_grade/del',
multi_url: 'stu_grade/multi',
import_url: 'stu_grade/import',
table: 'stu_grade',
}
});
在add方法里面修改,添加自动修改stu_id
add: function () {
function getQueryParam(param) {
var searchParams = new URLSearchParams(window.location.search);
return searchParams.get(param);
}
var id = getQueryParam('stu_id');
if (id) document.getElementById('c-stu_id').value = id;
Controller.api.bindevent();
},
33.tab更换默认选中项
在列表加载前,重新修改类型为目标tab,因为这个js只加载一次,所以只会在第一个次加载时才会选中目标tab
var Controller = {
index: function () {
// 初始化表格参数配置
Table.api.init({
extend: {
index_url: 'stu/index' + location.search,
add_url: 'stu/add'+location.search,
edit_url: 'stu/edit',
del_url: 'stu/del',
multi_url: 'stu/multi',
import_url: 'stu/import',
table: 'stu',
}
});
let urlSearch = new URLSearchParams(location.search);
if( !urlSearch.has('is_has') ) {
urlSearch.set('is_has', '1');
location.search = '?'+urlSearch.toString()
}
var table = $("#table");
...
34. api应用下controller 里面使用validate进行参数验证
重写这个方法
protected function validate($data = [], $validate = '', $message = [], $batch = false, $callback = null)
{
!$data && $data = $this->request->param();
$res = parent::validate($data, $validate, $message, $batch, $callback); // TODO: Change the autogenerated stub
if ($res !== true) $this->error('error', $res,-1);
}
然后我们再自己的controller里面就可以使用这个方法了,直接调用,如果验证不通过,会自动触发参数错误的返回值,并且返回的code为-1,方便前端统一处理
api应用中因为方法很多,又不是所有的方法都需要验证,所以验证器的名字最好是controller名+方法名,这样在需要验证的时候使用即可.
不过我还是觉得这个验证有些费劲,不过准确性比较高,后期看情况用一下吧.
admin里面要多用这个来限制用户提交的数据,更加方便
35.联动下拉选择框的使用
<div class="form-inline col-xs-12 col-sm-8" data-toggle="cxselect" data-selects="articletype_pid,articletype_id">
<select class="articletype_pid form-control" name="row[articletype_pid]" data-url="articletype/list"></select>
<select class="articletype_id form-control" name="row[articletype_id]" data-url="articletype/list" data-query-name="pid"></select>
</div>
需要注意的地方
data-selects=“articletype_pid,articletype_id” 这里的articletype_pid,articletype_id 要与里面的select 的class名字相同才可以,并且不能出现特殊字符
data-query-name=“pid” 这里规定了传入的参数键名
链接返回值必须是json,并且code=1,data里面有name和value,name是显值(比如name字段),value是id值(字段)
例如;
public function list($pid = ''): \think\response\Json
{
if (!$pid) {
$pid = 5607;
}
$list = $this->model
->field('id as value,name')
->where('parent_id', $pid)
->select();
$this->result($list, 0, 'ok', 'json');
}
36.列表里面显示统计数据,比如总金额
在controller里面加入对应字段,然后返回
//返回剩余次数和总次数
$lefttimes = $this->model->where($where)->where('expiretime', '>', time())
->sum('lefttimes');
$total = $this->model->where($where)->where('expiretime', '>', time())
->sum('total');
$result = array("total" => $list->total(), "rows" => $list->items(), 'ext' =>
[
'lefttimes' => $lefttimes,
'total' => $total
]
);
return json($result);
view里面加入对应的界面元素
..省略上面的代码
<a href="javascript:;" class="btn btn-default" style="font-size:14px;color:dodgerblue;">
<span class="extend">
剩余次数:<span id="left">0</span>
总次数:<span id="total_times">0</span>
</span>
</a>
</div>
<table id="table" class="table table-striped table-bordered table-hover table-nowrap"
data-operate-edit="{:$auth->check('recharge/edit')}"
data-operate-del="{:$auth->check('recharge/del')}"
width="100%">
</table>
js文件里面对界面进行渲染
//...之前的代码省略,当表格数据加载完成时
table.on('load-success.bs.table', function (e, data) {
//这里可以获取从服务端获取的JSON数据
console.log(data);
//这里我们手动设置底部的值
$("#left").text(data.ext.lefttimes);
$("#total_times").text(data.ext.total);
});
// 为表格绑定事件
Table.api.bindevent(table);
},