Browse Source

feat:创建新分支wechat_public_account,合并order分支

wechat_public_accounts
linyaostalker 5 years ago
parent
commit
83d6afae0d
  1. 76
      backend/controllers/ConfigController.php
  2. 61
      backend/controllers/SiteController.php
  3. 166
      backend/logic/PermissionManager.php
  4. 106
      backend/modules/file/logic/file/FileManager.php
  5. 6
      backend/modules/file/models/ars/File.php
  6. 6
      backend/modules/file/models/ars/TemFile.php
  7. 21
      backend/modules/goods/controllers/AttributeController.php
  8. 3
      backend/modules/goods/controllers/BrandController.php
  9. 90
      backend/modules/goods/controllers/CategoryController.php
  10. 97
      backend/modules/goods/controllers/GoodsController.php
  11. 95
      backend/modules/goods/controllers/ShopCategoryController.php
  12. 3
      backend/modules/goods/controllers/SupplierController.php
  13. 244
      backend/modules/goods/logic/goods/GoodsManager.php
  14. 22
      backend/modules/goods/migrations/m191211_060934_update_column_limit_count_in_table_atg_goods.php
  15. 22
      backend/modules/goods/migrations/m191211_092335_update_column_sort_order_in_table_atg_goods.php
  16. 41
      backend/modules/goods/migrations/m191217_091658_update_column_is_manual_in_table_atg_goods_sku.php
  17. 26
      backend/modules/goods/migrations/m191217_092101_add_column_sku_image_in_table_atg_goods_sku.php
  18. 5
      backend/modules/goods/models/ars/Attribute.php
  19. 24
      backend/modules/goods/models/ars/Category.php
  20. 10
      backend/modules/goods/models/ars/Goods.php
  21. 8
      backend/modules/goods/models/ars/GoodsSku.php
  22. 28
      backend/modules/goods/models/ars/ShopCategory.php
  23. 12
      backend/modules/goods/models/searchs/CategorySearch.php
  24. 27
      backend/modules/goods/models/searchs/GoodsSearch.php
  25. 13
      backend/modules/goods/views/attribute/_search.php
  26. 13
      backend/modules/goods/views/brand/_search.php
  27. 4
      backend/modules/goods/views/category/_form.php
  28. 13
      backend/modules/goods/views/category/_search.php
  29. 4
      backend/modules/goods/views/goods/_form.php
  30. 4
      backend/modules/goods/views/goods/_search.php
  31. 44
      backend/modules/goods/views/goods/create.php
  32. 71
      backend/modules/goods/views/goods/goods.php
  33. 6
      backend/modules/goods/views/goods/new_editor.php
  34. 4
      backend/modules/goods/views/goods/picture.php
  35. 41
      backend/modules/goods/views/goods/update.php
  36. 4
      backend/modules/goods/views/shop-category/_form.php
  37. 13
      backend/modules/goods/views/shop-category/_search.php
  38. 26
      backend/modules/goods/views/supplier/_search.php
  39. 175
      backend/modules/shop/controllers/AfterSaleController.php
  40. 179
      backend/modules/shop/controllers/CommentController.php
  41. 122
      backend/modules/shop/controllers/ExpressTemplateController.php
  42. 17
      backend/modules/shop/controllers/OrderController.php
  43. 7
      backend/modules/shop/controllers/TakingSiteController.php
  44. 139
      backend/modules/shop/logic/ShopManager.php
  45. 68
      backend/modules/shop/migrations/m191209_080245_update_table_after_sale.php
  46. 20
      backend/modules/shop/migrations/m191217_013625_add_column_sku_value_in_table_ats_cart.php
  47. 20
      backend/modules/shop/migrations/m191217_021455_update_column_star_in_table_ats_comment.php
  48. 98
      backend/modules/shop/models/ars/AfterSale.php
  49. 4
      backend/modules/shop/models/ars/Cart.php
  50. 13
      backend/modules/shop/models/ars/Comment.php
  51. 6
      backend/modules/shop/models/ars/OrderGoods.php
  52. 188
      backend/modules/shop/models/searchs/AfterSaleSearch.php
  53. 186
      backend/modules/shop/models/searchs/CommentSearch.php
  54. 58
      backend/modules/shop/views/after-sale/_form.php
  55. 62
      backend/modules/shop/views/after-sale/_search.php
  56. 18
      backend/modules/shop/views/after-sale/create.php
  57. 31
      backend/modules/shop/views/after-sale/index.php
  58. 19
      backend/modules/shop/views/after-sale/update.php
  59. 108
      backend/modules/shop/views/after-sale/view.php
  60. 32
      backend/modules/shop/views/comment/_form.php
  61. 49
      backend/modules/shop/views/comment/_search.php
  62. 18
      backend/modules/shop/views/comment/create.php
  63. 31
      backend/modules/shop/views/comment/index.php
  64. 19
      backend/modules/shop/views/comment/update.php
  65. 47
      backend/modules/shop/views/comment/view.php
  66. 9
      backend/modules/shop/views/express-template/express_area_view.php
  67. 13
      backend/modules/shop/views/taking-site/_search.php
  68. 4
      backend/modules/shop/views/taking-site/create.php
  69. 27
      backend/views/config/index.php
  70. 52
      backend/views/layouts/sidebar.php
  71. 17
      backend/web/.babelrc
  72. 26
      backend/web/.eslintrc.json
  73. 11
      backend/web/.gitignore
  74. 89
      backend/web/package.json
  75. 1
      backend/web/src/iconfont.html
  76. 4
      backend/web/src/import.html
  77. 36
      backend/web/src/js/amazing-creator/GridEditor.js
  78. 101
      backend/web/src/js/amazing-creator/Module.js
  79. 106
      backend/web/src/js/amazing-creator/Previewer.js
  80. 16
      backend/web/src/js/amazing-creator/PropsEditor.js
  81. 29
      backend/web/src/js/amazing-creator/index.js
  82. 67
      backend/web/src/js/amazing-creator/modules/GoodsList.js
  83. 14
      backend/web/src/js/amazing-creator/modules/Swiper.js
  84. 150
      backend/web/src/js/amazing-creator/old.js
  85. 68
      backend/web/src/js/amazing-creator/store.js
  86. 313
      backend/web/src/sku-item/index.js
  87. 74
      backend/web/src/sku/SelectCell.js
  88. 157
      backend/web/src/sku/index.js
  89. 83
      backend/web/src/styles/amazing-creator.scss
  90. 90
      backend/web/src/styles/content-header.scss
  91. 180
      backend/web/src/styles/dashboard.scss
  92. 183
      backend/web/src/styles/global.scss
  93. 128
      backend/web/src/styles/header.scss
  94. 7
      backend/web/src/styles/index.scss
  95. 193
      backend/web/src/styles/login.scss
  96. 201
      backend/web/src/styles/sidebar.scss
  97. 30
      backend/web/src/styles/variables.scss
  98. 301
      backend/web/src/styles/widget.scss
  99. 76
      backend/web/src/utils/ajax.js
  100. 84
      backend/web/webpack.config.js

