属性管理

This commit is contained in:
邓皓元 2019-04-03 18:04:39 +08:00
parent 94450bafd2
commit da88e826c9
27 changed files with 1720 additions and 33 deletions

View File

@ -41,7 +41,7 @@ class AutoActivate extends Command
// }
$sql = "WITH sim_array AS (
SELECT sim FROM virtual_order_cards WHERE created_at >= '%s' AND created_at <= '%s' AND service_start_at IS NULL
SELECT sim FROM virtual_order_cards WHERE created_at >= '%s' AND created_at <= '%s' AND service_start_at IS NULL ORDER BY created_at DESC
), activate_updates AS (
UPDATE cards SET virtual_activated_at='%s' WHERE sim IN (SELECT * FROM sim_array) AND virtual_activated_at IS NULL
)

View File

@ -0,0 +1,83 @@
<?php
namespace App\Domains\Virtual\Http\Controllers;
use App\Core\Controller;
use Illuminate\Http\Request;
use App\Domains\Virtual\Services\PropertyService;
class PropertyController extends Controller
{
protected $request;
protected $propertyService;
/**
* 构造函数,自动注入.
*/
public function __construct(Request $request, PropertyService $propertyService)
{
$this->request = $request;
$this->propertyService = $propertyService;
}
/**
* 配置.
*
* @return \Illuminate\Http\Response
*/
public function settings()
{
if ($this->request->isMethod('POST')) {
$values = $this->request->get('data');
$res = $this->propertyService->settingsStore($values);
return res($res, '修改成功');
} else {
$conditions = $this->request->all();
$res = $this->propertyService->settings($conditions);
return res($res, '配置分类', 201);
}
}
/**
* 列表.
*
* @return \Illuminate\Http\Response
*/
public function index()
{
$conditions = $this->request->all();
$list = $this->propertyService->index($conditions);
return res($list, '客户套餐属性配置列表', 201);
}
/**
* 存储.
*
* @return \Illuminate\Http\Response
*/
public function store()
{
$values = $this->request->get('data');
$res = $this->propertyService->store($values);
return res($res, '修改成功');
}
/**
* 导出.
*
* @return \Illuminate\Http\Response
*/
public function export()
{
//
}
/**
* 导入.
*
* @return \Illuminate\Http\Response
*/
public function import()
{
//
}
}

View File

@ -0,0 +1,62 @@
<?php
namespace App\Domains\Virtual\Repositories;
use App\Core\Repository;
use App\Models\Virtual\Property as Model;
class PropertyRepository extends Repository
{
/**
* 是否关闭缓存
*
* @var boolean
*/
protected $cacheSkip = false;
/**
* 是否开启数据转化
*
* @var bool
*/
protected $needTransform = false;
/**
* @var array
*/
protected $fieldSearchable = [
'id' => '=',
'created_at' => 'like',
];
public function model() {
return Model::class;
}
/**
* 数据格式化
*
* @param mixed $result
*
* @return mixed
*/
public function transform($model)
{
return $model->toArray();
}
/**
* 查询条件
*
* @return void
*/
public function withConditions(array $conditions = [])
{
if (isset($conditions['id'])) {
$conditions['id'] = array_wrap($conditions['id']);
$this->model = $this->model->whereIn('id', $conditions['id']);
}
return $this;
}
}

View File

@ -0,0 +1,72 @@
<?php
namespace App\Domains\Virtual\Repositories;
use App\Core\Repository;
use App\Models\Virtual\PropertySetting as Model;
class PropertySettingRepository extends Repository
{
/**
* 是否关闭缓存
*
* @var boolean
*/
protected $cacheSkip = false;
/**
* 是否开启数据转化
*
* @var bool
*/
protected $needTransform = false;
/**
* @var array
*/
protected $fieldSearchable = [
'id' => '=',
'created_at' => 'like',
];
public function model()
{
return Model::class;
}
/**
* 数据格式化
*
* @param mixed $result
*
* @return mixed
*/
public function transform($model)
{
return $model->toArray();
}
/**
* 查询条件
*
* @return void
*/
public function withConditions(array $conditions = [])
{
if (isset($conditions['id'])) {
$conditions['id'] = array_wrap($conditions['id']);
$this->model = $this->model->whereIn('id', $conditions['id']);
}
return $this;
}
public function getAll($force = false)
{
if ($force) {
$this->forgetCached();
}
return $this->get()->pluck('value', 'name');
}
}

View File

