├── .bowerrc ├── .gitattributes ├── .gitignore ├── LICENSE.md ├── README.md ├── actions └── GalleryAction.php ├── assets ├── AppAsset.php ├── BaseAsset.php ├── ClockPickerAsset.php ├── CountDownAsset.php ├── GalleryAsset.php ├── MetisMenuAsset.php ├── MultiSelectAsset.php └── ToasterAsset.php ├── commands └── HelloController.php ├── components ├── QiNiu.php └── Ucpaas.php ├── composer.json ├── composer.lock ├── config ├── console.php ├── db.php ├── params.php └── web.php ├── controllers ├── ActivityController.php ├── BusinessController.php ├── CategoryController.php ├── OrderController.php ├── ProductController.php ├── SiteController.php └── UserController.php ├── lazy_waimai.sql ├── mail └── layouts │ └── html.php ├── models ├── AccountLoginForm.php ├── Activity.php ├── Admin.php ├── Business.php ├── BusinessActivity.php ├── BusinessScene.php ├── Category.php ├── CategorySearch.php ├── ChangePhoneForm.php ├── LoginSendSmsForm.php ├── Order.php ├── OrderSearch.php ├── PhoneLoginForm.php ├── Product.php ├── ProductSearch.php ├── SendChangePhoneSmsForm.php ├── UpdatePasswordForm.php ├── User.php └── VerifyPasswordForm.php ├── requirements.php ├── runtime └── .gitignore ├── tests ├── README.md ├── codeception.yml └── codeception │ ├── .gitignore │ ├── _bootstrap.php │ ├── _output │ └── .gitignore │ ├── _pages │ ├── AboutPage.php │ ├── ContactPage.php │ └── LoginPage.php │ ├── acceptance.suite.yml │ ├── acceptance │ ├── AboutCept.php │ ├── ContactCept.php │ ├── HomeCept.php │ ├── LoginCept.php │ └── _bootstrap.php │ ├── bin │ ├── _bootstrap.php │ ├── yii │ └── yii.bat │ ├── config │ ├── acceptance.php │ ├── config.php │ ├── functional.php │ └── unit.php │ ├── fixtures │ └── .gitignore │ ├── functional.suite.yml │ ├── functional │ ├── AboutCept.php │ ├── ContactCept.php │ ├── HomeCept.php │ ├── LoginCept.php │ └── _bootstrap.php │ ├── templates │ └── .gitignore │ ├── unit.suite.yml │ └── unit │ ├── _bootstrap.php │ ├── fixtures │ ├── .gitkeep │ └── data │ │ └── .gitkeep │ ├── models │ ├── ContactFormTest.php │ ├── LoginFormTest.php │ └── UserTest.php │ └── templates │ └── fixtures │ └── .gitkeep ├── upload └── images │ └── .gitignore ├── views ├── activity │ └── index.php ├── business │ ├── profile.php │ ├── scene.php │ └── setup.php ├── category │ ├── form.php │ └── index.php ├── gallery.php ├── layouts │ ├── base.php │ └── main.php ├── order │ └── index.php ├── product │ ├── form.php │ └── index.php ├── site │ ├── error.php │ └── index.php └── user │ ├── login.php │ ├── profile.php │ ├── update-password.php │ └── update-phone.php ├── web ├── .htaccess ├── assets │ └── .gitignore ├── css │ ├── gallery.css │ ├── lightbox.css │ ├── login.css │ └── site.css ├── favicon.ico ├── images │ ├── close.png │ ├── loading.gif │ ├── login_bgx.gif │ ├── login_border_bg.png │ ├── logo.png │ ├── next.png │ └── prev.png ├── index-test.php ├── index.php ├── js │ ├── jquery.gallery.js │ ├── jquery.iframe-transport.js │ ├── lightbox.js │ ├── login.js │ ├── setup.js │ ├── site.js │ └── update-phone.js ├── raw │ └── booking_times.json └── robots.txt ├── widgets ├── Alert.php └── Gallery.php ├── yii └── yii.bat /.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory" : "vendor/bower" 3 | } 4 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.js linguist-language=php 2 | *.css linguist-language=php 3 | *.html linguist-language=php -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | /vendor 3 | .DS_Store 4 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The Yii framework is free software. It is released under the terms of 2 | the following BSD License. 3 | 4 | Copyright © 2008 by Yii Software LLC (http://www.yiisoft.com) 5 | All rights reserved. 6 | 7 | Redistribution and use in source and binary forms, with or without 8 | modification, are permitted provided that the following conditions 9 | are met: 10 | 11 | * Redistributions of source code must retain the above copyright 12 | notice, this list of conditions and the following disclaimer. 13 | * Redistributions in binary form must reproduce the above copyright 14 | notice, this list of conditions and the following disclaimer in 15 | the documentation and/or other materials provided with the 16 | distribution. 17 | * Neither the name of Yii Software LLC nor the names of its 18 | contributors may be used to endorse or promote products derived 19 | from this software without specific prior written permission. 20 | 21 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 22 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 23 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 24 | FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 25 | COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 26 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 27 | BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 28 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 29 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 30 | LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 31 | ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 32 | POSSIBILITY OF SUCH DAMAGE. 33 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | LazyWaimai-Web 2 | ========== 3 | 此项目是懒人外卖(本人用来练手的项目,类似于百度外卖,美团外卖和饿了么的系统)的商家端,为[Android客户端](https://github.com/cheikh-wang/LazyWaimai-Android)提供商铺管理服务,基于 [Yii2](https://github.com/yiisoft/yii2) 框架实现的。 4 | 5 | 环境条件 6 | ------- 7 | + PHP版本必须大于或等于php5.4 8 | 9 | 安装 10 | ------- 11 | #### 1.clone到本地 12 | ``` 13 | git clone -b develop https://github.com/cheikh-wang/LazyWaimai-Web.git 14 | ``` 15 | #### 2.配置数据库 16 | 1. 将sql文件导入到数据库中 17 | 18 | 2. 配置数据库 19 | ``` 20 | cd LazyWaimai-Web 21 | vi config/web.php // 将数据库密码修改成你本机的数据库密码 22 | ``` 23 | #### 3.安装依赖 24 | 本项目使用composer管理依赖,所以需要先安装composer(已安装请跳过) 25 | ``` 26 | curl -sS https://getcomposer.org/installer | php 27 | mv composer.phar /usr/local/bin/composer 28 | ``` 29 | 还需要安装composer-asset-plugin(已安装请跳过) 30 | ``` 31 | composer global require "fxp/composer-asset-plugin:^1.3.1" 32 | ``` 33 | 34 | 安装项目所需依赖(开始之前请确保composer和composer-asset-plugin已成功安装) 35 | ``` 36 | composer install 37 | ``` 38 | #### 5.配置服务器 39 | ``` 40 | 配置nginx/apache的webroot指向LazyWaimai-Web/web 41 | ``` 42 | #### 6.完毕 43 | 44 | 安装完毕,打开你的服务器访问地址:http://localhost:端口号, 默认的管理员账号,用户名:admin,密码:123456 45 | 46 | 其他配置 47 | ------- 48 | #### 1.短信服务的配置 49 | ###### 本项目的短信服务是使用的[云之讯](http://www.ucpaas.com),请自行注册账户并按如下方式配置: 50 | 51 | 编辑config/web.php 52 | 53 | ``` 54 | 'ucpass' => [ 55 | 'class' => 'app\components\Ucpaas', 56 | 'accountSid' => '修改为你的云之讯Account Sid', 57 | 'token' => '修改为你的云之讯Auth Token', 58 | 'appId' => '修改为你的云之讯应用ID', 59 | 'templateId' => '修改为你的云之讯短信模板ID', 60 | ], 61 | ``` 62 | #### 2.七牛云的配置 63 | ###### 本项目的图片上传服务是使用的[七牛](http://www.qiniu.com),请自行注册账户并按如下方式配置: 64 | ``` 65 | 'qiniu' => [ 66 | 'class' => 'app\components\QiNiu', 67 | 'accessKey' => '修改为你的AccessKey', 68 | 'secretKey' => '修改为你的SecretKey', 69 | 'bucket' => '修改为你的空间名', 70 | 'domain' => '修改为你的域名', 71 | ], 72 | ``` 73 | -------------------------------------------------------------------------------- /actions/GalleryAction.php: -------------------------------------------------------------------------------- 1 | actionDelete(Yii::$app->request->post('id')); 22 | break; 23 | case 'ajaxUpload': 24 | return $this->actionAjaxUpload(Yii::$app->request->get('type')); 25 | break; 26 | case 'order': 27 | return $this->actionOrder(Yii::$app->request->post('order')); 28 | break; 29 | default: 30 | throw new HttpException(400, 'Action do not exists'); 31 | break; 32 | } 33 | } 34 | 35 | /** 36 | * Removes image with ids specified in post request. 37 | * On success returns 'OK' 38 | * 39 | * @param $id 40 | * @return string 41 | * @throws Exception 42 | */ 43 | protected function actionDelete($id) { 44 | try { 45 | /** @var $scene BusinessScene */ 46 | $scene = BusinessScene::findOne($id); 47 | if (!$scene) { 48 | throw new Exception('未找到该资源!'); 49 | } 50 | // 删除数据库文件记录 51 | if (!$scene->delete()) { 52 | throw new Exception('图片删除失败'); 53 | } 54 | // 删除原图 55 | Yii::$app->qiniu->delete($scene->original_name); 56 | // 删除缩略图 57 | Yii::$app->qiniu->delete($scene->thumb_name); 58 | 59 | return Json::encode([ 60 | 'status' => 'ok' 61 | ]); 62 | } catch (Exception $e) { 63 | return Json::encode([ 64 | 'status' => 'err', 65 | 'message' => $e->getMessage() 66 | ]); 67 | } 68 | } 69 | 70 | /** 71 | * Method to handle file upload thought XHR2 72 | * On success returns JSON object with image info. 73 | * 74 | * @param $type 75 | * @return string 76 | * @throws Exception 77 | */ 78 | public function actionAjaxUpload($type) { 79 | try { 80 | $imageFile = UploadedFile::getInstanceByName('image'); 81 | 82 | // 生成原图名称和缩略图名称 83 | $randomStr = Yii::$app->security->generateRandomString(20); 84 | $originalName = $randomStr . '.' . $imageFile->extension; 85 | $thumbName = $randomStr . '_145x145.' . $imageFile->extension; 86 | 87 | $originalImage = Image::getImagine()->open($imageFile->tempName); 88 | $basePath = Yii::getAlias(Yii::$app->params['imageUploadPath']); 89 | 90 | // 临时保存原图 91 | $originalPath = $basePath . DIRECTORY_SEPARATOR . $originalName; 92 | if (!$originalImage->save($originalPath)) { 93 | throw new Exception('保存图片失败!'); 94 | } 95 | 96 | // 临时保存缩略图 97 | $thumbPath = $basePath . DIRECTORY_SEPARATOR . $thumbName; 98 | if (!$originalImage->copy()->thumbnail(new Box(145, 145))->save($thumbPath)) { 99 | throw new Exception('保存图片失败!'); 100 | } 101 | 102 | // 上传原图和缩略图到七牛 103 | $originalUrl = Yii::$app->qiniu->uploadFile($originalPath, $originalName); 104 | $thumbUrl = Yii::$app->qiniu->uploadFile($thumbPath, $thumbName); 105 | 106 | // 删除临时的图片文件 107 | if (file_exists($originalPath)) { 108 | @unlink($originalPath); 109 | } 110 | if (file_exists($thumbPath)) { 111 | @unlink($thumbPath); 112 | } 113 | 114 | /* @var $admin Admin */ 115 | $admin = Admin::findOne(Yii::$app->user->id); 116 | 117 | // 保存信息到数据库 118 | $model = new BusinessScene(); 119 | $model->business_id = $admin->business_id; 120 | $model->type = $type; 121 | $model->rank = 1; 122 | $model->original_name = $originalName; 123 | $model->original_url = $originalUrl; 124 | $model->thumb_name = $thumbName; 125 | $model->thumb_url = $thumbUrl; 126 | if (!$model->save(false)) { 127 | throw new Exception('保存图片记录失败!'); 128 | } 129 | 130 | Yii::$app->response->headers->set('Content-Type', 'text/html'); 131 | 132 | return Json::encode([ 133 | 'status' => 'ok', 134 | 'data' => [ 135 | 'id' => $model->id, 136 | 'original_url' => $originalUrl, 137 | 'thumb_url' => $thumbUrl, 138 | 'rank' => $model->rank 139 | ] 140 | ]); 141 | } catch (Exception $e) { 142 | return Json::encode([ 143 | 'status' => 'err', 144 | 'message' => $e->getMessage() 145 | ]); 146 | } 147 | } 148 | 149 | /** 150 | * Saves images order according to request. 151 | * 152 | * @param array $order new arrange of image ids, to be saved 153 | * 154 | * @return string 155 | * @throws HttpException 156 | */ 157 | public function actionOrder($order) { 158 | if (count($order) == 0) { 159 | throw new HttpException(400, 'No data, to save'); 160 | } 161 | $orders = []; 162 | $i = 0; 163 | foreach ($order as $k => $v) { 164 | if (!$v) { 165 | $order[$k] = $k; 166 | } 167 | $orders[] = $order[$k]; 168 | $i++; 169 | } 170 | sort($orders); 171 | $i = 0; 172 | $res = []; 173 | foreach ($order as $k => $v) { 174 | $res[$k] = $orders[$i]; 175 | // TODO 更新 rank 176 | // \Yii::$app->db->createCommand() 177 | // ->update( 178 | // $this->tableName, 179 | // ['rank' => $orders[$i]], 180 | // ['id' => $k] 181 | // )->execute(); 182 | 183 | $i++; 184 | } 185 | 186 | return Json::encode($order); 187 | } 188 | } -------------------------------------------------------------------------------- /assets/AppAsset.php: -------------------------------------------------------------------------------- 1 | registerJsFile($jsfile, [BaseAsset::className(), 'depends' => 'app\assets\BaseAsset']); 28 | } 29 | 30 | /** 31 | * 定义按需加载css方法,注意加载顺序在最后 32 | * @param $view View 33 | * @param $cssfile string 34 | */ 35 | public static function addCss($view, $cssfile) { 36 | $view->registerCssFile($cssfile, [BaseAsset::className(), 'depends' => 'app\assets\BaseAsset']); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /assets/ClockPickerAsset.php: -------------------------------------------------------------------------------- 1 | 18 | * @since 2.0 19 | */ 20 | class HelloController extends Controller 21 | { 22 | /** 23 | * This command echoes what you have entered as the message. 24 | * @param string $message the message to be echoed. 25 | */ 26 | public function actionIndex($message = 'hello world') 27 | { 28 | echo $message . "\n"; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "LazyWaimai-Web", 3 | "description": "懒人外卖的商家端", 4 | "keywords": ["懒人外卖", "商家端", "LazyWaimai"], 5 | "homepage": "https://github.com/cheikh-wang/LazyWaimai-Web", 6 | "type": "project", 7 | "license": "BSD-3-Clause", 8 | "support": { 9 | "issues": "https://github.com/cheikh-wang/LazyWaimai-Web/issues?state=open", 10 | "source": "https://github.com/cheikh-wang/LazyWaimai-Web" 11 | }, 12 | "minimum-stability": "stable", 13 | "require": { 14 | "php": ">=5.4.0", 15 | "yiisoft/yii2": "~2.0.5", 16 | "yiisoft/yii2-bootstrap": "~2.0.0", 17 | "yiisoft/yii2-swiftmailer": "~2.0.0", 18 | "yiisoft/yii2-imagine": "*", 19 | "yiisoft/yii2-jui": "*", 20 | 21 | "kartik-v/yii2-widgets": "*", 22 | "kartik-v/yii2-field-range": "*", 23 | "2amigos/yii2-chartjs-widget": "~2.0", 24 | "rmrevin/yii2-fontawesome": "*", 25 | 26 | "bower-asset/jquery.countdown": "*", 27 | "bower-asset/jquery.toaster": "*", 28 | "bower-asset/bootstrap-multiselect": "*", 29 | "bower-asset/metismenu": "*", 30 | "bower-asset/clockpicker": "*" 31 | }, 32 | "require-dev": { 33 | "yiisoft/yii2-debug": "~2.0.0", 34 | "yiisoft/yii2-gii": "~2.0.0", 35 | "yiisoft/yii2-faker": "~2.0.0", 36 | 37 | "codeception/base": "^2.2.3", 38 | "codeception/verify": "~0.3.1", 39 | "codeception/specify": "~0.4.3" 40 | }, 41 | "config": { 42 | "process-timeout": 1800, 43 | "fxp-asset":{ 44 | "installer-paths": { 45 | "npm-asset-library": "vendor/npm", 46 | "bower-asset-library": "vendor/bower" 47 | } 48 | } 49 | }, 50 | "scripts": { 51 | "post-install-cmd": [ 52 | "yii\\composer\\Installer::postInstall" 53 | ], 54 | "post-create-project-cmd": [ 55 | "yii\\composer\\Installer::postCreateProject", 56 | "yii\\composer\\Installer::postInstall" 57 | ] 58 | }, 59 | "extra": { 60 | "yii\\composer\\Installer::postCreateProject": { 61 | "generateCookieValidationKey": [ 62 | "config/web.php" 63 | ] 64 | }, 65 | "yii\\composer\\Installer::postInstall": { 66 | "setPermission": [ 67 | { 68 | "runtime": "0777", 69 | "web/assets": "0777", 70 | "yii": "0755", 71 | "upload/images": "0777" 72 | } 73 | ] 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /config/console.php: -------------------------------------------------------------------------------- 1 | 'basic-console', 10 | 'basePath' => dirname(__DIR__), 11 | 'bootstrap' => ['log', 'gii'], 12 | 'controllerNamespace' => 'app\commands', 13 | 'modules' => [ 14 | 'gii' => 'yii\gii\Module', 15 | ], 16 | 'components' => [ 17 | 'cache' => [ 18 | 'class' => 'yii\caching\FileCache', 19 | ], 20 | 'log' => [ 21 | 'targets' => [ 22 | [ 23 | 'class' => 'yii\log\FileTarget', 24 | 'levels' => ['error', 'warning'], 25 | ], 26 | ], 27 | ], 28 | 'db' => $db, 29 | ], 30 | 'params' => $params, 31 | ]; 32 | -------------------------------------------------------------------------------- /config/db.php: -------------------------------------------------------------------------------- 1 | 'yii\db\Connection', 5 | 'dsn' => 'mysql:host=localhost;dbname=lazy_waimai', 6 | 'username' => 'root', 7 | 'password' => '123456', 8 | 'charset' => 'utf8', 9 | ]; 10 | -------------------------------------------------------------------------------- /config/params.php: -------------------------------------------------------------------------------- 1 | 'admin@example.com', 5 | 6 | // 图片配置 7 | 'imageBaseUrl' => 'http://localhost/LazyWaimai-Web', 8 | 9 | 'imageUploadPath' => '@upload/images' 10 | ]; 11 | -------------------------------------------------------------------------------- /config/web.php: -------------------------------------------------------------------------------- 1 | 'basic', 7 | 'name' => '懒人外卖管理平台', 8 | 'basePath' => dirname(__DIR__), 9 | 'bootstrap' => ['log'], 10 | 'aliases' => [ 11 | '@upload' => '@app/upload', 12 | ], 13 | 'components' => [ 14 | 'request' => [ 15 | // !!! insert a secret key in the following (if it is empty) - this is required by cookie validation 16 | 'cookieValidationKey' => 'MrwoEsBNtoOdrBTUNbVufuYhN6P6OvzM', 17 | ], 18 | 'cache' => [ 19 | 'class' => 'yii\caching\FileCache', 20 | ], 21 | 'user' => [ 22 | 'identityClass' => 'app\models\Admin', 23 | 'enableAutoLogin' => true, 24 | 'loginUrl' => ['user/login'] 25 | ], 26 | 'urlManager' => [ 27 | 'enablePrettyUrl' => true, 28 | 'showScriptName' => false 29 | ], 30 | 'ucpass' => [ 31 | 'class' => 'app\components\Ucpaas', 32 | 'accountSid' => '3550344c4a362cffbb14ca55b4683772', 33 | 'token' => '2e3fcf93c16a77209145f74e3c234532', 34 | 'appId' => 'a8ef8cf06c1b4cef81ed57d7de7ceead', 35 | 'templateId' => '12084', 36 | ], 37 | 'qiniu' => [ 38 | 'class' => 'app\components\QiNiu', 39 | 'accessKey' => 'jqg0WMfuswsDOXwEywf_imIMl0iWGPPM5gRU7zfv', 40 | 'secretKey' => 'Q0iJyLo1NcwTf5RVx-qQtqvAXPcfDGq38uJRXNX3', 41 | 'bucket' => 'lazywaimai', 42 | 'domain' => 'ocd605cmo.bkt.clouddn.com', 43 | ], 44 | 'errorHandler' => [ 45 | 'errorAction' => 'site/error', 46 | ], 47 | 'mailer' => [ 48 | 'class' => 'yii\swiftmailer\Mailer', 49 | // send all mails to a file by default. You have to set 50 | // 'useFileTransport' to false and configure a transport 51 | // for the mailer to send real emails. 52 | 'useFileTransport' => true, 53 | ], 54 | 'log' => [ 55 | 'traceLevel' => YII_DEBUG ? 3 : 0, 56 | 'targets' => [ 57 | [ 58 | 'class' => 'yii\log\FileTarget', 59 | 'levels' => ['error', 'warning'], 60 | ], 61 | ], 62 | ], 63 | 'db' => require(__DIR__ . '/db.php'), 64 | ], 65 | 'params' => $params, 66 | ]; 67 | 68 | if (YII_ENV_DEV) { 69 | // configuration adjustments for 'dev' environment 70 | $config['bootstrap'][] = 'debug'; 71 | $config['modules']['debug'] = 'yii\debug\Module'; 72 | 73 | $config['bootstrap'][] = 'gii'; 74 | $config['modules']['gii'] = 'yii\gii\Module'; 75 | } 76 | 77 | return $config; 78 | -------------------------------------------------------------------------------- /controllers/ActivityController.php: -------------------------------------------------------------------------------- 1 | [ 27 | 'class' => AccessControl::className(), 28 | 'rules' => [ 29 | [ 30 | 'allow' => true, 31 | 'roles' => ['@'], 32 | ], 33 | ], 34 | ] 35 | ]; 36 | } 37 | 38 | /** 39 | * 浏览商品列表的操作 40 | * 41 | * @return string 42 | */ 43 | public function actionIndex() { 44 | $model = new Activity(); 45 | $dataProvider = new ActiveDataProvider([ 46 | 'query' => BusinessActivity::activities(2), 47 | // 'sort' => [ 48 | // 'defaultOrder' => ['priority' => SORT_ASC] 49 | // ], 50 | 'pagination' => [ 51 | 'pageSize' => 20 52 | ] 53 | ]); 54 | 55 | return $this->render('index', [ 56 | 'dataProvider' => $dataProvider, 57 | 'model' => $model 58 | ]); 59 | } 60 | 61 | /** 62 | * 添加商品的操作 63 | * @return string|\yii\web\Response 64 | * @throws Exception 65 | */ 66 | public function actionAdd() { 67 | $model = new Product(); 68 | $model->setScenario('insert'); 69 | 70 | if ($model->load(Yii::$app->request->post())) { 71 | 72 | $model->image = UploadedFile::getInstance($model, 'image'); 73 | $model->business_id = Yii::$app->user->id; 74 | 75 | if ($model->validate()) { 76 | // 通过事务来保存数据 77 | $transaction = Yii::$app->db->beginTransaction(); 78 | try { 79 | // 生成一个随机的图片名并保存到数据库 80 | $model->image_path = Yii::$app->security->generateRandomString(10).'.'.$model->image->extension; 81 | if (!$model->save(false)) { 82 | throw new Exception('商品添加失败!'); 83 | } 84 | 85 | // 保存上传图片到服务器的指定目录 86 | $filename = Yii::getAlias(Yii::$app->params['product.imagePath']).DIRECTORY_SEPARATOR.$model->image_path; 87 | if (!$model->image->saveAs($filename)) { 88 | throw new Exception('商品图片添加失败!'); 89 | } 90 | 91 | $transaction->commit(); 92 | Yii::$app->session->setFlash('success', '成功添加商品“'.$model->name.'”。'); 93 | 94 | return $this->refresh(); 95 | } catch (Exception $e) { 96 | $transaction->rollBack(); 97 | Yii::$app->session->setFlash('danger', $e->getMessage()); 98 | } 99 | } 100 | } 101 | 102 | return $this->render('form', [ 103 | 'model' => $model 104 | ]); 105 | } 106 | 107 | /** 108 | * 更新商品的操作 109 | * @param $id 110 | * @return string|\yii\web\Response 111 | * @throws Exception 112 | * @throws NotFoundHttpException 113 | * @throws \yii\db\Exception 114 | */ 115 | public function actionUpdate($id) { 116 | /* @var $model Product */ 117 | $model = Product::findOne($id); 118 | 119 | if (!$model) { 120 | throw new NotFoundHttpException('未找到该商品。'); 121 | } 122 | 123 | if ($model->load(Yii::$app->request->post())) { 124 | 125 | $model->image = UploadedFile::getInstance($model, 'image'); 126 | 127 | if ($model->validate()) { 128 | if ($model->image !== null) { 129 | $model->image_path = Yii::$app->security->generateRandomString(10).'.'.$model->image->extension; 130 | } 131 | 132 | $transaction = Yii::$app->db->beginTransaction(); 133 | try { 134 | if (!$model->save(false)) { 135 | throw new Exception('商品更新失败!'); 136 | } 137 | 138 | if ($model->image !== null) { 139 | $filename = Yii::getAlias(Yii::$app->params['product.imagePath']).DIRECTORY_SEPARATOR.$model->image_path; 140 | if (!$model->image->saveAs($filename)) { 141 | throw new Exception('商品图片添加失败!'); 142 | } 143 | } 144 | 145 | $transaction->commit(); 146 | Yii::$app->session->setFlash('success', '成功更新商品“'.$model->name.'”。'); 147 | 148 | return $this->refresh(); 149 | } catch (Exception $e) { 150 | $transaction->rollBack(); 151 | Yii::$app->session->setFlash('danger', $e->getMessage()); 152 | } 153 | } 154 | } 155 | 156 | return $this->render('form', [ 157 | 'model' => $model 158 | ]); 159 | } 160 | 161 | /** 162 | * 删除商品的操作 163 | * @param $id 164 | * @return \yii\web\Response 165 | * @throws Exception 166 | * @throws NotFoundHttpException 167 | */ 168 | public function actionDelete($id) { 169 | /* @var $model Product */ 170 | $model = Product::findOne($id); 171 | 172 | if (!$model) { 173 | throw new NotFoundHttpException('未找到该商品。'); 174 | } 175 | 176 | // 删除数据库数据 177 | if ($model->delete()) { 178 | Yii::$app->session->setFlash('success', '成功删除商品“'.$model->name.'”。'); 179 | } else { 180 | Yii::$app->session->setFlash('danger', '删除商品失败。'); 181 | } 182 | 183 | return $this->redirect(['activity/index']); 184 | } 185 | } -------------------------------------------------------------------------------- /controllers/BusinessController.php: -------------------------------------------------------------------------------- 1 | [ 26 | 'class' => AccessControl::className(), 27 | 'rules' => [ 28 | [ 29 | 'allow' => true, 30 | 'roles' => ['@'], 31 | ], 32 | ], 33 | ], 34 | ]; 35 | } 36 | 37 | /** 38 | * @inheritdoc 39 | */ 40 | public function actions() { 41 | return [ 42 | 'gallery' => [ 43 | 'class' => GalleryAction::className(), 44 | ], 45 | ]; 46 | } 47 | 48 | /** 49 | * 店铺设置的操作 50 | */ 51 | public function actionSetup() { 52 | /* @var $admin Admin */ 53 | $admin = Admin::findOne(Yii::$app->user->id); 54 | /* @var $model Business */ 55 | $model = Business::findOne($admin->business_id); 56 | 57 | $json = file_get_contents(Yii::getAlias('@webroot').'/raw/booking_times.json'); 58 | $bookingTimes = Json::decode($json); 59 | 60 | if ($model->load(Yii::$app->request->post()) && $model->validate()) { 61 | if ($model->save()) { 62 | Yii::$app->session->setFlash('success', '店铺设置成功。'); 63 | } else { 64 | Yii::$app->session->setFlash('error', '系统异常,店铺设置失败。'); 65 | } 66 | } 67 | 68 | return $this->render('setup', [ 69 | 'model' => $model, 70 | 'bookingTimes' => $bookingTimes 71 | ]); 72 | } 73 | 74 | /** 75 | * 基本资料的操作 76 | */ 77 | public function actionProfile() { 78 | /* @var $admin Admin */ 79 | $admin = Admin::findOne(Yii::$app->user->id); 80 | /* @var $model Business */ 81 | $model = Business::findOne($admin->business_id); 82 | 83 | if ($model->load(Yii::$app->request->post())) { 84 | 85 | $model->image = UploadedFile::getInstance($model, 'image'); 86 | 87 | if ($model->validate()) { 88 | if ($model->image != null) { 89 | // 随机生成图片名 90 | $filename = Yii::$app->security->generateRandomString(10).'.'.$model->image->extension; 91 | // 使用七牛上传图片 92 | $model->pic_url = Yii::$app->qiniu->uploadFile($model->image->tempName, $filename); 93 | } 94 | 95 | if (!$model->save(false)) { 96 | Yii::$app->session->setFlash('danger', '资料修改失败'); 97 | } 98 | 99 | Yii::$app->session->setFlash('success', '资料修改成功。'); 100 | 101 | return $this->refresh(); 102 | } 103 | } 104 | 105 | return $this->render('profile', [ 106 | 'model' => $model 107 | ]); 108 | } 109 | 110 | /** 111 | * 店面实景的操作 112 | */ 113 | public function actionScene() { 114 | /* @var $admin Admin */ 115 | $admin = Admin::findOne(Yii::$app->user->id); 116 | 117 | // 门面 118 | $frontImages = BusinessScene::find() 119 | ->where([ 120 | 'business_id' => $admin->business_id, 121 | 'type' => BusinessScene::TYPE_FRONT 122 | ]) 123 | ->orderBy(['rank' => 'asc']) 124 | ->all(); 125 | // 大厅 126 | $foyerImages = BusinessScene::find() 127 | ->where([ 128 | 'business_id' => $admin->business_id, 129 | 'type' => BusinessScene::TYPE_FOYER 130 | ]) 131 | ->orderBy(['rank' => 'asc']) 132 | ->all(); 133 | // 后厨 134 | $kitchenImages = BusinessScene::find() 135 | ->where([ 136 | 'business_id' => $admin->business_id, 137 | 'type' => BusinessScene::TYPE_KITCHEN 138 | ]) 139 | ->orderBy(['rank' => 'asc']) 140 | ->all(); 141 | 142 | return $this->render('scene', [ 143 | 'frontImages' => $frontImages, 144 | 'foyerImages' => $foyerImages, 145 | 'kitchenImages' => $kitchenImages 146 | ]); 147 | } 148 | } -------------------------------------------------------------------------------- /controllers/CategoryController.php: -------------------------------------------------------------------------------- 1 | [ 27 | 'class' => AccessControl::className(), 28 | 'rules' => [ 29 | [ 30 | 'allow' => true, 31 | 'roles' => ['@'], 32 | ], 33 | ], 34 | ], 35 | ]; 36 | } 37 | 38 | /** 39 | * 浏览商品分类的操作 40 | */ 41 | public function actionIndex() { 42 | $searchModel = new CategorySearch(); 43 | $dataProvider = $searchModel->search(Yii::$app->request->get()); 44 | 45 | return $this->render('index', [ 46 | 'dataProvider' => $dataProvider, 47 | 'searchModel' => $searchModel 48 | ]); 49 | } 50 | 51 | /** 52 | * 添加商品分类的操作 53 | * @return string|Response 54 | */ 55 | public function actionAdd() { 56 | $model = new Category(); 57 | 58 | if ($model->load(Yii::$app->request->post())) { 59 | /* @var $admin Admin */ 60 | $admin = Admin::findOne(Yii::$app->user->id); 61 | $model->business_id = $admin->business_id; 62 | 63 | if ($model->validate()) { 64 | if ($model->save()) { 65 | Yii::$app->session->setFlash('success', '成功添加分类“'.$model->name.'”。'); 66 | return $this->redirect(['index']); 67 | } else { 68 | Yii::$app->session->setFlash('danger', '分类添加失败。'); 69 | } 70 | } 71 | } 72 | 73 | return $this->render('form', [ 74 | 'model' => $model 75 | ]); 76 | } 77 | 78 | /** 79 | * 修改商品分类的操作 80 | * @param $id 81 | * @return string|Response 82 | * @throws NotFoundHttpException 83 | */ 84 | public function actionUpdate($id) { 85 | /** @var $model Category */ 86 | $model = Category::findOne($id); 87 | 88 | if (!$model) { 89 | throw new NotFoundHttpException('未找到该分类。'); 90 | } 91 | 92 | if ($model->load(Yii::$app->request->post())) { 93 | if ($model->validate()) { 94 | if ($model->save()) { 95 | Yii::$app->session->setFlash('success', '成功更新分类“'.$model->name.'”。'); 96 | return $this->refresh(); 97 | } else { 98 | Yii::$app->session->setFlash('danger', '分类更新失败。'); 99 | } 100 | } 101 | } 102 | 103 | return $this->render('form', [ 104 | 'model' => $model 105 | ]); 106 | } 107 | 108 | /** 109 | * 删除商品分类的操作 110 | * @param $id 111 | * @return \yii\web\Response 112 | * @throws Exception 113 | * @throws NotFoundHttpException 114 | */ 115 | public function actionDelete($id) { 116 | /* @var $model Category */ 117 | $model = Category::findOne($id); 118 | 119 | if (!$model) { 120 | throw new NotFoundHttpException('未找到该商品分类。'); 121 | } 122 | 123 | $transaction = Yii::$app->db->beginTransaction(); 124 | try { 125 | if (!$model->delete()) { 126 | throw new Exception('删除商品分类失败!'); 127 | } 128 | 129 | if (!Product::deleteAll(['category_id' => $model->id])) { 130 | throw new Exception('删除商品分类下的商品失败!'); 131 | } 132 | 133 | $transaction->commit(); 134 | Yii::$app->session->setFlash('success', '删除商品分类成功“'.$model->name.'”。'); 135 | 136 | return $this->refresh(); 137 | } catch (Exception $e) { 138 | $transaction->rollBack(); 139 | Yii::$app->session->setFlash('danger', $e->getMessage()); 140 | } 141 | 142 | return $this->redirect(['category/index']); 143 | } 144 | } -------------------------------------------------------------------------------- /controllers/OrderController.php: -------------------------------------------------------------------------------- 1 | [ 23 | 'class' => AccessControl::className(), 24 | 'rules' => [ 25 | [ 26 | 'allow' => true, 27 | 'roles' => ['@'], 28 | ], 29 | ], 30 | ], 31 | ]; 32 | } 33 | 34 | /** 35 | * 店铺设置的操作 36 | * @param int $status 37 | * @return string 38 | */ 39 | public function actionIndex($status = Order::STATUS_WAIT_ACCEPT) { 40 | $searchModel = new OrderSearch(); 41 | $dataProvider = $searchModel->search(Yii::$app->request->get()); 42 | 43 | return $this->render('index', [ 44 | 'status' => $status, 45 | 'dataProvider' => $dataProvider, 46 | 'searchModel' => $searchModel 47 | ]); 48 | } 49 | 50 | public function actionStatus($id) { 51 | $id = Yii::$app->request->get('id'); 52 | $currentStatus = Yii::$app->request->get('current'); 53 | $targetStatus = Yii::$app->request->get('target'); 54 | 55 | /* @var $model Order */ 56 | $model = Order::findOne($id); 57 | if (!$model) { 58 | throw new NotFoundHttpException('未找到该订单。'); 59 | } 60 | $model->status = $targetStatus; 61 | if ($model->save(false)) { 62 | Yii::$app->session->setFlash('success', '操作成功.'); 63 | } else { 64 | Yii::$app->session->setFlash('success', '操作失败.'); 65 | } 66 | 67 | return $this->redirect(['order/index', 'status' => $currentStatus]); 68 | } 69 | } -------------------------------------------------------------------------------- /controllers/ProductController.php: -------------------------------------------------------------------------------- 1 | [ 26 | 'class' => AccessControl::className(), 27 | 'rules' => [ 28 | [ 29 | 'allow' => true, 30 | 'roles' => ['@'], 31 | ], 32 | ], 33 | ], 34 | ]; 35 | } 36 | 37 | /** 38 | * 浏览商品列表的操作 39 | * 40 | * @return string 41 | */ 42 | public function actionIndex() { 43 | $searchModel = new ProductSearch(); 44 | $dataProvider = $searchModel->search(Yii::$app->request->get()); 45 | 46 | return $this->render('index', [ 47 | 'model' => new Product(), 48 | 'dataProvider' => $dataProvider, 49 | 'searchModel' => $searchModel 50 | ]); 51 | } 52 | 53 | /** 54 | * 添加商品的操作 55 | * @return string|\yii\web\Response 56 | * @throws Exception 57 | */ 58 | public function actionAdd() { 59 | $model = new Product(); 60 | $model->setScenario('insert'); 61 | 62 | if ($model->load(Yii::$app->request->post())) { 63 | 64 | $model->image = UploadedFile::getInstance($model, 'image'); 65 | 66 | /* @var $admin Admin */ 67 | $admin = Admin::findOne(Yii::$app->user->id); 68 | $model->business_id = $admin->business_id; 69 | 70 | if ($model->validate()) { 71 | // 随机生成图片名 72 | $filename = Yii::$app->security->generateRandomString(10).'.'.$model->image->extension; 73 | // 使用七牛上传图片并保存到数据库 74 | $model->image_path = Yii::$app->qiniu->uploadFile($model->image->tempName, $filename); 75 | if (!$model->save(false)) { 76 | Yii::$app->session->setFlash('danger', '商品添加失败'); 77 | } 78 | 79 | Yii::$app->session->setFlash('success', '成功添加商品“'.$model->name.'”。'); 80 | 81 | return $this->redirect(['index']); 82 | } 83 | } 84 | 85 | return $this->render('form', [ 86 | 'model' => $model 87 | ]); 88 | } 89 | 90 | /** 91 | * 更新商品的操作 92 | * @param $id 93 | * @return string|\yii\web\Response 94 | * @throws Exception 95 | * @throws NotFoundHttpException 96 | * @throws \yii\db\Exception 97 | */ 98 | public function actionUpdate($id) { 99 | /* @var $model Product */ 100 | $model = Product::findOne($id); 101 | 102 | if (!$model) { 103 | throw new NotFoundHttpException('未找到该商品。'); 104 | } 105 | 106 | if ($model->load(Yii::$app->request->post())) { 107 | 108 | $model->image = UploadedFile::getInstance($model, 'image'); 109 | 110 | if ($model->validate()) { 111 | // 如果有更新图片 112 | if ($model->image !== null) { 113 | // 随机生成图片名 114 | $filename = Yii::$app->security->generateRandomString(10).'.'.$model->image->extension; 115 | // 使用七牛上传图片 116 | $model->image_path = Yii::$app->qiniu->uploadFile($model->image->tempName, $filename); 117 | } 118 | 119 | if (!$model->save(false)) { 120 | Yii::$app->session->setFlash('danger', '商品更新失败'); 121 | } 122 | 123 | Yii::$app->session->setFlash('success', '成功更新商品“'.$model->name.'”。'); 124 | 125 | return $this->refresh(); 126 | } 127 | } 128 | 129 | return $this->render('form', [ 130 | 'model' => $model 131 | ]); 132 | } 133 | 134 | /** 135 | * 删除商品的操作 136 | * @param $id 137 | * @return \yii\web\Response 138 | * @throws Exception 139 | * @throws NotFoundHttpException 140 | */ 141 | public function actionDelete($id) { 142 | /* @var $model Product */ 143 | $model = Product::findOne($id); 144 | 145 | if (!$model) { 146 | throw new NotFoundHttpException('未找到该商品。'); 147 | } 148 | 149 | // 删除数据库数据 150 | if ($model->delete()) { 151 | Yii::$app->session->setFlash('success', '成功删除商品“'.$model->name.'”。'); 152 | } else { 153 | Yii::$app->session->setFlash('danger', '删除商品失败。'); 154 | } 155 | 156 | return $this->redirect(['product/index']); 157 | } 158 | } -------------------------------------------------------------------------------- /controllers/SiteController.php: -------------------------------------------------------------------------------- 1 | [ 24 | 'class' => AccessControl::className(), 25 | 'only' => ['index', 'error'], 26 | 'rules' => [ 27 | [ 28 | 'allow' => true, 29 | 'actions' => ['error'], 30 | 'roles' => ['?'], 31 | ], 32 | [ 33 | 'allow' => true, 34 | 'actions' => ['index'], 35 | 'roles' => ['@'], 36 | ], 37 | ], 38 | ], 39 | 'verbs' => [ 40 | 'class' => VerbFilter::className(), 41 | 'actions' => [ 42 | 'index' => ['get', 'post'], 43 | 'error' => ['get', 'post'], 44 | ], 45 | ], 46 | ]; 47 | } 48 | 49 | /** 50 | * 主页仪表盘的操作 51 | * @return string 52 | */ 53 | public function actionIndex() { 54 | $last15days = []; 55 | $last6Month = []; 56 | $numDataOrder = []; // 订单生成数据 57 | $numDataUser = []; // 用户注册数据 58 | $numDataVolume = []; // 营业额数据 59 | $numDataCompleted = []; // 订单完成数据 60 | $numDataVolumeMonth = []; // 每月营业额 61 | 62 | $today = strtotime("00:00:00"); 63 | $todayEnd = strtotime("23:59:59"); 64 | for ($i = 0; $i < 15; $i++) { 65 | $timestrap = strtotime('-' . $i . ' days', $today); 66 | $timestrapEnd = strtotime('-' . $i . ' days', $todayEnd); 67 | $where = [ 68 | 'and', 69 | ['>=', 'created_at', $timestrap], 70 | ['<=', 'created_at', $timestrapEnd] 71 | ]; 72 | array_unshift($last15days, date('m/d', $timestrap)); 73 | array_unshift($numDataOrder, Order::find()->where($where)->count()); 74 | array_unshift($numDataUser, User::find()->where($where)->count()); 75 | 76 | $data = Order::find()->select(['sum(total_price) AS volume', 'count(*) AS count']) 77 | ->where($where) 78 | ->asArray() 79 | ->one(); 80 | array_unshift($numDataVolume, $data['volume']); 81 | array_unshift($numDataCompleted, $data['count']); 82 | } 83 | 84 | for ($i = 0; $i < 6; $i ++) { 85 | $timestrap = strtotime("first day of -{$i} month", $today); 86 | $timestrapEnd = strtotime("last day of -{$i} month", $todayEnd); 87 | $where = [ 88 | 'and', 89 | ['>=', 'created_at', $timestrap], 90 | ['<=', 'created_at', $timestrapEnd] 91 | ]; 92 | array_unshift($last6Month, date('Y/m', $timestrap)); 93 | array_unshift($numDataVolumeMonth, Order::find()->where($where)->sum('total_price')); 94 | } 95 | 96 | $data2 = Order::find()->select(['sum(total_price) AS volume', 'count(*) AS count']) 97 | ->asArray() 98 | ->one(); 99 | 100 | return $this->render('index', [ 101 | 'last15days' => $last15days, 102 | 'last6Month' => $last6Month, 103 | 'numDataOrder' => $numDataOrder, 104 | 'numDataUser' => $numDataUser, 105 | 'numDataVolume' => $numDataVolume, 106 | 'numDataCompleted' => $numDataCompleted, 107 | 'numDataVolumeMonth' => $numDataVolumeMonth, 108 | 'countOrder' => Order::find()->count(), 109 | 'countCompleted' => $data2['count'], 110 | 'sumVolume' => $data2['volume'] ?: '0.00', 111 | 'countUser' => User::find()->count(), 112 | ]); 113 | } 114 | 115 | /** 116 | * 显示系统报错的操作 117 | * @return string 118 | */ 119 | public function actionError() { 120 | if (Yii::$app->user->isGuest) { 121 | $this->layout = 'base'; 122 | } 123 | 124 | if (($exception = Yii::$app->getErrorHandler()->exception) === null) { 125 | return ''; 126 | } 127 | 128 | if ($exception instanceof HttpException) { 129 | $code = $exception->statusCode; 130 | } else { 131 | $code = $exception->getCode(); 132 | } 133 | if ($exception instanceof Exception) { 134 | $name = $exception->getName(); 135 | } else { 136 | $name = '错误'; 137 | } 138 | if ($code) { 139 | $name .= " (#$code)"; 140 | } 141 | 142 | if ($exception instanceof UserException) { 143 | $message = $exception->getMessage(); 144 | } else { 145 | $message = '服务器错误!'; 146 | } 147 | 148 | if (Yii::$app->getRequest()->getIsAjax()) { 149 | return "$name: $message"; 150 | } else { 151 | return $this->render('error', [ 152 | 'name' => $name, 153 | 'message' => $message, 154 | 'exception' => $exception, 155 | ]); 156 | } 157 | } 158 | } -------------------------------------------------------------------------------- /controllers/UserController.php: -------------------------------------------------------------------------------- 1 | [ 33 | 'class' => AccessControl::className(), 34 | 'only' => ['login', 'logout', 'send-sms'], 35 | 'rules' => [ 36 | [ 37 | 'allow' => true, 38 | 'actions' => ['login', 'send-sms'], 39 | 'roles' => ['?'], 40 | ], 41 | [ 42 | 'allow' => true, 43 | 'actions' => ['logout'], 44 | 'roles' => ['@'], 45 | ], 46 | ], 47 | ], 48 | 'verbs' => [ 49 | 'class' => VerbFilter::className(), 50 | 'actions' => [ 51 | 'login' => ['get', 'post'], 52 | 'logout' => ['get'], 53 | 'send-sms' => ['post'], 54 | ], 55 | ], 56 | ]; 57 | } 58 | 59 | /** 60 | * 个人资料的操作 61 | */ 62 | public function actionProfile() { 63 | /* @var $model Admin */ 64 | $model = Admin::findOne(Yii::$app->user->id); 65 | 66 | if (!$model) { 67 | throw new NotFoundHttpException('未找到该管理员。'); 68 | } 69 | 70 | if ($model->load(Yii::$app->request->post()) && $model->validate()) { 71 | if ($model->save()) { 72 | Yii::$app->session->setFlash('success', '个人资料修改成功.'); 73 | } else { 74 | Yii::$app->session->setFlash('success', '个人资料修改失败.'); 75 | } 76 | } 77 | 78 | return $this->render('profile', [ 79 | 'model' => $model 80 | ]); 81 | } 82 | 83 | /** 84 | * 用户登录的操作 85 | */ 86 | public function actionLogin() { 87 | $this->layout = 'base'; 88 | 89 | return $this->render('login'); 90 | } 91 | 92 | /** 93 | * 用户注销登录的操作 94 | */ 95 | public function actionLogout() { 96 | if (Yii::$app->user->logout()) { 97 | return $this->redirect(['user/login']); 98 | } else { 99 | return $this->goBack(); 100 | } 101 | } 102 | 103 | /** 104 | * 通过ajax发送登录的验证码的操作 105 | * @return array 106 | */ 107 | public function actionSendLoginSms() { 108 | $model = new LoginSendSmsForm(); 109 | $model->phone = Yii::$app->request->post('phone'); 110 | 111 | if ($model->sendSms()) { 112 | return Json::encode(['status' => 'ok']); 113 | } else { 114 | $message = $model->getFirstError('phone'); 115 | return Json::encode(['status' => 'err', 'message' => $message]); 116 | } 117 | } 118 | 119 | /** 120 | * 通过ajax进行手机号登录的操作 121 | * @return array 122 | */ 123 | public function actionPhoneLogin() { 124 | $model = new PhoneLoginForm(); 125 | 126 | if ($model->load(Yii::$app->request->post(), '') && $model->login()) { 127 | return $this->redirect(['site/index']); 128 | } else { 129 | $message = $model->getFirstError('code'); 130 | return Json::encode(['status' => 'err', 'message' => $message]); 131 | } 132 | } 133 | 134 | /** 135 | * 通过ajax进行账户登录的操作 136 | * @return array 137 | */ 138 | public function actionAccountLogin() { 139 | $model = new AccountLoginForm(); 140 | 141 | if ($model->load(Yii::$app->request->post(), '') && $model->login()) { 142 | return $this->redirect(['site/index']); 143 | } else { 144 | $message = $model->getFirstError('password'); 145 | return Json::encode(['status' => 'err', 'message' => $message]); 146 | } 147 | } 148 | 149 | /** 150 | * 修改密码的操作 151 | * @return array 152 | */ 153 | public function actionUpdatePassword() { 154 | $model = new UpdatePasswordForm(); 155 | 156 | if ($model->load(Yii::$app->request->post()) && $model->updatePassword()) { 157 | Yii::$app->session->setFlash('success', '密码修改成功。'); 158 | return $this->refresh(); 159 | } 160 | 161 | return $this->render('update-password', [ 162 | 'model' => $model 163 | ]); 164 | } 165 | 166 | /** 167 | * 通过ajax发送修改手机号的验证码的操作 168 | * @return array 169 | */ 170 | public function actionSendUpdatePhoneSms() { 171 | $model = new SendChangePhoneSmsForm(); 172 | $model->phone = Yii::$app->request->post('phone'); 173 | 174 | if ($model->sendSms()) { 175 | return Json::encode(['status' => 'ok']); 176 | } else { 177 | $message = $model->getFirstError('phone'); 178 | return Json::encode(['status' => 'err', 'message' => $message]); 179 | } 180 | } 181 | 182 | /** 183 | * 修改手机号的操作 184 | * @param string $step 185 | * @return array 186 | */ 187 | public function actionUpdatePhone($step = '1') { 188 | $params = ['step' => $step]; 189 | 190 | if ($step === '1') { 191 | $verifyPasswordForm = new VerifyPasswordForm(); 192 | 193 | if ($verifyPasswordForm->load(Yii::$app->request->post()) && $verifyPasswordForm->validate()) { 194 | Yii::$app->session['passwordVerified'] = true; 195 | 196 | return $this->redirect(['user/update-phone', 'step' => '2']); 197 | } 198 | 199 | /** @var $admin Admin */ 200 | $admin = Yii::$app->user->identity; 201 | 202 | $params['phone'] = $admin->phone; 203 | $params['verifyPasswordForm'] = $verifyPasswordForm; 204 | } elseif ($step === '2' && Yii::$app->session->has('passwordVerified') && Yii::$app->session['passwordVerified']) { 205 | $changeMobileForm = new ChangePhoneForm(); 206 | 207 | if ($changeMobileForm->load(Yii::$app->request->post()) && $changeMobileForm->change()) { 208 | Yii::$app->session->setFlash('success', '手机更换成功!'); 209 | return $this->redirect(['user/update-phone']); 210 | } 211 | 212 | $params['changeMobileForm'] = $changeMobileForm; 213 | } else { 214 | return $this->redirect(['user/update-phone']); 215 | } 216 | 217 | return $this->render('update-phone', $params); 218 | } 219 | } -------------------------------------------------------------------------------- /mail/layouts/html.php: -------------------------------------------------------------------------------- 1 | 8 | beginPage() ?> 9 | 10 | 11 | 12 | 13 | <?= Html::encode($this->title) ?> 14 | head() ?> 15 | 16 | 17 | beginBody() ?> 18 | 19 | endBody() ?> 20 | 21 | 22 | endPage() ?> 23 | -------------------------------------------------------------------------------- /models/AccountLoginForm.php: -------------------------------------------------------------------------------- 1 | '用户名', 36 | 'password' => '密码', 37 | 'remember' => '记住我1周', 38 | ]; 39 | } 40 | 41 | /** 42 | * Validates the password. 43 | * This method serves as the inline validation for password. 44 | * 45 | * @param string $attribute the attribute currently being validated 46 | * @param array $params the additional name-value pairs given in the rule 47 | */ 48 | public function validatePassword($attribute, $params) { 49 | if (!$this->hasErrors()) { 50 | $user = $this->getUser(); 51 | if (!$user) { 52 | $this->addError($attribute, '不存在该用户名.'); 53 | } else if (!$user->validatePassword($this->password)) { 54 | $this->addError($attribute, ' 密码输入错误.'); 55 | } 56 | } 57 | } 58 | 59 | /** 60 | * Logs in a admin using the provided username and password. 61 | * 62 | * @return boolean whether the user is logged in successfully 63 | */ 64 | public function login() { 65 | if ($this->validate()) { 66 | return Yii::$app->user->login($this->getUser(), $this->remember ? 3600 * 24 * 7 : 0); 67 | } else { 68 | return false; 69 | } 70 | } 71 | 72 | /** 73 | * Finds user by [[username]] 74 | * 75 | * @return Admin|null 76 | */ 77 | public function getUser() { 78 | if ($this->_user === false) { 79 | $this->_user = Admin::findByUsername($this->username); 80 | } 81 | 82 | return $this->_user; 83 | } 84 | } -------------------------------------------------------------------------------- /models/Activity.php: -------------------------------------------------------------------------------- 1 | 50], 40 | [['description'], 'string', 'max' => 100], 41 | [['icon_name', 'icon_color'], 'string', 'max' => 10], 42 | ]; 43 | } 44 | 45 | /** 46 | * @inheritdoc 47 | */ 48 | public function attributeLabels() 49 | { 50 | return [ 51 | 'id' => '主键ID', 52 | 'name' => '活动的名称', 53 | 'description' => '活动的描述', 54 | 'icon_name' => '活动图标的文字', 55 | 'icon_color' => '活动图标的颜色', 56 | 'code' => '逻辑code', 57 | 'is_share' => '是否和其他活动共享', 58 | 'priority' => '活动的优先级', 59 | 'created_at' => '创建时间', 60 | 'updated_at' => 'Updated At', 61 | ]; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /models/Admin.php: -------------------------------------------------------------------------------- 1 | 'trim'], 68 | ['user_name', 'string', 'min' => 4, 'max' => 20], 69 | ['user_name', 'match', 'pattern' => '/^[A-Za-z_-][A-Za-z0-9_-]+$/'], 70 | ['user_name', 'unique', 'message' => '该用户名已被使用'], 71 | 72 | ['business_id', 'required'], 73 | 74 | ['real_name', 'filter', 'filter' => 'trim'], 75 | ['real_name', 'required'], 76 | ['real_name', 'string', 'min' => 2, 'max' => 20], 77 | 78 | ['identity_num', 'required'], 79 | ['identity_num', 'string', 'max' => 20], 80 | 81 | ['email', 'filter', 'filter' => 'trim'], 82 | ['email', 'required'], 83 | ['email', 'email'], 84 | 85 | [['password'], 'required', 'on' => 'insert'], 86 | [['password'], 'string', 'min' => 6, 'max' => 24], 87 | [['password'], 'match', 'pattern' => '/^\S+$/'], 88 | 89 | ['gender', 'default', 'value' => self::GENDER_MALE], 90 | ['gender', 'in', 'range' => [self::GENDER_MALE, self::GENDER_WOMAN, self::GENDER_OTHER]], 91 | 92 | ['phone', 'required'], 93 | ['phone', 'match', 'pattern' => '/^1[3|4|5|7|8][0-9]{9}$/'], 94 | ]; 95 | } 96 | 97 | /** 98 | * @inheritdoc 99 | */ 100 | public function attributeLabels() { 101 | return [ 102 | 'id' => 'ID', 103 | 'business_id' => '管理的商铺ID', 104 | 'user_name' => '用户名', 105 | 'real_name' => '真实姓名', 106 | 'identity_num' => '身份证号', 107 | 'password' => '密码', 108 | 'gender' => '性别', 109 | 'email' => '邮箱', 110 | 'phone' => '手机', 111 | 'status' => '状态', 112 | 'created_at' => '创建时间', 113 | 'updated_at' => '更新时间', 114 | ]; 115 | } 116 | 117 | /** 118 | * @inheritdoc 119 | */ 120 | public static function findIdentity($id) { 121 | return static::findOne(['id' => $id, 'status' => self::STATUS_ACTIVE]); 122 | } 123 | 124 | /** 125 | * @inheritdoc 126 | */ 127 | public static function findIdentityByAccessToken($token, $type = null) { 128 | throw new NotSupportedException('"findIdentityByAccessToken" is not implemented.'); 129 | } 130 | 131 | /** 132 | * Finds user by username 133 | * 134 | * @param string $username 135 | * @return Admin|null 136 | */ 137 | public static function findByUsername($username) { 138 | return static::findOne(['user_name' => $username, 'status' => self::STATUS_ACTIVE]); 139 | } 140 | 141 | /** 142 | * @inheritdoc 143 | */ 144 | public function getId() { 145 | return $this->getPrimaryKey(); 146 | } 147 | 148 | /** 149 | * @inheritdoc 150 | */ 151 | public function getAuthKey() { 152 | return $this->auth_key; 153 | } 154 | 155 | /** 156 | * @inheritdoc 157 | */ 158 | public function validateAuthKey($authKey) { 159 | return $this->getAuthKey() === $authKey; 160 | } 161 | 162 | /** 163 | * Validates password 164 | * 165 | * @param string $password password to validate 166 | * @return boolean if password provided is valid for current user 167 | */ 168 | public function validatePassword($password) { 169 | return Yii::$app->security->validatePassword($password, $this->password_hash); 170 | } 171 | 172 | /** 173 | * Generates password hash from password and sets it to the model 174 | * 175 | * @param string $password 176 | */ 177 | public function setPassword($password) { 178 | $this->password_hash = Yii::$app->security->generatePasswordHash($password); 179 | } 180 | 181 | /** 182 | * Generates "remember me" authentication key 183 | */ 184 | public function generateAuthKey() { 185 | $this->auth_key = Yii::$app->security->generateRandomString(); 186 | } 187 | 188 | public static function getGenderList() { 189 | if (self::$_genderList === null) { 190 | self::$_genderList = [ 191 | self::GENDER_MALE => '男', 192 | self::GENDER_WOMAN => '女', 193 | self::GENDER_OTHER => '保密' 194 | ]; 195 | } 196 | 197 | return self::$_genderList; 198 | } 199 | 200 | public function getGenderMsg() { 201 | $list = self::getGenderList(); 202 | 203 | return isset($list[$this->gender]) ? $list[$this->gender] : null; 204 | } 205 | 206 | public static function getStatusList() { 207 | if (self::$_statusList === null) { 208 | self::$_statusList = [ 209 | self::STATUS_ACTIVE => '正常', 210 | self::STATUS_BLOCKED => '禁用' 211 | ]; 212 | } 213 | 214 | return self::$_statusList; 215 | } 216 | 217 | public function getStatusMsg() { 218 | $list = self::getStatusList(); 219 | 220 | return isset($list[$this->status]) ? $list[$this->status] : null; 221 | } 222 | } -------------------------------------------------------------------------------- /models/Business.php: -------------------------------------------------------------------------------- 1 | 50], 63 | [['phone', 'shipping_time'], 'string', 'max' => 20], 64 | [['address'], 'string', 'max' => 100], 65 | [['pic_url'], 'string', 'max' => 200], 66 | [['bulletin'], 'string', 'max' => 300], 67 | ]; 68 | } 69 | 70 | /** 71 | * @inheritdoc 72 | */ 73 | public function attributeLabels() { 74 | return [ 75 | 'id' => 'ID', 76 | 'name' => '店名', 77 | 'phone' => '联系电话', 78 | 'address' => '地址', 79 | 'status' => '营业状态', 80 | 'opening_time' => '营业时间', 81 | 'pic_url' => 'Logo地址', 82 | 'image' => '店铺LOGO', 83 | 'shipping_fee' => '配送费', 84 | 'package_fee' => '打包费', 85 | 'min_price' => '起送价', 86 | 'shipping_time' => '平均配送时间', 87 | 'month_sales' => '月销量', 88 | 'bulletin' => '公告', 89 | 'category' => '分类', 90 | 'booking_times' => '可预订的时间段', 91 | 'updated_at' => '最近修改时间', 92 | 'created_at' => '创建时间', 93 | ]; 94 | } 95 | } -------------------------------------------------------------------------------- /models/BusinessActivity.php: -------------------------------------------------------------------------------- 1 | 100], 38 | ]; 39 | } 40 | 41 | /** 42 | * @inheritdoc 43 | */ 44 | public function attributeLabels() { 45 | return [ 46 | 'id' => '主键ID', 47 | 'business_id' => '商户ID', 48 | 'activity_id' => '活动ID', 49 | 'attribute' => '解析description所用的json数据', 50 | 'created_at' => '创建时间', 51 | 'updated_at' => 'Updated At', 52 | ]; 53 | } 54 | 55 | public static function activities($businessId) { 56 | $query = new Query(); 57 | return $query->select([ 58 | 'a.id', 59 | 'b.name', 60 | 'b.description', 61 | 'a.attribute', 62 | 'b.icon_name', 63 | 'b.icon_color', 64 | 'b.code', 65 | 'b.is_share', 66 | 'b.priority' 67 | ]) 68 | ->from('business_activity a') 69 | ->innerJoin('activity b', 'a.activity_id=b.id') 70 | ->where(['a.business_id' => $businessId]); 71 | } 72 | } -------------------------------------------------------------------------------- /models/BusinessScene.php: -------------------------------------------------------------------------------- 1 | 100], 57 | ]; 58 | } 59 | 60 | /** 61 | * @inheritdoc 62 | */ 63 | public function attributeLabels() 64 | { 65 | return [ 66 | 'id' => 'ID', 67 | 'business_id' => 'Business ID', 68 | 'type' => '实景类别,1=门面、2=大堂、3=后厨', 69 | 'original_name' => 'Original Name', 70 | 'original_url' => 'Original Url', 71 | 'thumb_name' => 'Thumb Name', 72 | 'thumb_url' => 'Thumb Url', 73 | 'rank' => 'Rank', 74 | 'created_at' => 'Created At', 75 | 'updated_at' => 'Updated At', 76 | ]; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /models/Category.php: -------------------------------------------------------------------------------- 1 | 30], 46 | [['description'], 'string', 'max' => 50], 47 | [['icon_url'], 'string', 'max' => 200], 48 | ]; 49 | } 50 | 51 | /** 52 | * @inheritdoc 53 | */ 54 | public function attributeLabels() { 55 | return [ 56 | 'id' => 'ID', 57 | 'business_id' => 'Business ID', 58 | 'name' => '分类名', 59 | 'description' => '描述', 60 | 'icon_url' => '图标url', 61 | 'created_at' => '创建时间', 62 | 'updated_at' => '最近更新时间', 63 | ]; 64 | } 65 | 66 | /** 67 | * 获取指定商家的所有商品分类(id name)键值对 68 | * @return array 69 | */ 70 | public static function getKeyValuePairs() { 71 | /* @var $admin Admin */ 72 | $admin = Admin::findOne(Yii::$app->user->id); 73 | 74 | $sql = "SELECT `id`, `name` FROM ".self::tableName()." WHERE `business_id`=".$admin->business_id." ORDER BY `id` ASC"; 75 | 76 | return Yii::$app->db->createCommand($sql)->queryAll(\PDO::FETCH_KEY_PAIR); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /models/CategorySearch.php: -------------------------------------------------------------------------------- 1 | user->id); 26 | 27 | $query = Category::find(); 28 | $dataProvider = new ActiveDataProvider([ 29 | 'query' => $query->where(['business_id' => $admin->business_id]), 30 | 'sort' => [ 31 | 'defaultOrder' => ['id' => SORT_ASC] 32 | ], 33 | 'pagination' => [ 34 | 'pageSize' => 20 35 | ] 36 | ]); 37 | 38 | if (!($this->load($params) && $this->validate())) { 39 | return $dataProvider; 40 | } 41 | 42 | $dateBegin = strtotime($this->date); 43 | $dateEnd = $dateBegin + 86400; 44 | 45 | $query->andFilterWhere(['like', 'name', $this->name]) 46 | ->andFilterWhere(['like', 'description', $this->description]) 47 | ->andFilterWhere(['>=', 'created_at', $this->date ? $dateBegin : null]) 48 | ->andFilterWhere(['<', 'created_at', $this->date ? $dateEnd : null]); 49 | 50 | return $dataProvider; 51 | } 52 | } -------------------------------------------------------------------------------- /models/ChangePhoneForm.php: -------------------------------------------------------------------------------- 1 | '/^1[3|5|7|8|][0-9]{9}$/'], 25 | [['phone'], 'unique', 'targetClass' => '\backend\models\Admin', 'message' => '该手机号已被注册!'], 26 | [['phone'], function ($attribute, $params) { 27 | $session = Yii::$app->session; 28 | if ($session->has('changePhoneSendPhone') && $session['changePhoneSendPhone'] !== $this->phone) { 29 | $this->addError($attribute, '该手机号与上次不匹配!'); 30 | } 31 | }], 32 | 33 | [['verifyCode'], 'string', 'length' => 6], 34 | [['verifyCode'], function ($attribute, $params) { 35 | $session = Yii::$app->session; 36 | if (!$session->has('changePhoneSendPhone') || !$session->has('changePhoneVerifyCode')) { 37 | $this->addError($attribute, '请您发送验证码!'); 38 | return; 39 | } 40 | if ($session['changePhoneVerifyCode'] !== $this->verifyCode) { 41 | $this->addError($attribute, '验证码不匹配!'); 42 | } 43 | }], 44 | ]; 45 | } 46 | 47 | /** 48 | * @inheritdoc 49 | */ 50 | public function attributeLabels() { 51 | return [ 52 | 'phone' => '新手机号', 53 | 'verifyCode' => '验证码', 54 | ]; 55 | } 56 | 57 | /** 58 | * 执行修改手机号的错作 59 | * @param bool $runValidation 60 | * @return bool 61 | */ 62 | public function change($runValidation = true) { 63 | if ($runValidation && !$this->validate()) { 64 | return false; 65 | } 66 | 67 | /** @var $admin Admin */ 68 | $admin = Yii::$app->user->identity; 69 | $admin->phone = $this->phone; 70 | 71 | return $admin->save(false); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /models/LoginSendSmsForm.php: -------------------------------------------------------------------------------- 1 | '/^1[3|5|7|8|][0-9]{9}$/'], 21 | // 验证手机号是否存在 22 | ['phone', function ($attribute, $params) { 23 | $admin = Admin::findOne(['phone' => $this->phone]); 24 | if (!$admin) { 25 | $this->addError($attribute, '不存在该手机号!'); 26 | } 27 | }], 28 | // 验证获取验证码的频率 29 | ['phone', function ($attribute, $params) { 30 | $session = Yii::$app->session; 31 | if ($session->has('loginNextSendTime') && $session['loginNextSendTime'] > time()) { 32 | $this->addError($attribute, '发送验证码过于频繁。'); 33 | } 34 | }] 35 | ]; 36 | } 37 | 38 | /** 39 | * @inheritdoc 40 | */ 41 | public function attributeLabels() { 42 | return [ 43 | 'phone' => '手机号', 44 | ]; 45 | } 46 | 47 | /** 48 | * 发送短信验证码 49 | * @param bool $runValidation 50 | * @return bool 51 | */ 52 | public function sendSms($runValidation = true) { 53 | 54 | if ($runValidation && !$this->validate()) { 55 | return false; 56 | } 57 | 58 | $verifyCode = (string) mt_rand(100000, 999999); 59 | $validMinutes = 30; 60 | 61 | // 调用云之讯组件发送模板短信 62 | /** @var $ucpass Ucpaas */ 63 | $ucpass = Yii::$app->ucpass; 64 | $ucpass->templateSMS($this->phone, $verifyCode.','.$validMinutes); 65 | 66 | if ($ucpass->state == Ucpaas::STATUS_SUCCESS) { 67 | $session = Yii::$app->session; 68 | $session['loginNextSendTime'] = time() + 60; 69 | $session['loginSendPhone'] = $this->phone; 70 | $session['loginVerifyCode'] = $verifyCode; 71 | 72 | return true; 73 | } else { 74 | $this->addError('phone', $ucpass->message); 75 | 76 | return false; 77 | } 78 | } 79 | } -------------------------------------------------------------------------------- /models/Order.php: -------------------------------------------------------------------------------- 1 | 50], 72 | [['consignee', 'phone', 'booked_at'], 'string', 'max' => 20], 73 | [['address'], 'string', 'max' => 100], 74 | [['remark'], 'string', 'max' => 200], 75 | ]; 76 | } 77 | 78 | /** 79 | * @inheritdoc 80 | */ 81 | public function attributeLabels() { 82 | return [ 83 | 'id' => '订单ID', 84 | 'cart_id' => '购物车ID', 85 | 'business_id' => '商铺ID', 86 | 'user_id' => '用户ID', 87 | 'order_num' => '订单编号', 88 | 'status' => '订单状态', 89 | 'origin_price' => '商品原价', 90 | 'discount_price' => '优惠价格', 91 | 'total_price' => '合计价格', 92 | 'consignee' => '联系人', 93 | 'phone' => '联系电话', 94 | 'address' => '收货地址', 95 | 'pay_method' => '支付方式', 96 | 'remark' => '备注', 97 | 'booked_at' => '预订时间', 98 | 'created_at' => '下单时间', 99 | 'updated_at' => 'Updated At', 100 | ]; 101 | } 102 | 103 | public static function getPayMethodList() { 104 | if (self::$_payMethodtList === null) { 105 | self::$_payMethodtList = [ 106 | self::PAYMENT_ONLINE => '在线支付', 107 | self::PAYMENT_OFFLINE => '货到付款' 108 | ]; 109 | } 110 | 111 | return self::$_payMethodtList; 112 | } 113 | 114 | public static function getStatusList() { 115 | if (self::$_statusList === null) { 116 | self::$_statusList = [ 117 | self::STATUS_WAIT_ACCEPT => '待接单', 118 | self::STATUS_WAIT_SEND => '待配送', 119 | self::STATUS_FINISHED => '已完成', 120 | ]; 121 | } 122 | 123 | return self::$_statusList; 124 | } 125 | } -------------------------------------------------------------------------------- /models/OrderSearch.php: -------------------------------------------------------------------------------- 1 | user->id); 28 | 29 | $query = Order::find(); 30 | $dataProvider = new ActiveDataProvider([ 31 | 'query' => $query->where([ 32 | 'business_id' => $admin->business_id, 33 | 'status' => $params['status'] 34 | ]), 35 | 'sort' => [ 36 | 'defaultOrder' => ['created_at' => SORT_DESC] 37 | ], 38 | 'pagination' => [ 39 | 'pageSize' => 20 40 | ] 41 | ]); 42 | 43 | if (!($this->load($params) && $this->validate())) { 44 | return $dataProvider; 45 | } 46 | 47 | $this->_addDigitalFilter($query, 'order_num'); 48 | $this->_addDigitalFilter($query, 'phone'); 49 | $this->_addDigitalFilter($query, 'origin_price'); 50 | $this->_addDigitalFilter($query, 'discount_price'); 51 | $this->_addDigitalFilter($query, 'total_price'); 52 | 53 | $dateBegin = strtotime($this->date); 54 | $dateEnd = $dateBegin + 86400; 55 | 56 | // adjust the query by adding the filters 57 | $query->andFilterWhere(['id' => $this->id]) 58 | ->andFilterWhere(['like', 'consignee', $this->consignee]) 59 | ->andFilterWhere(['like', 'address', $this->address]) 60 | ->andFilterWhere(['like', 'remark', $this->remark]) 61 | ->andFilterWhere(['pay_method' => $this->pay_method]) 62 | ->andFilterWhere(['>=', 'created_at', $this->date ? $dateBegin : null]) 63 | ->andFilterWhere(['<', 'created_at', $this->date ? $dateEnd : null]); 64 | 65 | return $dataProvider; 66 | } 67 | 68 | /** 69 | * 附加数字过滤器 70 | * @param $query ActiveQuery 71 | * @param $attribute string 72 | */ 73 | protected function _addDigitalFilter($query, $attribute) { 74 | $pattern = '/^(>|>=|<|<=|=)(\d*\.?\d+)$/'; 75 | if (preg_match($pattern, $this->{$attribute}, $matches) === 1) { 76 | $query->andFilterWhere([$matches[1], $attribute, $matches[2]]); 77 | } else { 78 | $query->andFilterWhere(['like', $attribute, $this->{$attribute}]); 79 | } 80 | } 81 | } -------------------------------------------------------------------------------- /models/PhoneLoginForm.php: -------------------------------------------------------------------------------- 1 | '手机号', 36 | 'code' => '验证码', 37 | 'remember' => '记住我1周', 38 | ]; 39 | } 40 | 41 | /** 42 | * Validates the code. 43 | * This method serves as the inline validation for password. 44 | * 45 | * @param string $attribute the attribute currently being validated 46 | * @param array $params the additional name-value pairs given in the rule 47 | */ 48 | public function validateCode($attribute, $params) { 49 | if (!$this->hasErrors()) { 50 | $session = Yii::$app->session; 51 | $user = $this->getUser(); 52 | if (!$user) { 53 | $this->addError($attribute, '不存在该手机号!'); 54 | } else if ($session['loginVerifyCode'] != $this->code) { 55 | $this->addError($attribute, '验证码不正确!'); 56 | } 57 | } 58 | } 59 | 60 | /** 61 | * Logs in a admin using the provided username and password. 62 | * 63 | * @return boolean whether the user is logged in successfully 64 | */ 65 | public function login() { 66 | if ($this->validate()) { 67 | return Yii::$app->user->login($this->getUser(), $this->remember ? 3600 * 24 * 7 : 0); 68 | } else { 69 | return false; 70 | } 71 | } 72 | 73 | /** 74 | * Finds user by [[username]] 75 | * 76 | * @return Admin|null 77 | */ 78 | public function getUser() { 79 | if ($this->_user === false) { 80 | $this->_user = Admin::findOne(['phone' => $this->phone]); 81 | } 82 | 83 | return $this->_user; 84 | } 85 | } -------------------------------------------------------------------------------- /models/Product.php: -------------------------------------------------------------------------------- 1 | 'insert'], 58 | [ 59 | 'image', 60 | 'image', 61 | 'extensions' => 'jpg, png, jpeg, gif', 62 | 'mimeTypes' => 'image/jpeg, image/png, image/gif', 63 | 'checkExtensionByMimeType' => false, 64 | 'minSize' => 100, 65 | 'maxSize' => 204800, 66 | 'tooBig' => '{attribute}最大不能超过200KB', 67 | 'tooSmall' => '{attribute}最小不能小于0.1KB', 68 | 'notImage' => '{file} 不是图片文件' 69 | ], 70 | 71 | [['business_id', 'category_id', 'left_num'], 'integer'], 72 | 73 | [['category_id'], 'exist', 'targetClass' => Category::className(), 'targetAttribute' => 'id'], 74 | 75 | ['price', 'number', 'min' => 0], 76 | 77 | [['name'], 'string', 'max' => 100], 78 | [['description'], 'string', 'max' => 200], 79 | [['image_path'], 'string', 'max' => 200], 80 | ]; 81 | } 82 | 83 | /** 84 | * @inheritdoc 85 | */ 86 | public function attributeLabels() { 87 | return [ 88 | 'id' => '编号', 89 | 'business_id' => '商家', 90 | 'category_id' => '分类', 91 | 'name' => '商品名', 92 | 'description' => '商品描述', 93 | 'image' => '商品图片', 94 | 'price' => '价格', 95 | 'image_path' => '商品图片', 96 | 'month_sales' => '月销量', 97 | 'rate' => '评价', 98 | 'left_num' => '库存', 99 | 'created_at' => '添加时间', 100 | ]; 101 | } 102 | 103 | /** 104 | * @return ActiveQuery 105 | */ 106 | public function getCategory() { 107 | return $this->hasOne(Category::className(), ['id' => 'category_id']); 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /models/ProductSearch.php: -------------------------------------------------------------------------------- 1 | user->id); 27 | 28 | $query = Product::find(); 29 | $dataProvider = new ActiveDataProvider([ 30 | 'query' => $query->where(['business_id' => $admin->business_id]), 31 | 'sort' => [ 32 | 'defaultOrder' => ['id' => SORT_ASC] 33 | ], 34 | 'pagination' => [ 35 | 'pageSize' => 20 36 | ] 37 | ]); 38 | 39 | if (!($this->load($params) && $this->validate())) { 40 | return $dataProvider; 41 | } 42 | 43 | $this->_addDigitalFilter($query, 'price'); 44 | $this->_addDigitalFilter($query, 'month_sales'); 45 | $this->_addDigitalFilter($query, 'left_num'); 46 | 47 | $dateBegin = strtotime($this->date); 48 | $dateEnd = $dateBegin + 86400; 49 | 50 | // adjust the query by adding the filters 51 | $query->andFilterWhere(['like', 'name', $this->name]) 52 | ->andFilterWhere(['like', 'description', $this->description]) 53 | ->andFilterWhere(['category_id' => $this->category_id]) 54 | ->andFilterWhere(['>=', 'created_at', $this->date ? $dateBegin : null]) 55 | ->andFilterWhere(['<', 'created_at', $this->date ? $dateEnd : null]); 56 | 57 | return $dataProvider; 58 | } 59 | 60 | /** 61 | * 附加数字过滤器 62 | * @param $query ActiveQuery 63 | * @param $attribute string 64 | */ 65 | protected function _addDigitalFilter($query, $attribute) { 66 | $pattern = '/^(>|>=|<|<=|=)(\d*\.?\d+)$/'; 67 | if (preg_match($pattern, $this->{$attribute}, $matches) === 1) { 68 | $query->andFilterWhere([$matches[1], $attribute, $matches[2]]); 69 | } else { 70 | $query->andFilterWhere(['like', $attribute, $this->{$attribute}]); 71 | } 72 | } 73 | } -------------------------------------------------------------------------------- /models/SendChangePhoneSmsForm.php: -------------------------------------------------------------------------------- 1 | '/^1[3|5|7|8|][0-9]{9}$/'], 21 | // 验证手机号是否存在 22 | ['phone', function ($attribute, $params) { 23 | $admin = Admin::findOne(['phone' => $this->phone]); 24 | if ($admin) { 25 | $this->addError($attribute, '该手机号已被注册!'); 26 | } 27 | }], 28 | // 验证获取验证码的频率 29 | ['phone', function ($attribute, $params) { 30 | $session = Yii::$app->session; 31 | if ($session->has('changePhoneNextSendTime') && $session['changePhoneNextSendTime'] > time()) { 32 | $this->addError($attribute, '发送验证码过于频繁。'); 33 | } 34 | }] 35 | ]; 36 | } 37 | 38 | /** 39 | * @inheritdoc 40 | */ 41 | public function attributeLabels() { 42 | return [ 43 | 'phone' => '手机号', 44 | ]; 45 | } 46 | 47 | /** 48 | * 发送短信验证码 49 | * @param bool $runValidation 50 | * @return bool 51 | */ 52 | public function sendSms($runValidation = true) { 53 | 54 | if ($runValidation && !$this->validate()) { 55 | return false; 56 | } 57 | 58 | $verifyCode = (string) mt_rand(100000, 999999); 59 | $validMinutes = 30; 60 | 61 | // 调用云之讯组件发送模板短信 62 | /** @var $ucpass Ucpaas */ 63 | $ucpass = Yii::$app->ucpass; 64 | $ucpass->templateSMS($this->phone, $verifyCode.','.$validMinutes); 65 | 66 | if ($ucpass->state == Ucpaas::STATUS_SUCCESS) { 67 | $session = Yii::$app->session; 68 | $session['changePhoneNextSendTime'] = time() + 60; 69 | $session['changePhoneSendPhone'] = $this->phone; 70 | $session['changePhoneVerifyCode'] = $verifyCode; 71 | 72 | return true; 73 | } else { 74 | $this->addError('phone', $ucpass->message); 75 | 76 | return false; 77 | } 78 | } 79 | } -------------------------------------------------------------------------------- /models/UpdatePasswordForm.php: -------------------------------------------------------------------------------- 1 | '原始密码不能为空'], 23 | ['new_password', 'required', 'message' => '新的密码不能为空'], 24 | ['repeat_password', 'required', 'message' => '确认密码不能为空'], 25 | ['repeat_password', 'compare', 'compareAttribute' => 'new_password', 'message' => '两次密码输入不一致'], 26 | ['old_password', 'validateOldPassword'], 27 | ]; 28 | } 29 | 30 | /** 31 | * @inheritdoc 32 | */ 33 | public function attributeLabels() { 34 | return [ 35 | 'old_password' => '原始密码', 36 | 'new_password' => '新的密码', 37 | 'repeat_password' => '确认密码', 38 | ]; 39 | } 40 | 41 | /** 42 | * Validates the old password. 43 | * This method serves as the inline validation for password. 44 | * 45 | * @param string $attribute the attribute currently being validated 46 | * @param array $params the additional name-value pairs given in the rule 47 | */ 48 | public function validateOldPassword($attribute, $params) { 49 | if (!$this->hasErrors()) { 50 | /** @var $admin Admin */ 51 | $admin = Admin::findOne(Yii::$app->user->id); 52 | if (!$admin->validatePassword($this->old_password)) { 53 | $this->addError($attribute, '原始密码输入错误'); 54 | } 55 | } 56 | } 57 | 58 | /** 59 | * 修改新密码 60 | * @return bool 61 | */ 62 | public function updatePassword() { 63 | if ($this->validate()) { 64 | /* @var $admin Admin */ 65 | $admin = Admin::findOne(Yii::$app->user->id); 66 | $admin->setPassword($this->new_password); 67 | return $admin->save(); 68 | } else { 69 | return false; 70 | } 71 | } 72 | } -------------------------------------------------------------------------------- /models/User.php: -------------------------------------------------------------------------------- 1 | 20], 44 | [['access_token', 'password_hash', 'password_reset_token'], 'string', 'max' => 255], 45 | [['email'], 'string', 'max' => 50], 46 | [['avatar_url'], 'string', 'max' => 200], 47 | ]; 48 | } 49 | 50 | /** 51 | * @inheritdoc 52 | */ 53 | public function attributeLabels() 54 | { 55 | return [ 56 | 'id' => '主键ID', 57 | 'username' => '用户名', 58 | 'access_token' => '身份标识', 59 | 'password_hash' => '密码hash 值', 60 | 'password_reset_token' => '重置密码的标识', 61 | 'mobile' => '手机号', 62 | 'email' => '邮箱', 63 | 'avatar_url' => '头像URL', 64 | 'last_address_id' => '最近一次使用的地址ID', 65 | 'last_ip' => '最近一次登录的IP', 66 | 'last_device_type' => '最近一次登录的设备类型', 67 | 'last_device_id' => '最近一次登录的设备ID', 68 | 'created_at' => '创建时间', 69 | 'updated_at' => '更新时间', 70 | ]; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /models/VerifyPasswordForm.php: -------------------------------------------------------------------------------- 1 | 6, 'max' => 24], 22 | ['password', 'match', 'pattern' => '/^\S+$/'], 23 | ['password', 'validatePassword'] 24 | ]; 25 | } 26 | 27 | /** 28 | * @inheritdoc 29 | */ 30 | public function attributeLabels() { 31 | return [ 32 | 'password' => '登录密码' 33 | ]; 34 | } 35 | 36 | /** 37 | * Validates the password. 38 | * This method serves as the inline validation for password. 39 | * 40 | * @param string $attribute the attribute currently being validated 41 | * @param array $params the additional name-value pairs given in the rule 42 | */ 43 | public function validatePassword($attribute, $params) { 44 | if (!$this->hasErrors()) { 45 | /** @var $admin Admin */ 46 | $admin = Yii::$app->user->identity; 47 | if (!$admin->validatePassword($this->password)) { 48 | $this->addError($attribute, '密码验证错误。'); 49 | } 50 | } 51 | } 52 | } -------------------------------------------------------------------------------- /requirements.php: -------------------------------------------------------------------------------- 1 | Error'; 18 | echo '

