fastadmin/thinkPHP5.0的框架使用注意事项

0.主要链接

一张图解析表格

  1. 数据表规划一定要做好,省的做的时候很乱,一会要改一下,就特别麻烦

  2. 在线命令生成crud的时候一定不要填写自定义控制器名,要让他自己生成,否则后面你要修改东西还需要再找.默认的永远能知道在哪里

  3. 在线命令生成的时候,可以试着删除一下(不会成功),但是能看到他实际上修改了那些文件,你修改内容的时候就知道应该去哪个文件里面去修改了

  4. 数据表名称,建议都用fa开头然后一级菜单,然后表的语义名称,比如 fa_dataproduct,后面就完全连接起来的小写,这样自动生成也能生成成功,要不然老是会有问题

  5. crud的时候关联外键只关联一对一的值,其他值如果显示数据量太大,同时也没法实现

  6. 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,则转发到Selectpageif ($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_idif (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 stubif ($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);},

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/346849.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Shell脚本01

一、shell脚本 脚本就是可运行的代码的集合&#xff0c;脚本语言&#xff08;计算机语言&#xff09;。 脚本的特点&#xff1a;从上到下&#xff0c;按行执行。 shell 脚本就是在shell环境&#xff08;bin/bash&#xff09;bash就是shell解释器&#xff0c;linux环境下的编…

重邮计算机网络803-(1)概述

目录 一.计算机网络向用户提供的最重要的功能 二.互联网概述 1.网络的网络 2.计算机网络的概念 3. 互联网发展的三个阶段 4.制订互联网的正式标准要经过以下的四个阶段 5.互联网的组成&#xff08;功能&#xff09; 6.互联网功能 7.互联网的组成&#xff08;物理&…

物联网TCP、UDP、CoAP、LwM2M、MQTT协议简单对比

一、前言 目前物联网行业有TCP、UDP、CoAP、LwM2M、MQTT、Modbus系列、JT808、HTTP、TLINK、ISAPI等协议&#xff0c;本文先对其中的几款协议进行介绍。具体关系见下图&#xff1a; 传输层协议&#xff1a;TCP、UDP&#xff1b;应用层协议&#xff1a;CoAP、LwM2M、MQTT、Modbu…

Go微服务: 关于消息队列的选择和分类以及使用场景

消息队列概述 在分布式系统和微服务架构中&#xff0c;消息队列&#xff08;Message Queue&#xff09;是一个核心组件&#xff0c;用于在不同的应用程序或服务之间异步传递消息在 Go 语言中&#xff0c;有多种实现消息队列的方式&#xff0c;包括使用开源的消息队列服务&…

OSI七层网络参考模型

一、物理层 我们要发送出去的数据在计算机里只不过是无数的0和1&#xff0c;0或1就叫做比特&#xff0c;物理层就是把这些比特用不同的媒介传输出去&#xff0c;可以用电、光或者其他形式的电磁波来表示和传输信号&#xff0c;数据从网络接口出去以后&#xff0c;会经过不同的网…

一文带你入门 - Qt绘图QPainter

QPaintEvent绘图事件: QPaintEvent 是 Qt 框架中一个重要的事件类&#xff0c;专门用于处理绘图事件。当 Qt 视图组件需要重绘自己的一部分时&#xff0c;就会产生 QPaintEvent 事件。这通常发生在以下几种情况&#xff1a; 1. 窗口第一次显示时&#xff1a;当窗口或控件第一次…

计算机组成原理(二)

ACC&#xff08;累加器&#xff09;&#xff1a; 用于存储高位部分 MQ&#xff08;乘数-商寄存器&#xff09;&#xff1a; 用于存储低位部分。在除法中保存商&#xff0c;在乘法中保存乘数&#xff0c;所以也叫乘商寄存器 左移 8 位&#xff08;相当于乘以 256&#xff09…

AI产品经理的转行之路,如何迈向年薪80w的职业高峰?

前言 在当今科技日新月异的时代&#xff0c;AI产品经理作为一个炙手可热的职业&#xff0c;吸引了众多向往高薪与前沿领域结合的求职者的目光。年薪80万的诱惑力无疑是巨大的&#xff0c;但不少自学中的朋友发现&#xff0c;即便涉猎广泛的产品知识&#xff0c;想要顺利转型成…

掌握Python的全方位教程,2024年最新版本,初学者必备指南

哈喽&#xff0c;大家好&#xff01;热烈欢迎你迈出成为python开发者的第一步。我想这一定非常激动人心&#xff0c;对吧&#xff1f;无论你是刚刚开始学习编程&#xff0c;还是曾经用过其他语言有一定的编程经验&#xff0c;本书中课程将帮助你加速实现你学习python的目标。作…

2024第十六届亚洲水技术展览会Aquatech China

Aquatech China 2024第十六届亚洲水技术展览会 专注水行业覆盖全领域—荷兰阿姆斯特丹水展中国展 2024.12.11-13 上海新国际博览中心 展会背景 Aquatech品牌创立于1968年。作为水处理行业历史悠久 的展览会&#xff0c;荷兰国际水处理展览会(Aquatech Amsterdam)至今已有近55…

物联网8大协议介绍及对比

一.物联网主流协议介绍 1.MQTT 协议 MQTT&#xff08;Message Queuing Telemetry Transport&#xff09;即消息队列遥测传输。 MQTT 协议最初是在 1999 年由 IBM 公司开发的&#xff0c;用于将石油管道上的传感器与卫星相连接。2014 年正式成为 OASIS 开放标准。 MQTT 使用…

车圈内卷的真相:技术创新与长期主义的存亡之战

引言 随着中国汽车市场的不断发展&#xff0c;行业竞争也日趋激烈。近期&#xff0c;在2024年6月6日举行的中国汽车重庆论坛上&#xff0c;多位汽车界大佬就“内卷”问题展开了激烈讨论。本文将详细分析这些讨论内容&#xff0c;揭示汽车行业内卷的真实情况及其背后的深层次原…

怎么选海外仓操作管理系统才能满足amazon电商需求?考虑好这些,做好FBA并不难

对于跨境电商领域来说&#xff0c;amazon一定是绕不过去的一个平台。不过想做好这个平台的业务并不容易&#xff0c;一方面是现在竞争确实越来越大&#xff0c;另一个是现在电商平台对海外仓业务水平的要求也越来越高。 尤其是对一些中小型的海外仓来说&#xff0c;如何高效、…

Autoware 定位之EKF 滤波定位(四)

Tip: 如果你在进行深度学习、自动驾驶、模型推理、微调或AI绘画出图等任务&#xff0c;并且需要GPU资源&#xff0c;可以考虑使用UCloud云计算旗下的Compshare的GPU算力云平台。他们提供高性价比的4090 GPU&#xff0c;按时收费每卡2.6元&#xff0c;月卡只需要1.7元每小时&…

新火种AI|摊上事儿了!13名OpenAI与谷歌员工联合发声:AI失控可能导致人类灭绝...

作者&#xff1a;小岩 编辑&#xff1a;彩云 2024年&#xff0c;OpenAI的CEO Sam Altman就没有清闲过&#xff0c;他似乎一直走在解决麻烦的路上。最近&#xff0c;他的麻烦又来了。 当地时间6月4日&#xff0c;13位来自OpenAI和Google Deep Mind的现任及前任员工联合发布了…

几首音乐怎么合成一首?值得推荐的四个几首音乐合成一首的方法

几首音乐怎么合成一首&#xff1f;合成几首音乐成一首新的作品是一项创造性而充满挑战的任务。通过将不同的音乐元素融合在一起&#xff0c;可以创造出独特的音乐体验&#xff0c;展示多样化的音乐风格和个性。将多首音乐合成一首可以创造出独特的音乐体验&#xff0c;融合不同…

PyTorch 维度变换-Tensor基本操作

以如下 tensor a 为例&#xff0c;展示常用的维度变换操作 >>> a torch.rand(4,3,28,28) >>> a.shape torch.Size([4, 3, 28, 28])view / reshape 两者功能完全相同: a.view(shape) >>> a.view(4,3,28*28) ## a.view(4,3,28,28) 可恢复squeeze…

二叉树左右树交换

leetcode 226题 翻转二叉树 题目描述 给你一棵二叉树的根节点 root &#xff0c;翻转这棵二叉树&#xff0c;并返回其根节点。 示例 1&#xff1a; 输入&#xff1a;root [4,2,7,1,3,6,9] 输出&#xff1a;[4,7,2,9,6,3,1]示例 2&#xff1a; 输入&#xff1a;root [2,1,3]…

计划任务 之 一次性的计划任务

计划任务 作用:定时自动完成特定的工作 计划任务的分类&#xff1a; &#xff08;1&#xff09;一次性的计划任务 例如下周三对系统的重要文件备份一次 &#xff08;2&#xff09;周期性重复计划任务 例如每天晚上12&#xff1a;00备份一次 一次性的任务计划&#xff1a…

linux的du命令简介

文章目录 linux的du命令简介du命令详解查看某个目录下 文件个数 linux的du命令简介 du命令是linux系统里的文件大小查看的命令。 du命令的应用场景十分广泛&#xff1a; 需要查看单个目录里面多个文件总大小。 需要查看目录中每个文件的大小以及每个子文件夹中文件的大小。 查…