@ -36,13 +36,20 @@ $router->group(['prefix' => 'virtual', 'as' => 'virtual', 'middleware' => ['admi
// $router->post('/company/addresses/update/{id}', ['as' => 'company.addresses.update', 'uses' => 'CompanyAddressController@update']);
// $router->post('/company/addresses/destroy', ['as' => 'company.addresses.destroy', 'uses' => 'CompanyAddressController@destroy']);
// 企业定价管理
// 定价管理
$router->get('/products/index', ['as' => 'products.index', 'uses' => 'ProductController@index']);
$router->get('/products/history', ['as' => 'products.history', 'uses' => 'ProductController@history']);
$router->post('/products/create', ['as' => 'products.create', 'uses' => 'ProductController@create']);
$router->post('/products/update/{id}', ['as' => 'products.update', 'uses' => 'ProductController@update']);
$router->post('/products/destroy', ['as' => 'products.destroy', 'uses' => 'ProductController@destroy']);
// 属性管理
$router->addRoute(['GET', 'POST'], '/properties/settings', ['as' => 'properties.settings', 'uses' => 'PropertyController@settings']);
$router->get('/properties/index', ['as' => 'properties.index', 'uses' => 'PropertyController@index']);
$router->post('/properties/store', ['as' => 'properties.store', 'uses' => 'PropertyController@store']);
$router->get('/properties/export', ['as' => 'properties.export', 'uses' => 'PropertyController@export']);
$router->post('/properties/import', ['as' => 'properties.import', 'uses' => 'PropertyController@import']);
// 订单管理
$router->get('/orders/index', ['as' => 'orders.index', 'uses' => 'OrderController@index']);
$router->get('/orders/show/{id}', ['as' => 'orders.show', 'uses' => 'OrderController@show']);

View File

@ -154,7 +154,7 @@ class PackageService extends Service
if (!self::$packages) {
self::$packages = app(PackageRepository::class)
->select(['id', 'type', 'sn', 'name', 'carrier_operator', 'flows', 'service_months', 'status'])
->withTrashed()->get()->keyBy('id');
->withTrashed()->get()->keyBy('id')->toArray();
}
return self::$packages[$id];

View File

@ -0,0 +1,304 @@
<?php
namespace App\Domains\Virtual\Services;
use App\Dicts;
use App\Core\Service;
use App\Exceptions\NotExistException;
use App\Exceptions\NotAllowedException;
use App\Models\Virtual\PropertySetting;
use App\Exceptions\InvalidArgumentException;
use App\Domains\Virtual\Repositories\ProductRepository;
use App\Domains\Virtual\Repositories\PropertyRepository;
use App\Domains\Virtual\Repositories\PropertySettingRepository;
use App\Domains\Virtual\Repositories\OrderCardPartitionRepository;
class PropertyService extends Service
{
protected $productRepository;
protected $propertyRepository;
protected $propertySettingRepository;
public static $property_types = [
'product' => '产品类型',
'vehicle' => '车辆类型',
'commercial_vehicle' => '商用车分类',
'company' => '公司类型',
'platform' => '平台/API类型',
'customer' => '客户类型',
'package' => '套餐分类',
'province' => '省份设置',
'package_type' => '套餐类型'
];
public static $default = [
'product' => '',
'vehicle' => '',
'commercial_vehicle' => '',
'company' => '',
'platform' => '',
'customer' => '',
'package' => '',
'province' => null,
'province_status' => 0,
'created_at' => null,
'updated_at' => null,
];
/**
* 构造函数
*
* @return void
*/
public function __construct(ProductRepository $productRepository, PropertyRepository $propertyRepository, PropertySettingRepository $propertySettingRepository)
{
$this->productRepository = $productRepository;
$this->propertyRepository = $propertyRepository;
$this->propertySettingRepository = $propertySettingRepository;
}
/**
* 配置设置查看
*
* @param array $conditions
* @return void
*/
public function settings($conditions = [])
{
$settings = $this->propertySettingRepository->getAll();
$products = $settings['product'];
foreach ($settings as $key => $setting) {
if ($setting['name'] === 'package') {
$values = $setting['value'];
foreach ($values as $k => $value) {
foreach ($value as $i => $v) {
if (!in_array($v, $products)) {
unset($settings[$key]['value'][$k][$i]);
$settings[$key]['value'][$k] = array_values($settings[$key]['value'][$k]);
}
}
}
}
}
if ($conditions['names']) {
$conditions['names'] = array_wrap($conditions['names']);
$settings = array_where($settings, function ($value) use ($conditions) {
return in_array($value['name'], $conditions['names']);
});
};
return $settings;
}
/**
* 配置设置写入
*
* @param array $values
* @return void
*/
public function settingsStore(array $values)
{
$settings = $this->propertySettingRepository->getAll();
if (isset($values['product'])) {
}
foreach ($values as $key => $value) {
if (!in_array($key, array_keys(self::$property_types))) {
throw new NotExistException("分类{$key}不存在");
}
if ($key !== 'province' && $key !== 'package') {
$value = array_values(array_unique(array_merge($settings[$key] ?: [], $value)));
$value = array_map(function ($item) {
return is_string($item) ? trim($item) : $item;
}, $value);
}
$values[$key] = $value;
}
if (isset($values['package'])) {
$packages = $values['package'];
$products = $values['product'];
foreach ($packages as $k => $v) {
if (!is_array($v)) {
throw new InvalidArgumentException('传入的产品参数错误');
}
foreach ($v as $m) {
if (!in_array($m, $products)) {
throw new NotExistException("产品({$m})未配置或已删除");
}
foreach ($packages as $i => $j) {
if ($i !== $k && in_array($m, $j)) {
throw new NotAllowedException('一个产品仅能归属一个套餐');
}
}
}
}
}
$data = [];
foreach ($values as $key => $value) {
$data[] = [
'name' => $key,
'value' => json_encode($value, 256),
];
}
PropertySetting::upsert($data, 'name');
$this->propertySettingRepository->forgetCached();
$settings = $this->propertySettingRepository->getAll();
return true;
}
/**
* 客户套餐配置列表
*
* @return void
*/
public function index($conditions = [])
{
$product = $this->productRepository->whereHas('package', function ($query) {
$query->whereNull('deleted_at');
})->whereHas('company', function ($query) {
$query->whereNull('deleted_at');
})->withConditions($conditions)->applyConditions()->get();
$properties = $this->propertyRepository->whereHas('package', function ($query) {
$query->whereNull('deleted_at');
})->whereHas('company', function ($query) {
$query->whereNull('deleted_at');
})->withConditions($conditions)->applyConditions()->get()->keyBy(function ($item) {
return $item->company_id . '_' . $item->package_id;
})->toArray();
$sells = app(OrderCardPartitionRepository::class)->selectRaw('company_id, package_id, count(*) as counts')
->where('type', 0)->groupBy(['company_id', 'package_id'])->get()->keyBy(function ($item) {
return $item->company_id . '_' . $item->package_id;
})->toArray();
$carrierOperators = app(Dicts::class)->get('carrier_operator');
$list = $product->map(function ($item) use ($properties, $sells, $carrierOperators) {
$item = $item->toArray();
$package = PackageService::load($item['package_id']);
$item['company_name'] = CompanyService::load($item['company_id'])['name'] ?? '';
$item['package_name'] = $package['name'] ?? '';
$item['flows'] = $package['flows'] ?? 0;
$item['carrier_operator'] = $package['carrier_operator'];
$item['carrier_operator_name'] = $carrierOperators[$item['carrier_operator']] ?? '未知';
$property = $properties[$item['company_id'] . '_' . $item['package_id']];
$sell = $sells[$item['company_id'] . '_' . $item['package_id']];
$item['counts'] = $sell ? $sell['counts'] : 0;
return array_merge($item, $property ?: self::$default);
});
return $list;
}
/**
* 客户套餐配置修改
*
* @param array $values
* @return void
*/
public function store(array $values)
{
if (! is_array(reset($values))) {
$values = [$values];
}
$only = ['company_id', 'package_id', 'product', 'vehicle', 'commercial_vehicle', 'company', 'platform', 'customer', 'province'];
$checks = ['product', 'vehicle', 'company', 'customer'];
$settings = $this->propertySettingRepository->getAll();
$data = [];
foreach ($values as $key => $value) {
if (!isset($value['company_id']) || !isset($value['package_id'])) {
continue;
}
$value['platform'] = $value['platform'] ?: '';
$value['commercial_vehicle'] = $value['commercial_vehicle'] ?: '';
$value = array_only($value, $only);
foreach ($value as $k => $v) {
if (in_array($k, $checks) && empty($v)) {
throw new InvalidArgumentException(self::$property_types[$k] . '值不能为空');
}
if (in_array($k, $checks) && !in_array($v, $settings[$k])) {
throw new InvalidArgumentException(self::$property_types[$k] . '值不正确');
}
if (in_array($k, ['commercial_vehicle', 'platform']) && !in_array($v, $settings[$k]) && ($v !== '')) {
throw new InvalidArgumentException(self::$property_types[$k] . '值不正确');
}
}
if (!is_null($value['province'])) {
if (!empty(array_diff(array_keys($value['province']), $settings['province']))) {
throw new InvalidArgumentException('省份配置不正确');
}
if (array_sum($value['province']) != 0 && sprintf("%.2f", array_sum($value['province'])) != 100) {
throw new InvalidArgumentException('百分比配置不正确');
}
$value['province'] = json_encode($value['province'], 256);
}
if (!$package = $this->getPackage($value['product'])) {
throw new NotExistException('产品套餐关系未配置');
}
$value['package'] = $package;
$value['created_at'] = date('Y-m-d H:i:s');
$value['updated_at'] = date('Y-m-d H:i:s');
$data[] = $value;
}
if (empty($data)) {
throw new NotAllowedException('数据未修改');
}
$this->propertyRepository->upsert($data, ['company_id', 'package_id']);
$this->propertyRepository->forgetCached();
return true;
}
protected function getPackage($product)
{
if (!$package = $this->package) {
$settings = $this->propertySettingRepository->getAll();
$packages = $settings['package'];
$this->package = $package;
}
foreach ($packages as $key => $products) {
if (in_array($product, $products)) {
return $key;
}
}
return null;
}
}

View File

@ -0,0 +1,13 @@
<?php
namespace App\Domains\Virtual\Tests\Services;
use App\Core\TestCase;
use App\Domains\Virtual\Services\PropertyService;
class PropertyServiceTest extends TestCase
{
public function testPropertyServiceTest()
{
$this->assertTrue(true);
}
}

View File

@ -0,0 +1,24 @@
<?php
namespace App\Models\Virtual;
use App\Core\Model;
class Property extends Model
{
protected $table = 'virtual_properties';
protected $casts = [
'province' => 'array',
];
public function company()
{
return $this->belongsTo(Company::class, 'company_id', 'id');
}
public function package()
{
return $this->belongsTo(Package::class, 'package_id', 'id');
}
}

View File

@ -0,0 +1,17 @@
<?php
namespace App\Models\Virtual;
use App\Core\Model;
class PropertySetting extends Model
{
protected $table = 'virtual_property_settings';
protected $primaryKey = 'name';
protected $keyType = 'string';
public $incrementing = false;
protected $casts = [
'value' => 'array',
];
}

View File

@ -0,0 +1,58 @@
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class CreatePropertyTables extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
if (!Schema::hasTable('virtual_properties')) {
Schema::create('virtual_properties', function (Blueprint $table) {
$table->increments('id')->comment('自增ID');
$table->integer('company_id')->unsigned()->default(0)->comment('企业ID');
$table->integer('package_id')->unsigned()->default(0)->comment('套餐ID');
$table->string('product', 100)->default('')->comment('产品类型');
$table->string('vehicle', 100)->default('')->comment('车辆类型');
$table->string('commercial_vehicle', 100)->default('')->comment('商用车分类');
$table->string('company', 100)->default('')->comment('公司类型');
$table->string('platform', 100)->default('')->comment('平台/API类型');
$table->string('customer', 100)->default('')->comment('客户类型');
$table->string('package', 100)->default('')->comment('套餐分类');
$table->text('province', 100)->nullable()->comment('省份设置');
$table->timestamps();
$table->unique(['company_id', 'package_id']);
$table->comment('企业套餐卡属性配置');
});
}
if (!Schema::hasTable('virtual_property_settings')) {
Schema::create('virtual_property_settings', function (Blueprint $table) {
$table->string('name', 20)->default('');
$table->text('value', 100)->nullable();
$table->primary('name');
$table->comment('卡属性分类配置');
});
}
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('virtual_property_settings');
Schema::dropIfExists('virtual_properties');
}
}

View File

@ -150,6 +150,14 @@ class PermissionSeeder extends Seeder
['name' => 'virtual.products.3.destroy', 'title' => '删除', 'description' => 'destroy', 'type' => 1],
],
],
[
'name' => 'virtual.properties.index', 'title' => '属性管理', 'path' => '/properties', 'icon' => 'md-cube', 'type' => 0, 'open' => 3,
'children' => [
['name' => 'virtual.properties.create', 'title' => '设置', 'description' => 'create', 'type' => 1],
['name' => 'virtual.properties.update', 'title' => '修改', 'description' => 'update', 'type' => 1],
['name' => 'virtual.properties.output', 'title' => '导出', 'description' => 'output', 'type' => 1],
],
],
],
],
[

View File

@ -21,6 +21,7 @@
"pinyin-engine": "^1.1.0",
"vue": "^2.5.2",
"vue-router": "^3.0.1",
"vuedraggable": "^2.20.0",
"vuex": "^3.0.1",
"xlsx": "^0.13.5"
},