The path to yii framework seems to be incorrect.

'; 19 | echo '

You need to install Yii framework via composer or adjust the framework path in file ' . basename(__FILE__) . '.

'; 20 | echo '

Please refer to the README on how to install Yii.

'; 21 | } 22 | 23 | require_once($frameworkPath . '/requirements/YiiRequirementChecker.php'); 24 | $requirementsChecker = new YiiRequirementChecker(); 25 | 26 | /** 27 | * Adjust requirements according to your application specifics. 28 | */ 29 | $requirements = array( 30 | // Database : 31 | array( 32 | 'name' => 'PDO extension', 33 | 'mandatory' => true, 34 | 'condition' => extension_loaded('pdo'), 35 | 'by' => 'All DB-related classes', 36 | ), 37 | array( 38 | 'name' => 'PDO SQLite extension', 39 | 'mandatory' => false, 40 | 'condition' => extension_loaded('pdo_sqlite'), 41 | 'by' => 'All DB-related classes', 42 | 'memo' => 'Required for SQLite database.', 43 | ), 44 | array( 45 | 'name' => 'PDO MySQL extension', 46 | 'mandatory' => false, 47 | 'condition' => extension_loaded('pdo_mysql'), 48 | 'by' => 'All DB-related classes', 49 | 'memo' => 'Required for MySQL database.', 50 | ), 51 | array( 52 | 'name' => 'PDO PostgreSQL extension', 53 | 'mandatory' => false, 54 | 'condition' => extension_loaded('pdo_pgsql'), 55 | 'by' => 'All DB-related classes', 56 | 'memo' => 'Required for PostgreSQL database.', 57 | ), 58 | // Cache : 59 | array( 60 | 'name' => 'Memcache extension', 61 | 'mandatory' => false, 62 | 'condition' => extension_loaded('memcache') || extension_loaded('memcached'), 63 | 'by' => 'MemCache', 64 | 'memo' => extension_loaded('memcached') ? 'To use memcached set MemCache::useMemcached to true.' : '' 65 | ), 66 | array( 67 | 'name' => 'APC extension', 68 | 'mandatory' => false, 69 | 'condition' => extension_loaded('apc'), 70 | 'by' => 'ApcCache', 71 | ), 72 | // PHP ini : 73 | 'phpSafeMode' => array( 74 | 'name' => 'PHP safe mode', 75 | 'mandatory' => false, 76 | 'condition' => $requirementsChecker->checkPhpIniOff("safe_mode"), 77 | 'by' => 'File uploading and console command execution', 78 | 'memo' => '"safe_mode" should be disabled at php.ini', 79 | ), 80 | 'phpExposePhp' => array( 81 | 'name' => 'Expose PHP', 82 | 'mandatory' => false, 83 | 'condition' => $requirementsChecker->checkPhpIniOff("expose_php"), 84 | 'by' => 'Security reasons', 85 | 'memo' => '"expose_php" should be disabled at php.ini', 86 | ), 87 | 'phpAllowUrlInclude' => array( 88 | 'name' => 'PHP allow url include', 89 | 'mandatory' => false, 90 | 'condition' => $requirementsChecker->checkPhpIniOff("allow_url_include"), 91 | 'by' => 'Security reasons', 92 | 'memo' => '"allow_url_include" should be disabled at php.ini', 93 | ), 94 | 'phpSmtp' => array( 95 | 'name' => 'PHP mail SMTP', 96 | 'mandatory' => false, 97 | 'condition' => strlen(ini_get('SMTP'))>0, 98 | 'by' => 'Email sending', 99 | 'memo' => 'PHP mail SMTP server required', 100 | ), 101 | ); 102 | $requirementsChecker->checkYii()->check($requirements)->render(); 103 | -------------------------------------------------------------------------------- /runtime/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !debug 3 | !logs 4 | !.gitignore -------------------------------------------------------------------------------- /tests/README.md: -------------------------------------------------------------------------------- 1 | This directory contains various tests for the basic application. 2 | 3 | Tests in `codeception` directory are developed with [Codeception PHP Testing Framework](http://codeception.com/). 4 | 5 | After creating the basic application, follow these steps to prepare for the tests: 6 | 7 | 1. Install Codeception if it's not yet installed: 8 | 9 | ``` 10 | composer global require "codeception/codeception=2.0.*" 11 | composer global require "codeception/specify=*" 12 | composer global require "codeception/verify=*" 13 | ``` 14 | 15 | If you've never used Composer for global packages run `composer global status`. It should output: 16 | 17 | ``` 18 | Changed current directory to 19 | ``` 20 | 21 | Then add `/vendor/bin` to you `PATH` environment variable. Now we're able to use `codecept` from command 22 | line globally. 23 | 24 | 2. Install faker extension by running the following from template root directory where `composer.json` is: 25 | 26 | ``` 27 | composer require --dev yiisoft/yii2-faker:* 28 | ``` 29 | 30 | 3. Create `yii2_basic_tests` database and update it by applying migrations: 31 | 32 | ``` 33 | codeception/bin/yii migrate 34 | ``` 35 | 36 | 4. Build the test suites: 37 | 38 | ``` 39 | codecept build 40 | ``` 41 | 42 | 5. In order to be able to run acceptance tests you need to start a webserver. The simplest way is to use PHP built in 43 | webserver. In the `web` directory execute the following: 44 | 45 | ``` 46 | php -S localhost:8080 47 | ``` 48 | 49 | 6. Now you can run the tests with the following commands: 50 | 51 | ``` 52 | # run all available tests 53 | codecept run 54 | # run acceptance tests 55 | codecept run acceptance 56 | # run functional tests 57 | codecept run functional 58 | # run unit tests 59 | codecept run unit 60 | ``` 61 | 62 | Please refer to [Codeception tutorial](http://codeception.com/docs/01-Introduction) for 63 | more details about writing and running acceptance, functional and unit tests. 64 | -------------------------------------------------------------------------------- /tests/codeception.yml: -------------------------------------------------------------------------------- 1 | actor: Tester 2 | paths: 3 | tests: codeception 4 | log: codeception/_output 5 | data: codeception/_data 6 | helpers: codeception/_support 7 | settings: 8 | bootstrap: _bootstrap.php 9 | suite_class: \PHPUnit_Framework_TestSuite 10 | memory_limit: 1024M 11 | log: true 12 | colors: true 13 | config: 14 | # the entry script URL (without host info) for functional and acceptance tests 15 | # PLEASE ADJUST IT TO THE ACTUAL ENTRY SCRIPT URL 16 | test_entry_url: /index-test.php -------------------------------------------------------------------------------- /tests/codeception/.gitignore: -------------------------------------------------------------------------------- 1 | # these files are auto generated by codeception build 2 | /unit/UnitTester.php 3 | /functional/FunctionalTester.php 4 | /acceptance/AcceptanceTester.php 5 | -------------------------------------------------------------------------------- /tests/codeception/_bootstrap.php: -------------------------------------------------------------------------------- 1 | $value) { 21 | $inputType = $field === 'body' ? 'textarea' : 'input'; 22 | $this->actor->fillField($inputType . '[name="ContactForm[' . $field . ']"]', $value); 23 | } 24 | $this->actor->click('contact-button'); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /tests/codeception/_pages/LoginPage.php: -------------------------------------------------------------------------------- 1 | actor->fillField('input[name="LoginForm[username]"]', $username); 22 | $this->actor->fillField('input[name="LoginForm[password]"]', $password); 23 | $this->actor->click('login-button'); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /tests/codeception/acceptance.suite.yml: -------------------------------------------------------------------------------- 1 | # Codeception Test Suite Configuration 2 | 3 | # suite for acceptance tests. 4 | # perform tests in browser using the Selenium-like tools. 5 | # powered by Mink (http://mink.behat.org). 6 | # (tip: that's what your customer will see). 7 | # (tip: test your ajax and javascript by one of Mink drivers). 8 | 9 | # RUN `build` COMMAND AFTER ADDING/REMOVING MODULES. 10 | 11 | class_name: AcceptanceTester 12 | modules: 13 | enabled: 14 | - PhpBrowser 15 | # you can use WebDriver instead of PhpBrowser to test javascript and ajax. 16 | # This will require you to install selenium. See http://codeception.com/docs/04-AcceptanceTests#Selenium 17 | # "restart" option is used by the WebDriver to start each time per test-file new session and cookies, 18 | # it is useful if you want to login in your app in each test. 19 | # - WebDriver 20 | config: 21 | PhpBrowser: 22 | url: 'http://localhost:8080' 23 | # WebDriver: 24 | # url: 'http://localhost' 25 | # browser: firefox 26 | # restart: true 27 | -------------------------------------------------------------------------------- /tests/codeception/acceptance/AboutCept.php: -------------------------------------------------------------------------------- 1 | wantTo('ensure that about works'); 7 | AboutPage::openBy($I); 8 | $I->see('About', 'h1'); 9 | -------------------------------------------------------------------------------- /tests/codeception/acceptance/ContactCept.php: -------------------------------------------------------------------------------- 1 | wantTo('ensure that contact works'); 7 | 8 | $contactPage = ContactPage::openBy($I); 9 | 10 | $I->see('Contact', 'h1'); 11 | 12 | $I->amGoingTo('submit contact form with no data'); 13 | $contactPage->submit([]); 14 | $I->expectTo('see validations errors'); 15 | $I->see('Contact', 'h1'); 16 | $I->see('Name cannot be blank'); 17 | $I->see('Email cannot be blank'); 18 | $I->see('Subject cannot be blank'); 19 | $I->see('Body cannot be blank'); 20 | $I->see('The verification code is incorrect'); 21 | 22 | $I->amGoingTo('submit contact form with not correct email'); 23 | $contactPage->submit([ 24 | 'name' => 'tester', 25 | 'email' => 'tester.email', 26 | 'subject' => 'test subject', 27 | 'body' => 'test content', 28 | 'verifyCode' => 'testme', 29 | ]); 30 | $I->expectTo('see that email adress is wrong'); 31 | $I->dontSee('Name cannot be blank', '.help-inline'); 32 | $I->see('Email is not a valid email address.'); 33 | $I->dontSee('Subject cannot be blank', '.help-inline'); 34 | $I->dontSee('Body cannot be blank', '.help-inline'); 35 | $I->dontSee('The verification code is incorrect', '.help-inline'); 36 | 37 | $I->amGoingTo('submit contact form with correct data'); 38 | $contactPage->submit([ 39 | 'name' => 'tester', 40 | 'email' => 'tester@example.com', 41 | 'subject' => 'test subject', 42 | 'body' => 'test content', 43 | 'verifyCode' => 'testme', 44 | ]); 45 | if (method_exists($I, 'wait')) { 46 | $I->wait(3); // only for selenium 47 | } 48 | $I->dontSeeElement('#contact-form'); 49 | $I->see('Thank you for contacting us. We will respond to you as soon as possible.'); 50 | -------------------------------------------------------------------------------- /tests/codeception/acceptance/HomeCept.php: -------------------------------------------------------------------------------- 1 | wantTo('ensure that home page works'); 5 | $I->amOnPage(Yii::$app->homeUrl); 6 | $I->see('My Company'); 7 | $I->seeLink('About'); 8 | $I->click('About'); 9 | $I->see('This is the About page.'); 10 | -------------------------------------------------------------------------------- /tests/codeception/acceptance/LoginCept.php: -------------------------------------------------------------------------------- 1 | wantTo('ensure that login works'); 7 | 8 | $loginPage = LoginPage::openBy($I); 9 | 10 | $I->see('Login', 'h1'); 11 | 12 | $I->amGoingTo('try to login with empty credentials'); 13 | $loginPage->login('', ''); 14 | $I->expectTo('see validations errors'); 15 | $I->see('Username cannot be blank.'); 16 | $I->see('Password cannot be blank.'); 17 | 18 | $I->amGoingTo('try to login with wrong credentials'); 19 | $loginPage->login('admin', 'wrong'); 20 | if (method_exists($I, 'wait')) { 21 | $I->wait(3); // only for selenium 22 | } 23 | $I->expectTo('see validations errors'); 24 | $I->see('Incorrect username or password.'); 25 | 26 | $I->amGoingTo('try to login with correct credentials'); 27 | $loginPage->login('admin', 'admin'); 28 | if (method_exists($I, 'wait')) { 29 | $I->wait(3); // only for selenium 30 | } 31 | $I->expectTo('see user info'); 32 | $I->see('Logout (admin)'); 33 | -------------------------------------------------------------------------------- /tests/codeception/acceptance/_bootstrap.php: -------------------------------------------------------------------------------- 1 | [ 18 | 'fixture' => [ 19 | 'class' => 'yii\faker\FixtureController', 20 | 'fixtureDataPath' => '@tests/codeception/fixtures', 21 | 'templatePath' => '@tests/codeception/templates' 22 | ], 23 | ], 24 | ] 25 | ); 26 | 27 | $application = new yii\console\Application($config); 28 | $exitCode = $application->run(); 29 | exit($exitCode); 30 | -------------------------------------------------------------------------------- /tests/codeception/bin/yii.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | rem ------------------------------------------------------------- 4 | rem Yii command line bootstrap script for Windows. 5 | rem 6 | rem @author Qiang Xue 7 | rem @link http://www.yiiframework.com/ 8 | rem @copyright Copyright (c) 2008 Yii Software LLC 9 | rem @license http://www.yiiframework.com/license/ 10 | rem ------------------------------------------------------------- 11 | 12 | @setlocal 13 | 14 | set YII_PATH=%~dp0 15 | 16 | if "%PHP_COMMAND%" == "" set PHP_COMMAND=php.exe 17 | 18 | "%PHP_COMMAND%" "%YII_PATH%yii_acceptance" %* 19 | 20 | @endlocal 21 | -------------------------------------------------------------------------------- /tests/codeception/config/acceptance.php: -------------------------------------------------------------------------------- 1 | [ 7 | 'db' => [ 8 | 'dsn' => 'mysql:host=localhost;dbname=yii2_basic_tests', 9 | ], 10 | 'mailer' => [ 11 | 'useFileTransport' => true, 12 | ], 13 | 'urlManager' => [ 14 | 'showScriptName' => true, 15 | ], 16 | ], 17 | ]; 18 | -------------------------------------------------------------------------------- /tests/codeception/config/functional.php: -------------------------------------------------------------------------------- 1 | wantTo('ensure that about works'); 7 | AboutPage::openBy($I); 8 | $I->see('About', 'h1'); 9 | -------------------------------------------------------------------------------- /tests/codeception/functional/ContactCept.php: -------------------------------------------------------------------------------- 1 | wantTo('ensure that contact works'); 7 | 8 | $contactPage = ContactPage::openBy($I); 9 | 10 | $I->see('Contact', 'h1'); 11 | 12 | $I->amGoingTo('submit contact form with no data'); 13 | $contactPage->submit([]); 14 | $I->expectTo('see validations errors'); 15 | $I->see('Contact', 'h1'); 16 | $I->see('Name cannot be blank'); 17 | $I->see('Email cannot be blank'); 18 | $I->see('Subject cannot be blank'); 19 | $I->see('Body cannot be blank'); 20 | $I->see('The verification code is incorrect'); 21 | 22 | $I->amGoingTo('submit contact form with not correct email'); 23 | $contactPage->submit([ 24 | 'name' => 'tester', 25 | 'email' => 'tester.email', 26 | 'subject' => 'test subject', 27 | 'body' => 'test content', 28 | 'verifyCode' => 'testme', 29 | ]); 30 | $I->expectTo('see that email adress is wrong'); 31 | $I->dontSee('Name cannot be blank', '.help-inline'); 32 | $I->see('Email is not a valid email address.'); 33 | $I->dontSee('Subject cannot be blank', '.help-inline'); 34 | $I->dontSee('Body cannot be blank', '.help-inline'); 35 | $I->dontSee('The verification code is incorrect', '.help-inline'); 36 | 37 | $I->amGoingTo('submit contact form with correct data'); 38 | $contactPage->submit([ 39 | 'name' => 'tester', 40 | 'email' => 'tester@example.com', 41 | 'subject' => 'test subject', 42 | 'body' => 'test content', 43 | 'verifyCode' => 'testme', 44 | ]); 45 | $I->dontSeeElement('#contact-form'); 46 | $I->see('Thank you for contacting us. We will respond to you as soon as possible.'); 47 | -------------------------------------------------------------------------------- /tests/codeception/functional/HomeCept.php: -------------------------------------------------------------------------------- 1 | wantTo('ensure that home page works'); 5 | $I->amOnPage(Yii::$app->homeUrl); 6 | $I->see('My Company'); 7 | $I->seeLink('About'); 8 | $I->click('About'); 9 | $I->see('This is the About page.'); 10 | -------------------------------------------------------------------------------- /tests/codeception/functional/LoginCept.php: -------------------------------------------------------------------------------- 1 | wantTo('ensure that login works'); 7 | 8 | $loginPage = LoginPage::openBy($I); 9 | 10 | $I->see('Login', 'h1'); 11 | 12 | $I->amGoingTo('try to login with empty credentials'); 13 | $loginPage->login('', ''); 14 | $I->expectTo('see validations errors'); 15 | $I->see('Username cannot be blank.'); 16 | $I->see('Password cannot be blank.'); 17 | 18 | $I->amGoingTo('try to login with wrong credentials'); 19 | $loginPage->login('admin', 'wrong'); 20 | $I->expectTo('see validations errors'); 21 | $I->see('Incorrect username or password.'); 22 | 23 | $I->amGoingTo('try to login with correct credentials'); 24 | $loginPage->login('admin', 'admin'); 25 | $I->expectTo('see user info'); 26 | $I->see('Logout (admin)'); 27 | -------------------------------------------------------------------------------- /tests/codeception/functional/_bootstrap.php: -------------------------------------------------------------------------------- 1 | mailer->fileTransportCallback = function ($mailer, $message) { 17 | return 'testing_message.eml'; 18 | }; 19 | } 20 | 21 | protected function tearDown() 22 | { 23 | unlink($this->getMessageFile()); 24 | parent::tearDown(); 25 | } 26 | 27 | public function testContact() 28 | { 29 | $model = $this->getMock('app\models\ContactForm', ['validate']); 30 | $model->expects($this->once())->method('validate')->will($this->returnValue(true)); 31 | 32 | $model->attributes = [ 33 | 'name' => 'Tester', 34 | 'email' => 'tester@example.com', 35 | 'subject' => 'very important letter subject', 36 | 'body' => 'body of current message', 37 | ]; 38 | 39 | $model->contact('admin@example.com'); 40 | 41 | $this->specify('email should be send', function () { 42 | expect('email file should exist', file_exists($this->getMessageFile()))->true(); 43 | }); 44 | 45 | $this->specify('message should contain correct data', function () use ($model) { 46 | $emailMessage = file_get_contents($this->getMessageFile()); 47 | 48 | expect('email should contain user name', $emailMessage)->contains($model->name); 49 | expect('email should contain sender email', $emailMessage)->contains($model->email); 50 | expect('email should contain subject', $emailMessage)->contains($model->subject); 51 | expect('email should contain body', $emailMessage)->contains($model->body); 52 | }); 53 | } 54 | 55 | private function getMessageFile() 56 | { 57 | return Yii::getAlias(Yii::$app->mailer->fileTransportPath) . '/testing_message.eml'; 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /tests/codeception/unit/models/LoginFormTest.php: -------------------------------------------------------------------------------- 1 | user->logout(); 17 | parent::tearDown(); 18 | } 19 | 20 | public function testLoginNoUser() 21 | { 22 | $model = new LoginForm([ 23 | 'username' => 'not_existing_username', 24 | 'password' => 'not_existing_password', 25 | ]); 26 | 27 | $this->specify('user should not be able to login, when there is no identity', function () use ($model) { 28 | expect('model should not login user', $model->login())->false(); 29 | expect('user should not be logged in', Yii::$app->user->isGuest)->true(); 30 | }); 31 | } 32 | 33 | public function testLoginWrongPassword() 34 | { 35 | $model = new LoginForm([ 36 | 'username' => 'demo', 37 | 'password' => 'wrong_password', 38 | ]); 39 | 40 | $this->specify('user should not be able to login with wrong password', function () use ($model) { 41 | expect('model should not login user', $model->login())->false(); 42 | expect('error message should be set', $model->errors)->hasKey('password'); 43 | expect('user should not be logged in', Yii::$app->user->isGuest)->true(); 44 | }); 45 | } 46 | 47 | public function testLoginCorrect() 48 | { 49 | $model = new LoginForm([ 50 | 'username' => 'demo', 51 | 'password' => 'demo', 52 | ]); 53 | 54 | $this->specify('user should be able to login with correct credentials', function () use ($model) { 55 | expect('model should login user', $model->login())->true(); 56 | expect('error message should not be set', $model->errors)->hasntKey('password'); 57 | expect('user should be logged in', Yii::$app->user->isGuest)->false(); 58 | }); 59 | } 60 | 61 | } 62 | -------------------------------------------------------------------------------- /tests/codeception/unit/models/UserTest.php: -------------------------------------------------------------------------------- 1 | loadFixtures(['user']); 14 | } 15 | 16 | // TODO add test methods here 17 | } 18 | -------------------------------------------------------------------------------- /tests/codeception/unit/templates/fixtures/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cheikh-wang/LazyWaimai-Web/c054ee4c11e11ad4e92abc376889335ad93fb4e3/tests/codeception/unit/templates/fixtures/.gitkeep -------------------------------------------------------------------------------- /upload/images/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore -------------------------------------------------------------------------------- /views/activity/index.php: -------------------------------------------------------------------------------- 1 | title = '活动列表'; 15 | 16 | ?> 17 | 18 |

19 | 添加活动', ['product/add'], ['class' => 'btn btn-primary']) ?> 20 |

21 |
22 |
23 | 24 | $dataProvider, 26 | 'tableOptions' => ['class' => 'table table-striped table-bordered table-center'], 27 | 'summaryOptions' => ['tag' => 'p', 'class' => 'text-right text-info'], 28 | 'columns' => [ 29 | [ 30 | 'class' => 'yii\grid\SerialColumn', 31 | 'header' => '编号', 32 | 'headerOptions' => ['class' => 'col-md-1'], 33 | ], 34 | [ 35 | 'attribute' => 'name', 36 | 'label' => '活动名称', 37 | 'format' => 'html', 38 | 'headerOptions' => ['class' => 'col-md-3'], 39 | 'value' => function ($data) { 40 | return Html::tag('span', $data['icon_name'], [ 41 | 'style' => [ 42 | 'background-color' => $data['icon_color'], 43 | 'color' => 'white' 44 | ] 45 | ]).' '.$data['name']; 46 | } 47 | ], 48 | [ 49 | 'attribute' => 'description', 50 | 'label' => '活动描述', 51 | 'headerOptions' => ['class' => 'col-md-4'] 52 | ], 53 | [ 54 | 'class' => 'yii\grid\ActionColumn', 55 | 'header' => '操作', 56 | 'headerOptions' => ['class' => 'col-md-4'], 57 | 'template' => '{update} {delete} {surplus}', 58 | 'buttons' => [ 59 | 'delete' => function ($url, $model, $key) { 60 | return Html::a('', $url, ['title' => '删除']); 61 | }, 62 | 'surplus' => function ($url, $model, $key) { 63 | return Html::a('', $url, ['title' => '库存变化记录']); 64 | }, 65 | ] 66 | ] 67 | ] 68 | ]) ?> 69 | 70 |
71 |
-------------------------------------------------------------------------------- /views/business/profile.php: -------------------------------------------------------------------------------- 1 | title = '商家资料'; 10 | 11 | ?> 12 |
13 |
14 | ['enctype' => 'multipart/form-data'] 16 | ]); ?> 17 | field($model, 'name') ?> 18 | field($model, 'phone') ?> 19 | pic_url)) :?> 20 | field($model, 'image')->widget(FileInput::className(), [ 21 | 'options' => ['accept' => 'image/*'], 22 | 'pluginOptions' => [ 23 | 'showUpload' => false, 24 | 'browseLabel' => '选择图片', 25 | 'removeLabel' => '删除' 26 | ], 27 | ]) ?> 28 | 29 | field($model, 'image')->widget(FileInput::className(), [ 30 | 'options' => ['accept' => 'image/*'], 31 | 'pluginOptions' => [ 32 | 'showUpload' => false, 33 | 'browseLabel' => '选择图片', 34 | 'removeLabel' => '删除', 35 | 'initialPreview' => Html::img($model->pic_url, ['class' => 'file-preview-image']) 36 | ], 37 | ]) ?> 38 | 39 | field($model, 'address')->textarea(['rows' => '3']) ?> 40 |
41 | 修改', ['class' => 'btn btn-primary']) ?> 42 |
43 | 44 |
45 |
-------------------------------------------------------------------------------- /views/business/scene.php: -------------------------------------------------------------------------------- 1 | title = '店面实景'; 12 | 13 | ?> 14 | 24 | 34 | -------------------------------------------------------------------------------- /views/business/setup.php: -------------------------------------------------------------------------------- 1 | registerJsFile('@web/js/setup.js', [ 18 | 'depends' => [ 19 | 'app\assets\ClockPickerAsset', 20 | 'app\assets\MultiSelectAsset', 21 | ] 22 | ]); 23 | 24 | $this->title = '店铺设置'; 25 | 26 | $html = ''; 27 | foreach ($bookingTimes as $item) { 28 | $html .= ""; 29 | foreach ($item['group_items'] as $time) { 30 | $html .= ""; 35 | } 36 | $html .= ""; 37 | } 38 | ?> 39 |
40 |
41 | 42 | field($model, 'status')->widget(SwitchInput::classname(), [ 43 | 'type' => SwitchInput::CHECKBOX, 44 | ]); ?> 45 | field($model, 'bulletin')->textarea(['rows' => '5']) ?> 46 | field($model, 'opening_time', [ 47 | 'inputTemplate' => '{input}
' 48 | ])->hiddenInput() ?> 49 | field($model, 'booking_times', [ 50 | 'inputTemplate' => '{input}' 51 | ])->hiddenInput() ?> 52 | field($model, 'shipping_fee', [ 53 | 'inputTemplate' => '
{input}.00
', 54 | ]) ?> 55 | field($model, 'package_fee', [ 56 | 'inputTemplate' => '
{input}.00
', 57 | ]) ?> 58 | field($model, 'min_price', [ 59 | 'inputTemplate' => '
{input}.00
', 60 | ]) ?> 61 |
62 | 提交', ['class' => 'btn btn-primary']) ?> 63 |
64 | 65 |
66 |
-------------------------------------------------------------------------------- /views/category/form.php: -------------------------------------------------------------------------------- 1 | title = $model->isNewRecord ? '添加分类' : '更新分类'; 10 | 11 | ?> 12 |
13 |
14 | 15 | field($model, 'name') ?> 16 | field($model, 'description')->textarea() ?> 17 |
18 | 保存', ['class' => 'btn btn-primary']) ?> 19 |
20 | 21 |
22 |
-------------------------------------------------------------------------------- /views/category/index.php: -------------------------------------------------------------------------------- 1 | title = '商品分类列表'; 13 | 14 | ?> 15 |

16 | 添加商品分类', ['category/add'], ['class' => 'btn btn-primary']) ?> 17 |

18 |
19 |
20 | 21 | $dataProvider, 23 | 'filterModel' => $searchModel, 24 | 'tableOptions' => ['class' => 'table table-striped table-bordered table-center'], 25 | 'summaryOptions' => ['tag' => 'p', 'class' => 'text-right text-info'], 26 | 'columns' => [ 27 | [ 28 | 'attribute' => 'id', 29 | 'headerOptions' => ['class' => 'col-md-1'] 30 | ], 31 | [ 32 | 'attribute' => 'name', 33 | 'headerOptions' => ['class' => 'col-md-1'], 34 | 'filterInputOptions' => ['class' => 'form-control input-sm'] 35 | ], 36 | [ 37 | 'attribute' => 'description', 38 | 'headerOptions' => ['class' => 'col-md-2'], 39 | 'filterInputOptions' => ['class' => 'form-control input-sm'] 40 | ], 41 | [ 42 | 'attribute' => 'created_at', 43 | 'format' => ['date', 'php:Y-m-d H:i'], 44 | 'filter' => DatePicker::widget([ 45 | 'model' => $searchModel, 46 | 'type' => DatePicker::TYPE_COMPONENT_APPEND, 47 | 'attribute' => 'date', 48 | 'options' => ['class' => 'input-sm'], 49 | 'pluginOptions' => [ 50 | 'autoclose' => true, 51 | 'format' => 'yyyy-mm-dd' 52 | ] 53 | ]), 54 | 'headerOptions' => ['class' => 'col-md-1'] 55 | ], 56 | [ 57 | 'class' => 'yii\grid\ActionColumn', 58 | 'header' => '操作', 59 | 'headerOptions' => ['class' => 'col-md-1'], 60 | 'template' => '{update} {delete}', 61 | 'buttons' => [ 62 | 'update' => function ($url, $model, $key) { 63 | return Html::a('', $url, ['title' => '修改']); 64 | }, 65 | 'delete' => function ($url, $model, $key) { 66 | return Html::a('', $url, ['title' => '删除']); 67 | }, 68 | ] 69 | ] 70 | ] 71 | ]) ?> 72 | 73 |
74 |
-------------------------------------------------------------------------------- /views/gallery.php: -------------------------------------------------------------------------------- 1 | 6 | */ 7 | use yii\helpers\Html; 8 | use yii\web\View; 9 | 10 | ?> 11 | 12 | 13 | 14 |
15 |
16 |
17 | 18 | 添加 19 | 20 |
21 |
22 |
23 | 27 |
28 | 29 | 删除 30 |
31 |
32 |
33 | 34 |
35 | 36 | 37 |
38 |
39 |
40 |
41 | 42 |
43 |
 
