!277 京东云打印

Merge pull request !277 from 杨建炊/fix-release-1.0.0/yjc-migrate
This commit is contained in:
杨建炊 2024-12-28 08:10:57 +00:00 committed by Gitee
commit 7003cd9147
No known key found for this signature in database
GPG Key ID: 173E9B9CA92EEF8F
76 changed files with 5258 additions and 250 deletions

View File

@ -0,0 +1,40 @@
<?php
namespace App\Events;
use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
class CancelLogisticEvent
{
use Dispatchable, InteractsWithSockets, SerializesModels;
public $shopId;
public $orderSn;
/**
* Create a new event instance.
*
* @return void
*/
public function __construct($shopId, $orderSn)
{
$this->shopId = $shopId;
$this->orderSn = $orderSn;
}
/**
* Get the channels the event should broadcast on.
*
* @return \Illuminate\Broadcasting\Channel|array
*/
public function broadcastOn()
{
return new PrivateChannel('channel-name');
}
}

View File

@ -10,6 +10,9 @@ class BusinessOrderFilter extends Filters
{ {
protected function ids($value) protected function ids($value)
{ {
if(is_string($value)){
$value = explode(",", $value);
}
return $this->builder->whereIn('id', $value); return $this->builder->whereIn('id', $value);
} }

View File

@ -9,12 +9,14 @@ use App\Models\BusinessOrder;
use App\Models\BusinessOrderItem; use App\Models\BusinessOrderItem;
use App\Models\GoodsSku; use App\Models\GoodsSku;
use App\Models\Shop; use App\Models\Shop;
use App\Services\Ship\WayBillService; use App\Models\Waybill;
use App\Services\WayBill\JingDong\WayBillService;
use App\Utils\DateTimeUtils; use App\Utils\DateTimeUtils;
use Carbon\Carbon; use Carbon\Carbon;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use App\Http\Resources\BusinessOrderResource; use App\Http\Resources\BusinessOrderResource;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Maatwebsite\Excel\Facades\Excel; use Maatwebsite\Excel\Facades\Excel;
class BusinessOrderController extends Controller class BusinessOrderController extends Controller
@ -27,17 +29,25 @@ class BusinessOrderController extends Controller
$builder = BusinessOrder::query() $builder = BusinessOrder::query()
->with([ ->with([
'shop:id,name', 'shop:id,name',
'items:id,business_order_id,goods_name,goods_number,external_sku_id' 'items:id,business_order_id,goods_name,goods_number,external_sku_id',
"waybill"
]) ])
->whereIn('shop_id', $shopIds) ->whereIn('shop_id', $shopIds)
->filter(); ->filter();
$externalSkuIds = $request->get('external_sku_ids'); $externalSkuIds = $request->get('external_sku_ids');
$externalSkuId = $request->get('external_sku_id');
if (!empty($externalSkuId)) {
$externalSkuIds = $externalSkuId;
}
if ($externalSkuIds) { if ($externalSkuIds) {
if (is_string($externalSkuIds)) {
$externalSkuIds = explode(",", $externalSkuIds);
}
$ids = BusinessOrderItem::query()->whereIn('external_sku_id', $externalSkuIds)->pluck('business_order_id'); $ids = BusinessOrderItem::query()->whereIn('external_sku_id', $externalSkuIds)->pluck('business_order_id');
$builder->whereIn('id', $ids); $builder->whereIn('id', $ids);
} }
if($request->get("is_export")){ if ($request->get("is_export")) {
$params = $request->validate([ $params = $request->validate([
'confirm_at_start' => 'required', 'confirm_at_start' => 'required',
'confirm_at_end' => 'required', 'confirm_at_end' => 'required',
@ -52,7 +62,7 @@ class BusinessOrderController extends Controller
throw new \Exception("导出时间超出一个月"); throw new \Exception("导出时间超出一个月");
} }
return Excel::download(new BusinessOrderExport($builder->get()->toArray()), $startDate . '~' . $endDate."订单数据" . '.xlsx'); return Excel::download(new BusinessOrderExport($builder->get()->toArray()), $startDate . '~' . $endDate . "订单数据" . '.xlsx');
} }
$businessOrders = $builder->orderByDesc('confirm_at') $businessOrders = $builder->orderByDesc('confirm_at')
->paginate($request->get('per_page')); ->paginate($request->get('per_page'));
@ -177,24 +187,25 @@ class BusinessOrderController extends Controller
->whereIn('shop_id', $shopIds) ->whereIn('shop_id', $shopIds)
->filter(); ->filter();
$externalSkuIds = $request->get('external_sku_ids'); $externalSkuIds = $request->get('external_sku_ids');
if ($externalSkuIds) { if ($externalSkuIds) {
$ids = BusinessOrderItem::query()->whereIn('external_sku_id', $externalSkuIds)->pluck('business_order_id'); $ids = BusinessOrderItem::query()->whereIn('external_sku_id', $externalSkuIds)->pluck('business_order_id');
$builder->whereIn('id', $ids); $builder->whereIn('id', $ids);
} }
if ($ids = $request->input('ids')) { if ($ids = $request->input('ids')) {
if (is_string($ids)) {
$ids = explode(",", $ids);
}
$builder->whereIn('id', $ids); $builder->whereIn('id', $ids);
} }
$businessOrders = $builder->get(); $businessOrders = $builder->get();
$waybill = new WayBillService(); $waybill = new WayBillService();
$waybill->setOrders($businessOrders); $waybill->setOrders($businessOrders);
$contents = $waybill->getContents(); $contents = $waybill->getWayBillContents();
// 待打印数据
[$documents, $orderIds] = $waybill->getDocumentsAndOrderIds($contents);
return response([ return response([
'documents' => $this->combinationPrintDocuments($documents), 'data' => $contents
'order_ids' => implode(',', $orderIds),
]); ]);
} }
@ -251,9 +262,11 @@ class BusinessOrderController extends Controller
$orderIds = $request->input('order_ids'); $orderIds = $request->input('order_ids');
$orderIds = explode(',', $orderIds); $orderIds = explode(',', $orderIds);
BusinessOrder::query() BusinessOrder::query()
->where('id', $orderIds) ->whereIn('id', $orderIds)
->increment('print_status'); ->update(['print_status' => 1]);
Waybill::query()
->where('order_id', $orderIds)
->update(['status' => Waybill::$STATUS_PRINT_SUCCESS]);
return response(['message' => 'success']); return response(['message' => 'success']);
} }
} }

View File

@ -0,0 +1,74 @@
<?php
namespace App\Http\Controllers\Business;
use App\Events\BusinessOrderCancelEvent;
use App\Events\CancelLogisticEvent;
use App\Exports\BusinessOrderExport;
use App\Exports\OrderBlankExport;
use App\Http\Controllers\Controller;
use App\Models\BusinessOrder;
use App\Models\BusinessOrderItem;
use App\Models\GoodsSku;
use App\Models\Shop;
use App\Models\Waybill;
use App\Services\Business\BusinessFactory;
use App\Services\WayBill\JingDong\JingDongService;
use App\Services\WayBill\JingDong\WayBillService;
use App\Utils\DateTimeUtils;
use Carbon\Carbon;
use Illuminate\Http\Request;
use App\Http\Resources\BusinessOrderResource;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Maatwebsite\Excel\Facades\Excel;
class WaybillController extends Controller
{
public function queryTrace(Request $request)
{
$params = $request->validate([
'order_id' => 'required',
], [
'order_id.required' => '订单id',
]);
$waybill = Waybill::query()->where("order_id", $params['order_id'])->firstOrFail();
$jingDongService = new JingDongService();
return $jingDongService->queryTrace($waybill);
}
public function cancel(Request $request)
{
$params = $request->validate([
'order_id' => 'required',
], [
'order_id.required' => '订单id',
]);
$waybill = Waybill::query()->where("order_id", $params['order_id'])->firstOrFail();
if (empty($waybill->waybill_code)) {
throw new \Exception("无快递单号可取消");
}
try{
//取消快团团物流
$shop = Shop::query()->findOrFail($waybill->shop_id);
$client = BusinessFactory::init()->make($shop['plat_id'])->setShop($shop);
$client->cancelLogistic($waybill->order_sn, $waybill->waybill_code);
//取消京东物流
$jingDongService = new JingDongService();
$jingDongService->cancelOrder($waybill);
}catch(\Exception $exception) {
Log::error("取消快团团或者京东物流异常", ["error" => $exception->getMessage()]);
}
$waybill->waybill_code = '';
$waybill->encryptedData = '';
$waybill->status = Waybill::$STATUS_INIT;
$waybill->cancel = 1;
$waybill->save();
BusinessOrder::query()->where("id", $params['order_id'])->update(['print_status' => 0]);
return response(['message' => 'success']);
}
}

View File

@ -0,0 +1,67 @@
<?php
namespace App\Http\Controllers\Shop;
use App\Http\Controllers\Controller;
use App\Models\BusinessGoodsSku;
use App\Models\DeveloperConfig;
use App\Models\GoodsSku;
use App\Models\Shop;
use App\Http\Resources\ShopsResource;
use App\Models\ShopSender;
use App\Models\ShopShip;
use App\Services\Business\KuaiTuanTuan\FaceSheet;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Validator;
use App\Services\Business\BusinessFactory;
use Illuminate\Validation\Rule;
use App\Models\BusinessOrderItem;
class ShopSendsController extends Controller
{
public function index(Request $request)
{
$shopSender = ShopSender::query()->filter()->paginate($request->get('per_page'));
return JsonResource::collection($shopSender);
}
public function store(Request $request)
{
$params = $request->validate([
'id' => 'sometimes',
'shop_id' => 'required|int',
'province' => 'required',
'city' => 'required',
'district' => 'required',
'detail' => 'required',
'name' => 'required',
'mobile' => 'required',
'sort' => 'sometimes',
'wp_code' => 'sometimes',
], [
'shop_id.required' => '请选择店铺',
'province.required' => '请选择省份',
'city.required' => '请选择城市',
'district.required' => '请选择地区',
'detail.required' => '请填写详细地址',
]);
$params['wp_code'] = $params['wp_code'] ?? 'JD';
$params['country'] = $params['country'] ?? '中国';
if (empty($params['id'])) {
$shopSender = new ShopSender();
$shopSender->fill($params);
$shopSender->save();
} else {
ShopSender::query()->where('id', $params['id'])->update($params);
}
return response($this->res, $this->res['httpCode']);
}
}

View File