76
backend/controllers/ConfigController.php

@ -1,76 +0,0 @@
<?php
namespace backend\controllers;
use Yii;
use backend\models\ars\Category;
use yii\web\Controller;
use yii\web\NotFoundHttpException;
use yii\filters\VerbFilter;
/**
* CategoryController implements the CRUD actions for Category model.
*/
class ConfigController extends Controller
{
/**
* {@inheritdoc}
*/
public function behaviors()
{
return [
'verbs' => [
'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.');
}
}

61
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();
}
}

166
backend/logic/PermissionManager.php

@ -0,0 +1,166 @@
<?php
namespace backend\logic;
use ReflectionClass;
use ReflectionException;
use ReflectionMethod;
use yii;
class PermissionManager
{
/**
* 获取app的已做标记控制器的权限
* DESCRIBE 控制器描述,只有控制器的注释添加了该描述,才会自动匹配该控制器;使用例子:@DESCRIBE {controllerDescribe} DESCRIBE
* ACTION 方法描述,只有控制器下action方法的注释添加了该描述,才会自动匹配该action方法;使用例子:@ACTION {actionDescribe} ACTION
* 匹配控制器中actions的方法,需要添加id参数并且在标明含义,例子:
* '{actionName}' => [
* '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('/(?<![A-Z])[A-Z]/', ' \0', $baseName));
$id = ltrim(str_replace(' ', '-', $name), '-');
$className = $nameSpace . $baseName . 'Controller';
$controller = Yii::$app->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('/(?<![A-Z])[A-Z]/', ' \0', $baseName));
$id = $module->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('/(?<![A-Z])[A-Z]/', ' \0', substr($actionName, 6)));
$id = $prefix . ltrim(str_replace(' ', '-', $name), '-');
$permission[$controllerDescribe[0]][$actionDescribe[0]] = $id;
}
}
}
return $permission;
}
}

106
backend/modules/file/logic/file/FileManager.php

@ -5,8 +5,11 @@ namespace backend\modules\file\logic\file;
use backend\modules\file\models\ars\File;
use backend\modules\file\models\ars\TemFile;
use Exception;
use yii\web\HttpException;
use yii;
use yii\web\ServerErrorHttpException;
use backend\modules\goods\logic\goods\GoodsManager;
class FileManager
{
@ -18,7 +21,7 @@ class FileManager
const TYPE_WORD = 4;//word文本
const TYPE_TXT = 5;//txt文本
private $extension = [
public static $extension = [
self::TYPE_IMAGE => ['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);
}
}

6
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 () {

6
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() {

21
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,

3
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,

90
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->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->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);
}
}

97
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);
}
}

95
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->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->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);
}
}

3
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,

244
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;
}
}

22
backend/modules/goods/migrations/m191211_060934_update_column_limit_count_in_table_atg_goods.php

@ -0,0 +1,22 @@
<?php
use yii\db\Migration;
/**
* Class m191211_060934_update_column_limit_count_in_table_atg_goods
*/
class m191211_060934_update_column_limit_count_in_table_atg_goods extends Migration
{
public function up()
{
$this->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;
}
}

22
backend/modules/goods/migrations/m191211_092335_update_column_sort_order_in_table_atg_goods.php

@ -0,0 +1,22 @@
<?php
use yii\db\Migration;
/**
* Class m191211_092335_update_column_sort_order_in_table_atg_goods
*/
class m191211_092335_update_column_sort_order_in_table_atg_goods extends Migration
{
public function up()
{
$this->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;
}
}

41
backend/modules/goods/migrations/m191217_091658_update_column_is_manual_in_table_atg_goods_sku.php

@ -0,0 +1,41 @@
<?php
use yii\db\Migration;
/**
* Class m191217_091658_update_column_is_manual_in_table_atg_goods_sku
*/
class m191217_091658_update_column_is_manual_in_table_atg_goods_sku extends Migration
{
/**
* {@inheritdoc}
*/
public function safeUp()
{
$this->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;
}
*/
}

26
backend/modules/goods/migrations/m191217_092101_add_column_sku_image_in_table_atg_goods_sku.php

@ -0,0 +1,26 @@
<?php
use yii\db\Migration;
/**
* Class m191217_092101_add_column_sku_image_in_table_atg_goods_sku
*/
class m191217_092101_add_column_sku_image_in_table_atg_goods_sku extends Migration
{
/**
* {@inheritdoc}
*/
public function safeUp()
{
$this->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;
}
}

5
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
}

24
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
}
}

10
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
}

8
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',
];
}

28
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
}
}

12
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',

27
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',

13
backend/modules/goods/views/attribute/_search.php