44 |
45 | 46 | 拖拽文件到这儿 47 | 48 |
49 |
50 |
51 |
 
52 | 53 | 69 |
70 | 71 | -------------------------------------------------------------------------------- /views/layouts/base.php: -------------------------------------------------------------------------------- 1 | 12 | 13 | beginPage() ?> 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | <?= Html::encode($this->title) ?> 22 | head() ?> 23 | 24 | 25 | beginBody() ?> 26 | 27 | endBody() ?> 28 | 29 | 30 | endPage() ?> 31 | -------------------------------------------------------------------------------- /views/layouts/main.php: -------------------------------------------------------------------------------- 1 | user->id); 16 | $route = Yii::$app->requestedAction->uniqueId; 17 | 18 | ?> 19 | beginContent('@app/views/layouts/base.php'); ?> 20 |
21 | 22 | 23 | 50 | 51 | 126 | 127 | 128 |
129 |
130 |
131 |

title) ?>

132 |
133 |
134 |
135 |
136 | 137 |
138 |
139 | 140 |
141 | 142 | 143 |
144 | 145 | 146 | endContent() ?> -------------------------------------------------------------------------------- /views/order/index.php: -------------------------------------------------------------------------------- 1 | title = isset($statusList[$status]) ? $statusList[$status] : ''; 16 | 17 | $buttons = []; 18 | if ($status == Order::STATUS_WAIT_ACCEPT) { 19 | $buttons['status'] = function ($url, $model, $key) { 20 | return Html::a('接单', ['/order/status', 21 | 'id' => $model->id, 22 | 'current' => Order::STATUS_WAIT_ACCEPT, 23 | 'target' => Order::STATUS_WAIT_SEND 24 | ], ['class' => 'btn btn-primary']); 25 | }; 26 | } else if ($status == Order::STATUS_WAIT_SEND) { 27 | $buttons['status'] = function ($url, $model, $key) { 28 | return Html::a('配送', ['/order/status', 29 | 'id' => $model->id, 30 | 'current' => Order::STATUS_WAIT_SEND, 31 | 'target' => Order::STATUS_WAIT_ARRIVE 32 | ], ['class' => 'btn btn-primary']); 33 | }; 34 | } 35 | 36 | ?> 37 |
38 |
39 | 40 | $dataProvider, 42 | 'filterModel' => $searchModel, 43 | 'tableOptions' => ['class' => 'table table-striped table-bordered table-center'], 44 | 'summaryOptions' => ['tag' => 'p', 'class' => 'text-right text-info'], 45 | 'columns' => [ 46 | [ 47 | 'attribute' => 'id', 48 | 'headerOptions' => ['class' => 'col-lg-1'], 49 | 'filterInputOptions' => ['class' => 'form-control input-sm'] 50 | ], 51 | [ 52 | 'attribute' => 'consignee', 53 | 'headerOptions' => ['class' => 'col-lg-1'], 54 | 'filterInputOptions' => ['class' => 'form-control input-sm'] 55 | ], 56 | [ 57 | 'attribute' => 'phone', 58 | 'headerOptions' => ['class' => 'col-lg-1'], 59 | 'filterInputOptions' => ['class' => 'form-control input-sm'] 60 | ], 61 | [ 62 | 'attribute' => 'address', 63 | 'headerOptions' => ['class' => 'col-lg-2'], 64 | 'filterInputOptions' => ['class' => 'form-control input-sm'] 65 | ], 66 | [ 67 | 'attribute' => 'total_price', 68 | 'headerOptions' => ['class' => 'col-lg-1'], 69 | 'format' => 'html', 70 | 'filterInputOptions' => ['class' => 'form-control input-sm', 'title' => '支持运算符'], 71 | 'value' => function ($model, $key, $index, $column) { 72 | /** @var $model Order */ 73 | return '¥ ' . $model->total_price; 74 | } 75 | ], 76 | [ 77 | 'attribute' => 'pay_method', 78 | 'filter' => Order::getPayMethodList(), 79 | 'filterInputOptions' => ['class' => 'form-control input-sm'], 80 | 'headerOptions' => ['class' => 'col-lg-1'], 81 | 'value' => function ($model, $key, $index, $column) { 82 | /** @var $model Order */ 83 | $list = Order::getPayMethodList(); 84 | 85 | return $list[$model->pay_method]; 86 | } 87 | ], 88 | [ 89 | 'attribute' => 'remark', 90 | 'headerOptions' => ['class' => 'col-lg-2'], 91 | 'filterInputOptions' => ['class' => 'form-control input-sm'] 92 | ], 93 | [ 94 | 'attribute' => 'created_at', 95 | 'headerOptions' => ['class' => 'col-lg-2'], 96 | 'format' => ['date', 'php:Y-m-d H:i'], 97 | 'filter' => DatePicker::widget([ 98 | 'model' => $searchModel, 99 | 'type' => DatePicker::TYPE_INPUT, 100 | 'attribute' => 'date', 101 | 'options' => ['class' => 'input-sm'], 102 | 'pluginOptions' => [ 103 | 'autoclose' => true, 104 | 'format' => 'yyyy-mm-dd hh:mm' 105 | ] 106 | ]), 107 | ], 108 | [ 109 | 'class' => 'yii\grid\ActionColumn', 110 | 'header' => '操作', 111 | 'headerOptions' => ['class' => 'col-lg-1'], 112 | 'template' => '{status}', 113 | 'buttons' => $buttons 114 | ] 115 | ] 116 | ]) ?> 117 | 118 |
119 |
-------------------------------------------------------------------------------- /views/product/form.php: -------------------------------------------------------------------------------- 1 | title = $model->isNewRecord ? '添加商品' : '更新商品'; 12 | 13 | ?> 14 |
15 |
16 | ['enctype' => 'multipart/form-data'] 18 | ]); ?> 19 | field($model, 'name') ?> 20 | field($model, 'description')->textarea() ?> 21 | field($model, 'category_id')->widget(Select2::className(), [ 22 | 'data' => Category::getKeyValuePairs(), 23 | 'options' => ['placeholder' => '请选择分类'], 24 | 'pluginOptions' => [ 25 | 'allowClear' => true 26 | ], 27 | ]) ?> 28 | isNewRecord) :?> 29 | field($model, 'image')->widget(FileInput::className(), [ 30 | 'options' => ['accept' => 'image/*'], 31 | 'pluginOptions' => [ 32 | 'showUpload' => false, 33 | 'browseLabel' => '选择图片', 34 | 'removeLabel' => '删除' 35 | ], 36 | ]) ?> 37 | 38 | field($model, 'image')->widget(FileInput::className(), [ 39 | 'options' => ['accept' => 'image/*'], 40 | 'pluginOptions' => [ 41 | 'showUpload' => false, 42 | 'browseLabel' => '选择图片', 43 | 'removeLabel' => '删除', 44 | 'initialPreview' => Html::img($model->image_path, ['class' => 'file-preview-image']) 45 | ], 46 | ]) ?> 47 | 48 | field($model, 'price') ?> 49 | field($model, 'left_num') ?> 50 |
51 | 保存', ['class' => 'btn btn-primary']) ?> 52 |
53 | 54 |
55 |
-------------------------------------------------------------------------------- /views/product/index.php: -------------------------------------------------------------------------------- 1 | title = '商品列表'; 15 | 16 | ?> 17 | 18 |