@ -2,6 +2,7 @@
namespace App\Listeners; namespace App\Listeners;
use App\Events\CancelLogisticEvent;
use App\Events\CreateLogisticEvent; use App\Events\CreateLogisticEvent;
use App\Models\Shop; use App\Models\Shop;
use App\Models\Waybill; use App\Models\Waybill;
@ -11,9 +12,6 @@ use Illuminate\Queue\InteractsWithQueue;
class CancelLogisticListener implements ShouldQueue class CancelLogisticListener implements ShouldQueue
{ {
public $connection = 'redis';
public $queue = 'listeners';
/** /**
* Create the event listener. * Create the event listener.
@ -31,7 +29,7 @@ class CancelLogisticListener implements ShouldQueue
* @param CreateLogisticEvent $event * @param CreateLogisticEvent $event
* @return void * @return void
*/ */
public function handle(CreateLogisticEvent $event) public function handle(CancelLogisticEvent $event)
{ {
$waybillNo = Waybill::query() $waybillNo = Waybill::query()
->where('shop_id', $event->shopId) ->where('shop_id', $event->shopId)

View File

@ -3,6 +3,7 @@
namespace App\Listeners; namespace App\Listeners;
use App\Events\CreateLogisticEvent; use App\Events\CreateLogisticEvent;
use App\Models\BusinessOrder;
use App\Models\Shop; use App\Models\Shop;
use App\Services\Business\BusinessFactory; use App\Services\Business\BusinessFactory;
use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Contracts\Queue\ShouldQueue;
@ -10,10 +11,6 @@ use Illuminate\Queue\InteractsWithQueue;
class CreateLogisticListener implements ShouldQueue class CreateLogisticListener implements ShouldQueue
{ {
public $connection = 'redis';
public $queue = 'listeners';
/** /**
* Create the event listener. * Create the event listener.
* *
@ -32,7 +29,7 @@ class CreateLogisticListener implements ShouldQueue
*/ */
public function handle(CreateLogisticEvent $event) public function handle(CreateLogisticEvent $event)
{ {
$shop = Shop::query()->findOrFail($event->shopId); $shop = Shop::query()->findOrFail($event->shopId);;
$client = BusinessFactory::init()->make($shop['plat_id'])->setShop($shop); $client = BusinessFactory::init()->make($shop['plat_id'])->setShop($shop);
$client->createLogistic($event->orderSn, $event->waybillNo); $client->createLogistic($event->orderSn, $event->waybillNo);
} }

View File

@ -23,6 +23,8 @@ class BusinessOrder extends Model
'print_status', 'print_status',
'ids', 'ids',
'pno', 'pno',
'order_sn',
'id',
]; ];
protected $fillable = [ protected $fillable = [
@ -72,4 +74,9 @@ class BusinessOrder extends Model
{ {
return $this->belongsTo(Shop::class, 'shop_id', 'id'); return $this->belongsTo(Shop::class, 'shop_id', 'id');
} }
public function waybill()
{
return $this->belongsTo(Waybill::class, 'id', 'order_id');
}
} }

View File

@ -2,9 +2,16 @@
namespace App\Models; namespace App\Models;
use App\Models\traits\Filter;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
class ShopSender extends Model class ShopSender extends Model
{ {
use Filter;
protected $guarded = []; protected $guarded = [];
//查询字段
public $fieldSearchable = [
'shop_id',
];
} }

View File

@ -10,4 +10,9 @@ class Waybill extends Model
public static $BUSINESS_EXPRESS_CODE = 247; public static $BUSINESS_EXPRESS_CODE = 247;
public static $AIR_FREIGHT_CODE = 266; public static $AIR_FREIGHT_CODE = 266;
public static $STATUS_INIT = 0;
public static $STATUS_CREATE_WAYBILL_CODE = 1;
public static $STATUS_CREATE_WAYBILL_ENCRYPTED_DATA = 2;
public static $STATUS_PRINT_SUCCESS = 3;
} }

View File

@ -3,6 +3,7 @@
namespace App\Providers; namespace App\Providers;
use App\Events\BusinessOrdersUpdate; use App\Events\BusinessOrdersUpdate;
use App\Events\CancelLogisticEvent;
use App\Events\StockUpdateEvent; use App\Events\StockUpdateEvent;
use App\Events\GroupSetEvent; use App\Events\GroupSetEvent;
use App\Events\BatchStockUpdateEvent; use App\Events\BatchStockUpdateEvent;
@ -47,6 +48,9 @@ class EventServiceProvider extends ServiceProvider
CreateLogisticEvent::class => [ CreateLogisticEvent::class => [
CreateLogisticListener::class CreateLogisticListener::class
], ],
CancelLogisticEvent::class => [
CancelLogisticListener::class
],
]; ];
/** /**

View File

@ -4,6 +4,7 @@ namespace App\Services\Business;
use App\Events\BusinessOrderCancelEvent; use App\Events\BusinessOrderCancelEvent;
use App\Events\BusinessOrdersUpdate; use App\Events\BusinessOrdersUpdate;
use App\Events\CancelLogisticEvent;
use App\Models\BusinessGoodsSku; use App\Models\BusinessGoodsSku;
use App\Models\BusinessOrder; use App\Models\BusinessOrder;
use App\Models\BusinessOrderItem; use App\Models\BusinessOrderItem;
@ -55,7 +56,7 @@ abstract class BusinessClient
$orderRecord->update($order); $orderRecord->update($order);
} }
if (!empty($order['cancel_status'])) { if (!empty($order['cancel_status'])) {
event(new BusinessOrderCancelEvent($shopId, $order['order_sn'])); event(new CancelLogisticEvent($shopId, $order['order_sn']));
} }
$goodsSkuNum = 0; $goodsSkuNum = 0;
foreach ($order['sub_order_list'] as $item) { foreach ($order['sub_order_list'] as $item) {
@ -154,7 +155,7 @@ abstract class BusinessClient
'headers' => ['Content-type' => 'application/x-www-form-urlencoded;charset=UTF-8'], 'headers' => ['Content-type' => 'application/x-www-form-urlencoded;charset=UTF-8'],
'form_params' => $params 'form_params' => $params
]; ];
$res = (new Client())->request($method, $url, $headers); $res = (new Client(['verify'=>false]))->request($method, $url, $headers);
$size = $res->getBody()->getSize(); $size = $res->getBody()->getSize();
$res = json_decode($res->getBody()->getContents(), true); $res = json_decode($res->getBody()->getContents(), true);
$paramsJson = json_encode($params, 256); $paramsJson = json_encode($params, 256);

View File

@ -374,7 +374,6 @@ class KuaiTuanTuan extends BusinessClient
public function createLogistic($orderSn, $waybillNo) public function createLogistic($orderSn, $waybillNo)
{ {
[$type, $appendParams] = Order::createOrderLogistic($orderSn, $waybillNo); [$type, $appendParams] = Order::createOrderLogistic($orderSn, $waybillNo);
return $this->doRequest($type, $appendParams); return $this->doRequest($type, $appendParams);
} }

View File

@ -0,0 +1,244 @@
<?php
namespace App\Services\WayBill\JingDong;
use App\Enum\Pickup\ConsignType;
use App\Models\BusinessOrder;
use App\Models\OrderItem;
use App\Models\Pickup;
use App\Models\Waybill;
use App\Services\Pickup\Exceptions\KdNiaoException;
use App\Utils\Env\EnvUtils;
use Carbon\Carbon;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\GuzzleException;
use Illuminate\Support\Facades\Log;
use Lop\LopOpensdkPhp\Filters\ErrorResponseFilter;
use Lop\LopOpensdkPhp\Filters\IsvFilter;
use Lop\LopOpensdkPhp\Options;
use Lop\LopOpensdkPhp\Support\DefaultClient;
use Lop\LopOpensdkPhp\Support\GenericRequest;
use Psr\Http\Message\ResponseInterface;
class JingDongService
{
public $baseUrl;
public $appKey;
public $appSecret;
public $accessToken;
public $customerCode;
public static $CANCEL_TYPE = [
1 => '预约信息有误',
2 => '快递员无法取件',
3 => '上门太慢',
4 => '运费太贵',
7 => '联系不上快递员',
8 => '快递员要求取消',
11 => '其他(如疫情管控,无法寄 件)',
9 => '预约信息有误',
];
public function __construct()
{
$this->baseUrl = "https://api.jdl.com";
$this->appKey = "5ee218f6619e438db8755ee560c3eaa7";
$this->appSecret = "7ffb176f75014ebbb37061f051024bf3";
$this->accessToken = "4e411a1d178147cdb58b5579a0df378d";//每年都会过期 https://oauth.jdl.com/oauth/authorize?client_id=YOUR_APP_KEY&redirect_uri=urn:ietf:wg:oauth:2.0:oob&response_type=code
//生产环境域名https://oauth.jdl.com 预发环境域名https://uat-oauth.jdl.com
$this->domain = "FreshMedicineDelivery";//对接方案的编码 生鲜快递
$this->customerCode = "028K4188368";//月结编码
/*if (!EnvUtils::checkIsProduce()) {
//沙箱环境
$this->baseUrl = "https://test-api.jdl.com";
$this->appKey = "62d07644754843cc882fca7c01476c4f";
$this->appSecret = "0c2c8b6b7c10481ea639f6daa09ac02e";
$this->accessToken = "78c246c0ab564e67add6296a9eaf04a1";;//每年都会过期 https://oauth.jdl.com/oauth/authorize?client_id=YOUR_APP_KEY&redirect_uri=urn:ietf:wg:oauth:2.0:oob&response_type=code
$this->customerCode = "27K1234912";//月结编码
}*/
}
public function request($body, $path)
{
// 生产环境: https://api.jdl.com
$client = new DefaultClient($this->baseUrl);
// 系统参数应用的app_ley和app_secret可从【控制台-应用管理-概览】中查看access_token是用户授权时获取的令牌用户授权相关说明请查看https://cloud.jdl.com/#/devSupport/53392
$isvFilter = new IsvFilter($this->appKey, $this->appSecret, $this->accessToken);
$errorResponseFilter = new ErrorResponseFilter();
$request = new GenericRequest();
$request->setDomain($this->domain);//对接方案的编码,应用订购对接方案后可在订阅记录查看
$request->setPath($path);//api的path可在API文档查看
$request->setMethod("POST");//只支持POST
// 序列化后的JSON字符串
$request->setBody(json_encode([$body]));
// 为请求添加ISV模式过滤器自动根据算法计算开放平台鉴权及签名信息
$request->addFilter($isvFilter);
// 为请求添加错误响应解析过滤器,如果不添加需要手动解析错误响应
$request->addFilter($errorResponseFilter);
$options = new Options();
$options->setAlgorithm(Options::MD5_SALT);
Log::info("京东请求body:{$path}", [$body]);
$response = $client->execute($request, $options);
$response = json_decode($response->getBody(), true);
Log::info("京东返回请求response", [$response]);
if (!isset($response['code']) || ($response['code'] != 0 && $response['code'] != 1 && $response['code'] != 1000)) {
throw new \Exception($response['message'] ?? "code异常");
}
return $response;
}
private function buildCargoes($extend)
{
return [
"quantity" => $extend["quantity"] ?? 1,
"goodsName" => "鲜花",
"volume" => ($extend['volume'] ?? 0) > 0 ? $extend['volume'] : 100,
"weight" => $extend['weight'] ?? 1,
"packageQty" => 1
];
}
public function buildContact($params)
{
return [
'senderName' => $params['name'] ?? '',
'senderPhone' => $params['mobile'] ?? '',
'senderAddress' => substr($params['province_name'] . $params['city_name'] . $params['area_name'] . $params['address_detail'], 0, 350),
];
}
public function createOrder(Waybill $pickup)
{
$path = "/freshmedicinedelivery/delivery/create/order/v1";
$pickupData = $pickup->toArray();
$body = [
"orderId" => $pickupData['order_sn'],
"senderContactRequest" => [
'senderName' => $pickupData['sender_name'] ?? '',
'senderPhone' => $pickupData['sender_mobile'] ?? '',
'senderAddress' => substr($pickupData['sender_province'] . $pickupData['sender_city']
. $pickupData['sender_district'] . $pickupData['sender_detail'], 0, 350)
],
"customerCode" => $this->customerCode,
"remark" => $pickup['note'] ?? '',
"salePlatform" => '0030001',//其他平台
"cargoesRequest" => $this->buildCargoes($pickup),
"receiverContactRequest" => [
'receiverName' => $pickupData['recipient_name'] ?? '',
'receiverPhone' => $pickupData['recipient_mobile'] ?? '',
'receiverAddress' => substr($pickupData['recipient_province'] . $pickupData['recipient_city'] . $pickupData['recipient_district'] . $pickupData['recipient_detail'], 0, 350)
],
"channelOrderId" => $pickupData['order_sn'],
"promiseTimeType" => 26,//22医药冷链 26冷链专送,29医药专送
"goodsType" => 7,//1:普通2:生鲜常温5:鲜活6:控温7:冷藏8:冷冻9:深冷21:医药冷藏23:医药控温24:医药常温25:医药冷冻27:医药深冷
//"pickUpStartTime" => Carbon::today()->startOfDay()->addDays(2)->addHours(19)->toDateTimeString(),
//"pickUpEndTime" => Carbon::today()->startOfDay()->addDays(2)->addHours(21)->toDateTimeString(),
];
$response = $this->request($body, $path);
return $response['data'];
}
public function cancelOrder(Waybill $pickup)
{
$path = "/freshmedicinedelivery/delivery/cancel/waybill/v1";
$pickupData = $pickup->toArray();
$body = [
"waybillNo" => $pickupData['waybill_code'],
"customerCode" => $this->customerCode,
"interceptReason" => "订单信息有误",
];
$response = $this->request($body, $path);
return $response['data'];
}
public function pullData(Waybill $pickup)
{
//云打印
$this->domain = "jdcloudprint";
$path = "/PullDataService/pullData";
$pickupData = $pickup->toArray();
$body = [
"cpCode" => "JD",
"wayBillInfos" => [[
'orderNo' => $pickupData['order_sn'],
'jdWayBillCode' => $pickupData['waybill_code'],
]],
"parameters" => ["ewCustomerCode" => $this->customerCode],
"objectId" => $pickupData['object_id'],
];
$response = $this->request($body, $path);
return $response['prePrintDatas'];
}
public function queryOrder(Waybill $pickup)
{
$path = "/ecap/v1/orders/details/query";
$pickupData = $pickup->toArray();
$body = [
"waybillCode" => $pickupData['ship_sn'] ?? '',
"orderCode" => $pickupData['out_order_sn'] ?? "",
"customerCode" => $this->customerCode,
"orderOrigin" => 2,
];
$response = $this->request($body, $path);
return $response['data'];
}
public function subscribe(Waybill $pickup)
{
$path = "/jd/tracking/subscribe";
$pickupData = $pickup->toArray();
$body = [
"referenceNumber" => $pickupData['ship_sn'] ?? '',
"referenceType" => 20000,
"customerCode" => $this->customerCode,
];
$this->domain = "Tracking_JD";//对接方案的编码
$response = $this->request($body, $path);
return $response['data'];
}
public function queryFee(Waybill $pickup)
{
$path = "/ecap/v1/orders/actualfee/query";
$pickupData = $pickup->toArray();
$body = [
"waybillCode" => $pickupData['ship_sn'] ?? '',
"orderCode" => $pickupData['out_order_sn'] ?? "",
"customerCode" => $this->customerCode,
"orderOrigin" => 2,
];
$response = $this->request($body, $path);
return $response['data'];
}
public function queryTrace(Waybill $pickup)
{
$path = "/freshmedicinedelivery/delivery/query/waybill/gis/v1";
$pickupData = $pickup->toArray();
$body = [
"waybillNo" => $pickupData['waybill_code'] ?? '',
"customerCode" => $this->customerCode,
];
$response = $this->request($body, $path);
return $response['data'];
}
}

View File