@ -29,6 +29,19 @@ use \blobt\widgets\DateRangePicker;
])
?>
</div>
<div class="col">
<?= $form->field($model, 'name', [
"template" => "{input}{error}",
"inputOptions" => [
"placeholder" => "规格名称",
"class" => "form-control",
],
"errorOptions" => [
"class" => "error-tips"
]
])
?>
</div>
<div class="col">
<?= $form->field($model, "created_at_range", [
"template" => "{input}{error}",

13
backend/modules/goods/views/brand/_search.php

@ -29,6 +29,19 @@ use \blobt\widgets\DateRangePicker;
])
?>
</div>
<div class="col">
<?= $form->field($model, 'name', [
"template" => "{input}{error}",
"inputOptions" => [
"placeholder" => "品牌名",
"class" => "form-control",
],
"errorOptions" => [
"class" => "error-tips"
]
])
?>
</div>
<div class="col">
<?= $form->field($model, "created_at_range", [
"template" => "{input}{error}",

4
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('类目图片') ?>

13
backend/modules/goods/views/category/_search.php

@ -29,6 +29,19 @@ use \blobt\widgets\DateRangePicker;
])
?>
</div>
<div class="col">
<?= $form->field($model, 'name', [
"template" => "{input}{error}",
"inputOptions" => [
"placeholder" => "类别名称",
"class" => "form-control",
],
"errorOptions" => [
"class" => "error-tips"
]
])
?>
</div>
<div class="col">
<?= $form->field($model, "created_at_range", [
"template" => "{input}{error}",

4
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('商品详情图') ?>

4
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' => '后台商品类别']);
?>
</div>
<div class="col">
@ -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' => '前端商品类别']);
?>
</div>
<div class="col">

44
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';
?>
<style>
.btn-success, .btn-float {
bottom: 120px;
}
.btn-success {
position: fixed;
display: block;
top: unset;
right: 50px;
bottom: 60px;
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 {
position: fixed;
display: block;
top: unset;
right: 50px;
bottom: 130px;
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;
}
</style>
<div class="goods-create">
<div class="goods-form">
<?php
$form = ActiveForm::begin(['options' => ['class' => 'container-fluid']]);
@ -66,13 +100,13 @@ Yii::$app->params['bsVersion'] = '4.x';
'encodeLabels' => false
]);
?>
</div>
</div>
<div class="form-group">
<?= Html::submitButton('保存', ['class' => 'btn btn-success']) ?>
<?= Html::a('返回', ['index'], ['class' => 'btn btn-info']) ?>
<?= Html::submitButton('<i class="fa fa-check"></i>', ['class' => 'btn-float btn btn-success']) ?>
<?= Html::a('<i class="fa fa-times"></i>', ['index'], ['class' => 'btn-float btn btn-info']) ?>
</div>
<?php ActiveForm::end(); ?>
</div>
</div>

71
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]) ?>
<style>
.form-group {
margin-right: 40px;
}
.goods-base-info {
display: flex;
}
</style>
<div class="goods-base-info">
<?= $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() ?>
</div>
<?= $form->field($model, 'code')->textInput(['maxlength' => true]) ?>
<div class="goods-base-info">
<?= $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) ?>
</div>
<?= $form->field($model, 'height')->textInput() ?>
<div class="goods-base-info">
<?= $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() ?>
</div>
<?= $form->field($model, 'stock_warn')->textInput() ?>
<div class="goods-base-info">
<?= $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() ?>
</div>
<?= $form->field($model, 'sort_order')->textInput() ?>
<div class="goods-base-info">
<?= $form->field($model, 'stock')->textInput()->label('库存(-1为不限制)') ?>
<?= $form->field($model, 'bouns_points')->textInput() ?>
<?= $form->field($model, 'stock_warn')->textInput(['placeholder' => '低于该值警告库存不足']) ?>
</div>
<?= $form->field($model, 'experience_points')->textInput() ?>
<div class="form-group">
<?= Html::submitButton('保存', ['class' => 'btn btn-success']) ?>
<?= Html::a('返回', ['index'], ['class' => 'btn btn-info']) ?>
</div>
<?= $form->field($model, 'brief')->textInput(['maxlength' => true]) ?>
<?php
$js =<<<JS
$(document).ready(function(){

6
backend/modules/goods/views/goods/new_editor.php

@ -21,11 +21,5 @@ use yii\helpers\Html;
]) ?>
</div>
</div>
<div class="form-group">
<?= Html::submitButton('保存', ['class' => 'btn btn-success']) ?>
</div>
</div>

4
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('商品详情图') ?>

41
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';
?>
<style>
.btn-success, .btn-float {
bottom: 120px;
}
.btn-success {
position: fixed;
display: block;
top: unset;
right: 50px;
bottom: 60px;
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 {
position: fixed;
display: block;
top: unset;
right: 50px;
bottom: 130px;
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;
}
</style>
<div class="goods-update">
<div class="goods-form">
<?php
@ -68,10 +103,12 @@ Yii::$app->params['bsVersion'] = '4.x';
'encodeLabels' => false
]);
?>
</div>
</div>
<div class="form-group">
<?= Html::submitButton('保存', ['class' => 'btn btn-success']) ?>
<?= Html::a('返回', ['index'], ['class' => 'btn btn-info']) ?>
<?= Html::submitButton('<i class="fa fa-check"></i>', ['class' => 'btn-float btn btn-success']) ?>
<?= Html::a('<i class="fa fa-times"></i>', ['index'], ['class' => 'btn-float btn btn-info']) ?>
</div>
<?php ActiveForm::end(); ?>

4
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('类目图片') ?>

13
backend/modules/goods/views/shop-category/_search.php