19 | 添加商品', ['product/add'], ['class' => 'btn btn-primary']) ?> 20 |

21 |
22 |
23 | 24 | $dataProvider, 26 | 'filterModel' => $searchModel, 27 | 'tableOptions' => ['class' => 'table table-striped table-bordered table-center'], 28 | 'summaryOptions' => ['tag' => 'p', 'class' => 'text-right text-info'], 29 | 'columns' => [ 30 | [ 31 | 'attribute' => 'id', 32 | 'headerOptions' => ['class' => 'col-md-1'], 33 | ], 34 | [ 35 | 'attribute' => 'image_path', 36 | 'headerOptions' => ['class' => 'col-md-1'], 37 | 'format' => 'raw', 38 | 'value' => function($model){ 39 | /** @var $model Product */ 40 | return Html::img($model->image_path, [ 41 | 'class' => 'img-rounded', 42 | 'width' => 50, 43 | 'height' => 50 44 | ]); 45 | } 46 | ], 47 | [ 48 | 'attribute' => 'name', 49 | 'headerOptions' => ['class' => 'col-md-1'], 50 | 'filterInputOptions' => ['class' => 'form-control input-sm'] 51 | ], 52 | [ 53 | 'attribute' => 'description', 54 | 'headerOptions' => ['class' => 'col-md-2'], 55 | 'filterInputOptions' => ['class' => 'form-control input-sm'] 56 | ], 57 | [ 58 | 'attribute' => 'category_id', 59 | 'headerOptions' => ['class' => 'col-md-1'], 60 | 'filter' => Category::getKeyValuePairs(), 61 | 'filterInputOptions' => ['class' => 'form-control input-sm'], 62 | 'value' => function ($model, $key, $index, $column) { 63 | return $model->category->name; 64 | } 65 | ], 66 | [ 67 | 'attribute' => 'price', 68 | 'headerOptions' => ['class' => 'col-md-1'], 69 | 'format' => 'html', 70 | 'filterInputOptions' => ['class' => 'form-control input-sm', 'title' => '支持运算符'], 71 | 'value' => function ($model, $key, $index, $column) { 72 | return '¥ ' . $model->price; 73 | } 74 | ], 75 | [ 76 | 'attribute' => 'month_sales', 77 | 'headerOptions' => ['class' => 'col-md-1'], 78 | 'filterInputOptions' => ['class' => 'form-control input-sm', 'title' => '支持运算符'] 79 | ], 80 | [ 81 | 'attribute' => 'left_num', 82 | 'headerOptions' => ['class' => 'col-md-1'], 83 | 'filterInputOptions' => ['class' => 'form-control input-sm', 'title' => '支持运算符'] 84 | ], 85 | [ 86 | 'attribute' => 'created_at', 87 | 'headerOptions' => ['class' => 'col-md-2'], 88 | 'format' => ['date', 'php:Y-m-d H:i'], 89 | 'filter' => DatePicker::widget([ 90 | 'model' => $searchModel, 91 | 'type' => DatePicker::TYPE_COMPONENT_APPEND, 92 | 'attribute' => 'date', 93 | 'options' => ['class' => 'input-sm'], 94 | 'pluginOptions' => [ 95 | 'autoclose' => true, 96 | 'format' => 'yyyy-mm-dd' 97 | ] 98 | ]), 99 | ], 100 | [ 101 | 'class' => 'yii\grid\ActionColumn', 102 | 'header' => '操作', 103 | 'headerOptions' => ['class' => 'col-md-1'], 104 | 'template' => '{update} {delete} {surplus}', 105 | 'buttons' => [ 106 | 'delete' => function ($url, $model, $key) { 107 | return Html::a('', $url, ['title' => '删除']); 108 | }, 109 | 'surplus' => function ($url, $model, $key) { 110 | return Html::a('', $url, ['title' => '库存变化记录']); 111 | }, 112 | ] 113 | ] 114 | ] 115 | ]) ?> 116 | 117 |
118 |
-------------------------------------------------------------------------------- /views/site/error.php: -------------------------------------------------------------------------------- 1 | title = $name; 10 | 11 | ?> 12 |
13 |
14 | 15 |
16 |