@ -0,0 +1,281 @@
<?php
namespace App\Services\WayBill\JingDong;
use App\Events\CreateLogisticEvent;
use App\Http\Enum\BusinessOrderShippingStatus;
use App\Models\BusinessOrder;
use App\Models\GoodsSku;
use App\Models\ShopSender;
use App\Models\ShopShip;
use App\Models\Waybill;
use App\Services\Business\KuaiTuanTuan\FaceSheet;
class WayBillService
{
public $orders;
public $objectId;
public $adminUser;
// 标准模板
public $templateUrl = 'https://template-content.jd.com/template-oss?tempCode=jdkd76x130';
// 时效类型
public $timedDeliveryCode;
/**
* 新版京东打印-下单
* @return array
*/
public function getWayBillContents()
{
// 已下单过的订单不再下单
$contents = [];
$this->adminUser = auth('api')->user();
$jingDongService = new JingDongService();
foreach ($this->orders as $shopId => $order) {
// 订单取消的情况暂不处理
$shopSend = $this->getShopSend($shopId);
foreach ($order as $item) {
[$sender, $orderInfo, $senderConfig] = $this->prepareRequest($item, $shopSend);
$waybill = $this->saveWayBill($item, $shopSend, $senderConfig);
if (empty($waybill->waybill_code)) {
$resp = $jingDongService->createOrder($waybill);
if (isset($resp['waybillNo'])) {
$waybill->status = Waybill::$STATUS_CREATE_WAYBILL_CODE;
$waybill->waybill_code = $resp['waybillNo'];
$waybill->save();
//物流发货
event(new CreateLogisticEvent($waybill->shop_id, $waybill->order_sn, $waybill->waybill_code));
}
}
//返回面单待打印数据
if (empty($waybill->encryptedData)) {
// 延时0.5秒 防止订单查询不到
usleep(0.5 * 1000000);
$resp = $jingDongService->pullData($waybill);
if (!empty($resp[0]['perPrintData'])) {
$waybill->status = Waybill::$STATUS_CREATE_WAYBILL_ENCRYPTED_DATA;
$waybill->templateUrl = $this->templateUrl;
$waybill->encryptedData = $resp[0]['perPrintData'];
$waybill->save();
}
}
$contents[$item['id']] = $waybill;
}
}
return $contents;
}
public function getDocumentsAndOrderIds($contents)
{
// 打印单,根据商品排序
$items = [];
foreach ($contents as $docId => $content) {
foreach ($content['items'] as $item) {
if ($item['is_single']) {
$items[$item['external_sku_id']][] = $docId;
}
}
}
ksort($items);
$documents = $orderIds = $hasIds = [];
foreach ($items as $docIds) {
$docIds = array_unique($docIds);
$docIds = array_diff($docIds, $hasIds);
$hasIds = array_merge($hasIds, $docIds);
foreach ($docIds as $docId) {
$orderIds[] = $contents[$docId]['order_id'];
$documents[] = $contents[$docId];
}
}
return [$documents, $orderIds];
}
private function saveWayBill($order, $shopShip, $senderConfig)
{
$waybill = Waybill::query()->firstOrNew(
['order_sn' => $order['order_sn']]
);
$waybill->shop_id = $order['shop_id'];
$waybill->object_id = $this->objectId;
$waybill->sender_country = $senderConfig['country'];
$waybill->sender_province = $senderConfig['province'];
$waybill->sender_city = $senderConfig['city'];
$waybill->sender_district = $senderConfig['district'];
$waybill->sender_detail = $senderConfig['detail'];
$waybill->sender_name = $senderConfig['name'];
$waybill->sender_mobile = (int)$senderConfig['mobile'];
$waybill->recipient_province = $order['recipient_province'];
$waybill->recipient_city = $order['recipient_city'];
$waybill->recipient_district = $order['recipient_district'];
$waybill->recipient_detail = $order['recipient_detail'];
$waybill->recipient_name = $order['recipient_name'];
$waybill->recipient_mobile = $order['recipient_mobile'];
$waybill->user_id = $this->adminUser->id ?? 0;
$waybill->wp_code = $senderConfig['wp_code'];
$waybill->order_sn = $order['order_sn'];
$waybill->order_id = $order['id'];
$waybill->participate_no = $order['participate_no'];
$waybill->note = $order['note'];
$waybill->items = json_encode($order['items'], 256);
$waybill->save();
return $waybill;
}
private function getTimedDelivery($order)
{
$this->timedDeliveryCode = Waybill::$BUSINESS_EXPRESS_CODE;
$address = [
'辽宁省' => [],
'吉林省' => [],
'黑龙江省' => [],
'甘肃省' => [],
'海南省' => [],
'宁夏回族自治区' => [
'银川市', '石嘴山市', '吴忠市', '固原市'
],
'青海省' => [
'西宁市', '海东市', '海北藏族自治州', '黄南藏族自治州', '海南藏族自治州', '玉树藏族自治州'
],
];
if (isset($address[$order['recipient_province']])) {
if (empty($address[$order['recipient_province']])) {
$this->timedDeliveryCode = Waybill::$AIR_FREIGHT_CODE;
}
if ($address[$order['recipient_province']] && in_array($order['recipient_city'], $address[$order['recipient_province']], true)) {
$this->timedDeliveryCode = Waybill::$AIR_FREIGHT_CODE;
}
}
}
private function prepareRequest($order, $shopSend)
{
$this->setObjectId();
$items = [];
foreach ($order['items'] as $item) {
$items[] = [
'name' => $item['name'],
'count' => $item['count'],
];
}
if (empty($shopSend)) {
abort(404, '发货人信息未匹配');
}
$senderConfig = $shopSend;
$sender = [
'address' => [
'city' => $senderConfig['city'],
'country' => $senderConfig['country'],
'detail' => $senderConfig['detail'],
'district' => $senderConfig['district'],
'province' => $senderConfig['province'],
],
'name' => $senderConfig['name'],
'mobile' => $senderConfig['mobile'],
];
$orderInfo = [
'object_id' => $this->objectId,
'order_info' => [
'order_channels_type' => 'PDD',
'trade_order_list' => [$order['order_sn']],
],
'package_info' => [
'items' => $items,
],
'recipient' => [
'address' => [
'city' => $order['recipient_city'],
'detail' => $order['recipient_detail'],
'district' => $order['recipient_district'],
'province' => $order['recipient_province'],
],
'name' => $order['recipient_name'],
'mobile' => $order['recipient_mobile'],
],
'template_url' => $this->templateUrl,
];
return [$sender, $orderInfo, $senderConfig];
}
public function setObjectId()
{
$this->objectId = date('YmdHis') . str_pad(mt_rand(1, 9999999), 7, '0', STR_PAD_LEFT);
return $this;
}
public function setOrders($orders)
{
$orders = $orders->toArray();
// 订单拆分组合
foreach ($orders as $order) {
if ($order['shipping_status'] != BusinessOrderShippingStatus::UNSHIP) {
continue;
}
if ($order['cancel_status'] == 1) {
continue;
}
$info = [
'id' => $order['id'],
'shop_id' => $order['shop_id'],
'order_sn' => $order['order_sn'],
'recipient_province' => $order['receiver_address_province'],
'recipient_city' => $order['receiver_address_city'],
'recipient_district' => $order['receiver_address_district'],
'recipient_detail' => $order['receiver_address_detail'],
'recipient_name' => $order['receiver_name'],
'recipient_mobile' => $order['receiver_mobile'],
'participate_no' => $order['is_supplier'] ? $order['participate_no'] : $order['supply_participate_no'],
'note' => $order['business_note'] ? $order['business_note'] . ',' . $order['buyer_memo'] : $order['buyer_memo'],
'items' => []
];
$note = "";
foreach ($order['items'] as $item) {
$count = $item['goods_number'] - $item['already_cancel_number'];
$info['items'][] = [
'is_single' => true,
'should_print' => true,
'name' => $item['goods_name'],
'count' => $count,
'external_sku_id' => $item['external_sku_id'],
];
$note .= $item['goods_name'] . " " . $count . "件,";
}
$note .= "[备注:" . $info['note'] . "]";
$info['note'] = $note;
$this->orders[$order['shop_id']][] = $info;
}
return $this;
}
private function getShopSend($shopId)
{
return ShopSender::query()
->where('status', 1)->orderBy('sort')
->first();
}
private function getShopShip($shopId)
{
return ShopShip::query()
->select(['id', 'shop_id', 'access_token', 'owner_id'])
->where('shop_id', $shopId)
->with([
'senders' => function ($query) {
$query->where('status', 1)->orderBy('sort');
}
])
->first();
}
}

View File

