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;
])
?>
+
+ = $form->field($model, 'name', [
+ "template" => "{input}{error}",
+ "inputOptions" => [
+ "placeholder" => "规格名称",
+ "class" => "form-control",
+ ],
+ "errorOptions" => [
+ "class" => "error-tips"
+ ]
+ ])
+ ?>
+
= $form->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;
])
?>
+
+ = $form->field($model, 'name', [
+ "template" => "{input}{error}",
+ "inputOptions" => [
+ "placeholder" => "品牌名",
+ "class" => "form-control",
+ ],
+ "errorOptions" => [
+ "class" => "error-tips"
+ ]
+ ])
+ ?>
+
= $form->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;
= $form->field($model, 'name')->textInput(['maxlength' => true]) ?>
- = $form->field($model, 'pid')->dropDownList(array_merge([0 => '一级分类'], Category::modelColumn())) ?>
+ = $form->field($model, 'pid')->dropDownList(Category::modelColumn($model->id)) ?>
= $form->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;
])
?>
+
+ = $form->field($model, 'name', [
+ "template" => "{input}{error}",
+ "inputOptions" => [
+ "placeholder" => "类别名称",
+ "class" => "form-control",
+ ],
+ "errorOptions" => [
+ "class" => "error-tips"
+ ]
+ ])
+ ?>
+
= $form->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
]);
?>
-
+
+
- = Html::submitButton('保存', ['class' => 'btn btn-success']) ?>
- = Html::a('返回', ['index'], ['class' => 'btn btn-info']) ?>
+ = Html::submitButton('', ['class' => 'btn-float btn btn-success']) ?>
+ = Html::a('', ['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 //判断是否能够修改 */
?>
-= $form->field($model, 'cat_id')->dropDownList(Category::modelColumn(), ['prompt' => '请选择', 'disabled' => $judgeGoodsCategory]) ?>
+
+
+ = $form->field($model, 'name')->textInput(['maxlength' => true]) ?>
-= $form->field($model, 'brand_id')->dropDownList(Brand::modelColumn(), ['prompt' => '请选择']) ?>
+ = $form->field($model, 'sn')->textInput(['maxlength' => true]) ?>
-= $form->field($model, 'shop_cat_id')->dropDownList(ShopCategory::modelColumn(), ['prompt' => '请选择']) ?>
+ = $form->field($model, 'code')->textInput(['maxlength' => true]) ?>
-= $form->field($model, 'name')->textInput(['maxlength' => true]) ?>
+ = $form->field($model, 'market_price')->textInput() ?>
-= $form->field($model, 'sn')->textInput(['maxlength' => true]) ?>
+ = $form->field($model, 'price')->textInput() ?>
+
-= $form->field($model, 'code')->textInput(['maxlength' => true]) ?>
+
+ = $form->field($model, 'cat_id')->dropDownList(Category::modelColumn(0,0), ['prompt' => '请选择', 'disabled' => $judgeGoodsCategory]) ?>
-= $form->field($model, 'supplier_id')->dropDownList(Supplier::modelColumn(), ['prompt' => '请选择']) ?>
+ = $form->field($model, 'brand_id')->dropDownList(Brand::modelColumn(0,0), ['prompt' => '请选择']) ?>
-= $form->field($model, 'weight')->textInput() ?>
+ = $form->field($model, 'shop_cat_id')->dropDownList(ShopCategory::modelColumn(), ['prompt' => '请选择']) ?>
-= $form->field($model, 'length')->textInput() ?>
+ = $form->field($model, 'supplier_id')->dropDownList(Supplier::modelColumn(), ['prompt' => '请选择']) ?>
-= $form->field($model, 'width')->textInput() ?>
+ = $form->field($model, 'is_sale')->radioList(Goods::$isSale) ?>
+
-= $form->field($model, 'height')->textInput() ?>
+
+ = $form->field($model, 'weight')->textInput() ?>
-= $form->field($model, 'diameter')->textInput() ?>
+ = $form->field($model, 'length')->textInput() ?>
-= $form->field($model, 'unit')->textInput(['maxlength' => true]) ?>
+ = $form->field($model, 'width')->textInput() ?>
-= $form->field($model, 'limit_count')->textInput() ?>
+ = $form->field($model, 'height')->textInput() ?>
-= $form->field($model, 'stock')->textInput() ?>
+ = $form->field($model, 'diameter')->textInput() ?>
+
-= $form->field($model, 'stock_warn')->textInput() ?>
+
+ = $form->field($model, 'sort_order')->textInput() ?>
-= $form->field($model, 'market_price')->textInput() ?>
+ = $form->field($model, 'bouns_points')->textInput() ?>
-= $form->field($model, 'price')->textInput() ?>
+ = $form->field($model, 'experience_points')->textInput() ?>
-= $form->field($model, 'brief')->textInput(['maxlength' => true]) ?>
+ = $form->field($model, 'unit')->textInput(['maxlength' => true]) ?>
-= $form->field($model, 'is_sale')->radioList(Goods::$isSale) ?>
+ = $form->field($model, 'limit_count')->textInput() ?>
+
-= $form->field($model, 'sort_order')->textInput() ?>
+
+ = $form->field($model, 'stock')->textInput()->label('库存(-1为不限制)') ?>
-= $form->field($model, 'bouns_points')->textInput() ?>
+ = $form->field($model, 'stock_warn')->textInput(['placeholder' => '低于该值警告库存不足']) ?>
+
-= $form->field($model, 'experience_points')->textInput() ?>
-
-
- = Html::submitButton('保存', ['class' => 'btn btn-success']) ?>
- = Html::a('返回', ['index'], ['class' => 'btn btn-info']) ?>
-
+= $form->field($model, 'brief')->textInput(['maxlength' => true]) ?>
-
-
-
-
- = Html::submitButton('保存', ['class' => '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
]);
?>
+
+
- = Html::submitButton('保存', ['class' => 'btn btn-success']) ?>
- = Html::a('返回', ['index'], ['class' => 'btn btn-info']) ?>
+ = Html::submitButton('', ['class' => 'btn-float btn btn-success']) ?>
+ = Html::a('', ['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;
= $form->field($model, 'keywords')->textInput(['maxlength' => true]) ?>
- = $form->field($model, 'pid')->dropDownList(array_merge([0 => '一级分类'], ShopCategory::modelColumn())) ?>
+ = $form->field($model, 'pid')->dropDownList(ShopCategory::modelColumn($model->id)) ?>
= $form->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;
])
?>
+
+ = $form->field($model, 'name', [
+ "template" => "{input}{error}",
+ "inputOptions" => [
+ "placeholder" => "类别名称",
+ "class" => "form-control",
+ ],
+ "errorOptions" => [
+ "class" => "error-tips"
+ ]
+ ])
+ ?>
+
= $form->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;
])
?>
+
+ = $form->field($model, 'name', [
+ "template" => "{input}{error}",
+ "inputOptions" => [
+ "placeholder" => "供应商名称",
+ "class" => "form-control",
+ ],
+ "errorOptions" => [
+ "class" => "error-tips"
+ ]
+ ])
+ ?>
+
+
+ = $form->field($model, 'phone', [
+ "template" => "{input}{error}",
+ "inputOptions" => [
+ "placeholder" => "手机号码",
+ "class" => "form-control",
+ ],
+ "errorOptions" => [
+ "class" => "error-tips"
+ ]
+ ])
+ ?>
+
= $form->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 @@
+
+
+
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,
+ ]);
+?>
+
+
+ = $form->field($model, 'id', [
+ "template" => "{input}{error}",
+ "inputOptions" => [
+ "placeholder" => "检索ID",
+ "class" => "form-control",
+ ],
+ "errorOptions" => [
+ "class" => "error-tips"
+ ]
+ ])
+ ?>
+
+
+ = $form->field($model, 'after_sale_sn', [
+ "template" => "{input}{error}",
+ "inputOptions" => [
+ "placeholder" => "售后单号",
+ "class" => "form-control",
+ ],
+ "errorOptions" => [
+ "class" => "error-tips"
+ ]
+ ])
+ ?>
+
+
+ = $form->field($model, "created_at_range", [
+ "template" => "{input}{error}",
+ "inputOptions" => [
+ "placeholder" => "创建时间",
+ ],
+ "errorOptions" => [
+ "class" => "error-tips"
+ ]
+ ])->widget(DateRangePicker::className());
+ ?>
+
+
+ = Html::submitButton('', ['class' => 'btn btn-default']) ?>
+ = Html::resetButton('', ['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;
+?>
+
+
+ = $this->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;
+?>
+
+
+ = GridView::widget([
+ 'dataProvider' => $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 ';
+?>
+
+
+ = $this->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);
+?>
+
+
+
+ = Html::a('返回列表', ['index'], ['class' => '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',
+ ],
+ ]
+ );
+ }
+ ?>
+
+
+ = DetailView::widget([
+ 'model' => $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 @@
+
+
+
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,
+ ]);
+?>
+
+
+ = $form->field($model, 'id', [
+ "template" => "{input}{error}",
+ "inputOptions" => [
+ "placeholder" => "检索ID",
+ "class" => "form-control",
+ ],
+ "errorOptions" => [
+ "class" => "error-tips"
+ ]
+ ])
+ ?>
+
+
+ = $form->field($model, "created_at_range", [
+ "template" => "{input}{error}",
+ "inputOptions" => [
+ "placeholder" => "创建时间",
+ ],
+ "errorOptions" => [
+ "class" => "error-tips"
+ ]
+ ])->widget(DateRangePicker::className());
+ ?>
+
+
+ = Html::submitButton('', ['class' => 'btn btn-default']) ?>
+ = Html::resetButton('', ['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;
+?>
+
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;
+?>
+
+
+ = GridView::widget([
+ 'dataProvider' => $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 ';
+?>
+
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);
+?>
+
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;
])
?>
+
+ = $form->field($model, 'name', [
+ "template" => "{input}{error}",
+ "inputOptions" => [
+ "placeholder" => "名称",
+ "class" => "form-control",
+ ],
+ "errorOptions" => [
+ "class" => "error-tips"
+ ]
+ ])
+ ?>
+
= $form->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 (
+
+
+
+
+
+
+
+ )
+ }
+}
+
+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 (
+
+
+
+
+ )
+ }
+}
+
+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 (
+
+
+
+
+
+
+
+
+
+
+ )
+ }
+}
+
+const SortableItem = SortableElement(({value}) => {
+ return (
+
+
+ {value}
+
+ );
+});
+
+const SortableList = SortableContainer(({items}) => {
+ return (
+
+ {items.map((value, index) => (
+
+ ))}
+
+ );
+});
+
+class SortableComponent extends Component {
+ state = {
+ items: data,
+ };
+
+ onSortEnd = ({oldIndex, newIndex}) => {
+ const {items} = this.state;
+
+ this.setState({
+ items: arrayMove(items, oldIndex, newIndex),
+ });
+ };
+
+ onChangeTitle = (index, event) => {
+ let {items} = this.state;
+ items[index].title = event.target.value;
+ this.setState({
+ items
+ })
+ };
+
+ onChangeType = (index, newType) => {
+ let {items} = this.state;
+ items[index].type = newType.value;
+ this.setState({
+ items
+ })
+ };
+
+ submit = () => {
+ const {items} = this.state;
+ axios.post(location.href, {
+ '_csrf-api': csrf,
+ data: items
+ }).then((res) => {
+ if (res.data === 1) {
+ this.back();
+ }
+ })
+ };
+
+ back = () => {
+ history.go(-1)
+ };
+
+ render() {
+ const {items} = this.state;
+
+ const itemsView = items.map((item, index) => (
+
+ ));
+
+ return (
+
+
+
+
+
+ );
+ }
+}
\ No newline at end of file
diff --git a/backend/web/src/js/amazing-creator/store.js b/backend/web/src/js/amazing-creator/store.js
new file mode 100644
index 0000000..85a5f70
--- /dev/null
+++ b/backend/web/src/js/amazing-creator/store.js
@@ -0,0 +1,68 @@
+import {observable, action, configure} from "mobx";
+
+configure({
+ enforceActions: 'always'
+});
+
+class Store {
+ @observable currentId = 3;
+
+ @observable layout = [
+ {
+ i: '1',
+ x: 0,
+ y: 0,
+ w: 12,
+ h: 3
+ },
+ {
+ i: '2',
+ x: 1,
+ y: 2,
+ w: 12,
+ h: 3
+ },
+ {
+ i: '3',
+ x: 2,
+ y: 3,
+ w: 12,
+ h: 3
+ },
+ ];
+
+ @observable attrs = {
+ 1: {
+ component: 'Swiper',
+ },
+ 2: {
+ component: 'GoodsList',
+ type: 'grid',
+ title: '新品推荐',
+ data: 'best'
+ },
+ 3: {
+ component: 'GoodsList',
+ type: 'list',
+ title: '火爆热销',
+ data: 'hot'
+ }
+ };
+
+ @action.bound changeLayout(layout) {
+ this.layout = layout;
+ }
+
+ @action.bound add(attr) {
+ this.layout.push({
+ i: (++this.currentId).toString(),
+ x: 2,
+ y: 3,
+ w: 12,
+ h: 3
+ });
+ this.attrs[this.currentId] = attr;
+ }
+}
+
+export default new Store()
\ No newline at end of file
diff --git a/backend/web/src/sku-item/index.js b/backend/web/src/sku-item/index.js
new file mode 100644
index 0000000..acb0188
--- /dev/null
+++ b/backend/web/src/sku-item/index.js
@@ -0,0 +1,313 @@
+import React, {useState, useEffect} from 'react'
+import ReactDOM from 'react-dom'
+import {Table, Select, Input, InputNumber, Button, message, Modal, Spin, LocaleProvider} from "antd";
+import styled from 'styled-components'
+import produce from 'immer'
+import axios from 'axios'
+import zhCN from 'antd/lib/locale-provider/zh_CN';
+import queryString from 'qs';
+import deepEqual from 'fast-deep-equal'
+import deepClone from 'clone'
+import '../utils/ajax'
+
+const {sku: defaultSku, attributes: defaultAttributes} = window;
+
+const Option = Select.Option;
+
+const Fragment = React.Fragment;
+
+const additionalCol = [
+ {
+ key: 'price',
+ title: '价格',
+ precision: 2,
+ min: 0
+ },
+ {
+ key: 'stock',
+ title: '库存(-1 为不限库存)',
+ precision: 0,
+ min: -1
+ },
+ {
+ key: 'weight',
+ title: '重量(kg)',
+ precision: 2,
+ min: 0
+ }
+];
+
+function buildInputCol(data, onChange) {
+ return data.map(({key, title, ...others}) => ({
+ title,
+ dataIndex: key,
+ key,
+ align: 'center',
+ render: (text, _, index) => (
+
onChange(index, key, value)}
+ />
+ )
+ }))
+}
+
+function SkuTable({ skuData, attributes, type, onChangeItem, onDeleteItem }) {
+ function setSkuItem(skuIndex, skuItemIndex, skuItemValue) {
+ const newValue = Object.assign(skuData[skuIndex].value, {
+ [skuItemIndex]: skuItemValue
+ });
+
+ onChangeItem(skuIndex, 'value', newValue)
+ }
+
+ const dataSource = skuData.map(({ id, value, price, stock, weight }, index) => {
+ const attrList = type === 'select' ? value.reduce((accumulator, currentValue, currentIndex) => ({
+ ...accumulator,
+ [currentIndex]: currentValue
+ }), {}) : { value };
+
+ return {
+ key: index,
+ ...attrList,
+ price,
+ stock,
+ weight
+ }
+ });
+
+ const skuColumns = type === 'select' ? attributes.map(({id, name, attrValue}, index) => ({
+ title: name,
+ dataIndex: index,
+ key: id,
+ align: 'center',
+ render: (value, _, rowIndex) => (
+
+ )
+ })) : [{
+ 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 = ltrim($generator->baseControllerClass, '\\') ?>;
use yii\web\NotFoundHttpException;
use yii\filters\VerbFilter;
+use iron\widget\Excel;
/**
* = $controllerClass ?> implements the CRUD actions for = $modelClass ?> 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 = $className ?> extends = '\\' . ltrim($generator->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);
+ }
+
}