@ -29,6 +29,19 @@ use \blobt\widgets\DateRangePicker;
])
?>
</div>
<div class="col">
<?= $form->field($model, 'name', [
"template" => "{input}{error}",
"inputOptions" => [
"placeholder" => "类别名称",
"class" => "form-control",
],
"errorOptions" => [
"class" => "error-tips"
]
])
?>
</div>
<div class="col">
<?= $form->field($model, "created_at_range", [
"template" => "{input}{error}",

26
backend/modules/goods/views/supplier/_search.php

@ -29,6 +29,32 @@ use \blobt\widgets\DateRangePicker;
])
?>
</div>
<div class="col">
<?= $form->field($model, 'name', [
"template" => "{input}{error}",
"inputOptions" => [
"placeholder" => "供应商名称",
"class" => "form-control",
],
"errorOptions" => [
"class" => "error-tips"
]
])
?>
</div>
<div class="col">
<?= $form->field($model, 'phone', [
"template" => "{input}{error}",
"inputOptions" => [
"placeholder" => "手机号码",
"class" => "form-control",
],
"errorOptions" => [
"class" => "error-tips"
]
])
?>
</div>
<div class="col">
<?= $form->field($model, "created_at_range", [
"template" => "{input}{error}",

175
backend/modules/shop/controllers/AfterSaleController.php

@ -0,0 +1,175 @@
<?php
namespace backend\modules\shop\controllers;
use Yii;
use backend\modules\shop\models\ars\AfterSale;
use backend\modules\shop\models\searchs\AfterSaleSearch;
use yii\web\Controller;
use yii\web\NotFoundHttpException;
use yii\filters\VerbFilter;
use iron\widget\Excel;
use yii\web\Response;
/**
* AfterSaleController implements the CRUD actions for AfterSale model.
*/
class AfterSaleController extends Controller
{
/**
* {@inheritdoc}
*/
public function behaviors()
{
return [
'verbs' => [
'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);
}
}
}

179
backend/modules/shop/controllers/CommentController.php

@ -0,0 +1,179 @@
<?php
namespace backend\modules\shop\controllers;
use Yii;
use backend\modules\shop\models\ars\Comment;
use backend\modules\shop\models\searchs\CommentSearch;
use yii\web\Controller;
use yii\web\NotFoundHttpException;
use yii\filters\VerbFilter;
use iron\widget\Excel;
use yii\web\Response;
/**
* CommentController implements the CRUD actions for Comment model.
*/
class CommentController extends Controller
{
/**
* {@inheritdoc}
*/
public function behaviors()
{
return [
'verbs' => [
'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']);
}
}

122
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);

17
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 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,

7
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,

139
backend/modules/shop/logic/ShopManager.php

@ -0,0 +1,139 @@
<?php
namespace backend\modules\shop\logic;
use backend\modules\shop\models\ars\City;
use backend\modules\shop\models\ars\ExpressArea;
use backend\modules\shop\models\ars\Province;
use backend\modules\shop\models\ars\ExpressTemplate;
class ShopManager
{
//单位类型
const UNIT_TYPE_WEIGHT = 1; //重量
const UNIT_TYPE_MONEY = 2; //金额
const UNIT_TYPE_ITEM = 3; //件
const UNIT_TYPE_LENGTH = 4; //长度,宽度,高度,直径
/**
* @param $type
* @return int
* 比例转换
*/
public static function proportionalConversion($type)
{
switch ($type) {
case self::UNIT_TYPE_WEIGHT:
return 1000; //后台显示为kg,数据库保存为g
break;
case self::UNIT_TYPE_MONEY:
return 100; //后台显示为元,数据库保存为分
break;
case self::UNIT_TYPE_ITEM:
return 1; //后台显示和数据库都为件
break;
case self::UNIT_TYPE_LENGTH:
return 1; //后台和数据库都为毫米
break;
default:
return 1;
}
}
/**
* @param ExpressArea|$expressAreaModel
* @param ExpressTemplate|$expressTemplateModel
* 区域运费模板按比例转化数据
*/
public static function expressAreaScaleDate($expressAreaModel, $expressTemplateModel)
{
$expressAreaModel->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;
}
}

68
backend/modules/shop/migrations/m191209_080245_update_table_after_sale.php

@ -0,0 +1,68 @@
<?php
use yii\db\Migration;
/**
* Class m191209_080245_update_table_after_sale
*/
class m191209_080245_update_table_after_sale extends Migration
{
/**
* {@inheritdoc}
*/
public function up()
{
$this->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;
}
}

20
backend/modules/shop/migrations/m191217_013625_add_column_sku_value_in_table_ats_cart.php

@ -0,0 +1,20 @@
<?php
use yii\db\Migration;
/**
* Class m191217_013625_add_column_sku_value_in_table_ats_cart
*/
class m191217_013625_add_column_sku_value_in_table_ats_cart extends Migration
{
public function up()
{
$this->addColumn('ats_cart', 'sku_value', $this->string(120)->defaultValue('')->comment('商品sku'));
}
public function down()
{
$this->dropColumn('ats_cart', 'sku_value');
return true;
}
}

20
backend/modules/shop/migrations/m191217_021455_update_column_star_in_table_ats_comment.php

@ -0,0 +1,20 @@
<?php
use yii\db\Migration;
/**
* Class m191217_021455_update_column_star_in_table_ats_comment
*/
class m191217_021455_update_column_star_in_table_ats_comment extends Migration
{
public function up()
{
$this->dropColumn('ats_comment', 'star');
$this->addColumn('ats_comment', 'star', $this->integer(11)->notNull()->defaultValue("0")->comment('星级'));
}
public function down()
{
return true;
}
}

98
backend/modules/shop/models/ars/AfterSale.php

@ -2,32 +2,68 @@
namespace backend\modules\shop\models\ars;
use Yii;
use yii\behaviors\TimestampBehavior;
/**
* This is the model class for table "ats_after_sale".
*
* @property int $id
* @property int $operator_id 操作者
* @property int $user_id 用户id
* @property string $wx_refund_id 微信退款单号
* @property string $after_sale_sn 售后单号
* @property int $user_id 用户id
* @property int $order_goods_id 订单商品id
* @property int $count 退换货的商品数量
* @property int $amount 退货时实际退的金额
* @property int $type 类型
* @property int $reason 退换货理由
* @property int $count 退货的商品数量
* @property int $apply_at 申请时间
* @property int $dealt_at 处理时间
* @property int $finish_at 完成时间
* @property int $operator_id 操作者
* @property int $refund_type 退款类型:1:全额退款;2:部分退款
* @property string $description 描述
* @property string $take_shipping_sn 用户发货物流单号
* @property string $send_shipping_sn 换货商家发货的物流单号
* @property string $image 图片
* @property int $status 处理状态:0:未处理;1:已同意,待买家确认;2:用户已确认;3:已拒绝;4:退款成功;5:已取消;
* @property int $reason 退货理由
* @property string $remarks 店家备注
* @property int $applyed_at 申请时间
* @property int $dealed_at 处理时间
* @property int $finished_at 完成时间
* @property string $take_shipping_sn 用户发货物流单号
* @property int $refund_mode 退款方式:1:仅退款;2:退货退款;
*/
class AfterSale extends \yii\db\ActiveRecord
{
public $order_pay_amount; //订单支付金额
//退款类型
const REFUND_TYPE_ALL = 1; //全额退款
const REFUND_TYPE_PART = 2; //部分退款
//处理状态
const STATUS_UNTREATED = 0; //未处理
const STATUS_ACCEPT = 1; //已同意,待买家确认
const STATUS_CONFIRM = 2; //用户已确认
const STATUS_REJECT = 3; //已拒绝
const STATUS_FINISH = 4; //退款成功
const STATUS_CANCEL = 5; //已取消
//退款方式
const REFUND_MODE_MONEY = 1; //仅退款
const REFUND_MODE_MONEY_GOODS = 2; //退货退款
public static $refundType = [
self::REFUND_TYPE_ALL => '全额退款',
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']);
}
}

4
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',
];
}

