YII2 框架 高级模板 学习笔记

749 阅读4分钟

路由

  1. 使用<?= \yii\helpers\Url::toRoute('') ?>进行路由的跳转
  2. 需要进行参数的传递:"<?= \yii\helpers\Url::toRoute(['send', 'uid' => 'id']) ?
  3. ajax请求URL传参: "<?= \yii\helpers\Url::toRoute(['send', 'uid' => '']) ?>" + uid, // 在URL中添加uid参数

增删改查

路由配置

  1. 首先在header.php文件中的全局变量 bootUrl进行url地址的配置 Snipaste_2023-05-12_08-52-00.jpg

表单验证常用规则

高级专题(Special Topics): 核心验证器(Core Validators) | Yii 2.0 权威指南 | Yii PHP Framework (yiiframework.com)

Yii:: 是 Yii2 框架中的命名空间,表示全局命名空间。在 PHP 中,:: 是一个静态操作符,称为范围解析操作符,用于访问类的静态属性、常量和静态方法,如 ClassName::staticMethod()

Yii::$app 表示访问 yii\web\Application 类型对象的静态属性 $app。通常在 Yii2 中,我们会把应用程序实例赋值给 Yii::$app 属性,以便在后续代码中使用该应用程序实例的各种组件。

总之, Yii::$app->user->login() 是访问 Yii2 应用程序组件的一种方式,而 Yii:::: 是用于访问类和静态成员的 PHP 中的操作符。

设置的树形分类弹窗

QQ截图20230511102710.png

实现方式

  1. 弹窗视图
    <--该组件的作用是让用户从一个商品分类树中选择一个分类,并将所选分类的ID和名称存储在表单中。在js中,使用`$('#treeId').val()`和`$('#treeName').val()`来获取所选分类的ID和名称。-->
 <div class="form-group">
            <label class="col-sm-3 control-label is-required">商品分类:</label>
            <div class="col-sm-8">
                <div class="input-group">
                    <input type="hidden" id="treeId" name="cat_id" value="">
                    <input type="text" id="treeName" value="" class="form-control" placeholder="请选择商品分类" readonly autocomplete="off" />
                    <span class="input-group-addon"><i class="fa fa-search"></i></span>
                </div>
            </div>
  </div>
// 当用户点击商品分类选择器时,将打开一个模态框,显示一个商品分类树。
 $(function() {
        $("#treeName").click(function() {
            var treeId = $("#treeId").val(); // 获取隐藏的输入控件`#treeId`的值,并将其存储在变量`treeId`中。
            var url = ("<?= \yii\helpers\Url::toRoute('tree') ?>"); //生成一个URL,用于加载商品分类树的数据。`Url::toRoute()`方法用于生成一个相对于应用程序根目录的URL,指向名为`tree`的操作。
            var options = {
                title: '商品分类选择', //设置模态框的标题为“商品分类选择”
                width: "380", //设置模态框的宽度为380像素。
                url: url,  //设置模态框的URL为上面生成的URL。
                callBack: doSubmit  //设置模态框关闭时要执行的回调函数为`doSubmit`。
            };
            $.modal.openOptions(options);  //打开一个模态框,使用上面配置的选项。
        });
    });
  1. 树形视图
<!-- 该行代码用于设置当前视图页面的布局文件为/form,即使用名为form.php的布局文件来渲染该页面。 -->
<?php $this->context->layout = '/form'; ?>
<!-- 该行代码用于引入一个名为Asset的widget组件,并传入一个名为type的参数数组,其中包含一个名为ztree的元素。这个组件会自动加载ztree相关的静态资源,使树形结构选择器能够正常显示。ztree插件的初始化参数可以通过传递不同的选项对象进行配置。 -->
<?= \backend\extend\widgets\Asset::widget(['type' => ['ztree']]) ?>
<style>
    body {
        height: auto;
        font-family: "Microsoft YaHei";
        background-color: #fff !important;
    }
    button {
        font-family: "SimSun", "Helvetica Neue", Helvetica, Arial;
    }