@ -39,7 +39,8 @@
}, },
"autoload": { "autoload": {
"psr-4": { "psr-4": {
"App\\": "app/" "App\\": "app/",
"Lop\\LopOpensdkPhp\\" : "extend/lop-opensdk-php/src/"
}, },
"classmap": [ "classmap": [
"database/seeds", "database/seeds",

View File

@ -0,0 +1,42 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class AddFieldToWaybillsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
if (Schema::hasColumns('waybills', ["status"])) {
return;
}
Schema::table('waybills', function (Blueprint $table) {
$table->integer('status')->default(0)->comment('状态 0待申请物流单 1已申请物流 2已申请面单 3已打印');
$table->index('order_id');
$table->index('order_sn');
$table->index('waybill_code');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('waybills', function (Blueprint $table) {
//
$table->dropColumn('status');
$table->dropIndex('order_id');
$table->dropIndex('order_sn');
$table->dropIndex('waybill_code');
});
}
}

12
extend/lop-opensdk-php/.gitignore vendored Normal file
View File

@ -0,0 +1,12 @@
# Created by https://www.toptal.com/developers/gitignore/api/composer
# Edit at https://www.toptal.com/developers/gitignore?templates=composer
### Composer ###
composer.phar
/vendor/
# Commit your application's lock file https://getcomposer.org/doc/01-basic-usage.md#commit-your-composer-lock-file-to-version-control
# You may choose to ignore a library lock file http://getcomposer.org/doc/02-libraries.md#lock-file
# composer.lock
# End of https://www.toptal.com/developers/gitignore/api/composer

View File

View File

@ -0,0 +1,18 @@
{
"name": "lop/lop-opensdk-php",
"type": "library",
"version": "1.0.0",
"autoload": {
"psr-4": {
"Lop\\LopOpensdkPhp\\": "src/"
}
},
"require": {
"php": ">=5.6",
"ext-json": "*",
"guzzlehttp/guzzle": "^6.5"
},
"require-dev": {
"phpunit/phpunit": "^5.7"
}
}

2284
extend/lop-opensdk-php/composer.lock generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,14 @@
<?php
namespace Lop\LopOpensdkPhp;
interface Client
{
/**
* @param Request $request
* @param Options $options
* @return Response
* @throws SdkException
*/
function execute($request, $options);
}

View File

@ -0,0 +1,14 @@
<?php
namespace Lop\LopOpensdkPhp;
interface Executor
{
/**
* @param Request $request
* @param Options $options
* @return Response
* @throws SdkException
*/
function execute($request, $options);
}

View File

@ -0,0 +1,13 @@
<?php
namespace Lop\LopOpensdkPhp;
interface ExecutorFactory
{
/**
* @param string $baseUri
* @return Executor
* @throws SdkException
*/
function create($baseUri);
}

View File

@ -0,0 +1,15 @@
<?php
namespace Lop\LopOpensdkPhp;
interface Filter
{
/**
* @param Request $request
* @param Options $options
* @param FilterChain $chain
* @return Response
* @throws SdkException
*/
function doFilter($request, $options, $chain);
}

View File

@ -0,0 +1,14 @@
<?php
namespace Lop\LopOpensdkPhp;
interface FilterChain
{
/**
* @param Request $request
* @param Options $options
* @return Response
* @throws SdkException
*/
function doFilter($request, $options);
}

View File

@ -0,0 +1,24 @@
<?php
namespace Lop\LopOpensdkPhp\Filters;
use Lop\LopOpensdkPhp\Filter;
class ErrorResponseFilter implements Filter
{
const ERROR_RESPONSE_PATTERN = "/^{\"error_response\":{\"en_desc\":\"(.*)\",\"zh_desc\":\"(.*)\",\"code\":(.*)}}$/";
function doFilter($request, $options, $chain)
{
$response = $chain->doFilter($request, $options);
$matches = array();
if (preg_match(self::ERROR_RESPONSE_PATTERN, $response->getBody(), $matches) === 1) {
$response->setSucceed(false);
$response->setEnDesc($matches[1]);
$response->setZhDesc($matches[2]);
$response->setCode(intval($matches[3]));
}
return $response;
}
}

View File

@ -0,0 +1,66 @@
<?php
namespace Lop\LopOpensdkPhp\Filters;
use Lop\LopOpensdkPhp\Filter;
use Lop\LopOpensdkPhp\SdkException;
class IsvFilter implements Filter
{
/**
* @var string
*/
private $appKey;
/**
* @var string
*/
private $appSecret;
/**
* @var string
*/
private $accessToken;
/**
* @param string $appKey
* @param string $appSecret
* @param string $accessToken
*/
public function __construct($appKey, $appSecret, $accessToken)
{
$this->appKey = $appKey;
$this->appSecret = $appSecret;
$this->accessToken = $accessToken;
}
/**
* @throws SdkException
*/
function doFilter($request, $options, $chain)
{
$request->setHeader("lop-tz", strval(date("Z") / 3600));
$timestamp = date("Y-m-d H:i:s");
$content = implode("", array(
$this->appSecret,
"access_token", $this->accessToken,
"app_key", $this->appKey,
"method", $request->getPath(),
"param_json", $request->getBody(),
"timestamp", $timestamp,
"v", "2.0",
$this->appSecret
));
$sign = Utils::sign($options->getAlgorithm(), $content, $this->appSecret);
$request->setQuery("app_key", $this->appKey);
$request->setQuery("access_token", $this->accessToken);
$request->setQuery("timestamp", $timestamp);
$request->setQuery("v", "2.0");
$request->setQuery("sign", $sign);
$request->setQuery("algorithm", $options->getAlgorithm());
return $chain->doFilter($request, $options);
}
}

View File

@ -0,0 +1,59 @@
<?php
namespace Lop\LopOpensdkPhp\Filters;
use Lop\LopOpensdkPhp\Filter;
use Lop\LopOpensdkPhp\SdkException;
class LoggerFilter implements Filter
{
/**
* @var resource
*/
private $file;
/**
* @param resource $file
*/
public function __construct($file)
{
$this->file = $file;
}
function doFilter($request, $options, $chain)
{
$this->printf("[%s, Request ] Domain: %s, Path: %s, Method: %s", $request->getRequestId(),
$request->getDomain(), $request->getPath(), $request->getMethod());
$this->printf("[%s, Request ] Queries: %s", $request->getRequestId(), json_encode($request->getQueries()));
$this->printf("[%s, Request ] Headers: %s", $request->getRequestId(), json_encode($request->getHeaders()));
$this->printf("[%s, Request ] Body: %s", $request->getRequestId(), $request->getBody());
try {
$response = $chain->doFilter($request, $options);
} catch (SdkException $e) {
$this->printf("[%s, Response ] Error: %s", $e->getMessage());
throw $e;
}
$this->printf("[%s, Response ] Succeed: %t, TraceId: %s, EnDesc: %s, ZhDesc: %s, Code: %d, Status: %d",
$request->getRequestId(), $response->isSucceed(), $response->getTraceId(),
$response->getEnDesc(), $response->getZhDesc(), $response->getCode(), $response->getStatus());
$this->printf("[%s, Response ] Body: %s", $request->getRequestId(), $response->getBody());
return $response;
}
/**
* @param string $format
* @param mixed ...$values
* @return void
*/
private function printf($format, ...$values)
{
fwrite($this->file, date("Y/m/d H:i:s"));
fwrite($this->file, " ");
fwrite($this->file, sprintf($format, $values));
fwrite($this->file, "\n");
}
}

View File

@ -0,0 +1,58 @@
<?php
namespace Lop\LopOpensdkPhp\Filters;
use Lop\LopOpensdkPhp\Filter;
use Lop\LopOpensdkPhp\SdkException;
class PartnerFilter implements Filter
{
/**
* @var string
*/
private $appKey;
/**
* @var string
*/
private $appSecret;
/**
* @param string $appKey
* @param string $appSecret
*/
public function __construct($appKey, $appSecret)
{
$this->appKey = $appKey;
$this->appSecret = $appSecret;
}
/**
* @throws SdkException
*/
function doFilter($request, $options, $chain)
{
$request->setHeader("lop-tz", strval(date("Z") / 3600));
$timestamp = date("Y-m-d H:i:s");
$content = implode("", array(
$this->appSecret,
"access_token", "",
"app_key", $this->appKey,
"method", $request->getPath(),
"param_json", $request->getBody(),
"timestamp", $timestamp,
"v", "2.0",
$this->appSecret
));
$sign = Utils::sign($options->getAlgorithm(), $content, $this->appSecret);
$request->setQuery("app_key", $this->appKey);
$request->setQuery("timestamp", $timestamp);
$request->setQuery("v", "2.0");
$request->setQuery("sign", $sign);
$request->setQuery("algorithm", $options->getAlgorithm());
return $chain->doFilter($request, $options);
}
}

View File

@ -0,0 +1,36 @@
<?php
namespace Lop\LopOpensdkPhp\Filters;
use Lop\LopOpensdkPhp\Options;
use Lop\LopOpensdkPhp\SdkException;
abstract class Utils
{
/**
* Signature data with given algorithm and secret.
*
* @param string $algorithm
* @param string $secret
* @param string $data
* @return string
* @throws SdkException
*/
public static function sign($algorithm, $data, $secret)
{
if ($algorithm == Options::MD5_SALT) {
return md5($data);
} else if ($algorithm == Options::SM3_SALT) {
throw new SdkException("Algorithm sm3-salt not supported yet");
} else if ($algorithm == Options::HMAC_MD5) {
return base64_encode(hash_hmac("md5", $data, $secret, true));
} else if ($algorithm == Options::HMAC_SHA1) {
return base64_encode(hash_hmac("sha1", $data, $secret, true));
} else if ($algorithm == Options::HMAC_SHA256) {
return base64_encode(hash_hmac("sha256", $data, $secret, true));
} else if ($algorithm == Options::HMAC_SHA512) {
return base64_encode(hash_hmac("sha512", $data, $secret, true));
}
throw new SdkException("Algorithm " . $algorithm . " not supported yet");
}
}

View File

@ -0,0 +1,40 @@
<?php
namespace Lop\LopOpensdkPhp;
class Options
{
const MD5_SALT = "md5-salt";
const SM3_SALT = "sm3-salt";
const HMAC_MD5 = "HMacMD5";
const HMAC_SHA1 = "HMacSHA1";
const HMAC_SHA256 = "HMacSHA256";
const HMAC_SHA512 = "HMacSHA512";
/**
* @var string
*/
private $algorithm;
public function __construct()
{
$this->algorithm = self::MD5_SALT;
}
/**
* @return string
*/
public function getAlgorithm()
{
return $this->algorithm;
}
/**
* @param string $algorithm
*/
public function setAlgorithm($algorithm)
{
$this->algorithm = $algorithm;
}
}

View File

@ -0,0 +1,137 @@
<?php
namespace Lop\LopOpensdkPhp;
interface Request
{
/**
* @return string
*/
function getDomain();
/**
* @param string $domain
* @return void
*/
function setDomain($domain);
/**
* @return string
*/
function getPath();
/**
* @param string $path
* @return void
*/
function setPath($path);
/**
* @return string
*/
function getMethod();
/**
* @param string $method
* @return void
*/
function setMethod($method);
/**
* @param string $key
* @return bool
*/
function hasQuery($key);
/**
* @param string $key
* @return string
*/
function getQuery($key);
/**
* @param string $key
* @param string $value
* @return void
*/
function setQuery($key, $value);
/**
* @return string[]
*/
function getQueries();
/**
* @param string $key
* @return bool
*/
function hasHeader($key);
/**
* @param string $key
* @return string
*/
function getHeader($key);
/**
* @param string $key
* @param string $value
* @return void
*/
function setHeader($key, $value);
/**
* @return string[]
*/
function getHeaders();
/**
* @param Filter $filter
* @return void
*/
function addFilter($filter);
/**
* @return Filter[]
*/
function getFilters();
/**
* @return mixed
*/
function getEntity();
/**
* @return string
*/
function getBody();
/**
* @param string $body
* @return void
*/
function setBody($body);
/**
* @return string
*/
function getRequestId();
/**
* @param string $requestId
* @return void
*/
function setRequestId($requestId);
/**
* @return string
*/
function getResponseType();
/**
* @param string $responseType
* @return void
*/
function setResponseType($responseType);
}

View File

@ -0,0 +1,94 @@
<?php
namespace Lop\LopOpensdkPhp;
interface Response
{
/**
* @return bool
*/
function isSucceed();
/**
* @param bool $succeed
* @return void
*/
function setSucceed($succeed);
/**
* @return string
*/
function getTraceId();
/**
* @param string $traceId
* @return void
*/
function setTraceId($traceId);
/**
* @return string
*/
function getZhDesc();
/**
* @param string $zhDesc
* @return void
*/
function setZhDesc($zhDesc);
/**
* @return string
*/
function getEnDesc();
/**
* @param string $enDesc
* @return void
*/
function setEnDesc($enDesc);
/**
* @return int
*/
function getCode();
/**
* @param int $code
* @return void
*/
function setCode($code);
/**
* @return int
*/
function getStatus();
/**
* @param int $status
* @return void
*/
function setStatus($status);
/**
* @return string
*/
function getBody();
/**
* @param string $body
* @return void
*/
function setBody($body);
/**
* @return mixed
*/
function getEntity();
/**
* @param mixed $entity
* @return void
*/
function setEntity($entity);
}

View File

@ -0,0 +1,27 @@
<?php
namespace Lop\LopOpensdkPhp;
use Exception;
class SdkException extends Exception
{
/**
* @var Response $response
*/
private $response;
public function __construct($message = "", $code = 0, $response = null, $previous = null)
{
parent::__construct($message, $code, $previous);
$this->response = $response;
}
/**
* @return Response
*/
public function getResponse()
{
return $this->response;
}
}

View File

@ -0,0 +1,79 @@
<?php
namespace Lop\LopOpensdkPhp\Support;
use Lop\LopOpensdkPhp\Client;
use Lop\LopOpensdkPhp\ExecutorFactory;
use Lop\LopOpensdkPhp\SdkException;
use Lop\LopOpensdkPhp\Version;
class DefaultClient implements Client
{
private $baseUri;
/**
* @var ExecutorFactory
*/
private $executorFactory;
public function __construct($baseUri)
{
$this->baseUri = $baseUri;
$this->executorFactory = new DefaultExecutorFactory();
}
/**
* @throws SdkException
*/
function execute($request, $options)
{
if (empty($request->getRequestId())) {
$request->setRequestId(uniqid());
}
if (empty($request->getBody())) {
$request->setBody(json_encode($request->getEntity()));
}
if (!$request->hasQuery("LOP-DN")) {
$request->setQuery("LOP-DN", $request->getDomain());
}
if (!$request->hasHeader("User-Agent")) {
$request->setHeader("User-Agent", "lop-opensdk/php@" . Version::VERSION);
}
$executor = $this->executorFactory->create($this->baseUri);
$chain = new DefaultFilterChain($executor, $request->getFilters());
$response = $chain->doFilter($request, $options);
if (!$response->isSucceed()) {
return $response;
}
if (empty($request->getResponseType())) {
return $response;
}
$entity = json_decode($response->getBody());
if (json_last_error() != JSON_ERROR_NONE) {
throw new SdkException("json decode failed", 0, $response);
}
if (!settype($entity, $request->getResponseType())) {
throw new SdkException("set entity to response type failed", 0, $response);
}
$response->setEntity($entity);
return $response;
}
/**
* @param ExecutorFactory $executorFactory
*/
public function setExecutorFactory($executorFactory)
{
$this->executorFactory = $executorFactory;
}
}

View File

@ -0,0 +1,56 @@
<?php
namespace Lop\LopOpensdkPhp\Support;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\GuzzleException;
use GuzzleHttp\Psr7\Request;
use Lop\LopOpensdkPhp\Executor;
use Lop\LopOpensdkPhp\SdkException;
class DefaultExecutor implements Executor
{
/**
* @var string
*/
private $baseUri;
/**
* @param string $baseUri
*/
public function __construct($baseUri)
{
$this->baseUri = $baseUri;
}
/**
* @throws SdkException
*/
function execute($request, $options)
{
$uri = $this->baseUri . $request->getPath();
$httpRequest = new Request($request->getMethod(), $uri, $request->getHeaders(), $request->getBody());
$client = new Client(['verify' => false]);
try {
$httpResponse = $client->send($httpRequest, [
"query" => $request->getQueries(),
"http_errors" => false
]);
} catch (GuzzleException $e) {
throw new SdkException("", 0, null, $e);
}
$response = new GenericResponse();
$response->setSucceed($httpResponse->getStatusCode() == 200);
$response->setStatus($httpResponse->getStatusCode());
$response->setBody($httpResponse->getBody()->getContents());
$traceIds = $httpResponse->getHeader("Lop-Trace-Id");
if (count($traceIds) > 0) {
$response->setTraceId($traceIds[0]);
}
return $response;
}
}

View File

@ -0,0 +1,14 @@
<?php
namespace Lop\LopOpensdkPhp\Support;
use GuzzleHttp\Client;
use Lop\LopOpensdkPhp\ExecutorFactory;
class DefaultExecutorFactory implements ExecutorFactory
{
function create($baseUri)
{
return new DefaultExecutor($baseUri);
}
}

View File

@ -0,0 +1,51 @@
<?php
namespace Lop\LopOpensdkPhp\Support;
use Lop\LopOpensdkPhp\Executor;
use Lop\LopOpensdkPhp\Filter;
use Lop\LopOpensdkPhp\FilterChain;
use Lop\LopOpensdkPhp\SdkException;
class DefaultFilterChain implements FilterChain
{
/**
* @var Executor
*/
private $executor;
/**
* @var Filter[]
*/
private $filters;
/**
* @var int
*/
private $position;
/**
* @param Executor $executor
* @param Filter[] $filters
*/
public function __construct($executor, $filters)
{
$this->executor = $executor;
$this->filters = $filters;
$this->position = 0;
}
/**
* @throws SdkException
*/
function doFilter($request, $options)
{
if ($this->position < count($this->filters)) {
$filter = $this->filters[$this->position];
$this->position++;
return $filter->doFilter($request, $options, $this);
} else {
return $this->executor->execute($request, $options);
}
}
}

View File

@ -0,0 +1,184 @@
<?php
namespace Lop\LopOpensdkPhp\Support;
use Lop\LopOpensdkPhp\Filter;
use Lop\LopOpensdkPhp\Request;
class GenericRequest implements Request
{
/**
* @var string
*/
private $domain;
/**
* @var string
*/
private $path;
/**
* @var string
*/
private $method;
/**
* @var string[]
*/
private $queries;
/**
* @var string[]
*/
private $headers;
/**
* @var Filter[]
*/
private $filters;
/**
* @var string
*/
private $body;
/**
* @var string
*/
private $requestId;
/**
* @var string
*/
private $responseType;
public function __construct()
{
$this->queries = array();
$this->headers = array();
$this->filters = array();
}
function getDomain()
{
return $this->domain;
}
function setDomain($domain)
{
$this->domain = $domain;
}
function getPath()
{
return $this->path;
}
function setPath($path)
{
$this->path = $path;
}
function getMethod()
{
return $this->method;
}
function setMethod($method)
{
$this->method = $method;
}
function hasQuery($key)
{
return array_key_exists($key, $this->queries);
}
function getQuery($key)
{
return $this->queries[$key];
}
function setQuery($key, $value)
{
$this->queries[$key] = $value;
}
function getQueries()
{
return $this->queries;
}
function hasHeader($key)
{
return array_key_exists($key, $this->headers);
}
function getHeader($key)
{
return $this->headers[$key];
}
function setHeader($key, $value)
{
$this->headers[$key] = $value;
}
function getHeaders()
{
return $this->headers;
}
function addFilter($filter)
{
$this->filters[] = $filter;
}
function getFilters()
{
return $this->filters;
}
function getEntity()
{
return null;
}
function getBody()
{
return $this->body;
}
function setBody($body)
{
$this->body = $body;
}
function getRequestId()
{
return $this->requestId;
}
function setRequestId($requestId)
{
$this->requestId = $requestId;
}
/**
* @return string
*/
function getResponseType()
{
return $this->responseType;
}
/**
* @param string $responseType
* @return void
*/
function setResponseType($responseType)
{
$this->responseType = $responseType;
}
}

View File

@ -0,0 +1,129 @@
<?php
namespace Lop\LopOpensdkPhp\Support;
use Lop\LopOpensdkPhp\Response;
class GenericResponse implements Response
{
/**
* @var bool
*/
private $succeed;
/**
* @var string
*/
private $traceId;
/**
* @var string
*/
private $zhDesc;
/**
* @var string
*/
private $enDesc;
/**
* @var int
*/
private $code;
/**
* @var string
*/
private $status;
/**
* @var string
*/
private $body;
/**
* @var mixed
*/
private $entity;
function isSucceed()
{
return $this->succeed;
}
function setSucceed($succeed)
{
$this->succeed = $succeed;
}
function getTraceId()
{
return $this->traceId;
}
function setTraceId($traceId)
{
$this->traceId = $traceId;
}
function getZhDesc()
{
return $this->zhDesc;
}
function setZhDesc($zhDesc)
{
$this->zhDesc = $zhDesc;
}
function getEnDesc()
{
return $this->enDesc;
}
function setEnDesc($enDesc)
{
$this->enDesc = $enDesc;
}
function getCode()
{
return $this->code;
}
function setCode($code)
{
$this->code = $code;
}
function getStatus()
{
return $this->status;
}
function setStatus($status)
{
$this->status = $status;
}
function getBody()
{
return $this->body;
}
function setBody($body)
{
$this->body = $body;
}
function getEntity()
{
return $this->entity;
}
function setEntity($entity)
{
$this->entity = $entity;
}
}

View File

@ -0,0 +1,8 @@
<?php
namespace Lop\LopOpensdkPhp;
class Version
{
const VERSION = "1.0.0";
}

View File

@ -1 +1 @@
#nprogress{pointer-events:none}#nprogress .bar{background:#29d;position:fixed;z-index:1031;top:0;left:0;width:100%;height:2px}#nprogress .peg{display:block;position:absolute;right:0;width:100px;height:100%;box-shadow:0 0 10px #29d,0 0 5px #29d;opacity:1;transform:rotate(3deg) translateY(-4px)}#nprogress .spinner{display:block;position:fixed;z-index:1031;top:15px;right:15px}#nprogress .spinner-icon{width:18px;height:18px;box-sizing:border-box;border:2px solid transparent;border-top-color:#29d;border-left-color:#29d;border-radius:50%;animation:nprogress-spinner .4s linear infinite}.nprogress-custom-parent{overflow:hidden;position:relative}.nprogress-custom-parent #nprogress .bar,.nprogress-custom-parent #nprogress .spinner{position:absolute}@keyframes nprogress-spinner{0%{transform:rotate(0deg)}to{transform:rotate(1turn)}}a[data-v-9f160e6c]{text-decoration:none;color:#fff}.opaBox[data-v-9f160e6c]{margin-bottom:15px} #nprogress{pointer-events:none}#nprogress .bar{background:#29d;position:fixed;z-index:1031;top:0;left:0;width:100%;height:2px}#nprogress .peg{display:block;position:absolute;right:0;width:100px;height:100%;box-shadow:0 0 10px #29d,0 0 5px #29d;opacity:1;transform:rotate(3deg) translateY(-4px)}#nprogress .spinner{display:block;position:fixed;z-index:1031;top:15px;right:15px}#nprogress .spinner-icon{width:18px;height:18px;box-sizing:border-box;border:2px solid transparent;border-top-color:#29d;border-left-color:#29d;border-radius:50%;animation:nprogress-spinner .4s linear infinite}.nprogress-custom-parent{overflow:hidden;position:relative}.nprogress-custom-parent #nprogress .bar,.nprogress-custom-parent #nprogress .spinner{position:absolute}@keyframes nprogress-spinner{0%{transform:rotate(0deg)}to{transform:rotate(1turn)}}a[data-v-d25d9320]{text-decoration:none;color:#fff}.opaBox[data-v-d25d9320]{margin-bottom:15px}

View File

@ -1 +1 @@
<!doctype html><html lang=""><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1"><link rel="icon" href="favicon.ico"><title>erp</title><script defer="defer" src="js/chunk-vendors.04652b46.js"></script><script defer="defer" src="js/app.f1ce3c3c.js"></script><link href="css/chunk-vendors.77489a8d.css" rel="stylesheet"><link href="css/app.7e37f273.css" rel="stylesheet"></head><body><noscript><strong>We're sorry but erp doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div></body></html> <!doctype html><html lang=""><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1"><link rel="icon" href="favicon.ico"><title>erp</title><script defer="defer" src="js/chunk-vendors.04652b46.js"></script><script defer="defer" src="js/app.10acb235.js"></script><link href="css/chunk-vendors.77489a8d.css" rel="stylesheet"><link href="css/app.7e37f273.css" rel="stylesheet"></head><body><noscript><strong>We're sorry but erp doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div></body></html>

File diff suppressed because one or more lines are too long

1
public/dist/js/147.34297e9a.js.map vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

2
public/dist/js/19.39213a6d.js vendored Normal file

File diff suppressed because one or more lines are too long

1
public/dist/js/19.39213a6d.js.map vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

1
public/dist/js/298.c971cd33.js.map vendored Normal file

File diff suppressed because one or more lines are too long

2
public/dist/js/463.09656921.js vendored Normal file

File diff suppressed because one or more lines are too long

1
public/dist/js/463.09656921.js.map vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

1
public/dist/js/64.2db5913e.js.map vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

1
public/dist/js/999.7767bffa.js.map vendored Normal file

File diff suppressed because one or more lines are too long

2
public/dist/js/app.10acb235.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -96,3 +96,11 @@ export function exportAfterOrder(params) {
responseType: 'blob' responseType: 'blob'
}) })
} }
export function getPrintInfo(params) {
return http({
url: "/api/print/orders",
method: "get",
params
})
}

