首页模块添加 商品模块 和 图标链接模块

This commit is contained in:
pushuo 2023-02-01 18:46:55 +08:00 committed by Edward Yang
parent 2743057667
commit 59f47ec84c
20 changed files with 504 additions and 8 deletions

View File

@ -18,7 +18,7 @@ class DesignController extends Controller
public function index(Request $request): View
{
$data = [
'editors' => ['editor-slide_show', 'editor-image401', 'editor-tab_product', 'editor-image100', 'editor-brand'],
'editors' => ['editor-slide_show', 'editor-image401', 'editor-tab_product', 'editor-product', 'editor-image100', 'editor-brand', 'editor-icons'],
'design_settings' => system_setting('base.design_setting'),
];

View File

@ -0,0 +1,44 @@
<?php
/**
* Render.php
*
* @copyright 2022 beikeshop.com - All Rights Reserved
* @link https://beikeshop.com
* @author Edward Yang <yangjin@guangda.work>
* @created 2022-07-08 17:09:15
* @modified 2022-07-08 17:09:15
*/
namespace Beike\Admin\View\DesignBuilders;
use Illuminate\Contracts\View\View;
use Illuminate\View\Component;
class Icons extends Component
{
/**
* Create a new component instance.
*
* @return void
*/
public function __construct()
{
}
/**
* Get the view / contents that represent the component.
*
* @return View
*/
public function render(): View
{
$data['register'] = [
'code' => 'icons',
'sort' => 0,
'name' => trans('admin/design_builder.module_icons'),
'icon' => '&#xe60e;',
];
return view('admin::pages.design.module.icons', $data);
}
}

View File

@ -0,0 +1,44 @@
<?php
/**
* Render.php
*
* @copyright 2022 beikeshop.com - All Rights Reserved
* @link https://beikeshop.com
* @author Edward Yang <yangjin@guangda.work>
* @created 2022-07-08 17:09:15
* @modified 2022-07-08 17:09:15
*/
namespace Beike\Admin\View\DesignBuilders;
use Illuminate\View\Component;
use Illuminate\Contracts\View\View;
class Product extends Component
{
/**
* Create a new component instance.
*
* @return void
*/
public function __construct()
{
}
/**
* Get the view / contents that represent the component.
*
* @return View
*/
public function render(): View
{
$data['register'] = [
'code' => 'product',
'sort' => 0,
'name' => trans('admin/design_builder.module_product'),
'icon' => '&#xe606;',
];
return view('admin::pages.design.module.product', $data);
}
}

View File

@ -49,6 +49,10 @@ class DesignService
return self::handleBrand($content);
} elseif ($moduleCode == 'tab_product') {
return self::handleTabProducts($content);
} elseif ($moduleCode == 'product') {
return self::handleProducts($content);
} elseif ($moduleCode == 'icons') {
return self::handleIcons($content);
}
return $content;
@ -111,6 +115,35 @@ class DesignService
return $content;
}
/**
* 处理 icons 模块
*
* @param $content
* @return array
* @throws \Exception
*/
private static function handleIcons($content): array
{
$content['title'] = $content['title'][locale()] ?? '';
if (empty($content['images'])) {
return $content;
}
$images = [];
foreach ($content['images'] as $image) {
$images[] = [
'image' => image_origin($image['image'] ?? ''),
'text' => $image['text'][locale()] ?? '',
'link' => self::handleLink($image['link']['type'] ?? '', $image['link']['value'] ?? ''),
];
}
$content['images'] = $images;
return $content;
}
/**
* 处理选项卡商品列表模块
*
@ -137,6 +170,19 @@ class DesignService
return $content;
}
/**
* 处理商品模块
*
* @param $content
* @return array
*/
private static function handleProducts($content): array
{
$content['products'] = ProductRepo::getProductsByIds($content['products'])->jsonSerialize();
$content['title'] = $content['title'][locale()] ?? '';
return $content;
}
/**
* 处理图片以及链接
* @throws \Exception

View File

@ -0,0 +1,104 @@
<template id="module-editor-icons-template">
<div class="image-edit-wrapper">
<div class="module-editor-row">{{ __('admin/builder.text_set_up') }}</div>
<div class="module-edit-group">
<div class="module-edit-title">{{ __('admin/builder.text_module_title') }}</div>
<text-i18n v-model="module.title"></text-i18n>
</div>
<div class="module-edit-group" style="margin-bottom: 200px;">
<div class="module-edit-title">{{ __('admin/builder.text_add_pictures') }}</div>
<div class="pb-images-selector" v-for="(item, index) in module.images" :key="index">
<div class="selector-head" @click="itemShow(index)">
<div class="left">
<img :src="thumbnail(item.image, 40, 40)" class="img-responsive">
</div>
<div class="right">
<span @click="removeItem(index)" class="remove-item"><i class="el-icon-delete"></i></span>
<i :class="'el-icon-arrow-'+(item.show ? 'up' : 'down')"></i>
</div>
</div>
<div :class="'pb-images-list ' + (item.show ? 'active' : '')">
<div class="pb-images-top">
<pb-image-selector v-model="item.image" :is-language="false"></pb-image-selector>
<div class="tag">{{ __('admin/builder.text_suggested_size') }}: 200x200</div>
</div>
<text-i18n v-model="item.text" style="margin-bottom: 10px"></text-i18n>
<link-selector v-model="item.link"></link-selector>
</div>
</div>
<div class="add-items" style="margin-top: 20px">
<el-button icon="el-icon-circle-plus-outline" type="primary" size="small" style="width: 100%" @click="addItems" plain>{{ __('common.add') }}</el-button>
</div>
</div>
</div>
</template>
<script type="text/javascript">
Vue.component('module-editor-icons', {
template: '#module-editor-icons-template',
props: ['module'],
data: function () {
return {
//
}
},
watch: {
module: {
handler: function (val) {
this.$emit('on-changed', val);
},
deep: true
}
},
created: function () {
//
},
methods: {
itemShow(index) {
this.module.images.find((e, key) => {if (index != key) return e.show = false});
this.module.images[index].show = !this.module.images[index].show;
},
addItems() {
this.module.images.push({
image: '',
link: {
type: 'product',
value:''
},
text: languagesFill(''),
show: true
})
this.module.images.find((e, key) => {if (this.module.images.length - 1 != key) return e.show = false});
},
removeItem(index) {
this.module.images.splice(index, 1);
}
}
});
setTimeout(() => {
const make = {
style: {
background_color: ''
},
title: languagesFill('{{ __('admin/builder.text_module_title') }}'),
floor: languagesFill(''),
images: []
}
let register = @json($register);
register.make = make;
app.source.modules.push(register)
}, 100)
</script>

View File

@ -0,0 +1,138 @@
<template id="module-editor-product-template">
<div class="module-editor-product-template">
<div class="module-editor-row">{{ __('admin/builder.text_set_up') }}</div>
<div class="module-edit-group">
<div class="module-edit-title">{{ __('admin/builder.text_module_title') }}</div>
<text-i18n v-model="module.title"></text-i18n>
</div>
<div class="module-editor-row">{{ __('admin/builder.modules_content') }}</div>
<div class="module-edit-group">
<div class="module-edit-title">{{ __('admin/builder.modules_set_product') }}</div>
<div class="tab-info">
<div class="module-edit-group">
<div class="autocomplete-group-wrapper">
<el-autocomplete
class="inline-input"
v-model="keyword"
value-key="name"
size="small"
:fetch-suggestions="querySearch"
placeholder="{{ __('admin/builder.modules_keywords_search') }}"
:highlight-first-item="true"
@select="handleSelect"
></el-autocomplete>
<div class="item-group-wrapper" v-loading="loading">
<template v-if="productData.length">
<draggable
ghost-class="dragabble-ghost"
:list="productData"
@change="itemChange"
:options="{animation: 330}"
>
<div v-for="(item, index) in productData" :key="index" class="item">
<div>
<i class="el-icon-s-unfold"></i>
<span>${item.name}</span>
</div>
<i class="el-icon-delete right" @click="removeProduct(index)"></i>
</div>
</draggable>
</template>
<template v-else>{{ __('admin/builder.modules_please_products') }}</template>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script type="text/javascript">
Vue.component('module-editor-product', {
delimiters: ['${', '}'],
template: '#module-editor-product-template',
props: ['module'],
data: function () {
return {
keyword: '',
productData: [],
loading: null,
}
},
watch: {
module: {
handler: function (val) {
this.$emit('on-changed', val);
},
deep: true
},
},
created: function () {
this.tabsValueProductData();
},
computed: {
},
methods: {
tabTitleLanguage(titles) {
return titles['{{ locale() }}'];
},
tabsValueProductData() {
var that = this;
if (!this.module.products.length) return;
this.loading = true;
$http.get('products/names?product_ids='+this.module.products.join(','), {hload: true}).then((res) => {
this.loading = false;
that.productData = res.data;
})
},
querySearch(keyword, cb) {
$http.get('products/autocomplete?name=' + encodeURIComponent(keyword), null, {hload:true}).then((res) => {
cb(res.data);
})
},
handleSelect(item) {
if (!this.module.products.find(v => v == item.id)) {
this.module.products.push(item.id * 1);
this.productData.push(item);
}
this.keyword = ""
},
itemChange(evt) {
this.module.products = this.productData.map(e => e.id * 1);
},
removeProduct(index) {
this.productData.splice(index, 1)
this.module.products.splice(index, 1);
},
}
});
setTimeout(() => {
const make = {
style: {
background_color: ''
},
floor: languagesFill(''),
products: [],
title: languagesFill('{{ __('admin/builder.text_module_title') }}'),
}
let register = @json($register);
register.make = make;
app.source.modules.push(register)
}, 100)
</script>

View File

@ -31,27 +31,39 @@
.swiper-button-prev,.swiper-button-next {
width: 34px;
height: 37px;
color: #333;
color: #999;
@media (max-width: 768px) {
display: none;
}
&:after {
font-size: 22px;
&:hover {
color: $primary;
}
&:after {
font-size: 26px;
}
}
.swiper-button-prev {
left: -50px;
left: -40px;
}
.swiper-button-next {
right: -50px;
right: -40px;
}
.swiper-pagination {
.swiper-pagination-bullet-active {
background: $primary;
}
&.rectangle {
span {
border-radius: 0;
height: 3px;
}
}
}
}

View File

@ -120,7 +120,7 @@
font-weight: bold;
}
.price-lod {
.price-old {
color: #aaa;
margin-left: 4px;
text-decoration: line-through;

View File

@ -15,4 +15,6 @@ return [
'module_four_image_pro' => 'eine Zeile vier Bilder PRO',
'module_slideshow' => 'Folienmodul',
'module_tab_products' => 'Tab-Produkte',
'module_product' => 'Warenmodul',
'module_icons' => 'Symbolmodul',
];

View File

@ -15,4 +15,6 @@ return [
'module_four_image_pro' => 'For Image PRO Module',
'module_slideshow' => 'Slideshow Module',
'module_tab_products' => 'Tab Products Module',
'module_product' => 'Products Module',
'module_icons' => 'Icon module',
];

View File

@ -15,4 +15,6 @@ return [
'module_four_image_pro' => 'Cuatro imágenes en una línea PRO',
'module_slideshow' => 'módulo de presentación de diapositivas',
'module_tab_products' => 'elemento de pestaña',
'module_product' => 'módulo de productos básicos',
'module_icons' => 'módulo de iconos',
];

View File

@ -15,4 +15,6 @@ return [
'module_four_image_pro' => 'une ligne quatre images PRO',
'module_slideshow' => 'module diapositive',
'module_tab_products' => 'onglet produits',
'module_product' => 'module marchandise',
'module_icons' => 'module d\'icônes',
];

View File

@ -15,4 +15,6 @@ return [
'module_four_image_pro' => 'una riga quattro immagini PRO',
'module_slideshow' => 'modulo diapositiva',
'module_tab_products' => 'scheda prodotti',
'module_product' => 'modulo merci',
'module_icons' => 'modulo icona',
];

View File

@ -15,4 +15,6 @@ return [
'module_four_image_pro' => '1 行 4 つの画像 PRO',
'module_slideshow' => 'スライド モジュール',
'module_tab_products' => 'タブ製品',
'module_product' => '商品モジュール',
'module_icons' => 'アイコンモジュール',
];

View File

@ -15,4 +15,6 @@ return [
'module_four_image_pro' => 'Четыре картинки в одну строку PRO',
'module_slideshow' => 'модуль слайд-шоу',
'module_tab_products' => 'элемент вкладки',
'module_product' => 'товарный модуль',
'module_icons' => 'модуль значков',
];

View File

@ -15,4 +15,6 @@ return [
'module_four_image_pro' => '一行四图 PRO',
'module_slideshow' => '幻灯片模块',
'module_tab_products' => '选项卡商品',
'module_product' => '商品模块',
'module_icons' => '图标模块',
];

View File

@ -15,4 +15,6 @@ return [
'module_four_image_pro' => '一行四圖 PRO',
'module_slideshow' => '幻燈片模塊',
'module_tab_products' => '選項卡商品',
'module_icons' => '圖標模塊',
'module_product' => '商品模塊',
];

View File

@ -0,0 +1,21 @@
<section class="module-item {{ $design ? 'module-item-design' : ''}}" id="module-{{ $module_id }}">
@include('design._partial._module_tool')
<div class="module-info mb-3 mb-md-5">
<div class="module-title">{{ $content['title'] }}</div>
<div class="container">
<div class="row">
@foreach ($content['images'] as $image)
<div class="col-6 col-md-4 col-lg-2">
<a href="{{ $image['link'] }}" class="text-decoration-none">
<div class="image-item">
<img src="{{ $image['image'] }}" class="img-fluid">
</div>
<p class="text-center text-dark mb-4 mt-2">{{ $image['text'] }}</p>
</a>
</div>
@endforeach
</div>
</div>
</div>
</section>

View File

@ -0,0 +1,67 @@
<section class="module-item {{ $design ? 'module-item-design' : ''}}" id="module-{{ $module_id }}">
@include('design._partial._module_tool')
<div class="module-info module-product mb-3 mb-md-5 swiper-style-plus">
<div class="container position-relative">
<div class="module-title">{{ $content['title'] }}</div>
@if ($content['products'])
<div class="swiper module-product-{{ $module_id }} module-slideshow">
<div class="swiper-wrapper">
@foreach ($content['products'] as $product)
<div class="swiper-slide">
@include('shared.product')
</div>
@endforeach
</div>
</div>
<div class="swiper-pagination rectangle module-product-{{ $module_id }}-pagination"></div>
<div class="swiper-button-prev product-prev"></div>
<div class="swiper-button-next product-next"></div>
@elseif (!$content['products'] and $design)
<div class="row">
@for ($s = 0; $s < 4; $s++)
<div class="col-6 col-md-4 col-lg-3">
<div class="product-wrap">
<div class="image"><a href="javascript:void(0)"><img src="{{ asset('catalog/placeholder.png') }}" class="img-fluid"></a></div>
<div class="product-name">请配置商品</div>
<div class="product-price">
<span class="price-new">66.66</span>
<span class="price-lod">99.99</span>
</div>
</div>
</div>
@endfor
</div>
@endif
</div>
</div>
<script>
new Swiper ('.module-product-{{ $module_id }}', {
loop: 1,
watchSlidesProgress: true,
breakpoints:{
320: {
slidesPerView: 2,
spaceBetween: 10,
},
768: {
slidesPerView: 4,
spaceBetween: 30,
},
},
spaceBetween: 30,
// 如果需要分页器
pagination: {
el: '.module-product-{{ $module_id }}-pagination',
clickable: true,
},
// 如果需要前进后退按钮
navigation: {
nextEl: '.product-next',
prevEl: '.product-prev',
},
})
</script>
</section>

View File

@ -31,7 +31,9 @@
<div class="product-name">{{ $product['name_format'] }}</div>
<div class="product-price">
<span class="price-new">{{ $product['price_format'] }}</span>
<span class="price-lod">{{ $product['origin_price_format'] }}</span>
@if ($product['price'] != $product['origin_price'] && $product['origin_price'] > 0)
<span class="price-old">{{ $product['origin_price_format'] }}</span>
@endif
</div>
@if (request('style_list') == 'list')