</style>
<!-- 该行代码定义了一个隐藏的input元素,其名称为treeId,ID为treeId,并且将前端传递过来的变量$struct_id作为其值。该元素用于保存当前选中节点的编号。 -->
<input type="hidden" name='treeId' id='treeId' value='<?= $struct_id ?>'>
<!-- 该行代码定义了一个隐藏的input元素,其名称为treeName,ID为treeName,初始值为空。该元素用于保存当前选中节点的名称。 -->
<input type="hidden" name='treeName' id='treeName' value=''>
<!-- 该部分代码<div class="wrapper">包含了整个树形结构选择器的核心代码,包括了搜索按钮、展开和折叠按钮以及显示树形结构的容器。其中,类名为treeselect的DIV元素最终会被ztree插件实例化成一个树形结构的DOM元素。 -->
<div class="wrapper">
    <div class="treeShowHideButton" onclick="$.tree.toggleSearch();">
        <label id="btnShow" title="显示搜索" style="display:none;"></label>
        <label id="btnHide" title="隐藏搜索"></label>
    </div>
    <div class="treeSearchInput" id="search">
        <label for="keyword">关键字:</label><input type="text" class="empty" id="keyword" maxlength="50">
        <button class="btn" id="btn" onclick="$.tree.searchNode()"> 搜索</button>
    </div>
    <div class="treeExpandCollapse">
        <a href="javascript:;" onclick="$.tree.expand()">展开</a> /
        <a href="javascript:;" onclick="$.tree.collapse()">折叠</a>
        <?php if ($ismult == '1') : ?>
            /<a href="javascript:;" onclick="$._tree.checkAllNodes(false);$.tree.zOnCheck();">取消选择</a>
        <?php endif; ?>
    </div>
    <div id="tree" class="ztree treeselect"></div>
</div>
<?php $this->beginBlock('script'); ?>
<script>
    var parent = "<?= $parent ?>";
    var ismult = "<?= $ismult ?? '' ?>";
    $(function() {
        console.log(aUrl);
        var options = {
            url: aUrl,
            showParentLevel: parent == '1' ? 0 : false,
            ismult: (ismult == '1') ? true : false,
            expandLevel: 2
        };
        $.tree.init(options);
    });
</script>
<?php $this->endBlock(); ?>
  1. 控制器
    /**
     * 获取所有菜单,用于树形组件使用
     * @return array
     */
    public function getList(): array
    {
        $list = Classify::find()->select(['id', 'pid', 'name'])->orderBy('pid asc,id asc')->asArray()->all();
        // 定义$map数组,用于存储节点的键值对信息,其中键为节点的id,值为节点的信息和其子节点信息
        $map = [];
        // 定义$root数组,用于存储根节点的子节点信息
        $root = [];
        // 加上 & 符号可以将一个变量声明为引用变量。引用变量是指向内存中同一个变量的不同名称,它们共享同一个内存地址,因此对其中一个变量的修改会影响到其他变量。
        // &$item 是一个引用变量,表示将 $item 变量的引用传递给数组中的元素,而不是将 $item 变量的值复制到数组中的元素。这样做的好处是,当修改数组中的元素时,原始变量 $item 的值也会被修改,
        foreach ($list as &$item) {
            // 为节点添加一个空的children数组,用于存储其子节点信息。
            $item['children'] = [];
            // 将节点加入到$map数组中,以节点的id为键,节点信息和子节点信息为值。
            $map[$item['id']] = &$item;
            // 如果节点的pid为0,则将其加入到$root数组中。
            if ($item['pid'] == 0) {
                $root[] = &$item;
            } else {
                // 否则,将其加入到其父节点的children数组中。
                $map[$item['pid']]['children'][] = &$item;
            }
        }
        return $root;
    }
    /**
     * 树形页面
     * @return array|string
     */
    public function actionTree()
    {
        if ($this->request->isPost) {
            $list = $this->getList();
            return $this->success('', $list);
        } else { //是否显示父级名称
            // * 父节点的名称
            $parent = $this->request->get('parent', 0);
            // * 树节点的id
            $id = $this->request->get('id', 0);
            // * 多选
            $ismult = $this->request->get('ismult', 0);
            $list = $this->getList();
            return $this->render('', ['struct_id' => $id, 'parent' => $parent, 'ismult' => $ismult]);
        }
    }