View File

@ -82,3 +82,19 @@ export function orderRest(params) {
method: 'get', method: 'get',
}) })
} }
export function shopAddress(params) {
return http({
url: "/api/shop_sends",
method: "get",
params,
});
}
export function updateShopAddress(params) {
return http({
url: '/api/shop_sends',
method: 'post',
params
})
}

View File

@ -0,0 +1,174 @@
<template>
<div>
<el-dialog :visible.sync="showVisible" title="地址管理" width="900px" @close="closeDialog">
<div style="margin-bottom: 20px;"><el-button type="primary" @click="addPrintAddr()">添加地址</el-button></div>
<div>
<el-table :data="addrList" border style="width: 100%;height: auto;" v-loading="loading">
<el-table-column prop="id" label="ID" align="center" width="70"/>
<el-table-column prop="name" label="姓名" align="center" width="100"/>
<el-table-column prop="mobile" label="联系方式" align="center" width="120"/>
<el-table-column label="详细地址">
<template slot-scope="scope">
<span>{{ scope.row.province }}{{ scope.row.city }}{{ scope.row.district }}{{ scope.row.detail }}</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" width="100">
<template slot-scope="scope">
<el-button type="primary" size="mini" @click="editPrintAddr(scope.row)">编辑</el-button>
</template>
</el-table-column>
</el-table>
</div>
</el-dialog>
<el-dialog :visible.sync="printVisible" :title="curAddrId ? '编辑地址' : '新增地址'" width="500px">
<el-form label-width="100px">
<el-form-item label="姓名:">
<el-input v-model="printForm.name" type="input" placeholder="请输入姓名" />
</el-form-item>
<el-form-item label="联系电话:">
<el-input v-model="printForm.mobile" type="input" placeholder="请输入联系电话" />
</el-form-item>
<el-form-item label="省:">
<el-input v-model="printForm.province" type="input" placeholder="请输入" />
</el-form-item>
<el-form-item label="市:">
<el-input v-model="printForm.city" type="input" placeholder="请输入号" />
</el-form-item>
<el-form-item label="区:">
<el-input v-model="printForm.district" type="input" placeholder="请输入" />
</el-form-item>
<el-form-item label="详细地址:">
<el-input v-model="printForm.detail" type="input" placeholder="街道-小区-门牌号" />
</el-form-item>
<el-form-item label="排序:">
<el-input v-model="printForm.sort" type="number" />
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="printVisible = false">取消</el-button>
<el-button type="primary" @click="commitPrintAddr()" :loading="btnLoading">确定</el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import { shopAddress, updateShopAddress } from "@/api/shop"
export default {
props: {
id: {
type: Number,
default: () => {
return 0
}
},
show: {
type: Boolean,
default: () => {
return false
}
}
},
data() {
return {
btnLoading: false,
showVisible: false,
addrList: [],
curAddrId: '',
loading: false,
printForm: {},
printVisible: false
}
},
methods: {
getAddrList() {
this.loading = true
let params = {
shop_id: this.id,
page: 1,
pageSize: 1000
}
shopAddress(params).then((res) => {
console.log(res)
this.addrList = res.data.data
this.loading = false
}).catch(() => {
this.loading = false
})
},
addPrintAddr() {
this.curAddrId = ''
this.printForm = {}
this.printVisible = true
},
commitPrintAddr(){
if(!this.printForm.name) {
ElMessage({ type: 'warning', message: '请输入姓名' })
return
} else if(!this.printForm.mobile) {
ElMessage({ type: 'warning', message: '请输入联系电话' })
return
} else if(!this.printForm.province || !this.printForm.city || !this.printForm.district || !this.printForm.detail) {
ElMessage({ type: 'warning', message: '请输入地址信息' })
return
}
this.btnLoading = true
let params = {
...this.printForm
}
if(this.curAddrId) {
params.id = this.curAddrId
updateShopAddress(params).then((res) => {
this.$message({ type: 'success', message: '编辑成功' })
this.printVisible = false
this.getAddrList()
this.btnLoading = false
}).catch(() => {
this.btnLoading = false
})
} else {
updateShopAddress(params).then((res) => {
this.$message({ type: 'success', message: '添加成功' })
this.printVisible = false
this.getAddrList()
this.btnLoading = false
}).catch(() => {
this.btnLoading = false
})
}
},
editPrintAddr(row) {
this.curAddrId = row.id
this.printForm = JSON.parse(JSON.stringify(row))
this.printVisible = true
},
closeDialog() {
this.showVisible = false
this.$emit('close')
}
},
watch: {
show: {
handler(newVal, oldVal) {
if (newVal) {
this.showVisible = true
this.addrList = []
this.printList = []
this.getAddrList()
}
},
deep: true,
immediate: true
}
}
}
</script>
<style scoped lang="scss">
</style>

View File