13
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']);
}
}

6
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']);
}
}

188
backend/modules/shop/models/searchs/AfterSaleSearch.php

@ -0,0 +1,188 @@
<?php
namespace backend\modules\shop\models\searchs;
use yii\base\Model;
use yii\data\ActiveDataProvider;
use yii\helpers\ArrayHelper;
use backend\modules\shop\models\ars\AfterSale;
use backend\modules\shop\models\ars\OrderGoods;
/**
* AfterSaleSearch represents the model behind the search form of `backend\modules\shop\models\ars\AfterSale`.
*/
class AfterSaleSearch extends AfterSale
{
/**
* @return array
* 增加创建时间查询字段
*/
public function attributes()
{
return ArrayHelper::merge(['created_at_range'], parent::attributes());
}
/**
* {@inheritdoc}
*/
public function rules()
{
return [
[['id', 'user_id', 'order_goods_id', 'amount', 'count', 'apply_at', 'dealt_at', 'finish_at', 'operator_id', 'refund_type', 'status', 'reason', 'refund_mode'], 'integer'],
[['wx_refund_id', 'after_sale_sn', 'description', 'image', 'remarks', 'take_shipping_sn'], 'safe'],
['created_at_range','safe'],
];
}
/**
* {@inheritdoc}
*/
public function scenarios()
{
// bypass scenarios() implementation in the parent class
return Model::scenarios();
}
/**
* @return array
* 列格式
*/
public function columns()
{
return [
[
'class' => '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;
}
}

186
backend/modules/shop/models/searchs/CommentSearch.php

@ -0,0 +1,186 @@
<?php
namespace backend\modules\shop\models\searchs;
use yii\base\Model;
use yii\data\ActiveDataProvider;
use yii\helpers\ArrayHelper;
use backend\modules\shop\models\ars\Comment;
/**
* CommentSearch represents the model behind the search form of `backend\modules\shop\models\ars\Comment`.
*/
class CommentSearch extends Comment
{
/**
* @return array
* 增加创建时间查询字段
*/
public function attributes()
{
return ArrayHelper::merge(['created_at_range'], parent::attributes());
}
/**
* {@inheritdoc}
*/
public function rules()
{
return [
[['id', 'user_id', 'order_goods_id', 'star', 'status', 'updated_at', 'created_at'], 'integer'],
[['content'], 'safe'],
['created_at_range','safe'],
];
}
/**
* {@inheritdoc}
*/
public function scenarios()
{
// bypass scenarios() implementation in the parent class
return Model::scenarios();
}
/**
* @return array
* 列格式
*/
public function columns()
{
return [
'id',
'user_id',
[
'attribute' => '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;
}
}

58
backend/modules/shop/views/after-sale/_form.php

@ -0,0 +1,58 @@
<?php
use yii\helpers\Html;
use yii\bootstrap4\ActiveForm;
/* @var $this yii\web\View */
/* @var $model backend\modules\shop\models\ars\AfterSale */
/* @var $form yii\widgets\ActiveForm */
?>
<div class="after-sale-form">
<?php $form = ActiveForm::begin(); ?>
<?= $form->field($model, 'wx_refund_id')->textInput(['maxlength' => true]) ?>
<?= $form->field($model, 'after_sale_sn')->textInput(['maxlength' => true]) ?>
<?= $form->field($model, 'user_id')->textInput() ?>
<?= $form->field($model, 'order_goods_id')->textInput() ?>
<?= $form->field($model, 'amount')->textInput() ?>
<?= $form->field($model, 'count')->textInput() ?>
<?= $form->field($model, 'apply_at')->textInput() ?>
<?= $form->field($model, 'dealt_at')->textInput() ?>
<?= $form->field($model, 'finish_at')->textInput() ?>
<?= $form->field($model, 'operator_id')->textInput() ?>
<?= $form->field($model, 'refund_type')->textInput() ?>
<?= $form->field($model, 'description')->textarea(['rows' => 6]) ?>
<?= $form->field($model, 'image')->textarea(['rows' => 6]) ?>
<?= $form->field($model, 'status')->textInput() ?>
<?= $form->field($model, 'reason')->textInput() ?>
<?= $form->field($model, 'remarks')->textarea(['rows' => 6]) ?>
<?= $form->field($model, 'take_shipping_sn')->textInput(['maxlength' => true]) ?>
<?= $form->field($model, 'refund_mode')->textInput() ?>
<div class="form-group">
<?= Html::submitButton('保存', ['class' => 'btn btn-success']) ?>
<?= Html::a('返回', ['index'], ['class' => 'btn btn-info']) ?>
</div>
<?php ActiveForm::end(); ?>
</div>

62
backend/modules/shop/views/after-sale/_search.php

@ -0,0 +1,62 @@
<?php
use yii\helpers\Html;
use yii\widgets\ActiveForm;
use \blobt\widgets\DateRangePicker;
/* @var $this yii\web\View */
/* @var $model backend\modules\shop\models\searchs\AfterSaleSearch */
/* @var $form yii\widgets\ActiveForm */
?>
<?php $form = ActiveForm::begin([
'action' => ['index'],
'method' => 'get',
'validateOnType' => true,
]);
?>
<div class="row">
<div class="col">
<?= $form->field($model, 'id', [
"template" => "{input}{error}",
"inputOptions" => [
"placeholder" => "检索ID",
"class" => "form-control",
],
"errorOptions" => [
"class" => "error-tips"
]
])
?>
</div>
<div class="col">
<?= $form->field($model, 'after_sale_sn', [
"template" => "{input}{error}",
"inputOptions" => [
"placeholder" => "售后单号",
"class" => "form-control",
],
"errorOptions" => [
"class" => "error-tips"
]
])
?>
</div>
<div class="col">
<?= $form->field($model, "created_at_range", [
"template" => "{input}{error}",
"inputOptions" => [
"placeholder" => "创建时间",
],
"errorOptions" => [
"class" => "error-tips"
]
])->widget(DateRangePicker::className());
?>
</div>
<div class="form-group">
<?= Html::submitButton('<i class="fa fa-filter"></i>', ['class' => 'btn btn-default']) ?>
<?= Html::resetButton('<i class="fa fa-eraser"></i>', ['class' => 'btn btn-default']) ?>
</div>
</div>
<?php ActiveForm::end(); ?>

18
backend/modules/shop/views/after-sale/create.php

@ -0,0 +1,18 @@
<?php
use yii\helpers\Html;
/* @var $this yii\web\View */
/* @var $model backend\modules\shop\models\ars\AfterSale */
$this->title = '创建 After Sale';
$this->params['breadcrumbs'][] = ['label' => 'After Sales', 'url' => ['index']];
$this->params['breadcrumbs'][] = $this->title;
?>
<div class="after-sale-create">
<?= $this->render('_form', [
'model' => $model,
]) ?>
</div>

31
backend/modules/shop/views/after-sale/index.php

@ -0,0 +1,31 @@
<?php
use yii\helpers\Html;
use iron\grid\GridView;
/* @var $this yii\web\View */
/* @var $searchModel backend\modules\shop\models\searchs\AfterSaleSearch */
/* @var $dataProvider yii\data\ActiveDataProvider */
$this->title = '售后管理';
$this->params['breadcrumbs'][] = $this->title;
?>
<div class="row">
<div class="col-12">
<?= GridView::widget([
'dataProvider' => $dataProvider,
'filter' => $this->render("_search", ['model' => $searchModel]),
'batch' => [
[
"label" => "删除",
"url" => "after-sale/deletes"
],
],
'columns' => $columns,
'batchTemplate' => '',
'export' => '',
'create' => ''
]);
?>
</div>
</div>

19
backend/modules/shop/views/after-sale/update.php

@ -0,0 +1,19 @@
<?php
use yii\helpers\Html;
/* @var $this yii\web\View */
/* @var $model backend\modules\shop\models\ars\AfterSale */
$this->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 ';
?>
<div class="after-sale-update">
<?= $this->render('_form', [
'model' => $model,
]) ?>
</div>

108
backend/modules/shop/views/after-sale/view.php

@ -0,0 +1,108 @@
<?php
use yii\helpers\Html;
use yii\widgets\DetailView;
use backend\modules\shop\models\ars\AfterSale;
/* @var $this yii\web\View */
/* @var $model backend\modules\shop\models\ars\AfterSale */
$this->title = $model->id;
$this->params['breadcrumbs'][] = ['label' => 'After Sales', 'url' => ['index']];
$this->params['breadcrumbs'][] = $this->title;
\yii\web\YiiAsset::register($this);
?>
<div class="after-sale-view">
<p>
<?= Html::a('返回列表', ['index'], ['class' => 'btn btn-default']) ?>
<?php
if ($model->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',
],
]
);
}
?>
</p>
<?= 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];
}
],
],
]) ?>
</div>

