diff --git a/backend/controllers/ConfigController.php b/backend/controllers/ConfigController.php deleted file mode 100755 index 53da01f..0000000 --- a/backend/controllers/ConfigController.php +++ /dev/null @@ -1,76 +0,0 @@ - [ - 'class' => VerbFilter::className(), - 'actions' => [ - 'delete' => ['POST'], - ], - ], - ]; - } - - /** - * Lists all Category models. - * @return mixed - */ - public function actionIndex() - { - return $this->render('index'); - } - - - /** - * Updates an existing Category model. - * If update is successful, the browser will be redirected to the 'view' page. - * @param integer $id - * @return mixed - * @throws NotFoundHttpException if the model cannot be found - */ - public function actionUpdate($id) - { - $model = $this->findModel($id); - - if ($model->load(Yii::$app->request->post()) && $model->save()) { - return $this->redirect('index'); - } - - return $this->render('update', [ - 'model' => $model, - ]); - } - - /** - * Finds the Category model based on its primary key value. - * If the model is not found, a 404 HTTP exception will be thrown. - * @param integer $id - * @return Category the loaded model - * @throws NotFoundHttpException if the model cannot be found - */ - protected function findModel($id) - { - if (($model = Category::findOne($id)) !== null) { - return $model; - } - - throw new NotFoundHttpException('The requested page does not exist.'); - } -} diff --git a/backend/controllers/SiteController.php b/backend/controllers/SiteController.php index e19447c..bd5c7b0 100755 --- a/backend/controllers/SiteController.php +++ b/backend/controllers/SiteController.php @@ -14,9 +14,13 @@ use yii\web\Cookie; use yii\web\ForbiddenHttpException; use yii\web\NotAcceptableHttpException; use yii\web\NotFoundHttpException; +use backend\logic\PermissionManager; +use ReflectionException; +use yii\base\InvalidConfigException; /** * Site controller + * @DESCRIBE 网站基本权限 DESCRIBE */ class SiteController extends Controller { @@ -24,8 +28,35 @@ class SiteController extends Controller /** * {@inheritdoc} */ - public function actions() - { + public function behaviors() { + return [ + 'access' => [ + 'class' => AccessControl::className(), + 'rules' => [ + [ + 'actions' => ['login', 'error', 'get-permission'], + 'allow' => true, + ], + [ + 'actions' => ['logout', 'index'], + 'allow' => true, + 'roles' => ['@'], + ], + ], + ], + 'verbs' => [ + 'class' => VerbFilter::className(), + 'actions' => [ +// 'logout' => ['post'], + ], + ], + ]; + } + + /** + * {@inheritdoc} + */ + public function actions() { return [ 'error' => [ 'class' => 'yii\web\ErrorAction', @@ -46,9 +77,16 @@ class SiteController extends Controller return $this->render('index'); } + /** + * Login action. + * + * @return string + * @ACTION 登录 ACTION + * @throws ForbiddenHttpException + */ + public function actionLogin() { - public function actionLogin() - { + $this->layout = 'base'; if (!Yii::$app->user->isGuest) { return $this->goHome(); } @@ -64,6 +102,7 @@ class SiteController extends Controller * Logout action. * * @return string + * @ACTION 登出 ACTION */ public function actionLogout() { @@ -72,13 +111,15 @@ class SiteController extends Controller return $this->goHome(); } - public function actionTest() + /** + * 获取权限 + * @return array + * @throws ReflectionException + * @throws InvalidConfigException + */ + public function actionGetPermission() { - $searchModel = new CategorySearch(); - return $this->render('test', [ - 'name' => 'blobt', - 'model' => $searchModel - ]); + return PermissionManager::getAppPermission(); } } diff --git a/backend/logic/PermissionManager.php b/backend/logic/PermissionManager.php new file mode 100644 index 0000000..0a0c230 --- /dev/null +++ b/backend/logic/PermissionManager.php @@ -0,0 +1,166 @@ + [ + * 'class' => 'xxx\xxx\xxx', + * 'id' => '{actionDescribe}' + * ] + * @return array 返回已匹配到的权限。返回例子:[["{controllerDescribe}"=>["{actionDescribe}"=>"action route"]]] + * @throws yii\base\InvalidConfigException + * @throws ReflectionException + */ + public static function getAppPermission() + { + $permission = []; + $permission = self::getControllersAndActions($permission); //获取该app的所有controller权限 + $permission = self::getModuleControllerAndAction($permission); //获取该app引用module的所有controller权限 + return $permission; + } + + /** + * 获取app下controller已标记的action方法权限 + * 这个方法先是获取该app下controller的路径,控制器的命名空间,然后匹配该路径下面的所有控制器文件 + * 截取控制器文件的控制器基础名称,转换为相应url规则名称,拼接为相应类名 + * 根据控制器id,通过[[Module::createControllerByID]]方法实例化该控制器,最后通过[[PermissionManager::constructingPermissionArray]]获取所有已标记的权限 + * @param array $permission 权限数组 + * @return array 权限数组 + * @throws yii\base\InvalidConfigException + * @throws ReflectionException + */ + private static function getControllersAndActions($permission = []) + { + $dir = Yii::$app->getControllerPath(); + $nameSpace = Yii::$app->controllerNamespace . "\\"; + $fileList = glob($dir."/*Controller.php"); + foreach ($fileList as $file) { + $baseName = substr(basename($file), 0, -14); + + //根据路由规则转换控制器名称 + $name = strtolower(preg_replace('/(?createControllerByID($id); + $permission = self::constructingPermissionArray($controller, $className, $permission); + } + return $permission; + } + + /** + * 获取该app下关联module的已标记controller的权限 + * 这个方法通过[[Module::getModules]]获取该app下关联的module并循环 + * 去除gii和debug,通过[[Module::getModule]]获取子类module,获取命名空间,文件位置,然后匹配该路径下面的所有控制器文件 + * 截取控制器文件的控制器基础名称,转换为相应url规则名称,拼接为相应类名 + * 通过[[BaseYii::createObject]]实例化,最后通过[[PermissionManager::constructingPermissionArray]]获取所有已标记的权限 + * @param array $permission 权限数组 + * @return array 权限数组 + * @throws yii\base\InvalidConfigException + * @throws ReflectionException + */ + private static function getModuleControllerAndAction($permission = []) + { + foreach (Yii::$app->getModules() as $id => $child) { + if(in_array($id, ['gii', 'debug'])) { + continue; + } + $module = Yii::$app->getModule($id); + $nameSpace = $module->controllerNamespace."\\"; + $dir = $module->controllerPath; + $fileList = glob($dir."/*Controller.php"); + foreach ($fileList as $file) { + $baseName = substr(basename($file), 0, -14); + $name = strtolower(preg_replace('/(?id.'/'.ltrim(str_replace(' ', '-', $name), '-'); + $className = $nameSpace . $baseName . 'Controller'; + $controller = Yii::createObject($className, [$id, $module]); + $permission = self::constructingPermissionArray($controller, $className, $permission); + } + } + return $permission; + } + + /** + * 构建权限数组 + * 根据类名,使用ReflectionClass方法获取该类的信息 + * 通过该类的注释,判断是否存在DESCRIBE标记,以此判断是否要记录该类的权限 + * 若存在标记,则通过[[PermissionManager::getActionsInController]]获取actions里面的方法权限数组,通过[[PermissionManager::getActionInController]]获取该类下面的action方法权限数组 + * @param $controllerObject + * @param $className + * @param array $permission 权限数组 + * @return array 权限数组 + * @throws ReflectionException + */ + private static function constructingPermissionArray($controllerObject, $className, $permission = []) + { + $prefix = '/'.$controllerObject->id.'/'; + $reflection = new ReflectionClass($className); //通过ReflectionClass方法获取该类的所有信息,包括参数方法等 + $controllerComment = $reflection->getDocComment(); + $controllerPregRes = preg_match("/(?<=@DESCRIBE ).*?(?= DESCRIBE)/", $controllerComment, $controllerDescribe); + if ($controllerPregRes) { + $permission = self::getActionsInController($controllerObject, $controllerDescribe, $prefix, $permission); + $permission = self::getActionInController($className, $prefix, $controllerDescribe, $permission); + } + return $permission; + } + + /** + * 获取控制器类中actions的方法权限 + * 根据实例化的控制器类,获取actions的方法并且循环,方法存在id这个参数,则记录 + * 最后放回权限数组 + * @param $controllerObject + * @param $controllerDescribe + * @param $prefix + * @param array $permission 权限数组 + * @return array 权限数组 + */ + private static function getActionsInController($controllerObject, $controllerDescribe, $prefix, $permission = []) + { + foreach ($controllerObject->actions() as $id => $item) { + if (isset($item['id'])) { + $permission[$controllerDescribe[0]][$item['id']] = $prefix . $id; + } + } + return $permission; + } + + /** + * 获取控制器类中的action方法权限 + * 通过ReflectionClass方法获取所有的action方法,若注释中存在ACTION标记,则记录 + * @param $className + * @param $prefix + * @param $controllerDescribe + * @param array $permission 权限数组 + * @return array 权限数组 + * @throws ReflectionException + */ + private static function getActionInController($className, $prefix, $controllerDescribe, $permission = []) + { + $reflection = new ReflectionClass($className); //通过ReflectionClass方法获取该类的所有信息,包括参数方法等 + foreach ($reflection->getMethods(ReflectionMethod::IS_PUBLIC) as $method) { + //action的注释 + $actionComment = $method->getDocComment(); + $actionPregRes = preg_match("/(?<=@ACTION ).*?(?= ACTION)/", $actionComment, $actionDescribe); + if ($actionPregRes) { + $actionName = $method->getName(); + if ($actionName != 'actions' && strpos($actionName, 'action') === 0) { + $name = strtolower(preg_replace('/(? ['jpg', 'png', 'jpeg'], self::TYPE_VIDEO => ['mp4'], self::TYPE_EXCEL => [], @@ -31,9 +34,9 @@ class FileManager * @return int|string * 根据文件拓展名在$extension中查找对应的文件类型,若不存在则返回self::TYPE_NONE */ - public function searchType($keyword) + public static function searchType($keyword) { - foreach($this->extension as $key => $type){ + foreach(self::$extension as $key => $type){ if (in_array($keyword, $type)) { return $key; } @@ -46,7 +49,8 @@ class FileManager * @param $ownId * @param $ownType * @return array|bool - * @throws \Exception + * @throws HttpException + * @throws ServerErrorHttpException * 根据临时文件id将临时文件保存在文件中 */ public function saveTemFileToFile($temFileIdArr, $ownId, $ownType) @@ -71,14 +75,14 @@ class FileManager $tra->commit(); return ['status' => true, 'info' => '保存成功', 'first_file_id' => $firstFileId]; - } catch (\Exception $e) { + } catch (yii\db\Exception $e) { $tra->rollBack(); - throw new \Exception($e->getMessage()); + throw new ServerErrorHttpException($e->getMessage()); } } /** - * @param $temFile + * @param TemFile|$temFile * @param $ownId * @param $ownType * @return array @@ -122,4 +126,92 @@ class FileManager } return true; } + + /** + * @param yii\base\Model|$dataModel //数据模型 + * @param $fileIdStrName //表单中临时保存的文件id字符串组合(以,隔开) + * @param string $fileNameInModel //需要保存到数据库中的数据表字段名称 + * @param $fileOldIdStr //数据库中已经保存的文件id字符串组合(以,隔开) + * @param $fileType //File模型中定义的own_type常量 + * @return bool + * @throws Exception + * 数据模型保存文件操作 + */ + public static function saveFileInModel($dataModel, $fileIdStrName, $fileOldIdStr, $fileType, $fileNameInModel = '') + { + if (is_array($dataModel)) { + throw new ServerErrorHttpException('数据模型不得为数组'); + } + $dataModel->save(); + $saveFileRes = GoodsManager::saveFile(explode(',', $dataModel->$fileIdStrName), $dataModel, explode(',', $fileOldIdStr), $fileType); + if ($fileNameInModel && $saveFileRes['status'] && $saveFileRes['first_file_id'] !== 0) { + $dataModel->$fileNameInModel = $saveFileRes['first_file_id']; + if (!$dataModel->save()) { + throw new ServerErrorHttpException('dataModel保存失败'); + } + } + return true; + } + + /** + * @param $fileName + * @param $formData + * @return int + * 保存临时文件操作 + */ + public static function saveTemFile($fileName, $formData) + { + $temFileModel = new TemFile(); + $temFileModel->user_id = Yii::$app->user->identity->id; + $temFileModel->name = $fileName; + $temFileModel->type = FileManager::searchType(pathinfo($formData['path'])['extension']); + $temFileModel->alias = $formData['alias']; + $temFileModel->path = $formData['path']; + $temFileModel->save(); + return $temFileModel->id; + } + + /** + * @param $data //ajax接收的数据 + * @return string + * 上传插件点击删除按钮时处理文件id字符串(以,分隔) + */ + public static function dealFileIdStrInDel($data) + { + $imgIdArr = explode(',', $data['imgid']); + if(isset($data['data']['alias'])) { + $temFile = TemFile::findOne(['alias' => $data['data']['alias']]); + if ($temFile) { + $imgIdArr = array_diff($imgIdArr, [$temFile->id]); + } + }else{ + foreach ($data as $key => $value) { + $temFile = File::findOne(['alias' => $value]); + if ($temFile) { + $imgIdArr = array_diff($imgIdArr, [$temFile->id]); + } + } + } + return implode(',', $imgIdArr); + } + + /** + * @param $fileIdStr + * @return false|string + * 加载已有的文件数据 + */ + public static function loadExitFile($fileIdStr) + { + $fileIdArr = explode(',', $fileIdStr); + $files = File::find()->where(['id' => $fileIdArr])->all(); + $fileInfo = array(); + if($files) { + foreach ($files as $key => $file) { + $fileInfo[$key]['name'] = $file->alias; + $fileInfo[$key]['path'] = Yii::$app->request->hostInfo . '/' . $file->path; + $fileInfo[$key]['size'] = filesize($file->path); + } + } + return json_encode($fileInfo); + } } \ No newline at end of file diff --git a/backend/modules/file/models/ars/File.php b/backend/modules/file/models/ars/File.php index 8f29387..1908c57 100755 --- a/backend/modules/file/models/ars/File.php +++ b/backend/modules/file/models/ars/File.php @@ -2,8 +2,8 @@ namespace backend\modules\file\models\ars; -use Yii; use yii\behaviors\TimestampBehavior; +use yii\db\ActiveRecord; /** * This is the model class for table "ats_file". @@ -20,7 +20,7 @@ use yii\behaviors\TimestampBehavior; * @property int $updated_at 更新时间 * @property int $created_at 创建时间 */ -class File extends \yii\db\ActiveRecord +class File extends ActiveRecord { //own_type const OWN_TYPE_GOODS_INDEX = 1;//商品首页 @@ -83,7 +83,7 @@ class File extends \yii\db\ActiveRecord { return [ [ - 'class' => TimestampBehavior::className(), + 'class' => TimestampBehavior::class, 'createdAtAttribute' => 'created_at', 'updatedAtAttribute' => 'updated_at', 'value' => function () { diff --git a/backend/modules/file/models/ars/TemFile.php b/backend/modules/file/models/ars/TemFile.php index c8b02fb..fd04b0b 100755 --- a/backend/modules/file/models/ars/TemFile.php +++ b/backend/modules/file/models/ars/TemFile.php @@ -2,8 +2,8 @@ namespace backend\modules\file\models\ars; -use Yii; use yii\behaviors\TimestampBehavior; +use yii\db\ActiveRecord; /** * This is the model class for table "ats_tem_file". @@ -17,7 +17,7 @@ use yii\behaviors\TimestampBehavior; * @property int $updated_at 更新时间 * @property int $created_at 创建时间 */ -class TemFile extends \yii\db\ActiveRecord +class TemFile extends ActiveRecord { /** @@ -69,7 +69,7 @@ class TemFile extends \yii\db\ActiveRecord { return [ [ - 'class' => TimestampBehavior::className(), + 'class' => TimestampBehavior::class, 'createdAtAttribute' => 'created_at', 'updatedAtAttribute' => 'updated_at', 'value' => function() { diff --git a/backend/modules/goods/controllers/AttributeController.php b/backend/modules/goods/controllers/AttributeController.php index 41f3796..67e7ba9 100755 --- a/backend/modules/goods/controllers/AttributeController.php +++ b/backend/modules/goods/controllers/AttributeController.php @@ -2,12 +2,14 @@ namespace backend\modules\goods\controllers; +use backend\modules\goods\logic\goods\GoodsManager; use Yii; use backend\modules\goods\models\ars\Attribute; use backend\modules\goods\models\searchs\AttributeSearch; use yii\web\Controller; use yii\web\NotFoundHttpException; use yii\filters\VerbFilter; +use iron\widget\Excel; /** * AttributeController implements the CRUD actions for Attribute model. @@ -70,13 +72,7 @@ class AttributeController extends Controller $model->cat_id = 0; if ($model->load(Yii::$app->request->post()) && $model->validate()) { - $model->value = str_replace(',', ',', $model->value); - $array = explode(',', $model->value); - if (count($array) != count(array_unique($array))) { - \Yii::$app->getSession()->setFlash('error', '不能有相同的属性值'); - return $this->render('create', ['model' => $model]); - } - if ($model->save()) { + if (GoodsManager::updateAttribute($model)) { return $this->redirect(['index']); } } @@ -98,16 +94,9 @@ class AttributeController extends Controller $model = $this->findModel($id); if ($model->load(Yii::$app->request->post()) && $model->validate()) { - $model->value = str_replace(',', ',', $model->value); - $array = explode(',', $model->value); - if (count($array) != count(array_unique($array))) { - \Yii::$app->getSession()->setFlash('error', '不能有相同的属性值'); - return $this->render('create', ['model' => $model]); - } - if ($model->save()) { + if (GoodsManager::updateAttribute($model)) { return $this->redirect(['index']); } - return $this->redirect('index'); } return $this->render('update', [ @@ -159,7 +148,7 @@ class AttributeController extends Controller } else { $dataProvider = $searchModel->search($params); } - \iron\widget\Excel::export([ + Excel::export([ 'models' => $dataProvider->getModels(), 'format' => 'Xlsx', 'asAttachment' => true, diff --git a/backend/modules/goods/controllers/BrandController.php b/backend/modules/goods/controllers/BrandController.php index c96b99f..99bc76b 100755 --- a/backend/modules/goods/controllers/BrandController.php +++ b/backend/modules/goods/controllers/BrandController.php @@ -8,6 +8,7 @@ use backend\modules\goods\models\searchs\BrandSearch; use yii\web\Controller; use yii\web\NotFoundHttpException; use yii\filters\VerbFilter; +use iron\widget\Excel; /** * BrandController implements the CRUD actions for Brand model. @@ -140,7 +141,7 @@ class BrandController extends Controller } else { $dataProvider = $searchModel->search($params); } - \iron\widget\Excel::export([ + Excel::export([ 'models' => $dataProvider->getModels(), 'format' => 'Xlsx', 'asAttachment' => true, diff --git a/backend/modules/goods/controllers/CategoryController.php b/backend/modules/goods/controllers/CategoryController.php index 305db28..40dd420 100755 --- a/backend/modules/goods/controllers/CategoryController.php +++ b/backend/modules/goods/controllers/CategoryController.php @@ -2,14 +2,17 @@ namespace backend\modules\goods\controllers; -use backend\modules\goods\logic\goods\GoodsManager; use backend\modules\file\models\ars\File; +use Exception; use Yii; use backend\modules\goods\models\ars\Category; use backend\modules\goods\models\searchs\CategorySearch; use yii\web\Controller; use yii\web\NotFoundHttpException; use yii\filters\VerbFilter; +use backend\modules\file\logic\file\FileManager; +use yii\web\Response; +use iron\widget\Excel; /** * CategoryController implements the CRUD actions for Category model. @@ -72,30 +75,21 @@ class CategoryController extends Controller } /** - * Creates a new Category model. - * If creation is successful, the browser will be redirected to the 'view' page. - * @return mixed + * @return string|Response + * @throws Exception */ public function actionCreate() { $model = new Category(); $model->is_show = Category::IS_SHOW_DISPLAY; $model->sort_order = 0; - if ($model->load(Yii::$app->request->post()) && $model->validate()) { - //类目图片上传保存处理 - $icon_image_id_str = $model->iconImageId; - $model->save(); - $save_icon_image_res = GoodsManager::saveFile(explode(',', $icon_image_id_str), $model, [], File::OWN_TYPE_CATEGORY_ICON); - if($save_icon_image_res['status']){ - $model->icon = $save_icon_image_res['first_file_id']; - $model->save(); + $res = FileManager::saveFileInModel($model, 'iconImageId', '', File::OWN_TYPE_CATEGORY_ICON, 'icon'); + if ($res) { + return $this->redirect('index'); } - - return $this->redirect('index'); } - return $this->render('create', [ 'model' => $model, ]); @@ -107,6 +101,7 @@ class CategoryController extends Controller * @param integer $id * @return mixed * @throws NotFoundHttpException if the model cannot be found + * @throws Exception */ public function actionUpdate($id) { @@ -114,21 +109,13 @@ class CategoryController extends Controller $model->iconImageId = $model->icon; //记录已保存的类目图片id,用于修改 $icon_image_old_id_arr = $model->icon; - if ($model->load(Yii::$app->request->post()) && $model->validate()) { - //类目图片上传保存处理 - $icon_image_id_str = $model->iconImageId; - $model->save(); - $save_icon_image_res = GoodsManager::saveFile(explode(',', $icon_image_id_str), $model, explode(',', $icon_image_old_id_arr), File::OWN_TYPE_CATEGORY_ICON); - if($save_icon_image_res['status'] && $save_icon_image_res['first_file_id'] !== 0){ - $model->icon = $save_icon_image_res['first_file_id']; - $model->save(); + $res = FileManager::saveFileInModel($model, 'iconImageId', $icon_image_old_id_arr, File::OWN_TYPE_CATEGORY_ICON, 'icon'); + if ($res) { + return $this->redirect('index'); } - - return $this->redirect('index'); } - return $this->render('update', [ 'model' => $model, ]); @@ -179,7 +166,7 @@ class CategoryController extends Controller } else { $dataProvider = $searchModel->search($params); } - \iron\widget\Excel::export([ + Excel::export([ 'models' => $dataProvider->getModels(), 'format' => 'Xlsx', 'asAttachment' => true, @@ -194,19 +181,12 @@ class CategoryController extends Controller public function actionSaveFile() { $data = Yii::$app->request->get('data'); - $file_name = Yii::$app->request->get('fileName')[0]; + $fileName = Yii::$app->request->get('fileName')[0]; if ($data['status'] == true) { - $model = new \backend\modules\file\models\ars\TemFile(); - $model->user_id = Yii::$app->user->identity->id; - $model->name = $file_name; - $file_manager = new \backend\modules\file\logic\file\FileManager(); - $model->type = $file_manager->searchType(pathinfo($data['path'])['extension']); - $model->alias = $data['alias']; - $model->path = $data['path']; - $model->save(); - return $model->id; + return FileManager::saveTemFile($fileName, $data); } + return false; } /** @@ -215,24 +195,8 @@ class CategoryController extends Controller */ public function actionImgIdDel() { - $img_id = Yii::$app->request->get('imgid'); - $img_id_arr = explode(',', $img_id); - if(isset(Yii::$app->request->get('data')['alias'])) { - $alias = Yii::$app->request->get('data')['alias']; - $tem_file = \backend\modules\file\models\ars\TemFile::findOne(['alias' => $alias]); - if ($tem_file) { - $img_id_arr = array_diff($img_id_arr, [$tem_file->id]); - } - }else{ - foreach (Yii::$app->request->get() as $key => $value) { - $tem_file = \backend\modules\file\models\ars\File::findOne(['alias' => $value]); - if ($tem_file) { - $img_id_arr = array_diff($img_id_arr, [$tem_file->id]); - } - } - } - $img_id_str = implode(',', $img_id_arr); - return $img_id_str; + $data = Yii::$app->request->get(); + return FileManager::dealFileIdStrInDel($data); } /** @@ -241,19 +205,7 @@ class CategoryController extends Controller */ public function actionImageFile() { - $file_id_str = Yii::$app->request->get('fileidstr'); - $file_id_arr = explode(',', $file_id_str); - $data = \backend\modules\file\models\ars\File::find()->where(['id' => $file_id_arr])->all(); - $res = array(); - if($data) { - $i = 0; - foreach ($data as $key => $value) { - $res[$i]['name'] = $value->alias; - $res[$i]['path'] = Yii::$app->request->hostInfo . '/' . $value->path; - $res[$i]['size'] = filesize($value->path); - $i++; - } - } - return json_encode($res); + $fileIdStr = Yii::$app->request->get('fileIdStr'); + return FileManager::loadExitFile($fileIdStr); } } diff --git a/backend/modules/goods/controllers/GoodsController.php b/backend/modules/goods/controllers/GoodsController.php index 91d07c6..58166c0 100755 --- a/backend/modules/goods/controllers/GoodsController.php +++ b/backend/modules/goods/controllers/GoodsController.php @@ -2,22 +2,22 @@ namespace backend\modules\goods\controllers; +use backend\modules\file\logic\file\FileManager; +use backend\modules\goods\models\ars\FilterAttr; use backend\modules\goods\models\ars\GoodsAttr; -use backend\modules\goods\models\ars\GoodsSku; -use backend\models\ars\OrderGoods; -use backend\modules\file\models\ars\TemFile; -use MongoDB\Driver\Manager; +use backend\modules\shop\logic\ShopManager; +use Exception; +use Throwable; use Yii; use backend\modules\goods\models\ars\Goods; use backend\modules\goods\models\searchs\GoodsSearch; use yii\web\Controller; -use yii\web\HttpException; use yii\web\NotFoundHttpException; use yii\filters\VerbFilter; use backend\modules\goods\logic\goods\GoodsManager; use backend\modules\file\models\ars\File; -use yii\web\Response; use backend\modules\goods\models\ars\Attribute; +use iron\widget\Excel; /** * GoodsController implements the CRUD actions for Goods model. @@ -91,6 +91,7 @@ class GoodsController extends Controller * Creates a new Goods model. * If creation is successful, the browser will be redirected to the 'view' page. * @return mixed + * @throws Exception */ public function actionCreate() { @@ -103,11 +104,9 @@ class GoodsController extends Controller if ($model->load(Yii::$app->request->post()) && $model->validate()) { //商品封面图和商品详情图上传保存处理 $res = GoodsManager::updateGoods(Yii::$app->request->post(), $model); - if ($res['status']) { + if ($res) { return $this->redirect('index'); } - } else { - $model->ruleVerify = 1; } return $this->render('create', [ 'model' => $model, @@ -120,6 +119,7 @@ class GoodsController extends Controller * @param integer $id * @return mixed * @throws NotFoundHttpException if the model cannot be found + * @throws Exception */ public function actionUpdate($id) { @@ -132,16 +132,18 @@ class GoodsController extends Controller if ($model->load(Yii::$app->request->post()) && $model->validate()) { //商品封面图和商品详情图上传保存处理 $res = GoodsManager::updateGoods(Yii::$app->request->post(), $model, $cover_image_old_id_str, $detail_image_old_id_str); - if ($res['status']) { + if ($res) { return $this->redirect('index'); } } - $model->uniform_postage /= 100; - $model->market_price /= 100; - $model->price /= 100; - $attributeModel = GoodsManager::getAttribute($id); + $model->uniform_postage /= ShopManager::proportionalConversion(ShopManager::UNIT_TYPE_MONEY); + $model->market_price /= ShopManager::proportionalConversion(ShopManager::UNIT_TYPE_MONEY); + $model->price /= ShopManager::proportionalConversion(ShopManager::UNIT_TYPE_MONEY); + $goodsAttributes = GoodsAttr::find()->where(['goods_id' => $id, 'is_delete' => GoodsAttr::IS_DELETE_NO])->andWhere(['!=', 'attr_id', 0])->all(); + $attributeModel = GoodsManager::getAttribute($goodsAttributes); $checkAttr = GoodsManager::getSkuInfo($id); - $filterAttributeModel = GoodsManager::getFilterAttribute($id); + $goodsFilterAttributes = FilterAttr::find()->where(['goods_id' => $id, 'is_delete' => FilterAttr::IS_DELETE_NO])->andWhere(['!=', 'attr_id', 0])->all(); + $filterAttributeModel = GoodsManager::getAttribute($goodsFilterAttributes); $judgeGoodsCategory = GoodsManager::judgeGoodsCategory($model); return $this->render('update', [ 'model' => $model, @@ -196,7 +198,7 @@ class GoodsController extends Controller } else { $dataProvider = $searchModel->search($params); } - \iron\widget\Excel::export([ + Excel::export([ 'models' => $dataProvider->getModels(), 'format' => 'Xlsx', 'asAttachment' => true, @@ -211,19 +213,12 @@ class GoodsController extends Controller public function actionSaveFile() { $data = Yii::$app->request->get('data'); - $file_name = Yii::$app->request->get('fileName')[0]; + $fileName = Yii::$app->request->get('fileName')[0]; if ($data['status'] == true) { - $model = new \backend\modules\file\models\ars\TemFile(); - $model->user_id = Yii::$app->user->identity->id; - $model->name = $file_name; - $file_manager = new \backend\modules\file\logic\file\FileManager(); - $model->type = $file_manager->searchType(pathinfo($data['path'])['extension']); - $model->alias = $data['alias']; - $model->path = $data['path']; - $model->save(); - return $model->id; + return FileManager::saveTemFile($fileName, $data); } + return false; } /** @@ -232,24 +227,8 @@ class GoodsController extends Controller */ public function actionImgIdDel() { - $img_id = Yii::$app->request->get('imgid'); - $img_id_arr = explode(',', $img_id); - if(isset(Yii::$app->request->get('data')['alias'])) { - $alias = Yii::$app->request->get('data')['alias']; - $tem_file = \backend\modules\file\models\ars\TemFile::findOne(['alias' => $alias]); - if ($tem_file) { - $img_id_arr = array_diff($img_id_arr, [$tem_file->id]); - } - }else{ - foreach (Yii::$app->request->get() as $key => $value) { - $tem_file = \backend\modules\file\models\ars\File::findOne(['alias' => $value]); - if ($tem_file) { - $img_id_arr = array_diff($img_id_arr, [$tem_file->id]); - } - } - } - $img_id_str = implode(',', $img_id_arr); - return $img_id_str; + $data = Yii::$app->request->get(); + return FileManager::dealFileIdStrInDel($data); } /** @@ -258,25 +237,8 @@ class GoodsController extends Controller */ public function actionImageFile() { - $rule_verify = Yii::$app->request->get('ruleverify'); - $file_id_str = Yii::$app->request->get('fileidstr'); - $file_id_arr = explode(',', $file_id_str); - if ($rule_verify == 1) { - $data = \backend\modules\file\models\ars\TemFile::find()->where(['id' => $file_id_arr])->all(); - } else { - $data = \backend\modules\file\models\ars\File::find()->where(['id' => $file_id_arr])->all(); - } - $res = array(); - if($data) { - $i = 0; - foreach ($data as $key => $value) { - $res[$i]['name'] = $value->alias; - $res[$i]['path'] = Yii::$app->request->hostInfo . '/' . $value->path; - $res[$i]['size'] = filesize($value->path); - $i++; - } - } - return json_encode($res); + $fileIdStr = Yii::$app->request->get('fileIdStr'); + return FileManager::loadExitFile($fileIdStr); } /** @@ -296,7 +258,7 @@ class GoodsController extends Controller /** * @return array - * @throws \Throwable + * @throws Throwable * 添加sku */ public function actionAddSku() @@ -318,7 +280,7 @@ class GoodsController extends Controller GoodsManager::deleteSku($res['type'], $data, $goodsId); $tra->commit(); return ['status' => true]; - } catch (\Exception $e) { + } catch (Exception $e) { $tra->rollBack(); return ['status' => false, 'info' => $e->getMessage()]; } @@ -331,7 +293,7 @@ class GoodsController extends Controller $type = Yii::$app->request->get('type'); $id = Yii::$app->request->get('goodsId'); $data['sku'] = GoodsManager::getCreatedSku($id, $type); - $data['attributes'] = GoodsManager::getAttrs($id, $type); + $data['attributes'] = GoodsManager::getAttrs($id); return $data; } @@ -342,8 +304,7 @@ class GoodsController extends Controller public function actionFilterAttribute() { $catId = Yii::$app->request->get('catId')??0; - $goodsId = Yii::$app->request->get('goodsId')??0; - $allAttr = Attribute::find()->where(['type' => Attribute::TYPE_ATTR])->andWhere(['cat_id' => [0,$catId]])->asArray()->all(); + $allAttr = Attribute::find()->where(['type' => Attribute::TYPE_ATTR, 'is_delete' => Attribute::IS_DELETE_NO])->andWhere(['cat_id' => [0,$catId]])->asArray()->all(); return json_encode($allAttr); } } diff --git a/backend/modules/goods/controllers/ShopCategoryController.php b/backend/modules/goods/controllers/ShopCategoryController.php index c0feeec..2ee1bfb 100755 --- a/backend/modules/goods/controllers/ShopCategoryController.php +++ b/backend/modules/goods/controllers/ShopCategoryController.php @@ -2,14 +2,16 @@ namespace backend\modules\goods\controllers; -use backend\modules\goods\logic\goods\GoodsManager; +use backend\modules\file\logic\file\FileManager; use backend\modules\file\models\ars\File; +use Exception; use Yii; use backend\modules\goods\models\ars\ShopCategory; use backend\modules\goods\models\searchs\ShopCategorySearch; use yii\web\Controller; use yii\web\NotFoundHttpException; use yii\filters\VerbFilter; +use iron\widget\Excel; /** * ShopcategoryController implements the CRUD actions for ShopCategory model. @@ -75,32 +77,20 @@ class ShopCategoryController extends Controller * Creates a new ShopCategory model. * If creation is successful, the browser will be redirected to the 'view' page. * @return mixed + * @throws Exception */ public function actionCreate() { $model = new ShopCategory(); $model->is_show = ShopCategory::IS_SHOW_HIDE; $model->sort_order = 0; - if ($model->load(Yii::$app->request->post())) { - if ($model->filter_attr != null && is_array($model->filter_attr)) { - $model->filter_attr = implode(',', $model->filter_attr); - } else { - $model->filter_attr = ''; - } - //类目图片上传保存处理 - $icon_image_id_str = $model->iconImageId; - $model->save(); - $save_icon_image_res = GoodsManager::saveFile(explode(',', $icon_image_id_str), $model, [], File::OWN_TYPE_CATEGORY_ICON); - if($save_icon_image_res['status']){ - $model->icon = $save_icon_image_res['first_file_id']; - $model->save(); + $res = FileManager::saveFileInModel($model, 'iconImageId', '', File::OWN_TYPE_CATEGORY_ICON, 'icon'); + if ($res) { + return $this->redirect('index'); } - - return $this->redirect('index'); } - return $this->render('create', [ 'model' => $model, ]); @@ -112,6 +102,7 @@ class ShopCategoryController extends Controller * @param integer $id * @return mixed * @throws NotFoundHttpException if the model cannot be found + * @throws Exception */ public function actionUpdate($id) { @@ -121,26 +112,13 @@ class ShopCategoryController extends Controller } $model->iconImageId = $model->icon; $icon_image_old_id_arr = $model->icon;//记录已保存的类目图片id,用于修改 - if ($model->load(Yii::$app->request->post())) { - if ($model->filter_attr != null && is_array($model->filter_attr)) { - $model->filter_attr = implode(',', $model->filter_attr); - } else { - $model->filter_attr = ''; - } - //类目图片上传保存处理 - $icon_image_id_str = $model->iconImageId; - $model->save(); - $save_icon_image_res = GoodsManager::saveFile(explode(',', $icon_image_id_str), $model, explode(',', $icon_image_old_id_arr), File::OWN_TYPE_CATEGORY_ICON); - if($save_icon_image_res['status'] && $save_icon_image_res['first_file_id'] !== 0){ - $model->icon = $save_icon_image_res['first_file_id']; - $model->save(); + $res = FileManager::saveFileInModel($model, 'iconImageId', $icon_image_old_id_arr, File::OWN_TYPE_CATEGORY_ICON, 'icon'); + if ($res) { + return $this->redirect('index'); } - - return $this->redirect('index'); } - return $this->render('update', [ 'model' => $model, ]); @@ -190,7 +168,7 @@ class ShopCategoryController extends Controller } else { $dataProvider = $searchModel->search($params); } - \iron\widget\Excel::export([ + Excel::export([ 'models' => $dataProvider->getModels(), 'format' => 'Xlsx', 'asAttachment' => true, @@ -205,19 +183,12 @@ class ShopCategoryController extends Controller public function actionSaveFile() { $data = Yii::$app->request->get('data'); - $file_name = Yii::$app->request->get('fileName')[0]; + $fileName = Yii::$app->request->get('fileName')[0]; if ($data['status'] == true) { - $model = new \backend\modules\file\models\ars\TemFile(); - $model->user_id = Yii::$app->user->identity->id; - $model->name = $file_name; - $file_manager = new \backend\modules\file\logic\file\FileManager(); - $model->type = $file_manager->searchType(pathinfo($data['path'])['extension']); - $model->alias = $data['alias']; - $model->path = $data['path']; - $model->save(); - return $model->id; + return FileManager::saveTemFile($fileName, $data); } + return false; } /** @@ -226,24 +197,8 @@ class ShopCategoryController extends Controller */ public function actionImgIdDel() { - $img_id = Yii::$app->request->get('imgid'); - $img_id_arr = explode(',', $img_id); - if(isset(Yii::$app->request->get('data')['alias'])) { - $alias = Yii::$app->request->get('data')['alias']; - $tem_file = \backend\modules\file\models\ars\TemFile::findOne(['alias' => $alias]); - if ($tem_file) { - $img_id_arr = array_diff($img_id_arr, [$tem_file->id]); - } - }else{ - foreach (Yii::$app->request->get() as $key => $value) { - $tem_file = \backend\modules\file\models\ars\File::findOne(['alias' => $value]); - if ($tem_file) { - $img_id_arr = array_diff($img_id_arr, [$tem_file->id]); - } - } - } - $img_id_str = implode(',', $img_id_arr); - return $img_id_str; + $data = Yii::$app->request->get(); + return FileManager::dealFileIdStrInDel($data); } /** @@ -252,19 +207,7 @@ class ShopCategoryController extends Controller */ public function actionImageFile() { - $file_id_str = Yii::$app->request->get('fileidstr'); - $file_id_arr = explode(',', $file_id_str); - $data = \backend\modules\file\models\ars\File::find()->where(['id' => $file_id_arr])->all(); - $res = array(); - if($data) { - $i = 0; - foreach ($data as $key => $value) { - $res[$i]['name'] = $value->alias; - $res[$i]['path'] = Yii::$app->request->hostInfo . '/' . $value->path; - $res[$i]['size'] = filesize($value->path); - $i++; - } - } - return json_encode($res); + $fileIdStr = Yii::$app->request->get('fileIdStr'); + return FileManager::loadExitFile($fileIdStr); } } diff --git a/backend/modules/goods/controllers/SupplierController.php b/backend/modules/goods/controllers/SupplierController.php index 80684f4..ffe0ab8 100755 --- a/backend/modules/goods/controllers/SupplierController.php +++ b/backend/modules/goods/controllers/SupplierController.php @@ -8,6 +8,7 @@ use backend\modules\goods\models\searchs\SupplierSearch; use yii\web\Controller; use yii\web\NotFoundHttpException; use yii\filters\VerbFilter; +use iron\widget\Excel; /** * SupplierController implements the CRUD actions for Supplier model. @@ -140,7 +141,7 @@ class SupplierController extends Controller } else { $dataProvider = $searchModel->search($params); } - \iron\widget\Excel::export([ + Excel::export([ 'models' => $dataProvider->getModels(), 'format' => 'Xlsx', 'asAttachment' => true, diff --git a/backend/modules/goods/logic/goods/GoodsManager.php b/backend/modules/goods/logic/goods/GoodsManager.php index ea4d990..2f98ff3 100755 --- a/backend/modules/goods/logic/goods/GoodsManager.php +++ b/backend/modules/goods/logic/goods/GoodsManager.php @@ -2,28 +2,33 @@ namespace backend\modules\goods\logic\goods; use backend\modules\file\models\ars\File; +use backend\modules\goods\models\ars\Category; +use backend\modules\goods\models\ars\ShopCategory; +use Throwable; use Yii; -use yii\base\Exception; +use yii\db\Exception; use backend\modules\goods\models\ars\GoodsAttr; use backend\modules\goods\models\ars\Attribute; use backend\modules\goods\models\ars\GoodsSku; use backend\modules\goods\models\ars\Goods; use backend\modules\goods\models\ars\FilterAttr; -use backend\modules\goods\models\ars\Category; -use yii\web\HttpException; +use yii\db\StaleObjectException; +use backend\modules\file\logic\file\FileManager; +use yii\web\ServerErrorHttpException; +use backend\modules\shop\logic\ShopManager; class GoodsManager { /** * @param $newFileIdArr - * @param $goodsModel + * @param $hostObject * @param array $oldFileIdArr * @param int $fileType * @return array * @throws \Exception * 保存新文件,删除不需要的文件操作 */ - public static function saveFile($newFileIdArr, $goodsModel, $oldFileIdArr = [], $fileType = 1) + public static function saveFile($newFileIdArr, $hostObject, $oldFileIdArr = [], $fileType = File::OWN_TYPE_GOODS_INDEX) { $tra = Yii::$app->db->beginTransaction(); try { @@ -31,8 +36,8 @@ class GoodsManager $createFileIdArr = array_diff($newFileIdArr, $oldFileIdArr); //创建文件 - $class = new \backend\modules\file\logic\file\FileManager(); - $createFileRes = $class->saveTemFileToFile($createFileIdArr, $goodsModel->id, $fileType); + $class = new FileManager(); + $createFileRes = $class->saveTemFileToFile($createFileIdArr, $hostObject->id, $fileType); //需要删除的文件id $delFileIdArr = array_diff($oldFileIdArr, $newFileIdArr); @@ -54,45 +59,35 @@ class GoodsManager $tra->commit(); return ['status' => true, 'info' => '操作成功', 'first_file_id' => $firstFileId]; - } catch (\Exception $e) { + } catch (Exception $e) { $tra->rollBack(); - throw new \Exception($e->getMessage()); + throw new ServerErrorHttpException($e->getMessage()); } } /** - * @param $data - * @param $model + * @param $formData + * @param Goods|$goodsModel * @param null $coverImageOldIdStr * @param null $detailImageOldIdStr - * @return array - * @throws \Exception - * 创建修改商品操作 + * @return bool + * @throws \Exception 创建修改商品操作 */ - public static function updateGoods($data, $model, $coverImageOldIdStr = null, $detailImageOldIdStr = null) + public static function updateGoods($formData, $goodsModel, $coverImageOldIdStr = null, $detailImageOldIdStr = null) { - $attribute = $data['attribute']; - $filterAttribute = $data['filterattribute']; - $model->uniform_postage *= 100; - $model->market_price *= 100; - $model->price *= 100; + $attribute = $formData['attribute']; + $filterAttribute = $formData['filterattribute']; + $goodsModel->uniform_postage *= ShopManager::proportionalConversion(ShopManager::UNIT_TYPE_MONEY); + $goodsModel->market_price *= ShopManager::proportionalConversion(ShopManager::UNIT_TYPE_MONEY); + $goodsModel->price *= ShopManager::proportionalConversion(ShopManager::UNIT_TYPE_MONEY); $tra = Yii::$app->db->beginTransaction(); try { - if (!$model->save()) { - throw new \Exception('商品保存失败'); - } - $saveCoverImageRes = self::saveFile(explode(',', $model->coverImageId), $model, explode(',', $coverImageOldIdStr)); - $saveDetailImageRes = self::saveFile(explode(',', $model->detailImageId), $model, explode(',', $detailImageOldIdStr), File::OWN_TYPE_GOODS_DETAILS); - if($saveCoverImageRes['first_file_id'] !== 0) { - $model->image = $saveCoverImageRes['first_file_id']; - if (!$model->save()) { - throw new \Exception('图片保存失败'); - } - } - self::addAttributeOperating(['id' => $model->id, 'attribute' => $attribute]); - self::addFilterAttributeOperating(['id' => $model->id, 'filterAttribute' => $filterAttribute]); + FileManager::saveFileInModel($goodsModel, 'coverImageId', $coverImageOldIdStr, File::OWN_TYPE_GOODS_INDEX, 'image'); + FileManager::saveFileInModel($goodsModel, 'detailImageId', $detailImageOldIdStr, File::OWN_TYPE_GOODS_DETAILS); + self::addAttributeOperating(['id' => $goodsModel->id, 'attribute' => $attribute]); + self::addFilterAttributeOperating(['id' => $goodsModel->id, 'filterAttribute' => $filterAttribute]); $tra->commit(); - return ['status' => true]; + return true; } catch (\yii\base\Exception $e) { $tra->rollBack(); throw new \Exception($e->getMessage()); @@ -198,33 +193,32 @@ class GoodsManager } /** - * @param $id - * @return Attribute|array|null + * @param $attributes + * @return array * 获取属性信息 */ - public static function getAttribute($id) + public static function getAttribute($attributes) { - $goodsAttributes = GoodsAttr::find()->where(['goods_id' => $id, 'is_delete' => GoodsAttr::IS_DELETE_NO])->andWhere(['!=', 'attr_id', 0])->all(); - $filter = []; - $goodsAttributeModel = []; - if (!$goodsAttributes) { - return $goodsAttributeModel; + $filter = array(); + $attributeModel = array(); + if (!$attributes) { + return $attributeModel; } - foreach ($goodsAttributes as $key => $value) { + foreach ($attributes as $key => $value) { $attribute = Attribute::findOne($value->attr_id); if (!in_array($attribute->name, $filter)) { $filter[] = $attribute->name; $attribute = ['name' => $attribute->name, 'id' => $attribute->id, 'value' => [$value->attr_value]]; - $goodsAttributeModel[] = $attribute; + $attributeModel[] = $attribute; } else { - foreach ($goodsAttributeModel as $k => $v) { + foreach ($attributeModel as $k => $v) { if ($v['name'] == $attribute->name) { - $goodsAttributeModel[$k]['value'][] = $value->attr_value; + $attributeModel[$k]['value'][] = $value->attr_value; } } } } - return $goodsAttributeModel; + return $attributeModel; } /** @@ -261,6 +255,7 @@ class GoodsManager /** * @param $id + * @param int $type * @return mixed * 已创建sku信息 */ @@ -271,11 +266,11 @@ class GoodsManager $data['data'] = []; if ($data['type'] == Goods::SKU_MODE_ATTR) { $sku = GoodsSku::find() - ->where(['goods_id' => $id, 'is_manaul' => 0]) + ->where(['goods_id' => $id, 'is_manual' => 0]) ->all(); } else { $sku = GoodsSku::find() - ->where(['goods_id' => $id, 'is_manaul' => 1]) + ->where(['goods_id' => $id, 'is_manual' => 1]) ->all(); } foreach ($sku as $value) { @@ -303,7 +298,7 @@ class GoodsManager $data['id'] = $sku->id; $data['price'] = $sku->price; $data['stock'] = $sku->stock; - $data['weight'] = $sku->weight/1000; + $data['weight'] = $sku->weight/ShopManager::proportionalConversion(ShopManager::UNIT_TYPE_WEIGHT); return $data; } @@ -327,7 +322,7 @@ class GoodsManager $ret['id'] = $attribute->id; $ret['attrValue'] = GoodsAttr::find() ->select(['id', 'attr_value']) - ->where(['goods_id' => $id]) + ->where(['goods_id' => $id, 'is_delete' => GoodsAttr::IS_DELETE_NO]) ->andWhere(['attr_id' => $attribute->id]) ->asArray() ->all(); @@ -348,10 +343,10 @@ class GoodsManager $ids = []; if ($type == Goods::SKU_MODE_MANUAL) { $query = GoodsSku::find() - ->where(['is_manaul' => 1]); + ->where(['is_manual' => 1]); } else { $query = GoodsSku::find() - ->where(['is_manaul' => 0]); + ->where(['is_manual' => 0]); } $sku = $query ->andWhere(['goods_id' => $goodsId]) @@ -391,7 +386,7 @@ class GoodsManager throw new \Exception('手动属性修改失败'); } $goodsSku->goods_attr = (string)$attr->id; - $goodsSku->is_manaul = 1; + $goodsSku->is_manual = 1; } else { $goodsSku->goods_attr = implode(',', array_filter($sku['value'])); } @@ -420,8 +415,8 @@ class GoodsManager * @param $data * @param $goodsId * @return bool - * @throws \Throwable - * @throws \yii\db\StaleObjectException + * @throws Throwable + * @throws StaleObjectException * 删除sku */ public static function deleteSku($type, $data, $goodsId) @@ -431,10 +426,10 @@ class GoodsManager } if ($type == Goods::SKU_MODE_MANUAL) { $query = GoodsSku::find() - ->where(['is_manaul' => 1]); + ->where(['is_manual' => 1]); } else { $query = GoodsSku::find() - ->where(['is_manaul' => 0]); + ->where(['is_manual' => 0]); } $sku = $query ->andWhere(['goods_id' => $goodsId]) @@ -444,13 +439,14 @@ class GoodsManager foreach ($sku as $value) { $value->delete(); } + + return true; } /** * @param $data * @return bool - * @throws Exception - * @throws HttpException + * @throws \Exception * 创建修改商品筛选属性操作 */ private static function addFilterAttributeOperating($data) @@ -484,11 +480,12 @@ class GoodsManager } } } + return true; } /** * @param $goodsFilterAttr - * @throws HttpException + * @throws \Exception * 删除商品筛选属性 */ private static function delFilterAttribute($goodsFilterAttr) @@ -534,36 +531,6 @@ class GoodsManager return $newAttr; } - /** - * @param $id - * @return Attribute|array|null - * 获取筛选属性信息 - */ - public static function getFilterAttribute($id) - { - $goodsFilterAttributes = FilterAttr::find()->where(['goods_id' => $id, 'is_delete' => FilterAttr::IS_DELETE_NO])->andWhere(['!=', 'attr_id', 0])->all(); - $filter = []; - $goodsFilterAttributeModel = []; - if (!$goodsFilterAttributes) { - return $goodsFilterAttributeModel; - } - foreach ($goodsFilterAttributes as $key => $value) { - $attribute = Attribute::findOne($value->attr_id); - if (!in_array($attribute->name, $filter)) { - $filter[] = $attribute->name; - $attribute = ['name' => $attribute->name, 'id' => $attribute->id, 'value' => [$value->attr_value]]; - $goodsFilterAttributeModel[] = $attribute; - } else { - foreach ($goodsFilterAttributeModel as $k => $v) { - if ($v['name'] == $attribute->name) { - $goodsFilterAttributeModel[$k]['value'][] = $value->attr_value; - } - } - } - } - return $goodsFilterAttributeModel; - } - /** * @param $goodsModel * @return bool @@ -584,8 +551,101 @@ class GoodsManager return false; //否则返回false,表示后台分类可以修改 } - public static function categoryBtreeList() + /** + * @param Attribute|$attrModel + * @return bool + * 编辑规格属性 + */ + public static function updateAttribute($attrModel) { + $attrModel->value = str_replace(',', ',', $attrModel->value); + $attrValue = explode(',', $attrModel->value); + if (count($attrValue) != count(array_unique($attrValue))) { + Yii::$app->getSession()->setFlash('error', '不能有相同的属性值'); + return false; + } + if (!$attrModel->save()) { + Yii::$app->getSession()->setFlash('error', '保存失败'); + return false; + } + return true; + } + + /** + * 根据pid的父级关系,给字段|___做层级标记 + * @param $data + * @param int $pid + * @param int $lev + * @return array + */ + public static function btree($data, $pid = 0, $lev = 0) + { + $tree = []; + foreach ($data as $k => $value) { + if ($value['pid'] == $pid) { + $value['name'] = str_repeat('|___', $lev) . $value['name']; + $tree[] = $value; + $tree = array_merge($tree, self::btree($data, $value['id'], $lev + 1)); + } + } + return $tree; + } + + /** + * 构建下拉数组 + * @param $dataArr + * @param int $needDefault + * @return array + */ + public static function constructDropList($dataArr, $needDefault = 1) + { + $data = []; + if ($needDefault) { + $data[0] = '一级分类'; + } + foreach ($dataArr as $k => $v) { + $data[$v['id']] = $v['name']; + } + return $data; + } + /** + * 获取分类中所有下级id + * @param int $ownId + * @param array $idArr + * @return array + */ + public static function subCategoryId($ownId = 0, $idArr = []) + { + $idArr[] = $ownId; + if ($ownId) { + $subCategoryModel = Category::find()->where(['pid' => $ownId])->all(); + if ($subCategoryModel) { + foreach ($subCategoryModel as $subCategory) { + $idArr = self::subCategoryId($subCategory->id, $idArr); + } + } + } + return $idArr; + } + + /** + * 获取分类中所有下级id + * @param int $ownId + * @param array $idArr + * @return array + */ + public static function subShopCategoryId($ownId = 0, $idArr = []) + { + $idArr[] = $ownId; + if ($ownId) { + $subShopCategoryModel = ShopCategory::find()->where(['pid' => $ownId])->all(); + if ($subShopCategoryModel) { + foreach ($subShopCategoryModel as $subShopCategory) { + $idArr = self::subShopCategoryId($subShopCategory->id, $idArr); + } + } + } + return $idArr; } } \ No newline at end of file diff --git a/backend/modules/goods/migrations/m191211_060934_update_column_limit_count_in_table_atg_goods.php b/backend/modules/goods/migrations/m191211_060934_update_column_limit_count_in_table_atg_goods.php new file mode 100644 index 0000000..b49fa9c --- /dev/null +++ b/backend/modules/goods/migrations/m191211_060934_update_column_limit_count_in_table_atg_goods.php @@ -0,0 +1,22 @@ +dropColumn('atg_goods', 'limit_count'); + $this->addColumn('atg_goods', 'limit_count', $this->integer(11)->defaultValue("0")->comment("限购数量")); + } + + public function down() + { + $this->dropColumn('atg_goods', 'limit_count'); + $this->addColumn('atg_goods', 'limit_count', $this->integer(11)->defaultValue(null)->comment("限购数量")); + return true; + } +} diff --git a/backend/modules/goods/migrations/m191211_092335_update_column_sort_order_in_table_atg_goods.php b/backend/modules/goods/migrations/m191211_092335_update_column_sort_order_in_table_atg_goods.php new file mode 100644 index 0000000..4b3e960 --- /dev/null +++ b/backend/modules/goods/migrations/m191211_092335_update_column_sort_order_in_table_atg_goods.php @@ -0,0 +1,22 @@ +dropColumn('atg_goods', 'sort_order'); + $this->addColumn('atg_goods', 'sort_order', $this->smallInteger(3)->defaultValue("999")->comment('排序')); + } + + public function down() + { + $this->dropColumn('atg_goods', 'sort_order'); + $this->addColumn('atg_goods', 'sort_order', $this->smallInteger(3)->defaultValue(null)->comment('排序')); + return true; + } +} diff --git a/backend/modules/goods/migrations/m191217_091658_update_column_is_manual_in_table_atg_goods_sku.php b/backend/modules/goods/migrations/m191217_091658_update_column_is_manual_in_table_atg_goods_sku.php new file mode 100644 index 0000000..fc5b910 --- /dev/null +++ b/backend/modules/goods/migrations/m191217_091658_update_column_is_manual_in_table_atg_goods_sku.php @@ -0,0 +1,41 @@ +dropColumn('atg_goods_sku', 'is_manaul'); + $this->addColumn('atg_goods_sku', 'is_manual', $this->tinyInteger(1)->notNull()->defaultValue(0)->comment('是否手动')); + } + + /** + * {@inheritdoc} + */ + public function safeDown() + { + return true; + } + + /* + // Use up()/down() to run migration code without a transaction. + public function up() + { + + } + + public function down() + { + echo "m191217_091658_update_column_is_manual_in_table_atg_goods_sku cannot be reverted.\n"; + + return false; + } + */ +} diff --git a/backend/modules/goods/migrations/m191217_092101_add_column_sku_image_in_table_atg_goods_sku.php b/backend/modules/goods/migrations/m191217_092101_add_column_sku_image_in_table_atg_goods_sku.php new file mode 100644 index 0000000..3ae6b83 --- /dev/null +++ b/backend/modules/goods/migrations/m191217_092101_add_column_sku_image_in_table_atg_goods_sku.php @@ -0,0 +1,26 @@ +addColumn('atg_goods_sku', 'sku_image', $this->integer(11)->notNull()->defaultValue("0")->comment('sku图片id')); + } + + /** + * {@inheritdoc} + */ + public function safeDown() + { + $this->dropColumn('atg_goods_sku', 'sku_image'); + return true; + } +} diff --git a/backend/modules/goods/models/ars/Attribute.php b/backend/modules/goods/models/ars/Attribute.php index ad8870f..269510a 100755 --- a/backend/modules/goods/models/ars/Attribute.php +++ b/backend/modules/goods/models/ars/Attribute.php @@ -98,9 +98,12 @@ class Attribute extends \yii\db\ActiveRecord public function beforeSave($insert) { - if(!$this->type){ + if (!$this->type) { $this->type = self::TYPE_ATTR; } + if (!$this->sort_order) { + $this->sort_order = "999"; + } return parent::beforeSave($insert); // TODO: Change the autogenerated stub } diff --git a/backend/modules/goods/models/ars/Category.php b/backend/modules/goods/models/ars/Category.php index 827ec36..6c9b073 100755 --- a/backend/modules/goods/models/ars/Category.php +++ b/backend/modules/goods/models/ars/Category.php @@ -5,6 +5,7 @@ namespace backend\modules\goods\models\ars; use Yii; use yii\behaviors\TimestampBehavior; use backend\modules\file\models\ars\File; +use backend\modules\goods\logic\goods\GoodsManager; /** * This is the model class for table "atg_category". @@ -88,7 +89,7 @@ class Category extends \yii\db\ActiveRecord { return [ [ - 'class' => TimestampBehavior::className(), + 'class' => TimestampBehavior::class, 'createdAtAttribute' => 'created_at', 'updatedAtAttribute' => 'updated_at', 'value' => function() { @@ -99,16 +100,33 @@ class Category extends \yii\db\ActiveRecord } /** + * @param int $ownId + * @param int $needDefault * @return array * 数据键值对 */ - public static function modelColumn() + public static function modelColumn($ownId = 0, $needDefault = 1) { - return $column = self::find()->select(['name'])->where(['is_delete' => self::IS_DELETE_NO])->indexBy('id')->column(); + $query = self::find()->where(['is_delete' => self::IS_DELETE_NO]); + if ($ownId) { + $query->andWhere(['not in', 'id', GoodsManager::subCategoryId($ownId)]); + } + $data = $query->asArray()->all(); + $data = GoodsManager::btree($data); + $data = GoodsManager::constructDropList($data, $needDefault); + return $data; } public function getIconFile() { return $this->hasOne(File::className(), ['id' => 'icon']); } + + public function beforeSave($insert) + { + if (!$this->sort_order) { + $this->sort_order = "999"; + } + return parent::beforeSave($insert); // TODO: Change the autogenerated stub + } } diff --git a/backend/modules/goods/models/ars/Goods.php b/backend/modules/goods/models/ars/Goods.php index 0028e81..a43c03f 100755 --- a/backend/modules/goods/models/ars/Goods.php +++ b/backend/modules/goods/models/ars/Goods.php @@ -28,7 +28,7 @@ use backend\modules\goods\models\ars\Supplier; * @property int $height 高度 * @property int $diameter 直径 * @property string $unit 单位 - * @property int $sold_count 已售数量 + * @property int $sold_count 总销量 * @property int $limit_count 限购数量 * @property int $stock 库存 * @property int $stock_warn 库存警告 @@ -194,7 +194,7 @@ class Goods extends \yii\db\ActiveRecord 'height' => '高度', 'diameter' => '直径', 'unit' => '单位', - 'sold_count' => '已售数量', + 'sold_count' => '总销量', 'limit_count' => '限购数量', 'stock' => '库存', 'stock_warn' => '库存警告', @@ -252,6 +252,12 @@ class Goods extends \yii\db\ActiveRecord if (!$this->sn) { $this->sn = time() . rand(1111, 9999); } + if (!$this->limit_count) { + $this->limit_count = 0; + } + if (!$this->sort_order) { + $this->sort_order = "999"; + } return parent::beforeSave($insert); // TODO: Change the autogenerated stub } diff --git a/backend/modules/goods/models/ars/GoodsSku.php b/backend/modules/goods/models/ars/GoodsSku.php index 68bf7e9..21e1348 100755 --- a/backend/modules/goods/models/ars/GoodsSku.php +++ b/backend/modules/goods/models/ars/GoodsSku.php @@ -23,12 +23,13 @@ use yii\behaviors\TimestampBehavior; * @property int $is_delete 是否删除,1为已删除 * @property int $created_at 创建时间 * @property int $updated_at 更新时间 - * @property int $is_manaul 是否手动 + * @property int $is_manual 是否手动 * @property int $weight 重量 + * @property int $sku_image sku图片id */ class GoodsSku extends \yii\db\ActiveRecord { - //是否手动is_manaul + //是否手动is_manual const IS_MANUAL_YES = 1; //是手动 const IS_MANUAL_NO = 0; //不是手动 //是否删除is_delete @@ -49,7 +50,7 @@ class GoodsSku extends \yii\db\ActiveRecord { return [ [['goods_id', 'goods_sn'], 'required'], - [['goods_id', 'sold_count', 'stock', 'market_price', 'price', 'model_id', 'is_sale', 'sort_order', 'is_delete', 'is_manaul'], 'integer'], + [['goods_id', 'sold_count', 'stock', 'market_price', 'price', 'model_id', 'is_sale', 'sort_order', 'is_delete', 'is_manual', 'sku_image'], 'integer'], [['goods_code'], 'string', 'max' => 50], [['goods_sn', 'goods_attr'], 'string', 'max' => 60], [['weight'], 'safe'] @@ -78,6 +79,7 @@ class GoodsSku extends \yii\db\ActiveRecord 'created_at' => '创建时间', 'updated_at' => '更新时间', 'weight' => '重量', + 'sku_image' => 'sku图片id', ]; } diff --git a/backend/modules/goods/models/ars/ShopCategory.php b/backend/modules/goods/models/ars/ShopCategory.php index 9f4e199..37ff7d9 100755 --- a/backend/modules/goods/models/ars/ShopCategory.php +++ b/backend/modules/goods/models/ars/ShopCategory.php @@ -2,9 +2,11 @@ namespace backend\modules\goods\models\ars; +use backend\modules\goods\logic\goods\GoodsManager; use Yii; use yii\behaviors\TimestampBehavior; use backend\modules\file\models\ars\File; +use yii\db\ActiveQuery; /** * This is the model class for table "atg_shop_category". @@ -107,16 +109,36 @@ class ShopCategory extends \yii\db\ActiveRecord } /** - * @return array + * @param int $ownId + * @param int $needDefault + * @return array 数据键值对 * 数据键值对 */ - public static function modelColumn() + public static function modelColumn($ownId = 0, $needDefault = 1) { - return $column = self::find()->select(['name'])->where(['is_delete' => self::IS_DELETE_NO])->indexBy('id')->column(); + $query = self::find()->where(['is_delete' => self::IS_DELETE_NO]); + if ($ownId) { + $query->andWhere(['not in', 'id', GoodsManager::subShopCategoryId($ownId)]); + } + $data = $query->asArray()->all(); + $data = GoodsManager::btree($data); + $data = GoodsManager::constructDropList($data, $needDefault); + return $data; } public function getIconFile() { return $this->hasOne(File::className(), ['id' => 'icon']); } + + public function beforeSave($insert) + { + if ($this->filter_attr != null && is_array($this->filter_attr)) { + $this->filter_attr = implode(',', $this->filter_attr); + } + if (!$this->sort_order) { + $this->sort_order = "999"; + } + return parent::beforeSave($insert); // TODO: Change the autogenerated stub + } } diff --git a/backend/modules/goods/models/searchs/CategorySearch.php b/backend/modules/goods/models/searchs/CategorySearch.php index 969f1ac..b9c3028 100755 --- a/backend/modules/goods/models/searchs/CategorySearch.php +++ b/backend/modules/goods/models/searchs/CategorySearch.php @@ -55,6 +55,18 @@ class CategorySearch extends Category ], 'id', 'name', + 'pid', + [ + 'attribute' => 'pid', + 'value' => function ($model) { + $parentsCategory = Category::findOne($model->pid); + if ($parentsCategory) { + return $parentsCategory->name; + } else { + return '一级分类'; + } + } + ], ['attribute' => 'icon', 'contentOptions' => [ 'align' => 'center', diff --git a/backend/modules/goods/models/searchs/GoodsSearch.php b/backend/modules/goods/models/searchs/GoodsSearch.php index aa0c6e1..8d0ab51 100755 --- a/backend/modules/goods/models/searchs/GoodsSearch.php +++ b/backend/modules/goods/models/searchs/GoodsSearch.php @@ -7,8 +7,7 @@ use yii\data\ActiveDataProvider; use yii\helpers\ArrayHelper; use backend\modules\goods\models\ars\Goods; use yii\bootstrap4\Html; -use backend\modules\goods\models\ars\Category; -use backend\modules\goods\models\ars\ShopCategory; +use backend\modules\shop\logic\ShopManager; /** * GoodsSearch represents the model behind the search form of `backend\modules\goods\models\ars\Goods`. @@ -55,7 +54,6 @@ class GoodsSearch extends Goods 'width' => '2%', 'align' => 'center' ], - 'id', ['attribute' => 'image', 'contentOptions' => [ 'align' => 'center', @@ -69,20 +67,19 @@ class GoodsSearch extends Goods } ], + 'id', 'name', [ - 'attribute' => 'cat_id', - 'width' => '10%', + 'attribute' => 'market_price', 'value' => function ($model) { - return $model->category ? $model->category->name : ''; - }, + return sprintf("%1\$.2f",$model->market_price / ShopManager::proportionalConversion(ShopManager::UNIT_TYPE_MONEY)); + } ], [ - 'attribute' => 'shop_cat_id', - 'width' => '10%', + 'attribute' => 'price', 'value' => function ($model) { - return $model->shopCategory ? $model->shopCategory->name : ''; - }, + return sprintf("%1\$.2f",$model->price / ShopManager::proportionalConversion(ShopManager::UNIT_TYPE_MONEY)); + } ], [ 'attribute' => 'stock', @@ -95,7 +92,6 @@ class GoodsSearch extends Goods } }, ], - 'price', ['attribute' => 'is_sale', 'width' => '5%', 'value' => @@ -104,16 +100,11 @@ class GoodsSearch extends Goods }, ], - 'updated_at:datetime', + 'sort_order', [ 'class' => 'iron\grid\ActionColumn', 'align' => 'center', 'config' => [ - [ - 'name' => 'view', - 'icon' => 'list', - 'title' => '详情', - ], [ 'name' => 'update', 'icon' => 'pencil', diff --git a/backend/modules/goods/views/attribute/_search.php b/backend/modules/goods/views/attribute/_search.php index 0a9d3be..a25765b 100755 --- a/backend/modules/goods/views/attribute/_search.php +++ b/backend/modules/goods/views/attribute/_search.php @@ -29,6 +29,19 @@ use \blobt\widgets\DateRangePicker; ]) ?> +
+ field($model, 'name', [ + "template" => "{input}{error}", + "inputOptions" => [ + "placeholder" => "规格名称", + "class" => "form-control", + ], + "errorOptions" => [ + "class" => "error-tips" + ] + ]) + ?> +
field($model, "created_at_range", [ "template" => "{input}{error}", diff --git a/backend/modules/goods/views/brand/_search.php b/backend/modules/goods/views/brand/_search.php index ed21354..544ff25 100755 --- a/backend/modules/goods/views/brand/_search.php +++ b/backend/modules/goods/views/brand/_search.php @@ -29,6 +29,19 @@ use \blobt\widgets\DateRangePicker; ]) ?>
+
+ field($model, 'name', [ + "template" => "{input}{error}", + "inputOptions" => [ + "placeholder" => "品牌名", + "class" => "form-control", + ], + "errorOptions" => [ + "class" => "error-tips" + ] + ]) + ?> +
field($model, "created_at_range", [ "template" => "{input}{error}", diff --git a/backend/modules/goods/views/category/_form.php b/backend/modules/goods/views/category/_form.php index 87b09b9..c0ad3c8 100755 --- a/backend/modules/goods/views/category/_form.php +++ b/backend/modules/goods/views/category/_form.php @@ -17,7 +17,7 @@ use backend\modules\goods\models\ars\Category; field($model, 'name')->textInput(['maxlength' => true]) ?> - field($model, 'pid')->dropDownList(array_merge([0 => '一级分类'], Category::modelColumn())) ?> + field($model, 'pid')->dropDownList(Category::modelColumn($model->id)) ?> field($model, 'sort_order')->textInput() ?> @@ -31,7 +31,7 @@ use backend\modules\goods\models\ars\Category; 'fillInAttribute' => 'iconImageId', 'model' => $model, 'previewConfig' => [ - 'url' => Url::to(['image-file', 'fileidstr' => $model->iconImageId]), + 'url' => Url::to(['image-file', 'fileIdStr' => $model->iconImageId]), ], ])->label('类目图片') ?> diff --git a/backend/modules/goods/views/category/_search.php b/backend/modules/goods/views/category/_search.php index d43cb1d..baf7c29 100755 --- a/backend/modules/goods/views/category/_search.php +++ b/backend/modules/goods/views/category/_search.php @@ -29,6 +29,19 @@ use \blobt\widgets\DateRangePicker; ]) ?>
+
+ field($model, 'name', [ + "template" => "{input}{error}", + "inputOptions" => [ + "placeholder" => "类别名称", + "class" => "form-control", + ], + "errorOptions" => [ + "class" => "error-tips" + ] + ]) + ?> +
field($model, "created_at_range", [ "template" => "{input}{error}", diff --git a/backend/modules/goods/views/goods/_form.php b/backend/modules/goods/views/goods/_form.php index 564faa4..63c2fb8 100755 --- a/backend/modules/goods/views/goods/_form.php +++ b/backend/modules/goods/views/goods/_form.php @@ -79,7 +79,7 @@ use yii\helpers\Url; 'fillInAttribute' => 'coverImageId', 'model' => $model, 'previewConfig' => [ - 'url' => Url::to(['image-file', 'fileidstr' => $model->coverImageId]), + 'url' => Url::to(['image-file', 'fileIdStr' => $model->coverImageId]), ], ])->label('商品封面图') ?> @@ -93,7 +93,7 @@ use yii\helpers\Url; 'fillInAttribute' => 'detailImageId', 'model' => $model, 'previewConfig' => [ - 'url' => Url::to(['image-file', 'fileidstr' => $model->detailImageId]), + 'url' => Url::to(['image-file', 'fileIdStr' => $model->detailImageId]), ], ])->label('商品详情图') ?> diff --git a/backend/modules/goods/views/goods/_search.php b/backend/modules/goods/views/goods/_search.php index 8e00aec..5b8fca9 100755 --- a/backend/modules/goods/views/goods/_search.php +++ b/backend/modules/goods/views/goods/_search.php @@ -68,7 +68,7 @@ use backend\modules\goods\models\ars\ShopCategory; "errorOptions" => [ "class" => "error-tips" ] - ])->dropDownList(Category::modelColumn(), ['prompt' => '后台商品类别']); + ])->dropDownList(Category::modelColumn(0, 0), ['prompt' => '后台商品类别']); ?>
@@ -81,7 +81,7 @@ use backend\modules\goods\models\ars\ShopCategory; "errorOptions" => [ "class" => "error-tips" ] - ])->dropDownList(ShopCategory::modelColumn(), ['prompt' => '前端商品类别']); + ])->dropDownList(ShopCategory::modelColumn(0,0), ['prompt' => '前端商品类别']); ?>
diff --git a/backend/modules/goods/views/goods/create.php b/backend/modules/goods/views/goods/create.php index 1d87807..13874f2 100755 --- a/backend/modules/goods/views/goods/create.php +++ b/backend/modules/goods/views/goods/create.php @@ -12,8 +12,42 @@ $this->params['breadcrumbs'][] = ['label' => '商品列表', 'url' => ['index']] $this->params['breadcrumbs'][] = $this->title; Yii::$app->params['bsVersion'] = '4.x'; ?> +
-
['class' => 'container-fluid']]); @@ -66,13 +100,13 @@ Yii::$app->params['bsVersion'] = '4.x'; 'encodeLabels' => false ]); ?> - +
+
- 'btn btn-success']) ?> - 'btn btn-info']) ?> + ', ['class' => 'btn-float btn btn-success']) ?> + ', ['index'], ['class' => 'btn-float btn btn-info']) ?>
-
diff --git a/backend/modules/goods/views/goods/goods.php b/backend/modules/goods/views/goods/goods.php index c94a5f4..5058a04 100755 --- a/backend/modules/goods/views/goods/goods.php +++ b/backend/modules/goods/views/goods/goods.php @@ -4,64 +4,77 @@ use backend\modules\goods\models\ars\Category; use backend\modules\goods\models\ars\Brand; use backend\modules\goods\models\ars\ShopCategory; use backend\modules\goods\models\ars\Supplier; -use linyao\widgets\Select2; use yii\bootstrap4\Html; use backend\modules\goods\models\ars\Goods; /* @var $this yii\web\View */ /* @var $model backend\modules\goods\models\ars\Goods */ /* @var $form yii\widgets\ActiveForm */ +/* @var $judgeGoodsCategory //判断是否能够修改 */ ?> -field($model, 'cat_id')->dropDownList(Category::modelColumn(), ['prompt' => '请选择', 'disabled' => $judgeGoodsCategory]) ?> + +
+ field($model, 'name')->textInput(['maxlength' => true]) ?> -field($model, 'brand_id')->dropDownList(Brand::modelColumn(), ['prompt' => '请选择']) ?> + field($model, 'sn')->textInput(['maxlength' => true]) ?> -field($model, 'shop_cat_id')->dropDownList(ShopCategory::modelColumn(), ['prompt' => '请选择']) ?> + field($model, 'code')->textInput(['maxlength' => true]) ?> -field($model, 'name')->textInput(['maxlength' => true]) ?> + field($model, 'market_price')->textInput() ?> -field($model, 'sn')->textInput(['maxlength' => true]) ?> + field($model, 'price')->textInput() ?> +
-field($model, 'code')->textInput(['maxlength' => true]) ?> +
+ field($model, 'cat_id')->dropDownList(Category::modelColumn(0,0), ['prompt' => '请选择', 'disabled' => $judgeGoodsCategory]) ?> -field($model, 'supplier_id')->dropDownList(Supplier::modelColumn(), ['prompt' => '请选择']) ?> + field($model, 'brand_id')->dropDownList(Brand::modelColumn(0,0), ['prompt' => '请选择']) ?> -field($model, 'weight')->textInput() ?> + field($model, 'shop_cat_id')->dropDownList(ShopCategory::modelColumn(), ['prompt' => '请选择']) ?> -field($model, 'length')->textInput() ?> + field($model, 'supplier_id')->dropDownList(Supplier::modelColumn(), ['prompt' => '请选择']) ?> -field($model, 'width')->textInput() ?> + field($model, 'is_sale')->radioList(Goods::$isSale) ?> +
-field($model, 'height')->textInput() ?> +
+ field($model, 'weight')->textInput() ?> -field($model, 'diameter')->textInput() ?> + field($model, 'length')->textInput() ?> -field($model, 'unit')->textInput(['maxlength' => true]) ?> + field($model, 'width')->textInput() ?> -field($model, 'limit_count')->textInput() ?> + field($model, 'height')->textInput() ?> -field($model, 'stock')->textInput() ?> + field($model, 'diameter')->textInput() ?> +
-field($model, 'stock_warn')->textInput() ?> +
+ field($model, 'sort_order')->textInput() ?> -field($model, 'market_price')->textInput() ?> + field($model, 'bouns_points')->textInput() ?> -field($model, 'price')->textInput() ?> + field($model, 'experience_points')->textInput() ?> -field($model, 'brief')->textInput(['maxlength' => true]) ?> + field($model, 'unit')->textInput(['maxlength' => true]) ?> -field($model, 'is_sale')->radioList(Goods::$isSale) ?> + field($model, 'limit_count')->textInput() ?> +
-field($model, 'sort_order')->textInput() ?> +
+ field($model, 'stock')->textInput()->label('库存(-1为不限制)') ?> -field($model, 'bouns_points')->textInput() ?> + field($model, 'stock_warn')->textInput(['placeholder' => '低于该值警告库存不足']) ?> +
-field($model, 'experience_points')->textInput() ?> - -
- 'btn btn-success']) ?> - 'btn btn-info']) ?> -
+field($model, 'brief')->textInput(['maxlength' => true]) ?> - - - -
- 'btn btn-success']) ?> -
diff --git a/backend/modules/goods/views/goods/picture.php b/backend/modules/goods/views/goods/picture.php index 022f640..d3ad8ea 100755 --- a/backend/modules/goods/views/goods/picture.php +++ b/backend/modules/goods/views/goods/picture.php @@ -16,7 +16,7 @@ use yii\helpers\Url; 'fillInAttribute' => 'coverImageId', 'model' => $model, 'previewConfig' => [ - 'url' => Url::to(['image-file', 'fileidstr' => $model->coverImageId, 'ruleverify' => $model->ruleVerify]), + 'url' => Url::to(['image-file', 'fileIdStr' => $model->coverImageId, 'ruleverify' => $model->ruleVerify]), ], ])->label('商品封面图') ?> @@ -30,6 +30,6 @@ use yii\helpers\Url; 'fillInAttribute' => 'detailImageId', 'model' => $model, 'previewConfig' => [ - 'url' => Url::to(['image-file', 'fileidstr' => $model->detailImageId, 'ruleverify' => $model->ruleVerify]), + 'url' => Url::to(['image-file', 'fileIdStr' => $model->detailImageId]), ], ])->label('商品详情图') ?> diff --git a/backend/modules/goods/views/goods/update.php b/backend/modules/goods/views/goods/update.php index 25ac202..e3f5ac8 100755 --- a/backend/modules/goods/views/goods/update.php +++ b/backend/modules/goods/views/goods/update.php @@ -13,6 +13,41 @@ $this->params['breadcrumbs'][] = ['label' => $model->name, 'url' => ['view', 'id $this->params['breadcrumbs'][] = '编辑 '; Yii::$app->params['bsVersion'] = '4.x'; ?> +
params['bsVersion'] = '4.x'; 'encodeLabels' => false ]); ?> +
+
- 'btn btn-success']) ?> - 'btn btn-info']) ?> + ', ['class' => 'btn-float btn btn-success']) ?> + ', ['index'], ['class' => 'btn-float btn btn-info']) ?>
diff --git a/backend/modules/goods/views/shop-category/_form.php b/backend/modules/goods/views/shop-category/_form.php index c953a9f..7621f14 100755 --- a/backend/modules/goods/views/shop-category/_form.php +++ b/backend/modules/goods/views/shop-category/_form.php @@ -21,7 +21,7 @@ use backend\modules\goods\models\ars\ShopCategory; field($model, 'keywords')->textInput(['maxlength' => true]) ?> - field($model, 'pid')->dropDownList(array_merge([0 => '一级分类'], ShopCategory::modelColumn())) ?> + field($model, 'pid')->dropDownList(ShopCategory::modelColumn($model->id)) ?> field($model, 'desc')->textInput(['maxlength' => true]) ?> @@ -37,7 +37,7 @@ use backend\modules\goods\models\ars\ShopCategory; 'fillInAttribute' => 'iconImageId', 'model' => $model, 'previewConfig' => [ - 'url' => Url::to(['image-file', 'fileidstr' => $model->iconImageId]), + 'url' => Url::to(['image-file', 'fileIdStr' => $model->iconImageId]), ], ])->label('类目图片') ?> diff --git a/backend/modules/goods/views/shop-category/_search.php b/backend/modules/goods/views/shop-category/_search.php index 1e839e6..b7d6f94 100755 --- a/backend/modules/goods/views/shop-category/_search.php +++ b/backend/modules/goods/views/shop-category/_search.php @@ -29,6 +29,19 @@ use \blobt\widgets\DateRangePicker; ]) ?> +
+ field($model, 'name', [ + "template" => "{input}{error}", + "inputOptions" => [ + "placeholder" => "类别名称", + "class" => "form-control", + ], + "errorOptions" => [ + "class" => "error-tips" + ] + ]) + ?> +
field($model, "created_at_range", [ "template" => "{input}{error}", diff --git a/backend/modules/goods/views/supplier/_search.php b/backend/modules/goods/views/supplier/_search.php index 1f00e41..e960226 100755 --- a/backend/modules/goods/views/supplier/_search.php +++ b/backend/modules/goods/views/supplier/_search.php @@ -29,6 +29,32 @@ use \blobt\widgets\DateRangePicker; ]) ?>
+
+ field($model, 'name', [ + "template" => "{input}{error}", + "inputOptions" => [ + "placeholder" => "供应商名称", + "class" => "form-control", + ], + "errorOptions" => [ + "class" => "error-tips" + ] + ]) + ?> +
+
+ field($model, 'phone', [ + "template" => "{input}{error}", + "inputOptions" => [ + "placeholder" => "手机号码", + "class" => "form-control", + ], + "errorOptions" => [ + "class" => "error-tips" + ] + ]) + ?> +
field($model, "created_at_range", [ "template" => "{input}{error}", diff --git a/backend/modules/shop/controllers/AfterSaleController.php b/backend/modules/shop/controllers/AfterSaleController.php new file mode 100644 index 0000000..36176aa --- /dev/null +++ b/backend/modules/shop/controllers/AfterSaleController.php @@ -0,0 +1,175 @@ + [ + 'class' => VerbFilter::className(), + 'actions' => [ + 'delete' => ['POST'], + ], + ], + ]; + } + + /** + * Lists all AfterSale models. + * @return mixed + */ + public function actionIndex() + { + $searchModel = new AfterSaleSearch(); + $dataProvider = $searchModel->search(Yii::$app->request->queryParams); + + return $this->render('index', [ + 'searchModel' => $searchModel, + 'dataProvider' => $dataProvider, + 'columns' => $searchModel->columns() + ]); + } + + /** + * Displays a single AfterSale model. + * @param integer $id + * @return mixed + * @throws NotFoundHttpException if the model cannot be found + */ + public function actionView($id) + { + return $this->render('view', [ + 'model' => $this->findModel($id), + ]); + } + + /** + * Creates a new AfterSale model. + * If creation is successful, the browser will be redirected to the 'view' page. + * @return mixed + */ + public function actionCreate() + { + $model = new AfterSale(); + + if ($model->load(Yii::$app->request->post()) && $model->save()) { + return $this->redirect('index'); + } + + return $this->render('create', [ + 'model' => $model, + ]); + } + + /** + * Updates an existing AfterSale model. + * If update is successful, the browser will be redirected to the 'view' page. + * @param integer $id + * @return mixed + * @throws NotFoundHttpException if the model cannot be found + */ + public function actionUpdate($id) + { + $model = $this->findModel($id); + + if ($model->load(Yii::$app->request->post()) && $model->save()) { + return $this->redirect('index'); + } + + return $this->render('update', [ + 'model' => $model, + ]); + } + + /** + * Deletes an existing AfterSale model. + * If deletion is successful, the browser will be redirected to the 'index' page. + * @param integer $id + * @return mixed + * @throws NotFoundHttpException if the model cannot be found + */ + public function actionDelete($id) + { + $this->findModel($id); + + return $this->redirect(['index']); + } + + /** + * Finds the AfterSale model based on its primary key value. + * If the model is not found, a 404 HTTP exception will be thrown. + * @param integer $id + * @return AfterSale the loaded model + * @throws NotFoundHttpException if the model cannot be found + */ + protected function findModel($id) + { + if (($model = AfterSale::findOne($id)) !== null) { + return $model; + } + + throw new NotFoundHttpException('The requested page does not exist.'); + } + /** + * @author iron + * 文件导出 + */ + public function actionExport() + { + $searchModel = new AfterSaleSearch(); + $params = Yii::$app->request->queryParams; + if ($params['page-type'] == 'all') { + $dataProvider = $searchModel->allData($params); + } else { + $dataProvider = $searchModel->search($params); + } + Excel::export([ + 'models' => $dataProvider->getModels(), + 'format' => 'Xlsx', + 'asAttachment' => true, + 'fileName' =>'After Sales'. "-" .date('Y-m-d H/i/s', time()), + 'columns' => $searchModel->columns() + ]); + } + + /** + * @param $status + * @param $id + * @return Response + * 处理售后申请 + */ + public function actionHandle($status, $id) + { + $url = Yii::$app->request->referrer; + $model = AfterSale::findOne($id); + if ($status == AfterSale::STATUS_ACCEPT) { + $model->status = AfterSale::STATUS_ACCEPT; + } else { + $model->status = AfterSale::STATUS_REJECT; + } + $model->dealt_at = time(); + $model->operator_id = Yii::$app->user->id; + if ($model->save()) { + return $this->redirect('index'); + } else { + return $this->redirect($url); + } + } +} diff --git a/backend/modules/shop/controllers/CommentController.php b/backend/modules/shop/controllers/CommentController.php new file mode 100644 index 0000000..01ab832 --- /dev/null +++ b/backend/modules/shop/controllers/CommentController.php @@ -0,0 +1,179 @@ + [ + 'class' => VerbFilter::className(), + 'actions' => [ + 'delete' => ['POST'], + ], + ], + ]; + } + + /** + * Lists all Comment models. + * @return mixed + */ + public function actionIndex() + { + $searchModel = new CommentSearch(); + $dataProvider = $searchModel->search(Yii::$app->request->queryParams); + + return $this->render('index', [ + 'searchModel' => $searchModel, + 'dataProvider' => $dataProvider, + 'columns' => $searchModel->columns() + ]); + } + + /** + * Displays a single Comment model. + * @param integer $id + * @return mixed + * @throws NotFoundHttpException if the model cannot be found + */ + public function actionView($id) + { + return $this->render('view', [ + 'model' => $this->findModel($id), + ]); + } + + /** + * Creates a new Comment model. + * If creation is successful, the browser will be redirected to the 'view' page. + * @return mixed + */ + public function actionCreate() + { + $model = new Comment(); + + if ($model->load(Yii::$app->request->post()) && $model->save()) { + return $this->redirect('index'); + } + + return $this->render('create', [ + 'model' => $model, + ]); + } + + /** + * Updates an existing Comment model. + * If update is successful, the browser will be redirected to the 'view' page. + * @param integer $id + * @return mixed + * @throws NotFoundHttpException if the model cannot be found + */ + public function actionUpdate($id) + { + $model = $this->findModel($id); + + if ($model->load(Yii::$app->request->post()) && $model->save()) { + return $this->redirect('index'); + } + + return $this->render('update', [ + 'model' => $model, + ]); + } + + /** + * Deletes an existing Comment model. + * If deletion is successful, the browser will be redirected to the 'index' page. + * @param integer $id + * @return mixed + * @throws NotFoundHttpException if the model cannot be found + */ + public function actionDelete($id) + { + $this->findModel($id)->delete(); + + return $this->redirect(['index']); + } + + /** + * Finds the Comment model based on its primary key value. + * If the model is not found, a 404 HTTP exception will be thrown. + * @param integer $id + * @return Comment the loaded model + * @throws NotFoundHttpException if the model cannot be found + */ + protected function findModel($id) + { + if (($model = Comment::findOne($id)) !== null) { + return $model; + } + + throw new NotFoundHttpException('The requested page does not exist.'); + } + /** + * @author iron + * 文件导出 + */ + public function actionExport() + { + $searchModel = new CommentSearch(); + $params = Yii::$app->request->queryParams; + if ($params['page-type'] == 'all') { + $dataProvider = $searchModel->allData($params); + } else { + $dataProvider = $searchModel->search($params); + } + Excel::export([ + 'models' => $dataProvider->getModels(), + 'format' => 'Xlsx', + 'asAttachment' => true, + 'fileName' =>'Comments'. "-" .date('Y-m-d H/i/s', time()), + 'columns' => $searchModel->columns() + ]); + } + + /** + * 显示评论 + * @param $id + * @return Response + * @throws NotFoundHttpException + */ + public function actionDisplay($id) + { + $model = $this->findModel($id); + $model->status = Comment::STATUS_DISPLAY; + $model->save(); + return $this->redirect(['index']); + } + + /** + * 隐藏评论 + * @param $id + * @return Response + * @throws NotFoundHttpException + */ + public function actionHide($id) + { + $model = $this->findModel($id); + $model->status = Comment::STATUS_HIDE; + $model->save(); + return $this->redirect(['index']); + } +} diff --git a/backend/modules/shop/controllers/ExpressTemplateController.php b/backend/modules/shop/controllers/ExpressTemplateController.php index 454bbb5..223faf5 100755 --- a/backend/modules/shop/controllers/ExpressTemplateController.php +++ b/backend/modules/shop/controllers/ExpressTemplateController.php @@ -2,12 +2,14 @@ namespace backend\modules\shop\controllers; -use backend\modules\shop\models\ars\City; -use backend\modules\shop\models\ars\Province; +use backend\modules\shop\logic\ShopManager; use backend\modules\shop\models\searchs\ExpressAreaSearch; +use http\Url; +use Throwable; use Yii; use backend\modules\shop\models\ars\ExpressTemplate; use backend\modules\shop\models\searchs\ExpressTemplateSearch; +use yii\db\StaleObjectException; use yii\web\Controller; use yii\web\NotFoundHttpException; use yii\filters\VerbFilter; @@ -15,6 +17,7 @@ use yii\web\Response; use yii\widgets\ActiveForm; use backend\modules\shop\models\ars\ExpressArea; use backend\modules\goods\models\ars\Goods; +use iron\widget\Excel; /** @@ -110,10 +113,12 @@ class ExpressTemplateController extends Controller * @param integer $id * @return mixed * @throws NotFoundHttpException if the model cannot be found + * @throws Throwable + * @throws StaleObjectException */ public function actionDelete($id) { - if (Goods::find()->where(['express_template' => $id])->count() == 0) { + if (Goods::find()->where(['express_template' => $id, 'is_delete' => Goods::IS_DELETE_NO])->count() == 0) { $expressTemplateModel = $this->findModel($id); ExpressArea::deleteAll(['express_template' => $expressTemplateModel->id]); $expressTemplateModel->delete(); @@ -152,7 +157,7 @@ class ExpressTemplateController extends Controller } else { $dataProvider = $searchModel->search($params); } - \iron\widget\Excel::export([ + Excel::export([ 'models' => $dataProvider->getModels(), 'format' => 'Xlsx', 'asAttachment' => true, @@ -162,6 +167,7 @@ class ExpressTemplateController extends Controller } /** + * @param $id * @return string * 运费区域列表 */ @@ -185,8 +191,7 @@ class ExpressTemplateController extends Controller */ public function actionExpressAreaCreate() { - $expressTemplateId = Yii::$app->request->get('expressTemplateId'); - $expressTemplateModel = ExpressTemplate::findOne($expressTemplateId); + $expressTemplateModel = ExpressTemplate::findOne(Yii::$app->request->get('expressTemplateId')); $model = new ExpressArea(); $model->basic_count = 1; $model->basic_price = '0.00'; @@ -201,48 +206,15 @@ class ExpressTemplateController extends Controller $data['status'] = 2; return $data; } - if (Yii::$app->request->post('area') == null) { - return $this->redirect(Yii::$app->request->referrer . '?status=1'); - } - $cityIds = array_keys(Yii::$app->request->post('area')); - $data['city'] = implode(',', $cityIds); - $model->load($data, ''); - $model->basic_price *= 100; - $model->extra_price *= 100; - if ($expressTemplateModel->calculation_type == ExpressTemplate::CALCULATION_TYPE_WEIGHT) { - $model->basic_count *= 1000; - $model->extra_count *= 1000; - } else { - $model->basic_count *= 1; - $model->extra_count *= 1; + $area = Yii::$app->request->post('area'); + if ($area == null) { + return $this->redirect(Yii::$app->request->referrer . '&status=1'); } - $model->save(); + ShopManager::dealAreaInExpressArea($area, $model, $expressTemplateModel); return $this->redirect('express-area-list?id='.$model->express_template); } - $data = []; - $expressAreas = ExpressArea::find()->select(['city'])->where(['express_template' => $expressTemplateModel->id])->all(); - $expressAresCityIdArr = []; - if ($expressAreas) { - foreach ($expressAreas as $expressAreaCity) { - $cityIdArr = explode(',', $expressAreaCity->city); - $expressAresCityIdArr = array_unique(array_merge($cityIdArr, $expressAresCityIdArr)); - } - } - $provinces = Province::find()->cache(0)->all(); - $j = 0; - foreach ($provinces as $k => $v) { - $cities = City::find() - ->where(['province_id' => $v->province_id]) - ->andWhere(['not in', 'city_id', $expressAresCityIdArr]) - ->all(); - if ($cities) { - $data[$j]['province'] = $v->name; - foreach ($cities as $city) { - $data[$j]['city'][] = ['id' => $city->city_id, 'name' => $city->name]; - } - $j++; - } - } + + $data = ShopManager::filterCity($model); //获取筛选的城市数据 if (empty($data)) { Yii::$app->session->setFlash('error', '已无地区选择'); return $this->redirect('express-area-list?id='.$expressTemplateModel->id); @@ -261,57 +233,27 @@ class ExpressTemplateController extends Controller public function actionExpressAreaUpdate($id) { $model = ExpressArea::findOne($id); + + //数据按比例转换 $expressTemplateModel = ExpressTemplate::findOne($model->express_template); - $model->basic_price /= 100; - $model->extra_price /= 100; + $model->basic_price /= ShopManager::proportionalConversion(ShopManager::UNIT_TYPE_MONEY); + $model->extra_price /= ShopManager::proportionalConversion(ShopManager::UNIT_TYPE_MONEY); if ($expressTemplateModel->calculation_type == ExpressTemplate::CALCULATION_TYPE_WEIGHT) { - $model->basic_count /= 10; - $model->extra_count /= 10; + $model->basic_count /= ShopManager::proportionalConversion(ShopManager::UNIT_TYPE_WEIGHT); + $model->extra_count /= ShopManager::proportionalConversion(ShopManager::UNIT_TYPE_WEIGHT); } + $data = Yii::$app->request->post('ExpressArea'); if ($data) { - if (Yii::$app->request->post('area') == null) { + $area = Yii::$app->request->post('area'); + if ($area == null) { return $this->redirect(Yii::$app->request->referrer . '&status=1'); } - $cityIds = array_keys(Yii::$app->request->post('area')); - $data['city'] = implode(',', $cityIds); - $model->load($data, ''); - $model->basic_price *= 100; - $model->extra_price *= 100; - if ($expressTemplateModel->calculation_type == ExpressTemplate::CALCULATION_TYPE_WEIGHT) { - $model->basic_count *= 1000; - $model->extra_count *= 1000; - } else { - $model->basic_count *= 1; - $model->extra_count *= 1; - } - $model->save(); + ShopManager::dealAreaInExpressArea($area, $model, $expressTemplateModel); return $this->redirect('express-area-list?id='.$model->express_template); } - $data = []; - $expressAreas = ExpressArea::find()->select(['city'])->where(['express_template' => $expressTemplateModel->id])->andWhere(['!=', 'id', $id])->all(); - $expressAresCityIdArr = []; - if ($expressAreas) { - foreach ($expressAreas as $expressAreaCity) { - $cityIdArr = explode(',', $expressAreaCity->city); - $expressAresCityIdArr = array_unique(array_merge($cityIdArr, $expressAresCityIdArr)); - } - } - $provinces = Province::find()->cache(0)->all(); - $j = 0; - foreach ($provinces as $k => $v) { - $cities = City::find() - ->where(['province_id' => $v->province_id]) - ->andWhere(['not in', 'city_id', $expressAresCityIdArr]) - ->all(); - if ($cities) { - $data[$j]['province'] = $v->name; - foreach ($cities as $city) { - $data[$j]['city'][] = ['id' => $city->city_id, 'name' => $city->name]; - } - $j++; - } - } + + $data = ShopManager::filterCity($model); //获取筛选的城市数据 return $this->render('express_area_update', [ 'model' => $model, 'data' => $data, 'cities' => explode(',', $model->city), 'expressTemplateModel' => $expressTemplateModel @@ -333,6 +275,12 @@ class ExpressTemplateController extends Controller ]); } + /** + * @param $id + * @return Response + * @throws StaleObjectException + * @throws Throwable + */ public function actionExpressAreaDelete($id) { $expressAreaModel = ExpressArea::findOne($id); diff --git a/backend/modules/shop/controllers/OrderController.php b/backend/modules/shop/controllers/OrderController.php index 70e2fca..a3038a2 100755 --- a/backend/modules/shop/controllers/OrderController.php +++ b/backend/modules/shop/controllers/OrderController.php @@ -7,12 +7,15 @@ use backend\models\shop\logic\payment\WxPaymentManager; use backend\modules\shop\logic\delivery\DeliveryManager; use backend\modules\shop\models\ars\Delivery; use backend\modules\shop\models\ars\RefundLog; +use Throwable; use Yii; -use backend\modules\shop\models\ars\Order; -use backend\modules\shop\models\searchs\OrderSearch; +use backend\modules\shop\models\ars\Order; +use backend\modules\shop\models\searchs\OrderSearch; +use yii\db\StaleObjectException; use yii\web\Controller; use yii\web\NotFoundHttpException; use yii\filters\VerbFilter; +use iron\widget\Excel; /** * OrderController implements the CRUD actions for Order model. @@ -102,11 +105,13 @@ class OrderController extends Controller } /** - * @param $id - * @return \yii\web\Response - * @throws NotFoundHttpException - * @throws \Throwable - * @throws \yii\db\StaleObjectException + * Deletes an existing Order model. + * If deletion is successful, the browser will be redirected to the 'index' page. + * @param integer $id + * @return mixed + * @throws NotFoundHttpException if the model cannot be found + * @throws Throwable + * @throws StaleObjectException */ public function actionDelete($id) { @@ -143,7 +148,7 @@ class OrderController extends Controller } else { $dataProvider = $searchModel->search($params); } - \iron\widget\Excel::export([ + Excel::export([ 'models' => $dataProvider->getModels(), 'format' => 'Xlsx', 'asAttachment' => true, diff --git a/backend/modules/shop/controllers/TakingSiteController.php b/backend/modules/shop/controllers/TakingSiteController.php index b5daec9..9bfdd89 100755 --- a/backend/modules/shop/controllers/TakingSiteController.php +++ b/backend/modules/shop/controllers/TakingSiteController.php @@ -4,12 +4,15 @@ namespace backend\modules\shop\controllers; use backend\modules\shop\models\ars\Area; use backend\modules\shop\models\ars\City; +use Throwable; use Yii; use backend\modules\shop\models\ars\TakingSite; use backend\modules\shop\models\searchs\TakingSiteSearch; +use yii\db\StaleObjectException; use yii\web\Controller; use yii\web\NotFoundHttpException; use yii\filters\VerbFilter; +use iron\widget\Excel; /** * TakingSiteController implements the CRUD actions for TakingSite model. @@ -104,6 +107,8 @@ class TakingSiteController extends Controller * @param integer $id * @return mixed * @throws NotFoundHttpException if the model cannot be found + * @throws Throwable + * @throws StaleObjectException */ public function actionDelete($id) { @@ -140,7 +145,7 @@ class TakingSiteController extends Controller } else { $dataProvider = $searchModel->search($params); } - \iron\widget\Excel::export([ + Excel::export([ 'models' => $dataProvider->getModels(), 'format' => 'Xlsx', 'asAttachment' => true, diff --git a/backend/modules/shop/logic/ShopManager.php b/backend/modules/shop/logic/ShopManager.php new file mode 100644 index 0000000..bde5b00 --- /dev/null +++ b/backend/modules/shop/logic/ShopManager.php @@ -0,0 +1,139 @@ +basic_price *= self::proportionalConversion(self::UNIT_TYPE_MONEY); + $expressAreaModel->extra_price *= self::proportionalConversion(self::UNIT_TYPE_MONEY); + if ($expressTemplateModel->calculation_type == ExpressTemplate::CALCULATION_TYPE_WEIGHT) { + $expressAreaModel->basic_count *= self::proportionalConversion(self::UNIT_TYPE_WEIGHT); + $expressAreaModel->extra_count *= self::proportionalConversion(self::UNIT_TYPE_WEIGHT); + } else { + $expressAreaModel->basic_count *= self::proportionalConversion(self::UNIT_TYPE_ITEM); + $expressAreaModel->extra_count *= self::proportionalConversion(self::UNIT_TYPE_ITEM); + } + } + + /** + * @param ExpressArea|$expressAreaModel + * @return array + * 除去已被选取的城市,筛选剩下的城市数据 + */ + public static function filterCity($expressAreaModel) + { + $query = ExpressArea::find() + ->select(['city']) + ->where(['express_template' => $expressAreaModel->express_template]); + if ($expressAreaModel->id) { //修改操作时,除去自身的城市 + $query = $query->andWhere(['!=', 'id', $expressAreaModel->id]); + } + $allDate = $query->all(); + + $expressAresCityIdArr = self::filterSelectedCtiyIdArr($allDate); + + $leftoverCityArr = self::filterLeftoverCityArr($expressAresCityIdArr); + return $leftoverCityArr; + } + + /** + * @param $expressAreas + * @return array 已选的城市id数组 + * 获取已选的城市id数组 + */ + private static function filterSelectedCtiyIdArr($expressAreas) + { + $expressAresCityIdArr = []; + if ($expressAreas) { + foreach ($expressAreas as $expressAreaCity) { + $cityIdArr = explode(',', $expressAreaCity->city); + $expressAresCityIdArr = array_unique(array_merge($cityIdArr, $expressAresCityIdArr)); + } + } + return $expressAresCityIdArr; + } + + /** + * @param $expressAresCityIdArr + * @return array 未选的城市id数组 + * 筛选剩下的城市 + */ + private static function filterLeftoverCityArr($expressAresCityIdArr) + { + $data = []; + $provinces = Province::find()->cache(0)->all(); + foreach ($provinces as $key => $province) { + $cities = City::find() + ->where(['province_id' => $province->province_id]) + ->andWhere(['not in', 'city_id', $expressAresCityIdArr]) + ->all(); + + if ($cities) { + $data[$key]['province'] = $province->name; + foreach ($cities as $city) { + $data[$key]['city'][] = ['id' => $city->city_id, 'name' => $city->name]; + } + } + } + return array_values($data); + } + + /** + * 处理所选区域数据 + * @param $area + * @param ExpressArea|$expressAreaModel + * @param ExpressTemplate|$expressTemplateModel + * @return bool + */ + public static function dealAreaInExpressArea($area, $expressAreaModel, $expressTemplateModel) + { + $cityIds = array_keys($area); + $data['city'] = implode(',', $cityIds); + $expressAreaModel->load($data, ''); + self::expressAreaScaleDate($expressAreaModel, $expressTemplateModel); //按比例转换数据 + $expressAreaModel->save(); + return true; + } +} \ No newline at end of file diff --git a/backend/modules/shop/migrations/m191209_080245_update_table_after_sale.php b/backend/modules/shop/migrations/m191209_080245_update_table_after_sale.php new file mode 100644 index 0000000..ea81c27 --- /dev/null +++ b/backend/modules/shop/migrations/m191209_080245_update_table_after_sale.php @@ -0,0 +1,68 @@ +dropTable('ats_after_sale'); + $tableOptions = 'CHARACTER SET utf8 COLLATE utf8_general_ci ENGINE=InnoDB COMMENT="售后表"'; + $this->createTable('ats_after_sale', [ + 'id' => $this->primaryKey(), + 'wx_refund_id'=>$this->string(64)->defaultValue('')->notNull()->comment('微信退款单号'), + 'after_sale_sn'=>$this->string(64)->defaultValue('')->notNull()->comment('售后单号'), + 'user_id'=>$this->integer(11)->defaultValue('0')->notNull()->comment('用户id'), + 'order_goods_id'=>$this->integer(11)->defaultValue('0')->notNull()->comment('订单商品id'), + 'amount'=>$this->integer(20)->defaultValue("0")->notNull()->comment('退货时实际退的金额'), + 'count'=>$this->integer(11)->defaultValue("0")->notNull()->comment('退换货的商品数量'), + 'apply_at'=>$this->integer(11)->defaultValue(null)->comment('申请时间'), + 'dealt_at'=>$this->integer(11)->defaultValue(null)->comment('处理时间'), + 'finish_at'=>$this->integer(11)->defaultValue(null)->comment('完成时间'), + 'operator_id'=>$this->integer(11)->defaultValue("0")->notNull()->comment('操作者'), + 'refund_type'=>$this->tinyInteger(1)->defaultValue("1")->comment('退款类型:1:全额退款;2:部分退款'), + 'description'=>$this->text()->comment('描述'), + 'image' => $this->text()->comment('图片'), + 'status' => $this->tinyInteger(2)->defaultValue('0')->notNull()->comment('处理状态:0:未处理;1:已同意,待买家确认;2:用户已确认;3:已拒绝;4:退款成功;5:已取消;'), + 'reason'=>$this->smallInteger(2)->defaultValue("0")->comment('退换货理由'), + 'remarks'=>$this->text()->comment('店家备注'), + 'take_shipping_sn'=>$this->string(50)->defaultValue(null)->comment('用户发货物流单号'), + 'refund_mode' => $this->tinyInteger(2)->defaultValue('1')->notNull()->comment('退款方式:1:仅退款;2:退货退款;') + ],$tableOptions); + } + + /** + * {@inheritdoc} + */ + public function down() + { + $this->dropTable('ats_after_sale'); + $tableOptions = 'CHARACTER SET utf8 COLLATE utf8_general_ci ENGINE=InnoDB COMMENT="售后表"'; + $this->createTable('ats_after_sale', [ + 'id' => $this->primaryKey(), + 'operator_id'=>$this->integer(11)->notNull()->comment('操作者'), + 'user_id'=>$this->integer(11)->notNull()->comment('用户id'), + 'wx_refund_id'=>$this->string(64)->defaultValue(null)->comment('微信退款单号'), + 'after_sale_sn'=>$this->string(64)->defaultValue(null)->comment('售后单号'), + 'order_goods_id'=>$this->integer(11)->defaultValue(null)->comment('订单商品id'), + 'count'=>$this->integer(11)->defaultValue(null)->comment('退换货的商品数量'), + 'amount'=>$this->integer(20)->notNull()->comment('退货时实际退的金额'), + 'type'=>$this->tinyInteger(1)->defaultValue(0)->comment('类型'), + 'reason'=>$this->smallInteger(2)->defaultValue(0)->comment('退换货理由'), + 'description'=>$this->text()->comment('描述'), + 'take_shipping_sn'=>$this->string(50)->defaultValue(null)->comment('用户发货物流单号'), + 'send_shipping_sn'=>$this->string(50)->defaultValue(null)->comment('换货商家发货的物流单号'), + 'remarks'=>$this->text()->comment('店家备注'), + 'applyed_at'=>$this->integer(11)->defaultValue(null)->comment('申请时间'), + 'dealed_at'=>$this->integer(11)->defaultValue(null)->comment('处理时间'), + 'finished_at'=>$this->integer(11)->defaultValue(null)->comment('完成时间'), + ],$tableOptions); + return true; + } +} diff --git a/backend/modules/shop/migrations/m191217_013625_add_column_sku_value_in_table_ats_cart.php b/backend/modules/shop/migrations/m191217_013625_add_column_sku_value_in_table_ats_cart.php new file mode 100644 index 0000000..5a88fff --- /dev/null +++ b/backend/modules/shop/migrations/m191217_013625_add_column_sku_value_in_table_ats_cart.php @@ -0,0 +1,20 @@ +addColumn('ats_cart', 'sku_value', $this->string(120)->defaultValue('')->comment('商品sku')); + } + + public function down() + { + $this->dropColumn('ats_cart', 'sku_value'); + return true; + } +} diff --git a/backend/modules/shop/migrations/m191217_021455_update_column_star_in_table_ats_comment.php b/backend/modules/shop/migrations/m191217_021455_update_column_star_in_table_ats_comment.php new file mode 100644 index 0000000..921cc04 --- /dev/null +++ b/backend/modules/shop/migrations/m191217_021455_update_column_star_in_table_ats_comment.php @@ -0,0 +1,20 @@ +dropColumn('ats_comment', 'star'); + $this->addColumn('ats_comment', 'star', $this->integer(11)->notNull()->defaultValue("0")->comment('星级')); + } + + public function down() + { + return true; + } +} diff --git a/backend/modules/shop/models/ars/AfterSale.php b/backend/modules/shop/models/ars/AfterSale.php index 6a46d0a..5b41f47 100755 --- a/backend/modules/shop/models/ars/AfterSale.php +++ b/backend/modules/shop/models/ars/AfterSale.php @@ -1,33 +1,69 @@ '全额退款', + self::REFUND_TYPE_PART => '部分退款' + ]; + public static $status = [ + self::STATUS_UNTREATED => '未处理', + self::STATUS_ACCEPT => '已同意,待买家确认', + self::STATUS_CONFIRM => '用户已确认', + self::STATUS_REJECT => '已拒绝', + self::STATUS_FINISH => '退款成功', + self::STATUS_CANCEL => '已取消' + ]; + public static $refundMode = [ + self::REFUND_MODE_MONEY => '仅退款', + self::REFUND_MODE_MONEY_GOODS => '退货退款' + ]; + public static $afterSaleReason = [ + 1 => '7天无理由退货', + 2 => '质量问题', + 3 => '买错东西', + 4 => '商品不满意', + ]; /** * {@inheritdoc} */ @@ -42,11 +78,10 @@ class AfterSale extends \yii\db\ActiveRecord public function rules() { return [ - [['operator_id', 'user_id', 'amount'], 'required'], - [['operator_id', 'user_id', 'order_goods_id', 'count', 'amount', 'type', 'reason', 'applyed_at', 'dealed_at', 'finished_at'], 'integer'], - [['description', 'remarks'], 'string'], + [['user_id', 'order_goods_id', 'amount', 'count', 'apply_at', 'dealt_at', 'finish_at', 'operator_id', 'refund_type', 'status', 'reason', 'refund_mode'], 'integer'], + [['description', 'image', 'remarks'], 'string'], [['wx_refund_id', 'after_sale_sn'], 'string', 'max' => 64], - [['take_shipping_sn', 'send_shipping_sn'], 'string', 'max' => 50], + [['take_shipping_sn'], 'string', 'max' => 50], ]; } @@ -57,24 +92,31 @@ class AfterSale extends \yii\db\ActiveRecord { return [ 'id' => 'id', - 'operator_id' => '操作者', - 'user_id' => '用户id', 'wx_refund_id' => '微信退款单号', 'after_sale_sn' => '售后单号', - 'order_goods_id' => '订单商品id', - 'count' => '退换货的商品数量', + 'user_id' => '用户id', + 'order_goods_id' => '订单商品', 'amount' => '退货时实际退的金额', - 'type' => '类型', - 'reason' => '退换货理由', + 'count' => '退货的商品数量', + 'apply_at' => '申请时间', + 'dealt_at' => '处理时间', + 'finish_at' => '完成时间', + 'operator_id' => '操作者', + 'refund_type' => '退款类型', 'description' => '描述', - 'take_shipping_sn' => '用户发货物流单号', - 'send_shipping_sn' => '换货商家发货的物流单号', + 'image' => '图片', + 'status' => '处理状态', + 'reason' => '退货理由', 'remarks' => '店家备注', - 'applyed_at' => '申请时间', - 'dealed_at' => '处理时间', - 'finished_at' => '完成时间', + 'take_shipping_sn' => '用户发货物流单号', + 'refund_mode' => '退款方式', ]; } + + public function getGoods() + { + return $this->hasOne(OrderGoods::className(), ['id' => 'order_goods_id']); + } } diff --git a/backend/modules/shop/models/ars/Cart.php b/backend/modules/shop/models/ars/Cart.php index 5b47553..2a2901b 100755 --- a/backend/modules/shop/models/ars/Cart.php +++ b/backend/modules/shop/models/ars/Cart.php @@ -18,6 +18,7 @@ use yii\behaviors\TimestampBehavior; * @property int $goods_count 商品数量 * @property int $created_at 创建时间 * @property int $updated_at 更新时间 + * @property string $sku_value 商品sku */ class Cart extends \yii\db\ActiveRecord { @@ -37,7 +38,7 @@ class Cart extends \yii\db\ActiveRecord return [ [['user_id', 'goods_id', 'goods_name', 'sku_id'], 'required'], [['user_id', 'goods_id', 'goods_img', 'goods_price', 'sku_id', 'goods_count'], 'integer'], - [['goods_name'], 'string', 'max' => 120], + [['goods_name', 'sku_value'], 'string', 'max' => 120], ]; } @@ -57,6 +58,7 @@ class Cart extends \yii\db\ActiveRecord 'goods_count' => '商品数量', 'created_at' => '创建时间', 'updated_at' => '更新时间', + 'sku_value' => '商品sku', ]; } diff --git a/backend/modules/shop/models/ars/Comment.php b/backend/modules/shop/models/ars/Comment.php index 113ee20..ffa1245 100644 --- a/backend/modules/shop/models/ars/Comment.php +++ b/backend/modules/shop/models/ars/Comment.php @@ -4,6 +4,7 @@ namespace backend\modules\shop\models\ars; use Yii; use yii\behaviors\TimestampBehavior; +use backend\modules\shop\models\ars\OrderGoods; /** * This is the model class for table "ats_comment". @@ -21,6 +22,13 @@ use yii\behaviors\TimestampBehavior; */ class Comment extends \yii\db\ActiveRecord { + //状态 + const STATUS_DISPLAY = 1; //显示 + const STATUS_HIDE = 0; //隐藏 + public static $commentStatus = [ + self::STATUS_DISPLAY => '显示', + self::STATUS_HIDE => '隐藏' + ]; /** * {@inheritdoc} */ @@ -81,4 +89,9 @@ class Comment extends \yii\db\ActiveRecord ], ]; } + + public function getOrderGoods() + { + return $this->hasOne(OrderGoods::class,['id' => 'order_goods_id']); + } } diff --git a/backend/modules/shop/models/ars/OrderGoods.php b/backend/modules/shop/models/ars/OrderGoods.php index 1c77ee3..a689ca4 100755 --- a/backend/modules/shop/models/ars/OrderGoods.php +++ b/backend/modules/shop/models/ars/OrderGoods.php @@ -4,6 +4,7 @@ namespace backend\modules\shop\models\ars; use Yii; use yii\behaviors\TimestampBehavior; +use backend\modules\shop\models\ars\Order; /** * This is the model class for table "ats_order_goods". @@ -89,4 +90,9 @@ class OrderGoods extends \yii\db\ActiveRecord ], ]; } + + public function getOrder() + { + return $this->hasOne(Order::className(), ['id' => 'order_id']); + } } diff --git a/backend/modules/shop/models/searchs/AfterSaleSearch.php b/backend/modules/shop/models/searchs/AfterSaleSearch.php new file mode 100644 index 0000000..74ecb78 --- /dev/null +++ b/backend/modules/shop/models/searchs/AfterSaleSearch.php @@ -0,0 +1,188 @@ + 'blobt\grid\CheckboxColumn', + 'width' => '2%', + 'align' => 'center' + ], + 'id', + 'after_sale_sn', + [ + 'attribute' => 'order_goods_id', + 'value' => function ($model) { + return $model->goods->goods_name; + } + ], + [ + 'attribute' => 'order_pay_amount', + 'value' => function ($model) { + return sprintf("%1\$.2f", $model->goods->order->payment_amount); + }, + 'label' => '订单支付金额' + ], + [ + 'attribute' => 'amount', + 'value' => function ($model) { + return sprintf("%1\$.2f", $model->amount); + } + ], + [ + 'attribute' => 'status', + 'value' => function ($model) { + return AfterSale::$status[$model->status]; + } + ], + 'apply_at:datetime', + 'dealt_at:datetime', + 'finish_at:datetime', + [ + 'class' => 'iron\grid\ActionColumn', + 'align' => 'center', + 'config' => [ + [ + 'name' => 'view', + 'icon' => 'list', + 'title' => '详情', + ] + ] + ], + ]; + } + /** + * @param $params + * @return ActiveDataProvider + * 不分页的所有数据 + */ + public function allData($params) + { + $query = AfterSale::find(); + $dataProvider = new ActiveDataProvider([ + 'query' => $query, + 'pagination' => false, + 'sort' => false + ]); + $this->load($params); + return $this->filter($query, $dataProvider); + } + + /** + * Creates data provider instance with search query applied + * + * @param array $params + * + * @return ActiveDataProvider + */ + public function search($params) + { + $query = AfterSale::find(); + + // add conditions that should always apply here + + $dataProvider = new ActiveDataProvider([ + 'query' => $query, + 'pagination' => [ + 'pageSizeLimit' => [1, 200] + ], + 'sort' => [ + 'defaultOrder' => [ + 'id' => SORT_DESC, + ] + ], + ]); + + $this->load($params); + return $this->filter($query, $dataProvider); + } + /** + * @param $query + * @param $dataProvider + * @return ActiveDataProvider + * 条件筛选 + */ + private function filter($query, $dataProvider){ + if (!$this->validate()) { + // uncomment the following line if you do not want to return any records when validation fails + // $query->where('0=1'); + return $dataProvider; + } + + // grid filtering conditions + $query->andFilterWhere([ + 'id' => $this->id, + 'user_id' => $this->user_id, + 'order_goods_id' => $this->order_goods_id, + 'amount' => $this->amount, + 'count' => $this->count, + 'apply_at' => $this->apply_at, + 'dealt_at' => $this->dealt_at, + 'finish_at' => $this->finish_at, + 'operator_id' => $this->operator_id, + 'refund_type' => $this->refund_type, + 'status' => $this->status, + 'reason' => $this->reason, + 'refund_mode' => $this->refund_mode, + ]); + + $query->andFilterWhere(['like', 'wx_refund_id', $this->wx_refund_id]) + ->andFilterWhere(['like', 'after_sale_sn', $this->after_sale_sn]) + ->andFilterWhere(['like', 'description', $this->description]) + ->andFilterWhere(['like', 'image', $this->image]) + ->andFilterWhere(['like', 'remarks', $this->remarks]) + ->andFilterWhere(['like', 'take_shipping_sn', $this->take_shipping_sn]); + if ($this->created_at_range) { + $arr = explode(' ~ ', $this->created_at_range); + $start = strtotime($arr[0]); + $end = strtotime($arr[1]) + 3600 * 24; + $query->andFilterWhere(['between', 'created_at', $start, $end]); + } + return $dataProvider; + } +} diff --git a/backend/modules/shop/models/searchs/CommentSearch.php b/backend/modules/shop/models/searchs/CommentSearch.php new file mode 100644 index 0000000..ac8e189 --- /dev/null +++ b/backend/modules/shop/models/searchs/CommentSearch.php @@ -0,0 +1,186 @@ + 'order_goods_id', + 'value' => function ($model) { + return $model->orderGoods->goods_name; + }, + 'label' => '商品名称' + ], + 'star', + 'content', + [ + 'attribute' => 'status', + 'value' => function ($model) { + return Comment::$commentStatus[$model->status]; + }, + 'label' => '状态' + ], + 'created_at:datetime', + [ + 'class' => 'iron\grid\ActionColumn', + 'align' => 'center', + 'config' => [ + [ + 'name' => 'view', + 'icon' => 'list', + 'title' => '详情', + ], + [ + 'name' => 'display', + 'icon' => 'eye', + 'title' => '显示', + 'hide' => [ + 'attributes' => [ + 'status' + ], + 'values' => [ + Comment::STATUS_DISPLAY + ] + ] + ], + [ + 'name' => 'hide', + 'icon' => 'eye', + 'title' => '隐藏', + 'hide' => [ + 'attributes' => [ + 'status' + ], + 'values' => [ + Comment::STATUS_HIDE + ] + ] + ], + ] + ], + ]; + } + /** + * @param $params + * @return ActiveDataProvider + * 不分页的所有数据 + */ + public function allData($params) + { + $query = Comment::find(); + $dataProvider = new ActiveDataProvider([ + 'query' => $query, + 'pagination' => false, + 'sort' => false + ]); + $this->load($params); + return $this->filter($query, $dataProvider); + } + + /** + * Creates data provider instance with search query applied + * + * @param array $params + * + * @return ActiveDataProvider + */ + public function search($params) + { + $query = Comment::find(); + + // add conditions that should always apply here + + $dataProvider = new ActiveDataProvider([ + 'query' => $query, + 'pagination' => [ + 'pageSizeLimit' => [1, 200] + ], + 'sort' => [ + 'defaultOrder' => [ + 'id' => SORT_DESC, + ] + ], + ]); + + $this->load($params); + return $this->filter($query, $dataProvider); + } + /** + * @param $query + * @param $dataProvider + * @return ActiveDataProvider + * 条件筛选 + */ + private function filter($query, $dataProvider){ + if (!$this->validate()) { + // uncomment the following line if you do not want to return any records when validation fails + // $query->where('0=1'); + return $dataProvider; + } + + // grid filtering conditions + $query->andFilterWhere([ + 'id' => $this->id, + 'user_id' => $this->user_id, + 'order_goods_id' => $this->order_goods_id, + 'star' => $this->star, + 'status' => $this->status, + 'updated_at' => $this->updated_at, + 'created_at' => $this->created_at, + ]); + + $query->andFilterWhere(['like', 'content', $this->content]); + if ($this->created_at_range) { + $arr = explode(' ~ ', $this->created_at_range); + $start = strtotime($arr[0]); + $end = strtotime($arr[1]) + 3600 * 24; + $query->andFilterWhere(['between', 'created_at', $start, $end]); + } + return $dataProvider; + } +} diff --git a/backend/modules/shop/views/after-sale/_form.php b/backend/modules/shop/views/after-sale/_form.php new file mode 100644 index 0000000..b29c73a --- /dev/null +++ b/backend/modules/shop/views/after-sale/_form.php @@ -0,0 +1,58 @@ + + +
+ + + + field($model, 'wx_refund_id')->textInput(['maxlength' => true]) ?> + + field($model, 'after_sale_sn')->textInput(['maxlength' => true]) ?> + + field($model, 'user_id')->textInput() ?> + + field($model, 'order_goods_id')->textInput() ?> + + field($model, 'amount')->textInput() ?> + + field($model, 'count')->textInput() ?> + + field($model, 'apply_at')->textInput() ?> + + field($model, 'dealt_at')->textInput() ?> + + field($model, 'finish_at')->textInput() ?> + + field($model, 'operator_id')->textInput() ?> + + field($model, 'refund_type')->textInput() ?> + + field($model, 'description')->textarea(['rows' => 6]) ?> + + field($model, 'image')->textarea(['rows' => 6]) ?> + + field($model, 'status')->textInput() ?> + + field($model, 'reason')->textInput() ?> + + field($model, 'remarks')->textarea(['rows' => 6]) ?> + + field($model, 'take_shipping_sn')->textInput(['maxlength' => true]) ?> + + field($model, 'refund_mode')->textInput() ?> + +
+ 'btn btn-success']) ?> + 'btn btn-info']) ?> +
+ + + +
diff --git a/backend/modules/shop/views/after-sale/_search.php b/backend/modules/shop/views/after-sale/_search.php new file mode 100644 index 0000000..f84ef70 --- /dev/null +++ b/backend/modules/shop/views/after-sale/_search.php @@ -0,0 +1,62 @@ + + + ['index'], + 'method' => 'get', + 'validateOnType' => true, + ]); +?> +
+
+ field($model, 'id', [ + "template" => "{input}{error}", + "inputOptions" => [ + "placeholder" => "检索ID", + "class" => "form-control", + ], + "errorOptions" => [ + "class" => "error-tips" + ] + ]) + ?> +
+
+ field($model, 'after_sale_sn', [ + "template" => "{input}{error}", + "inputOptions" => [ + "placeholder" => "售后单号", + "class" => "form-control", + ], + "errorOptions" => [ + "class" => "error-tips" + ] + ]) + ?> +
+
+ field($model, "created_at_range", [ + "template" => "{input}{error}", + "inputOptions" => [ + "placeholder" => "创建时间", + ], + "errorOptions" => [ + "class" => "error-tips" + ] + ])->widget(DateRangePicker::className()); + ?> +
+
+ ', ['class' => 'btn btn-default']) ?> + ', ['class' => 'btn btn-default']) ?> +
+
+ \ No newline at end of file diff --git a/backend/modules/shop/views/after-sale/create.php b/backend/modules/shop/views/after-sale/create.php new file mode 100644 index 0000000..ddd13eb --- /dev/null +++ b/backend/modules/shop/views/after-sale/create.php @@ -0,0 +1,18 @@ +title = '创建 After Sale'; +$this->params['breadcrumbs'][] = ['label' => 'After Sales', 'url' => ['index']]; +$this->params['breadcrumbs'][] = $this->title; +?> +
+ + render('_form', [ + 'model' => $model, + ]) ?> + +
diff --git a/backend/modules/shop/views/after-sale/index.php b/backend/modules/shop/views/after-sale/index.php new file mode 100644 index 0000000..2916aed --- /dev/null +++ b/backend/modules/shop/views/after-sale/index.php @@ -0,0 +1,31 @@ +title = '售后管理'; +$this->params['breadcrumbs'][] = $this->title; +?> +
+
+ $dataProvider, + 'filter' => $this->render("_search", ['model' => $searchModel]), + 'batch' => [ + [ + "label" => "删除", + "url" => "after-sale/deletes" + ], + ], + 'columns' => $columns, + 'batchTemplate' => '', + 'export' => '', + 'create' => '' + ]); + ?> +
+
\ No newline at end of file diff --git a/backend/modules/shop/views/after-sale/update.php b/backend/modules/shop/views/after-sale/update.php new file mode 100644 index 0000000..a4268c5 --- /dev/null +++ b/backend/modules/shop/views/after-sale/update.php @@ -0,0 +1,19 @@ +title = '编辑 After Sale: ' . $model->id; +$this->params['breadcrumbs'][] = ['label' => 'After Sales', 'url' => ['index']]; +$this->params['breadcrumbs'][] = ['label' => $model->id, 'url' => ['view', 'id' => $model->id]]; +$this->params['breadcrumbs'][] = 'Update '; +?> +
+ + render('_form', [ + 'model' => $model, + ]) ?> + +
diff --git a/backend/modules/shop/views/after-sale/view.php b/backend/modules/shop/views/after-sale/view.php new file mode 100644 index 0000000..a9f2f1d --- /dev/null +++ b/backend/modules/shop/views/after-sale/view.php @@ -0,0 +1,108 @@ +title = $model->id; +$this->params['breadcrumbs'][] = ['label' => 'After Sales', 'url' => ['index']]; +$this->params['breadcrumbs'][] = $this->title; +\yii\web\YiiAsset::register($this); +?> +
+ +