@ -0,0 +1,489 @@
<template>
<div class="conent">
<el-card>
<el-form ref="form" :inline="true" label-width="90px">
<el-form-item label="所属店铺:">
<el-select v-model="filter.shop_id" placeholder="店铺" @change="handleChoose()">
<el-option v-for="item in shopsList" :key="item.id" :label="item.name" :value="item.id">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="发货状态:">
<el-select v-model="filter.shipping_status" placeholder="发货状态" @change="handleChoose()">
<el-option value="" label="全部"></el-option>
<el-option value="0" label="未发货"></el-option>
<el-option value="2" label="部分发货"></el-option>
<el-option value="1" label="已发货"></el-option>
</el-select>
</el-form-item>
<el-form-item label="订单类型:">
<el-select v-model="filter.is_supplier" placeholder="订单类型" @change="handleChoose()">
<el-option value="" label="全部订单"></el-option>
<el-option :value="1" label="自卖团订单"></el-option>
<el-option :value="0" label="帮卖团订单"></el-option>
</el-select>
</el-form-item>
<el-form-item label="订单状态:">
<el-select v-model="filter.cancel_status" placeholder="订单状态" @change="handleChoose()">
<el-option value="" label="全部"></el-option>
<el-option :value="0" label="未取消"></el-option>
<el-option :value="1" label="已取消"></el-option>
</el-select>
</el-form-item>
<el-form-item label="售后状态:">
<el-select v-model="filter.after_sales_status" placeholder="售后状态" @change="handleChoose()">
<el-option value="" label="全部"></el-option>
<el-option :value="0" label="未售后"></el-option>
<el-option :value="1" label="有售后"></el-option>
</el-select>
</el-form-item>
<el-form-item label="商品数量:">
<el-select v-model="filter.goods_sku_num" placeholder="商品数量" @change="handleChoose()">
<el-option value="" label="全部"></el-option>
<el-option :value="1" label="1个商品"></el-option>
<el-option :value="2" label="2-5个商品"></el-option>
<el-option :value="6" label="6个以上商品"></el-option>
</el-select>
</el-form-item>
<el-form-item label="订单商品:">
<el-select v-model="filter.external_sku_ids" multiple filterable remote reserve-keyword placeholder="订单商品"
:remote-method="remoteMethod" :loading="searchLoading">
<el-option v-for="item in options" :key="item.external_sku_id" :label="item.title"
:value="item.external_sku_id">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="下单时间:">
<el-date-picker v-model="confirmAt" type="datetimerange" range-separator="-" start-placeholder=""
end-placeholder="止" format="yyyy-MM-dd HH:mm:ss" value-format="yyyy-MM-dd HH:mm:ss" @change="handleChoose()">
</el-date-picker>
</el-form-item>
<el-form-item label="跟团号:">
<el-input v-model="filter.pno" placeholder="跟团号" @keyup.enter.native="handleChoose"></el-input>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleChoose">筛选</el-button>
<el-button plain @click="handleReChoose">重置筛选</el-button>
<el-button type="primary" @click="openPrint">打印</el-button>
<!-- <el-button v-if="form.goods_sku_num === 2" type="primary">配货单导出</el-button> -->
<el-button type="warning" :loading="exportLoading" @click="handleExport" icon="el-icon-download">数据导出</el-button>
</el-form-item>
</el-form>
</el-card>
<el-card style="margin-top: 10px">
<el-table v-loading="loading" ref="multipleTable" :data="orderList" style="width: 100%" border
@selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="店铺名称" prop="shop.name" />
<el-table-column label="跟团号" align="center">
<template slot-scope="scope">
{{ scope.row.is_supplier ? '自卖团: ' : '帮卖团: ' }}
{{ scope.row.is_supplier ? scope.row.participate_no : scope.row.supply_participate_no }}
</template>
</el-table-column>
<el-table-column label="收件人信息" width="240">
<template slot-scope="scope">
<p>{{ scope.row.receiver_name }}&nbsp;&nbsp;{{ scope.row.receiver_mobile }}</p>
<p>{{ scope.row.receiver_address_province }}/{{ scope.row.receiver_address_city }}/{{
scope.row.receiver_address_district }}</p>
<p>{{ scope.row.receiver_address_detail }}</p>
</template>
</el-table-column>
<el-table-column prop="shipping_status" label="发货状态" align="center"></el-table-column>
<el-table-column prop="cancel_status" label="订单状态" align="center"></el-table-column>
<el-table-column prop="after_sales_status" label="售后状态" align="center"></el-table-column>
<el-table-column label="商品信息" width="380">
<template slot-scope="scope">
<div v-for="item in scope.row.items" :key="item.id">
{{ item.goods_name }}
<span style="color: red;">+{{ item.goods_number }}</span>
</div>
</template>
</el-table-column>
<el-table-column prop="confirm_at" label="下单时间" width="150" align="center"></el-table-column>
</el-table>
<div class="page-pagination">
<el-pagination
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current-page="page"
:page-sizes="[10, 50, 100]"
:page-size="per_page"
layout="prev, pager, next, sizes, jumper, total"
:total="total">
</el-pagination>
</div>
</el-card>
</div>
</template>
<script>
import { platOrderList, printOrders, printSuccess, exportOrder } from "@/api/plat";
import { storeList } from "@/api/shop"
import { goodsSkusList } from "@/api/goods"
import dayjs from 'dayjs'
export default {
data() {
return {
filter: {
shop_id: '',
shipping_status: '',
is_supplier: '',
cancel_status: 0,
after_sales_status: 0,
goods_sku_num: '',
external_sku_ids: '',
pno: ''
},
loading: false,
searchLoading: false,
options: [],
orderList: [],
total: 0,
page: 1, //
per_page: 10, //
shopsList: [],
confirmAt: [],
print_order_ids: '',
print_documents: [],
socket: null,
lockReconnect: false, //
timeout: 58 * 1000, //58
timeoutObj: null, //
serverTimeoutObj: null, //
timeoutnum: null, //
defaultPrinter: null,
taskIDArray: [],
requestIDGetGlobalConfig: '',
exportLoading: false
}
},
created() {
let start = dayjs().format('YYYY-MM-DD') + ' 00:00:00'
let end = dayjs().format('YYYY-MM-DD') + ' 23:59:59'
this.confirmAt = [start, end]
this.getOrderList()
this.getShopsList()
},
methods: {
getOrderList() {
this.loading = true
let params = {
...this.filter,
page: this.page,
per_page: this.per_page,
confirm_at_start: this.confirmAt ? this.confirmAt[0] : '',
confirm_at_end: this.confirmAt ? this.confirmAt[1] : ''
}
platOrderList(params).then((res) => {
this.orderList = res.data.data
this.total = res.data.meta.total
this.loading = false
}).catch(() => {
this.loading = false
})
},
getShopsList() {
let params = {
page: 0,
per_page: 99
}
storeList(params).then((res) => {
this.shopsList = res.data.data
})
},
handleSizeChange(val) {
this.page = 1
this.per_page = val
this.getOrderList()
},
handleCurrentChange(val) {
this.page = val
this.getOrderList()
},
handleChoose() {
this.page = 1
this.getOrderList()
},
handleReChoose() {
this.filter = {
shop_id: '',
shipping_status: '',
is_supplier: '',
cancel_status: 0,
after_sales_status: 0,
goods_sku_num: '',
external_sku_ids: '',
pno: ''
}
this.page = 1
this.getOrderList()
},
remoteMethod(query) {
if (query !== '') {
this.searchLoading = true;
setTimeout(() => {
this.searchLoading = false;
goodsSkusList(query).then((res) => {
this.options = res.data.filter(item => {
return item.title.toLowerCase().indexOf(query.toLowerCase()) > -1;
});
});
}, 200);
} else {
this.options = [];
}
},
print() {
const print_loading = this.$loading({
lock: true,
text: 'Loading',
spinner: 'el-icon-loading',
background: 'rgba(0, 0, 0, 0.7)'
});
printOrders(this.filter).then((res) => {
this.print_documents = res.data.documents;
this.print_order_ids = res.data.order_ids;
print_loading.close();
this.doPrint();
})
},
handleSelectionChange(val) {
let ids = []
val.forEach((item) => {
ids.push(item.id)
})
this.filter.ids = ids
},
initWebSocket() {
//weosocket
const wsuri = "ws://127.0.0.1:5000";
this.socket = new WebSocket(wsuri);
//
this.socket.onmessage = this.websocketonmessage;
//
this.socket.onopen = this.websocketonopen;
//
this.socket.onerror = this.websocketonerror;
//
this.socket.onclose = this.websocketclose;
},
//
websocketonopen() {
//
this.start();
if (this.socket.readyState === 1) {
//
this.getPrinters();
}
},
start() {
//
console.log("开启心跳");
var self = this;
self.timeoutObj && clearTimeout(self.timeoutObj);
self.serverTimeoutObj && clearTimeout(self.serverTimeoutObj);
self.timeoutObj = setTimeout(function () {
//
if (self.socket.readyState == 1) {
//
} else {
//
self.reconnect();
}
self.serverTimeoutObj = setTimeout(function () {
//
self.socket.close();
}, self.timeout);
}, self.timeout);
},
reconnect() {
//
var that = this;
if (that.lockReconnect) {
return;
}
that.lockReconnect = true;
//
that.timeoutnum && clearTimeout(that.timeoutnum);
that.timeoutnum = setTimeout(function () {
//
that.initWebSocket();
that.lockReconnect = false;
}, 5000);
},
//
websocketonerror() {
console.log("出现错误");
this.reconnect();
},
//
websocketonmessage(e) {
console.log(e);
if (JSON.parse(e.data).printers !== undefined) {
this.defaultPrinter = JSON.parse(e.data).defaultPrinter;
}
// JSON.parse JSON
console.log("默认打印机" + this.defaultPrinter)
if (JSON.parse(e.data).status === 'success') {
console.log("打印就绪..")
}
if (JSON.parse(e.data).status === 'failed') {
console.log("打印未就绪..")
}
if (JSON.parse(e.data).taskStatus === 'printed') {
console.log('出纸成功--打印成功')
//
printSuccess({ order_ids: this.print_order_ids }).then((res) => {
console.log(res)
})
}
if (JSON.parse(e.data).taskStatus === 'failed') {
console.log("打印失败!")
}
if (JSON.parse(e.data).taskStatus === 'canceled') {
console.log("打印取消!")
}
//
this.reset();
},
reset() {
//
var that = this;
//
clearTimeout(that.timeoutObj);
clearTimeout(that.serverTimeoutObj);
//
that.start();
},
websocketsend(Data) {
//
this.socket.send(Data);
},
//
websocketclose(e) {
//
console.log("断开连接", e);
//
this.reconnect();
},
getPrinters() {
var request = this.getRequestObject("getPrinters");
this.websocketsend(JSON.stringify(request));
},
doPrint() {
var request = this.getRequestObject("print");
request.task = new Object();
request.task.taskID = this.getUUID(8, 10);
// taskID taskIDArray
this.taskIDArray.push(request.task.taskID)
// ID
this.requestIDGetGlobalConfig = request.task.taskID
request.task.preview = false;
request.task.printer = this.defaultPrinter;
request.task.documents = this.print_documents;
console.log(request.task.documents)
this.websocketsend(JSON.stringify(request));
},
getRequestObject(cmd) {
var request = new Object();
// :ID,ID
request.requestID = this.getUUID(8, 16);
//:
request.version = "1.0";
//:
request.cmd = cmd;
return request;
},
getUUID(len, radix) {
var chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'.split('');
var uuid = [], i;
radix = radix || chars.length;
if (len) {
for (i = 0; i < len; i++) uuid[i] = chars[0 | Math.random() * radix];
} else {
var r;
uuid[8] = uuid[13] = uuid[18] = uuid[23] = '-';
uuid[14] = '4';
for (i = 0; i < 36; i++) {
if (!uuid[i]) {
r = 0 | Math.random() * 16;
uuid[i] = chars[(i == 19) ? (r & 0x3) | 0x8 : r];
}
}
}
return uuid.join('');
},
handleExport() {
this.exportLoading = true
let params = {
...this.form,
page: 1,
per_page: this.per_page,
confirm_at_start: this.confirmAt[0] ? this.confirmAt[0] : '',
confirm_at_end: this.confirmAt[1] ? this.confirmAt[1] : '',
is_export: 1
}
// window.open("/api/plat_orders?" + this.objectToQueryString(params))
exportOrder(params).then((res) => {
this.downLoadXls(res.data)
this.$message({ type: 'success', message: '导出成功!' })
this.exportLoading = false
}).catch(() => {
this.exportLoading = false
})
},
downLoadXls(response) {
const content = response
const blob = new Blob([content])
const today = new Date().toLocaleDateString()
const fileName = `订单列表${today}.xlsx`
if ('download' in document.createElement('a')) {
// IE
const elink = document.createElement('a')
elink.download = fileName
elink.style.display = 'none'
elink.href = URL.createObjectURL(blob)
document.body.appendChild(elink)
elink.click()
URL.revokeObjectURL(elink.href) // URL
document.body.removeChild(elink)
} else {
// IE10+
navigator.msSaveBlob(blob, fileName)
}
},
objectToQueryString(obj) {
return Object.keys(obj)
.map(key => encodeURIComponent(key) + '=' + (obj[key] ? encodeURIComponent(obj[key]) : ''))
.join('&');
}
},
mounted() {
}
}
</script>
<style lang="scss" scoped>
</style>

View File

