diff --git a/app/Dicts.php b/app/Dicts.php index 79c8ae8b..c92c89ef 100644 --- a/app/Dicts.php +++ b/app/Dicts.php @@ -27,6 +27,7 @@ class Dicts extends Repository 'tables' => ['real' => 'RD', 'virtual' => 'VD'], 'order_status' => ['已下单', '已取消', '已出库', '已签收'], 'transaction_status' => ['未收款', '已收款'], + 'logistics' => ['sf' => '顺丰速运', 'sto' => '申通快递','yto' => '圆通速递', 'zto' => '中通快递', 'best' => '百世快递', 'yunda' => '韵达快递', 'ttkd'=> '天天快递', 'ems' => 'EMS邮政特快专递'], ]; public function __construct() diff --git a/app/Domains/Company/Http/Controllers/OrderController.php b/app/Domains/Company/Http/Controllers/OrderController.php index 4c7d07d6..96b7f20a 100644 --- a/app/Domains/Company/Http/Controllers/OrderController.php +++ b/app/Domains/Company/Http/Controllers/OrderController.php @@ -39,7 +39,6 @@ class OrderController extends Controller $carrierOperators = $dicts->get('carrier_operator'); $orderStatues = $dicts->get('order_status'); $transactionStatuses = $dicts->get('transaction_status'); - $res->transform(function ($item) use ($carrierOperators, $orderStatues, $transactionStatuses) { return [ diff --git a/app/Domains/Virtual/Http/Controllers/OrderController.php b/app/Domains/Virtual/Http/Controllers/OrderController.php new file mode 100644 index 00000000..48f3e8a0 --- /dev/null +++ b/app/Domains/Virtual/Http/Controllers/OrderController.php @@ -0,0 +1,107 @@ +request = $request; + $this->orderService = $orderService; + } + + /** + * 列表. + * + * @return \Illuminate\Http\Response + */ + public function paginate(Dicts $dicts) + { + $conditions = $this->request->all(); + $conditions['limit'] = $this->request->get('limit', 20); + + $orders = $this->orderService->paginate($conditions); + + $carrierOperators = $dicts->get('carrier_operator'); + $orderStatues = $dicts->get('order_status'); + $transactionStatuses = $dicts->get('transaction_status'); + + $orders->transform(function ($item) use ($carrierOperators, $orderStatues, $transactionStatuses) { + return [ + 'id' => $item->id, + 'sn' => $item->sn, + 'package_name' => $item->package->name, + 'company_name' => $item->company->name, + 'pay_channel' => CommonService::namePayChannel($item->pay_channel), + 'carrier_operator' => $carrierOperators[$item->package->carrier_operator], + 'unit_price' => $item->unit_price, + 'counts' => $item->counts, + 'total_price' => $item->total_price, + 'custom_price' => $item->custom_price, + 'order_status' => $item->order_status, + 'order_status_name' => $orderStatues[$item->order_status], + 'transaction_status' => $item->transaction_status, + 'transaction_status_name' => $transactionStatuses[$item->transaction_status], + 'order_at' => $item->order_at, + ]; + }); + + return res($orders, '订单列表', 201); + } + + + /** + * 订单详情 + */ + public function show(Dicts $dicts, $id) + { + if (!$order = app(OrderRepository::class)->find($id)) { + throw new NotExistException('订单不存在或已删除'); + } + + $logistics = app(ConfigService::class)->get('logistics'); + + $carrierOperators = $dicts->get('carrier_operator'); + $orderStatues = $dicts->get('order_status'); + $transactionStatuses = $dicts->get('transaction_status'); + + $order->load(['company:id,name', 'package:id,name,carrier_operator']); + + $order->pay_channel = CommonService::namePayChannel($order->pay_channel); + $order->carrier_operator = $carrierOperators[$order->package->carrier_operator]; + $order->order_status_name = $orderStatues[$order->order_status]; + $order->transaction_status_name = $transactionStatuses[$order->transaction_status]; + $order->logistics_company_name = $logistics[$order->logistics_company] ?? ''; + + return res($order, '订单详情', 201); + } + + /** + * 编辑. + * + * @return \Illuminate\Http\Response + */ + public function update($id) + { + $attributes = $this->request->all(); + $attributes['id'] = $id; + + $order = $this->orderService->store($attributes); + + return res($order, '修改成功'); + } +} diff --git a/app/Domains/Virtual/Routes/api.php b/app/Domains/Virtual/Routes/api.php index 1f71d03e..202212fe 100644 --- a/app/Domains/Virtual/Routes/api.php +++ b/app/Domains/Virtual/Routes/api.php @@ -34,7 +34,14 @@ $router->group(['prefix' => 'virtual', 'as' => 'virtual', 'middleware' => ['admi $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->get('/orders/paginate', ['as' => 'orders/paginate', 'uses' => 'OrderController@paginate']); + $router->get('/orders/show/{id}', ['as' => 'orders/show', 'uses' => 'OrderController@show']); + $router->post('/orders/create', ['as' => 'orders.create', 'uses' => 'OrderController@create']); + $router->post('/orders/update/{id}', ['as' => 'orders/update', 'uses' => 'OrderController@update']); + $router->post('/orders/destroy', ['as' => 'orders.destroy', 'uses' => 'OrderController@destroy']); + /** * 需要认证的接口 */ diff --git a/app/Domains/Virtual/Services/OrderService.php b/app/Domains/Virtual/Services/OrderService.php index 8e5cac5d..82ff15a1 100644 --- a/app/Domains/Virtual/Services/OrderService.php +++ b/app/Domains/Virtual/Services/OrderService.php @@ -78,14 +78,14 @@ class OrderService extends Service $attributes['sn'] = $attributes['sn'] ?: $this->generateSn(); $rule = [ - 'company_id' => ['required', 'exists:virtual_companies,id'], - 'product_id' => ['required'], - 'counts' => ['required'], - 'pay_channel' => ['required', Rule::in(array_collapse(app(Dicts::class)->get('pay_channel')))], - 'contacts' => ['required', 'display_length:2,32'], - 'mobile' => ['required', 'cn_phone'], - 'area' => ['required', 'max:255'], - 'address' => ['required', 'max:255'], + 'company_id' => ['exists:virtual_companies,id'], + 'product_id' => [], + 'counts' => [], + 'pay_channel' => [Rule::in(array_collapse(app(Dicts::class)->get('pay_channel')))], + 'contacts' => ['display_length:2,32'], + 'mobile' => ['cn_phone'], + 'area' => ['max:255'], + 'address' => ['max:255'], ]; $message = [ @@ -103,23 +103,34 @@ class OrderService extends Service 'address.required' => '请输入详细地址', ]; - Validator::validate($attributes, $rule, $message); - - if (!$product = app(ProductRepository::class)->withConditions(['id' => $attributes['product_id']])->first()) { - throw new NotExistException('套餐不存在或已删除'); - } - - if ($product->company_id != $attributes['company_id']) { - throw new NotAllowedException('非法操作'); - } - - $attributes['unit_price'] = $product->base_price; - $attributes['total_price'] = $attributes['unit_price'] * $attributes['counts']; - $attributes['custom_price'] = $attributes['unit_price'] * $attributes['counts']; - $attributes['order_at'] = date('Y-m-d H:i:s'); - $attributes['package_id'] = $attributes['package_id'] ?? $product->package_id; - if (!$attributes['id']) { + $rule['company_id'][] = 'required'; + $rule['product_id'][] = 'required'; + $rule['counts'][] = 'required'; + $rule['pay_channel'][] = 'required'; + $rule['contacts'][] = 'required'; + $rule['mobile'][] = 'required'; + $rule['area'][] = 'required'; + $rule['address'][] = 'required'; + } + + Validator::validate($attributes, $rule, $message); + + if (!$attributes['id']) { + if (!$product = app(ProductRepository::class)->withConditions(['id' => $attributes['product_id']])->first()) { + throw new NotExistException('套餐不存在或已删除'); + } + + if ($product->company_id != $attributes['company_id']) { + throw new NotAllowedException('非法操作'); + } + + $attributes['unit_price'] = $product->base_price; + $attributes['total_price'] = $attributes['unit_price'] * $attributes['counts']; + $attributes['custom_price'] = $attributes['unit_price'] * $attributes['counts']; + $attributes['order_at'] = $attributes['order_at'] ?? date('Y-m-d H:i:s'); + $attributes['package_id'] = $attributes['package_id'] ?? $product->package_id; + $node = $this->orderRepository->create($attributes); } diff --git a/database/migrations/2018_12_12_170419_create_virtual_orders_table.php b/database/migrations/2018_12_12_170419_create_virtual_orders_table.php index a9ca4894..4b9c5ab1 100644 --- a/database/migrations/2018_12_12_170419_create_virtual_orders_table.php +++ b/database/migrations/2018_12_12_170419_create_virtual_orders_table.php @@ -31,6 +31,8 @@ class CreateVirtualOrdersTable extends Migration $table->string('address')->default('')->comment('收货地址'); $table->string('contacts')->default('')->comment('联系人'); $table->string('mobile')->default('')->comment('电话'); + $table->string('logistics_company', '20')->default('')->comment('物流公司'); + $table->string('logistics_no', 64)->default('')->comment('物流单号'); $table->tinyInteger('order_status')->unsigned()->default(0)->after('mobile')->comment('订单状态(0:已下单 1:已取消 2:已出库 3:已签收)'); $table->tinyInteger('transaction_status')->unsigned()->default(0)->after('order_status')->comment('收款状态(0:未收款 1:已收款)'); $table->text('logistics_remark')->nullable()->comment('物流备注'); diff --git a/frontend/public/config.js b/frontend/public/config.js index ddadee67..592c2075 100644 --- a/frontend/public/config.js +++ b/frontend/public/config.js @@ -1,9 +1,9 @@ var CONFIG = { - title: "车联网企业管理平台", - login_background: "/assets/login_background.jpg", - logo_big: "/assets/logo_big.png", - logo_small: "/assets/logo_small.jpg", - url: "http://v.denghaoyuan.cn" -} + title: '流量卡BOSS管理平台', + login_background: '/assets/login_background.jpg', + logo_big: '/assets/logo_big.png', + logo_small: '/assets/logo_small.jpg', + url: 'http://v.denghaoyuan.cn' +}; -window.CONFIG = CONFIG; \ No newline at end of file +window.CONFIG = CONFIG; diff --git a/frontend/src/api/virtual/configs.js b/frontend/src/api/virtual/configs.js new file mode 100644 index 00000000..f60da159 --- /dev/null +++ b/frontend/src/api/virtual/configs.js @@ -0,0 +1,31 @@ +/** + * 配置 + */ + +/** + * [get 获取配置] + * @param {[type]} key [description] + * @return {[type]} [description] + */ +export function get(key) { + return service.get('api/configs/get', { + params: { + key: key + } + }); +} + +/** + * [set 修改配置] + * @param {[type]} key [description] + * @param {[type]} value [description] + * @return {[type]} [description] + */ +export function set(key, value) { + return service.get('api/configs/set', { + params: { + key: key, + value: value + } + }); +} diff --git a/frontend/src/api/virtual/orders.js b/frontend/src/api/virtual/orders.js new file mode 100644 index 00000000..287f082b --- /dev/null +++ b/frontend/src/api/virtual/orders.js @@ -0,0 +1,51 @@ +/** + * 订单管理 + */ + +/** + * [paginate 订单列表] + * @param {[type]} data [description] + * @return {[type]} [description] + */ +export function paginate(data) { + return service.get('api/virtual/orders/paginate', { + params: data + }); +} + +/** + * [show 订单详情] + * @param {[type]} id [description] + * @return {[type]} [description] + */ +export function show(id) { + return service.get(`api/virtual/orders/show/${id}`); +} + +/** + * [create 创建订单] + * @param {[type]} data [description] + * @return {[type]} [description] + */ +export function create(data) { + return serviceForm.post('api/virtual/orders/create', data); +} + +/** + * [update 修改订单] + * @param {[type]} data [description] + * @param {[type]} id [角色id] + * @return {[type]} [description] + */ +export function update(data, id) { + return serviceForm.post(`api/virtual/orders/update/${id}`, data); +} + +/** + * [destroy 删除订单] + * @param {[type]} data [description] + * @return {[type]} [description] + */ +export function destroy(data) { + return service.post('api/virtual/orders/destroy', data); +} diff --git a/frontend/src/router/routes.js b/frontend/src/router/routes.js index 2afc9c7a..ca46da79 100644 --- a/frontend/src/router/routes.js +++ b/frontend/src/router/routes.js @@ -19,7 +19,8 @@ const routes = [ { path: '/iframe', name: 'Iframe', component: load('iframe/index'), meta: { title: 'iframe' } }, { path: '/companies', name: 'Companies', component: load('virtual/companies/index'), meta: { title: '企业管理' } }, { path: '/company/accounts', name: 'CompanyAccounts', component: load('virtual/company_accounts/index'), meta: { title: '账号管理' } }, - { path: '/products', name: 'Products', component: load('virtual/products/index'), meta: { title: '定价管理' } } + { path: '/products', name: 'Products', component: load('virtual/products/index'), meta: { title: '定价管理' } }, + { path: '/orders', name: 'Orders', component: load('virtual/orders/index'), meta: { title: '订单列表' } } ] }, { path: '*', redirect: { path: '/home' } } diff --git a/frontend/src/views/virtual/orders/detail.vue b/frontend/src/views/virtual/orders/detail.vue new file mode 100644 index 00000000..19b37aae --- /dev/null +++ b/frontend/src/views/virtual/orders/detail.vue @@ -0,0 +1,108 @@ + + + + diff --git a/frontend/src/views/virtual/orders/edit.vue b/frontend/src/views/virtual/orders/edit.vue new file mode 100644 index 00000000..927769fd --- /dev/null +++ b/frontend/src/views/virtual/orders/edit.vue @@ -0,0 +1,95 @@ + + + diff --git a/frontend/src/views/virtual/orders/index.vue b/frontend/src/views/virtual/orders/index.vue new file mode 100644 index 00000000..3c81aa3a --- /dev/null +++ b/frontend/src/views/virtual/orders/index.vue @@ -0,0 +1,77 @@ + + + diff --git a/frontend/src/views/virtual/orders/js/detail.js b/frontend/src/views/virtual/orders/js/detail.js new file mode 100644 index 00000000..f503418c --- /dev/null +++ b/frontend/src/views/virtual/orders/js/detail.js @@ -0,0 +1,29 @@ +export default{ + props: { + show: { + type: Boolean, + default: false + }, + data: { + type: Object, + default() { + return null; + } + } + }, + watch: { + show(bool) { + this.my_show = bool; + } + }, + data() { + return { + my_show: false + }; + }, + methods: { + visibleChange(bool) { + this.$emit('update:show', bool); + } + } +}; diff --git a/frontend/src/views/virtual/orders/js/edit.js b/frontend/src/views/virtual/orders/js/edit.js new file mode 100644 index 00000000..bd436115 --- /dev/null +++ b/frontend/src/views/virtual/orders/js/edit.js @@ -0,0 +1,102 @@ +import * as API from 'api/virtual/orders'; + +export default { + props: { + show: { + type: Boolean, + default: false + }, + data: { + type: Object, + default () { + return null; + } + } + }, + data() { + return { + my_show: false, + isUpdate: false, + loading: false, + params: { + name: '', + contacts: '', + mobile: '', + address: '', + remark: '', + extends: { + bank_account: '', + wechat_account: '', + alipay_account: '' + } + } + }; + }, + watch: { + show(bool) { + this.my_show = bool; + if (bool) { + if (this.data) { + for (let k in this.data) { + if (k in this.params) { + this.params[k] = this.data[k]; + } + } + } + } + } + }, + methods: { + ok() { + if (!this.params.name) { + this.$Message.info('请填写企业名称'); + return; + } + + if (!(/[\s\S]{2,32}/.test(this.params.contacts))) { + this.$Message.info('联系人长度在2-32之间'); + return; + } + + if (this.data) { + // 编辑 + API.update(this.params, this.data.id).then(res => { + this.loading = false; + if (res.code == 0) { + this.$emit('update-success'); + this.$Message.success('更新成功'); + this.clear(); + } + }).catch(err => { + this.loading = false; + }); + } else { + // 添加 + API.create(this.params).then(res => { + this.loading = false; + if (res.code == 0) { + this.$emit('add-success'); + this.$Message.success('添加成功'); + this.clear(); + } + }).catch(err => { + this.loading = false; + }); + } + }, + + visibleChange(bool) { + if (!bool) { + this.$emit('update:show', false); + } + }, + + clear() { + for (let k in this.params) { + this.params[k] = ''; + } + + this.my_show = false; + } + } +}; diff --git a/frontend/src/views/virtual/orders/js/index.js b/frontend/src/views/virtual/orders/js/index.js new file mode 100644 index 00000000..4273e1a6 --- /dev/null +++ b/frontend/src/views/virtual/orders/js/index.js @@ -0,0 +1,390 @@ +import * as API from 'api/virtual/orders'; +import * as CONFIGS from 'api/virtual/configs'; +export default { + name: 'Orders', + components: { + UiEdit: resolve => require(['views/virtual/orders/edit'], resolve), + UiDetail: resolve => require(['views/virtual/orders/detail'], resolve) + }, + data() { + return { + params: { + name: '' + }, + trashed: '', + list_data: null, + editObj: { + show: false, + data: null + }, + detailObj: { + show: false, + data: null + }, + search: { + show: false + }, + logistics: null, + logisticsParams: { + 'logistics_company': '', + 'logistics_no': '' + }, + table_titles: [ + { + title: '订单编号', + key: 'sn', + width: 200 + }, + { + title: '企业名称', + key: 'company_name', + width: 300 + }, + { + title: '运营商', + key: 'carrier_operator', + width: 90 + }, + { + title: '套餐名称', + key: 'package_name', + width: 110 + }, + { + title: '套餐单价(元/周期)', + key: '', + width: 160, + render: (h, { row, column, index }) => { + let price = Number(row.unit_price / 100); + return h('span', price.toFixed(2)); + } + }, + { + title: '订单卡量(张)', + key: 'counts', + width: 135 + }, + { + title: '订单金额(元)', + key: '', + width: 135, + render: (h, { row, column, index }) => { + let price = Number(row.custom_price / 100); + return h('span', price.toFixed(2)); + } + }, + { + title: '订单状态', + key: '', + width: 100, + render: (h, { row, column, index }) => { + let html = []; + + html.push(h('Button', { + props: { + type: 'primary', + size: 'small' + } + }, row.order_status_name)); + + return h('div', html); + } + }, + { + title: '支付状态', + key: '', + width: 100, + render: (h, { row, column, index }) => { + let html = []; + + html.push(h('Button', { + props: { + type: row.transaction_status ? 'success' : 'error', + size: 'small' + } + }, row.transaction_status_name)); + + return h('div', html); + } + }, + { + title: '下单时间', + key: 'order_at', + width: 170 + }, + { + title: '操作', + key: 'action', + width: 340, + render: (h, { + row, + column, + index + }) => { + let html = []; + + if (row.deleted_at) { + return h('Tag', { props: { color: 'default' } }, '该企业已被删除'); + } + + if (this.haveJurisdiction('index')) { + html.push(h('Button', { + props: { + type: 'success', + size: 'small', + disabled: false, + icon: 'md-eye' + }, + class: ['btn'], + on: { + click: (event) => { + this.isShowLoading(true); + API.show(row.id).then(res => { + this.isShowLoading(false); + if (res.code === 0) { + this.detailObj = { + show: true, + data: res.data + }; + } + }).catch(() => { this.isShowLoading(false); }); + } + } + }, '查看')); + } + + if (this.haveJurisdiction('update')) { + if (!row.transaction_status && !row.order_status) { + html.push(h('Button', { + props: { + type: 'info', + size: 'small', + disabled: false + }, + class: ['btn'], + on: { + click: () => { + this.$Modal.confirm({ + title: '提示', + content: '是否确认取消订单?', + onOk: () => { + API.update({ + order_status: 1 + }, row.id).then(res => { + if (res.code == 0) { + this.$Message.success('修改成功'); + this.request(); + } + }); + } + }); + } + } + }, '取消订单')); + } + + if (!row.transaction_status) { + html.push(h('Button', { + props: { + type: 'error', + size: 'small', + disabled: false + }, + class: ['btn'], + on: { + click: () => { + this.$Modal.confirm({ + title: '提示', + content: '请确认是否已收款?', + onOk: () => { + API.update({ + transaction_status: 1 + }, row.id).then(res => { + if (res.code == 0) { + this.$Message.success('修改成功'); + this.request(); + } + }); + } + }); + } + } + }, '确认收款')); + } + + if (row.order_status === 0) { + html.push(h('Button', { + props: { + type: 'warning', + size: 'small', + disabled: false + }, + class: ['btn'], + on: { + click: () => { + this.$Modal.confirm({ + title: '提示', + content: '请确认订单是否已出库?', + onOk: () => { + API.update({ + order_status: 2 + }, row.id).then(res => { + if (res.code == 0) { + this.$Message.success('修改成功'); + this.request(); + } + }); + } + }); + } + } + }, '确认出库')); + } + + if (row.order_status === 2) { + html.push(h('Button', { + props: { + type: 'warning', + size: 'small', + disabled: false + }, + class: ['btn'], + on: { + click: () => { + this.getLogistics().then(logistics => { + this.$Modal.confirm({ + title: '请确认订单是否已签收?', + render: (h) => { + let Options = []; + for (const key in logistics) { + Options.push(h('Option', { props: { key: key, value: key } }, logistics[key])); + } + + let Select = h('Select', { + props: { + value: this.logisticsParams.logistics_company, + placeholder: '请选择快递公司...' + }, + class: ['umar-b10'], + on: { + 'on-change': (val) => { + this.logisticsParams.logistics_company = val; + } + } + }, Options); + + let Input = h('Input', { + props: { + value: this.logisticsParams.logistics_no, + autofocus: true, + placeholder: '请输入快递单号...' + }, + on: { + 'input': (val) => { + this.logisticsParams.logistics_no = val; + } + } + }); + + return h('div', [Select, Input]); + }, + onOk: () => { + API.update({ + order_status: 3, + logistics_company: this.logisticsParams.logistics_company, + logistics_no: this.logisticsParams.logistics_no + }, row.id).then(res => { + if (res.code == 0) { + this.$Message.success('修改成功'); + this.request(); + } + }); + } + + }); + }); + } + } + }, '确认签收')); + } + } + + if (html.length) { + return h('div', html); + } + } + } + ] + }; + }, + created() { + this.index(1); + }, + methods: { + /** + * [index 列表] + * @param {Number} page [description] + * @return {[type]} [description] + */ + index(page = 1) { + let data = this.searchDataHandle(this.params, { page }); + this.isShowLoading(true); + API.paginate(data).then(res => { + this.isShowLoading(false); + if (res.code == 0) { + this.list_data = res.data; + } + }).catch(() => { + this.isShowLoading(false); + }); + }, + + /** + * [openEdit 打开编辑弹窗] + * @return {[type]} [description] + */ + openEdit(bool, data = null) { + this.editObj = { + show: bool, + data + }; + }, + + /** + * [request 刷新] + * @return {[type]} [description] + */ + request() { + const result = this.list_data; + let page = result.current_page; + + if (this.list_data.data.length == 1) { + page = this.returnPage(result.total, result.current_page, result.per_page); + } + + this.index(page); + }, + + resetSearch() { + for (let k in this.params) { + this.params[k] = ''; + } + this.trashed = ''; + this.index(1); + }, + getLogistics() { + return new Promise(resolve => { + if (this.logistics) { + resolve(this.logistics); + } else { + CONFIGS.get('logistics').then(res => { + if (res.code === 0) { + this.logistics = res.data; + } + resolve(this.logistics); + }); + } + }); + } + } +};