+ 'btn btn-default']) ?> + status == AfterSale::STATUS_UNTREATED) { + echo Html::a('同意', + [ + 'handle', + 'status' => AfterSale::STATUS_ACCEPT, + 'id' => $model->id + ], + [ + 'class' => 'btn btn-default', + 'data' => [ + 'confirm' => '同意用户的退货申请?', + 'method' => 'post', + ] + ] + ); + echo Html::a('拒绝', + [ + 'handle', + 'status' => AfterSale::STATUS_REJECT, + 'id' => $model->id + ], + [ + 'class' => 'btn btn-default', + 'data' => [ + 'confirm' => '拒绝用户的退货申请?', + 'method' => 'post', + ], + ] + ); + } + ?> +

+ + $model, + 'attributes' => [ + 'id', + 'wx_refund_id', + 'after_sale_sn', + 'user_id', + [ + 'attribute' => 'order_goods_id', + 'value' => function ($model) { + return $model->goods->goods_name; + } + ], + [ + 'attribute' => 'amount', + 'value' => function ($model) { + return sprintf("%1\$.2f", $model->amount); + } + ], + 'count', + 'apply_at:datetime', + 'dealt_at:datetime', + 'finish_at:datetime', + 'operator_id', + [ + 'attribute' => 'refund_type', + 'value' => function ($model) { + return AfterSale::$refundType[$model->refund_type]; + } + ], + 'description:ntext', + 'image:ntext', + [ + 'attribute' => 'status', + 'value' => function ($model) { + return AfterSale::$status[$model->status]; + } + ], + [ + 'attribute' => 'reason', + 'value' => function ($model) { + return $model->reason ? AfterSale::$afterSaleReason[$model->reason] : '未设置'; + } + ], + 'remarks:ntext', + 'take_shipping_sn', + [ + 'attribute' => 'refund_mode', + 'value' => function ($model) { + return AfterSale::$refundMode[$model->refund_mode]; + } + ], + ], + ]) ?> + +
diff --git a/backend/modules/shop/views/comment/_form.php b/backend/modules/shop/views/comment/_form.php new file mode 100644 index 0000000..78898f8 --- /dev/null +++ b/backend/modules/shop/views/comment/_form.php @@ -0,0 +1,32 @@ + + +
+ + + + field($model, 'user_id')->textInput() ?> + + field($model, 'order_goods_id')->textInput() ?> + + field($model, 'star')->textInput() ?> + + field($model, 'content')->textarea(['rows' => 6]) ?> + + field($model, 'status')->textInput() ?> + +
+ 'btn btn-success']) ?> + 'btn btn-info']) ?> +
+ + + +
diff --git a/backend/modules/shop/views/comment/_search.php b/backend/modules/shop/views/comment/_search.php new file mode 100644 index 0000000..54cde7b --- /dev/null +++ b/backend/modules/shop/views/comment/_search.php @@ -0,0 +1,49 @@ + + + ['index'], + 'method' => 'get', + 'validateOnType' => true, + ]); +?> +
+
+ field($model, 'id', [ + "template" => "{input}{error}", + "inputOptions" => [ + "placeholder" => "检索ID", + "class" => "form-control", + ], + "errorOptions" => [ + "class" => "error-tips" + ] + ]) + ?> +
+
+ field($model, "created_at_range", [ + "template" => "{input}{error}", + "inputOptions" => [ + "placeholder" => "创建时间", + ], + "errorOptions" => [ + "class" => "error-tips" + ] + ])->widget(DateRangePicker::className()); + ?> +
+
+ ', ['class' => 'btn btn-default']) ?> + ', ['class' => 'btn btn-default']) ?> +
+
+ \ No newline at end of file diff --git a/backend/modules/shop/views/comment/create.php b/backend/modules/shop/views/comment/create.php new file mode 100644 index 0000000..2c4decc --- /dev/null +++ b/backend/modules/shop/views/comment/create.php @@ -0,0 +1,18 @@ +title = '创建 Comment'; +$this->params['breadcrumbs'][] = ['label' => 'Comments', 'url' => ['index']]; +$this->params['breadcrumbs'][] = $this->title; +?> +
+ + render('_form', [ + 'model' => $model, + ]) ?> + +
diff --git a/backend/modules/shop/views/comment/index.php b/backend/modules/shop/views/comment/index.php new file mode 100644 index 0000000..3737f47 --- /dev/null +++ b/backend/modules/shop/views/comment/index.php @@ -0,0 +1,31 @@ +title = '评论'; +$this->params['breadcrumbs'][] = $this->title; +?> +
+
+ $dataProvider, + 'filter' => $this->render("_search", ['model' => $searchModel]), + 'batch' => [ + [ + "label" => "删除", + "url" => "comment/deletes" + ], + ], + 'columns' => $columns, + 'batchTemplate' => '', + 'create' => '', + 'export' => '', + ]); + ?> +
+
\ No newline at end of file diff --git a/backend/modules/shop/views/comment/update.php b/backend/modules/shop/views/comment/update.php new file mode 100644 index 0000000..da61136 --- /dev/null +++ b/backend/modules/shop/views/comment/update.php @@ -0,0 +1,19 @@ +title = '编辑 Comment: ' . $model->id; +$this->params['breadcrumbs'][] = ['label' => 'Comments', 'url' => ['index']]; +$this->params['breadcrumbs'][] = ['label' => $model->id, 'url' => ['view', 'id' => $model->id]]; +$this->params['breadcrumbs'][] = 'Update '; +?> +
+ + render('_form', [ + 'model' => $model, + ]) ?> + +
diff --git a/backend/modules/shop/views/comment/view.php b/backend/modules/shop/views/comment/view.php new file mode 100644 index 0000000..a57e1bb --- /dev/null +++ b/backend/modules/shop/views/comment/view.php @@ -0,0 +1,47 @@ +title = $model->id; +$this->params['breadcrumbs'][] = ['label' => 'Comments', 'url' => ['index']]; +$this->params['breadcrumbs'][] = $this->title; +\yii\web\YiiAsset::register($this); +?> +
+ +

