1688 integration

This commit is contained in:
JiaSheng
2024-01-25 16:52:06 +08:00
parent aa57a79ab7
commit 94596baf13
15 changed files with 776 additions and 6 deletions
@@ -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;
}
}
?>
+79
View File
@@ -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);
}
}
@@ -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
];
}
}
+11
View File
@@ -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">&laquo;</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">&raquo;</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>
@@ -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">
+2
View File
@@ -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';
+8
View File
@@ -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');
});