linyaostalker
5 years ago
105 changed files with 5536 additions and 669 deletions
-
76backend/controllers/ConfigController.php
-
61backend/controllers/SiteController.php
-
166backend/logic/PermissionManager.php
-
106backend/modules/file/logic/file/FileManager.php
-
6backend/modules/file/models/ars/File.php
-
6backend/modules/file/models/ars/TemFile.php
-
21backend/modules/goods/controllers/AttributeController.php
-
3backend/modules/goods/controllers/BrandController.php
-
90backend/modules/goods/controllers/CategoryController.php
-
97backend/modules/goods/controllers/GoodsController.php
-
95backend/modules/goods/controllers/ShopCategoryController.php
-
3backend/modules/goods/controllers/SupplierController.php
-
244backend/modules/goods/logic/goods/GoodsManager.php
-
22backend/modules/goods/migrations/m191211_060934_update_column_limit_count_in_table_atg_goods.php
-
22backend/modules/goods/migrations/m191211_092335_update_column_sort_order_in_table_atg_goods.php
-
41backend/modules/goods/migrations/m191217_091658_update_column_is_manual_in_table_atg_goods_sku.php
-
26backend/modules/goods/migrations/m191217_092101_add_column_sku_image_in_table_atg_goods_sku.php
-
3backend/modules/goods/models/ars/Attribute.php
-
24backend/modules/goods/models/ars/Category.php
-
10backend/modules/goods/models/ars/Goods.php
-
8backend/modules/goods/models/ars/GoodsSku.php
-
28backend/modules/goods/models/ars/ShopCategory.php
-
12backend/modules/goods/models/searchs/CategorySearch.php
-
27backend/modules/goods/models/searchs/GoodsSearch.php
-
13backend/modules/goods/views/attribute/_search.php
-
13backend/modules/goods/views/brand/_search.php
-
4backend/modules/goods/views/category/_form.php
-
13backend/modules/goods/views/category/_search.php
-
4backend/modules/goods/views/goods/_form.php
-
4backend/modules/goods/views/goods/_search.php
-
44backend/modules/goods/views/goods/create.php
-
65backend/modules/goods/views/goods/goods.php
-
6backend/modules/goods/views/goods/new_editor.php
-
4backend/modules/goods/views/goods/picture.php
-
41backend/modules/goods/views/goods/update.php
-
4backend/modules/goods/views/shop-category/_form.php
-
13backend/modules/goods/views/shop-category/_search.php
-
26backend/modules/goods/views/supplier/_search.php
-
175backend/modules/shop/controllers/AfterSaleController.php
-
179backend/modules/shop/controllers/CommentController.php
-
122backend/modules/shop/controllers/ExpressTemplateController.php
-
17backend/modules/shop/controllers/OrderController.php
-
7backend/modules/shop/controllers/TakingSiteController.php
-
139backend/modules/shop/logic/ShopManager.php
-
68backend/modules/shop/migrations/m191209_080245_update_table_after_sale.php
-
20backend/modules/shop/migrations/m191217_013625_add_column_sku_value_in_table_ats_cart.php
-
20backend/modules/shop/migrations/m191217_021455_update_column_star_in_table_ats_comment.php
-
98backend/modules/shop/models/ars/AfterSale.php
-
4backend/modules/shop/models/ars/Cart.php
-
13backend/modules/shop/models/ars/Comment.php
-
6backend/modules/shop/models/ars/OrderGoods.php
-
188backend/modules/shop/models/searchs/AfterSaleSearch.php
-
186backend/modules/shop/models/searchs/CommentSearch.php
-
58backend/modules/shop/views/after-sale/_form.php
-
62backend/modules/shop/views/after-sale/_search.php
-
18backend/modules/shop/views/after-sale/create.php
-
31backend/modules/shop/views/after-sale/index.php
-
19backend/modules/shop/views/after-sale/update.php
-
108backend/modules/shop/views/after-sale/view.php
-
32backend/modules/shop/views/comment/_form.php
-
49backend/modules/shop/views/comment/_search.php
-
18backend/modules/shop/views/comment/create.php
-
31backend/modules/shop/views/comment/index.php
-
19backend/modules/shop/views/comment/update.php
-
47backend/modules/shop/views/comment/view.php
-
9backend/modules/shop/views/express-template/express_area_view.php
-
13backend/modules/shop/views/taking-site/_search.php
-
4backend/modules/shop/views/taking-site/create.php
-
27backend/views/config/index.php
-
52backend/views/layouts/sidebar.php
-
17backend/web/.babelrc
-
26backend/web/.eslintrc.json
-
11backend/web/.gitignore
-
89backend/web/package.json
-
1backend/web/src/iconfont.html
-
4backend/web/src/import.html
-
36backend/web/src/js/amazing-creator/GridEditor.js
-
101backend/web/src/js/amazing-creator/Module.js
-
106backend/web/src/js/amazing-creator/Previewer.js
-
16backend/web/src/js/amazing-creator/PropsEditor.js
-
29backend/web/src/js/amazing-creator/index.js
-
67backend/web/src/js/amazing-creator/modules/GoodsList.js
-
14backend/web/src/js/amazing-creator/modules/Swiper.js
-
150backend/web/src/js/amazing-creator/old.js
-
68backend/web/src/js/amazing-creator/store.js
-
313backend/web/src/sku-item/index.js
-
74backend/web/src/sku/SelectCell.js
-
157backend/web/src/sku/index.js
-
83backend/web/src/styles/amazing-creator.scss
-
90backend/web/src/styles/content-header.scss
-
180backend/web/src/styles/dashboard.scss
-
183backend/web/src/styles/global.scss
-
128backend/web/src/styles/header.scss
-
7backend/web/src/styles/index.scss
-
193backend/web/src/styles/login.scss
-
201backend/web/src/styles/sidebar.scss
-
30backend/web/src/styles/variables.scss
-
301backend/web/src/styles/widget.scss
-
76backend/web/src/utils/ajax.js
-
84backend/web/webpack.config.js
@ -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.'); |
|
||||
} |
|
||||
} |
|
@ -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; |
||||
|
} |
||||
|
} |
@ -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; |
||||
|
} |
||||
|
} |
@ -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; |
||||
|
} |
||||
|
} |
@ -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; |
||||
|
} |
||||
|
*/ |
||||
|
} |
@ -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; |
||||
|
} |
||||
|
} |
@ -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); |
||||
|
} |
||||
|
} |
||||
|
} |
@ -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']); |
||||
|
} |
||||
|
} |
@ -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; |
||||
|
} |
||||
|
} |
@ -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; |
||||
|
} |
||||
|
} |
@ -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; |
||||
|
} |
||||
|
} |
@ -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; |
||||
|
} |
||||
|
} |
@ -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; |
||||
|
} |
||||
|
} |
@ -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; |
||||
|
} |
||||
|
} |
@ -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> |
@ -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(); ?>
|
@ -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> |
@ -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> |
@ -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> |
@ -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> |
@ -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> |
@ -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(); ?>
|
@ -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> |
@ -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> |
@ -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> |
@ -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> |
@ -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. |
|
||||
*/ |
|
||||
?>
|
|
||||
|
|
||||
配置页 |
|
@ -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> |
@ -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" |
||||
|
}] |
||||
|
] |
||||
|
} |
@ -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" |
||||
|
} |
||||
|
} |
@ -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 |
@ -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" |
||||
|
} |
||||
|
} |
@ -0,0 +1 @@ |
|||||
|
<link rel="stylesheet" href="//at.alicdn.com/t/font_827976_jtnsfqxuiaf.css"> |
@ -0,0 +1,4 @@ |
|||||
|
<div id="app"></div> |
||||
|
<script> |
||||
|
var csrfToken = "<?= Yii::$app->request->csrfToken ?>"; |
||||
|
</script> |
@ -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> |
||||
|
) |
||||
|
} |
||||
|
} |
@ -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); |
||||
|
} |
||||
|
`;
|
@ -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; |
||||
|
`;
|
@ -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; |
||||
|
`;
|
@ -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')); |
@ -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%; |
||||
|
`;
|
@ -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; |
||||
|
`;
|
@ -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> |
||||
|
); |
||||
|
} |
||||
|
} |
@ -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() |
@ -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') |
||||
|
); |
@ -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; |
||||
|
`;
|
@ -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') |
||||
|
); |
@ -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; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
@ -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: ''; |
||||
|
} |
||||
|
} |
@ -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%; |
||||
|
} |
||||
|
} |
||||
|
} |
@ -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; |
||||
|
} |
||||
|
} |
@ -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; |
||||
|
} |
||||
|
} |
||||
|
} |
@ -0,0 +1,7 @@ |
|||||
|
@import "./global"; |
||||
|
@import "sidebar"; |
||||
|
@import "widget"; |
||||
|
@import "header"; |
||||
|
@import "content-header"; |
||||
|
@import "dashboard"; |
||||
|
@import "amazing-creator"; |
@ -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; |
||||
|
} |
||||
|
} |
||||
|
} |
@ -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; |
||||
|
} |
||||
|
} |
@ -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; |
||||
|
} |
@ -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; |
||||
|
} |
||||
|
} |
@ -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); |
||||
|
}); |
@ -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
Write
Preview
Loading…
Cancel
Save
Reference in new issue