注意:以上操作适用于添加,使用编辑跟详情的时候,调用对应的组件,应在控制器里进行对应数据的查询,再进行赋值渲染当前分类选项

     /**
     * 编辑页渲染
     * @param array $info  获取到的对应信息
     * @return string
     */
    protected function editRender(array $info): string
    {
        $struct_id = Classify::find()->select('id')->where(['id' => $info['cat_id']])->scalar();
        $struct_name = Classify::find()->select('name')->where(['id' => $info['cat_id']])->scalar();
        return $this->render('', ['info' => $info, 'struct_id' => $struct_id, 'struct_name' => $struct_name]);
    }

导入EXCEL

  1. 视图
  <!-- 对上传文件的类型进行限制,只允许上传excel文件,可以在input元素中添加accept属性,并设置为".xlsx, .xls" -->
    <input type="file" id="inputExcel" style="display: none" accept=".xlsx, .xls">
    <a class="btn btn-warning" id="inputExcelBtn"> <i class="fa fa-file-excel-o"></i> 导入EXCEL</a>
    $('#inputExcelBtn').on('click', function() {
        // 打开文件选择器
        $('#inputExcel').click();
    });

    // 监听文件选择器的 change 事件,在选择文件后立即触发上传操作
    $('#inputExcel').on('change', function() {
        var file = this.files[0];
        var formData = new FormData();
        formData.append('file', file);
        // 调用上传方法,传递 formData 对象和其他参数
        $.ajax({
            //当前控制器的方法
            url: "<?= \yii\helpers\Url::toRoute('import') ?>",
            data: formData,
            type: 'POST',
            contentType: false,
            processData: false,
            dataType: 'json',
            cache: false,
            success: function(res) {
                // 处理上传成功的回调
                history.go(0);
                alert(res.msg);
            },
            error: function(err) {
                // 处理上传失败的回调
                console.error('Upload error:', err);
            }
        });
    })

2、控制器

    /**
     * 导入excel
     */
    public function actionImport()
    {
        // 判断当前请求是否为POST请求,并且检查是否存在名为file的上传文件。如果条件成立,则继续执行代码;否则返回错误信息
        if (Yii::$app->request->isPost && isset($_FILES['file']['tmp_name'])) {
            // 生成一个唯一的文件名,使用当前时间戳和一个随机数进行MD5加密,最后加上后缀名.xlsx。
            $fileName = md5(microtime(true) . mt_rand(1000, 9999)) . '.xlsx';
            // 获取应用程序根目录下的@backend/web/assets目录,并将其赋值给变量$root。
            $root = Yii::getAlias('@backend/web/assets/');
            // 生成一个以当前日期为名称的子目录,用于存放上传的Excel文件。
            $savePath = 'importExcel/' . date('Ymd') . '/';
            // 将上传文件的路径设置为$root与$savePath的拼接结果。
            $path = $root . $savePath;
            //将路径中的斜杆替换为操作系统对应的分隔符,以确保路径在各个操作系统之间的兼容性
            $path = str_replace('/', DIRECTORY_SEPARATOR, $path);
            // is_dir查上传文件的目录是否存在,如果不存在则创建该目录。
            if (!is_dir($path)) {
                // mkdir($path, 0777, true) 该方法创建名为$path的目录,并赋予最大的权限
                if (false === @mkdir($path, 0777, true) && !is_dir($path)) {
                    throw new HttpException(500, '存储文件夹创建失败:' . $path);
                }
            }
            // 组合出完整的文件路径。
            $filePath = $path . $fileName;
            // 将上传的Excel文件保存到服务器上指定的位置
            if (move_uploaded_file($_FILES['file']['tmp_name'], $filePath)) {
                // 加载Excel文件
                $objPHPExcel = IOFactory::load($filePath);
                // 获取第一个工作表
                $worksheet = $objPHPExcel->getActiveSheet()->toArray(null, true, true, true);
                // 获取A1单元格的值
                // $value = $worksheet->getCell('A1')->getValue();
                // 处理Excel文件内容并保存到数据库中
                $transaction = Yii::$app->db->beginTransaction();
                try {
                    foreach ($worksheet as $key => $value) {
                        //  跳过表头
                        if ($key == 1) continue;
                        // 跳过空行
                        if (empty($value['A'])) continue;
                        $model = new NewTestDemo();
                        $model->name = $value['A'];
                        // 当参数为false时,会禁用数据验证,直接保存数据到数据库中。而当参数为true时,会强制进行数据验证,
                        $model->save(false);
                    }
                    $transaction->commit();
                    return $this->success('文件上传成功');
                } catch (\Exception $e) {
                    $transaction->rollBack();
                    return $this->error($e->getMessage());
                }
            } else {
                return $this->error('文件上传失败');
            }
        } else {
            return $this->error();
        }
    }  