服务器处理您的请求时出现以上错误。

17 |

请联系我们如果您觉得该错误很严重的话,谢谢。

18 |
19 | -------------------------------------------------------------------------------- /views/user/login.php: -------------------------------------------------------------------------------- 1 | registerJsFile('@web/js/login.js', [ 19 | 'depends' => [ 20 | 'app\assets\CountDownAsset', 21 | ] 22 | ]); 23 | 24 | $this->title = '登录'; 25 | 26 | ?> 27 |
28 | 31 |
32 | 33 |
34 |
35 |
36 | 37 | 38 | 39 | 40 |
41 |
42 |
43 |
44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 |
52 |
53 |
54 |
55 | 59 |
60 |
61 |
62 | 63 |
64 |
65 | 66 | 使用用户名登录 67 | 68 |
69 |
70 | 71 | 72 | 73 |
74 |
75 |
76 | 77 | 78 | 79 | 80 |
81 |
82 |
83 |
84 | 85 | 86 | 87 | 88 |
89 |
90 |
91 |
92 | 96 |
97 |
98 |
99 | 100 |
101 | 104 |
105 | 106 |
107 |
108 | -------------------------------------------------------------------------------- /views/user/profile.php: -------------------------------------------------------------------------------- 1 | title = '个人资料'; 10 | 11 | ?> 12 |
13 |
14 | 15 | field($model, 'user_name')->textInput(['disabled' => true]) ?> 16 | field($model, 'real_name')->textInput(['disabled' => true]) ?> 17 | field($model, 'identity_num')->textInput(['disabled' => true]) ?> 18 | field($model, 'phone')->textInput(['disabled' => true]) ?> 19 | field($model, 'email')->textInput(['disabled' => true]) ?> 20 | field($model, 'gender')->dropDownList(Admin::getGenderList()) ?> 21 |
22 | 保存', ['class' => 'btn btn-primary']) ?> 23 |
24 | 25 |
26 |
-------------------------------------------------------------------------------- /views/user/update-password.php: -------------------------------------------------------------------------------- 1 | title = '修改密码'; 10 | 11 | ?> 12 |
13 |
14 | 15 | field($model, 'old_password')->passwordInput() ?> 16 | field($model, 'new_password')->passwordInput() ?> 17 | field($model, 'repeat_password')->passwordInput() ?> 18 |
19 | 确认修改', ['class' => 'btn btn-primary']) ?> 20 |
21 | 22 |
23 |
-------------------------------------------------------------------------------- /views/user/update-phone.php: -------------------------------------------------------------------------------- 1 | registerJsFile('@web/js/update-phone.js', [ 18 | 'depends' => [ 19 | 'app\assets\CountDownAsset', 20 | ] 21 | ]); 22 | 23 | $this->title = '修改手机号'; 24 | 25 | ?> 26 |
27 |
28 | 29 |
30 |
31 |