32
backend/modules/shop/views/comment/_form.php

@ -0,0 +1,32 @@
<?php
use yii\helpers\Html;
use yii\bootstrap4\ActiveForm;
/* @var $this yii\web\View */
/* @var $model backend\modules\shop\models\ars\Comment */
/* @var $form yii\widgets\ActiveForm */
?>
<div class="comment-form">
<?php $form = ActiveForm::begin(); ?>
<?= $form->field($model, 'user_id')->textInput() ?>
<?= $form->field($model, 'order_goods_id')->textInput() ?>
<?= $form->field($model, 'star')->textInput() ?>
<?= $form->field($model, 'content')->textarea(['rows' => 6]) ?>
<?= $form->field($model, 'status')->textInput() ?>
<div class="form-group">
<?= Html::submitButton('保存', ['class' => 'btn btn-success']) ?>
<?= Html::a('返回', ['index'], ['class' => 'btn btn-info']) ?>
</div>
<?php ActiveForm::end(); ?>
</div>

49
backend/modules/shop/views/comment/_search.php

@ -0,0 +1,49 @@
<?php
use yii\helpers\Html;
use yii\widgets\ActiveForm;
use \blobt\widgets\DateRangePicker;
/* @var $this yii\web\View */
/* @var $model backend\modules\shop\models\searchs\CommentSearch */
/* @var $form yii\widgets\ActiveForm */
?>
<?php $form = ActiveForm::begin([
'action' => ['index'],
'method' => 'get',
'validateOnType' => true,
]);
?>
<div class="row">
<div class="col">
<?= $form->field($model, 'id', [
"template" => "{input}{error}",
"inputOptions" => [
"placeholder" => "检索ID",
"class" => "form-control",
],
"errorOptions" => [
"class" => "error-tips"
]
])
?>
</div>
<div class="col">
<?= $form->field($model, "created_at_range", [
"template" => "{input}{error}",
"inputOptions" => [
"placeholder" => "创建时间",
],
"errorOptions" => [
"class" => "error-tips"
]
])->widget(DateRangePicker::className());
?>
</div>
<div class="form-group">
<?= Html::submitButton('<i class="fa fa-filter"></i>', ['class' => 'btn btn-default']) ?>
<?= Html::resetButton('<i class="fa fa-eraser"></i>', ['class' => 'btn btn-default']) ?>
</div>
</div>
<?php ActiveForm::end(); ?>

18
backend/modules/shop/views/comment/create.php

@ -0,0 +1,18 @@
<?php
use yii\helpers\Html;
/* @var $this yii\web\View */
/* @var $model backend\modules\shop\models\ars\Comment */
$this->title = '创建 Comment';
$this->params['breadcrumbs'][] = ['label' => 'Comments', 'url' => ['index']];
$this->params['breadcrumbs'][] = $this->title;
?>
<div class="comment-create">
<?= $this->render('_form', [
'model' => $model,
]) ?>
</div>

31
backend/modules/shop/views/comment/index.php

@ -0,0 +1,31 @@
<?php
use yii\helpers\Html;
use iron\grid\GridView;
/* @var $this yii\web\View */
/* @var $searchModel backend\modules\shop\models\searchs\CommentSearch */
/* @var $dataProvider yii\data\ActiveDataProvider */
$this->title = '评论';
$this->params['breadcrumbs'][] = $this->title;
?>
<div class="row">
<div class="col-12">
<?= GridView::widget([
'dataProvider' => $dataProvider,
'filter' => $this->render("_search", ['model' => $searchModel]),
'batch' => [
[
"label" => "删除",
"url" => "comment/deletes"
],
],
'columns' => $columns,
'batchTemplate' => '',
'create' => '',
'export' => '',
]);
?>
</div>
</div>