发送邮箱

邮箱配置

参考:www.jianshu.com/p/f6c6355f4…

         //定义邮箱配置
        'mailer' => [
            'class' => 'yii\swiftmailer\Mailer',
            'viewPath' => 'common/mail', //根据实际情况配置
            'useFileTransport' => false,
            // 以下为 SMTP 发送邮件配置
            'transport' => [
                'class' => 'Swift_SmtpTransport',
                'host' => 'smtp.qq.com',  // 以下为 SMTP 发送邮件配置
                'username' => 'U Email@qq.com', //改成自己的用户名密码
                'password' => 'U password',
                'port' => '465',
                'encryption' => 'ssl',
            ],
            'messageConfig' => [
                'charset' => 'UTF-8',
                'from' => ['U Email@qq.com' => 'U name']
            ],
        ],
        
  1. 发送邮件时,将生成的验证码插入到邮件内容中,并发送给用户。可以使用 Yii2 的邮件组件 yii\swiftmailer\Mailer 发送邮件,

    public function actionSend()
    {
        // 生成一个随机四位数的验证码
        $code = rand(1000, 9999);
        // 将生成的验证码保存到缓存中,以邮箱地址作为键名,使用了文件缓存,默认的缓存文件夹路径是 @runtime/cache,可以通过文件管理器或命令行进入该目录查看相关缓存文件。  实际开发可以使用redis缓存进行配置
        \Yii::$app->cache->set($userData['email'], $code, 300); // 有效时间为 300 秒(5分钟)
        // 将生成的验证码插入到邮件内容中,并发送给用户,使用 Yii2 的邮件组件 yii\swiftmailer\Mailer 发送邮件
        $emailObj = \Yii::$app->mailer->compose();
                        ->setTo($email) // 收件人邮箱地址
                        ->setSubject('验证码') // 邮件主题
                        ->setTextBody("您的验证码是:{$code}") // 邮件正文
                        ->send(); // 发送邮件
     }
    
  2. 用户输入收到的验证码后,从缓存中获取验证码并和用户输入的进行比对,以验证验证码是否正确。

     
    $inputCode = Yii::$app->request->post('code');
    $savedCode = Yii::$app->cache->get($email);
    if ($savedCode === false || $savedCode != $inputCode) {
        // 验证码不正确
    } else {
        // 验证码正确
        Yii::$app->cache->delete($email); // 验证成功后,可以删除缓存中的验证码
    }
    

项目开发遇到的一些报错

列表图片不能正常显示

  • 文件上传的配置文件在common\helpers\UploadHelper.php里面定义了图片视频等上传方法。
  • 在上传操作的_upload()方法中,配置了文件路径等信息。
  • 项目初始的时候,运行的是backend的web目录,而上传的文件安装在了网站的根目录里面了,@root_path表示根目录,运行的是web目录,所以图片是读取不到的,这里修改了一下路径,就能正常显示在页面中了。 image.png