View File

@ -0,0 +1,63 @@
/**
* 属性管理
*/
/**
* [settings 属性设置]
* @param {[type]} data [description]
* @return {[type]} [description]
*/
export function settings(data) {
return service.get('api/virtual/properties/settings', {
params: data
});
}
/**
* [settingsStore 属性设置存储]
* @param {[type]} data [description]
* @return {[type]} [description]
*/
export function settingsStore(data) {
return service.post('api/virtual/properties/settings', data);
}
/**
* [index 属性列表]
* @param {[type]} data [description]
* @return {[type]} [description]
*/
export function index(data) {
return service.get('api/virtual/properties/index', {
params: data
});
}
/**
* [store 属性存储]
* @param {[type]} data [description]
* @return {[type]} [description]
*/
export function store(data) {
return serviceForm.post('api/virtual/properties/store', data);
}
/**
* [export 属性导出]
* @param {[type]} data [description]
* @return {[type]} [description]
*/
export function exportExcel(data) {
return service.get('api/virtual/properties/export', {
params: data
});
}
/**
* [import 属性导入]
* @param {[type]} data [description]
* @return {[type]} [description]
*/
export function importExcel(data) {
return service.post('api/virtual/properties/import', data);
}

View File

@ -20,8 +20,6 @@ export default {
return array;
}
console.log(array);
const pinyinEngine = new PinyinEngine(array, [key]);
let res = [];

