程志辉博客 - 一个伪文艺青年
贵在坚持,难在坚持,成在坚持!
程志辉

day4

分页

$keyword = input('keyword');
$where = [];
if(!empty($keyword)){
    $where['goods_name']=['like','%'.$keyword.'%'];
}

$list = \app\admin\model\Goods::where($where)->order('id desc')->paginate(10,false,['var_page'=>'page','query'=>['keyword'=>$keyword]]);

paginate第一个值当前页多少条数据 第二个true简洁 false全部 第三个值传递参数

不传递参数的话 点击下一页不会带参

<!-- pagination -->
{$list->render()}

<form action="{:url('admin/goods/index')}" method="get" class="form-search">
    <div class="row-fluid" style="text-align: left;">
        <div class="pull-left span4 unstyled">
            <p> 商品名称:<input class="input-medium" name="keyword" value="{$Request.param.keyword}" type="text"></p>
        </div>
    </div>
    <button type="submit" class="btn">查找</button>
    <a class="btn btn-primary" href="{:url('create')}">新增</a>
</form>

action 用{:url{'admin/goods/index'}} 推荐写全

value值{$Request.param.keyword} 这样写搜索后 搜索框值还在

图片上传
<form action="{:url('admin/goods/save')}" method="post" id="tab" enctype="multipart/form-data">
<input type="file" name="logo">

enctype要填写 上传文件name要填写

//文件上传
$params['goods_logo'] =$this->upload_logo();

private function upload_logo(){
    //获取上传的文件
    $file = request()->file('logo');
    //判断是否上传了文件
    if(empty($file)){
        $this->error("没有上传文件");
    }
    //移动图片到指定的目录下 /public/uploads/
    //这里的validate与表单验证的validate没有任何关系
    $info = $file->validate(['size'=>10*1024*1024,'ext'=>'jpg,png,gif,jpeg'])->move(ROOT_PATH.'public'.DS.'uploads');
    if($info){
        //上传成功  /uploads/20190709/fsfs.jpg
        $goods_logo = DS.'uploads'.DS.$info->getSaveName();
        //返回图片路径
        return $goods_logo;
    }else{
        //上传失败
        $error_msg=$file->getError();
        $this->error($error_msg);
    }
}

缩略图

//生成缩略图 \think\Image类 保存
$image =\think\Image::open('.'.$goods_logo);
//生成缩略图 保存图片 /是根 ./是当前目录
$image->thumb(1000,1000)->text('程志辉博客','./ttf/STXINGKA.TTF','30','#ffffff',\Think\Image::WATER_SOUTHEAST,[-30,-50])->save('.'.$goods_logo);

Db类操作数据

//执行原生sql语句 查询  返回数组
// $res = \think\Db::query('select * from tpshop_goods ORDER BY id desc limit 10 ');
$res = \think\Db::query('select * from tpshop_goods where id=:id',['id'=>1]);
dump($res);
//执行原生sql语句 添加  返回影响条数
//$res1 = \think\Db::execute('insert into tpshop_goods (goods_name,goods_price)
// values(:goods_name,:goods_price)'
// ,['goods_name'=>'thinkphp','goods_price'=>"1999"]);
// dump($res1);

//Db类的方法 查询
$res2 = \think\Db::table('tpshop_goods')->where('id','33')->find();
dump($res2);

//        $res3 = \think\Db::table('tpshop_goods')->order('id','desc')->limit(10)->select();
//        dump($res3);
//改
$res4 = \think\Db::table('tpshop_goods')->where('id','108')->update(['goods_name'=>"gouge666"]);
dump($res4);
//删
$res5 = \think\Db::table('tpshop_goods')->where('id','108')->delete();
dump($res5);

路由

路由将用户的请求按照事先规划的方案提交给指定的控制器或者功能函数来进行处理。

Thinkphp5的路由比较灵活,并且不需要强制等你故意,可以总结归纳如下三种方式

普通模式、混合模式、强制模式

强制模式下,系统中的每次请求都不是随意的,必须做相关的设置,该请求才被允许,这样做的好处是系统相对比较安全、路由非常规范、有利于整体协调配置等。

配置方式:开启路由模式(application/config.php)

普通模式

'url_route_on'=>false,     //开启路由设置
'url_route_must'=>false, //必须使用路由

混合模式

'url_route_on'=>true,     //开启路由设置
'url_route_must'=>false, //必须使用路由

强制模式

'url_route_on'=>true,     //开启路由设置'url_route_must'=>true, //必须使用路由

路由定义

Route::rule("路由表达式","路由地址","请求类型","路由参数(数组)","变量规则(数组)");//简化格式Route::get("路由表达式","路由地址","路由参数(数组)","变量规则(数组)");Route::post("路由表达式","路由地址","路由参数(数组)","变量规则(数组)");Route::any("路由表达式","路由地址","路由参数(数组)","变量规则(数组)");

路由表达式:请求地址(比如浏览器地址栏输入的地址)

路由地址:表示路由表达式最终需要路由到的地址(比如模块/控制器/方法),有多重定制方式,常用的是“模块/控制器/方法?额外参数1=值1&额外参数2=值2"形式

请求类型:GET、POST,大写

路由参数:

methods:请求类型

ext:允许的后缀

deny_ext:进制的访问后缀

domain:允许什么样的域名访问有效

https:检测是否是https请求

before_behavior:行为发生前,进行路由检查

after_behavior:行为发生后,要执行的动作

……

变量规则:检查传递的变量是否符合某个(正则)规则,规则可言通过正则定义

Route::rule('login','home/login/login','GET',['ext'=>'html']);访问: http://servername/index.php/login.html路由到: https://servername/index.php/home/login/login Route::rule('login/:id','home/login/login','GET',['ext'=>'html'],['id'=>'\d+']);访问: http://servername/index.php/login/101.html路由到: http://servername/index.php/home/login/login/id/101Route::rule('login/:id/:name','home/login/login','GET',['ext'=>'html'],['id'=>'\d+','name'=>'[a-zA-z]+']);访问: http://servername/index.php/login/101/tom.html路由: http://servername/index.php/home/login/login/id/101/name/tom

注意点:写了路由 默认路径就无法访问 只能用路由地址进行访问