19
backend/modules/shop/views/comment/update.php

@ -0,0 +1,19 @@
<?php
use yii\helpers\Html;
/* @var $this yii\web\View */
/* @var $model backend\modules\shop\models\ars\Comment */
$this->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 ';
?>
<div class="comment-update">
<?= $this->render('_form', [
'model' => $model,
]) ?>
</div>

47
backend/modules/shop/views/comment/view.php

@ -0,0 +1,47 @@
<?php
use backend\modules\shop\models\ars\Comment;
use yii\helpers\Html;
use yii\widgets\DetailView;
/* @var $this yii\web\View */
/* @var $model backend\modules\shop\models\ars\Comment */
$this->title = $model->id;
$this->params['breadcrumbs'][] = ['label' => 'Comments', 'url' => ['index']];
$this->params['breadcrumbs'][] = $this->title;
\yii\web\YiiAsset::register($this);
?>
<div class="comment-view">
<p>
<?= Html::a('返回列表', ['index'], ['class' => 'btn btn-success']) ?>
</p>
<?= DetailView::widget([
'model' => $model,
'attributes' => [
'id',
'user_id',
[
'attribute' => 'order_goods_id',
'value' => function ($model) {
return $model->orderGoods->goods_name;
},
'label' => '商品名称'
],
'star',
'content:ntext',
[
'attribute' => 'status',
'value' => function ($model) {
return Comment::$commentStatus[$model->status];
},
'label' => '状态'
],
'updated_at:datetime',
'created_at:datetime',
],
]) ?>
</div>

9
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',

13
backend/modules/shop/views/taking-site/_search.php