+ 'btn btn-success']) ?> +

+ + $model, + 'attributes' => [ + 'id', + 'user_id', + [ + 'attribute' => 'order_goods_id', + 'value' => function ($model) { + return $model->orderGoods->goods_name; + }, + 'label' => '商品名称' + ], + 'star', + 'content:ntext', + [ + 'attribute' => 'status', + 'value' => function ($model) { + return Comment::$commentStatus[$model->status]; + }, + 'label' => '状态' + ], + 'updated_at:datetime', + 'created_at:datetime', + ], + ]) ?> + +
diff --git a/backend/modules/shop/views/express-template/express_area_view.php b/backend/modules/shop/views/express-template/express_area_view.php index 31d353e..88bc8e4 100755 --- a/backend/modules/shop/views/express-template/express_area_view.php +++ b/backend/modules/shop/views/express-template/express_area_view.php @@ -6,6 +6,7 @@ use backend\modules\shop\models\ars\Province; use yii\helpers\Html; use yii\widgets\DetailView; use backend\modules\shop\models\ars\ExpressTemplate; +use backend\modules\shop\logic\ShopManager; /* @var $this yii\web\View */ /* @var $model backend\modules\shop\models\ars\ExpressTemplate */ @@ -32,7 +33,7 @@ $this->params['breadcrumbs'][] = $this->title; 'value' => function ($model) { $expressTemplateModel = ExpressTemplate::findOne($model->express_template); if ($expressTemplateModel->calculation_type == ExpressTemplate::CALCULATION_TYPE_WEIGHT) { - return $model->basic_count /= 10; + return $model->basic_count /= ShopManager::proportionalConversion(ShopManager::UNIT_TYPE_WEIGHT); } else { return $model->basic_count; } @@ -42,7 +43,7 @@ $this->params['breadcrumbs'][] = $this->title; 'attribute' => 'basic_price', 'label' => ExpressArea::$formList[$expressTemplateModel->calculation_type]['basic_price'], 'value' => function ($model) { - return $model->basic_price /= 100; + return $model->basic_price /= ShopManager::proportionalConversion(ShopManager::UNIT_TYPE_MONEY); } ], [ @@ -51,7 +52,7 @@ $this->params['breadcrumbs'][] = $this->title; 'value' => function ($model) { $expressTemplateModel = ExpressTemplate::findOne($model->express_template); if ($expressTemplateModel->calculation_type == ExpressTemplate::CALCULATION_TYPE_WEIGHT) { - return $model->extra_count /= 10; + return $model->extra_count /= ShopManager::proportionalConversion(ShopManager::UNIT_TYPE_WEIGHT); } else { return $model->extra_count; } @@ -61,7 +62,7 @@ $this->params['breadcrumbs'][] = $this->title; 'attribute' => 'extra_price', 'label' => ExpressArea::$formList[$expressTemplateModel->calculation_type]['extra_price'], 'value' => function ($model) { - return $model->extra_price /= 100; + return $model->extra_price /= ShopManager::proportionalConversion(ShopManager::UNIT_TYPE_MONEY); } ], 'updated_at:datetime', diff --git a/backend/modules/shop/views/taking-site/_search.php b/backend/modules/shop/views/taking-site/_search.php index 6d1d771..6db10b9 100755 --- a/backend/modules/shop/views/taking-site/_search.php +++ b/backend/modules/shop/views/taking-site/_search.php @@ -29,6 +29,19 @@ use \blobt\widgets\DateRangePicker; ]) ?>
+
+ field($model, 'name', [ + "template" => "{input}{error}", + "inputOptions" => [ + "placeholder" => "名称", + "class" => "form-control", + ], + "errorOptions" => [ + "class" => "error-tips" + ] + ]) + ?> +
field($model, "created_at_range", [ "template" => "{input}{error}", diff --git a/backend/modules/shop/views/taking-site/create.php b/backend/modules/shop/views/taking-site/create.php index 94319fc..c744c1e 100755 --- a/backend/modules/shop/views/taking-site/create.php +++ b/backend/modules/shop/views/taking-site/create.php @@ -5,8 +5,8 @@ use yii\bootstrap4\Html; /* @var $this yii\web\View */ /* @var $model backend\models\ars\TakingSite */ -$this->title = '创建 Taking Site'; -$this->params['breadcrumbs'][] = ['label' => 'Taking Sites', 'url' => ['index']]; +$this->title = '创建自提点'; +$this->params['breadcrumbs'][] = ['label' => '上门自提', 'url' => ['index']]; $this->params['breadcrumbs'][] = $this->title; ?>
diff --git a/backend/views/config/index.php b/backend/views/config/index.php deleted file mode 100755 index 3bbeb28..0000000 --- a/backend/views/config/index.php +++ /dev/null @@ -1,27 +0,0 @@ - - -配置页 diff --git a/backend/views/layouts/sidebar.php b/backend/views/layouts/sidebar.php new file mode 100755 index 0000000..efe095e --- /dev/null +++ b/backend/views/layouts/sidebar.php @@ -0,0 +1,52 @@ + + \ No newline at end of file diff --git a/backend/web/.babelrc b/backend/web/.babelrc new file mode 100644 index 0000000..0b6f443 --- /dev/null +++ b/backend/web/.babelrc @@ -0,0 +1,17 @@ +{ + "presets": [ + "@babel/preset-react", + "@babel/preset-env" + ], + "plugins": [ + ["@babel/plugin-proposal-decorators", { "legacy": true }], + ["@babel/proposal-class-properties", { "loose": true }], + "@babel/plugin-transform-runtime", + "babel-plugin-styled-components", + ["import", { + "libraryName": "antd", + "libraryDirectory": "es", + "style": "css" + }] + ] +} \ No newline at end of file diff --git a/backend/web/.eslintrc.json b/backend/web/.eslintrc.json new file mode 100644 index 0000000..a30c45b --- /dev/null +++ b/backend/web/.eslintrc.json @@ -0,0 +1,26 @@ +{ + "env": { + "browser": true, + "es6": true + }, + "extends": "eslint:recommended", + "globals": { + "Atomics": "readonly", + "SharedArrayBuffer": "readonly" + }, + "parserOptions": { + "ecmaFeatures": { + "jsx": true + }, + "ecmaVersion": 2018, + "sourceType": "module" + }, + "plugins": [ + "react", + "react-hooks" + ], + "rules": { + "react-hooks/rules-of-hooks": "error", + "react-hooks/exhaustive-deps": "warn" + } +} \ No newline at end of file diff --git a/backend/web/.gitignore b/backend/web/.gitignore new file mode 100644 index 0000000..cb7f43e --- /dev/null +++ b/backend/web/.gitignore @@ -0,0 +1,11 @@ +/index.php +/index-test.php +/robots.txt +uploads +css/umeditor/php/upload/ + +node_modules/ +yarn-error.log +yarn.lock +package-lock.json +ueditor diff --git a/backend/web/package.json b/backend/web/package.json new file mode 100644 index 0000000..a2b9588 --- /dev/null +++ b/backend/web/package.json @@ -0,0 +1,89 @@ +{ + "scripts": { + "dev": "webpack --mode=development --watch", + "dev:dashboard": "webpack --module dashboard --mode=development --watch", + "build:dashboard": "webpack --module dashboard --mode=production", + "dev:mini-program-management": "webpack --module mini_program_management --mode=development --watch", + "build:mini-program-management": "webpack --module mini_program_management --mode=production", + "dev:spread": "webpack --module spread --mode=development --watch", + "build:spread": "webpack --module spread --mode=production", + "dev:sku": "webpack --module sku --mode=development --watch", + "build:sku": "webpack --module sku --mode=production", + "dev:style": "webpack --module style --mode=development --watch", + "build:style": "webpack --module style --mode=production", + "dev:order-detail": "webpack --module order_detail --mode=development --watch", + "build:order-detail": "webpack --module order_detail --mode=production", + "dev:sku-item": "webpack --module sku_item --mode=development --watch", + "build:sku-item": "webpack --module sku_item --mode=production", + "dev:custom-menu": "webpack --module custom-menu --mode=development --watch", + "build:custom-menu": "webpack --module custom-menu --mode=production", + "dev:sku-for-activity": "webpack --module sku_for_activity --mode=development --watch", + "build:sku-for-activity": "webpack --module sku_for_activity --mode=production", + "build": "webpack --mode=production" + }, + "dependencies": { + "@babel/core": "^7.0.1", + "@babel/preset-react": "^7.0.0", + "antd": "^3.16.5", + "axios": "^0.18.0", + "babel-loader": "^8.0.2", + "bizcharts": "^3.4.3", + "che-react-number-easing": "^0.1.2", + "classnames": "^2.2.6", + "clone": "^2.1.2", + "compare-versions": "^3.4.0", + "dayjs": "^1.8.0", + "extract-text-webpack-plugin": "^4.0.0-beta.0", + "fast-deep-equal": "^2.0.1", + "file-loader": "^2.0.0", + "html2canvas": "^1.0.0-alpha.12", + "immer": "^2.1.5", + "mobx": "^5.6.0", + "mobx-react": "^5.3.6", + "polished": "^2.3.3", + "prop-types": "^15.6.2", + "qs": "^6.6.0", + "rasterizehtml": "^1.3.0", + "rc-switch": "^1.8.0", + "rc-upload": "^2.6.3", + "react": "^16.8.6", + "react-beautiful-dnd": "^11.0.2", + "react-chartjs-2": "^2.7.4", + "react-circle": "^1.1.1", + "react-color": "^2.17.0", + "react-dom": "^16.8.6", + "react-grid-layout": "^0.16.6", + "react-select": "^2.1.1", + "react-sortable-hoc": "^0.8.3", + "sass-resources-loader": "^1.3.3", + "styled-components": "^4.1.3", + "throttle-debounce": "^2.1.0", + "to-string-loader": "^1.1.5", + "url-loader": "^1.1.1", + "uuid": "^3.3.2", + "webpack": "^4.29.5", + "webpack-cli": "^3.2.3" + }, + "devDependencies": { + "@babel/plugin-proposal-class-properties": "^7.1.0", + "@babel/plugin-proposal-decorators": "^7.1.2", + "@babel/plugin-transform-runtime": "^7.4.3", + "@babel/preset-env": "^7.1.0", + "babel-plugin-import": "^1.11.0", + "babel-plugin-styled-components": "^1.10.0", + "clean-webpack-plugin": "^2.0.0", + "copy-webpack-plugin": "^4.6.0", + "css-loader": "^1.0.0", + "eslint": "^5.16.0", + "eslint-plugin-react": "^7.12.4", + "eslint-plugin-react-hooks": "^1.6.0", + "html-webpack-plugin": "^3.2.0", + "mini-css-extract-plugin": "^0.5.0", + "mobx-react-devtools": "^6.0.3", + "node-sass": "^4.9.3", + "require-context": "^1.1.0", + "resolve-url-loader": "^2.3.1", + "sass-loader": "^7.1.0", + "style-loader": "^0.23.0" + } +} diff --git a/backend/web/src/iconfont.html b/backend/web/src/iconfont.html new file mode 100644 index 0000000..7679640 --- /dev/null +++ b/backend/web/src/iconfont.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/backend/web/src/import.html b/backend/web/src/import.html new file mode 100644 index 0000000..db8c706 --- /dev/null +++ b/backend/web/src/import.html @@ -0,0 +1,4 @@ +
+ \ No newline at end of file diff --git a/backend/web/src/js/amazing-creator/GridEditor.js b/backend/web/src/js/amazing-creator/GridEditor.js new file mode 100644 index 0000000..aa3c959 --- /dev/null +++ b/backend/web/src/js/amazing-creator/GridEditor.js @@ -0,0 +1,36 @@ +import React, {Component} from "react"; +import GridLayout from "react-grid-layout"; +import styled from 'styled-components' +import "react-grid-layout/css/styles.css"; +import "react-resizable/css/styles.css"; +import {inject, observer} from 'mobx-react' +import Module from './Module' + +@inject('store') +@observer +export default class GridEditor extends React.Component { + onLayoutChange = (layout) => { + const {changeLayout} = this.props.store; + changeLayout(layout); + }; + + render() { + const {layout, attrs, add} = this.props.store; + + return ( + + {layout.map(item => ( +
+ +
+ ))} +
+ ) + } +} \ No newline at end of file diff --git a/backend/web/src/js/amazing-creator/Module.js b/backend/web/src/js/amazing-creator/Module.js new file mode 100644 index 0000000..8562f50 --- /dev/null +++ b/backend/web/src/js/amazing-creator/Module.js @@ -0,0 +1,101 @@ +import React from "react"; +import styled from "styled-components"; +import PropTypes from 'prop-types' +import Swiper from './modules/Swiper' +import GoodsList from './modules/GoodsList' + +export default class Module extends React.Component { + onLayoutChange = (layout) => { + const {changeLayout} = this.props.store; + changeLayout(layout); + }; + + static defaultProps = { + layout: {}, + attr: {} + }; + + static propTypes = { + layout: PropTypes.object, + attr: PropTypes.object + }; + + render() { + const {attr} = this.props; + + const moduleTable = { + Swiper: , + GoodsList: + }; + + if (moduleTable[attr.component]) { + return ( + + {moduleTable[attr.component]} + + + + + ) + } else { + return ( + {attr.component} + ) + } + } +} + +const Wrapper = styled.div` + position: relative; + height: 100%; + background: #fff; + box-shadow: 0 0 40px 0 rgba(0, 0, 0, .1); + overflow: hidden; +`; + +const ErrorTip = styled.div` + display: flex; + word-break: break-all; + height: 100%; + justify-content: center; + align-items: center; + background: #efefef; +`; + +const HoverLayer = styled.div` + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + display: flex; + justify-content: center; + align-items: center; + opacity: 0; + background: rgba(0, 0, 0, .6); + z-index: auto; + transition: all .3s; + &:hover { + opacity: 1; + z-index: 100; + } +`; + +const buttonSize = 40; +const EditButton = styled.div` + display: flex; + justify-content: center; + align-items: center; + width: ${buttonSize}px; + height: ${buttonSize}px; + border-radius: 100%; + background: #fff; + transition: all .1s; + font-size: ${buttonSize / 2}px; + font-weight: bold; + color: #000; + &:active { + background:#eee; + transform: scale(.8); + } +`; \ No newline at end of file diff --git a/backend/web/src/js/amazing-creator/Previewer.js b/backend/web/src/js/amazing-creator/Previewer.js new file mode 100644 index 0000000..64f2dc3 --- /dev/null +++ b/backend/web/src/js/amazing-creator/Previewer.js @@ -0,0 +1,106 @@ +import React, {Component} from "react"; +import GridEditor from './GridEditor' +import styled from 'styled-components' + +export default class Previewer extends React.Component { + state = { + time: `${new Date().getHours()}:${new Date().getMinutes()}` + }; + + componentWillMount() { + this.timer = setInterval(() => { + const now = new Date(); + this.setState({ + time: `${now.getHours()}:${now.getMinutes().toString().padStart(2, '0')}` + }) + }, 1000) + } + + componentWillUnmount() { + this.timer && clearInterval(this.timer); + } + + render() { + const {time} = this.state; + // layout is an array of objects, see the demo for more complete usage + + return ( + +
+ + {time} + + + 首页 + +
+ + + + +
+ ) + } +} + +const ratio = 16 / 9; +const width = 300; + +const Wrapper = styled.div` + display: flex; + flex-direction: column; + width: ${width}px; + height: ${width * ratio}px; + border: 1px solid #eee; + border-radius: 10px; + overflow-x: hidden; + background: #fff; + box-shadow: 0 0 40px 0 rgba(0, 0, 0, .1); +`; + +const Header = styled.div` + flex-shrink: 0; + width: 100%; + background-size: cover; + background-repeat: no-repeat; + text-align: center; + font-size: 10px; + margin-bottom: 3px; +`; + +const SimulationBar = styled.div` + width: 100%; + background-size: contain; + background-repeat: no-repeat; +`; + +const StatusBar = styled(SimulationBar)` + background-image: url("/img/status-bar.png"); + background-size: cover; +`; + +const TitleBar = styled(SimulationBar)` + display: flex; + align-items: center; + height: 38px; + background-image: url("/img/weapp-status-bar.png"); + span { + margin-left: 10px; + font-size: 1.4em; + } +`; + +const Tabbar = styled(SimulationBar)` + flex-shrink: 0; + width: 100%; + height: 40px; + border-top: 1px solid #eee; + background-size: contain; + background-repeat: no-repeat; + background-image: url('/img/tabbar.png'); +`; + +const Content = styled.div` + flex-grow: 1; + overflow-y: auto; +`; diff --git a/backend/web/src/js/amazing-creator/PropsEditor.js b/backend/web/src/js/amazing-creator/PropsEditor.js new file mode 100644 index 0000000..2474ffb --- /dev/null +++ b/backend/web/src/js/amazing-creator/PropsEditor.js @@ -0,0 +1,16 @@ +import React, {Component} from "react"; +import styled from 'styled-components' + +export default class PropsEditor extends React.Component { + render() { + return ( + + + + ) + } +} + +const Wrapper = styled.div` + flex-grow: 1; +`; \ No newline at end of file diff --git a/backend/web/src/js/amazing-creator/index.js b/backend/web/src/js/amazing-creator/index.js new file mode 100644 index 0000000..23c0637 --- /dev/null +++ b/backend/web/src/js/amazing-creator/index.js @@ -0,0 +1,29 @@ +import React, {Component} from 'react'; +import {render} from "react-dom"; +import axios from 'axios' +import styled from 'styled-components' +import Previewer from './Previewer' +import PropsEditor from './PropsEditor' +import {Provider} from 'mobx-react' +import store from './store' +import DevTools from 'mobx-react-devtools'; + +const Wrapper = styled.div` + display: flex; +`; + +class AmazingCreator extends React.Component { + render() { + return ( + + + + + + + + ) + } +} + +render(, document.getElementById('edit-home')); \ No newline at end of file diff --git a/backend/web/src/js/amazing-creator/modules/GoodsList.js b/backend/web/src/js/amazing-creator/modules/GoodsList.js new file mode 100644 index 0000000..e4e68e1 --- /dev/null +++ b/backend/web/src/js/amazing-creator/modules/GoodsList.js @@ -0,0 +1,67 @@ +import React from "react"; +import PropTypes from "prop-types"; +import styled from 'styled-components' + +export default class GoodsList extends React.Component { + static propTypes = { + name: PropTypes.string, + title: PropTypes.string, + type: PropTypes.string, + onChangeTitle: PropTypes.func, + onChangeType: PropTypes.func + }; + + render() { + const {name, title, type, onChangeTitle, onChangeType} = this.props; + const typeImageTable = { + grid: '/img/grid.png', + list: '/img/list.png', + horizScroll: '/img/scroll.png', + }; + + const options = [ + {value: 1, label: '横排(右滚动)'}, + {value: 2, label: '多列(三列)'}, + {value: 3, label: '竖排(下滚动)'} + ]; + + return ( + +
+ {title} + 查看更多> +
+ +
+ ) + } +} + +const Wrapper = styled.div` + width: 100%; +`; + +const Header = styled.div` + display: flex; + justify-content: space-between; + align-items: center; + padding: 5px 10px; +`; + +const Title = styled.div` + color: #e2314a; + font-weight: bold; +`; + +const More = styled.div` + font-size: .8em; + color: #aaa; + cursor: pointer; + &:active { + color: #666; + } +`; + +const Image = styled.img` + width: 100%; +`; \ No newline at end of file diff --git a/backend/web/src/js/amazing-creator/modules/Swiper.js b/backend/web/src/js/amazing-creator/modules/Swiper.js new file mode 100644 index 0000000..d464a1f --- /dev/null +++ b/backend/web/src/js/amazing-creator/modules/Swiper.js @@ -0,0 +1,14 @@ +import React from "react"; +import styled from 'styled-components' + +export default class Swiper extends React.Component { + render() { + return + } +} + +const Image = styled.img` + width: 100%; + height: 100%; + user-select: none; +`; \ No newline at end of file diff --git a/backend/web/src/js/amazing-creator/old.js b/backend/web/src/js/amazing-creator/old.js new file mode 100644 index 0000000..43a77be --- /dev/null +++ b/backend/web/src/js/amazing-creator/old.js @@ -0,0 +1,150 @@ +import React, {Component} from 'react'; +import { + SortableContainer, + SortableElement, + SortableHandle, + arrayMove, +} from 'react-sortable-hoc'; +import PropTypes from 'prop-types' +import Select from 'react-select'; +import axios from 'axios' + +const DragHandle = SortableHandle(() => ( +
+)); // This can be any component you want + +class Part extends React.Component { + static propTypes = { + name: PropTypes.string, + title: PropTypes.string, + onChangeTitle: PropTypes.func, + onChangeType: PropTypes.func + }; + + render() { + const {name, title, type, onChangeTitle, onChangeType} = this.props; + const typeImageTable = [ + '/img/scroll.png', + '/img/grid.png', + '/img/list.png', + ]; + const options = [ + { value: 1, label: '横排(右滚动)' }, + { value: 2, label: '多列(三列)' }, + { value: 3, label: '竖排(下滚动)' } + ]; + + return ( +
+
+ + setSkuItem(rowIndex, index, newValue)} + > + {attrValue.map(({ id, attr_value }) => ( + + ))} + + ) + })) : [{ + title: '规格', + dataIndex: 'value', + key: 'value', + align: 'center', + render: (value, _, rowIndex) => ( + onChangeItem(rowIndex, 'value', e.target.value)} + /> + ) + }]; + + const columns = [ + ...skuColumns, + ...buildInputCol(additionalCol, onChangeItem), + { + key: 'delete', + title: '操作', + render: (_1, _2, index) => onDeleteItem(index)}>删除 + } + ]; + + return ( + + + + ) +} + +function App() { + const goodsId = queryString.parse(location.search.substr(1)).id; + const defaultData = (Number(defaultSku.type) === 1 && defaultAttributes.length === 0) ? { + type: 2, + data: [] + } : defaultSku; + const [data, setData] = useState(defaultData); + const [initData, setInitData] = useState(deepClone(defaultData)); + const [attributes, setAttributes] = useState(defaultAttributes); + const [loading, setLoading] = useState(false); + const modeList = [ + { + key: 'select', + title: '已选商品规格设置' + }, + { + key: 'manual', + title: '手动输入' + }, + ]; + + function setItem(skuIndex, key, value) { + setData(produce(data, draftState => { + draftState.data[skuIndex] = { + ...draftState.data[skuIndex], + [key]: value + }; + })); + } + + function deleteItem(index) { + setData(produce(data, draftState => { + draftState.data.splice(index, 1) + })); + } + + const type = modeList[data.type - 1].key; + + function selectMode(mode) { + async function switchMode() { + const modeTable = { + select: 1, + manual: 2 + }; + setLoading(true); + const {sku, attributes} = await axios.get('switch', { + params: {goodsId, type: modeTable[mode]} + }); + setData(sku); + setInitData(deepClone(sku)); + setAttributes(attributes); + setLoading(false); + } + + if (mode === type) { + return null + } else if (mode === 'select' && attributes.length === 0) { + Modal.info({ content: '无已选规格,请到【规格管理】中添加规格并在【商品列表】>【修改】>【商品规格】中选择需要的规格' }) + } else if (!deepEqual(initData, data)) { + Modal.confirm({ + content: '修改的内容没有保存,是否放弃修改并切换?', + onOk: () => { switchMode() } + }); + } else { + return switchMode(); + } + } + + function addItem() { + const attrValue = type === 'select' ? { value: [] } : { value: '' }; + + setData({ + ...data, + data: [ + ...data.data, + { + ...attrValue, + id: -1, + price: null, + stock: -1, + weight: 0 + } + ] + }) + } + + async function save() { + let errorMessage = '请填写完整的信息'; + + const isAllDataCorrect = data.data.every(item => { + const isAllNotEmpty = Object.keys(item) + .every(key => item[key] !== undefined && item[key] !== null && item[key] !== ''); + + if (type === 'select') { + const isAllSkuCorrect = item.value && item.value.length === attributes.length; + + if (!isAllSkuCorrect) errorMessage = '请选择所有的规格项'; + + return isAllNotEmpty && isAllSkuCorrect; + } else { + const isAllSkuCorrect = item.value && item.value.length > 0; + + if (!isAllSkuCorrect) errorMessage = '请填写所有的规格项'; + + return isAllNotEmpty && isAllSkuCorrect; + } + }); + + if (isAllDataCorrect) { + await axios.post('add-sku', { goodsId, sku: data}); + setInitData(deepClone(data)); + message.success('保存成功'); + history.back(); + } else { + message.error(errorMessage) + } + } + + return ( + + + + {modeList.map(({ key, title }) => ( + selectMode(key)} + >{title} + ))} + + + + + + + + + + ) +} + +const ButtonGroup = styled.div` + margin-top: 20px; +`; + +const Wrapper = styled.div` + +`; + +const SelectMode = styled.div` + display: flex; + justify-content: center; + margin-bottom: 20px; +`; + +const ModeItem = styled.div` + padding: 5px 15px; + margin: 10px; + cursor: pointer; + border-radius: 30px; + color: #999; + background: #eee; + border: 2px solid transparent; + transition: all .3s; + &.active { + background: #1c7cff; + color: #fff; + cursor: default; + } + &:hover { + border-color: #1c7cff; + } +`; + +ReactDOM.render( + + + , + document.getElementById('app') +); \ No newline at end of file diff --git a/backend/web/src/sku/SelectCell.js b/backend/web/src/sku/SelectCell.js new file mode 100644 index 0000000..62fae7e --- /dev/null +++ b/backend/web/src/sku/SelectCell.js @@ -0,0 +1,74 @@ +import React from "react"; +import styled from "styled-components"; +import { Select, Spin, Icon } from 'antd'; +import PropTypes from 'prop-types' + +const Option = Select.Option; + +const antIcon = ; + +export default class SelectCell extends React.Component { + constructor(props) { + super(props); + this.state = { + skuList: null + }; + } + + static defaultProps = { + onChange: () => {} + }; + + static propTypes = { + items: PropTypes.array, + defaultValue: PropTypes.array, + value: PropTypes.array, + label: PropTypes.string, + onChange: PropTypes.func + }; + + render() { + const {items, label, value, defaultValue, onChange} = this.props; + + return ( + + + {items ? ( + + ) : } + + ) + } +} + +const Root = styled.div` + display: flex; + align-items: center; + width: 100%; + margin: 10px 0; + input.ant-select-search__field:not([type="submit"]):not([type="button"]):not([type="reset"]) { + padding: 1px; + background: none!important; + } +`; + +const Label = styled.div` + width: 100px; + margin-right: 20px; +`; \ No newline at end of file diff --git a/backend/web/src/sku/index.js b/backend/web/src/sku/index.js new file mode 100644 index 0000000..42c96ef --- /dev/null +++ b/backend/web/src/sku/index.js @@ -0,0 +1,157 @@ +import React from "react"; +import styled from "styled-components"; +import ReactDOM from "react-dom"; +import { LocaleProvider, Alert } from 'antd'; +import zhCN from 'antd/lib/locale-provider/zh_CN'; +import SelectCell from './SelectCell' + +const goodsId = window.goodsId; +const currentAttr = window.currentAttr.map(({id, name, value}) => ({id, name, value})); +const canNotDeleteAttr = window.canNotDeleteAttr; + +class Sku extends React.Component { + constructor(props) { + super(props); + + this.state = { + skuList: currentAttr, + allAttr: null, + }; + } + + componentDidMount() { + const select = $('#goods-cat_id'); + const loadData = (id, shouldClearSelectedAttrs = false) => { + $.ajax({ + cache: false, + url: "filter-attribute", + data: {catId: id, goodsId: goodsId}, + dataType: "json", + success: data => { + const allAttr = data.map(({id, name, value}, index) => { + const canNotDeleteCurrent = canNotDeleteAttr.find(i => i.id === parseInt(id)); + + return { + id: parseInt(id), + name, + disabled: Boolean(canNotDeleteCurrent), + value: value.split(',').map(i => ({ + id: i, + name: i, + disabled: Boolean(canNotDeleteCurrent) && canNotDeleteCurrent.value.some(j => j === i) + })) + } + }); + if (shouldClearSelectedAttrs) { + this.setState({ + skuList: [], + allAttr + }) + } else { + this.setState({ + allAttr + }) + } + } + }); + } + + loadData(select.val()) + select.change(function () { + loadData($(this).val(), true) + }) + } + + handleChangeAttr = (value) => { + let {skuList} = this.state; + + this.setState({ + skuList: value.map(id => { + let result = skuList.find(i => i.id === id); + + if (!result) { + const attr = this.state.allAttr.find(i => i.id === id); + if (attr) { + const {id, name, value} = attr; + result = {id, name, value: value.map(i => i.id)}; + } + } + + return result; + }) + }) + }; + + handleChangeAttrItem = (index, value) => { + let {skuList} = this.state; + skuList[index].value = value; + this.setState({ + skuList + }) + }; + + render() { + const {skuList, allAttr} = this.state; + if (allAttr) { + return ( + + {(canNotDeleteAttr && canNotDeleteAttr.length > 0) && ( +
+ +
+ )} + + i.id)} + onChange={this.handleChangeAttr} + /> + {skuList && skuList.map((sku, index) => ( + i.id === sku.id).value} + value={sku.value} + onChange={this.handleChangeAttrItem.bind(undefined, index)} + /> + ))} + ({id, value})))} + /> + +
+ ) + } else { + return <> + } + } +} + +const Root = styled.div` + +`; + +const Header = styled.div` + margin-bottom: 30px; +`; + +const SelectGroup = styled.div` + width: 100%; +`; + +ReactDOM.render( + + + , + document.getElementById('sku-choose') +); \ No newline at end of file diff --git a/backend/web/src/styles/amazing-creator.scss b/backend/web/src/styles/amazing-creator.scss new file mode 100644 index 0000000..65402ae --- /dev/null +++ b/backend/web/src/styles/amazing-creator.scss @@ -0,0 +1,83 @@ +.react-resizable-handle { + z-index: 200; +} + +#edit-home { + width: 100%; + margin: auto; + //background: #eee; + ul { + padding: 0; + margin-bottom: 20px; + } + .btn { + margin-right: 10px; + } +} + +.amazing-creator, .ac { + display: flex; + &__board { + flex-grow: 1; + } +} + +.sortable-item { + display: flex; + position: relative; + list-style: none; + background: #fff; + margin: 10px 0; + padding: 10px; + border-radius: 10px; + box-shadow: 0 0 30px 0 rgba(0, 0, 0, .1); + .drag-handle { + display: flex; + align-items: center; + padding: 10px; + user-select: none; + cursor: row-resize; + color: #362c57; + font-size: 1.5em; + } + .part { + flex-grow: 1; + flex-shrink: 1; + display: flex; + justify-content: space-between; + &__handle { + display: flex; + flex-direction: column; + justify-content: center; + margin-left: 10px; + > * { + margin: 10px 0; + } + } + &__content { + flex-shrink: 0; + width: 250px; + img { + width: 100%; + } + } + &__header { + display: flex; + justify-content: space-between; + align-items: center; + .title { + color: #e2314a; + font-weight: bold; + margin-left: 15px; + } + .more { + font-size: .8em; + color: #aaa; + cursor: pointer; + &:active { + color: #666; + } + } + } + } +} \ No newline at end of file diff --git a/backend/web/src/styles/content-header.scss b/backend/web/src/styles/content-header.scss new file mode 100644 index 0000000..332f11b --- /dev/null +++ b/backend/web/src/styles/content-header.scss @@ -0,0 +1,90 @@ +.content-header { + display: flex; + flex-wrap: wrap; + justify-content: space-between; + align-items: center; + padding: 0; + h1, .breadcrumb { + margin: 5px 0!important; + } + h1 { + color: $color-primary; + font-weight: bold; + padding-left: 10px; + border-left: 5px solid $color-primary; + } +} + +.content-header>.breadcrumb { + $height: 25px; + $size: 10px; + display: flex; + flex-wrap: wrap; + position: static; + float: none!important; + height: $height; + background: none; + overflow: hidden; + padding: 0; + li { + display: flex; + align-items: center; + position: relative; + margin-right: $size + 3px; + background: $color-primary; + &:hover { + $background: darken($color-primary, 10%); + background: $background; + &::before { + border-top: $height / 2 solid $background; + border-bottom: $height / 2 solid $background; + } + &::after { + border-left: $size solid $background; + } + } + &::before { + content: ''; + position: absolute; + top: 0; + left: -1 * $size; + width: 0; + height: 0; + border-top: $height / 2 solid $color-primary; + border-bottom: $height / 2 solid $color-primary; + border-left: $size solid transparent; + } + &::after { + content: ''; + position: absolute; + top: 0; + right: -1 * $size; + width: 0; + height: 0; + border-top: $height / 2 solid transparent; + border-bottom: $height / 2 solid transparent; + border-left: $size solid $color-primary; + } + &.active { + $background: $color-primary-dim; + box-shadow: none; + background: $background; + color: $color-primary; + padding: 0 $size / 2 + 5px; + &::before { + border-top: $height / 2 solid $background; + border-bottom: $height / 2 solid $background; + } + &::after { + border-left: $size solid $background; + } + } + a { + padding: 5px 10px; + color: $color-primary-dim!important; + } + } + >li+li::before { + content: ''; + } +} \ No newline at end of file diff --git a/backend/web/src/styles/dashboard.scss b/backend/web/src/styles/dashboard.scss new file mode 100644 index 0000000..bcc6722 --- /dev/null +++ b/backend/web/src/styles/dashboard.scss @@ -0,0 +1,180 @@ +.content-header h1 { + margin: 0; +} + +.box h1 { + font-size: 1.5em; + margin-top: 0; + margin-bottom: 20px; +} + +.row { + display: flex; + flex-wrap: wrap; + margin: 0; + .row { + margin: 0; + } + > div { + margin: 10px; + } +} + +.col { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; +} + +.total { + width: 100%; + display: flex; + flex-wrap: wrap; +} + +#trend-chart { + width: 80%; +} + +#total-pie { + width: 20%; +} + +.box { + margin: 20px; +} + +#stock-tip { + width: 350px; +} + +.overview-card, .line-chart, .data-table { + //@extend %shadow-soft; + box-shadow: 3px 6px 40px -6px rgba(125, 138, 179, 0.3); +} + +.overview-card { + flex-grow: 1; + display: flex; + justify-content: space-around; + align-items: center; + width: 200px; + padding: 20px 15px; + margin: 10px; + background: $color-panel; + border-radius: 10px; + img { + $size: 50px; + width: $size; + height: $size; + } + h1 { + font-size: 1em; + } + p.number { + font-size: 1.6em; + font-weight: bold; + color: #ef9131; + margin: 0; + } +} + +.total-display { + width: 100%; + margin: 0!important; + display: flex; + justify-content: space-around; + flex-wrap: wrap; +} + +.line-chart { + flex-grow: 1; + flex-shrink: 1; + max-width: 100%; + padding: 10px 20px!important; +} + +.line-chart, .data-table { + display: flex; + flex-direction: column; + padding: 20px; + background: $color-panel; + border-radius: 10px; + h1 { + font-size: 1.5em; + font-weight: bold; + } + .content { + display: flex; + flex-direction: column; + justify-content: center; + flex-grow: 1; + margin: 0; + padding: 0; + } +} + +.data-table { + table { + thead { + tr { + font-weight: bold; + td { + white-space: nowrap; + } + } + } + td { + padding: 8px; + font-size: .9em; + //white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + &.line-number { + text-align: center; + } + &.highlight { + color: #ef9131; + } + } + } + .table-empty-text { + text-align: center; + font-size: 1.4em; + } +} + +.fill-space { + flex-grow: 1; +} + +@media (min-width: 1235px) { + .total-display { + flex-wrap: nowrap; + } + .row { + flex-wrap: nowrap; + width: 100%; + .line-chart { + //width: 60%; + flex: 1; + } + .data-table { + max-width: 400px; + flex-grow: 0; + flex-shrink: 0; + } + } +} + +@media (max-width: 1235px) { + .row { + flex-wrap: wrap; + .line-chart { + flex-grow: 1; + width: 98%; + padding-right: 2%; + } + } +} \ No newline at end of file diff --git a/backend/web/src/styles/global.scss b/backend/web/src/styles/global.scss new file mode 100644 index 0000000..f0db4c2 --- /dev/null +++ b/backend/web/src/styles/global.scss @@ -0,0 +1,183 @@ +html, body { + color: $color-fore; + background: $color-background!important; + font-family: -apple-system, PingFang SC, Hiragino Sans GB, Microsoft YaHei, Helvetica Neue, Arial, sans-serif; +} + +body .fa { + font-family: "fa", "FontAwesome" !important; +} + +.content-wrapper { + margin-top: 70px; + margin-bottom: 10px; +} +.content-wrapper { + background: rgb(248, 250, 255)!important; +} + +input, select { + border-radius: 3px!important; +} + +button.btn-float, a.btn-float { + position: fixed; + display: block; + top: unset; + right: 50px; + bottom: 50px; + width: 60px; + height: 60px; + line-height: 60px; + padding: 0; + border-radius: 50%; + font-size: 1.6em; + box-shadow: 0 0 20px 5px rgba(0, 0, 0, .2); + z-index: 10000; +} + +.btn-info.btn-float { + bottom: 120px; +} + +fieldset { + transition: all .4s; +} +fieldset[disabled] { + opacity: 0; + cursor: auto!important; +} + +#driver-popover-item, #driver-highlighted-element-stage { + position: fixed!important; +} + +//.main-footer { +// position: absolute; +// bottom: 0; +// width: 100%; +//} + +::-webkit-scrollbar{width:6px!important;height:6px!important;} +body::-webkit-scrollbar{width:6px!important;height:6px!important;} + +::-webkit-scrollbar-track{background:rgba(200,200,200,0.22)!important;border-radius:8px!important;} + + +::-webkit-scrollbar-thumb{background-color: rgba(0, 0, 0, .1) !important;min-height:50px;border-radius:5px!important;} + + +@keyframes fadein { + 0% {opacity: 0;} + 100% {} +} + +.content-wrapper { + $margin: 20px; + margin-left: $sidebar-width + $margin!important; + margin-right: $margin; +} + +.main-footer { + margin-left: $sidebar-width!important; + a { + color: $color-primary-vivid; + } +} + +.tab-content { + height: auto!important; + border: none!important; + padding: 0!important; + margin-top: 30px; +} + +.login-logo { + font-weight: bold; +} + +.category-list { + .category-list-item { + &.category__hidden { + background: #f8f8f8; + color: #aaa; + } + > div { + display: flex; + align-items: center; + border: none!important; + } + .hidden-tip { + margin-left: 20px; + color: #aaa; + } + } + a.btn { + padding: 10px !important; + } + .btn-group { + a.btn { + border-radius: 0; + &:first-of-type { + border-radius: 5px 0 0 5px; + } + &:last-child { + border-radius: 0 5px 5px 0; + } + } + &.btn-group__circle { + a.btn { + padding: 8px!important; + margin: 0 5px; + font-size: .8em; + border-radius: 100%!important; + } + } + } + a.collapse-list-btn { + color: $color-primary; + i { + transition: transform .3s; + } + &[aria-expanded="true"] i { + transform: rotate(180deg); + } + &.disabled { + color: $color-dim; + cursor: default; + } + } + .drag-btn { + margin-left: 20px; + color: $color-info; + } +} + +.category-index { + min-height: 75vh; +} + +.goods-update .props-main-box { + position: relative!important; + top: 0; + left: 0; + margin-top: 20px; +} + +// 侧边栏折叠状态 +.sidebar-mini.sidebar-collapse .content-wrapper, +.sidebar-mini.sidebar-collapse .right-side, +.sidebar-mini.sidebar-collapse .main-footer { + margin-left: 70px!important; +} + +@media (max-width: 767px) { + body.sidebar-mini.sidebar-open { + .content-wrapper { + margin-left: 0!important; + } + } + .main-footer { + margin-left: 0!important; + } +} \ No newline at end of file diff --git a/backend/web/src/styles/header.scss b/backend/web/src/styles/header.scss new file mode 100644 index 0000000..7059b4b --- /dev/null +++ b/backend/web/src/styles/header.scss @@ -0,0 +1,128 @@ + +//顶栏背景色 +.logo, .navbar-static-top { + background: none!important; +} + +.main-header { + position: fixed; + width: auto; + left: $sidebar-width; + right: 0; + display: flex; + //background-image: linear-gradient(45deg, $color-primary, adjust_hue($color-primary, 40)); + background: $color-panel; + box-shadow: 0 0 20px 0 rgba(0, 0, 0, 0.1)!important; + transition: all .3s ease-in-out; + z-index: 100 !important; + .logo-lg, .logo, .sidebar-toggle, .hidden-xs, .notifications-menu a.dropdown-toggle { + color: $color-thin !important; + } + .user-image { + background: lighten($color-primary, $lightenDegree); + } + .logo { + flex-grow: 1; + font-weight: bold; + } + .navbar-static-top { + margin-left: 0; + } +} + +.user-header { + background: $color-primary!important; +} + +.user-menu { + .dropdown-menu { + left: unset; + right: 0; + box-shadow: 0 4px 60px 10px rgba(0, 0, 0, 0.1)!important; + overflow: visible; + } +} + +.sidebar-toggle { + &:active, &:hover { + background: $color-pale!important; + } +} +.notifications-menu { + .label-warning { + display: block; + padding: 3px 5px!important; + font-size: .8em!important; + background: #ff4e3d !important; + border-radius: 100%; + } + .dropdown-menu { + width: auto!important; + box-shadow: 0 0 40px 10px rgba(0, 0, 0, 0.1)!important; + ul.menu { + max-height: 40vh!important; + li { + a { + display: flex!important; + align-items: center; + padding: 10px 20px!important; + span.number { + color: $color-danger; + margin: 0 10px; + font-weight: bold; + } + span.tip { + color: $color-danger; + margin-left: 10px; + font-weight: bold; + } + } + i { + width: auto!important; + font-size: 1.6em; + margin-right: 20px; + padding: 10px; + border-radius: 100%; + text-align: center; + &.blue { + $color: #5964ab; + background: $color; + color: lighten($color, 50%)!important; + } + &.yellow { + $color: #df8346; + background: $color; + color: lighten($color, 50%)!important; + } + &.red { + background: $color-danger; + color: lighten($color-danger, 50%)!important; + } + } + } + } + } +} + +body.sidebar-collapse { + .main-header { + left: 50px; + } +} + +@media (max-width: 767px) { + .main-header { + left: 0!important; + } + .logo { + padding: 0!important; + } + .main-header .logo, .main-header .navbar { + width: auto; + } + body.sidebar-mini.sidebar-open { + .main-header { + margin-left: 190px; + } + } +} \ No newline at end of file diff --git a/backend/web/src/styles/index.scss b/backend/web/src/styles/index.scss new file mode 100644 index 0000000..d705bb4 --- /dev/null +++ b/backend/web/src/styles/index.scss @@ -0,0 +1,7 @@ +@import "./global"; +@import "sidebar"; +@import "widget"; +@import "header"; +@import "content-header"; +@import "dashboard"; +@import "amazing-creator"; \ No newline at end of file diff --git a/backend/web/src/styles/login.scss b/backend/web/src/styles/login.scss new file mode 100644 index 0000000..19a0439 --- /dev/null +++ b/backend/web/src/styles/login.scss @@ -0,0 +1,193 @@ + +html { + background: #292d34; + background-image: url("/img/login_background.png") !important; + background-size: cover !important; + font-family: -apple-system, PingFang SC, Hiragino Sans GB, Microsoft YaHei, Helvetica Neue, Arial, sans-serif; +} + +body { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + //background: rgba(0, 0, 0, .7)!important; + background: none !important; +} + +.login-box { + $color-primary: #2b457c; + $background: #e9f4ff; + + &__body { + position: fixed; + left: 50%; + top: 50%; + transform: translate(-50%, -50%); + display: flex; + height: 80vh; + box-shadow: 0 4px 30px 10px rgba(0, 0, 0, 0.2)!important; + //border-radius: 10px; + overflow: hidden; + } + &__bar { + position: absolute; + top: 40px; + width: 100%; + padding-right: 10px; + text-align: right; + border-left: 6px solid $background; + border-right: 6px solid $color-primary; + color: $color-primary; + font-weight: bold; + font-size: 1.5em; + } + &__left, &__right { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + padding: 20px; + } + + &__left { + padding: 0 60px; + background: #1a1d25; + } + + &__logo { + display: flex; + flex-direction: column; + align-items: center; + justify-content: space-around; + + img { + $size: 150px; + width: $size; + height: $size; + margin-bottom: 40px; + } + + p { + font-size: 1.5em; + font-weight: bold; + color: #2ad0ff; + } + } + + &__right { + flex-grow: 1; + background: $background; + + .form-group.has-feedback { + position: relative; + + .form-control { + padding-left: 40px; + border: none; + border-bottom: 2px solid $color-primary; + background: none !important; + + &:-webkit-autofill { + -webkit-box-shadow: 0 0 0 1000px $background inset; + -webkit-text-fill-color: $color-primary; + } + } + + .form-control-feedback { + position: absolute; + left: 0; + color: $color-primary; + } + } + + .submit-area { + display: flex; + flex-direction: column; + align-content: center; + justify-content: space-around; + text-align: center; + .form-group.field-adminloginform-rememberme { + color: $color-primary; + + label { + font-weight: bold!important; + } + } + .btn.btn-primary.btn-block.btn-flat { + display: inline-block; + padding: 10px 20px; + border-radius: 20px; + background: $color-primary; + box-shadow: 0 5px 10px 0 rgba(0, 0, 0, .2); + } + } + } +} + +@media (min-width: 1200px) { + .login-box__body { + width: 60vw; + } +} + +@media (max-width: 1200px) and (min-width: 800px) { + .login-box__body { + width: 700px; + } +} + +@media (max-width: 800px) and (min-width: 400px) { + .login-box__body { + width: 87.5%; + .login-box__left { + padding: 0 30px; + } + } +} + +@media (max-width: 600px) { + .login-box { + &__body { + flex-direction: column; + width: 100%; + height: 100%; + } + &__left { + padding: 20px 0; + background: #1a1d25; + } + &__logo { + display: flex; + flex-direction: row; + align-items: center; + justify-content: space-around; + + img { + $size: 50px; + width: $size; + height: $size; + margin-bottom: 0; + margin-right: 20px; + } + + p { + margin-bottom: 0; + font-size: 1.5em; + font-weight: bold; + color: #6fd1ff; + } + } + &__bar { + top: 110px; + width: auto; + left: 50%; + padding: 0 10px 2px 10px; + transform: translateX(-50%); + border: none; + border-bottom: 4px solid $color-primary; + text-align: center; + } + } +} \ No newline at end of file diff --git a/backend/web/src/styles/sidebar.scss b/backend/web/src/styles/sidebar.scss new file mode 100644 index 0000000..dad50c1 --- /dev/null +++ b/backend/web/src/styles/sidebar.scss @@ -0,0 +1,201 @@ +.main-sidebar { + position: fixed; + width: $sidebar-width; + height: 100%; + padding-top: 20px!important; + margin-bottom: 100px; + overflow-y: auto; + background: $color-sidebar!important; + //background-image: linear-gradient(135deg, #3a345d, #2d2a46); + .sidebar__header { + display: flex; + flex-direction: column; + align-items: center; + img { + $size: 60px; + width: $size; + height: $size; + border-radius: 50%; + } + p { + color: $color-sidebar-logo; + font-style: italic; + margin-top: 15px; + font-size: 1.2em; + font-weight: bold; + } + } + .user-panel .info { + a { + color: lighten($color-thin, 20%); + } + i { + font-size: 10px; + } + } + .sidebar-form { + border: none !important; + .input-group { + border-radius: 40px; + overflow: hidden; + input { + border: none !important; + } + } + input.form-control[type="text"], #search-btn { + background: lighten($color-primary-pale, 5%)!important; + color: $color-primary-dim !important; + } + } + ul.sidebar-menu { + margin-top: 10px; + > li.treeview { + // 一级菜单 + list-style: none; + margin-left: 15px; + a { + background: $color-sidebar!important; + color: $color-sidebar-fore; + &:hover { + color: $color-sidebar-active-fore!important; + } + } + > a { + padding: 10px 15px!important; + font-weight: normal; + font-size: 1em; + .fa { + margin-right: 5px; + font-size: 1.2em; + } + } + ul.treeview-menu { + padding-left: 0; + padding-bottom: 10px; + background: $color-sidebar!important; + li { + // 二级菜单 + padding: 5px 0; + //border-left: 4px solid transparent; + background: $color-sidebar!important; + &:hover { + background: $color-pale; + } + a { + padding: 0; + padding-left: 48px; + font-size: .9em; + background: $color-sidebar!important; + } + // 隐藏二级菜单前面的 O + .fa-circle-o { + display: none; + } + } + } + &.active { + // 当前页面所在的一级菜单 + background: $color-sidebar-active!important; + border-radius: 20px 0 0 20px; + overflow: hidden; + > a { + //color: $color-primary-vivid !important; + background: $color-sidebar-active!important; + color: $color-sidebar-active-fore!important; + } + .treeview-menu { + background: $color-sidebar-active!important; + li { + background: $color-sidebar-active!important; + a { + background: $color-sidebar-active!important; + } + &.active { + // 选中的二级菜单 + //border-left-color: $color-primary; + background: $color-sidebar-active!important; + a { + color: $color-sidebar-active-fore !important; + font-weight: normal; + } + } + } + } + } + &.menu-open { + // 展开的一级菜单 + //color: $color-primary !important; + } + } + } +} + +body.sidebar-collapse { + .main-sidebar { + padding-top: 10px!important; + overflow: visible; + } + .sidebar__header { + display: flex; + flex-direction: column; + align-items: center; + img { + $size: 25px; + width: $size; + height: $size; + border-radius: 50%; + transition: all .3s ease-in-out; + } + p { + display: none; + font-size: 12px; + margin-top: 5px; + margin-bottom: 0; + transition: all .3s ease-in-out; + } + } + ul.sidebar-menu { + > li.treeview { + margin-left: 0; + overflow: visible!important; + a { + border-radius: 50%; + } + //border-radius: 50%!important; + //a { + // padding: 10px!important; + // i { + // margin: 0!important; + // } + //} + a span { + width: 178px!important; + } + .pull-right-container { + margin-top: -1px!important; + padding: 8px 3px!important; + } + .treeview-menu { + left: 48px!important; + top: unset!important; + border: none!important; + } + } + } +} + +.main-sidebar, .main-footer { + box-shadow: 0 4px 30px 0 rgba(223, 225, 230, 0.5) !important; + border: none !important; +} + +.panel { + padding: 10px; + margin: 0; +} + +@media (max-width: 767px) { + .content-wrapper { + margin-left: 30px!important; + } +} \ No newline at end of file diff --git a/backend/web/src/styles/variables.scss b/backend/web/src/styles/variables.scss new file mode 100644 index 0000000..b5ff942 --- /dev/null +++ b/backend/web/src/styles/variables.scss @@ -0,0 +1,30 @@ +$color-primary: #2f4887; +$color-primary-vivid: hsl(hue($color-primary), saturation($color-primary), 50%); +$color-primary-dim: lighten($color-primary-vivid, 35%); +$color-primary-pale: lighten($color-primary-vivid, 42%); +$color-background: rgb(248, 250, 255); +$color-panel: #fff; +$color-pale: #f6f6f6; +$color-fore: #666; +$color-dim: #b4b6c5; +$color-thin: #515156; +$color-sidebar: #080e39; +$color-sidebar-active: #2a2e52; +$color-sidebar-fore: #dce0f2; +$color-sidebar-active-fore: #26caff; +$color-sidebar-logo: #26caff; + +$color-success: #2ab57d; +$color-danger: #c60013; +$color-info: #2a92ec; + +$lightenDegree: 70%; +$sidebar-width: 190px; + +%shadow { + box-shadow: 0 0 30px 0 rgba(0, 0, 0, 0.1)!important; +} + +%shadow-soft { + box-shadow: 0 4px 30px 0 rgba(223, 225, 230, 0.5) !important; +} \ No newline at end of file diff --git a/backend/web/src/styles/widget.scss b/backend/web/src/styles/widget.scss new file mode 100644 index 0000000..b90af99 --- /dev/null +++ b/backend/web/src/styles/widget.scss @@ -0,0 +1,301 @@ +.content-wrapper { + margin-left: 30px; + margin-right: 30px; + > section.content { + margin: 30px 0; + background: $color-panel!important; + box-shadow: 0 4px 30px 2px rgba(223, 225, 230, 0.5)!important; + border-radius: 10px; + } + } + +.error-page .fa-warning { + font-size: 1.5em; +} + +#treeView { + background: none; +} + +body a:focus { + text-decoration: none; +} + +.box, .panel { + padding: 0!important; + margin: 0!important; + border: none!important; + border-image-width: 0!important; + box-shadow: none!important; + //box-shadow: 0 4px 30px 2px rgba(223, 225, 230, 0.5)!important; + overflow: hidden; + .panel { + box-shadow: none!important; + } +} + +.grid-view { + margin-top: 20px; +} + +.box.box-default { + margin: 0; +} + +.order-index { + .panel-default { + padding: 0; + padding-top: 20px; + margin: 0; + } +} + +.panel { + margin: 20px 0; + .panel-heading { + padding: 0; + } +} + +.btn { + border: none; + $btn-list: (name: default, color: $color-pale), + (name: primary, color: $color-primary), + (name: success, color: $color-success), + (name: info, color: $color-info), + (name: danger, color: $color-danger); + + @each $btn in $btn-list { + &.btn-#{map_get($btn, name)} { + $color: map_get($btn, color); + background: $color; + @if (lightness($color) < 70%) { + color: lighten($color, $lightenDegree)!important; + } @else { + color: darken($color, $lightenDegree)!important; + } + &:hover { + background: darken(map_get($btn, color), 5%); + } + &:active { + background: darken(map_get($btn, color), 10%); + } + } + } +} + +.dropdown-toggle { + box-shadow: none!important; +} +.select-data-box { + position: static!important; +} + +.panel-heading, .panel-footer { + background: $color-panel!important; +} + +.panel-heading { + border: none; +} + +.nav-tabs { + display: flex; + border-bottom-color: $color-pale; + li { + float: none; + border-bottom: 3px solid transparent; + a { + color: $color-fore; + } + a, a:hover { + border: none!important; + } + i { + color: $color-primary-vivid!important; + } + &.active { + border-bottom-color: $color-primary; + box-shadow: none; + a { + margin-right: 0; + color: $color-primary; + font-weight: bold; + } + } + } +} + +.kv-panel-before, .kv-panel-after, .panel-footer { + border: none; +} + +table.kv-grid-table { + &, thead, th, td { + border: none!important; + } + thead { + background: lighten($color-primary-pale, 5%); + a { + color: $color-primary; + } + input.form-control[type][name], select { + background: lighten($color-primary-pale, 10%)!important; + } + } + >tbody { + >tr:nth-of-type(2n) { + background: lighten($color-primary-pale, 6%); + &:hover { + background: lighten($color-primary-pale, 6%)!important; + } + } + >tr:hover { + background: $color-panel!important; + } + } + td { + vertical-align: middle!important; + } +} + +input:not([type="submit"]):not([type="button"]):not([type="reset"]):not([class^="ant-input"]), select { + width: 100%; + padding: 6px 12px; + background: lighten($color-primary-pale, 3%) !important; + //background: $color-pale!important; + //border: 1.5px solid lighten($color-primary-dim, 10%)!important; + border: none!important; + border-radius: 5px!important; + color: darken($color-primary, 10%); + box-shadow: none!important; +} + +input.file-caption-name { + background: none!important; +} + +.input-group-addon { + background: lighten($color-primary-pale, 5%)!important; + border: none!important; + &:hover { + background: $color-primary-pale!important; + } +} + +.select2-selection.select2-selection--multiple, +.select2-dropdown.select2-dropdown--below { + border: none!important; +} + +::-webkit-input-placeholder { + color: lighten($color-primary, 50%)!important; + //color: $color-dim!important; +} + +// 复选框 +.cbx-active { + border: 2px solid $color-primary-dim!important; + box-shadow: none!important; + border-radius: 0; + .cbx-icon { + background: lighten($color-primary-pale, 5%); + i { + color: $color-primary-vivid!important; + } + } +} + +// Switch +label.btn.btn-default { + box-shadow: none!important; + &.active { + background: darken($color-primary-vivid, 10%); + color: $color-primary-pale; + } +} + +.props-main-box, .props-detail-box { + position: static!important; + width: 100%!important; +} + +.tab-content { + min-height: 350px; +} + +.goods-index { + td:last-child { + font-size: 0; + .btn { + padding: 10px; + border-radius: 0; + } + } +} + +ul.pagination { + li { + a, span { + border: none!important; + margin: 0 5px; + border-radius: 100%!important; + } + &.active { + a { + background: $color-primary-vivid; + //box-shadow: 0 0 20px 2px rgba(0, 0, 0, 0.2)!important; + &:hover { + background: darken($color-primary-vivid, 10%); + } + } + } + } +} + +.after-sale-index .panel, .panel-default { + position: relative; + min-height: 75vh; +} + +$width: 95px; + +.rc-switch { + width: $width!important; + margin-bottom: 10px; + &[aria-checked="false"] { + background: $color-info; + } + &[aria-checked="true"] { + background: $color-success; + } +} + +.rc-switch-checked:after { + left: $width - 22px!important; +} + +.empty { + position: absolute; + display: block; + width: 100%; + left: 50%; + top: 60%; + transform: translate(-50%, -50%); + text-align: center; + font-size: 2em; +} + +.kv-page-summary-container td { + padding: 0!important; +} + +table.table.table-striped.table-bordered.detail-view { + &, tbody, tr, th, td { + border: none!important; + } + th { + padding-right: 50px; + white-space: nowrap; + } +} \ No newline at end of file diff --git a/backend/web/src/utils/ajax.js b/backend/web/src/utils/ajax.js new file mode 100644 index 0000000..cf62be7 --- /dev/null +++ b/backend/web/src/utils/ajax.js @@ -0,0 +1,76 @@ +import axios from 'axios' +import {message} from 'antd'; + +// 请求拦截器 +axios.interceptors.request.use(config => ({ + ...config, + data: { + ...config.data, + '_csrf-api': window.csrfToken + } +})); + +// 响应拦截器 +axios.interceptors.response.use(function (response) { + const rawData = response && response.data; + let data = null; + const errorText = '服务器返回的数据格式不正确'; + + if (rawData && typeof response.data === 'object') { + // 若返回的数据是对象或者数组 + data = rawData; + } else if (typeof response.data === 'string') { + // 若返回的数据是字符串,尝试将其按 JSON 格式转为对象 + try { + data = JSON.parse(rawData); + } catch (e) { + message.error(errorText); + return Promise.reject(errorText); + } + } else { + message.error(errorText); + return Promise.reject(errorText); + } + + if (data) { + const {status, info} = response.data; + + if (status === undefined || status === true || status === 1) { + return response.data; + } else if (info) { + const errorText = info; + message.error(errorText); + console.error(errorText); + return Promise.reject(errorText) + } else { + const errorText = `操作失败`; + message.error(errorText); + console.error(errorText); + return Promise.reject(errorText) + } + } else if (response) { + return response; + } else { + return Promise.reject(`服务器无响应`); + } +}, function (error) { + let errorText = ''; + + if (error && error.response && error.response.status === 500) { + if (error.response.data) { + errorText = error.response.data.message || error.response.data; + } else { + errorText = error.response.message + } + } else if (error.response && error.response.status) { + errorText = `请求出错:${error.response.statusText}(${error.response.status})` + } else if (error.message) { + errorText = error.message + } else { + errorText = error + } + + errorText && message.error(errorText); + error && console.error(error); + return Promise.reject(errorText); +}); \ No newline at end of file diff --git a/backend/web/webpack.config.js b/backend/web/webpack.config.js new file mode 100644 index 0000000..8538926 --- /dev/null +++ b/backend/web/webpack.config.js @@ -0,0 +1,84 @@ +const path = require('path'); +const webpack = require('webpack'); +const HtmlWebpackPlugin = require('html-webpack-plugin'); +const CleanWebpackPlugin = require('clean-webpack-plugin'); + +const entryList = { + dashboard: './src/dashboard/index.js', + style: './src/styles/index.scss', + login_style: './src/styles/login.scss', + order_detail: './src/detail-display/index.js', + sku: './src/sku/index.js', + mini_program_management: './src/mini-program-management/index.js', + spread: './src/spread/index.js', + sku_item: './src/sku-item/index.js', + 'custom-menu': './src/custom-menu/index.js', + sku_for_activity: './src/sku-for-activity/index.js', +}; + +const needIconfontEntries = ['style', 'login_style']; + +module.exports = function (env, argv) { + const pathToClean = argv.module ? `${argv.module}.*.js` : 'custom'; + const entry = argv.module ? {[argv.module]: entryList[argv.module]} : entryList; + const htmlWebpackPluginList = Object.keys(entry).map(entryItem => new HtmlWebpackPlugin({ + filename: `${entryItem}.html`, + chunks: [entryItem], + template: needIconfontEntries.includes(entryItem) ? 'src/iconfont.html' : 'src/import.html' + })); + + return { + entry, + output: { + filename: '[name].[chunkhash].js', + path: path.resolve(__dirname, 'custom'), + publicPath: '/custom/' + }, + module: { + rules: [ + { + test: /\.js$/, + exclude: /(node_modules|bower_components)/, + use: { + loader: 'babel-loader' + } + }, + { + test: /\.scss$/, + use: ["style-loader", "css-loader", "resolve-url-loader", "sass-loader?sourceMap", + { + loader: 'sass-resources-loader', + options: { + resources: './src/styles/variables.scss' + } + }] + }, + { + test: /\.css$/, + use: ["style-loader", "css-loader"] + }, + { + test: /\.(png|jpg|gif|eot|svg|ttf|woff2?)(\?.*)?$/i, + use: [ + { + loader: 'url-loader', + options: { + limit: 10000 + } + }, + 'file-loader' + ] + } + ] + }, + plugins: [ + new CleanWebpackPlugin({ + cleanOnceBeforeBuildPatterns: [pathToClean] + }), + new webpack.DefinePlugin({ + ENV_DEV: JSON.stringify(argv.mode === 'development') + }), + ...htmlWebpackPluginList, + ] + }; +}; \ No newline at end of file diff --git a/composer.json b/composer.json index 9197a72..45dd281 100755 --- a/composer.json +++ b/composer.json @@ -23,7 +23,8 @@ "kartik-v/yii2-widget-depdrop": "dev-master", "antkaz/yii2-vue": "dev-master", "xj/yii2-babel": "dev-master", - "overtrue/wechat": "~4.0" + "overtrue/wechat": "~4.0", + "ext-json": "*" }, "repositories": { "asset_packagist": { diff --git a/console/controllers/MenuController.php b/console/controllers/MenuController.php new file mode 100644 index 0000000..5e95ebf --- /dev/null +++ b/console/controllers/MenuController.php @@ -0,0 +1,35 @@ +security->generateRandomString(8); + $security = Yii::createObject("yii\base\Security"); + $n = 0; + for ($i = 1; $i < 1000; $i++) { + //$key = $security->generateRandomString(8); + $key = rand(10000000, 99999999); + //$key = strtolower($key); + //$key = str_replace(['_', '-'], 'a', $key); + $ret[$key] += 1; + } + + $this->stdout("duplicate data:\n", Console::FG_RED); + foreach ($ret as $k => $v) { + if ($v > 1) { + echo "$k $v\n"; + } + } + } +} diff --git a/vendor/iron/generators/crud/default/controller.php b/vendor/iron/generators/crud/default/controller.php index 7afcb98..7b8ba7b 100644 --- a/vendor/iron/generators/crud/default/controller.php +++ b/vendor/iron/generators/crud/default/controller.php @@ -39,6 +39,7 @@ use yii\data\ActiveDataProvider; use baseControllerClass, '\\') ?>; use yii\web\NotFoundHttpException; use yii\filters\VerbFilter; +use iron\widget\Excel; /** * implements the CRUD actions for model. @@ -190,7 +191,7 @@ if (count($pks) === 1) { } else { $dataProvider = $searchModel->search($params); } - \iron\widget\Excel::export([ + Excel::export([ 'models' => $dataProvider->getModels(), 'format' => 'Xlsx', 'asAttachment' => true, diff --git a/vendor/iron/generators/model/default/model.php b/vendor/iron/generators/model/default/model.php index f1ec568..0107b4f 100644 --- a/vendor/iron/generators/model/default/model.php +++ b/vendor/iron/generators/model/default/model.php @@ -112,7 +112,7 @@ class extends baseClass, '\\') . { return [ [ - 'class' => TimestampBehavior::className(), + 'class' => TimestampBehavior::class, 'createdAtAttribute' => 'created_at', 'updatedAtAttribute' => 'updated_at', 'value' => function() { diff --git a/vendor/iron/grid/GridView.php b/vendor/iron/grid/GridView.php index a26eaef..bcecfa3 100644 --- a/vendor/iron/grid/GridView.php +++ b/vendor/iron/grid/GridView.php @@ -216,6 +216,9 @@ class GridView extends BaseListView public $layout = <<< HTML
+
+ {filter} +
{batch} @@ -226,9 +229,9 @@ class GridView extends BaseListView {content}
-
- {filter} -
+ + +
@@ -316,6 +319,7 @@ HTML; $this->registerIcheckJs(); $this->registerConfirmJs(); $this->registerExportJs(); + $this->registerCss(); parent::run(); } @@ -743,4 +747,17 @@ SCRIPT; return Html::tag('div', $this->content, ['class' => 'btn-group']); } + /** + * 注册css + */ + protected function registerCss() + { + $css =<<getView()->registerCss($css); + } + }