@ -18,6 +18,10 @@
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item label="订单ID:">
<el-input v-model="filter.id" placeholder="订单编号" @keyup.enter.native="handleChoose"></el-input>
</el-form-item>
<el-form-item label="订单类型:"> <el-form-item label="订单类型:">
<el-select v-model="filter.is_supplier" placeholder="订单类型" @change="handleChoose()"> <el-select v-model="filter.is_supplier" placeholder="订单类型" @change="handleChoose()">
<el-option value="" label="全部订单"></el-option> <el-option value="" label="全部订单"></el-option>
@ -60,20 +64,31 @@
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item label="下单时间:"> <el-form-item label="商品编码:">
<el-date-picker v-model="confirmAt" type="datetimerange" range-separator="-" start-placeholder="" <el-input v-model="filter.external_sku_id" placeholder="商品编码" @keyup.enter.native="handleChoose"></el-input>
end-placeholder="止" format="yyyy-MM-dd HH:mm:ss" value-format="yyyy-MM-dd HH:mm:ss" @change="handleChoose()">
</el-date-picker>
</el-form-item> </el-form-item>
<el-form-item label="跟团号:"> <el-form-item label="跟团号:">
<el-input v-model="filter.pno" placeholder="跟团号" @keyup.enter.native="handleChoose"></el-input> <el-input v-model="filter.pno" placeholder="跟团号" @keyup.enter.native="handleChoose"></el-input>
</el-form-item> </el-form-item>
<el-form-item label="打印状态:">
<el-select v-model="filter.print_status" placeholder="请选择" @change="handleChoose()" clearable>
<el-option :value="1" label="已打印"></el-option>
<el-option :value="0" label="未打印"></el-option>
</el-select>
</el-form-item>
<el-form-item label="下单时间:">
<el-date-picker v-model="confirmAt" type="datetimerange" range-separator="-" start-placeholder=""
end-placeholder="止" format="yyyy-MM-dd HH:mm:ss" value-format="yyyy-MM-dd HH:mm:ss" @change="handleChoose()">
</el-date-picker>
</el-form-item>
<el-form-item> <el-form-item>
<el-button type="primary" @click="handleChoose">筛选</el-button> <el-button type="primary" @click="handleChoose">筛选</el-button>
<el-button plain @click="handleReChoose">重置筛选</el-button> <el-button plain @click="handleReChoose">重置筛选</el-button>
<el-button type="primary" @click="print">打印</el-button> <el-button type="primary" @click="openPrint">打印</el-button>
<!-- <el-button v-if="form.goods_sku_num === 2" type="primary">配货单导出</el-button> --> <!-- <el-button v-if="form.goods_sku_num === 2" type="primary">配货单导出</el-button> -->
<el-button type="warning" :loading="exportLoading" @click="handleExport" icon="el-icon-download">数据导出</el-button> <el-button type="warning" :loading="exportLoading" @click="handleExport" icon="el-icon-download">数据导出</el-button>
</el-form-item> </el-form-item>
@ -83,7 +98,8 @@
<el-card style="margin-top: 10px"> <el-card style="margin-top: 10px">
<el-table v-loading="loading" ref="multipleTable" :data="orderList" style="width: 100%" border <el-table v-loading="loading" ref="multipleTable" :data="orderList" style="width: 100%" border
@selection-change="handleSelectionChange"> @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" /> <el-table-column type="selection" width="55" align="center" :selectable="selectable" />
<el-table-column label="订单ID" prop="id" width="80" align="center" />
<el-table-column label="店铺名称" prop="shop.name" /> <el-table-column label="店铺名称" prop="shop.name" />
<el-table-column label="跟团号" align="center"> <el-table-column label="跟团号" align="center">
<template slot-scope="scope"> <template slot-scope="scope">
@ -103,7 +119,7 @@
<el-table-column prop="shipping_status" label="发货状态" align="center"></el-table-column> <el-table-column prop="shipping_status" label="发货状态" align="center"></el-table-column>
<el-table-column prop="cancel_status" label="订单状态" align="center"></el-table-column> <el-table-column prop="cancel_status" label="订单状态" align="center"></el-table-column>
<el-table-column prop="after_sales_status" label="售后状态" align="center"></el-table-column> <el-table-column prop="after_sales_status" label="售后状态" align="center"></el-table-column>
<el-table-column label="商品信息" width="380"> <el-table-column label="商品信息" min-width="150">
<template slot-scope="scope"> <template slot-scope="scope">
<div v-for="item in scope.row.items" :key="item.id"> <div v-for="item in scope.row.items" :key="item.id">
{{ item.goods_name }} {{ item.goods_name }}
@ -111,6 +127,23 @@
</div> </div>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="面单信息" min-width="100" align="center">
<template slot-scope="scope">
<div v-if="scope.row.waybill">
<el-tag type="info" v-if="scope.row.waybill.status == 0">待申请运单</el-tag>
<el-tag type="primary" v-else-if="scope.row.waybill.status == 1">已申请运单</el-tag>
<el-tag type="warning" v-else-if="scope.row.waybill.status == 2">待打印</el-tag>
<el-tag type="success" v-else-if="scope.row.waybill.status == 3">打印成功</el-tag>
<div style="line-height: 16px;margin-top: 10px;">运单号{{ scope.row.waybill.waybill_code }}</div>
</div>
</template>
</el-table-column>
<el-table-column label="打印状态" align="center" width="110">
<template slot-scope="scope">
<span v-if="scope.row.print_status == 1" style="color: rgb(4, 190, 2);">已打印</span>
<span v-else>未打印</span>
</template>
</el-table-column>
<el-table-column prop="confirm_at" label="下单时间" width="150" align="center"></el-table-column> <el-table-column prop="confirm_at" label="下单时间" width="150" align="center"></el-table-column>
</el-table> </el-table>
@ -119,18 +152,66 @@
@size-change="handleSizeChange" @size-change="handleSizeChange"
@current-change="handleCurrentChange" @current-change="handleCurrentChange"
:current-page="page" :current-page="page"
:page-sizes="[10, 50, 100]" :page-sizes="[10, 50, 100, 500]"
:page-size="per_page" :page-size="per_page"
layout="prev, pager, next, sizes, jumper, total" layout="prev, pager, next, sizes, jumper, total"
:total="total"> :total="total">
</el-pagination> </el-pagination>
</div> </div>
</el-card> </el-card>
<el-dialog title="发货信息" width="960px" :visible.sync="showPrint" :close-on-click-modal="false" @close="closePrint">
<div v-if="connectStatus == 1" style="margin: -20px 0 20px;">
<el-alert title="已建立连接" type="success" show-icon></el-alert>
</div>
<el-table :data="tempList" style="width: 100%" border>
<el-table-column label="订单ID" prop="id" width="80" align="center" />
<el-table-column label="收件人信息" width="240">
<template slot-scope="scope">
<p>{{ scope.row.receiver_name }}&nbsp;&nbsp;{{ scope.row.receiver_mobile }}</p>
<p>{{ scope.row.receiver_address_province }}/{{ scope.row.receiver_address_city }}/{{
scope.row.receiver_address_district }}</p>
<p>{{ scope.row.receiver_address_detail }}</p>
</template>
</el-table-column>
<el-table-column prop="shipping_status" label="发货状态" align="center"></el-table-column>
<el-table-column prop="cancel_status" label="订单状态" align="center"></el-table-column>
<el-table-column label="商品信息" width="220">
<template slot-scope="scope">
<div v-for="item in scope.row.items" :key="item.id">
{{ item.goods_name }}
<span style="color: red;">+{{ item.goods_number }}</span>
</div>
</template>
</el-table-column>
<el-table-column label="打印状态" align="center">
<template slot-scope="scope">
<span v-if="scope.row.print_status == 0 || !scope.row.print_status">等待打印</span>
<span v-else-if="scope.row.print_status == 1" style="color: rgb(4, 190, 2);">打印成功</span>
<div v-else-if="scope.row.print_status == 2">
<div style="color: #f00;">打印失败</div>
<span style="font-size: 12px;color: #777;">{{ scope.row.fail_msg }}</span>
</div>
</template>
</el-table-column>
</el-table>
<el-form label-width="80px" style="margin-top: 20px;">
<el-form-item label="打印机:">
<el-select v-model="printName" placeholder="请选择打印机">
<el-option v-for="(item, i) in printsList" :key="i" :label="item" :value="item"></el-option>
</el-select>
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button plain @click="showPrint = false">取消</el-button>
<el-button type="primary" @click="toPrint()" :loading="loading1">保存</el-button>
</span>
</el-dialog>
</div> </div>
</template> </template>
<script> <script>
import { platOrderList, printOrders, printSuccess, exportOrder } from "@/api/plat"; import { platOrderList, printOrders, getPrintInfo, exportOrder, printSuccess } from "@/api/plat";
import { storeList } from "@/api/shop" import { storeList } from "@/api/shop"
import { goodsSkusList } from "@/api/goods" import { goodsSkusList } from "@/api/goods"
import dayjs from 'dayjs' import dayjs from 'dayjs'
@ -146,7 +227,8 @@ export default {
after_sales_status: 0, after_sales_status: 0,
goods_sku_num: '', goods_sku_num: '',
external_sku_ids: '', external_sku_ids: '',
pno: '' pno: '',
external_sku_id: ''
}, },
loading: false, loading: false,
searchLoading: false, searchLoading: false,
@ -168,7 +250,15 @@ export default {
defaultPrinter: null, defaultPrinter: null,
taskIDArray: [], taskIDArray: [],
requestIDGetGlobalConfig: '', requestIDGetGlobalConfig: '',
exportLoading: false exportLoading: false,
webSocket: null,
chooseList: [],
showPrint: false,
printName: '',
printsList: [],
loading1: false,
connectStatus: -1,
tempList: []
} }
}, },
created() { created() {
@ -261,176 +351,15 @@ export default {
this.doPrint(); this.doPrint();
}) })
}, },
selectable(row, index) {
if (row.shipping_status == '未发货' && row.cancel_status == '未取消' && row.print_status !== 1) {
return true
} else {
return false
}
},
handleSelectionChange(val) { handleSelectionChange(val) {
let ids = [] this.chooseList = val
val.forEach((item) => {
ids.push(item.id)
})
this.filter.ids = ids
},
initWebSocket() {
//weosocket
const wsuri = "ws://127.0.0.1:5000";
this.socket = new WebSocket(wsuri);
//
this.socket.onmessage = this.websocketonmessage;
//
this.socket.onopen = this.websocketonopen;
//
this.socket.onerror = this.websocketonerror;
//
this.socket.onclose = this.websocketclose;
},
//
websocketonopen() {
//
this.start();
if (this.socket.readyState === 1) {
//
this.getPrinters();
}
},
start() {
//
console.log("开启心跳");
var self = this;
self.timeoutObj && clearTimeout(self.timeoutObj);
self.serverTimeoutObj && clearTimeout(self.serverTimeoutObj);
self.timeoutObj = setTimeout(function () {
//
if (self.socket.readyState == 1) {
//
} else {
//
self.reconnect();
}
self.serverTimeoutObj = setTimeout(function () {
//
self.socket.close();
}, self.timeout);
}, self.timeout);
},
reconnect() {
//
var that = this;
if (that.lockReconnect) {
return;
}
that.lockReconnect = true;
//
that.timeoutnum && clearTimeout(that.timeoutnum);
that.timeoutnum = setTimeout(function () {
//
that.initWebSocket();
that.lockReconnect = false;
}, 5000);
},
//
websocketonerror() {
console.log("出现错误");
this.reconnect();
},
//
websocketonmessage(e) {
console.log(e);
if (JSON.parse(e.data).printers !== undefined) {
this.defaultPrinter = JSON.parse(e.data).defaultPrinter;
}
// JSON.parse JSON
console.log("默认打印机" + this.defaultPrinter)
if (JSON.parse(e.data).status === 'success') {
console.log("打印就绪..")
}
if (JSON.parse(e.data).status === 'failed') {
console.log("打印未就绪..")
}
if (JSON.parse(e.data).taskStatus === 'printed') {
console.log('出纸成功--打印成功')
//
printSuccess({ order_ids: this.print_order_ids }).then((res) => {
console.log(res)
})
}
if (JSON.parse(e.data).taskStatus === 'failed') {
console.log("打印失败!")
}
if (JSON.parse(e.data).taskStatus === 'canceled') {
console.log("打印取消!")
}
//
this.reset();
},
reset() {
//
var that = this;
//
clearTimeout(that.timeoutObj);
clearTimeout(that.serverTimeoutObj);
//
that.start();
},
websocketsend(Data) {
//
this.socket.send(Data);
},
//
websocketclose(e) {
//
console.log("断开连接", e);
//
this.reconnect();
},
getPrinters() {
var request = this.getRequestObject("getPrinters");
this.websocketsend(JSON.stringify(request));
},
doPrint() {
var request = this.getRequestObject("print");
request.task = new Object();
request.task.taskID = this.getUUID(8, 10);
// taskID taskIDArray
this.taskIDArray.push(request.task.taskID)
// ID
this.requestIDGetGlobalConfig = request.task.taskID
request.task.preview = false;
request.task.printer = this.defaultPrinter;
request.task.documents = this.print_documents;
console.log(request.task.documents)
this.websocketsend(JSON.stringify(request));
},
getRequestObject(cmd) {
var request = new Object();
// :ID,ID
request.requestID = this.getUUID(8, 16);
//:
request.version = "1.0";
//:
request.cmd = cmd;
return request;
},
getUUID(len, radix) {
var chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'.split('');
var uuid = [], i;
radix = radix || chars.length;
if (len) {
for (i = 0; i < len; i++) uuid[i] = chars[0 | Math.random() * radix];
} else {
var r;
uuid[8] = uuid[13] = uuid[18] = uuid[23] = '-';
uuid[14] = '4';
for (i = 0; i < 36; i++) {
if (!uuid[i]) {
r = 0 | Math.random() * 16;
uuid[i] = chars[(i == 19) ? (r & 0x3) | 0x8 : r];
}
}
}
return uuid.join('');
}, },
handleExport() { handleExport() {
this.exportLoading = true this.exportLoading = true
@ -476,6 +405,143 @@ export default {
return Object.keys(obj) return Object.keys(obj)
.map(key => encodeURIComponent(key) + '=' + (obj[key] ? encodeURIComponent(obj[key]) : '')) .map(key => encodeURIComponent(key) + '=' + (obj[key] ? encodeURIComponent(obj[key]) : ''))
.join('&'); .join('&');
},
// https://open.jdl.com/#/open-business-document/access-guide/157/54222
initWebSocket() {
try {
let State = new Array('正在连接', '已建立连接', '正在关闭连接', '已关闭连接')
let host = 'ws://localhost:9113'
const that = this
that.webSocket = new WebSocket(host)
that.webSocket.onopen = function() {
console.log(that.webSocket)
that.connectStatus = that.webSocket.readyState
that.getPrintList()
}
that.webSocket.onmessage = function(msg) {
let returnData = msg.data
let json = eval('(' + returnData + ')')
let content = json.content;
if(json.code == '6'){ //
that.printsList = content.split(',')
} else if(json.success == 'true' && json.code == '2'){ //
console.log('0.0', json)
that.getOrderList()
if(json.message == 'print success all'){ //
let ids = []
that.tempList.forEach((item, index) => {
ids.push(item.id)
that.$set(that.tempList[index], 'print_status', 1)
})
let params = {
order_ids: ids.join(',')
}
printSuccess(params)
} else if(json.message == 'print success, partly wrong.') { //
for (let i = 0; i < json.detailinfo.errors.length; i++) {
const it = json.detailinfo.errors[i]
that.$set(that.tempList[it.seq * 1 - 1], 'print_status', 2) //
that.$set(that.tempList[it.seq * 1 - 1], 'fail_msg', it.msg) //
}
let ids = []
that.tempList.forEach((item, index) => {
if(!item.fail_msg) {
that.$set(that.tempList[index], 'print_status', 1)
ids.push(item.id)
}
})
let params = {
order_ids: ids.join(',')
}
printSuccess(params) //
}
} else if(json.success == 'true' && json.code == '2') { //
for (let i = 0; i < json.detailinfo.detail.length; i++) {
const it = json.detailinfo.detail[i]
that.$set(that.tempList[it.seq * 1 - 1], 'print_status', 2)
that.$set(that.tempList[it.seq * 1 - 1], 'fail_msg', it.msg)
}
}
}
that.webSocket.onclose = function() {
that.$message({ type: 'info', message: State[that.webSocket.readyState] })
}
} catch (error) {
that.$message({ type: 'info', message: '连接失败' })
}
},
getPrintList() {
if(this.printsList.length){
return
}
//
var text = "{\"orderType\":\"GET_Printers\",\"pin\":\"用户名\",\"parameters\":{ \"tempUrl\":\"\", \"printName\":\"\", \"printData\":\"\" }}";
this.webSocket.send(text)
},
openPrint() {
if(this.chooseList.length) {
this.initWebSocket()
this.tempList = JSON.parse(JSON.stringify(this.chooseList))
this.tempList.forEach((item) => {
item.fail_msg = ''
})
this.showPrint = true
} else {
this.$message({ type: 'info', message: '请选择需要打印的订单' })
}
},
toPrint() {
if(!this.printName) {
this.$message({ type: 'info', message: '请选择打印机' })
return
}
this.loading1 = true
let ids = []
this.chooseList.forEach((item) => {
ids.push(item.id)
})
let params = {
ids: ids.join(',')
}
getPrintInfo(params).then((res) => {
let printObject = res.data.data
console.log(printObject)
let parameters = {
"orderType": "print",
"version": "2",
"parameters": {
"printName": this.printName,
"contents": []
}
}
for(let key in printObject) {
console.log(key)
parameters.parameters.contents.push({
"tempUrl": printObject[key].templateUrl,
"printData": printObject[key].encryptedData
})
}
this.webSocket.send(JSON.stringify(parameters))
this.loading1 = false
}).catch(() => {
this.loading1 = false
})
},
toPrint1() {
let printCmd = 'print'
let temp = 'https://template-content.jd.com/template-oss?tempCode=jdkd76x130'
let tempUser = ''
let printDataUser = ''
let printData = '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'
let text = "{\"orderType\": \""+ printCmd +"\", \"parameters\": {\"printName\": \""+ this.printName +"\", \"tempUrl\": \""+ temp +"\", \"customTempUrl\": \""+ tempUser +"\", \"customData\": [" + printDataUser +"], \"printData\": [\""+ printData +"\"] } }";
this.webSocket.send(text)
},
closePrint() {
this.$refs.multipleTable.clearSelection()
this.chooseList = []
this.printName = ''
this.webSocket.close()
} }
} }
} }