@ -29,6 +29,19 @@ use \blobt\widgets\DateRangePicker;
])
?>
</div>
<div class="col">
<?= $form->field($model, 'name', [
"template" => "{input}{error}",
"inputOptions" => [
"placeholder" => "名称",
"class" => "form-control",
],
"errorOptions" => [
"class" => "error-tips"
]
])
?>
</div>
<div class="col">
<?= $form->field($model, "created_at_range", [
"template" => "{input}{error}",

4
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;
?>
<div class="taking-site-create">

27
backend/views/config/index.php

@ -1,27 +0,0 @@
<?php
/*
* The MIT License
*
* Copyright 2019 Blobt.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
?>
配置页

52
backend/views/layouts/sidebar.php

@ -0,0 +1,52 @@
<?php
use iron\widgets\Menu;
?>
<aside class="main-sidebar sidebar-dark-info elevation-4">
<a href="#" class="brand-link">
<img src="/img/logo.jpeg" alt="AdminLTE" class="brand-image img-circle elevation-3"
style="opacity: .8">
<span class="brand-text font-weight-light">AdminLTE 3</span>
</a>
<!-- sidebar: style can be found in sidebar.less -->
<section class="sidebar">
<?php
echo Menu::widget([
'items' => [
// ['label' => 'MAIN NAVIGATION', 'is_header' => true],
['label' => '商城管理', 'url' => '#', 'icon' => 'far fa-store', 'items' => [
['label' => '运营数据', 'url' => ['site/index', 'tag' => 'new']],
['label' => '基础配置', 'url' => ['config/index', 'tag' => 'new']],
]
],
['label' => '商品管理', 'url' => '#', 'icon' => 'far fa-box', 'items' => [
['label' => '后台商品分类', 'url' => ['/goods/category/index']],
['label' => '规格管理', 'url' => ['/goods/attribute/index']],
['label' => '前端商品分类', 'url' => ['/goods/shop-category/index']],
['label' => '品牌管理', 'url' => ['/goods/brand/index']],
['label' => '供应商管理', 'url' => ['/goods/supplier/index']],
['label' => '商品列表', 'url' => ['/goods/goods/index']],
]
],
['label' => '订单管理', 'url' => '#', 'icon' => 'far fa-list-alt', 'items' => [
['label' => '订单列表', 'url' => ['/shop/order/index', 'tag' => 'new']],
],
],
['label' => '配送服务', 'url' => '#', 'icon' => 'far fa-shipping-fast', 'items' => [
['label' => '上门自提', 'url' => ['/shop/taking-site/index']],
['label' => '运费模板', 'url' => ['/shop/express-template/index']],
]
],
['label' => '售后管理', 'url' => '#', 'icon' => 'far fa-retweet', 'items' => [
['label' => '售后管理', 'url' => ['/shop/after-sale/index']],
['label' => '评论', 'url' => ['/shop/comment/index']],
]
],
]
]);
?>
</section>
<!-- /.sidebar -->
</aside>

17
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"
}]
]
}

26
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"
}
}

11
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

89
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"
}
}

1
backend/web/src/iconfont.html

@ -0,0 +1 @@
<link rel="stylesheet" href="//at.alicdn.com/t/font_827976_jtnsfqxuiaf.css">

4
backend/web/src/import.html

@ -0,0 +1,4 @@
<div id="app"></div>
<script>
var csrfToken = "<?= Yii::$app->request->csrfToken ?>";
</script>

36
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 (
<GridLayout
layout={layout}
cols={12}
rowHeight={30}
width={300}
onLayoutChange={this.onLayoutChange}
>
{layout.map(item => (
<div key={item.i}>
<Module attr={attrs[item.i]} />
</div>
))}
</GridLayout>
)
}
}

101
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: <Swiper />,
GoodsList: <GoodsList title={attr.title} type={attr.type} name={attr.data} />
};
if (moduleTable[attr.component]) {
return (
<Wrapper>
{moduleTable[attr.component]}
<HoverLayer>
<EditButton className='fa fa-edit' />
</HoverLayer>
</Wrapper>
)
} else {
return (
<ErrorTip>{attr.component}</ErrorTip>
)
}
}
}
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);
}
`;

106
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 (
<Wrapper>
<Header>
<StatusBar>
<span>{time}</span>
</StatusBar>
<TitleBar>
<span>首页</span>
</TitleBar>
</Header>
<Content>
<GridEditor />
</Content>
<Tabbar />
</Wrapper>
)
}
}
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;
`;

16
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 (
<Wrapper>
</Wrapper>
)
}
}
const Wrapper = styled.div`
flex-grow: 1;
`;

29
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 (
<Provider store={store}>
<Wrapper>
<Previewer />
<PropsEditor />
<DevTools />
</Wrapper>
</Provider>
)
}
}
render(<AmazingCreator />, document.getElementById('edit-home'));

67
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 (
<Wrapper>
<Header>
<Title>{title}</Title>
<More>查看更多></More>
</Header>
<Image src={typeImageTable[type]} draggable="false" />
</Wrapper>
)
}
}
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%;
`;

14
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 <Image src='/img/banner.png' draggable="false" />
}
}
const Image = styled.img`
width: 100%;
height: 100%;
user-select: none;
`;

150
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(() => (
<div className='drag-handle glyphicon glyphicon-menu-hamburger' />
)); // 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 (
<div className="part">
<div className="part__handle">
<input type="text" value={title} onChange={onChangeTitle} />
<Select
value={options[type - 1]}
onChange={onChangeType}
options={options}
isSearchable={false}
/>
</div>
<div className="part__content">
<div className="part__header">
<div className="title">{title}</div>
<div className="more">查看更多></div>
</div>
<img src={typeImageTable[type - 1]} alt=""/>
</div>
</div>
)
}
}
const SortableItem = SortableElement(({value}) => {
return (
<li className='sortable-item'>
<DragHandle />
{value}
</li>
);
});
const SortableList = SortableContainer(({items}) => {
return (
<ul>
{items.map((value, index) => (
<SortableItem key={`item-${index}`} index={index} value={value} />
))}
</ul>
);
});
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) => (
<Part
key={index}
title={item.title}
type={item.type}
onChangeTitle={this.onChangeTitle.bind(undefined, index)}
onChangeType={this.onChangeType.bind(undefined, index)}
/>
));
return (
<div>
<SortableList
items={itemsView}
lockAxis='y'
onSortEnd={this.onSortEnd}
useDragHandle={true}
/>
<button type='button' className='btn btn-success' onClick={this.submit}>保存</button>
<button className='btn btn-info' onClick={this.back}>返回</button>
</div>
);
}
}

68
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()

313
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) => (
<InputNumber
type='number'
value={text}
{...others}
onChange={value => 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) => (
<Select
value={value}
style={{ width: '100%', minWidth: '50px' }}
onChange={newValue => setSkuItem(rowIndex, index, newValue)}
>
{attrValue.map(({ id, attr_value }) => (
<Option key={id} value={id}>{attr_value}</Option>
))}
</Select>
)
})) : [{
title: '规格',
dataIndex: 'value',
key: 'value',
align: 'center',
render: (value, _, rowIndex) => (
<Input
value={value}
style={{ width: '100%', minWidth: '50px' }}
onChange={e => onChangeItem(rowIndex, 'value', e.target.value)}
/>
)
}];
const columns = [
...skuColumns,
...buildInputCol(additionalCol, onChangeItem),
{
key: 'delete',
title: '操作',
render: (_1, _2, index) => <a href="javascript:void(0);" onClick={() => onDeleteItem(index)}>删除</a>
}
];
return (
<Fragment>
<Table dataSource={dataSource} columns={columns} pagination={false} />
</Fragment>
)
}
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 (
<Wrapper>
<Spin spinning={loading} tip='加载中...'>
<SelectMode>
{modeList.map(({ key, title }) => (
<ModeItem
key={key}
className={type === key ? 'active' : ''}
onClick={() => selectMode(key)}
>{title}</ModeItem>
))}
</SelectMode>
<SkuTable
skuData={data.data}
attributes={attributes}
type={type}
onChangeItem={setItem}
onDeleteItem={deleteItem}
/>
<ButtonGroup>
<Button type='primary' onClick={addItem}>新增一行</Button>
<Button type='primary' style={{ marginLeft: '20px' }} onClick={save}>保存</Button>
<Button style={{ marginLeft: '20px' }} onClick={() => history.back()}>返回</Button>
</ButtonGroup>
</Spin>
</Wrapper>
)
}
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(
<LocaleProvider locale={zhCN}>
<App />
</LocaleProvider>,
document.getElementById('app')
);

74
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 = <Icon type="loading" style={{ fontSize: 24 }} spin />;
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 (
<Root>
<Label>{label}</Label>
{items ? (
<Select
mode="multiple"
style={{width: '100%'}}
placeholder="请选择要使用的规格"
defaultValue={defaultValue}
value={value}
onChange={onChange}
>
{items.map(item => (
<Option
key={item.id}
disabled={item.disabled}
title={item.name}
value={item.id}
>{item.name}</Option>
))}
</Select>
) : <Spin indicator={antIcon} />}
</Root>
)
}
}
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;
`;

157
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 (
<Root>
{(canNotDeleteAttr && canNotDeleteAttr.length > 0) && (
<Header>
<Alert
message="提示"
description="一些规格已经被使用不能删除若想删除请先到商品列表找到该商品
点击右侧的添加 SKU按钮进入SKU 管理页面删除使用此规格的所有 SKU再回到这里进行删除"
type="info"
showIcon
/>
</Header>
)}
<SelectGroup>
<SelectCell
label='规格类型'
items={allAttr}
value={skuList.map(i => i.id)}
onChange={this.handleChangeAttr}
/>
{skuList && skuList.map((sku, index) => (
<SelectCell
key={sku.id}
label={sku.name}
items={allAttr.find(i => i.id === sku.id).value}
value={sku.value}
onChange={this.handleChangeAttrItem.bind(undefined, index)}
/>
))}
<input
type="hidden"
id="attribute"
name="attribute"
value={JSON.stringify(skuList.map(({id, value}) => ({id, value})))}
/>
</SelectGroup>
</Root>
)
} else {
return <></>
}
}
}
const Root = styled.div`
`;
const Header = styled.div`
margin-bottom: 30px;
`;
const SelectGroup = styled.div`
width: 100%;
`;
ReactDOM.render(
<LocaleProvider locale={zhCN}>
<Sku />
</LocaleProvider>,
document.getElementById('sku-choose')
);

83
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;
}
}
}
}
}

90
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: '';
}
}

180
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%;
}
}
}

183
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;
}
}

128
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;
}
}
}

7
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";

193
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;
}
}
}

201
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;
}
}

30
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;
}

301
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;
}
}

76
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);
});

84
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,
]
};
};

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save