View File

@ -21,6 +21,7 @@ const routes = [
{ path: '/company/accounts', name: 'CompanyAccounts', component: load('virtual/company_accounts/index'), meta: { title: '账号管理' } },
{ path: '/packages/:type', name: 'Packages', component: load('virtual/packages/index'), meta: { title: '套餐管理' } },
{ path: '/products/:type', name: 'Products', component: load('virtual/products/index'), meta: { title: '定价管理' } },
{ path: '/properties', name: 'Properties', component: load('virtual/properties/index'), meta: { title: '属性管理' } },
{ path: '/cards', name: 'Cards', component: load('virtual/cards/index'), meta: { title: '客户列表' } },
{ path: '/orders/:type', name: 'Orders', component: load('virtual/orders/index'), meta: { title: '订单列表' } },
{ path: '/exports', name: 'StatsExports', component: load('exports/index'), meta: { title: '导出记录' } },

View File

@ -51,13 +51,7 @@
<ul class="handle-wraper">
<li class="handle-item w-250">
<Select
icon="ios-search"
placeholder="企业名称"
v-model.trim="params.company_name"
clearable
filterable
>
<Select placeholder="企业名称" v-model.trim="params.company_name" clearable filterable>
<Option
:key="index"
:value="item ? item : ''"
@ -75,13 +69,7 @@
</li>
<li class="handle-item w-250">
<Select
icon="ios-search"
placeholder="套餐名称"
v-model.trim="params.package_name"
clearable
filterable
>
<Select placeholder="套餐名称" v-model.trim="params.package_name" clearable filterable>
<Option
:key="index"
:value="item ? item : ''"
@ -134,7 +122,11 @@
</a>
</Row>
<Row v-else>
<b class="umar-r10"> {{filterNoUsedTotal}} <i>可用</i> / {{filterTotal}} </b>
<b class="umar-r10">
{{filterNoUsedTotal}}
<i>可用</i>
/ {{filterTotal}}
</b>
<a @click="selectAll">
<b>全选</b>
</a>

View File

@ -30,7 +30,6 @@ export default {
name: '',
company_id: '',
package_id: '',
flowed: 0,
price: 0,
renew_price: 0,
remark: '',
@ -75,11 +74,6 @@ export default {
return;
}
if (this.params.flowed && (this.params.price !== 0)) {
this.$Message.info('后向套餐的价格必须为零');
return;
}
this.params.type = this.type;
if (this.isUpdate) {
@ -116,7 +110,7 @@ export default {
},
clear() {
for (let k in this.params) {
if (k === 'price' || k === 'status' || k === 'flowed' || k === 'renew_price') {
if (k === 'price' || k === 'status' || k === 'renew_price') {
this.params[k] = 0;
} else {
this.params[k] = '';

View File

@ -0,0 +1,20 @@
<template>
<Drawer
:mask-closable="false"
@on-visible-change="visibleChange"
title="省份设置"
v-model="my_show"
width="450"
>
<div class="page-edit-wrap uinn-lr20">
<Table :columns="columns" :data="dataProvince"></Table>
</div>
<div class="ta-c">
<Button @click="clear" class="w-80 umar-r5" ghost type="primary">取消</Button>
<Button v-if="isUpdate" :loading="loading" @click="ok" class="w-80" type="primary">确认</Button>
</div>
</Drawer>
</template>
<script src="./js/edit.js"></script>

View File

@ -0,0 +1,109 @@
<template>
<div class="page-wrap">
<ui-loading :show="page_loading.show"></ui-loading>
<div class="page-handle-wrap">
<ul class="handle-wraper bd-b">
<li class="f-l">
<div class="text-exp">
<b>全部信息</b>
</div>
</li>
<li class="f-r">
<div class="handle-item lh-32">
<b class="umar-r10">编辑模式</b>
<Switch v-model="editModel" size="large">
<span slot="open"></span>
<span slot="close"></span>
</Switch>
</div>
<div class="handle-item">
<Button
v-if="editModel"
@click="ok"
icon="md-checkmark"
type="primary"
v-has="'update'"
>保存修改</Button>
</div>
<div class="handle-item">
<Button @click="settingsShow = true" icon="md-add" type="primary" v-has="'create'">配置管理</Button>
</div>
<div class="handle-item">
<Button @click="search.show=!search.show" ghost icon="ios-search" type="primary">搜索</Button>
</div>
<div class="handle-item">
<Button @click="index(1)" icon="md-refresh">刷新</Button>
</div>
</li>
</ul>
<div class="search-wrap" v-show="search.show">
<ul class="handle-wraper">
<li class="handle-item w-250">
<Select placeholder="企业名称" v-model.trim="params.company_id" clearable filterable>
<Option
:key="index"
:value="item.id"
v-for="(item, index) in companies"
>{{ item.name }}</Option>
</Select>
</li>
<li class="handle-item w-250">
<Select placeholder="套餐名称" v-model.trim="params.package_id" clearable filterable>
<Option
:key="index"
:value="item.id"
v-for="(item, index) in companies"
>{{ item.name }}</Option>
</Select>
</li>
<li class="f-r">
<div class="handle-item">
<Button @click="index(1)" ghost type="primary">立即搜索</Button>
</div>
<div class="handle-item">
<Button @click="resetSearch" ghost type="warning">重置搜索</Button>
</div>
</li>
</ul>
</div>
</div>
<div class="page-list-wrap">
<Table ref="table" :columns="columns" :data="showData ? showData : []"></Table>
</div>
<div class="page-turn-wrap" v-if="showData">
<Page
:current="Number(page.page)"
:page-size="Number(page.limit)"
:page-size-opts="[10, 20, 50, 100]"
:total="Number(page.total)"
@on-change="changePage"
@on-page-size-change="changeLimit"
show-elevator
show-total
show-sizer
></Page>
</div>
<ui-edit
:data="editObj.data"
:isUpdate="editObj.isUpdate"
:show.sync="editObj.show"
:provinces="settingsData.province ? settingsData.province : []"
@province-success="handleProvinceSuccess"
></ui-edit>
<ui-settings :data="settingsData" :show.sync="settingsShow" @store-success="updateSettings"></ui-settings>
</div>
</template>
<script src="./js/index.js"></script>

View File

@ -0,0 +1,106 @@
export default {
props: {
show: {
type: Boolean,
default: false
},
data: {
type: Object,
default () {
return null;
}
},
isUpdate: {
type: Boolean,
default () {
return false;
}
},
provinces: {
type: Array,
default () {
return [];
}
}
},
data() {
return {
my_show: false,
loading: false,
dataProvince: [],
edits: [],
columns: [
{
title: '省份',
key: 'province',
minWidth: 180
},
{
title: '占比',
minWidth: 120,
render: (h, context) => {
return h('InputNumber', {
props: {
max: 100,
min: 0,
value: context.row.percentages,
disabled: !this.isUpdate
},
on: {
input: (val) => {
this.edits = JSON.parse(JSON.stringify(this.dataProvince));
this.edits[context.index]['percentages'] = val;
}
}
});
}
}
]
};
},
watch: {
show(bool) {
this.my_show = bool;
if (bool) {
if (this.data) {
this.dataProvince = this.provinces.map(el => {
let percentages = (this.data.province && this.data.province[el]) ? Number(this.data.province[el]) : 0;
return { province: el, percentages };
});
}
}
}
},
methods: {
ok() {
let total = this.edits.reduce((acc, cur) => {
return acc + cur.percentages;
}, 0);
if (total !== 100) {
return this.$Message.error('占比总和必须为100');
}
let province = {};
for (const key in this.edits) {
const element = this.edits[key];
province[element.province] = element.percentages;
}
let data = JSON.parse(JSON.stringify(this.data));
data.province = province;
this.$emit('province-success', data);
this.clear();
},
visibleChange(bool) {
if (!bool) {
this.$emit('update:show', false);
}
},
clear() {
this.dataProvince = [];
this.my_show = false;
}
}
};

View File

@ -0,0 +1,359 @@
import * as API from 'api/virtual/properties';
export default {
name: 'Products',
components: {
UiEdit: resolve => require(['views/virtual/properties/edit'], resolve),
UiSettings: resolve => require(['views/virtual/properties/settings'], resolve)
},
data() {
return {
properties: [],
showData: [],
params: {
company_id: '',
package_id: ''
},
only: ['company_id', 'package_id', 'product', 'vehicle', 'commercial_vehicle', 'company', 'platform', 'customer', 'province'],
updates: [],
settingsShow: false,
settingsData: {},
editModel: false,
editObj: {
show: false,
isUpdate: false,
data: null
},
search: {
show: false
},
page: {
total: 0,
limit: 10,
page: 1
},
companies: [],
companyFilters: [],
packages: [],
packageFilters: [],
data: [],
columns: [
{
title: '序号',
key: '',
width: 80,
render: (h, context) => {
return h('span', context.row._index + 1);
}
},
{
title: '企业名称',
key: 'company_name',
width: 210,
tooltip: true
},
{
title: '套餐名称',
key: 'package_name',
width: 120
},
{
title: '月流量',
key: 'flows',
width: 100
},
{
title: '销售数量',
key: 'counts',
width: 100
},
{
title: '公司类型',
key: 'company',
minWidth: 170,
render: (h, context) => {
return this.editRender('company', h, context);
}
},
{
title: '产品类型',
key: 'product',
minWidth: 130,
render: (h, context) => {
return this.editRender('product', h, context);
}
},
{
title: '套餐类型',
key: 'package_type',
width: 100,
render: (h, context) => {
return h('span', this.productPackageTypes[context.row.product]);
}
},
{
title: '平台/API',
key: 'platform',
minWidth: 120,
render: (h, context) => {
return this.editRender('platform', h, context);
}
},
{
title: '车辆类型',
key: 'vehicle',
minWidth: 120,
render: (h, context) => {
return this.editRender('vehicle', h, context);
}
},
{
title: '商用车分类',
key: 'commercial_vehicle',
minWidth: 120,
render: (h, context) => {
return this.editRender('commercial_vehicle', h, context);
}
},
{
title: '客户类型',
key: 'customer',
minWidth: 120,
render: (h, context) => {
return this.editRender('customer', h, context);
}
},
{
title: '销售省份',
key: 'action',
width: 150,
render: (h, {
row,
column,
index
}) => {
let html = [];
if (this.haveJurisdiction('update')) {
let button = h('Button', {
props: {
type: row.province ? 'primary' : 'error',
size: 'small'
},
class: ['btn'],
on: {
click: (event) => {
this.editObj = { show: true, data: row, isUpdate: this.editModel };
}
}
}, this.editModel ? '设置' : '查看');
html.push(h('Tooltip', {
props: {
content: row.province ? '已设置' : '省份未设置'
}
}, [button]));
}
if (html.length) {
return h('div', html);
}
}
}
]
};
},
watch: {
editModel(value) {
this.columns = this.columns;
this.$refs.table.handleResize();
}
},
computed: {
productPackageTypes() {
let obj = {};
let packages = this.settingsData.package ? this.settingsData.package : {};
for (const key in packages) {
const element = packages[key];
for (let index = 0; index < element.length; index++) {
const value = element[index];
obj[value] = key;
}
}
return obj;
}
},
created() {
this.index();
this.settings();
},
methods: {
editRender(key, h, context) {
if (!this.editModel) {
return h('span', context.row[key]);
}
let options = [];
for (let index = 0; index < this.settingsData[key].length; index++) {
const element = this.settingsData[key][index];
options.push(h('Option', {
props: {
value: element
}
}, element));
}
return h('Select', {
props: {
value: context.row[key],
size: 'small'
},
on: {
input: (value) => {
let index = (this.page.page - 1) * this.page.limit + context.index;
this.properties[index][key] = value;
this.changePage(this.page.page);
this.updates[index] = this.properties[index];
}
}
}, options);
},
/**
* [index 列表]
* @param {Number} company_id [description]
* @return {[type]} [description]
*/
index(page = 1) {
if (!this.properties.length) {
this.isShowLoading(true);
API.index().then(res => {
if (res.code === 0) {
this.properties = res.data;
this.changePage(page);
this.complete();
}
this.isShowLoading(false);
});
} else {
this.changePage(page);
}
},
complete() {
let companies = {};
this.properties.map(function(item) {
companies[item.company_id] = item.company_name;
});
let companyArray = [];
for (const key in companies) {
companyArray.push({ id: key, name: companies[key] });
}
this.companies = companyArray;
let packages = {};
this.properties.map(function(item) {
packages[item.package_id] = item.package_name;
});
let packageArray = [];
for (const key in packages) {
packageArray.push({ id: key, name: packages[key] });
}
this.packages = packageArray;
},
/**
* [request 刷新]
* @return {[type]} [description]
*/
request() {
this.index();
},
resetSearch() {
for (let k in this.params) {
this.params[k] = '';
}
this.index(1);
},
changeLimit(limit) {
this.page.limit = limit;
this.changePage(1);
},
changePage(page) {
this.page.page = page;
let properties = JSON.parse(JSON.stringify(this.properties));
if (this.params.company_id !== '' && this.params.company_id !== undefined) {
properties = properties.filter(el => { return el.company_id == this.params.company_id; });
}
if (this.params.package_id !== '' && this.params.package_id !== undefined) {
properties = properties.filter(el => { return el.package_id == this.params.package_id; });
}
this.page.total = properties.length;
this.showData = properties.slice((page - 1) * this.page.limit, page * this.page.limit);
},
settings() {
if (!this.settingsData.length) {
API.settings().then(res => {
if (res.code === 0) {
this.settingsData = res.data;
}
});
}
},
updateSettings(values) {
this.settingsData = values;
},
ok() {
if (!this.updates.length) {
this.$Message.warning('数据未修改');
this.editModel = false;
return;
}
this.isShowLoading(true);
let updates = [];
for (let index = 0; index < this.updates.length; index++) {
const element = this.updates[index];
let obj = {};
for (const key in element) {
if (this.only.indexOf(key) !== -1) {
obj[key] = element[key];
}
}
updates.push(obj);
}
API.store({ data: updates }).then(res => {
if (res.code === 0) {
this.updates = [];
this.editModel = false;
}
this.isShowLoading(false);
});
},
handleProvinceSuccess(data) {
let index = (this.page.page - 1) * this.page.limit + data._index;
this.properties[index] = data;
this.changePage(this.page.page);
this.updates[index] = this.properties[index];
}
}
};

View File

@ -0,0 +1,213 @@
import * as API from 'api/virtual/properties';
import draggable from 'vuedraggable';
export default {
props: {
show: {
type: Boolean,
default: false
},
data: {
type: Object,
default: {}
}
},
components: {
draggable
},
data() {
return {
my_show: false,
loading: false,
settings: {},
selectedTab: '',
completePackagesFilter: [],
dragOptions: {
animation: 0,
group: "description",
ghostClass: "ghost"
}
};
},
computed: {
packages: {
get() {
return this.settings.package ? this.settings.package : {};
},
set(value) {
console.log('packages', value);
}
},
products: {
get() {
let products = this.settings.product ? this.settings.product : [];
let packages = this.settings.package ? this.settings.package : {};
let values = [];
for (const key in packages) {
values = values.concat(packages[key]);
}
return products.filter(v => {
return !values.includes(v);
});
},
set(array) {
let products = this.settings.product ? this.settings.product : [];
let values = products.filter(v => {
return !array.includes(v);
});
this.settings.product = values.concat(array);
}
}
},
watch: {
show(bool) {
this.my_show = bool;
if (bool) {
if (this.data) {
this.settings = JSON.parse(JSON.stringify(this.data));
if (this.settings.package_type && this.settings.package_type.length) {
this.selectedTab = this.settings.package_type[0];
}
}
}
},
settings(obj) {
if (JSON.stringify(obj) != "{}") {
this.isShowLoading(false);
} else {
this.isShowLoading(true);
}
}
},
methods: {
ok() {
this.loading = true;
API.settingsStore({ data: this.settings }).then(res => {
this.loading = false;
if (res.code == 0) {
this.$Message.success('修改成功');
this.clear();
this.$emit('store-success', this.settings);
}
}).catch(err => {
this.loading = false;
});
},
visibleChange(bool) {
if (!bool) {
this.$emit('update:show', false);
}
},
clear() {
this.my_show = false;
},
handelRemove(key, value) {
this.$Modal.confirm({
title: '提示',
content: '已设置的属性值不会因删除改变,确认是否还要删除',
onOk: () => {
let node = key === 'package' ? this.settings[key][this.selectedTab] : this.settings[key];
let index = node.indexOf(value);
node.splice(index, 1);
if (key === 'package') {
this.settings[key][this.selectedTab] = node;
this.settings.product.splice(this.settings.product.indexOf(value), 1);
} else {
this.settings[key] = node;
}
}
});
},
handleAdd(key) {
let value = '';
let node = key === 'package' ? this.settings[key][this.selectedTab] : this.settings[key];
this.$Modal.confirm({
render: (h) => {
return h('Input', {
props: {
value: value,
autofocus: true,
placeholder: '请输入名称'
},
on: {
input: (val) => {
value = val;
}
}
});
},
onOk: () => {
if (value === '') {
return this.$Message.error('名称不能为空');
}
node.push(value);
if (key === 'package') {
this.settings[key][this.selectedTab] = node;
this.settings.product.push(value);
} else {
this.settings[key] = node;
}
}
});
},
handleRemovePackageType(val) {
return new Promise(resolve => {
this.$Modal.confirm({
title: '提示',
content: '删除套餐分类将同时删除分类及分类下的产品,但已设置的属性值不会因删除改变,请谨慎操作',
onOk: () => {
resolve(true);
},
onCannel: () => {
resolve(false);
}
});
});
},
handleAddPackageType() {
let value = '';
this.$Modal.confirm({
render: (h) => {
return h('Input', {
props: {
value: value,
autofocus: true,
placeholder: '请输入套餐分类名称'
},
on: {
input: (val) => {
value = val;
}
}
});
},
onOk: () => {
if (value === '') {
return this.$Message.error('名称不能为空');
}
let package_type = this.settings.package_type ? this.settings.package_type : [];
package_type.push(value);
this.settings.package_type = package_type;
}
});
},
onChange(event) {
let packages = this.packages[this.selectedTab];
this.settings.package[this.selectedTab] = packages;
}
}
};

View File

@ -0,0 +1,185 @@
<template>
<Modal
:closable="false"
:mask-closable="false"
:title="'配置管理'"
@on-visible-change="visibleChange"
v-model="my_show"
width="750"
>
<div class="page-edit-wrap uinn-lr20">
<ui-loading :show="page_loading.show"></ui-loading>
<ul>
<li class="ui-list">
<div class="ui-list-title">车辆类型:</div>
<div class="ui-list-content lh-32">
<Tag
v-for="item in settings.vehicle ? settings.vehicle : []"
:key="item"
:name="item"
closable
@on-close="handelRemove('vehicle', item)"
>{{item}}</Tag>
<Button icon="ios-add" type="dashed" size="small" @click="handleAdd('vehicle')">添加</Button>
</div>
</li>
<li class="ui-list">
<div class="ui-list-title">商用车分类:</div>
<div class="ui-list-content lh-32">
<Tag
v-for="item in settings.commercial_vehicle ? settings.commercial_vehicle : []"
:key="item"
:name="item"
closable
@on-close="handelRemove('commercial_vehicle', item)"
>{{item}}</Tag>
<Button
icon="ios-add"
type="dashed"
size="small"
@click="handleAdd('commercial_vehicle')"
>添加</Button>
</div>
</li>
<li class="ui-list">
<div class="ui-list-title">公司类型:</div>
<div class="ui-list-content lh-32">
<Tag
v-for="item in settings.company ? settings.company : []"
:key="item"
:name="item"
closable
@on-close="handelRemove('company', item)"
>{{item}}</Tag>
<Button icon="ios-add" type="dashed" size="small" @click="handleAdd('company')">添加</Button>
</div>
</li>
<li class="ui-list">
<div class="ui-list-title">平台/API类型:</div>
<div class="ui-list-content lh-32">
<Tag
v-for="item in settings.platform ? settings.platform : []"
:key="item"
:name="item"
closable
@on-close="handelRemove('platform', item)"
>{{item}}</Tag>
<Button icon="ios-add" type="dashed" size="small" @click="handleAdd('platform')">添加</Button>
</div>
</li>
<li class="ui-list">
<div class="ui-list-title">客户类型:</div>
<div class="ui-list-content lh-32">
<Tag
v-for="item in settings.customer ? settings.customer : []"
:key="item"
:name="item"
closable
@on-close="handelRemove('customer', item)"
>{{item}}</Tag>
<Button icon="ios-add" type="dashed" size="small" @click="handleAdd('customer')">添加</Button>
</div>
</li>
<li class="ui-list">
<div class="ui-list-title">套餐分类:</div>
<div class="ui-list-content lh-32">
<Tabs
type="card"
closable
v-model="selectedTab"
:before-remove="handleRemovePackageType"
>
<TabPane
v-for="item in settings.package_type ? settings.package_type : []"
:key="item"
:label="item"
:name="item"
>
<div
class="package-content"
:class="item === selectedTab ? 'package-content-active' : ''"
>
<draggable
draggable=".item"
v-bind="dragOptions"
:list="packages[item]"
@change="onChange"
>
<Tag
class="item"
v-for="pitem in packages[item]"
:key="pitem"
:name="pitem"
closable
@on-close="handelRemove('package', pitem)"
>{{pitem}}</Tag>
</draggable>
<Button icon="ios-add" type="dashed" size="small" @click="handleAdd('package')">添加</Button>
</div>
</TabPane>
<Button
@click="handleAddPackageType"
icon="ios-add"
type="dashed"
size="small"
slot="extra"
>添加</Button>
</Tabs>
</div>
</li>
<li class="ui-list">
<div class="ui-list-title">未分类产品:</div>
<div class="ui-list-content lh-32">
<draggable draggable=".item" v-bind="dragOptions" v-model="products">
<Tag
class="item"
v-for="item in products"
:key="item"
:name="item"
closable
@on-close="handelRemove('product', item)"
>{{item}}</Tag>
</draggable>
<Button icon="ios-add" type="dashed" size="small" @click="handleAdd('product')">添加</Button>
</div>
</li>
</ul>
</div>
<footer class="ta-c" slot="footer">
<Button @click="clear" class="w-80" ghost type="primary">取消</Button>
<Button :loading="loading" @click="ok" class="w-80" type="primary">提交</Button>
</footer>
</Modal>
</template>
<script src="./js/settings.js"></script>
<style scoped>
>>> .ivu-tabs-bar {
margin-bottom: 0;
}
>>> .ivu-tabs.ivu-tabs-card > .ivu-tabs-bar .ivu-tabs-tab-active {
border-color: #2d8cf0;
border-bottom-color: #dcdee2;
}
</style>
<style lang="less" scoped>
.package-content {
border: 1px solid #dcdee2;
padding: 16px;
}
.package-content-active {
border-color: #2d8cf0;
}
</style>

View File

@ -11,12 +11,11 @@ return array(
'AssertionError' => $vendorDir . '/symfony/polyfill-php70/Resources/stubs/AssertionError.php',
'CompanyAccountSeeder' => $baseDir . '/database/seeds/CompanyAccountSeeder.php',
'ConfigSeeder' => $baseDir . '/database/seeds/ConfigSeeder.php',
'CreateBlocsTable' => $baseDir . '/database/migrations/2018_12_24_164210_create_blocs_table.php',
'CreateCardsTable' => $baseDir . '/database/migrations/2018_12_24_164218_create_cards_table.php',
'CreateCompanyRelations' => $baseDir . '/database/migrations/2019_03_08_110806_create_company_relations.php',
'CreateFailedJobsTable' => $baseDir . '/database/migrations/0000_00_00_000000_create_failed_jobs_table.php',
'CreateFlowPoolTables' => $baseDir . '/database/migrations/2019_01_24_175246_create_flow_pool_tables.php',
'CreateJobsTable' => $baseDir . '/database/migrations/0000_00_00_000000_create_jobs_table.php',
'CreatePropertyTables' => $baseDir . '/database/migrations/2019_04_02_101740_create_property_tables.php',
'CreateRealCompaniesTable' => $baseDir . '/database/migrations/2018_12_24_164318_create_real_companies_table.php',
'CreateRealOrderCardsTable' => $baseDir . '/database/migrations/2018_12_24_164434_create_real_order_cards_table.php',
'CreateRealOrdersTable' => $baseDir . '/database/migrations/2018_12_24_164430_create_real_orders_table.php',

View File

@ -730,12 +730,11 @@ class ComposerStaticInite79258a3e34ad3e251999111d9f334d9
'AssertionError' => __DIR__ . '/..' . '/symfony/polyfill-php70/Resources/stubs/AssertionError.php',
'CompanyAccountSeeder' => __DIR__ . '/../..' . '/database/seeds/CompanyAccountSeeder.php',
'ConfigSeeder' => __DIR__ . '/../..' . '/database/seeds/ConfigSeeder.php',
'CreateBlocsTable' => __DIR__ . '/../..' . '/database/migrations/2018_12_24_164210_create_blocs_table.php',
'CreateCardsTable' => __DIR__ . '/../..' . '/database/migrations/2018_12_24_164218_create_cards_table.php',
'CreateCompanyRelations' => __DIR__ . '/../..' . '/database/migrations/2019_03_08_110806_create_company_relations.php',
'CreateFailedJobsTable' => __DIR__ . '/../..' . '/database/migrations/0000_00_00_000000_create_failed_jobs_table.php',
'CreateFlowPoolTables' => __DIR__ . '/../..' . '/database/migrations/2019_01_24_175246_create_flow_pool_tables.php',
'CreateJobsTable' => __DIR__ . '/../..' . '/database/migrations/0000_00_00_000000_create_jobs_table.php',
'CreatePropertyTables' => __DIR__ . '/../..' . '/database/migrations/2019_04_02_101740_create_property_tables.php',
'CreateRealCompaniesTable' => __DIR__ . '/../..' . '/database/migrations/2018_12_24_164318_create_real_companies_table.php',
'CreateRealOrderCardsTable' => __DIR__ . '/../..' . '/database/migrations/2018_12_24_164434_create_real_order_cards_table.php',
'CreateRealOrdersTable' => __DIR__ . '/../..' . '/database/migrations/2018_12_24_164430_create_real_orders_table.php',

View File

@ -179,7 +179,7 @@ class DatabaseServiceProvider extends ServiceProvider
*/
public function macroUpsert()
{
return function (array $values, $fields = ['id'], $doNoting = false) {
return function (array $values, $fields = ['id'], $only = null) {
if (empty($values)) {
return false;
}
@ -193,7 +193,7 @@ class DatabaseServiceProvider extends ServiceProvider
}
return $this->connection->affectingStatement(
$this->grammar->compileUpsert($this, $values, $fields, $doNoting),
$this->grammar->compileUpsert($this, $values, $fields, $only),
$this->cleanBindings(Arr::flatten($values, 1))
);
};