//后台商品新增Route::get('goods_create','admin/goods/create',['ext'=>'html']);//不能简写{:url('create')}        //href="/admin/goods/create.html"//必须写全{:url('admin/goods/create')}        //href="/goods_create.html"

设置路由后 url必须写全 不然无法访问

路由分组

路由分组功能允许把相同前缀的路由定义合并分组,这样可以提高路由匹配的效率,不必每次都去遍历完整地路由规则。

Route::group('分组名称',function(){    //单个路由的定义});//后台商品详情Route::rule('goods_read/:id','admin/goods/read','GET',['ext'=>'html'],['id'=>'\d+']);//后台商品删除Route::get('goods_delete/:id','admin/goods/delete',['ext'=>'html'],['id'=>'\d+']);Route::group('admin',function(){    //后台商品详情    Route::rule('goods_read/:id','admin/goods/read','GET',['ext'=>'html'],['id'=>'\d+']);    //后台商品删除    Route::get('goods_delete/:id','admin/goods/delete',['ext'=>'html'],['id'=>'\d+']);});

路由地址会发生变化 http://www.tpshop.com/admin/goods_read/106.html

分组名+路由名

组合变量

如果路由中的变量,不想使用/:变量名 的方式定义,可以使用<变量名>组合变量

Route::get('item-<name>-<id>','product/detail',[],['name'=>'\w+','id'=>'\d+']);Route::rule('goods_read/:id','admin/goods/read','GET',['ext'=>'html'],['id'=>'\d+']);Route::rule('goods_read_<id>','admin/goods/read','GET',['ext'=>'html'],['id'=>'\d+']);http://www.tpshop.com/admin/goods_read/106.htmlhttp://www.tpshop.com/admin/goods_read_106.html

资源路由

Route::resource('goods','admin/goods');//对应商品的访问地址//列表get请求 http://servername/goods//添加页面    get请求    http://servername/goods/create
域名路由

Thinkphp支持完整域名、子域名和IP部署的路由和绑定功能,同时还可以起到简化URL的作用

要启用域名部署路由功能,首先需要开启

'url_domain_deploy'=>true    //参数 子域名 对应模块Route::domain('admin','admin');http://www.tpshop.com/admin/goods/index.htmlhttp://admin.tpshop.com/goods/index.html

防止表单多次提交

View{:token()}<input type="hidden" name="__token__" value="{$Request.token}" />    Controllersleep(2);  //模拟方便重复点击测试$rule = [    'goods_name|商品名称' => 'require|token',    'goods_price|商品价格' => 'require|float|egt:0',    'goods_number|商品数量' =>'require|integer|egt:0'];

总结

1.分页+查找 paginate方法

2.文件上传 (请求对象的file方法、文件对象的validate方法、move方法、getSaveName方法)

3.缩略图生成(thinkImage::open()->thumb()->save())

4.了解Db方式操作数据库(执行原生sql等)

5.Composer基本使用

6.注册路由(路由的基本定义、路由分组、资源路由、域名路由)

7.防止表单重复提交

重点案例:商品列表分页+查找、商品logo图片上传(新增、修改)、缩略图生成、composer基本使用、莲西路与基本内容

2021年10月01日
程志辉

day3

修改操作

//save方法
//先查询再修改
$goods=\app\admin\model\Goods::find(32);
$goods -> goods_price ='123.00';
$goods -> goods_number = 321;
$res1 =$goods->allowField(true)->save();
dump($res1);
//返回一个1  代表修改了一条

//(推荐)静态update方法  修改数据 修改条件 过滤非数据表字段
\app\admin\model\Goods::update(['goods_price'=>'123.00','goods_number'=>321,'id'=>33],[],true);
\app\admin\model\Goods::update(['goods_price'=>'123.00','goods_number'=>321,'id'=>33],['id'=>33],true);
$res2 =\app\admin\model\Goods::update(['goods_price'=>'123.00','goods_number'=>321],['id'=>33],true);
dump($res2);
//返回的是数据库模型对象

//底层Db的update方法 (通过where方法后使用update方法
//通过where方法 返回的是修改条数
//单个修改
$res3 = \app\admin\model\Goods::where('id',34)->update(['goods_price'=>'123.00','goods_number'=>321]);
dump($res3);
//批量修改
$res4 = \app\admin\model\Goods::where('id','>=',53)->update(['goods_price'=>'123.00','goods_number'=>321]);
dump($res4);

区别返回值不同 模型的update方法 和底层update(where)方法区别
1.返回值不同,模型返回模型对象,底层返回修改条数
2.包含的功能不同 模型的update有过滤非数据表字段功能 底层的没有

只有静态方法可以过滤非数据表字段

save方法既可以做添加方法也可以做修改方法,在于传来的值是否有主键ID

手动判断save是否更新

$user= User::get(1);
$user->name = 'thinkphp';
//显示指定当前操作为新增操作
$user->isUpdate(false)->save();

删除操作

先查询,在调用模型delete方法删除

$user = User::get(1);
$user -> delete();

先调用where方法,在调用Query类的delete方法删除

User::where('id',1)->delete();

静态调用 destroy 方法删除(推荐)

User::destroy(1);
User::destroy('1,2,3');
User::destroy([1,2,3]);
User::destroy(['status'=>1]);

例子

//查询并删除(调用模型的delete方法)
//Goods返回的是一个对象
$goods = \app\admin\model\Goods::find('41');
$res =$goods->delete();
dump($res);
dump($goods);

$goods = \app\admin\model\Goods::find('41');
//Call to a member function delete() on a non-objec
//先查询后删除 如果没有查询到数据 会报错  查询到的是一个对象  查询为空会返回NULL
//需要使用empty判断是否为空
if(empty($goods)){
    echo "数据已经不存在了";die;
}
$res =$goods->delete();
dump($res);

//通过where指定条件 再调用Query对象的delete方法
//where返回的是db对象 如果查询没有结果 删除delete的时候 显示int(0) 影响0条
$res =\app\admin\model\Goods::where('id','42')->delete();
dump($res);


//destroy方法     返回int(1)  括号里显示影响条数
$res = \app\admin\model\Goods::destroy('38');
dump($res).("=============");
$res = \app\admin\model\Goods::destroy('39,40');
dump($res).("=============");

$res = \app\admin\model\Goods::destroy([36,47]);
dump($res).("=============");

$res = \app\admin\model\Goods::destroy(['goods_price'=>'0.01']);
dump($res).("=============");

注意点使用delete方法 必须先查询是否有这个数据,如果没有会报错。所以需要多加一层判断

Traits

trait 是一种为类似 PHP 的单继承语言而准备的代码复用机制。trait为了减少单继承语言的限制,使开发人员能够自由的在不同层次结构内独立的类中复用方法集。trait和类组合的语义是定义了一种方式来减少复杂性,避免传统多继承和混入类 ( mixin ) 相关的典型问题

trait A{
    public function getName()
    {
        return "this is trait";
    }
}

class B{
    use A;
}
$b = new B();
echo $b->getName(); //This is trait

只有class可以被实例化 trait不能被实例化,没有实例化方法

注意点有同名的方法,当前类方法会覆盖 trait方法会覆盖继承类的方法

优先级 自己的类方法>trait方法>继承的父类方法

TP中的trait

父类控制器Controller中使用了Jump这个trait,提供了页面跳转相关方法

模型的softDelete这个trait,需要在自定义模型中,手动使用,提供的软删除相关功能

软删除

物理(硬)删除:真删除,从数据表直接删除记录。

逻辑(软)删除:假删除(本质是修改操作),只是让数据在页面不显示,数据表中仍然保留。

原理:在数据表添加一个字段控制数据在页面的展示。点击删除操作时,修改对应字段的值。

use traits\model\SoftDelete;
use SoftDelete;

use \traits\model\SoftDelete;

protected $deleteTime = 'delete_time';

只要加上了上面的内容 默认情况下就是软删除

//删除操作  软删除\app\admin\model\Goods::destroy($id);//真删除\app\admin\model\Goods::destroy($id,true);$goods= \app\admin\model\Goods::find($id);if(empty($goods)){    $this->error("数据已经不存在了");}//软删除$goods->delete();//真删除$goods->delete(true);//默认情况下删除成功跳转到当前页面 也就是列表页$this->success("删除成功");

day3下午

id参数检测

 //id参数检测//大于0的整数if(!is_numeric($id) || $id===((int)$id)  || $id<=0){    $this->error("参数错误");}if(!preg_match('/^\d+$/',$id) || $id==0){    $this->error("参数错误");}

密码加密函数

双重md5加密 外层md5加密加点盐 salt得固定 不然下次生成不一样

if(!function_exists('encrypt_password')){    //密码加密函数    function encrypt_password($string){        //加盐  得固定 php.拼接        $salt = 'gougeniubi';        return md5(md5($string).$salt);    }}

Request-cookie-session

模板中使用

$Request.方法名.参数

{$Request.get.id}{$Request.param.name}
public function test_session(){    //设置    session('username','admin');    dump(session('username'));    dump(session('?username'));    //删除单个    session('username',null);    dump(session('username'));    //删除所有    session(null);    //数组的用法    session('user',['id'=>1,'username'=>'admin']);    dump(session('user.username'));    session('user.email','admin@qqzmly.com');    dump(session('user'));    /*            string(5) "admin"            array(3) {              ["id"] => int(1)              ["username"] => string(5) "admin"              ["email"] => string(12) "admin@qqzmly.com"            }         */    session('user.email',null);    dump(session('user'));    //cookie    cookie('name','zhangsan',3600);    dump(cookie('name'));}

登录功能

//登录public function login(){    //一个方法 处理两个业务逻辑 页面展示 表单提交    if(request()->isPost()){        //表单提交 post请求        //接收参数 查询管理员用户表        //接收参数  username password code        $params = input();        //参数检测 表单验证        $rule = [            'username|用户名' =>'require',            'password|密码' =>'require',            'code|验证码'=>'require'        ];        $res = $this->validate($params,$rule);        if(true!==$res){            $this->error($res);        }        $password = encrypt_password($params['password']);        //第一种写法两个where去查找        //$manager = \app\admin\model\Manager::where('username',$params['username'])        //    ->where('password',$password)->find();        //第二种批量查询        $manager = \app\admin\model\Manager::where(['username'=>$params['username'],'password'=>$password])->find();        //where查询会返回影响条数        if($manager){            //登录成功  设置登录标识到session中            session('manager_info',$manager->toArray());            //页面跳转            $this->success('登录成功','admin/index/index');        }else{            //登录失败            $this->error('用户名或密码错误');        }    }else{        //get请求 页面展示        //临时关闭全局模板布局        $this->view->engine->layout(false);        return view();    }}

退出功能

public function logout(){    //清空session    session(null);    $this->redirect('admin/login/login');}

登录检测功能

新建一个控制器

php think make:controller admin/Base --plain

Base控制器使用构造方法 其他的控制器继承Base Login登录页面不继承

class Base extends Controller{    public function __construct(Request $request = null)    {        //构造方法  实现父类的构造函数        parent::__construct($request);        //登录检测        if(!session('?manager_info')){            //没登录            $this->redirect('admin/login/login');        }    }}

验证码

没有安装THINKPHP自带的captcha就去安装 去项目根目录安装

composer require topthink/think-captcha=1.*        删除composer remove topthink/think-captcha    拓展composer require tp5/captcha
'captcha'  => [       // 验证码字符集合       'codeSet'  => '2345678abcdefhijkmnpqrstuvwxyzABCDEFGHJKLMNPQRTUVWXY',        // 验证码字体大小(px)       'fontSize' => 25,        // 是否画混淆曲线       'useCurve' => true,         // 验证码图片高度       'imageH'   => 30,        // 验证码图片宽度        'imageW'   => 100,         // 验证码位数        'length'   => 5,         // 验证成功后是否重置                'reset'    => true],

点击图片换验证码

 onclick="this.src='{:captcha_src()}?'+Math.random()"

字符串拼接一个随机数 这样避免同一地址资源文件 浏览器有缓存不去加载

验证码显示

<div>{:captcha_img()}</div><div><img src="{:captcha_src()}" alt="captcha" /></div>

控制器验证

使用TP5的内置验证功能,添加captcha验证规则即可

$this->validate($data,[    'captcha|验证码'=>'require|captcha']);

或者手动验证

if(!captcha_check($captcha)){ //验证失败};//手动验证码校验if(!captcha_check($params['code'])){    $this->error('验证码错误');}

总结

1.修改操作以及后台商品修改功能(save方法、saveAll方法、静态update方法)

2.删除操作(delete方法、destroy方法)与软删除功能、trait

3.cookie 和 session 操作 (助手函数)

4.登录模块功能 (登录、退出、登录检测)

5.验证码( captcha_src() captcha_img() captcha_check() )

作业

1.商品修改、商品删除(软删除)、登录、退出、登录检测、验证码

2.管理员模块增删改查

注意点:

管理员列表:不显示密码

管理员添加:不做角色 用户名、密码(加密)、邮箱、昵称等

管理员修改:用户名不能修改 ,可以改密码(重置别人的密码)、邮箱、昵称等

管理员删除:不能删除admin管理员

修改密码 (修改自己的密码)

2021年10月01日
程志辉

day2上午

配置文件database.php

数据库使用前缀

// 数据库表前缀
'prefix'          => 'tpshop_',

使用前缀原因

早期互联网服务器费用非常昂贵,多数租用的主机数据库等服务(虚拟空间),商家给的数据库只有一个。一个数据库里放多个项目,所以就通过后缀来区分项目。(现在不存在这个问题,价格便宜了

自动写入时间戳字段

// 自动写入时间戳字段
'auto_timestamp'  => true,

字段中也需要有 create_time update_time

自动写入创建时间自动写入更新时间
insert()不支持不支持
save()支持支持
update()不支持支持

数据及返回类型

// 数据集返回类型
'resultset_type'  => 'array',        //数组

// 数据集返回类型
'resultset_type'  => 'collection',    //集合

手动实例化模型

$model = new \app\admin\model\Goods();

use app\admin\model\Goods;
$model = new Goods();

//控制器 如果 与模型重名  会引发冲突 
use app\admin\model\Goods as GoodsModel;
$model = new GoodsModel();

查询多条数据

//取到的对象 外层数组里层对象 里层的对象 可以当数组使用
// all 和 select都可以用
$data = \app\admin\model\Goods::select();
$data = \app\admin\model\Goods::select('32,33,34');
$data = \app\admin\model\Goods::select([32,33,34]);

$data = $model->all();

//只能用all 不能用select
$data = \app\admin\model\Goods::all(['status'=>1]);

将返回结果 转化为标准二维数组

默认情况下 返回的结果 外层是数组 里层是对象

foreach ($data as $v){
    //输出商品名称 goods_name
    //dump($v->goods_name);
    dump($v['goods_name']);
}

foreach ($data as &$v){
    //对象转化成数组
    $v = $v->toArray();
}

unset($v);  //$v没用了就销毁


//集合对象   $data是一个数组 数组无法直接用toArray 需要给他转化为一个对象
$data = (new Collection($data)) -> toArray();
dump($data);die;

后缀html

// URL伪静态后缀
'url_html_suffix'        => 'html',

页面渲染

//查询列表数据
$list = \app\admin\model\Goods::select();

//$this->assign('list',$list);
//return view();

return view('good_list',['list'=>$list]);

//变量输出 数组遍历
{foreach name="list" key="k" item="v"}
{foreach $list as $v}
{$v['id']}
{$v.goods_name}
{$v->goods_price}
{$v:goods_number}

//文件地址
/index.php/admin/goods/create.html
//:冒号的意思 与$变量区分开
// 在同一模块 统一控制器 可以省略前面两个 admin/goods
{:url('admin/goods/create')}
{:url('goods/create')}
{:url('create')}

//URL 传参
index.php/admin/goods/edit/id/32.html
{:url('admin/goods/edit',['id'=>$v.id])}

注意点: 模板语言{$name} 不要用html注释<!-- 我是注释 --> 推荐直接删掉

volist标签

{volist name="数组变量名" key="k" id="v" offset="5" length="10" empty="nothing"}
{/volist}

id:当前的循环变量,是一个变量名(foreach使用的是item)

offset:指定从第几个开始遍历,从0开始计数

key:定义循环变量名,默认为i,当前遍历的是第几个,从1开始计数,与原数组下标无关

empty:没有数据时显示的内容

其中,如果没有定义key属性,可以直接输出{4key}变量表示原数组的下标

URL生成

THINKPHP5.0支持路由URL地址的统一生成,并且支持所有的路由方式,以及完美解决了路由地址的反转解析,无需再为路由定义的变化而变化URL生成。URL生成使用thinkUrl::build()方法或者使用系统提供的助手函数url(),参数一致。

Url::build("地址表达式",['参数'],['URL后缀'],['域名'])

url("地址表达式",['参数'],['URL后缀'],['域名'])

默认情况下 URL后缀为true 即有.html

dump(url('admin/test/index'));
dump(url('admin/test/read',['id'=>10,'page'=>20]));
dump(url('admin/test/read',['id'=>10,'page'=>20],false));
dump(url('admin/test/read',['id'=>10,'page'=>20],true,true));
dump(url('admin/test/read',['id'=>10,'page'=>20],true,"www.qqzmly.com"));

/*
string(32) "/index.php/admin/test/index.html"
string(45) "/index.php/admin/test/read/id/10/page/20.html"
string(40) "/index.php/admin/test/read/id/10/page/20"
string(66) "http://www.tpshop.com/index.php/admin/test/read/id/10/page/20.html"
string(66) "http://www.qqzmly.com/index.php/admin/test/read/id/10/page/20.html"
*/

查询一条数据

静态调用get find方法

//取出主键为1的数据 where id =1
$user = \app\admin\model\User::get(1)
$user = \app\admin\model\User::find(1)

//使用数组查询(非主键字段),只能用get不能用find where name = 'thinkphp'
$user = User::get(['name'=>'thinkphp']);

//取出第一条数据,只能用find不能用get get方法必须传参数
$user = User::find();

动态调用get find方法

$user = new User();

//取出主键为1的数据
$info = $user->find(1);
$info = $user->get(1);

注意点:get或者find方法返回的是当前模型的对象实例或者null

返回的模型对象,可以直接当做数组使用,也可以调用toArray(),直接转化为数组。

where方法使用

//where方法的使用
$goods = \app\admin\model\Goods::where('id','=','33')->find();
// where 与 find区别 find只能查询主键  而where可以查询其他字段
$goods = \app\admin\model\Goods::find(33);

注意点: 查询方法有find get select all

在使用where后,紧接着只能用find 和 select方法 因为 get 和 all是模型对象方法,where方法返回的Query对象中没有get 和 all方法

批量条件查询

$goods = $mode->where(
    [
        'goods_price'=>['>','100'],
        'id'=>['=','32']
    ])->select();

$model ->having('id>3')->select();

having与where区别 where是sql条件语句,having是返回结果集后再进行处理数据

连表查询语法

//连表查询语法
        //SELECT t1.*,t2.username FROM `tpshop_address` as t1 LEFT JOIN `tpshop_user` as t2 on t1.user_id = t2.id where t1.id<10;
$data = \app\admin\model\Address::alias('t1')
    ->join('tpshop_user t2','t1.user_id = t2.id','left')
    ->where('t1.id','<','10')
    ->field('t1.*,t2.username')
    ->select();

注意点select all find get一定要放在最后

字段查询

string(14) "Apple iPhone X"
array(5) {
  [0] => string(14) "Apple iphone 5"
  [1] => string(14) "Apple iphone 6"
  [2] => string(14) "Apple iphone 8"
  [3] => string(19) "Apple iphone 6 Plus"
  [4] => string(14) "Apple iphone 3"
}

//字段值查询
$goods_name = \app\admin\model\Goods::where('id','=','33')->value('goods_name');
dump($goods_name);
$goods_names = \app\admin\model\Goods::where('id','>','35')->column('goods_name');
dump($goods_names);


array(5) {
  [36] => string(14) "Apple iphone 5"
  [37] => string(14) "Apple iphone 6"
  [38] => string(14) "Apple iphone 8"
  [39] => string(19) "Apple iphone 6 Plus"
  [40] => string(14) "Apple iphone 3"
}
$goods_names2 = \app\admin\model\Goods::where('id','>','35')->column('goods_name','id');

注意点: field 与 value和column区别,打印sql语句是一样的,只是返回对象value和column是一个数组,field是一个对象,需要自己转换为数组

trace调试

// 应用Trace
'app_trace'              => true,

小结

where方法 连贯操作方法

连表查询

day2下午

条件判断标签if

{if condition="($name==1) OR ($name>100)"}value
{elseif condition="$name eq 2" /}value
{else /}value3
{/if}
//也可以写为以下形式
{if ($name==1)}value1
{/if}

范围判断标签in

范围判断标签包括 in notin between ontbetween四个标签,都用于判断变量中是否在某个范围

{in name="id" value="1,2,3"} id在范围内
{/in}

{in name="id" value="$range"} id在范围内
{/in}

Name属性值为变量名,不带$符号

Value属性值可以使字符串范围,也可以是数组变量或者字符串变量,相当于原生php中的if(in_array())

使用函数

两种语法

{$变量名称|函数名称=arg1,arg2,###}

参数中,可以使用###代替输出的变量本身,如果变量本身是第一个参数,可以省略

//对时间格式化
{$v.goods_create_time|date='Y-m-d H:i:s',###}
//对字符串进行md5加密
{$name|md5}

{:函数名(参数)}

{:date("Y-m-d H:i:s",$v['goods_create_time'])}

系统变量

TP框架自带了一个$Think变量,用于在模板中输出系统变量

$Think.server :相当于$_SERVER
$Think.get     :相当于$_GET
$Think.post     :相当于$_POST

$Think = [
    'get' =>$_GET,
    'post'=>$_POST
];
$this->assign('Think',$Think);

示例:url地址 http://www.tpshop.com/index.php/admin/goods/index?page=1

展示page参数值 $Think.get.page

使用运算符
+    {$a+$b}
-    {$a-$b}
*    {$a*$b}
/    {$a/$b}

添加数据

//save方法
$goods_model = new \app\admin\model\Goods();
$goods_model ->goods_name='huawei';
$goods_model ->goods_price='2999';
$goods_model->save();
dump($goods_model->id);

//create方法
$data = ['goods_name'=>'xiaomi','goods_price'=>'1799'];
$goods = \app\admin\model\Goods::create($data);
dump($goods->id);

//批量添加
$goods_model = new \app\admin\model\Goods();
$data = [
    ['goods_name'=>'xiaomi','goods_price'=>'1799'],
    ['goods_name'=>'xiaomi','goods_price'=>'1799']
];
$res=$goods_model ->saveAll($data);
dump($res);

create 与 saveAll更加方便 直接传一个数组 save需要一个个去定义

知识点

时间戳

//数据库配置文件中添加全局设置
'auto_timestamp' => true,

//在单独的模型类中设置
protected $autoWriteTimestamp = true;

//时间戳默认为整型int类型,如果时间字段不是int  可以自定义
'auto_timestamp' => 'datetime'

protected $autoWriteTimestamp = 'datetime';

页面跳转与重定向

//成功时跳转
$this->success("提示信息","跳转地址","返回数据","等待时间","header信息");
//失败时跳转
$this->error("提示信息","跳转地址","返回数据","等待时间","header信息");

$this->success('操作成功','index');

返回数据指的是给提示页面返回数据

等待时间默认3s,跳转地址可以不写,默认跳转回上一页

重定向跳转,用于直接跳转,不显示提示信息

$this->redirect("跳转地址","请求参数","http code");

页面提示信息 是继承Controller控制器中的Jump

表单验证

 //1.独立验证 定义一个验证规则
 $rule = [
     'goods_name|商品名称' => 'require',
     'goods_price|商品价格' => 'require|float|egt:0',
     'goods_number|商品数量' =>'require|integer|egt:0'
 ];
 //错误提示信息  可选
 $msg =[
     'goods_price.float' => '商品价格必须是整数或者小数'
 ];
 //实例化验证类 validate
 $validate = new \think\Validate($rule,$msg);
 if(!$validate->check($params)){
     //验证失败
     $error_msg = $validate->getError();
     $this->error($error_msg);
 }



//控制器验证
$rule = [
    'goods_name|商品名称' => 'require',
    'goods_price|商品价格' => 'require|float|egt:0',
    'goods_number|商品数量' =>'require|integer|egt:0'
];
$msg =[
    'goods_price.float' => '商品价格必须是整数或者小数'
];
//调用控制器的validate方法
$validate = $this->validate($params,$rule,$msg);
if(true!==$validate){
    $this->error($validate);
}

过滤非数据表字段

例如 goods_name 数据表字段中没有 这个模型方法就会忽略掉这个字段不去处理他

//第二个值为true  过滤非数据表字段
\app\admin\model\Goods::create(['goods_name'=>'tp','goods_price'=>100],true);

//新增数据
$model -> goods_name ='tp';
$model -> goods_price = 200;
$goods = $model->allowField(true)->save();
2021年10月01日
程志辉

day1上午

什么是MVC?

Model View Controller

模型 视图 控制器

Model 模型 处理业务数据 与数据库做交互

View 显示html页面 用户能够看到并与之交互的页面

Controller 接收请求 调用模型处理数据 调用视图显示页面

url访问

示例:http://www.tpshop.com/index.php/index/test/index

格式:http://域名/入口文件/模块名/控制器名称/操作方法名称 .../参数名/参数值

模块 控制器 方法

默认访问模块、开发者模式

// 应用调试模式
'app_debug'              => true,
// 默认模块名
'default_module'         => 'home',

命令行创建模块

1.命令行创建模块目录

php think bulid --module 模块名
例如:
php think build --module home
Successed

2.命令行创建控制器

php think make:controller 模块名/控制器名
php think make:controller 模块名/控制器名 --plain
例如:
php think make:controller home/Test
Controller created successfully.

3.命令行创建模型

php think make:model 模块名/模型名

php think make:model home/User
Model created successfully.

day1下午

Request请求

ThinkPhp5.0 版本默认的变量修饰符是/s(字符串),还可以用其他的

s字符串 d整型 b布尔型 a数组 f浮点型

如果你要获取的数据为数组,请一定注意要加上/a 修饰符 强制转换为array数组 才能正确获取到

// 默认全局过滤方法 用逗号分隔多个        //trim去掉两端空格
'default_filter'         => 'trim',    


变量类型方法('变量名/变量修饰符','默认值','过滤方法')

http://www.tpshop.com/home/index/index/id/heloworld
$request = Request::instance();
$id = $request->param('id/a');
dump($id);

创建Request对象 四种种方法

public function index(Request $request)
    {
//        $request = Request::instance();    //对象方法
//        $request = request();                //函数
        $request = $this->request;            //父类属性
    }

$request = $this->request; 由于当前控制器继承了父类控制器Controller

使用$this->request 必须要当前控制器继承extends Controller

Controller里面有一个$request这个属性 这个属性的值就是一个请求对象

protected $request;
$this->request = is_null($request) ? Request::instance() : $request;

获取请求对象 两种方法

dump($request->param());
echo $request->param('id');
dump(input());
echo input('name');

get param route区别

http://www.tpshop.com/index.php/home/test/index/id/100/name/tp?age=30&sex=nan

$request = request();
$get = $request->get();
$param = $request->param();
$route = $request->route();
dump($get);
dump($param);
dump($route);

array(2) {
  ["age"] => string(2) "30"
  ["sex"] => string(3) "nan"
}
array(4) {
  ["age"] => string(2) "30"
  ["sex"] => string(3) "nan"
  ["id"] => string(3) "100"
  ["name"] => string(2) "tp"
}
array(2) {
  ["id"] => string(3) "100"
  ["name"] => string(2) "tp"
}

get只有?后面的 route只有/部分的 param都有

参数绑定

http://www.tpshop.com/index.php/home/test/read?id=100
public function read($id=0,$name="")
{
    dump($id);
    dump($name);
}

一般推荐只写id 不然写多了 不好看

参数绑定是把URL地址(或者路由地址)中的变量作为操作方法的参数直接传入

依赖注入

简单点说,要在一个类A中使用另一个依赖类B时,不能直接在A中实例化类B,而是先实例化类B再以参数的形式传入类A

<?php

class A{
    public function get_name(){
        return 'A-getname';
    }
}

class B{
    public function get_name(A $a){
//        return 'B-getname';
//        $a = new A();
        $name =$a->get_name();
        return $name;
    }
}

$b = new B();

$obj = new A();

echo $b->get_name($obj);

TP

public function index(Request request){    }

小结

可以找到任何一种方法 接收请求参数

//1.获取请求对象
$request = request();
$request = \think\Request::instance();
$request = $this->request     //仅限于继承了底层控制器的情况下
//依赖注入
public function save(Request $request){}

//2.接收请求参数 param方法
$params = $request->param();
$params = input();
$params = request()->param();
$id = $request->param('id');
$id = input('id');
//参数绑定
public function edit($id){}

视图

全局布局设计,对所有页面全部生效

<?php
//配置文件
return [
    'template'               => [
        'layout_on'     =>true,     //开启全局模块布局
        'layout_name'   =>'layout'  //布局文件名称    默认就是layout可以省略不写
    ],
];

特殊页面(不需要使用布局的页面),可以在控制器方法中,临时关闭模板布局。

$this->view->engine->layout(false)

技巧 默认就是layout可以省略不写

注意点一定不能将所有的css和JS写在layout.html中否则可能导致报错

./static/admin/css/main.css
/static
可以替换为
__STATIC__     //系统变量

./     与    /    区别
./是相对路径    会相对当前的URL进行相对定位请求资源
/是绝对定位    根据入口文件作为根目录来请求资源
框架中html推荐加载文件 统一以/开头

总结

1.安装部署框架,熟悉目录结构(application,public)

2.控制器定义,控制器访问(隐藏入口文件、Path_info格式URL)能够访问到指定的控制器方法

3.命令行操作(创建模块目录,控制器类,模型)(环境变量等)

4.Request请求对象基本使用,接收参数不同写法(熟练使用一种)

5.模板整合(模板的布局)

2021年10月01日
程志辉

day1 说走就走的旅行

大学最快乐的事情就是无忧无虑,随时都可以来一场说走就走的旅行。29号晚上赶 Java 课设赶到转点,还不确定能不能写完就买好了第二天去长沙的票。答辩顺利就直接走,不顺利就退票。第二天课程设计答辩非常顺利,答辩完就回寝室收拾行李奔往火车站。路上还在找晚上住宿的位子,工作日房价普遍比周末便宜一些。

绿皮车对大学生来说真的友好,便宜哇……武汉坐过去几个小时就到了。

为什么去长沙呢?因为七一临近想去感受一下建党节。要么南昌要么长沙,最终选择了长沙。

地铁上都是热烈庆祝中国共产党建党100周年~~~

南门口地铁站出口发现有很多长沙本地市民,上前询问怎么这么多人。他们说建党一百周年,长沙江滩有烟花,很壮观。

说来也巧,问当地居民说是8:30烟花开始,我们下地铁刚好8:20可以赶上,当时外面下着大雨,但还是有很多人,这么壮观肯定少不了我们哈哈。听说还有很多黑科技,来长沙之前都不知道有烟花,真的运气好,后来看微博长沙烟花还上了热搜,我们就在现场。

看完烟花已经很晚了,酒店入住后就近找了一家点吃晚饭。螺蛳粉店,看到很多人就进去了,不过这家店一般般,应该都是看完烟花来吃饭的吧。

day2 橘子洲头看毛爷爷

长沙的米粉好像很出名,楼下就有好几家随便找了一家,大碗嗦粉真的舒服,武汉感觉都是小纸碗端着吃,这边蛮大一碗,还来了一瓶本地的碳酸饮料。

抓紧时间,立即前往橘子洲头~

真的壮观,百年华诞之际能够来到长沙~

橘子洲头个人推荐步行,游览车感觉没必要

祝愿祖国繁荣昌盛,国泰民安~!!

下饭神剧,《守护解放西》。来了长沙肯定要来坡子街派出所~

来碗黑色金典臭豆腐,李同志说很好吃,想再来一碗

day3 湖南博物馆

湖南米粉还是得搞起来,巷子里吃的,应该是本地人吃的店,不过感觉有点贵啊,但味道确实不错,粉里的配料很丰富

预约了湖南博物馆的讲解,时间还很充裕,就去逛了逛IFS网红景点

进去拍照的话得排队,边上拍照打卡就溜了

长沙博物馆主要围绕辛追夫人讲解,推荐去的朋友找一个导游

因为是七一,所以有红色展览

入党誓词下庄严宣誓,也希望自己能够早日入党

说走就走的七一长沙游到这里就结束了,真的很匆忙,没做任何计划,玩什么都是头一天晚上来计划。不过热门的一些景点也基本都转了一圈。下一站会是哪呢?博客近一年基本都没有更新,也会慢慢捡起来,记录生活点滴~

2021年08月04日
程志辉

Vue官方文档

<transition-group> 组件还有一个特殊之处。不仅可以进入和离开动画,还可以改变定位。要使用这个新功能只需了解新增的 v-move,它会在元素的改变定位的过程中应用。将元素从之前的位置平滑过渡新的位置。

<style>
    .list-item{
        display: inline-block;
        margin-right: 10px;
    }
    .v-enter-from{
        opacity: 0;
    }
    .v-enter-active{
        transition: all 1s ease-in;
    }
    .v-enter-to{
        opacity: 1;
    }

    .v-leave-from{
        opacity: 1;
    }
    .v-leave-active{
        transition: all 1s ease-in;
    }
    .v-leave-to{
        opacity: 0;
    }
    .v-move{
        transition: all 1s ease;
    }
</style>

<div id="root"></div>
<script>
    const app = Vue.createApp({
        data(){
            return {
                list:[1,2,3,4,5,6,7,8,9,10]
            }
        },
        methods:{
            handleClick(){
                this.list.unshift(this.list.length+1)
            },
        },
        template:`
        <div>
            <transition-group>
                <span class="list-item" v-for="item in list" :key="item">{{item}}</span>
            </transition-group>
            <button @click="handleClick">增加</button>
        </div>
        `
    })
    const vm = app.mount("#root")
</script>

重点就是在css中添加v-move 设置transition 可以实现列表其他元素的平滑过渡

上面演示的是在列表第一位增加内容,原来的内容平滑过渡效果。想要删除第一位的内容,理论上其他代码都不用修改,改handleClick里的内容就可以实现删除元素,其他元素平滑过渡。

handleClick(){
    this.list.splice(0,1)
},

经过测试后发现之后删除的元素有过渡动画,但是其他原来的元素没有。经过搜索引擎检索,解决问题。

参考Vue - v-move列表进入/离开的平滑过渡,官方教程学习释疑

删除数组的元素,由于删除的元素经理了一个过渡,始终占据文档流的这个位置,因此下一个元素要等待其过渡消失后再移动过来,造成一个生硬的效果。要达到平滑过渡,就要在删除元素leave-active阶段用position:absolute将其移出文档流,后面的元素才能同时平滑过渡过来。完整代码如下

<style>
    .list-item{
        display: inline-block;
        margin-right: 10px;
    }
    .v-enter-from{
        opacity: 0;
    }
    .v-enter-active{
        transition: all 1s ease-in;
    }
    .v-enter-to{
        opacity: 1;
    }

    .v-leave-from{
        opacity: 1;
    }
    .v-leave-active{
        transition: all 1s ease-in;
        position: absolute
    }
    .v-leave-to{
        opacity: 0;
    }
    .v-move{
        transition: all 1s ease;
    }
</style>

<div id="root"></div>
<script>
    const app = Vue.createApp({
        data(){
            return {
                list:[1,2,3,4,5,6,7,8,9,10]
            }
        },
        methods:{
            handleClick(){
                this.list.splice(0,1)
            },
        },
        template:`
        <div>
            <transition-group>
                <span class="list-item" v-for="item in list" :key="item">{{item}}</span>
            </transition-group>
            <button @click="handleClick">删除</button>
        </div>
        `
    })
    const vm = app.mount("#root")
</script>

2021年08月04日
  • 马内 感谢博主的分享,支持了。 技术文章,学习了。
程志辉

Vue3 父子组件通过事件通信

在 3.x 中,自定义组件上的 v-model 相当于传递了 modelValue prop 并接收抛出的 update:modelValue 事件。 官方文档介绍

model不带参 默认就为modelValue可以进行传值 下面案例还用到了自定义修饰符 可以通过this.modelModifiers.修饰符 查看是否传入修饰符

<div id="root"></div>

<script>
    const app = Vue.createApp({
        data(){
            return {count:'a'}
        },
        template:`
            <div>
                <count v-model.uppercase="count"></count>
            </div>
        `
    });  

    app.component("count",{
        props:{
            'modelValue':String,
            // 不传递默认为空对象 传递则为true
            'modelModifiers':{
                default:()=>({})
            }
        },
        methods:{
            handleClick(){
                let newValue = this.modelValue+'b'
                if(this.modelModifiers.uppercase){
                    newValue=newValue.toUpperCase()
                }
                this.$emit("update:modelValue",newValue)
            }
        },
        template:`<div @click="handleClick">{{modelValue}}</div>`
    })
    const vm = app.mount("#root");
</script>

model带参写法如下 需要注意modelModifiers也得跟着改为参数名称+Modifiers

<div id="root"></div>

<script>
    const app = Vue.createApp({
        data(){
            return {count:'a'}
        },
        template:`
            <div>
                <count v-model:app.uppercase="count"></count>
            </div>
        `
    });  

    app.component("count",{
        props:{
            'app':String,
            // 不传递默认为空对象 传递则为true
            'appModifiers':{
                default:()=>({})
            }
        },
        mounted(){
            console.log(this.appModifiers)  
        },
        methods:{
            handleClick(){
                let newValue = this.app+'b'
                if(this.appModifiers.uppercase){
                    newValue=newValue.toUpperCase()
                }
                // console.log(this.modelModifiers);
                this.$emit("update:app",newValue)
            }
        },
        template:`<div @click="handleClick">{{app}}</div>`
    })
    const vm = app.mount("#root");
</script>

Vue3 作用域插槽

作用域插槽官方讲解

绑定在 <slot> 元素上的 attribute 被称为插槽 prop。将子组件插槽prop传递给父组件使用方法如下

<div id="root"></div>

<script>
    const app = Vue.createApp({
        template:`
            <list v-slot="slotProps">
                <span>{{slotProps.item11}}</span>    
            </list>`
    })

    app.component("list",{
        data(){
            return {
                list:[1,2,3]
            }
        },
        template:`
            <div>
                <slot v-for="item in list" :item11="item">
                    {{item}}
                </slot>
            </div>
        `
    })
    const vm = app.mount("#root");
</script>

ES6结构写法如下 对象的解构赋值学习

<div id="root"></div>

<script>
    const app = Vue.createApp({
        //ES6 解构
        template:`
            <list v-slot="{item11}">
                <span>{{item11}}</span>    
            </list>`
    })

    app.component("list",{
        data(){
            return {
                list:[1,2,3]
            }
        },
        template:`
            <div>
                <slot v-for="item in list" :item11="item">
                    {{item}}
                </slot>
            </div>
        `
    })
    const vm = app.mount("#root");
</script>
2021年08月02日
  • 马内 80% WordPress/Typecho,都是在讲前端开发 技术文章,学习了。
程志辉

首先安装iscroll

npm install iscroll

创建scroll组件

<template>
  <div id="wrapper" ref="wrapper">
    <slot></slot>
  </div>
</template>

<script>
import IScroll from 'iscroll/build/iscroll-probe' //专业版本可以监听滚动的细节
export default {
  name: 'ScrollView',
  mounted () {
    this.iscroll = new IScroll(this.$refs.wrapper, {
      mouseWheel: true,//禁用鼠标滚轮
      scrollbars: true, //禁用滚动条
      // 解决拖拽卡顿问题
      scrollX: false,
      scrollY: true, //y方向滚动
      disablePointer: true,
      disableTouch: false,
      disableMouse: true
    })
    // setTimeout(() => {
    //   // console.log(this.iscroll.maxScrollY)
    //   this.iscroll.refresh()
    //   // console.log(this.iscroll.maxScrollY)
    // }, 5000)
    // 1.创建一个观察者的对象
    /*
    MutationObserver构造函数只要监听到了指定内容发生了变化,就会执行传入的回调函数
    MutationList: 发生变化的数组
    observer:观察者对象
     */
    const observer = new MutationObserver((mutationList, observer) => {
      // console.log(MutationList)
      // console.log(this.iscroll.maxScrollY)
      this.iscroll.refresh()
      // console.log(this.iscroll.maxScrollY)
    })
    // 2.告诉观察者对象我们需要观察什么内容
    const config = {
      childList: true, // 观察目标子节点的变化,是否有添加或者删除
      subtree: true, // 观察后代节点,默认为 false
      attributeFilter: ['height', 'offsetHeight'] // 观察特定属性
    }
    // 3.告诉观察者对象,我们需要观察谁,需要观察什么内容
    /*
    第一个参数: 告诉观察者对象我们需要观察谁
    第二个参数: 告诉观察者对象我们需要观察什么内容
     */
    observer.observe(this.$refs.wrapper, config)
  }
}
</script>

<style scoped>
  #wrapper{
    width: 100%; //容器的宽高等于可视区域的宽高
    height: 100%;
  }
</style>

要注意把系统默认滚动关闭

html, body{
  width: 100%;
  height: 100%;
  overflow: hidden;
  // 解决IScroll拖拽卡顿问题 
  touch-action: none; 
}

并且iscroll容器的高度必须小于内容的高度
容器的宽高等于可视区域的宽高
要给容器设置一个宽高,可以固定定位

.recommend{
  position: fixed;
  top: 184px;
  left: 0;
  right: 0;
  bottom: 0;
}

MutationObserver()
创建并返回一个新的 MutationObserver 它会在指定的DOM发生变化时被调用。

// 1.创建一个观察者的对象
/*
MutationObserver构造函数只要监听到了指定内容发生了变化,就会执行传入的回调函数
MutationList: 发生变化的数组
observer:观察者对象
 */
const observer = new MutationObserver((mutationList, observer) => {
  // console.log(MutationList)
  // console.log(this.iscroll.maxScrollY)
  this.iscroll.refresh()
  // console.log(this.iscroll.maxScrollY)
})
// 2.告诉观察者对象我们需要观察什么内容
const config = {
  childList: true, // 观察目标子节点的变化,是否有添加或者删除
  subtree: true, // 观察后代节点,默认为 false
  attributeFilter: ['height', 'offsetHeight'] // 观察特定属性
}
// 3.告诉观察者对象,我们需要观察谁,需要观察什么内容
/*
第一个参数: 告诉观察者对象我们需要观察谁
第二个参数: 告诉观察者对象我们需要观察什么内容
 */
observer.observe(this.$refs.wrapper, config)

引用:

Vue中使用滚动组件iscroll

从零玩转Vue全家桶+项目实战⑿

2021年07月27日
Icefox Theme . 鄂ICP备16001608号-1