当前手机号:,如需更换,请输入登录密码进行下一步。

32 |
33 | 34 | field($verifyPasswordForm, 'password')->passwordInput() ?> 35 |
36 | ', ['class' => 'btn btn-primary']) ?> 37 |
38 | 39 |
40 | 41 |
42 | 43 | 44 | 45 | field($changeMobileForm, 'phone') ?> 46 | field($changeMobileForm, 'verifyCode', [ 47 | 'inputTemplate' => '
{input}
' 48 | ]) ?> 49 |
50 | 提交', ['class' => 'btn btn-primary']) ?> 51 |
52 | 53 |
54 | 55 |
56 |
-------------------------------------------------------------------------------- /web/.htaccess: -------------------------------------------------------------------------------- 1 | Options +FollowSymLinks 2 | IndexIgnore */* 3 | 4 | RewriteEngine on 5 | 6 | # if a directory or a file exists, use it directly 7 | RewriteCond %{REQUEST_FILENAME} !-f 8 | RewriteCond %{REQUEST_FILENAME} !-d 9 | 10 | # otherwise forward it to index.php 11 | RewriteRule . index.php -------------------------------------------------------------------------------- /web/assets/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /web/css/gallery.css: -------------------------------------------------------------------------------- 1 | /* Photo Gallery */ 2 | .gallery { 3 | position: relative; 4 | border: 1px solid #DDD; 5 | -webkit-border-radius: 4px; 6 | -moz-border-radius: 4px; 7 | border-radius: 4px; 8 | -webkit-box-shadow: 0 1px 1px rgba(0, 0, 0, 0.075); 9 | -moz-box-shadow: 0 1px 1px rgba(0, 0, 0, 0.075); 10 | box-shadow: 0 1px 1px rgba(0, 0, 0, 0.075); 11 | min-height: 250px; 12 | } 13 | 14 | .gallery .photo { 15 | position: relative; 16 | float: left; 17 | background-color: #fff; 18 | margin: 4px; 19 | width: 145px; 20 | height: 185px; 21 | display: block; 22 | padding: 4px; 23 | line-height: 1; 24 | border: 1px solid #DDD; 25 | -webkit-border-radius: 4px; 26 | -moz-border-radius: 4px; 27 | border-radius: 4px; 28 | -webkit-box-shadow: 0 1px 1px rgba(0, 0, 0, 0.075); 29 | -moz-box-shadow: 0 1px 1px rgba(0, 0, 0, 0.075); 30 | box-shadow: 0 1px 1px rgba(0, 0, 0, 0.075); 31 | } 32 | 33 | .gallery .photo img { 34 | width: 145px; 35 | height: 145px; 36 | } 37 | 38 | .gallery .photo .actions { 39 | float: right; 40 | position: absolute; 41 | bottom: 4px; 42 | right: 4px; 43 | } 44 | 45 | .gallery hr { 46 | margin: 0 4px; 47 | } 48 | 49 | .gallery .btn-file { 50 | position: relative; 51 | overflow: hidden; 52 | } 53 | 54 | .gallery .btn-file input[type=file] { 55 | position: absolute; 56 | top: 0; 57 | right: 0; 58 | min-width: 100%; 59 | min-height: 100%; 60 | font-size: 999px; 61 | text-align: right; 62 | filter: alpha(opacity=0); 63 | opacity: 0; 64 | outline: none; 65 | background: white; 66 | cursor: inherit; 67 | display: block; 68 | } 69 | 70 | .gallery .image-preview { 71 | height: 200px; 72 | overflow: hidden; 73 | } 74 | 75 | .gallery .photo-select { 76 | position: absolute; 77 | bottom: 8px; 78 | left: 8px; 79 | } 80 | 81 | .gallery .photo.selected { 82 | background-color: #cef; 83 | border-color: blue; 84 | } 85 | 86 | .gallery .overlay { 87 | display: none; 88 | position: absolute; 89 | top: 0; 90 | left: 0; 91 | width: 100%; 92 | height: 100%; 93 | } 94 | 95 | .gallery .overlay-bg { 96 | position: absolute; 97 | top: 0; 98 | left: 0; 99 | background-color: #efefef; 100 | opacity: .5; 101 | width: 100%; 102 | height: 100%; 103 | } 104 | 105 | .gallery.over .overlay { 106 | display: block; 107 | } 108 | 109 | .gallery .drop-hint { 110 | background-color: #efefef; 111 | border: 2px #777 dashed; 112 | position: absolute; 113 | top: 50%; 114 | left: 50%; 115 | height: 100px; 116 | width: 50%; 117 | margin: -50px 0 0 -25%; 118 | text-align: center; 119 | } 120 | 121 | .gallery .drop-hint-info { 122 | color: #777; 123 | font-weight: bold; 124 | font-size: 30px; 125 | margin-top: 35px; 126 | vertical-align: middle; 127 | display: inline-block; 128 | } 129 | 130 | .gallery .progress-overlay { 131 | display: none; 132 | position: absolute; 133 | top: 0; 134 | left: 0; 135 | width: 100%; 136 | height: 100%; 137 | } 138 | 139 | .gallery .progress-overlay .modal { 140 | position: relative; 141 | display: block; 142 | top: 80px; 143 | } 144 | 145 | .editor-modal .photo-editor { 146 | min-height: 156px; 147 | padding: 4px; 148 | } 149 | 150 | .editor-modal .photo-editor:not(:last-child) { 151 | border-bottom: 1px solid #DDD; 152 | } 153 | -------------------------------------------------------------------------------- /web/css/lightbox.css: -------------------------------------------------------------------------------- 1 | /* Preload images */ 2 | body:after { 3 | content: url(../images/close.png) url(../images/loading.gif) url(../images/prev.png) url(../images/next.png); 4 | display: none; 5 | } 6 | 7 | body.lb-disable-scrolling { 8 | overflow: hidden; 9 | } 10 | 11 | .lightboxOverlay { 12 | position: absolute; 13 | top: 0; 14 | left: 0; 15 | z-index: 9999; 16 | background-color: black; 17 | filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=80); 18 | opacity: 0.8; 19 | display: none; 20 | } 21 | 22 | .lightbox { 23 | position: absolute; 24 | left: 0; 25 | width: 100%; 26 | z-index: 10000; 27 | text-align: center; 28 | line-height: 0; 29 | font-weight: normal; 30 | } 31 | 32 | .lightbox .lb-image { 33 | display: block; 34 | height: auto; 35 | max-width: inherit; 36 | border-radius: 3px; 37 | } 38 | 39 | .lightbox a img { 40 | border: none; 41 | } 42 | 43 | .lb-outerContainer { 44 | position: relative; 45 | background-color: white; 46 | *zoom: 1; 47 | width: 250px; 48 | height: 250px; 49 | margin: 0 auto; 50 | border-radius: 4px; 51 | } 52 | 53 | .lb-outerContainer:after { 54 | content: ""; 55 | display: table; 56 | clear: both; 57 | } 58 | 59 | .lb-container { 60 | padding: 4px; 61 | } 62 | 63 | .lb-loader { 64 | position: absolute; 65 | top: 43%; 66 | left: 0; 67 | height: 25%; 68 | width: 100%; 69 | text-align: center; 70 | line-height: 0; 71 | } 72 | 73 | .lb-cancel { 74 | display: block; 75 | width: 32px; 76 | height: 32px; 77 | margin: 0 auto; 78 | background: url(../images/loading.gif) no-repeat; 79 | } 80 | 81 | .lb-nav { 82 | position: absolute; 83 | top: 0; 84 | left: 0; 85 | height: 100%; 86 | width: 100%; 87 | z-index: 10; 88 | } 89 | 90 | .lb-container > .nav { 91 | left: 0; 92 | } 93 | 94 | .lb-nav a { 95 | outline: none; 96 | background-image: url('data:image/gif;base64,R0lGODlhAQABAPAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw=='); 97 | } 98 | 99 | .lb-prev, .lb-next { 100 | height: 100%; 101 | cursor: pointer; 102 | display: block; 103 | } 104 | 105 | .lb-nav a.lb-prev { 106 | width: 34%; 107 | left: 0; 108 | float: left; 109 | background: url(../images/prev.png) left 48% no-repeat; 110 | filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=0); 111 | opacity: 0; 112 | -webkit-transition: opacity 0.6s; 113 | -moz-transition: opacity 0.6s; 114 | -o-transition: opacity 0.6s; 115 | transition: opacity 0.6s; 116 | } 117 | 118 | .lb-nav a.lb-prev:hover { 119 | filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=100); 120 | opacity: 1; 121 | } 122 | 123 | .lb-nav a.lb-next { 124 | width: 64%; 125 | right: 0; 126 | float: right; 127 | background: url(../images/next.png) right 48% no-repeat; 128 | filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=0); 129 | opacity: 0; 130 | -webkit-transition: opacity 0.6s; 131 | -moz-transition: opacity 0.6s; 132 | -o-transition: opacity 0.6s; 133 | transition: opacity 0.6s; 134 | } 135 | 136 | .lb-nav a.lb-next:hover { 137 | filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=100); 138 | opacity: 1; 139 | } 140 | 141 | .lb-dataContainer { 142 | margin: 0 auto; 143 | padding-top: 5px; 144 | *zoom: 1; 145 | width: 100%; 146 | -moz-border-radius-bottomleft: 4px; 147 | -webkit-border-bottom-left-radius: 4px; 148 | border-bottom-left-radius: 4px; 149 | -moz-border-radius-bottomright: 4px; 150 | -webkit-border-bottom-right-radius: 4px; 151 | border-bottom-right-radius: 4px; 152 | } 153 | 154 | .lb-dataContainer:after { 155 | content: ""; 156 | display: table; 157 | clear: both; 158 | } 159 | 160 | .lb-data { 161 | padding: 0 4px; 162 | color: #ccc; 163 | } 164 | 165 | .lb-data .lb-details { 166 | width: 85%; 167 | float: left; 168 | text-align: left; 169 | line-height: 1.1em; 170 | } 171 | 172 | .lb-data .lb-caption { 173 | font-size: 13px; 174 | font-weight: bold; 175 | line-height: 1em; 176 | } 177 | 178 | .lb-data .lb-number { 179 | display: block; 180 | clear: left; 181 | padding-bottom: 1em; 182 | font-size: 12px; 183 | color: #999999; 184 | } 185 | 186 | .lb-data .lb-close { 187 | display: block; 188 | float: right; 189 | width: 30px; 190 | height: 30px; 191 | background: url(../images/close.png) top right no-repeat; 192 | text-align: right; 193 | outline: none; 194 | filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=70); 195 | opacity: 0.7; 196 | -webkit-transition: opacity 0.2s; 197 | -moz-transition: opacity 0.2s; 198 | -o-transition: opacity 0.2s; 199 | transition: opacity 0.2s; 200 | } 201 | 202 | .lb-data .lb-close:hover { 203 | cursor: pointer; 204 | filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=100); 205 | opacity: 1; 206 | } 207 | -------------------------------------------------------------------------------- /web/css/login.css: -------------------------------------------------------------------------------- 1 | @charset "utf-8"; 2 | /* CSS Document */ 3 | 4 | body{ 5 | background: url(../images/login_bgx.gif); 6 | } 7 | 8 | #login-box { 9 | width: 403px; 10 | margin: 100px auto 0; 11 | } 12 | 13 | #login-box .logo { 14 | text-align: center; 15 | margin-bottom: 20px; 16 | } 17 | 18 | #login-box .logo img { 19 | width: 196px; 20 | height: 46px; 21 | border: none; 22 | } 23 | 24 | #login-box .border { 25 | background: url(../images/login_border_bg.png) no-repeat; 26 | width: 403px; 27 | height: 302px; 28 | } 29 | 30 | #login-box .border { 31 | padding: 30px 40px; 32 | } 33 | 34 | #login-box .border form#mobile-login-form { 35 | display: block; 36 | } 37 | 38 | #login-box .border form#account-login-form { 39 | opacity: 0; 40 | display: none; 41 | } -------------------------------------------------------------------------------- /web/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cheikh-wang/LazyWaimai-Web/c054ee4c11e11ad4e92abc376889335ad93fb4e3/web/favicon.ico -------------------------------------------------------------------------------- /web/images/close.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cheikh-wang/LazyWaimai-Web/c054ee4c11e11ad4e92abc376889335ad93fb4e3/web/images/close.png -------------------------------------------------------------------------------- /web/images/loading.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cheikh-wang/LazyWaimai-Web/c054ee4c11e11ad4e92abc376889335ad93fb4e3/web/images/loading.gif -------------------------------------------------------------------------------- /web/images/login_bgx.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cheikh-wang/LazyWaimai-Web/c054ee4c11e11ad4e92abc376889335ad93fb4e3/web/images/login_bgx.gif -------------------------------------------------------------------------------- /web/images/login_border_bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cheikh-wang/LazyWaimai-Web/c054ee4c11e11ad4e92abc376889335ad93fb4e3/web/images/login_border_bg.png -------------------------------------------------------------------------------- /web/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cheikh-wang/LazyWaimai-Web/c054ee4c11e11ad4e92abc376889335ad93fb4e3/web/images/logo.png -------------------------------------------------------------------------------- /web/images/next.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cheikh-wang/LazyWaimai-Web/c054ee4c11e11ad4e92abc376889335ad93fb4e3/web/images/next.png -------------------------------------------------------------------------------- /web/images/prev.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cheikh-wang/LazyWaimai-Web/c054ee4c11e11ad4e92abc376889335ad93fb4e3/web/images/prev.png -------------------------------------------------------------------------------- /web/index-test.php: -------------------------------------------------------------------------------- 1 | run(); 17 | -------------------------------------------------------------------------------- /web/index.php: -------------------------------------------------------------------------------- 1 | run(); 13 | -------------------------------------------------------------------------------- /web/js/login.js: -------------------------------------------------------------------------------- 1 | $(function() { 2 | 3 | var mobileLoginFrom = $('#mobile-login-form'); 4 | var accountLoginForm = $('#account-login-form'); 5 | 6 | //////////////////////////////////////////// 7 | //////// 切换登录方式 ///////////// 8 | //////////////////////////////////////////// 9 | var speed = 400; 10 | $('#to-account-login').click(function(){ 11 | mobileLoginFrom.fadeTo(speed, 0.01).hide(); 12 | accountLoginForm.fadeTo(speed, 1).show(); 13 | }); 14 | $('#to-mobile-login').click(function(){ 15 | accountLoginForm.fadeTo(speed, 0.01).hide() 16 | mobileLoginFrom.fadeTo(speed, 1).show(); 17 | }); 18 | 19 | 20 | //////////////////////////////////////////// 21 | //////// 发送验证码 ///////////// 22 | //////////////////////////////////////////// 23 | $('button[name="send-sms-btn"]', mobileLoginFrom).click(function() { 24 | var phone = $('input[name="phone"]', mobileLoginFrom).val(); 25 | if (phone == '') { 26 | $.toaster({message : '手机号不能为空', title : '', priority : 'danger'}); 27 | return false; 28 | } 29 | 30 | var $this = $(this); 31 | 32 | // 发送验证码的 url 33 | var sendSmsUrl = $this.attr('data-url'); 34 | 35 | // 通过ajax发送短信验证码 36 | $.ajax({ 37 | url : sendSmsUrl, 38 | type : 'post', 39 | data : {phone : phone}, 40 | dataType : 'json', 41 | beforeSend : function() { 42 | // 避免重复点击 43 | $this.attr('disabled', true); 44 | }, 45 | success : function(data) { 46 | if (data.status === 'ok') { 47 | $.toaster({message : '短信发送成功, 请注意查收...', title : '', priority : 'success'}); 48 | 49 | $this.html('60 秒后可重发'); 50 | $this.find('em').countdown((new Date()).getTime() + 59000, function (event) { 51 | $(this).text(event.strftime('%S')); 52 | }).on('finish.countdown', function(event) { 53 | $this.attr('disabled', false); 54 | $this.text('重新发送'); 55 | }); 56 | } else { 57 | $.toaster({message : data.message, title : '', priority : 'danger'}); 58 | $this.attr('disabled', false); 59 | } 60 | }, 61 | error : function() { 62 | $.toaster({message : '系统异常', title : '', priority : 'danger'}); 63 | $this.attr('disabled', false); 64 | } 65 | }); 66 | }); 67 | 68 | 69 | //////////////////////////////////////////// 70 | //////// 使用手机号登录 //////////// 71 | //////////////////////////////////////////// 72 | $('button[name="login-btn"]', mobileLoginFrom).click(function() { 73 | var phone = $('input[name="phone"]', mobileLoginFrom).val(); 74 | var code = $('input[name="code"]', mobileLoginFrom).val(); 75 | var remember = $('input[name="remember"]', mobileLoginFrom).is(':checked') ? 1 : 0; 76 | if (phone == '') { 77 | $.toaster({message : '手机号不能为空', title : '', priority : 'danger'}); 78 | return false; 79 | } 80 | if (code == '') { 81 | $.toaster({message : '验证码不能为空', title : '', priority : 'danger'}); 82 | return false; 83 | } 84 | 85 | var $this = $(this); 86 | 87 | // 验证手机号登录的url 88 | var loginUrl = $this.attr('data-url'); 89 | 90 | // 通过ajax验证手机号登录 91 | $.ajax({ 92 | url : loginUrl, 93 | type : 'post', 94 | data : { 95 | phone : phone, 96 | code : code, 97 | remember : remember 98 | }, 99 | dataType : 'json', 100 | beforeSend : function() { // 避免重复点击 101 | $this.attr('disabled', true); 102 | }, 103 | success : function(data) { 104 | if (data.status === 'err') { 105 | $.toaster({message : data.message, title : '', priority : 'danger'}); 106 | $this.attr('disabled', false); 107 | } 108 | }, 109 | error : function() { 110 | $.toaster({message : '系统异常', title : '', priority : 'danger'}); 111 | $this.attr('disabled', false); 112 | } 113 | }); 114 | }); 115 | 116 | 117 | //////////////////////////////////////////// 118 | //////// 使用账户登录 ///////////// 119 | //////////////////////////////////////////// 120 | $('button[name="login-btn"]', accountLoginForm).click(function() { 121 | var username = $('input[name="username"]', accountLoginForm).val(); 122 | var password = $('input[name="password"]', accountLoginForm).val(); 123 | var remember = $('input[name="remember"]', accountLoginForm).is(':checked') ? 1 : 0; 124 | if (username == '') { 125 | $.toaster({message : '帐号不能为空', title : '', priority : 'danger'}); 126 | return false; 127 | } 128 | if (password == '') { 129 | $.toaster({message : '密码不能为空', title : '', priority : 'danger'}); 130 | return false; 131 | } 132 | 133 | var $this = $(this); 134 | 135 | // 验证手机号登录的url 136 | var loginUrl = $this.attr('data-url'); 137 | 138 | // 通过ajax验证手机号登录 139 | $.ajax({ 140 | url : loginUrl, 141 | type : 'post', 142 | data : { 143 | username : username, 144 | password : password, 145 | remember : remember 146 | }, 147 | dataType : 'json', 148 | beforeSend : function() { // 避免重复点击 149 | $this.attr('disabled', true); 150 | }, 151 | success : function(data) { 152 | if (data.status === 'err') { 153 | $.toaster({message : data.message, title : '', priority : 'danger'}); 154 | $this.attr('disabled', false); 155 | } 156 | }, 157 | error : function() { 158 | $.toaster({message : '系统异常', title : '', priority : 'danger'}); 159 | $this.attr('disabled', false); 160 | } 161 | }); 162 | }); 163 | }); -------------------------------------------------------------------------------- /web/js/setup.js: -------------------------------------------------------------------------------- 1 | $(function() { 2 | 3 | var rowTemplate = '
' 4 | + '
' 5 | + '' 6 | + '' 7 | + '' 8 | + '' 9 | + '
' 10 | + '' 11 | + '
' 12 | + '' 13 | + '' 14 | + '' 15 | + '' 16 | + '
' 17 | + '' 18 | + '' 19 | + '
'; 20 | var addBtnTemplate = ''; 23 | var deleteBtnTemplate = ''; 26 | 27 | var timeRows = $('#time-rows'); 28 | 29 | function addTimeRow(openTime, closeTime) { 30 | var row = $(rowTemplate); 31 | var operateBtn = $('.operate-btn', row); 32 | 33 | if (timeRows.children().length == 0) { 34 | operateBtn.append(addBtnTemplate); 35 | } else { 36 | operateBtn.append(deleteBtnTemplate); 37 | } 38 | timeRows.append(row); 39 | 40 | row.find('.clockpicker').clockpicker({ 41 | afterDone: updateTimes 42 | }); 43 | 44 | if (openTime != null && closeTime != null) { 45 | row.find('input.open-time').val(openTime); 46 | row.find('input.close-time').val(closeTime); 47 | } 48 | } 49 | 50 | function deleteTimeRow() { 51 | var row = $(this).closest('div.time-row'); 52 | row.remove(); 53 | updateTimes(); 54 | } 55 | 56 | function updateTimes() { 57 | var times = []; 58 | $('div.time-row', timeRows).each(function() { 59 | var openTime = $(this).find('input.open-time').val(); 60 | var closeTime = $(this).find('input.close-time').val(); 61 | times.push({ 62 | 'open_time': openTime, 63 | 'close_time': closeTime 64 | }); 65 | }); 66 | timeRows.prev('input').val(JSON.stringify(times)); 67 | } 68 | 69 | function setupDefaultTimes() { 70 | var json = timeRows.prev('input').val(); 71 | var times = null; 72 | try { 73 | times = $.parseJSON(json); 74 | } catch (e) { 75 | //console.error(e); 76 | // noting to do... 77 | } 78 | if (times == null || times.length == 0) { 79 | addTimeRow(null, null); 80 | } else { 81 | for (var i = 0; i < times.length; i++) { 82 | var time = times[i]; 83 | addTimeRow(time['open_time'], time['close_time']); 84 | } 85 | } 86 | } 87 | 88 | $(document).on('click', '.add-time-row', addTimeRow); 89 | $(document).on('click', '.delete-time-row', deleteTimeRow); 90 | 91 | setupDefaultTimes(); 92 | 93 | 94 | 95 | 96 | 97 | $('#booking-times').multiselect({ 98 | enableCollapsibleOptGroups: true, 99 | onDropdownHidden: function() { 100 | var times = []; 101 | $('#booking-times option:selected').each(function() { 102 | times.push($(this).val()); 103 | }); 104 | $('#booking-times').prev('input').val(times.join(',')); 105 | } 106 | }); 107 | }); -------------------------------------------------------------------------------- /web/js/site.js: -------------------------------------------------------------------------------- 1 | $(function() { 2 | 3 | $('#side-menu').metisMenu(); 4 | 5 | var element = $('#side-menu li.active').parent().addClass('in').parent(); 6 | if (element.is('li')) { 7 | element.addClass('active'); 8 | } 9 | }); -------------------------------------------------------------------------------- /web/js/update-phone.js: -------------------------------------------------------------------------------- 1 | $(function() { 2 | 3 | function toastSuccess(message) { 4 | $.toaster({message : message, title : '', priority : 'success'}); 5 | } 6 | 7 | function toastError(message) { 8 | $.toaster({message : message, title : '', priority : 'danger'}); 9 | } 10 | 11 | //////////////////////////////////////////// 12 | //////// 发送验证码 ///////////// 13 | //////////////////////////////////////////// 14 | $('#send-sms-btn').click(function() { 15 | var phone = $('#changephoneform-phone').val(); 16 | if (phone == '') { 17 | toastError('手机号不能为空'); 18 | return false; 19 | } 20 | 21 | var $this = $(this); 22 | 23 | // 发送验证码的 url 24 | var sendSmsUrl = $(this).attr('data-url'); 25 | 26 | // 通过ajax发送短信验证码 27 | $.ajax({ 28 | url : sendSmsUrl, 29 | type : 'post', 30 | data : {phone : phone}, 31 | dataType : 'json', 32 | beforeSend : function() { 33 | // 避免重复点击 34 | $this.attr('disabled', true); 35 | } 36 | }).done(function (data) { 37 | if (data.status === 'ok') { 38 | toastSuccess('短信发送成功,请注意查收...'); 39 | 40 | $this.html('60 秒后可重发'); 41 | $this.find('em').countdown((new Date()).getTime() + 59000, function (event) { 42 | $(this).text(event.strftime('%S')); 43 | }).on('finish.countdown', function(event) { 44 | $this.attr('disabled', false); 45 | $this.text('重新发送'); 46 | }); 47 | } else { 48 | $this.attr('disabled', false); 49 | 50 | toastError(data.message); 51 | } 52 | }); 53 | }); 54 | }); -------------------------------------------------------------------------------- /web/raw/booking_times.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "group_label": "上午", 4 | "group_items": [ 5 | "09:00-09:15", 6 | "09:15-09:30", 7 | "09:30-09:45", 8 | "09:45-10:00", 9 | "10:00-10:15", 10 | "10:15-10:30", 11 | "10:30-10:45", 12 | "10:45-11:00", 13 | "11:00-11:15", 14 | "11:15-11:30", 15 | "11:30-11:45", 16 | "11:45-12:00", 17 | "12:00-12:15", 18 | "12:15-12:30", 19 | "12:30-12:45", 20 | "12:45-13:00", 21 | "13:00-13:15", 22 | "13:15-13:30", 23 | "13:30-13:45", 24 | "13:45-14:00" 25 | ] 26 | }, 27 | { 28 | "group_label": "下午", 29 | "group_items": [ 30 | "16:00-16:15", 31 | "16:15-16:30", 32 | "16:30-16:45", 33 | "16:45-17:00", 34 | "17:00-17:15", 35 | "17:15-17:30", 36 | "17:30-17:45", 37 | "17:45-18:00", 38 | "18:00-18:15", 39 | "18:15-18:30", 40 | "18:30-18:45", 41 | "18:45-19:00", 42 | "19:00-19:15", 43 | "19:15-19:30", 44 | "19:30-19:45", 45 | "19:45-20:00" 46 | ] 47 | } 48 | ] -------------------------------------------------------------------------------- /web/robots.txt: -------------------------------------------------------------------------------- 1 | User-Agent: * 2 | Disallow: / 3 | -------------------------------------------------------------------------------- /widgets/Alert.php: -------------------------------------------------------------------------------- 1 | getSession()->setFlash('error', 'This is the message'); 16 | * \Yii::$app->getSession()->setFlash('success', 'This is the message'); 17 | * \Yii::$app->getSession()->setFlash('info', 'This is the message'); 18 | * ``` 19 | * 20 | * Multiple messages could be set as follows: 21 | * 22 | * ```php 23 | * \Yii::$app->getSession()->setFlash('error', ['Error 1', 'Error 2']); 24 | * ``` 25 | * 26 | * @author Kartik Visweswaran 27 | * @author Alexander Makarov 28 | */ 29 | class Alert extends \yii\bootstrap\Widget 30 | { 31 | /** 32 | * @var array the alert types configuration for the flash messages. 33 | * This array is setup as $key => $value, where: 34 | * - $key is the name of the session flash variable 35 | * - $value is the bootstrap alert type (i.e. danger, success, info, warning) 36 | */ 37 | public $alertTypes = [ 38 | 'error' => 'alert-danger', 39 | 'danger' => 'alert-danger', 40 | 'success' => 'alert-success', 41 | 'info' => 'alert-info', 42 | 'warning' => 'alert-warning' 43 | ]; 44 | 45 | /** 46 | * @var array the options for rendering the close button tag. 47 | */ 48 | public $closeButton = []; 49 | 50 | public function init() 51 | { 52 | parent::init(); 53 | 54 | $session = \Yii::$app->getSession(); 55 | $flashes = $session->getAllFlashes(); 56 | $appendCss = isset($this->options['class']) ? ' ' . $this->options['class'] : ''; 57 | 58 | foreach ($flashes as $type => $data) { 59 | if (isset($this->alertTypes[$type])) { 60 | $data = (array) $data; 61 | foreach ($data as $i => $message) { 62 | /* initialize css class for each alert box */ 63 | $this->options['class'] = $this->alertTypes[$type] . $appendCss; 64 | 65 | /* assign unique id to each alert box */ 66 | $this->options['id'] = $this->getId() . '-' . $type . '-' . $i; 67 | 68 | echo \yii\bootstrap\Alert::widget([ 69 | 'body' => $message, 70 | 'closeButton' => $this->closeButton, 71 | 'options' => $this->options, 72 | ]); 73 | } 74 | 75 | $session->removeFlash($type); 76 | } 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /widgets/Gallery.php: -------------------------------------------------------------------------------- 1 | apiRoute === null) { 33 | throw new Exception('$apiRoute must be set.', 500); 34 | } 35 | 36 | $images = []; 37 | foreach ($this->images as $image) { 38 | /** @var $image BusinessScene */ 39 | $images[] = array( 40 | 'id' => $image->id, 41 | 'original_url' => $image->original_url, 42 | 'thumb_url' => $image->thumb_url, 43 | 'rank' => $image->rank 44 | ); 45 | } 46 | 47 | $baseUrl = [ 48 | $this->apiRoute, 49 | 'type' => $this->type 50 | ]; 51 | 52 | $opts = array( 53 | 'uploadUrl' => Url::to($baseUrl + ['action' => 'ajaxUpload']), 54 | 'deleteUrl' => Url::to($baseUrl + ['action' => 'delete']), 55 | 'arrangeUrl' => Url::to($baseUrl + ['action' => 'order']), 56 | 'photos' => $images, 57 | ); 58 | 59 | $opts = Json::encode($opts); 60 | $view = $this->getView(); 61 | GalleryAsset::register($view); 62 | $view->registerJs("$('#{$this->id}').gallery({$opts});"); 63 | 64 | $this->options['id'] = $this->id; 65 | $this->options['class'] = 'gallery'; 66 | 67 | return $this->render('@app/views/gallery', [ 68 | 'options' => $this->options 69 | ]); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /yii: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | run(); 24 | exit($exitCode); 25 | -------------------------------------------------------------------------------- /yii.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | rem ------------------------------------------------------------- 4 | rem Yii command line bootstrap script for Windows. 5 | rem 6 | rem @author Qiang Xue 7 | rem @link http://www.yiiframework.com/ 8 | rem @copyright Copyright (c) 2008 Yii Software LLC 9 | rem @license http://www.yiiframework.com/license/ 10 | rem ------------------------------------------------------------- 11 | 12 | @setlocal 13 | 14 | set YII_PATH=%~dp0 15 | 16 | if "%PHP_COMMAND%" == "" set PHP_COMMAND=php.exe 17 | 18 | "%PHP_COMMAND%" "%YII_PATH%yii" %* 19 | 20 | @endlocal 21 | --------------------------------------------------------------------------------