mirror of
https://gitlab.com/CIEFWorldwideSdnBhd/exchange-2.0.git
synced 2026-08-19 04:23:55 +00:00
1688 integration
This commit is contained in:
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
namespace App\Classes\General\Open1688;
|
||||
|
||||
class SignatureUtil {
|
||||
|
||||
/**
|
||||
*
|
||||
* @param $path
|
||||
* @param array $parameters
|
||||
* @param RequestPolicy $requestPolicy
|
||||
* @param ClientPolicy $clientPolicy
|
||||
* @return string
|
||||
*/
|
||||
public static function signature($path, array $parameters) {
|
||||
$paramsToSign = array ();
|
||||
foreach ( $parameters as $k => $v ) {
|
||||
$paramToSign = $k . $v;
|
||||
Array_push ( $paramsToSign, $paramToSign );
|
||||
}
|
||||
sort ( $paramsToSign );
|
||||
$implodeParams = implode ( $paramsToSign );
|
||||
$pathAndParams = $path . $implodeParams;
|
||||
$sign = hash_hmac ( "sha1", $pathAndParams, config('open1688.app_secret'), true );
|
||||
$signHexWithLowcase = bin2hex ( $sign );
|
||||
$signHexUppercase = strtoupper ( $signHexWithLowcase );
|
||||
return $signHexUppercase;
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
@@ -0,0 +1,79 @@
|
||||
<?php
|
||||
|
||||
namespace App\Classes\General;
|
||||
|
||||
use App\Classes\Exceptions\MalformedRequestException;
|
||||
use App\Classes\General\Open1688\SignatureUtil;
|
||||
use Symfony\Component\HttpFoundation\Exception\BadRequestException;
|
||||
|
||||
class Open1688Helper
|
||||
{
|
||||
|
||||
public static function productSearchKeyword($keyword, $page) {
|
||||
$requestData = [
|
||||
'offerQueryParam' => "{\"keyword\":\"$keyword\",\"beginPage\":$page,\"pageSize\":18,\"country\":\"en\"}",
|
||||
];
|
||||
|
||||
return self::getFromOpen1688($requestData, config('open1688.product_search_keyword_api'));
|
||||
}
|
||||
|
||||
public static function productDetail($offerId) {
|
||||
$requestData = [
|
||||
'offerDetailParam' => "{\"offerId\":$offerId,\"country\":\"en\"}",
|
||||
];
|
||||
|
||||
return self::getFromOpen1688($requestData, config('open1688.product_detail_api'));
|
||||
}
|
||||
|
||||
private static function getFromOpen1688($requestData, $apiName) {
|
||||
$requestData["access_token"] = config('open1688.access_token');
|
||||
|
||||
$signaturedStr = self::generateRequestSignature($apiName, $requestData);
|
||||
$url = config('open1688.url') . $apiName . "/" . config('open1688.app_key') . "?_aop_signature=" . $signaturedStr;
|
||||
|
||||
|
||||
foreach($requestData as $k => $v) {
|
||||
$url = $url . "&$k=$v";
|
||||
}
|
||||
|
||||
try {
|
||||
$client = new \GuzzleHttp\Client(['verify' => false]);
|
||||
$response = $client->request('GET', $url);
|
||||
$body = $response->getBody();
|
||||
$contents = json_decode($body, true);
|
||||
|
||||
return $contents['result']['result'];
|
||||
} catch(\GuzzleHttp\Exception\RequestException $exception){
|
||||
throw new MalformedRequestException(json_decode($exception->getResponse()->getBody()->getContents(), true)['error_message']);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static function generateRequestSignature ($apiName, $requestData) {
|
||||
$pathToSign = self::generateAPIPath($apiName);
|
||||
$signaturedStr = SignatureUtil::signature ($pathToSign, $requestData);
|
||||
|
||||
return $signaturedStr;
|
||||
}
|
||||
|
||||
private static function generateAPIPath($apiName) {
|
||||
$urlResult = "";
|
||||
$defs = array (
|
||||
$urlResult,
|
||||
"param2",
|
||||
"/",
|
||||
"1",
|
||||
"/",
|
||||
"com.alibaba.fenxiao.crossborder",
|
||||
"/",
|
||||
$apiName,
|
||||
"/",
|
||||
config('open1688.app_key')
|
||||
);
|
||||
|
||||
$urlResult = implode ( $defs );
|
||||
|
||||
return $urlResult;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
<?php
|
||||
|
||||
namespace App\Classes\Modules\Open1688\ControllersLogic;
|
||||
|
||||
use App\Classes\General\Abstracts\AbstractControllerLogic;
|
||||
use App\Classes\General\Open1688Helper;
|
||||
|
||||
use ErrorException;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class GetProductDetailLogic extends AbstractControllerLogic
|
||||
{
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
protected function notification():array {
|
||||
return [
|
||||
'title' => 'Get product detail',
|
||||
'message' => 'You have successfully get 1688 product detail'
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Request $request
|
||||
* @return JsonResponse
|
||||
* @throws ErrorException
|
||||
*/
|
||||
public function logic(Request $request) : JsonResponse
|
||||
{
|
||||
$result = Open1688Helper::productDetail($request->route('offer_id'));
|
||||
$productAttribute = [];
|
||||
$variants = [];
|
||||
$uniqueVariants = [];
|
||||
$variantInfo = [];
|
||||
$priceList = [];
|
||||
$variantKeyCombinationOrder = [];
|
||||
|
||||
foreach ($result['productAttribute'] as $attr) {
|
||||
$productAttribute[$attr['attributeName']] = array_key_exists($attr['attributeName'], $productAttribute) ? $productAttribute[$attr['attributeName']] . ", " . $attr['value'] : $attr['value'];
|
||||
}
|
||||
|
||||
$result['productAttribute'] = $productAttribute;
|
||||
|
||||
foreach ($result['productSkuInfos'] as $sku) {
|
||||
$uniqueVariantKey = '';
|
||||
foreach ($sku['skuAttributes'] as $skuAttr) {
|
||||
if (count($variantKeyCombinationOrder) < count($sku['skuAttributes'])) {
|
||||
array_push($variantKeyCombinationOrder, $skuAttr['attributeName']);
|
||||
}
|
||||
if (!in_array($skuAttr['value'], $uniqueVariants)) {
|
||||
$variants[$skuAttr['attributeName']][] = [
|
||||
'image' => isset($skuAttr['skuImageUrl']) ? $skuAttr['skuImageUrl'] : null,
|
||||
'value' => $skuAttr['value']
|
||||
];
|
||||
}
|
||||
|
||||
array_push($uniqueVariants, $skuAttr['value']);
|
||||
|
||||
$uniqueVariantKey .= $skuAttr['value'];
|
||||
}
|
||||
|
||||
$price = isset($sku['price']) ? $sku['price'] : $sku['consignPrice'];
|
||||
|
||||
$variantInfo[$uniqueVariantKey] = [
|
||||
'skuId' => $sku['skuId'],
|
||||
'amountOnSale' => $sku['amountOnSale'],
|
||||
'price' => $price,
|
||||
];
|
||||
array_push($priceList, $price);
|
||||
}
|
||||
|
||||
$result['variants'] = $variants;
|
||||
$result['variantInfo'] = $variantInfo;
|
||||
|
||||
$minPrice = min($priceList);
|
||||
$maxPrice = max($priceList);
|
||||
|
||||
$result['priceRange'] = $minPrice === $maxPrice ? $minPrice : "CNY ¥$minPrice - CNY ¥$maxPrice";
|
||||
$result['variantKeyCombinationOrder'] = $variantKeyCombinationOrder;
|
||||
|
||||
return $this->response(['data' => $result]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
namespace App\Classes\Modules\Open1688\ControllersLogic;
|
||||
|
||||
use App\Classes\General\Abstracts\AbstractControllerLogic;
|
||||
use App\Classes\General\Open1688Helper;
|
||||
use App\Http\Resources\WalletResource;
|
||||
|
||||
use ErrorException;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class KeywordSearchProductLogic extends AbstractControllerLogic
|
||||
{
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
protected function notification():array {
|
||||
return [
|
||||
'title' => 'Search 1688 Products',
|
||||
'message' => 'You have successfully retrieved a list of products from 1688'
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Request $request
|
||||
* @return JsonResponse
|
||||
* @throws ErrorException
|
||||
*/
|
||||
public function logic(Request $request) : JsonResponse
|
||||
{
|
||||
$result = Open1688Helper::productSearchKeyword($request->input('keyword'), $request->input('page'));
|
||||
|
||||
return $this->response($result);
|
||||
}
|
||||
}
|
||||
+20
-2
@@ -72,14 +72,32 @@ class CreatePurchaseOrderTransactionLogic extends AbstractControllerLogic
|
||||
|
||||
$billNumber = $this->generatesTransactionBillNumber->execute('PO-');
|
||||
|
||||
$total = collect($request->input('products'))->sum(function($product){
|
||||
$products = $request->input('products');
|
||||
|
||||
if ($request->input('pushToExisting')) {
|
||||
$transaction = $booking->transactions()->where('type', TransactionType::PURCHASE_ORDER)->first();
|
||||
|
||||
if ($transaction) {
|
||||
foreach ($transaction->transactionDetails as $detail) {
|
||||
array_push($products, [
|
||||
'stockCode' => $detail->product_code,
|
||||
'description' => $detail->product_name,
|
||||
'quantity' => $detail->quantity,
|
||||
'unit_price' => $detail->price,
|
||||
'total' => $detail->amount
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$total = collect($products)->sum(function($product){
|
||||
return $product['quantity'] * floatval(str_replace(',', '', $product['unit_price']));
|
||||
});
|
||||
|
||||
$object = new TransactionObject($billNumber, TransactionType::PURCHASE_ORDER, $booking->company->id, 1,
|
||||
1, PaymentMethodType::CASH,
|
||||
$total, $total, $booking->fix_currency_id, $booking->fix_currency_id,
|
||||
1, 0, 0, null, ApprovalStatus::PENDING_SUBMISSION, $request->input('products'));
|
||||
1, 0, 0, null, ApprovalStatus::PENDING_SUBMISSION, $products);
|
||||
|
||||
|
||||
$transaction = $this->createPurchaseOrderTransactionProcessor->execute($booking, $object);
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Open1688;
|
||||
|
||||
use App\Classes\Modules\Open1688\ControllersLogic\GetProductDetailLogic;
|
||||
use App\Classes\Modules\Open1688\ControllersLogic\KeywordSearchProductLogic;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class Open1688Controller
|
||||
{
|
||||
/**
|
||||
* @param Request $request
|
||||
* @param KeywordSearchProductLogic $logic
|
||||
* @return JsonResponse
|
||||
*/
|
||||
public function keywordSearchProduct(Request $request, KeywordSearchProductLogic $logic): JsonResponse {
|
||||
return $logic->execute($request);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Request $request
|
||||
* @param GetProductDetailLogic $logic
|
||||
* @return JsonResponse
|
||||
*/
|
||||
public function getProductDetail(Request $request, GetProductDetailLogic $logic): JsonResponse {
|
||||
return $logic->execute($request);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -14,14 +14,23 @@ class TransactionDetailResource extends JsonResource
|
||||
*/
|
||||
public function toArray($request)
|
||||
{
|
||||
|
||||
$item_from_1688 = str_contains($this->product_code, 'offerId');
|
||||
$offer_id_1688 = null;
|
||||
if ($item_from_1688) {
|
||||
$offerId = explode(',', $this->product_code)[0];
|
||||
$offer_id_1688 = substr($offerId, strpos($offerId, ':') + 1);
|
||||
}
|
||||
|
||||
|
||||
return [
|
||||
'id' => $this->id,
|
||||
'stockCode' => $this->product_code,
|
||||
'description' => $this->product_name,
|
||||
'quantity' => $this->quantity,
|
||||
'unit_price' => (double) $this->price,
|
||||
'total' => (double) $this->amount
|
||||
'total' => (double) $this->amount,
|
||||
'item_from_1688' => $item_from_1688,
|
||||
'offer_id_1688' => $offer_id_1688
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
'app_key' => env('OPEN_1688_APP_KEY', ''),
|
||||
'app_secret' => env('OPEN_1688_APP_SECRET', ''),
|
||||
'access_token' => env('open_1688_ACCESS_TOKEN', ''),
|
||||
'refresh_token' => env('open_1688_REFRESH_TOKEN', ''),
|
||||
'url' => env('OPEN_1688_URL', ''),
|
||||
'product_search_keyword_api' => env('OPEN_1688_PRODUCT_SEARCH_KEYWORD', ''),
|
||||
'product_detail_api' => env('OPEN_1688_PRODUCT_DETAIL', ''),
|
||||
];
|
||||
@@ -0,0 +1,34 @@
|
||||
<template>
|
||||
<div class="row p-2 pointer h-100">
|
||||
<div class="col no-padding card btn btn-raised shadow">
|
||||
<img :src="itemDetails.imageUrl" class="w-100" alt="productImg"/>
|
||||
<div class="col p-2 d-flex flex-column">
|
||||
<div class="row align-top">
|
||||
<div class="col">
|
||||
<div class="text-left">{{ itemDetails.subject }}</div>
|
||||
<div class="muted p-t-10 text-left">复购率:{{ itemDetails.repurchaseRate }}</div>
|
||||
<div class="muted text-left">销量:{{ itemDetails.monthSold }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row" style="margin-top: auto;">
|
||||
<div class="col">
|
||||
<div class="fs-20 bold text-right" style="color: #ff6000;">¥{{ itemDetails.priceInfo.price }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import componentHandler from '../../../general/mixins/componentHandler';
|
||||
export default {
|
||||
props:{
|
||||
itemDetails: {
|
||||
type: Object,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
mixins: [componentHandler]
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,275 @@
|
||||
<template>
|
||||
<div class="row bg-white p-5 overflow-auto" style="height: 90vh;width: 1200px;">
|
||||
<div class="col">
|
||||
<loading-component style="height: 300px; top: 0;" key="1" color="success" v-if="isLoading"></loading-component>
|
||||
<div class="row justify-content-center" v-if="!isLoading && productInfo">
|
||||
<div class="col-4">
|
||||
<div class="row b-a b-grey">
|
||||
<div class="col" style="min-height: 330px;">
|
||||
<img v-if="!selectedContent.isVideo" :src="selectedContent.link" class="w-100" alt="productImg"/>
|
||||
<video v-else :src="selectedContent.link" style="width: 100%;height: 100%;object-fit: contain;" controls></video>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col no-padding card btn btn-raised shadow" v-if="productInfo.mainVideo">
|
||||
<video :src="productInfo.mainVideo" style="width: 100%;height: 100%;object-fit: contain;" @click="setSelectedContent(productInfo.mainVideo, true)"></video>
|
||||
</div>
|
||||
<div class="col no-padding card btn btn-raised shadow" v-for="image in productInfo.productImage.images" @click="setSelectedContent(image)">
|
||||
<img :src="image" class="w-100" alt="productImg"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-8">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<div class="fs-18">{{ productInfo.subject }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mx-1">
|
||||
<div class="col">
|
||||
<div class="fs-28 bold" style="color: #ff6000;">{{ variantInfo ? "CNY ¥" + variantInfo.price : productInfo.priceRange }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row m-b-20 mx-1" v-for="(selections, key) in productInfo.variants">
|
||||
<div class="col">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<div class="fs-18">
|
||||
{{ key }}:
|
||||
<span class="text-primary"> {{ selectedVariantAttr[key] }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div v-for="selection in selections">
|
||||
<div class="m-1">
|
||||
<button v-if="selection.image" class="col-auto no-padding card btn shadow" :disabled="shouldDisable(key, selection.value)" :class="[{'disabled': shouldDisable(key, selection.value)}, {'b-success': selectedVariantAttr[key] === selection.value}]" @click="onClickAttr(key, selection)">
|
||||
<img :src="selection.image" style="object-fit: cover;width: 65px;height: 65px;border-radius: 3px;" alt="selectionImg"/>
|
||||
</button>
|
||||
<button v-else class="col-auto no-padding card btn btn-raised btn shadow" :disabled="shouldDisable(key, selection.value)" :class="[{'disabled': shouldDisable(key, selection.value)}, {'b-success': selectedVariantAttr[key] === selection.value}]" style="height: 65px;" @click="onClickAttr(key, selection)">
|
||||
<div class="fs-14 text-center px-3" :class="[{'text-success': selectedVariantAttr[key] === selection.value}]" style="margin-top: auto;margin-bottom: auto;">{{ selection.value }}</div>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<div class="col-12 pl-md-1">
|
||||
<div class="form-group form-group-default b-rad-none no-padding no-border">
|
||||
<div class="row no-margin">
|
||||
<div id="minus-quantity" name="minus-quantity" class="col-auto bg-master-lightest pointer" @click="updateQuantity('minus')">
|
||||
<div class="row h-100 align-items-center">
|
||||
<div class="col">
|
||||
<i class="fa fa-minus"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-auto no-padding">
|
||||
<label class="p-t-5 p-l-5 text-center">Quantity</label>
|
||||
<input id="quantity" name="quantity" type="text" class="form-control m-b-5 b-rad-none no-border text-center fs-18" v-model.lazy="quantity" v-mask="'#########'"/>
|
||||
</div>
|
||||
<div id="add-quantity" name="add-quantity" class="col-auto bg-master-lightest pointer" @click="updateQuantity('add')">
|
||||
<div class="row h-100 align-items-center">
|
||||
<div class="col">
|
||||
<i class="fa fa-plus"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-auto d-flex align-items-center muted" v-if="productInfo.variantInfo[this.uniqueVariantKey]">
|
||||
<div class="fs-16">库存: {{ productInfo.variantInfo[this.uniqueVariantKey].amountOnSale }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<button class="btn btn-lg all-caps b-rad-none btn-success btn-block" @click="addToPurchaseOrder()">Add to purchase order</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 tabsContainer b-a b-grey mt-5">
|
||||
<div class="row">
|
||||
<div class="col bg-master-lighter card btn shadow active tabButton padding-15" tab-name="description">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<div class="fs-12 m-t-5 all-caps">商品详情</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col bg-master-lighter card btn shadow tabButton padding-15" tab-name="property">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<div class="fs-12 m-t-5 all-caps">规格参数</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col tabsContainer tabContent active" tab-name="description">
|
||||
<div class="row py-3">
|
||||
<div class="col-12 d-flex justify-content-center" v-if="productInfo.detailVideo">
|
||||
<video :src="productInfo.detailVideo" style="width: 85%;height: 100%;object-fit: contain;" controls></video>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-12 d-flex justify-content-center">
|
||||
<div v-html="productInfo.description"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col tabsContainer tabContent hide d-flex justify-content-center" tab-name="property">
|
||||
<table class="w-100 table table-bordered my-3">
|
||||
<tr v-for="(value, key) in productInfo.productAttribute">
|
||||
<td>
|
||||
{{ key }}
|
||||
</td>
|
||||
<td>
|
||||
{{ value }}
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import componentHandler from '../../../general/mixins/componentHandler';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
bookingId: {
|
||||
required: true,
|
||||
type: Number
|
||||
}
|
||||
},
|
||||
data(){
|
||||
return {
|
||||
section: 'bookingDetailSection',
|
||||
isLoading: true,
|
||||
isFetchingProduct: true,
|
||||
productInfo: null,
|
||||
priceSection: '',
|
||||
selectedVariantAttr: {},
|
||||
uniqueVariantKey: '',
|
||||
variantInfo: null,
|
||||
selectedContent: {},
|
||||
quantity: 0
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
'data': function() {
|
||||
this.fetchProduct();
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
updateQuantity(type){
|
||||
type === 'add' ? this.quantity++ : this.quantity > 0 ? this.quantity-- : null
|
||||
},
|
||||
setSelectedContent(link, isVideo = false) {
|
||||
this.selectedContent = {
|
||||
link,
|
||||
isVideo
|
||||
}
|
||||
},
|
||||
shouldDisable(key, value) {
|
||||
let combineKey = '';
|
||||
this.productInfo.variantKeyCombinationOrder.forEach((variantKey) => {
|
||||
if (variantKey !== key) {
|
||||
combineKey += this.selectedVariantAttr[variantKey];
|
||||
} else {
|
||||
combineKey += value;
|
||||
}
|
||||
});
|
||||
|
||||
if (combineKey.includes("undefined")) {
|
||||
return false;
|
||||
} else {
|
||||
return this.productInfo.variantInfo[combineKey] ? false : true;
|
||||
}
|
||||
|
||||
},
|
||||
onClickAttr(key, selection) {
|
||||
if (selection.image) {
|
||||
this.setSelectedContent(selection.image)
|
||||
}
|
||||
this.$set(this.selectedVariantAttr, key, selection.value);
|
||||
this.uniqueVariantKey = '';
|
||||
this.productInfo.variantKeyCombinationOrder.forEach((variantKey) => {
|
||||
this.uniqueVariantKey += this.selectedVariantAttr[variantKey];
|
||||
});
|
||||
|
||||
this.variantInfo = this.productInfo.variantInfo[this.uniqueVariantKey];
|
||||
},
|
||||
addToPurchaseOrder() {
|
||||
this.variantInfo = this.productInfo.variantInfo[this.uniqueVariantKey];
|
||||
if (this.variantInfo && this.quantity > 0) {
|
||||
let product = {
|
||||
stockCode: `offerId:${this.productInfo.offerId},skuId:${this.variantInfo.skuId}`,
|
||||
description: this.productInfo.subject,
|
||||
quantity: this.quantity,
|
||||
unit_price: parseFloat((this.variantInfo.price).toString().replaceAll(',', '')),
|
||||
total: this.quantity * parseFloat((this.variantInfo.price).toString().replace(',', ''))
|
||||
}
|
||||
this.parameters = {
|
||||
products: [product],
|
||||
pushToExisting: true,
|
||||
};
|
||||
|
||||
console.log(this.bookingId);
|
||||
console.log(this.parameters);
|
||||
console.log(this.isFetchingProduct);
|
||||
console.log(this.section)
|
||||
|
||||
this.isFetchingProduct = false;
|
||||
this.submit(route('api.transaction.po.create', this.bookingId), 'post', 'bookingDetailSection', true, true);
|
||||
}
|
||||
|
||||
},
|
||||
fetchProduct() {
|
||||
this.isFetchingProduct = true;
|
||||
this.parameters = null;
|
||||
this.isLoading = true;
|
||||
this.submit(route('api.open1688.getProductDetail', this.data.offerId), 'get', 'bookingDetailSection', false, true)
|
||||
},
|
||||
successHandler(response){
|
||||
console.log(this.isFetchingProduct)
|
||||
if (this.isFetchingProduct) {
|
||||
this.productInfo = response.payload.data;
|
||||
this.selectedVariantAttr = {};
|
||||
this.priceSection = response.payload.data.priceRange;
|
||||
this.setSelectedContent(response.payload.data.productImage.images[0])
|
||||
this.uniqueVariantKey = '';
|
||||
this.variantInfo = null;
|
||||
this.quantity = 0;
|
||||
this.isLoading = false;
|
||||
} else {
|
||||
this.closeModal();
|
||||
this.updateList();
|
||||
}
|
||||
|
||||
},
|
||||
errorHandler(error){
|
||||
console.log(this.isFetchingProduct)
|
||||
if (this.isFetchingProduct) {
|
||||
this.productInfo = null;
|
||||
this.selectedVariantAttr = {};
|
||||
this.priceSection = '';
|
||||
this.setSelectedContent({})
|
||||
this.uniqueVariantKey = '';
|
||||
this.variantInfo = null;
|
||||
this.quantity = 0;
|
||||
this.isLoading = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
mixins: [componentHandler]
|
||||
|
||||
}
|
||||
</script>
|
||||
@@ -22,7 +22,10 @@
|
||||
<div class="col">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<p class="no-margin bold fs-12">{{product.description}}</p>
|
||||
<a :href="`https://detail.1688.com/offer/${product.offer_id_1688}.html`" target="_blank" v-if="product.item_from_1688">
|
||||
<p class="no-margin bold fs-12">{{product.description}}</p>
|
||||
</a>
|
||||
<p class="no-margin bold fs-12" v-else>{{product.description}}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
|
||||
@@ -0,0 +1,145 @@
|
||||
<template>
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<div class="row p-b-5">
|
||||
<div class="col-auto">
|
||||
<div class="row d-flex align-items-center">
|
||||
<div class="col">
|
||||
<img src="/images/1688_logo.png" alt="" width="40">
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<div class="fs-14 bold all-caps">1688 Product Search</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row" @keyup.enter="searchProducts">
|
||||
<div class="col-8">
|
||||
<validation-wrapper-component :validator="$v.keyword">
|
||||
<label class="all-caps">Keyword</label>
|
||||
<input type="text" class="form-control" v-model.lazy="keyword">
|
||||
</validation-wrapper-component>
|
||||
</div>
|
||||
<div class="col-2 d-flex align-items-center justify-content-center mt-2 mt-md-0">
|
||||
<button type="button" class="btn btn-lg btn-primary fs-11 w-100 d-block mr-2 mr-md-0" @click="searchProducts()">Search</button>
|
||||
</div>
|
||||
<div class="col-2 d-flex align-items-center justify-content-center mt-2 mt-md-0">
|
||||
<button type="button" class="btn btn-lg btn-primary fs-11 w-100 d-block mr-2 mr-md-0" @click="showProductList = !showProductList">{{ showProductList ? "Hide" : "Show" }} Section</button>
|
||||
</div>
|
||||
</div>
|
||||
<div v-show="showProductList">
|
||||
<div class="row">
|
||||
<div class="col-12" style="min-height: 20px;" v-show="isLoading">
|
||||
<loading-component style="height: 470px; top: 0;" key="1" color="success"></loading-component>
|
||||
</div>
|
||||
<div class="col-12 m-t-10" v-show="products.length === 0 && !isLoading">
|
||||
<div class="row align-items-center justify-content-center hint-text">
|
||||
<div class="col-4 hint-text"><img src="/images/not-found-illustration.png" class="w-100 hint-text"/></div>
|
||||
</div>
|
||||
<div class="row text-center">
|
||||
<div class="col">
|
||||
<div class="row m-t-20">
|
||||
<div class="col">
|
||||
<p class="all-caps no-margin fs-11" style="letter-spacing: 2px;">Nothing To Show Here</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row m-t-5 align-items-center justify-content-center">
|
||||
<div class="col">
|
||||
<small class="fs-9 muted all-caps font-lato" style="letter-spacing: 2px">There is no results found, Try adjusting your filters to find what you are looking for.</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col" v-show="products.length > 0">
|
||||
<div class="row" style="padding-left: 15px;padding-right: 15px;">
|
||||
<div class="col-lg-2 col-md-3 col-sm-6 requestModal" data-type="alibabaProduct" @click="selectedProduct=product" v-for="product in products">
|
||||
<alibaba-product-overview-component :itemDetails="product"></alibaba-product-overview-component>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<modal-component class="animate__animated animate__fast animate__fadeIn" styleType="fill-in" type="alibabaProduct">
|
||||
<alibaba-product-detail-form-component :data="selectedProduct" :bookingId="bookingId" section="alibabaProductDetailSection"></alibaba-product-detail-form-component>
|
||||
</modal-component>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row" v-if="products.length > 0">
|
||||
<div class="col-12 p-t-20 d-flex align-items-center justify-content-center">
|
||||
<nav>
|
||||
<ul class="pagination">
|
||||
<li class="page-item pointer" @click="setPage(1)">
|
||||
<div class="page-link" aria-label="Previous">
|
||||
<span aria-hidden="true">«</span>
|
||||
<span class="sr-only">Previous</span>
|
||||
</div>
|
||||
</li>
|
||||
<li class="page-item pointer" @click="setPage(currentPage - 2)"><div class="page-link" v-if="currentPage === totalPage">{{ currentPage - 2 }}</div></li>
|
||||
<li class="page-item pointer" @click="setPage(currentPage - 1)"><div class="page-link" v-if="currentPage - 1 > 0">{{ currentPage - 1 }}</div></li>
|
||||
<li class="page-item pointer active" @click="setPage(currentPage)"><div class="page-link">{{ currentPage }}</div></li>
|
||||
<li class="page-item pointer" @click="setPage(currentPage + 1)"><div class="page-link" v-if="currentPage !== totalPage">{{ currentPage + 1 }}</div></li>
|
||||
<li class="page-item pointer" @click="setPage(currentPage + 2)"><div class="page-link" v-if="currentPage === 1">{{ currentPage + 2 }}</div></li>
|
||||
<li class="page-item pointer" @click="setPage(totalPage)">
|
||||
<div class="page-link" aria-label="Next">
|
||||
<span aria-hidden="true">»</span>
|
||||
<span class="sr-only">Next</span>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { required } from "vuelidate/lib/validators";
|
||||
|
||||
export default {
|
||||
props: {
|
||||
bookingId: {
|
||||
required: true,
|
||||
type: Number
|
||||
}
|
||||
},
|
||||
data(){
|
||||
return {
|
||||
keyword: '',
|
||||
isLoading: false,
|
||||
products: [],
|
||||
currentPage: 1,
|
||||
totalPage: 0,
|
||||
selectedProduct: null,
|
||||
showProductList: false,
|
||||
}
|
||||
},
|
||||
validations: {
|
||||
keyword: {
|
||||
required
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
setPage(newPage) {
|
||||
this.currentPage = newPage;
|
||||
this.searchProducts()
|
||||
},
|
||||
searchProducts(){
|
||||
this.showProductList = true;
|
||||
this.products = [];
|
||||
this.isLoading = true;
|
||||
this.submit(route('api.open1688.keywordSearchProduct') + '?page=' + this.currentPage + '&keyword=' + this.keyword, 'get', 'alibabaSiteSection', false, true);
|
||||
},
|
||||
successHandler(response){
|
||||
this.isLoading = false;
|
||||
this.totalPage = response.payload.totalPage;
|
||||
if (response.payload.totalRecords > 0) {
|
||||
this.products = response.payload.data;
|
||||
}
|
||||
},
|
||||
errorHandler(error){
|
||||
this.isLoading = false;
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
+6
-1
@@ -110,10 +110,15 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row" v-if='booking.service.id === 4'>
|
||||
<div class="col">
|
||||
<alibaba-site-section-component :bookingId="booking.id"></alibaba-site-section-component>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-12 col-sm-12 col-md">
|
||||
<div class="row no-margin relative b-b b-dashed b-grey">
|
||||
<div class="absolute" style="top: -5px;right: -5px;width: 0px;height: 0px;border-style: solid;border-width: 0px 80px 80px 0px;border-color: transparent rgb(249, 249, 249) transparent transparent;z-index: 2;"></div>
|
||||
<div class="absolute" style="top: 0;right: -5px;width: 0px;height: 0px;border-style: solid;border-width: 0px 80px 80px 0px;border-color: transparent rgb(249, 249, 249) transparent transparent;z-index: 2;"></div>
|
||||
<div class="absolute shadow-sm " style="top: 0px; right: 0px; width: 0px; height: 0px; border-style: solid; border-width: 70px 0px 0px 70px; border-color: transparent transparent transparent rgb(255, 255, 255); z-index: 1;"></div>
|
||||
<div class="col bg-white padding-25">
|
||||
<div class="row">
|
||||
|
||||
@@ -67,6 +67,8 @@ Route::group(['middleware' => 'api', 'prefix' => 'v1', 'as' => 'api.'], function
|
||||
|
||||
require __DIR__ . '/milestone.php';
|
||||
|
||||
require __DIR__ . '/open1688.php';
|
||||
|
||||
// require __DIR__ . '/rate.php';
|
||||
// require __DIR__ . '/receipt.php';
|
||||
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Support\Facades\Route;
|
||||
|
||||
Route::group(['prefix' => 'open1688', 'namespace' => 'Open1688', 'as' => 'open1688.'], function () {
|
||||
Route::get('/', 'Open1688Controller@keywordSearchProduct')->name('keywordSearchProduct');
|
||||
Route::get('/{offer_id}', 'Open1688Controller@getProductDetail')->name('getProductDetail');
|
||||
});
|
||||
Reference in New Issue
Block a user