View File

@ -18,28 +18,29 @@
</el-input> </el-input>
</template> </template>
</el-table-column> --> </el-table-column> -->
<el-table-column label="操作" width="280"> <el-table-column label="操作" width="360">
<template slot-scope="scope"> <template slot-scope="scope">
<el-button type="danger" v-if="scope.row.status === '未授权'" size="mini"><a :href="scope.row.authUrl" <el-button type="danger" v-if="scope.row.status === '未授权'" size="mini"><a :href="scope.row.authUrl"
target="_blank" rel="noopener noreferrer">授权</a> target="_blank" rel="noopener noreferrer">授权</a>
</el-button> </el-button>
<div v-if="scope.row.status === '已授权'"> <template v-if="scope.row.status === '已授权'">
<el-button type="success" :disabled="true" size="mini">{{ scope.row.status }}</el-button> <el-button type="success" :disabled="true" size="mini">{{ scope.row.status }}</el-button>
<el-button @click="download(scope.row)" size="mini">下载商品</el-button> <el-button @click="download(scope.row)" size="mini">下载商品</el-button>
<el-button type="primary" v-if="scope.row.status === '已授权'" size="mini" <el-button type="primary" v-if="scope.row.status === '已授权'" size="mini"
@click="syncShopStock(scope.row.id)">同步库存</el-button> @click="syncShopStock(scope.row.id)">同步库存</el-button>
</div> </template>
<div v-if="scope.row.status === '重新授权'"> <template v-if="scope.row.status === '重新授权'">
<el-button type="danger" target="_blank" size="mini"> <el-button type="danger" target="_blank" size="mini">
<a :href="scope.row.authUrl" rel="noopener noreferrer">重新授权</a> <a :href="scope.row.authUrl" rel="noopener noreferrer">重新授权</a>
</el-button> </el-button>
<el-button @click="download(scope.row)" size="mini">下载商品</el-button> <el-button @click="download(scope.row)" size="mini">下载商品</el-button>
</div> </template>
<div v-if="scope.row.status === '无需授权'"> <template v-if="scope.row.status === '无需授权'">
<el-button type="success" :disabled="true" size="mini">{{ scope.row.status }}</el-button> <el-button type="info" :disabled="true" size="mini">{{ scope.row.status }}</el-button>
</div> </template>
<el-button type="warning" size="mini" @click="openAddr(scope.row.id)">发货地址</el-button>
</template> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>
@ -72,14 +73,22 @@
<el-button type="primary" @click="addSubmit"> </el-button> <el-button type="primary" @click="addSubmit"> </el-button>
</div> </div>
</el-dialog> </el-dialog>
<shop-address :show="showAddr" :id="shop_id" @close="showAddr = false"></shop-address>
</div> </div>
</template> </template>
<script> <script>
import { shopListId, shopAdd, storeList, downloadGoods, updateStore, syncStoreStock } from "../../api/shop"; import { shopListId, shopAdd, storeList, downloadGoods, updateStore, syncStoreStock } from "../../api/shop";
import shopAddress from "@/components/shop/address.vue"
export default { export default {
components: { shopAddress },
data() { data() {
return { return {
showAddr: false,
shop_id: 0,
dialogFormVisible: false, dialogFormVisible: false,
form: { form: {
name: "", name: "",
@ -98,7 +107,7 @@ export default {
], ],
}, },
storeId: [], // id storeId: [], // id
loading: true, loading: false,
tableData: [], tableData: [],
Paginationdata: {}, // Paginationdata: {}, //
current_page: 1, // current_page: 1, //
@ -135,6 +144,7 @@ export default {
// //
getStoreList() { getStoreList() {
this.loading = true
let page = { let page = {
page: this.current_page, page: this.current_page,
per_page: this.per_page, per_page: this.per_page,
@ -142,8 +152,11 @@ export default {
storeList(page).then((res) => { storeList(page).then((res) => {
this.tableData = res.data.data; this.tableData = res.data.data;
this.Paginationdata = res.data.meta; this.Paginationdata = res.data.meta;
});
this.loading = false this.loading = false
}).catch(() => {
this.loading = false
})
}, },
// //
@ -167,19 +180,21 @@ export default {
// //
download(row) { download(row) {
const loading = this.$loading({ const Loading = this.$loading({
lock: true, lock: true,
text: row.name + ' 店铺商品下载中...', text: row.name + ' 店铺商品下载中...',
spinner: 'el-icon-loading', spinner: 'el-icon-loading',
background: 'rgba(0, 0, 0, 0.7)' background: 'rgba(0, 0, 0, 0.7)'
}); });
downloadGoods(row.id).then((res) => { downloadGoods(row.id).then((res) => {
loading.close(); Loading.close();
this.$message({ this.$message({
type: "success", type: "success",
message: res.data.message, message: res.data.message,
}); });
}); }).catch(() => {
Loading.close()
})
}, },
handleCellChange(row) { handleCellChange(row) {
this.$confirm('确认修改成本变动吗?', '提示', { this.$confirm('确认修改成本变动吗?', '提示', {
@ -197,19 +212,25 @@ export default {
}, },
// //
syncShopStock(id) { syncShopStock(id) {
let loading = this.$loading({ const Loading = this.$loading({
lock: true, lock: true,
text: '店铺商品库存同步中...', text: '店铺商品库存同步中...',
spinner: 'el-icon-loading', spinner: 'el-icon-loading',
background: 'rgba(0, 0, 0, 0.7)' background: 'rgba(0, 0, 0, 0.7)'
}); });
syncStoreStock({ shop_id: id }).then((res) => { syncStoreStock({ shop_id: id }).then((res) => {
loading.close(); Loading.close()
this.$message({ this.$message({
type: 'info', type: 'info',
message: res.data.message message: res.data.message
}) })
}); }).catch(() => {
Loading.close()
})
},
openAddr(id) {
this.shop_id = id
this.showAddr = true
} }
}, },
}; };

View File

@ -72,9 +72,13 @@ Route::middleware(['auth:api', 'check.permissions'])->group(function () {
Route::get('shop/ship', [ShipController::class, 'index'])->name('shop_ship.index'); Route::get('shop/ship', [ShipController::class, 'index'])->name('shop_ship.index');
Route::get('print/orders', [BusinessOrderController::class, 'print'])->name('order.print'); Route::get('print/orders', [BusinessOrderController::class, 'print'])->name('order.print');
Route::put('print/success', [BusinessOrderController::class, 'printSuccess'])->name('order.printSuccess'); Route::put('print/success', [BusinessOrderController::class, 'printSuccess'])->name('order.printSuccess');
Route::get('waybill/queryTrace', [\App\Http\Controllers\Business\WaybillController::class, 'queryTrace']);
Route::post('waybill/cancel', [\App\Http\Controllers\Business\WaybillController::class, 'cancel']);
// 发货信息 // 发货信息
Route::get('shop/ship/senders', [ShipController::class, 'getSenders'])->name('shop_ship.senders.get'); Route::get('shop/ship/senders', [ShipController::class, 'getSenders'])->name('shop_ship.senders.get');
Route::post('shop/ship/senders', [ShipController::class, 'saveSenders'])->name('shop_ship.senders.save'); Route::post('shop/ship/senders', [ShipController::class, 'saveSenders'])->name('shop_ship.senders.save');
Route::resource('shop_sends', 'Shop\ShopSendsController', ['only' => ['index', 'store']]);
// 数据中心 // 数据中心
Route::get('data_center/sales_report', [DataCenterController::class, 'salesReport'])->name('sales_report.index'); Route::get('data_center/sales_report', [DataCenterController::class, 'salesReport'])->name('sales_report.index');
//供应商 //供应商

View File

@ -1,5 +1,6 @@
<?php <?php
use App\Services\Business\BusinessFactory;
use Illuminate\Foundation\Inspiring; use Illuminate\Foundation\Inspiring;
/* /*