添加文章分类相关功能

优化链接配置词条

文章分类状态默认修改

wip

wip
This commit is contained in:
Edward Yang 2023-02-10 19:46:19 +08:00
parent 1276d0a98b
commit 7e972a8723
68 changed files with 2295 additions and 104 deletions

View File

@ -0,0 +1,143 @@
<?php
/**
* PageCategoryController.php
*
* @copyright 2023 beikeshop.com - All Rights Reserved
* @link https://beikeshop.com
* @author Edward Yang <yangjin@guangda.work>
* @created 2023-02-09 10:21:27
* @modified 2023-02-09 10:21:27
*/
namespace Beike\Admin\Http\Controllers;
use Beike\Admin\Http\Requests\PageCategoryRequest;
use Beike\Admin\Http\Resources\PageCategoryResource;
use Beike\Models\PageCategory;
use Beike\Repositories\PageCategoryRepo;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
class PageCategoryController extends Controller
{
/**
* 显示文章分类列表
*
* @return mixed
*/
public function index()
{
$pageCategoryList = PageCategoryRepo::getList();
$data = [
'page_categories' => $pageCategoryList,
'page_categories_format' => PageCategoryResource::collection($pageCategoryList)->jsonSerialize(),
];
return view('admin::pages.page_categories.index', $data);
}
/**
* 创建文章分类
*
* @return mixed
*/
public function create(): mixed
{
return view('admin::pages.page_categories.form', ['page_category' => new PageCategory()]);
}
/**
* 保存新建
*
* @param PageCategoryRequest $request
* @return RedirectResponse
* @throws \Throwable
*/
public function store(PageCategoryRequest $request)
{
try {
$requestData = $request->all();
PageCategoryRepo::createOrUpdate($requestData);
return redirect(admin_route('page_categories.index'));
} catch (\Exception $e) {
return redirect(admin_route('page_categories.index'))->withErrors(['error' => $e->getMessage()]);
}
}
/**
* @param Request $request
* @param PageCategory $pageCategory
* @return mixed
*/
public function edit(Request $request, PageCategory $pageCategory)
{
$pageCategory->load(['descriptions', 'parent.description']);
$descriptions = $pageCategory->descriptions->keyBy('locale')->toArray();
$data = [
'page_category' => $pageCategory,
'descriptions' => $descriptions,
];
return view('admin::pages.page_categories.form', $data);
}
/**
* 保存更新
*
* @param PageCategoryRequest $request
* @param PageCategory $pageCategory
* @return RedirectResponse
* @throws \Throwable
*/
public function update(PageCategoryRequest $request, PageCategory $pageCategory)
{
try {
$requestData = $request->all();
$requestData['id'] = $pageCategory->id;
PageCategoryRepo::createOrUpdate($requestData);
return redirect()->to(admin_route('page_categories.index'));
} catch (\Exception $e) {
return redirect(admin_route('page_categories.index'))->withErrors(['error' => $e->getMessage()]);
}
}
/**
* 删除单页
*
* @param Request $request
* @param int $pageId
* @return array
*/
public function destroy(Request $request, int $pageId): array
{
PageCategoryRepo::deleteById($pageId);
return json_success(trans('common.deleted_success'));
}
/**
* 搜索页面标题自动完成
* @param Request $request
* @return array
*/
public function autocomplete(Request $request): array
{
$products = PageCategoryRepo::autocomplete($request->get('name') ?? '');
return json_success(trans('common.get_success'), $products);
}
/**
* 获取单页名称
* @param PageCategory $pageCategory
* @return array
*/
public function name(PageCategory $pageCategory): array
{
$name = $pageCategory->description->title ?? '';
return json_success(trans('common.get_success'), $name);
}
}

View File

@ -15,10 +15,11 @@ use Beike\Admin\Http\Requests\PageRequest;
use Beike\Admin\Repositories\PageRepo;
use Beike\Models\Page;
use Beike\Shop\Http\Resources\PageDetail;
use Beike\Shop\Http\Resources\ProductSimple;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
class PagesController
class PagesController extends Controller
{
/**
* 显示单页列表
@ -66,14 +67,17 @@ class PagesController
/**
* @param Request $request
* @param int $pageId
* @param Page $page
* @return mixed
*/
public function edit(Request $request, int $pageId)
public function edit(Request $request, Page $page)
{
$page->load(['products.description', 'category.description']);
$data = [
'page' => PageRepo::findByPageId($pageId),
'descriptions' => PageRepo::getDescriptionsByLocale($pageId),
'page' => $page,
'products' => ProductSimple::collection($page->products)->jsonSerialize(),
'descriptions' => PageRepo::getDescriptionsByLocale($page->id),
];
return view('admin::pages.pages.form', $data);

View File

@ -0,0 +1,49 @@
<?php
/**
* PageCategoryRequest.php
*
* @copyright 2023 beikeshop.com - All Rights Reserved
* @link https://beikeshop.com
* @author Edward Yang <yangjin@guangda.work>
* @created 2023-02-10 16:04:14
* @modified 2023-02-10 16:04:14
*/
namespace Beike\Admin\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class PageCategoryRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize(): bool
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules(): array
{
return [
'descriptions.*.locale' => 'required|string',
'descriptions.*.title' => 'required|string|min:3|max:32',
'descriptions.*.summary' => 'string',
];
}
public function attributes()
{
return [
'title' => trans('page_category.title'),
'summary' => trans('page_category.summary'),
];
}
}

View File

@ -33,7 +33,7 @@ class PageRequest extends FormRequest
public function rules(): array
{
$rules = [
'descriptions.*.title' => 'required|string|min:3|max:32',
'descriptions.*.title' => 'required|string|min:3|max:128',
'descriptions.*.content' => 'required|string',
'descriptions.*.locale' => 'required|string',
];

View File

@ -0,0 +1,36 @@
<?php
/**
* PageCategoryResource.php
*
* @copyright 2023 beikeshop.com - All Rights Reserved
* @link https://beikeshop.com
* @author Edward Yang <yangjin@guangda.work>
* @created 2023-02-10 09:20:33
* @modified 2023-02-10 09:20:33
*/
namespace Beike\Admin\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
class PageCategoryResource extends JsonResource
{
public function toArray($request): array
{
$description = $this->description;
return [
'id' => $this->id,
'active' => $this->active,
'title' => $description->title,
'title_format' => sub_string($description->title),
'summary' => $description->summary,
'summary_format' => sub_string($description->summary, 128),
'meta_title' => $description->meta_title,
'meta_description' => $description->meta_description,
'meta_keywords' => $description->meta_keywords,
'created_at' => time_format($this->created_at),
'updated_at' => time_format($this->updated_at),
];
}
}

View File

@ -70,14 +70,31 @@ class PageRepo
$page = new Page();
}
$page->fill([
'position' => $data['position'] ?? 0,
'active' => $data['active'] ?? true,
'page_category_id' => (int) ($data['page_category_id'] ?? 0),
'position' => $data['position'] ?? 0,
'active' => $data['active'] ?? true,
'author' => $data['author'] ?? true,
'views' => $data['views'] ?? 0,
]);
$page->saveOrFail();
$page->descriptions()->delete();
$page->descriptions()->createMany($data['descriptions']);
$page->load(['descriptions']);
$products = $data['products'] ?? [];
if ($products) {
$items = [];
foreach ($products as $item) {
$items[] = [
'product_id' => $item,
];
}
$page->pageProducts()->delete();
$page->pageProducts()->createMany($items);
}
$page->load(['descriptions', 'pageProducts']);
return $page;
}

View File

@ -154,16 +154,26 @@ Route::prefix($adminName)
Route::middleware('can:marketing_buy')->post('marketing/{code}/buy', [Controllers\MarketingController::class, 'buy'])->name('marketing.buy');
Route::middleware('can:marketing_download')->post('marketing/{code}/download', [Controllers\MarketingController::class, 'download'])->name('marketing.download');
// 单页
// 文章
Route::middleware('can:pages_index')->get('pages', [Controllers\PagesController::class, 'index'])->name('pages.index');
Route::middleware('can:pages_index')->get('pages/autocomplete', [Controllers\PagesController::class, 'autocomplete'])->name('pages.autocomplete');
Route::middleware('can:pages_create')->get('pages/create', [Controllers\PagesController::class, 'create'])->name('pages.create');
Route::middleware('can:pages_show')->get('pages/{code}/edit', [Controllers\PagesController::class, 'edit'])->name('pages.edit');
Route::middleware('can:pages_show')->get('pages/{page}/edit', [Controllers\PagesController::class, 'edit'])->name('pages.edit');
Route::middleware('can:pages_show')->get('pages/{page}/name', [Controllers\PagesController::class, 'name'])->name('pages.name');
Route::middleware('can:pages_create')->post('pages', [Controllers\PagesController::class, 'store'])->name('pages.store');
Route::middleware('can:pages_update')->put('pages/{page}', [Controllers\PagesController::class, 'update'])->name('pages.update');
Route::middleware('can:pages_delete')->delete('pages/{page}', [Controllers\PagesController::class, 'destroy'])->name('pages.destroy');
// 文章分类
Route::middleware('can:page_categories_index')->get('page_categories', [Controllers\PageCategoryController::class, 'index'])->name('page_categories.index');
Route::middleware('can:page_categories_index')->get('page_categories/autocomplete', [Controllers\PageCategoryController::class, 'autocomplete'])->name('page_categories.autocomplete');
Route::middleware('can:page_categories_create')->get('page_categories/create', [Controllers\PageCategoryController::class, 'create'])->name('page_categories.create');
Route::middleware('can:page_categories_show')->get('page_categories/{page_category}/edit', [Controllers\PageCategoryController::class, 'edit'])->name('page_categories.edit');
Route::middleware('can:page_categories_show')->get('page_categories/{page_category}/name', [Controllers\PageCategoryController::class, 'name'])->name('page_categories.name');
Route::middleware('can:page_categories_create')->post('page_categories', [Controllers\PageCategoryController::class, 'store'])->name('page_categories.store');
Route::middleware('can:page_categories_update')->put('page_categories/{page_category}', [Controllers\PageCategoryController::class, 'update'])->name('page_categories.update');
Route::middleware('can:page_categories_delete')->delete('page_categories/{page_category}', [Controllers\PageCategoryController::class, 'destroy'])->name('page_categories.destroy');
// 商品
Route::middleware('can:products_restore')->put('products/restore', [Controllers\ProductController::class, 'restore']);
Route::middleware('can:products_trashed')->get('products/trashed', [Controllers\ProductController::class, 'trashed'])->name('products.trashed');

View File

@ -152,7 +152,7 @@ class Sidebar extends Component
*/
private function getPageSubPrefix()
{
$prefix = ['pages.'];
$prefix = ['pages.', 'page_categories.'];
return hook_filter('admin.sidebar.page.prefix', $prefix);
}
@ -230,12 +230,13 @@ class Sidebar extends Component
}
/**
* 获取内容管理子页面路由
* 获取文章管理子页面路由
* @return mixed
*/
public function getPageSubRoutes()
{
$routes = [
['route' => 'page_categories.index', 'icon' => 'fa fa-tachometer-alt'],
['route' => 'pages.index', 'icon' => 'fa fa-tachometer-alt'],
];

View File

@ -9,6 +9,7 @@ use Beike\Repositories\BrandRepo;
use Beike\Repositories\CategoryRepo;
use Beike\Repositories\CurrencyRepo;
use Beike\Repositories\LanguageRepo;
use Beike\Repositories\PageCategoryRepo;
use Beike\Repositories\PageRepo;
use Beike\Repositories\ProductRepo;
use Beike\Services\CurrencyService;
@ -117,7 +118,7 @@ function shop_route($route, $params = []): string
*/
function type_route($type, $value): string
{
$types = ['category', 'product', 'brand', 'page', 'order', 'rma', 'static', 'custom'];
$types = ['category', 'product', 'brand', 'page', 'page_category', 'order', 'rma', 'static', 'custom'];
if (empty($type) || empty($value) || ! in_array($type, $types)) {
return '';
}
@ -133,6 +134,8 @@ function type_route($type, $value): string
return shop_route('brands.show', [$value]);
} elseif ($type == 'page') {
return shop_route('pages.show', ['page' => $value]);
} elseif ($type == 'page_category') {
return shop_route('page_categories.show', ['page_category' => $value]);
} elseif ($type == 'order') {
return shop_route('account.order.show', ['number' => $value]);
} elseif ($type == 'rma') {
@ -160,7 +163,7 @@ function type_route($type, $value): string
*/
function type_label($type, $value, array $texts = []): string
{
$types = ['category', 'product', 'brand', 'page', 'static', 'custom'];
$types = ['category', 'product', 'brand', 'page', 'page_category', 'static', 'custom'];
if (empty($type) || empty($value) || ! in_array($type, $types)) {
return '';
}
@ -179,7 +182,9 @@ function type_label($type, $value, array $texts = []): string
return BrandRepo::getName($value);
} elseif ($type == 'page') {
return PageRepo::getName($value);
} elseif ($type == 'static') {
} elseif ($type == 'page_category') {
return PageCategoryRepo::getName($value);
} elseif ($type == 'static') {
return trans('shop/' . $value);
} elseif ($type == 'custom') {
return $text;

View File

@ -11,13 +11,15 @@
namespace Beike\Models;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\HasOne;
class Page extends Base
{
protected $fillable = [
'position', 'active',
'page_category_id', 'position', 'views', 'author', 'active',
];
public function description(): HasOne
@ -29,4 +31,19 @@ class Page extends Base
{
return $this->hasMany(PageDescription::class);
}
public function category(): BelongsTo
{
return $this->belongsTo(PageCategory::class, 'page_category_id', 'id');
}
public function pageProducts(): HasMany
{
return $this->hasMany(PageProduct::class);
}
public function products(): BelongsToMany
{
return $this->belongsToMany(Product::class, PageProduct::class, 'page_id', 'product_id')->withTimestamps();
}
}

View File

@ -0,0 +1,51 @@
<?php
/**
* PageCategory.php
*
* @copyright 2023 beikeshop.com - All Rights Reserved
* @link https://beikeshop.com
* @author Edward Yang <yangjin@guangda.work>
* @created 2023-02-09 10:31:38
* @modified 2023-02-09 10:31:38
*/
namespace Beike\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\HasOne;
class PageCategory extends Model
{
protected $table = 'page_categories';
protected $fillable = [
'parent_id', 'position', 'active',
];
public function parent(): BelongsTo
{
return $this->belongsTo(self::class, 'parent_id');
}
public function children(): HasMany
{
return $this->hasMany(self::class, 'parent_id');
}
public function descriptions(): HasMany
{
return $this->hasMany(PageCategoryDescription::class);
}
public function description(): HasOne
{
return $this->hasOne(PageCategoryDescription::class)->where('locale', locale());
}
public function pages(): HasMany
{
return $this->hasMany(Page::class, 'page_category_id');
}
}

View File

@ -0,0 +1,23 @@
<?php
/**
* PageCategoryDescription.php
*
* @copyright 2023 beikeshop.com - All Rights Reserved
* @link https://beikeshop.com
* @author Edward Yang <yangjin@guangda.work>
* @created 2023-02-09 10:33:08
* @modified 2023-02-09 10:33:08
*/
namespace Beike\Models;
use Illuminate\Database\Eloquent\Model;
class PageCategoryDescription extends Model
{
protected $table = 'page_category_descriptions';
protected $fillable = [
'page_category_id', 'locale', 'title', 'summary', 'meta_title', 'meta_description', 'meta_keywords',
];
}

View File

@ -14,6 +14,6 @@ namespace Beike\Models;
class PageDescription extends Base
{
protected $fillable = [
'page_id', 'locale', 'title', 'content', 'meta_title', 'meta_description', 'meta_keywords',
'page_id', 'locale', 'title', 'content', 'summary', 'meta_title', 'meta_description', 'meta_keywords',
];
}

View File

@ -0,0 +1,27 @@
<?php
/**
* PageProduct.php
*
* @copyright 2023 beikeshop.com - All Rights Reserved
* @link https://beikeshop.com
* @author Edward Yang <yangjin@guangda.work>
* @created 2023-02-10 14:49:45
* @modified 2023-02-10 14:49:45
*/
namespace Beike\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class PageProduct extends Model
{
protected $table = 'page_products';
protected $fillable = ['page_id', 'product_id'];
public function product(): BelongsTo
{
return $this->belongsTo(Product::class);
}
}

View File

@ -0,0 +1,160 @@
<?php
/**
* PageCategoryRepo.php
*
* @copyright 2023 beikeshop.com - All Rights Reserved
* @link https://beikeshop.com
* @author Edward Yang <yangjin@guangda.work>
* @created 2023-02-09 10:26:26
* @modified 2023-02-09 10:26:26
*/
namespace Beike\Repositories;
use Beike\Models\PageCategory;
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Facades\DB;
class PageCategoryRepo
{
/**
* @param array $filters
* @return LengthAwarePaginator
*/
public static function getList(array $filters = []): LengthAwarePaginator
{
$builder = self::getBuilder($filters);
return $builder->paginate(perPage());
}
/**
* @param array $filters
* @return LengthAwarePaginator
*/
public static function getActiveList(array $filters = []): LengthAwarePaginator
{
$filters['is_active'] = 1;
$builder = self::getBuilder($filters);
return $builder->paginate(perPage());
}
/**
* @param array $filters
* @return Builder
*/
public static function getBuilder(array $filters = []): Builder
{
$builder = PageCategory::query()->with('description');
$name = $filters['name'] ?? '';
if ($name) {
$builder->whereRelation('description', 'title', 'like', "%{$name}%");
}
if (isset($filters['is_active'])) {
$builder->where('active', (bool) $filters['is_active']);
}
return $builder;
}
/**
* 创建或更新文章分类
*
* @param $data
* @return mixed
* @throws \Exception|\Throwable
*/
public static function createOrUpdate($data): mixed
{
try {
DB::beginTransaction();
$pageCategory = self::pushPageCategory($data);
DB::commit();
return $pageCategory;
} catch (\Exception $e) {
DB::rollBack();
throw $e;
}
}
/**
* @param $data
* @return mixed
* @throws \Throwable
*/
private static function pushPageCategory($data)
{
$id = $data['id'] ?? 0;
if (empty($id)) {
$pageCategory = new PageCategory($data);
} else {
$pageCategory = PageCategory::query()->find($id);
}
$pageCategory->fill([
'parent_id' => (int) ($data['parent_id'] ?? 0),
'position' => (int) ($data['position'] ?? 0),
'active' => (bool) ($data['active'] ?? false),
]);
$pageCategory->saveOrFail();
$descriptions = $data['descriptions'] ?? [];
if ($descriptions) {
$pageCategory->descriptions()->delete();
$pageCategory->descriptions()->createMany($data['descriptions']);
}
$pageCategory->load(['descriptions']);
return $pageCategory;
}
/**
* 删除文章分类
*/
public static function deleteById($pageCategoryId)
{
$pageCategory = PageCategory::query()->findOrFail($pageCategoryId);
$pageCategory->descriptions()->delete();
$pageCategory->delete();
}
/**
* 通过名字搜索文章分类
*
* @param $name
* @return array
*/
public static function autocomplete($name)
{
$pageCategories = self::getBuilder()
->whereHas('description', function ($query) use ($name) {
$query->where('title', 'like', "%{$name}%");
})->limit(10)->get();
$results = [];
foreach ($pageCategories as $item) {
$results[] = [
'id' => $item->id,
'name' => $item->description->title,
'status' => $item->active,
];
}
return $results;
}
/**
* @param $page
* @return string
*/
public static function getName($page)
{
return $page->description->title ?? '';
}
}

View File

@ -12,15 +12,53 @@
namespace Beike\Repositories;
use Beike\Models\Page;
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
use Illuminate\Database\Eloquent\Builder;
class PageRepo
{
private static $allPagesWithName;
public static function getBuilder(): Builder
/**
* @param array $filters
* @return Builder
*/
public static function getBuilder(array $filters = []): Builder
{
return Page::query()->with('description');
$builder = Page::query()->with('description');
if (isset($filters['is_active'])) {
$builder->where('active', (bool) $filters['is_active']);
}
return $builder;
}
/**
* 获取所有启用的文章列表
*
* @param array $filters
* @return LengthAwarePaginator
*/
public static function getActivePages(array $filters = []): LengthAwarePaginator
{
$filters['is_active'] = 1;
$filters['category_id'] = 0;
$builder = self::getBuilder($filters);
return $builder->paginate(perPage());
}
/**
* 获取启用的非单页
*
* @return LengthAwarePaginator
*/
public static function getCategoryPages(): LengthAwarePaginator
{
$filters['is_active'] = 1;
$builder = self::getBuilder($filters)->whereNot('page_category_id', 0);
return $builder->paginate(perPage());
}
/**

View File

@ -0,0 +1,51 @@
<?php
/**
* PageCategoryController.php
*
* @copyright 2023 beikeshop.com - All Rights Reserved
* @link https://beikeshop.com
* @author Edward Yang <yangjin@guangda.work>
* @created 2023-02-10 11:39:46
* @modified 2023-02-10 11:39:46
*/
namespace Beike\Shop\Http\Controllers;
use Beike\Libraries\Breadcrumb;
use Beike\Models\PageCategory;
use Beike\Repositories\PageCategoryRepo;
use Beike\Repositories\PageRepo;
use Beike\Shop\Http\Resources\PageCategoryDetail;
class PageCategoryController extends Controller
{
public function home()
{
$breadCrumb = Breadcrumb::getInstance()
->addLink(shop_route('page_categories.home'), trans('page_category.index'));
$data = [
'breadcrumb' => $breadCrumb,
'active_page_categories' => PageCategoryRepo::getActiveList(),
'active_pages' => PageRepo::getCategoryPages(),
];
return view('page_categories/home', $data);
}
public function show(PageCategory $pageCategory)
{
$breadCrumb = Breadcrumb::getInstance()
->addLink(shop_route('page_categories.home'), trans('page_category.index'));
$data = [
'category' => new PageCategoryDetail($pageCategory),
'active_page_categories' => PageCategoryRepo::getActiveList(),
'breadcrumb' => $breadCrumb,
'category_pages' => $pageCategory->pages()->paginate(perPage()),
];
return view('page_categories/show', $data);
}
}

View File

@ -12,19 +12,30 @@
namespace Beike\Shop\Http\Controllers;
use Beike\Models\Page;
use Beike\Repositories\PageCategoryRepo;
use Beike\Shop\Http\Resources\PageDetail;
use Beike\Shop\Http\Resources\ProductSimple;
class PageController extends Controller
{
public function show(Page $page)
{
$page->load('description');
$page->load(['description', 'category.description', 'products.description']);
$page->increment('views');
$data = [
'page' => (new PageDetail($page))->jsonSerialize(),
'page' => $page,
'active_page_categories' => PageCategoryRepo::getActiveList(),
'page_format' => (new PageDetail($page))->jsonSerialize(),
'products' => ProductSimple::collection($page->products)->jsonSerialize(),
];
$data = hook_filter('page.show.data', $data);
return view('pages/detail', $data);
if ($page->category) {
return view('pages/article', $data);
}
return view('pages/single', $data);
}
}

View File

@ -0,0 +1,36 @@
<?php
/**
* PageCategoryDetail.php
*
* @copyright 2023 beikeshop.com - All Rights Reserved
* @link https://beikeshop.com
* @author Edward Yang <yangjin@guangda.work>
* @created 2023-02-10 11:54:20
* @modified 2023-02-10 11:54:20
*/
namespace Beike\Shop\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
class PageCategoryDetail extends JsonResource
{
public function toArray($request): array
{
$description = $this->description;
return [
'id' => $this->id,
'active' => $this->active,
'title' => $description->title,
'title_format' => sub_string($description->title),
'summary' => $description->summary,
'summary_format' => sub_string($description->summary, 128),
'meta_title' => $description->meta_title,
'meta_description' => $description->meta_description,
'meta_keywords' => $description->meta_keywords,
'created_at' => time_format($this->created_at),
'updated_at' => time_format($this->updated_at),
];
}
}

View File

@ -26,16 +26,21 @@ class PageDetail extends JsonResource
$description = $this->description;
return [
'id' => $this->id,
'active' => $this->active,
'title' => $description->title,
'title_format' => sub_string($description->title),
'content' => $description->content,
'meta_title' => $description->meta_title,
'meta_description' => $description->meta_description,
'meta_keywords' => $description->meta_keywords,
'created_at' => time_format($this->created_at),
'updated_at' => time_format($this->updated_at),
'id' => $this->id,
'parent_id' => $this->parent_id,
'active' => $this->active,
'author' => $this->author,
'views' => $this->views,
'title' => $description->title,
'title_format' => sub_string($description->title),
'content' => $description->content,
'summary' => $description->summary,
'summary_format' => sub_string($description->summary, 100),
'meta_title' => $description->meta_title,
'meta_description' => $description->meta_description,
'meta_keywords' => $description->meta_keywords,
'created_at' => time_format($this->created_at),
'updated_at' => time_format($this->updated_at),
];
}
}

View File

@ -19,6 +19,7 @@ use Beike\Shop\Http\Controllers\CurrencyController;
use Beike\Shop\Http\Controllers\FileController;
use Beike\Shop\Http\Controllers\HomeController;
use Beike\Shop\Http\Controllers\LanguageController;
use Beike\Shop\Http\Controllers\PageCategoryController;
use Beike\Shop\Http\Controllers\PageController;
use Beike\Shop\Http\Controllers\ProductController;
use Beike\Shop\Http\Controllers\ZoneController;
@ -54,6 +55,8 @@ Route::prefix('/')
Route::post('login', [LoginController::class, 'store'])->name('login.store');
Route::get('logout', [LogoutController::class, 'index'])->name('logout');
Route::get('page_categories', [PageCategoryController::class, 'home'])->name('page_categories.home');
Route::get('page_categories/{page_category}', [PageCategoryController::class, 'show'])->name('page_categories.show');
Route::get('pages/{page}', [PageController::class, 'show'])->name('pages.show');
Route::get('products/search', [ProductController::class, 'search'])->name('products.search');

View File

@ -11,6 +11,8 @@
namespace Beike\Shop\View\Components;
use Beike\Models\Page;
use Beike\Models\PageCategory;
use Beike\Models\Product;
use Illuminate\Contracts\View\View;
use Illuminate\Support\Collection;
@ -42,6 +44,10 @@ class Breadcrumb extends Component
$breadcrumbs = array_merge($breadcrumbs, $this->handleOrderLinks($value));
} elseif ($type == 'rma') {
$breadcrumbs = array_merge($breadcrumbs, $this->handleRmaLinks($value));
} elseif ($type == 'page') {
$breadcrumbs = array_merge($breadcrumbs, $this->handlePageLinks($value));
} elseif ($type == 'page_category') {
$breadcrumbs = array_merge($breadcrumbs, $this->handlePageCategoryLinks($value));
} elseif (Str::startsWith($value, 'account')) {
$breadcrumbs = array_merge($breadcrumbs, $this->handleAccountLinks($value));
} else {
@ -173,6 +179,70 @@ class Breadcrumb extends Component
return $links;
}
/**
* 获取文章页面包屑
*
* @param $value
* @return array
* @throws \Exception
*/
private function handlePageLinks($value): array
{
$pageId = 0;
if (is_array($value)) {
$pageId = $value['id'] ?? 0;
} elseif (is_int($value)) {
$pageId = $value;
}
if (empty($pageId)) {
return [];
}
$links = [];
$page = Page::query()->find($pageId);
$category = $page->category;
if ($category) {
$categoryLink = handle_link(['type' => 'page_category', 'value' => $category]);
$links[] = ['title' => $categoryLink['text'], 'url' => $categoryLink['link']];
}
$productLink = handle_link(['type' => 'page', 'value' => $value]);
$links[] = ['title' => $productLink['text'], 'url' => $productLink['link']];
return $links;
}
/**
* 获取文章页面包屑
*
* @param $value
* @return array
* @throws \Exception
*/
private function handlePageCategoryLinks($value): array
{
$id = 0;
if (is_array($value)) {
$id = $value['id'] ?? 0;
} elseif (is_int($value)) {
$id = $value;
}
if (empty($id)) {
return [];
}
$links = [];
$category = PageCategory::query()->find($id);
if ($category) {
$categoryLink = handle_link(['type' => 'page_category', 'value' => $category]);
$links[] = ['title' => $categoryLink['text'], 'url' => $categoryLink['link']];
}
return $links;
}
/**
* 处理个人中心面包屑
*

View File

@ -0,0 +1,65 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('page_categories', function (Blueprint $table) {
$table->comment('文章分类');
$table->id()->comment('ID');
$table->integer('parent_id')->comment('父级分类')->comment('parent_id');
$table->integer('position')->comment('排序');
$table->boolean('active')->comment('是否启用');
$table->timestamps();
});
Schema::create('page_category_descriptions', function (Blueprint $table) {
$table->comment('文章分类描述');
$table->id()->comment('ID');
$table->integer('page_category_id')->comment('分类 ID')->index('page_category_id');
$table->string('locale')->comment('语言');
$table->string('title')->comment('标题');
$table->text('summary')->comment('分类简介');
$table->string('meta_title')->comment('meta 标题');
$table->string('meta_description')->comment('meta 描述');
$table->string('meta_keywords')->comment('meta 关键字');
$table->timestamps();
});
Schema::table('pages', function (Blueprint $table) {
$table->integer('page_category_id')->comment('文章分类ID')->after('id')->index('page_category_id');
$table->string('author')->comment('作者')->after('position');
$table->integer('views')->comment('查看数')->after('position');
});
Schema::table('page_descriptions', function (Blueprint $table) {
$table->string('summary')->comment('文章摘要')->after('title');
});
Schema::create('page_products', function (Blueprint $table) {
$table->comment('文章产品关联');
$table->id()->comment('ID');
$table->integer('page_id')->comment('文章 ID')->index('page_id');
$table->integer('product_id')->comment('产品 ID')->index('product_id');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
}
};

View File

@ -0,0 +1,73 @@
@charset "UTF-8";
/**
* @copyright 2022 beikeshop.com - All Rights Reserved.
* @link https://beikeshop.com
* @Author pu shuo <pushuo@guangda.work>
* @Date 2022-08-09 14:12:25
* @LastEditTime 2022-09-16 19:05:44
*/
body.page-pages-form {
.autocomplete-group-wrapper {
.inline-input {
width: 100%;
}
.item-group-wrapper {
padding: 10px;
min-height: 280px;
overflow: auto;
background-color: #f5f5f5;
// border: 1px solid #e3e3e3;
.item {
overflow: hidden;
position: relative;
padding: 5px 8px;
margin-bottom: 4px;
background: #fff;
border: 1px solid #eee;
cursor: move;
display: flex;
align-items: center; // flex-start | center
justify-content: space-between; // flex-end | center | space-between
&:hover {
border-color: #aaa;
}
div {
display: flex;
line-height: 1;
width: calc(100% - 16px);
align-items: center; // flex-start | center
i {
margin-right: 4px;
}
}
span {
font-size: 12px;
overflow: hidden;
padding: 2px 0;
text-overflow:ellipsis;
white-space: nowrap;
}
i {
// position: absolute;
color: #999;
font-weight: 400;
&.right {
cursor: pointer;
&:hover {
color: #222;
}
}
}
}
}
}
}

View File

@ -23,6 +23,7 @@ $primary: #fd560f;
@import 'element-ui';
@import 'login';
@import 'page-product';
@import 'page-pages';
@import 'marketing';
@import 'autocomplete';
@import 'page-category';

View File

@ -140,9 +140,9 @@
{type: 'product', label: '{{ __('admin/builder.modules_product') }}'},
{type: 'category', label: '{{ __('admin/builder.text_category') }}'},
{type: 'page', label: '{{ __('admin/builder.text_information') }}'},
{type: 'page_category', label: '{{ __('admin/builder.page_category') }}'},
{type: 'brand', label: '{{ __('admin/builder.text_manufacturer') }}'},
{type: 'static',label: '{{ __('admin/builder.text_static') }}'},
// {type: 'blog',label: '博客'},
{type: 'custom',label: '{{ __('admin/builder.text_custom') }}'}
],
static: [
@ -209,6 +209,9 @@
case 'page':
url = '{{ admin_route('pages.index') }}';
break;
case 'page_category':
url = '{{ admin_route('page_categories.index') }}';
break;
default:
null;
}
@ -297,9 +300,12 @@
case 'brand':
url = 'brands/autocomplete?name=';
break;
case 'page':
case 'page':
url = 'pages/autocomplete?name=';
break;
case 'page_category':
url = 'page_categories/autocomplete?name=';
break;
default:
null;
}
@ -350,9 +356,12 @@
case 'brand':
url = `brands/${this.link.value}/name`;
break;
case 'page':
case 'page':
url = `pages/${this.link.value}/name`;
break;
case 'page_category':
url = `page_categories/${this.link.value}/name`;
break;
default:
null;
}

View File

@ -0,0 +1,112 @@
@extends('admin::layouts.master')
@section('title', __('admin/page_categories.index'))
@section('page-title-right')
<x-admin::form.row title="">
<button type="button" class="mt-3 btn btn-primary submit-form">{{ __('common.save') }}</button>
</x-admin::form.row>
@endsection
@section('content')
@if ($errors->has('error'))
<x-admin-alert type="danger" msg="{{ $errors->first('error') }}" class="mt-4" />
@endif
<ul class="nav nav-tabs nav-bordered mb-3" role="tablist">
<li class="nav-item" role="presentation">
<button class="nav-link active" data-bs-toggle="tab" data-bs-target="#tab-content" type="button" >{{ __('admin/product.basic_information') }}</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" data-bs-toggle="tab" data-bs-target="#tab-set" type="button">{{ __('common.data') }}</button>
</li>
</ul>
<div id="customer-app-form" class="card">
<div class="card-body h-min-600">
<form novalidate class="needs-validation"
action="{{ $page_category->id ? admin_route('page_categories.update', [$page_category->id]) : admin_route('page_categories.store') }}"
method="POST">
@csrf
@method($page_category->id ? 'PUT' : 'POST')
<div class="tab-content">
<div class="tab-pane fade show active" id="tab-content">
<ul class="nav nav-tabs mb-3" role="tablist">
@foreach (locales() as $language)
<li class="nav-item" role="presentation">
<button class="nav-link {{ $loop->first ? 'active' : '' }}" data-bs-toggle="tab" data-bs-target="#tab-{{ $language['code'] }}" type="button" >{{ $language['name'] }}</button>
</li>
@endforeach
</ul>
<div class="tab-content">
@foreach (locales() as $language)
<div class="tab-pane fade {{ $loop->first ? 'show active' : '' }}" id="tab-{{ $language['code'] }}">
@php
$error_title = $errors->first("descriptions.{$language['code']}.title");
@endphp
<x-admin-form-input
error="{{ $error_title }}"
name="descriptions[{{ $language['code'] }}][title]"
title="{{ __('admin/page.info_title') }}"
:required="true"
value="{{ old('descriptions.' . $language['code'] . '.title', $descriptions[$language['code']]['title'] ?? '') }}"
/>
<x-admin::form.row title="{{ __('page_category.text_summary') }}">
<div class="input-group w-max-400">
<textarea rows="4" type="text" name="descriptions[{{ $language['code'] }}][summary]" class="form-control wp-400" placeholder="分类概述">{{ old('descriptions.' . $language['code'] . '.summary', $descriptions[$language['code']]['summary'] ?? '') }}</textarea>
</div>
</x-admin::form.row>
<input type="hidden" name="descriptions[{{ $language['code'] }}][locale]" value="{{ $language['code'] }}">
<x-admin-form-input name="descriptions[{{ $language['code'] }}][meta_title]" title="{{ __('admin/setting.meta_title') }}" value="{{ old('descriptions.' . $language['code'] . '.meta_title', $descriptions[$language['code']]['meta_title'] ?? '') }}" />
<x-admin-form-input name="descriptions[{{ $language['code'] }}][meta_description]" title="{{ __('admin/setting.meta_description') }}" value="{{ old('descriptions.' . $language['code'] . '.meta_description', $descriptions[$language['code']]['meta_description'] ?? '') }}" />
<x-admin-form-input name="descriptions[{{ $language['code'] }}][meta_keywords]" title="{{ __('admin/setting.meta_keywords') }}" value="{{ old('descriptions.' . $language['code'] . '.meta_keywords', $descriptions[$language['code']]['meta_keywords'] ?? '') }}" />
</div>
@endforeach
</div>
</div>
<div class="tab-pane fade" id="tab-set">
<x-admin::form.row title="{{ __('admin/category.upper_category') }}">
<div class="wp-400">
<input type="text" value="{{ $page_category->parent->description->title ?? '' }}" id="categories-autocomplete" class="form-control wp-400 " />
<input type="hidden" name="parent_id" value="{{ old('categories_id', $page_category->parent->id ?? '') }}" />
</div>
</x-admin::form.row>
<x-admin-form-input name="position" title="{{ __('common.sort_order') }}" value="{{ old('position', $page_category->position ?? 0) }}" />
<x-admin-form-switch name="active" title="{{ __('common.status') }}" value="{{ old('active', $page_category->active ?? 1) }}" />
</div>
</div>
</form>
</div>
</div>
@endsection
@push('footer')
<script>
$(document).ready(function($) {
$('.submit-form').click(function () {
$('.needs-validation').submit()
})
$('#categories-autocomplete').autocomplete({
'source': function(request, response) {
$http.get(`page_categories/autocomplete?name=${encodeURIComponent(request)}`, null, {
hload: true
}).then((res) => {
response($.map(res.data, function(item) {
return {
label: item['name'],
value: item['id']
}
}));
})
},
'select': function(item) {
$(this).val(item['label']);
$('input[name="parent_id"]').val(item['value']);
}
});
})
</script>
@endpush

View File

@ -0,0 +1,86 @@
@extends('admin::layouts.master')
@section('title', __('admin/page_categories.index'))
@section('content')
@if ($errors->has('error'))
<x-admin-alert type="danger" msg="{{ $errors->first('error') }}" class="mt-4" />
@endif
{{-- {{ dd($page_categories_format) }} --}}
<div class="card" id="app">
<div class="card-body h-min-600">
<div class="d-flex justify-content-between mb-4">
<a href="{{ admin_route('page_categories.create') }}" class="btn btn-primary">{{ __('common.add') }}</a>
</div>
<div class="table-push">
<table class="table">
<thead>
<tr>
<th>ID</th>
<th>{{ __('common.title') }}</th>
<th>{{ __('common.status') }}</th>
<th>{{ __('common.created_at') }}</th>
<th>{{ __('common.updated_at') }}</th>
@hook('admin.page.list.column')
<th class="text-end">{{ __('common.action') }}</th>
</tr>
</thead>
<tbody>
@if (count($page_categories_format))
@foreach ($page_categories_format as $item)
<tr>
<td>{{ $item['id'] }}</td>
<td>
<div title="{{ $item['title'] ?? '' }}"><a class="text-dark" href="{{ shop_route('pages.show', $item['id']) }}" target="_blank">{{ $item['title_format'] ?? '' }}</a></div>
</td>
<td class="{{ $item['active'] ? 'text-success' : 'text-secondary' }}">
{{ $item['active'] ? __('common.enable') : __('common.disable') }}
</td>
<td>{{ $item['created_at'] }}</td>
<td>{{ $item['updated_at'] }}</td>
@hook('admin.page.list.column_value')
<td class="text-end">
<a href="{{ admin_route('page_categories.edit',$item['id']) }}" class="btn btn-outline-secondary btn-sm" >{{ __('common.edit') }}</a>
<button class="btn btn-outline-danger btn-sm delete-btn" type='button' data-id="{{ $item['id'] }}">{{ __('common.delete') }}</button>
@hook('admin.page.list.action')
</td>
</tr>
@endforeach
@else
<tr><td colspan="5" class="border-0"><x-admin-no-data /></td></tr>
@endif
</tbody>
</table>
</div>
{{ $page_categories->links('admin::vendor/pagination/bootstrap-4') }}
</div>
</div>
@hook('admin.page.list.content.footer')
@endsection
@push('footer')
<script>
$('.delete-btn').click(function(event) {
const id = $(this).data('id');
const self = $(this);
layer.confirm('{{ __('common.confirm_delete') }}', {
title: "{{ __('common.text_hint') }}",
btn: ['{{ __('common.cancel') }}', '{{ __('common.confirm') }}'],
area: ['400px'],
btn2: () => {
$http.delete(`page_categories/${id}`).then((res) => {
layer.msg(res.message);
window.location.reload();
})
}
})
});
</script>
@endpush

View File

@ -2,59 +2,142 @@
@section('title', __('admin/page.index'))
@section('body-class', 'page-pages-form')
@push('header')
<script src="{{ asset('vendor/vue/Sortable.min.js') }}"></script>
<script src="{{ asset('vendor/vue/vuedraggable.js') }}"></script>
<script src="{{ asset('vendor/tinymce/5.9.1/tinymce.min.js') }}"></script>
@endpush
@section('page-title-right')
<x-admin::form.row title="">
<button type="button" class="mt-3 btn btn-primary submit-form">{{ __('common.save') }}</button>
</x-admin::form.row>
@endsection
@section('content')
<div id="plugins-app-form" class="card h-min-600">
<ul class="nav nav-tabs nav-bordered mb-3" role="tablist">
<li class="nav-item" role="presentation">
<button class="nav-link active" data-bs-toggle="tab" data-bs-target="#tab-content" type="button" >{{ __('admin/product.basic_information') }}</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" data-bs-toggle="tab" data-bs-target="#tab-set" type="button">{{ __('common.data') }}</button>
</li>
</ul>
<div id="app" class="card h-min-600">
<div class="card-body">
<form novalidate class="needs-validation" action="{{ $page->id ? admin_route('pages.update', [$page->id]) : admin_route('pages.store') }}" method="POST">
@csrf
@method($page->id ? 'PUT' : 'POST')
<ul class="nav nav-tabs nav-bordered mb-3" role="tablist">
@foreach (locales() as $language)
<li class="nav-item" role="presentation">
<button class="nav-link {{ $loop->first ? 'active' : '' }}" data-bs-toggle="tab" data-bs-target="#tab-{{ $language['code'] }}" type="button" >{{ $language['name'] }}</button>
</li>
@endforeach
</ul>
<div class="tab-content">
@foreach (locales() as $language)
<div class="tab-pane fade {{ $loop->first ? 'show active' : '' }}" id="tab-{{ $language['code'] }}">
@php
$error_title = $errors->first("descriptions.{$language['code']}.title");
@endphp
<x-admin-form-input
error="{{ $error_title }}"
name="descriptions[{{ $language['code'] }}][title]"
title="{{ __('admin/page.info_title') }}"
:required="true"
value="{{ old('descriptions.' . $language['code'] . '.title', $descriptions[$language['code']]['title'] ?? '') }}"
/>
<div class="tab-pane fade show active" id="tab-content">
@csrf
@method($page->id ? 'PUT' : 'POST')
<ul class="nav nav-tabs mb-3" role="tablist">
@foreach (locales() as $language)
<li class="nav-item" role="presentation">
<button class="nav-link {{ $loop->first ? 'active' : '' }}" data-bs-toggle="tab" data-bs-target="#tab-{{ $language['code'] }}" type="button" >{{ $language['name'] }}</button>
</li>
@endforeach
</ul>
<div class="tab-content">
@foreach (locales() as $language)
<div class="tab-pane fade {{ $loop->first ? 'show active' : '' }}" id="tab-{{ $language['code'] }}">
@php
$error_title = $errors->first("descriptions.{$language['code']}.title");
@endphp
<x-admin-form-input
error="{{ $error_title }}"
name="descriptions[{{ $language['code'] }}][title]"
title="{{ __('admin/page.info_title') }}"
:required="true"
value="{{ old('descriptions.' . $language['code'] . '.title', $descriptions[$language['code']]['title'] ?? '') }}"
/>
<x-admin::form.row title="{{ __('admin/page.info_content') }}">
<div class="w-max-1000">
<textarea name="descriptions[{{ $language['code'] }}][content]" data-tinymce-height="600" class="form-control tinymce">
{{ old('descriptions.' . $language['code'] . '.content', $descriptions[$language['code']]['content'] ?? '') }}
</textarea>
<x-admin::form.row title="{{ __('page_category.text_summary') }}">
<div class="input-group w-max-400">
<textarea rows="3" type="text" name="descriptions[{{ $language['code'] }}][summary]" class="form-control wp-400" placeholder="{{ __('page_category.text_summary') }}">{{ old('descriptions.' . $language['code'] . '.summary', $descriptions[$language['code']]['summary'] ?? '') }}</textarea>
</div>
</x-admin::form.row>
<x-admin::form.row title="{{ __('admin/page.info_content') }}">
<div class="w-max-1000">
<textarea name="descriptions[{{ $language['code'] }}][content]" data-tinymce-height="600" class="form-control tinymce">
{{ old('descriptions.' . $language['code'] . '.content', $descriptions[$language['code']]['content'] ?? '') }}
</textarea>
</div>
@if ($errors->has("descriptions.{$language['code']}.content"))
<span class="invalid-feedback d-block" role="alert">{{ $errors->first("descriptions.{$language['code']}.content") }}</span>
@endif
</x-admin::form.row>
<input type="hidden" name="descriptions[{{ $language['code'] }}][locale]" value="{{ $language['code'] }}">
<x-admin-form-input name="descriptions[{{ $language['code'] }}][meta_title]" title="{{ __('admin/setting.meta_title') }}" value="{{ old('descriptions.' . $language['code'] . '.meta_title', $descriptions[$language['code']]['meta_title'] ?? '') }}" />
<x-admin-form-input name="descriptions[{{ $language['code'] }}][meta_description]" title="{{ __('admin/setting.meta_description') }}" value="{{ old('descriptions.' . $language['code'] . '.meta_description', $descriptions[$language['code']]['meta_description'] ?? '') }}" />
<x-admin-form-input name="descriptions[{{ $language['code'] }}][meta_keywords]" title="{{ __('admin/setting.meta_keywords') }}" value="{{ old('descriptions.' . $language['code'] . '.meta_keywords', $descriptions[$language['code']]['meta_keywords'] ?? '') }}" />
</div>
@if ($errors->has("descriptions.{$language['code']}.content"))
<span class="invalid-feedback d-block" role="alert">{{ $errors->first("descriptions.{$language['code']}.content") }}</span>
@endif
</x-admin::form.row>
<input type="hidden" name="descriptions[{{ $language['code'] }}][locale]" value="{{ $language['code'] }}">
<x-admin-form-input name="descriptions[{{ $language['code'] }}][meta_title]" title="{{ __('admin/setting.meta_title') }}" value="{{ old('descriptions.' . $language['code'] . '.meta_title', $descriptions[$language['code']]['meta_title'] ?? '') }}" />
<x-admin-form-input name="descriptions[{{ $language['code'] }}][meta_description]" title="{{ __('admin/setting.meta_description') }}" value="{{ old('descriptions.' . $language['code'] . '.meta_description', $descriptions[$language['code']]['meta_description'] ?? '') }}" />
<x-admin-form-input name="descriptions[{{ $language['code'] }}][meta_keywords]" title="{{ __('admin/setting.meta_keywords') }}" value="{{ old('descriptions.' . $language['code'] . '.meta_keywords', $descriptions[$language['code']]['meta_keywords'] ?? '') }}" />
@endforeach
</div>
@endforeach
</div>
<div class="tab-pane fade" id="tab-set">
<x-admin-form-input name="author" title="{{ __('page_category.author') }}" value="{{ old('author', $page->author ?? '') }}" />
<x-admin::form.row title="{{ __('admin/page_categories.index') }}">
<div class="wp-400">
<el-autocomplete
v-model="page_category_name"
value-key="name"
size="small"
name="category_name"
class="w-100"
:fetch-suggestions="(keyword, cb) => {relationsQuerySearch(keyword, cb, 'page_categories')}"
placeholder="{{ __('common.input') }}"
@select="(e) => {handleSelect(e, 'page_categories')}"
></el-autocomplete>
<input type="hidden" name="page_category_id" :value="page_category_id" />
</div>
</x-admin::form.row>
<x-admin-form-input name="views" title="{{ __('page_category.views') }}" value="{{ old('views', $page->views ?? '') }}" />
<x-admin-form-switch name="active" title="{{ __('common.status') }}" value="{{ old('active', $page->active) }}" />
<x-admin::form.row title="">
<button type="submit" class="mt-3 btn btn-primary">{{ __('common.submit') }}</button>
</x-admin::form.row>
<x-admin::form.row title="{{ __('admin/product.product_relations') }}">
<div class="module-edit-group wp-600">
<div class="autocomplete-group-wrapper">
<el-autocomplete
class="inline-input"
v-model="relations.keyword"
value-key="name"
size="small"
:fetch-suggestions="(keyword, cb) => {relationsQuerySearch(keyword, cb, 'products')}"
placeholder="{{ __('admin/builder.modules_keywords_search') }}"
@select="(e) => {handleSelect(e, 'product_relations')}"
></el-autocomplete>
<div class="item-group-wrapper" v-loading="relations.loading">
<template v-if="relations.products.length">
<draggable
ghost-class="dragabble-ghost"
:list="relations.products"
:options="{animation: 330}"
>
<div v-for="(item, index) in relations.products" :key="index" class="item">
<div>
<i class="el-icon-s-unfold"></i>
<span>@{{ item.name }}</span>
</div>
<i class="el-icon-delete right" @click="relationsRemoveProduct(index)"></i>
<input type="text" :name="'products['+ index +']'" v-model="item.id" class="form-control d-none">
</div>
</draggable>
</template>
<template v-else>{{ __('admin/builder.modules_please_products') }}</template>
</div>
</div>
</div>
</x-admin::form.row>
<x-admin-form-switch name="active" title="{{ __('common.status') }}" value="{{ old('active', $page->active ?? 1) }}" />
</div>
</div>
</form>
</div>
@ -63,5 +146,62 @@
@hook('admin.page.form.footer')
@endsection
@push('footer')
<script>
$('.submit-form').click(function () {
$('.needs-validation').submit()
})
var app = new Vue({
el: '#app',
data: {
relations: {
keyword: '',
products: [],
loading: null,
},
page_category_name: '{{ old('category_name', $page->category->description->title ?? '') }}',
page_category_id: '{{ old('categories_id', $page->category->id ?? '') }}',
},
created() {
const products = @json($page['products'] ?? []);
if (products.length) {
this.relations.products = products.map(v => {
return {
id: v.id,
name: v.description.name,
}
})
}
},
methods: {
relationsQuerySearch(keyword, cb, url) {
$http.get(url + '/autocomplete?name=' + encodeURIComponent(keyword), null, {hload:true}).then((res) => {
cb(res.data);
})
},
handleSelect(item, key) {
if (key == 'product_relations') {
if (!this.relations.products.find(v => v == item.id)) {
this.relations.products.push(item);
}
this.relations.keyword = ""
} else {
this.page_category_name = item.name
this.page_category_id = item.id
}
},
relationsRemoveProduct(index) {
this.relations.products.splice(index, 1);
},
}
})
</script>
@endpush

View File

@ -16,6 +16,7 @@ $primary: #fd560f;
@import './header';
@import './footer';
@import './product-style';
@import './news';
@import './login';
@import './account/account';
@import './page-product';

View File

@ -0,0 +1,13 @@
@charset "UTF-8";
/**
* @copyright 2022 beikeshop.com - All Rights Reserved.
* @link https://beikeshop.com
* @Author pu shuo <pushuo@guangda.work>
* @Date 2022-08-02 19:19:52
* @LastEditTime 2022-09-16 20:47:45
*/
.page-categories-home, .page-pages {
background-color: #f6f6f6;
}

View File

@ -0,0 +1,50 @@
@charset "UTF-8";
/**
* @copyright 2022 beikeshop.com - All Rights Reserved.
* @link https://beikeshop.com
* @Author pu shuo <pushuo@guangda.work>
* @Date 2022-08-15 15:43:12
* @LastEditTime 2022-09-16 20:56:17
*/
body.page-list {
.brand-item {
display: flex;
align-items: center; // flex-start | center
justify-content: center; // flex-end | center | space-between
box-shadow: 0 6px 18px rgba(0, 0, 0, .07);
margin-bottom: 10px;
height: 133px;
width: 100%;
>img {
max-height: 100%;
}
}
li {
list-style: none;
a{
color: #242424;
text-decoration: none;
}
}
.curser {
cursor: pointer;
>li>a {
display: block;
color: #242424;
transition: all .5s;
margin: 0 auto;
&:hover {
text-decoration: none;
background-color: #eee;
}
}
}
}

View File

@ -12,6 +12,7 @@
return [
'heading_title' => 'Seitenbearbeitung',
'page_category' => 'Artikelklassifizierung',
'modules_instructions' => 'Verfügbare Module, zum Hinzufügen zur Seite klicken',
'text_floor_prompt' => 'Einstellungen (Etagenkonfiguration muss gespeichert und aktualisiert werden, um die Seite zu aktualisieren)',
'text_new_page' => 'Neue Seite hinzufügen',

View File

@ -10,13 +10,13 @@
*/
return [
'index' => 'Informationsseite',
'info_title' => 'Informationstitel',
'info_content' => 'Inhalt',
'index' => 'Artikelverwaltung',
'info_title' => 'Informationstitel',
'info_content' => 'Inhalt',
'pages_index' => 'Einzelseitenliste',
'pages_create' => 'Eine einzelne Seite erstellen',
'pages_show' => 'Einzelseitendetails',
'pages_update' => 'Einzelne Seite bearbeiten',
'pages_delete' => 'Eine einzelne Seite löschen',
'pages_index' => 'Artikelliste',
'pages_create' => 'Artikel erstellen',
'pages_show' => 'Artikeldetails',
'pages_update' => 'Artikelbearbeitung',
'pages_delete' => 'Artikel löschen',
];

View File

@ -0,0 +1,18 @@
<?php
/**
* page.php
*
* @copyright 2022 beikeshop.com - All Rights Reserved
* @link https://beikeshop.com
* @author Edward Yang <yangjin@guangda.work>
* @created 2022-08-02 14:22:41
* @modified 2022-08-02 14:22:41
*/
return [
'index' => 'Artikelklassifizierung',
'info_title' => 'Informationstitel',
'info_content' => 'Inhalt',
'pages_index' => 'Kategorieliste',
];

View File

@ -0,0 +1,17 @@
<?php
/**
* page.php
*
* @copyright 2022 beikeshop.com - All Rights Reserved
* @link https://beikeshop.com
* @author TL <mengwb@guangda.work>
* @created 2022-07-28 20:59:38
* @modified 2022-07-28 20:59:38
*/
return [
'index' => 'blog de noticias',
'autor' => 'autor',
'views' => 'número de vista',
'created_at' => 'hora de lanzamiento',
'text_summary' => 'Kategorieübersicht',
];

View File

@ -12,6 +12,7 @@
return [
'heading_title' => 'Page editing',
'page_category' => 'Article Category',
'modules_instructions' => 'Available modules, click Add to page',
'text_floor_prompt' => 'Settings',
'text_new_page' => 'Add new page',
@ -69,7 +70,7 @@ return [
'text_disable' => 'Disable',
'text_no_data' => 'Data does not exist or has been deleted',
'text_to_add' => 'Go to add',
'text_category' => 'Category',
'text_category' => 'Porudct Category',
'text_information' => 'Information',
'text_manufacturer' => 'Brand',
'text_static' => 'Fixed connection',

View File

@ -0,0 +1,18 @@
<?php
/**
* page.php
*
* @copyright 2022 beikeshop.com - All Rights Reserved
* @link https://beikeshop.com
* @author Edward Yang <yangjin@guangda.work>
* @created 2022-08-02 14:22:41
* @modified 2022-08-02 14:22:41
*/
return [
'index' => 'article classification',
'info_title' => 'information title',
'info_content' => 'content',
'pages_index' => 'category list',
];

View File

@ -0,0 +1,18 @@
<?php
/**
* page.php
*
* @copyright 2022 beikeshop.com - All Rights Reserved
* @link https://beikeshop.com
* @author TL <mengwb@guangda.work>
* @created 2022-07-28 20:59:38
* @modified 2022-07-28 20:59:38
*/
return [
'index' => 'News blog',
'author' => 'Author',
'views' => 'View number',
'created_at' => 'Release time',
'text_summary' => 'Summary',
];

View File

@ -12,6 +12,7 @@
return [
'heading_title' => 'edición de página',
'page_category' => 'clasificación de artículos',
'modules_instructions' => 'Módulos disponibles, haga clic para agregar a la página',
'text_floor_prompt' => 'Configuración (la configuración del piso debe guardarse y actualizarse para actualizar la página)',
'text_new_page' => 'Agregar una nueva página',

View File

@ -0,0 +1,332 @@
<?php
/**
* order.php
*
* @copyright 2022 beikeshop.com - All Rights Reserved
* @link https://beikeshop.com
* @author Edward Yang <yangjin@guangda.work>
* @created 2022-08-02 14:22:41
* @modified 2022-08-02 14:22:41
*/
return [
'heading_title' => 'imprimir factura',
'text_success' => 'Éxito: ¡El pedido ha sido modificado! ',
'text_list' => 'lista de pedidos',
'text_add' => 'añadir pedido',
'text_edit' => 'orden de edición',
'text_filter' => 'filtro',
'text_order_detail' => 'detalles del pedido',
'text_customer_detail' => 'detalles del cliente',
'text_cart' => 'carrito de compras',
'text_payment_detail' => 'información de pago',
'text_shipping_detail' => 'información de envío',
'text_total_detail' => 'total',
'text_option' => 'opción',
'text_store' => 'tienda',
'text_date_added' => 'Fecha de generación:',
'text_payment_method' => 'Método de pago:',
'text_shipping_method' => 'Método de envío:',
'text_customer' => 'nombre del cliente:',
'text_customer_group' => 'grupo de clientes:',
'text_email' => 'correo electrónico del cliente:',
'text_store_email' => 'Email de contacto:',
'text_telephone' => 'Teléfono del cliente:',
'text_fax' => 'Fax:',
'text_invoice' => 'factura de pedido',
'text_reward' => 'puntos de recompensa',
'text_afiliado' => 'miembros afiliados',
'text_order' => 'pedir (#%s)',
'text_payment_address' => 'dirección de pago',
'text_shipping_address' => 'dirección de envío:',
'text_comment' => 'mensaje del cliente',
'text_history' => 'Añadir registro de pedido',
'text_history_add' => 'Añadir historial de pedidos',
'text_account_custom_field' => 'campo personalizado de cuenta',
'text_payment_custom_field' => 'campo personalizado de dirección de pago',
'text_shipping_custom_field' => 'Campo personalizado de dirección de envío',
'text_browser' => 'navegador',
'text_ip' => 'Dirección IP',
'text_forwarded_ip' => 'IP reenviada',
'text_user_agent' => 'sistema operativo',
'text_accept_language' => 'idioma del sistema',
'text_order_id' => 'número de pedido:',
'text_website' => 'Sitio web:',
'text_invoice_no' => 'número de factura:',
'text_invoice_date' => 'Fecha de la factura:',
'text_sku' => 'SKU:',
'text_upc' => 'UPC:',
'text_ean' => 'EAN:',
'text_jan' => 'ENE:',
'text_isbn' => 'ISBN:',
'text_mpn' => 'MPN:',
'text_date_modified' => 'fecha de modificación:',
'text_firstname' => 'primer nombre:',
'text_lastname' => 'apellido:',
'text_company' => 'Nombre de la empresa:',
'text_address_1' => 'Dirección&nbsp;&nbsp;Dirección:',
'text_address_2' => 'Dirección 2:',
'text_postcode' => 'código postal:',
'text_city' => 'Distritos y condados:',
'text_zone' => 'Provincia:',
'text_zone_code' => 'Código de provincia:',
'text_country' => 'nación:',
'text_from' => 'lugar de envío',
'texto_a' => 'a',
'text_missing' => 'pedido perdido',
'text_default' => 'predeterminado',
'text_product' => 'añadir productos',
'text_voucher' => 'Agregar certificado de regalo',
'text_order' => 'detalles del pedido',
'text_shipping' => 'entrega',
'text_contact' => 'contacto',
'text_reward_added' => 'Éxito: ¡se agregaron puntos de recompensa! ',
'text_reward_removed' => 'Éxito: ¡Se han eliminado los puntos de recompensa! ',
'text_commission_added' => 'Éxito: ¡Se ha agregado la comisión! ',
'text_commission_removed' => 'Éxito: ¡Se eliminó la comisión! ',
'text_restock' => 'Éxito: ¡El producto ha sido almacenado! ',
'text_upload' => '¡Su archivo ha sido subido correctamente! ',
'text_picklist' => 'Factura',
'text_print' => 'imprimir',
'text_reward' => 'puntos de recompensa',
'text_contacter' => 'Nombre del cliente:',
'text_shipping_comment' => '¡Muchas gracias por comprar en este centro comercial, esperamos su visita nuevamente! ',
'text_invoice_comment' => '¡Muchas gracias por comprar en este centro comercial, esperamos su visita nuevamente! ',
'text_pickup_info' => 'información de recogida',
'text_pickup_name' => 'punto de auto-recogida:',
'text_pickup_telephone' => 'Número de contacto:',
'text_pickup_code' => 'Código de recogida:',
'text_pickup_address' => 'dirección de auto-recogida:',
'text_pickup_number' => 'Código de recogida:',
'text_pickup_open' => 'Horario comercial:',
'text_pickup_comment' => 'posdata:',
'text_edit_price' => 'modificar el precio',
'text_alert' => 'advertencia',
'text_product_detail' => 'artículo de pedido',
'text_edit_shipping_address' => 'editar dirección de entrega',
'text_edit_payment_address' => 'editar dirección de facturación',
'text_aftership_info' => 'información logística',
'text_edit_products' => 'Añadir artículos de pedido',
'text_confirm_delete' => '¿Está seguro de que desea eliminar este producto? ',
'text_none' => 'ninguno',
'text_bargain' => 'pedido de oferta',
'text_group_buying' => 'pedido de grupo',
'text_shipping_id_number' => 'Número de identificación',
'text_shipping_id_front' => 'Foto del frente del DNI',
'text_shipping_id_back' => 'Foto del reverso de la tarjeta de identificación',
'text_flash_sale' => 'pedido seckill',
'text_normal' => 'orden normal',
'text_group_buying_member_add' => 'Agregar miembros del grupo',
'text_add_total' => 'añadir tarifa de pedido',
'entry_total_select' => 'seleccionar elemento de gasto',
'entry_total_price' => 'Por favor ingrese el monto',
'button_total_add' => 'añadir tarifas',
'text_type_total' => 'tarifa de pedido',
'text_type_product' => 'Producto',
'text_type_shipping_address' => 'dirección de envío',
'text_type_payment_address' => 'dirección de facturación',
'text_action_update' => 'actualizar',
'text_action_create' => 'crear',
'text_action_delete' => 'eliminar',
'text_group_buying_master' => '<span class="color-red">Maestro de impresión</span>',
'tab_aftership' => 'información logística',
'tab_operation' => 'registro de operación',
'text_promotion' => 'promoción',
'text_suite_count' => 'uno/conjunto',
'text_gift' => 'regalo',
'text_status_current' => 'estado actual',
'text_status_finished' => 'El proceso de pedido ha finalizado, el estado no se puede cambiar',
'text_confirm' => '¿Está seguro de modificar el estado del pedido? ',
'text_show_return' => 'Ver estado del reembolso',
'button_refund' => 'reembolso',
'text_delete_confirm' => 'OK para borrar',
'button_cancel' => 'Cancelar',
'text_explode_selected' => 'exportar seleccionado',
'text_warning' => 'advertencia',
'text_invoice_info' => 'información de la factura',
'text_invoice_manage' => 'administración de archivos adjuntos',
'text_invoice_attachment' => 'archivo adjunto',
'text_invoice_type' => 'Tipo de factura:',
'text_invoice_personal' => 'nombre personal:',
'text_invoice_content_type' => 'Contenido de la factura:',
'text_invoice_company_name' => 'nombre de la unidad:',
'text_invoice_vat' => 'número de identificación fiscal:',
'text_invoice_company_address' => 'dirección de la unidad:',
'text_invoice_company_telephone' => 'teléfono de la unidad:',
'text_invoice_bank' => 'cuenta bancaria:',
'text_invoice_account' => 'cuenta número de cuenta:',
'text_invoice_receiver_phone' => 'número de teléfono del receptor:',
'text_invoice_receiver_email' => 'correo electrónico del destinatario:',
'text_invoice_delete_confirm' => '¿Está seguro de que desea eliminar el adjunto de la factura?',
'text_warning' => 'advertencia',
// Colum,
'column_product_id' => 'ID del producto',
'column_order_id' => 'ID',
'column_order_number' => 'número de pedido',
'column_customer' => 'nombre del cliente',
'column_status' => 'estado',
'column_date_added' => 'fecha de generación',
'column_date_modified' => 'fecha de modificación',
'column_total' => 'total',
'column_image' => 'imagen',
'column_product' => 'nombre del producto',
'column_quantity' => 'cantidad',
'column_price' => 'precio',
'column_cost_price' => 'precio de coste',
'column_comment' => 'Observaciones',
'column_notify' => 'notificar a los clientes',
'column_ubicación' => 'ubicación',
'column_reference' => 'referencia',
'column_action' => 'administración',
'column_weight' => 'peso de la mercancía',
'column_product_total' => 'Total de mercancía:',
'column_shipping_fee' => 'Impuesto:',
'column_index' => 'número de serie',
'column_order_total' => 'cantidad total:',
'column_order_total_1' => 'Cantidad total: %s (capital)',
'column_shipping_address' => 'dirección de envío',
'column_telephone' => 'Número de teléfono del destinatario',
'column_products' => 'comprar bienes',
'column_shipping_name' => 'Nombre del destinatario',
'column_shipping_country' => 'país',
'column_shipping_zone' => 'Provincia',
'column_shipping_city' => 'ciudad',
'column_shipping_county' => 'condado',
'column_shipping_address_1' => 'Dirección 1',
'column_shipping_address_2' => 'Dirección 2',
'column_shipping_postcode' => 'código postal',
'column_payment' => 'método de pago',
'nombre_columna' => 'nombre',
'columna_avatar' => 'Avatar',
'column_username' => 'operador',
'tipo_columna' => 'tipo',
'column_operation_action' => 'modo de operación',
'column_before_data' => 'datos antes de la operación',
'column_after_data' => 'datos después de la operación',
// Entr,
'entry_store' => 'tienda',
'entry_customer' => 'cliente',
'entry_customer_group' => 'grupo de clientes',
'entry_firstname' => 'nombre',
'entry_lastname' => 'apellido',
'entry_email' => 'Correo electrónico',
'entry_telephone' => 'teléfono',
'entry_address' => 'seleccionar dirección',
'entry_company' => 'nombre de la empresa',
'entry_address_1' => 'Dirección detallada',
'entry_address_2' => 'Línea de dirección 2',
'entry_city' => 'ciudad',
'entry_postcode' => 'código postal',
'entry_country' => 'país',
'entry_zone' => 'Provincia',
'entry_zone_code' => 'código de provincia',
'entry_product' => 'seleccionar producto',
'entry_option' => 'Opción de producto',
'entry_quantity' => 'cantidad',
'entry_to_name' => 'nombre del destinatario',
'entry_to_email' => 'Correo electrónico del destinatario',
'entry_from_name' => 'nombre del emisor',
'entry_from_email' => 'Correo electrónico del emisor',
'entry_theme' => 'Tema de cupón de regalo',
'entry_message' => 'Información del certificado de regalo',
'entry_amount' => 'importe del cupón de regalo',
'entry_afiliado' => 'Miembro Afiliado',
'entry_order_status' => 'estado',
'entry_notify' => 'notificar a los clientes',
'entry_override' => 'reescribir',
'entry_comment' => 'Observaciones',
'entry_currency' => 'moneda',
'entry_shipping_method' => 'método de envío',
'entry_payment_method' => 'método de pago',
'entry_coupon' => 'cupón de descuento',
'entry_voucher' => 'vale regalo',
'entry_reward' => 'Puntos',
'entry_order_id' => 'ID de pedido',
'entry_order_number' => 'número de pedido',
'entry_total' => 'total',
'entry_total_format' => '(formato: 10 o 10-20)',
'entry_date_added' => 'Fecha agregada',
'entry_date_modified' => 'fecha de modificación',
'entry_date_added_start' => 'fecha de generación del pedido (inicio)',
'entry_date_added_end' => 'fecha de generación del pedido (fin)',
'entry_product_quantity' => 'Editar cantidad de producto',
'entry_product_price' => 'Editar precio de mercancía',
'entry_remove_product' => '¿Está seguro de que desea eliminar este producto?',
'entry_image' => 'imagen',
'entry_name' => 'nombre del producto',
'entry_model' => 'modelo',
'precio_entrada' => 'precio',
'entry_action' => 'administración',
'entry_location' => 'área',
'entry_aftership_code' => 'empresa de logística',
'entry_aftership_number' => 'Número de logística',
'entry_aftership_comment' => 'Observaciones',
'entry_platform' => 'plataforma',
'entry_payment' => 'método de pago',
'entry_type' => 'categoría de pedido',
'entry_group_buying_member_add' => 'añadir miembros',
'entry_avatar' => 'Avatar',
'tab_group_buying' => 'Únete al grupo',
'button_group_buying_member_add' => 'Agregar miembros del grupo',
'button_customer' => 'editar información del cliente',
'boton_ok' => 'Aceptar',
'button_aftership_add' => 'Agregar información logística',
'button_confirm' => 'Aceptar',
'button_cancel' => 'Cancelar',
// Hel,
'help_override' => 'La extensión antifraude puede hacer que se anule el estado del pedido, lo que provocará que no se cambie el estado del pedido. ',
// Erro,
'error_warning' => 'Error: ¡Compruebe si hay errores en el formulario! ',
'error_permission' => 'Error: ¡No tienes permiso para modificar el pedido! ',
'error_action' => 'Error: ¡No se pudo completar la operación! ',
'error_filetype' => '¡Tipo de archivo ilegal! ',
'error_customer_params' => 'error de datos del cliente',
'error_update_order_quantity' => 'La cantidad de bienes excede el inventario',
// Succes,
'success_customer' => '¡Información del cliente modificada con éxito! ',
'success_delete' => 'Éxito: Eliminado con éxito el artículo del pedido. ',
'success_edit_product' => 'Editar correctamente el producto del pedido',
'success_shipping_address' => 'Editar correctamente la dirección de envío',
'success_payment_address' => 'Editar correctamente la dirección de facturación',
'success_edit_order' => 'orden de edición exitosa',
'success_remove_total' => '%s eliminado con éxito',
'success_add_total' => 'tarifa de pedido agregada con éxito',
'error_customer' => '¡Seleccione un cliente! ',
'error_firstname' => '¡El nombre debe tener entre 1 y 32 caracteres! ',
'error_lastname' => '¡El apellido debe tener entre 1 y 32 caracteres! ',
'error_email' => '¡Dirección de correo electrónico no válida! ',
'error_telephone' => '¡El teléfono debe tener entre 1 y 32 caracteres! ',
'error_custom_field' => '%s es obligatorio! ',
'error_required' => '¡Se requiere %s! ',
'error_store' => '¡El nombre del producto es incorrecto o ha sido deshabilitado! ',
'error_integer' => 'Ingrese un número entero positivo. ',
'error_product_exists' => 'El producto no existe. ',
'error_out_stock' => 'agotado',
'error_edit_type' => 'Operación ilegal',
'error_total_type' => 'No existe tal elemento Total',
'error_nonnegative_number' => 'Por favor ingrese un número no negativo',
'error_address_1' => '¡La dirección detallada debe tener entre 3 y 128 caracteres! ',
'error_postcode' => '¡El código postal debe tener de 2 a 10 caracteres! ',
'error_zone' => '¡Seleccione una provincia! ',
'error_city_id' => '¡Seleccione una ciudad! ',
'error_county_id' => '¡Seleccione el condado! ',
'error_calling_code' => 'Seleccione un código de área de teléfono',
'error_aftership_number' => 'Por favor ingrese el número de orden de logística',
'error_order_exist' => 'No existe tal orden',
'error_product_exist' => 'No existe tal producto',
'error_total_exist' => 'No existe tal elemento de gasto',
'error_select_total' => 'Seleccione el artículo de costo',
'error_invoice_type' => 'La factura solo admite imágenes (png, jpg, jpeg) y pdf, vuelva a seleccionar cargar',
'error_invalid_invoice_attachment' => 'factura adjunta no válida',
'error_invalid_order_id' => 'error de número de pedido, compruébelo',
'error_invoice_no_need' => 'Este pedido no necesita emitir factura',
'error_invoice_file' => 'error en el archivo de la factura, verifíquelo',
'error_extension' => 'Habilite la extensión de información de archivo',
'error_invoice_mime_type' => 'error de tipo de archivo de factura, verifíquelo',
'error_invoice_upload' => 'La carga falló, por favor contacte al administrador',
'error_group_buy_completed_or_timeout' => '¡El grupo se ha llenado o ha caducado! ',
'error_cart_limit_100' => '¡El carrito de compras supera el límite de 100 líneas de productos! ',
];

View File

@ -0,0 +1,18 @@
<?php
/**
* page.php
*
* @copyright 2022 beikeshop.com - All Rights Reserved
* @link https://beikeshop.com
* @author Edward Yang <yangjin@guangda.work>
* @created 2022-08-02 14:22:41
* @modified 2022-08-02 14:22:41
*/
return [
'index' => 'clasificación de artículos',
'info_title' => 'título de la información',
'info_content' => 'contenido',
'pages_index' => 'lista de categorías',
];

View File

@ -0,0 +1,17 @@
<?php
/**
* page.php
*
* @copyright 2022 beikeshop.com - All Rights Reserved
* @link https://beikeshop.com
* @author TL <mengwb@guangda.work>
* @created 2022-07-28 20:59:38
* @modified 2022-07-28 20:59:38
*/
return [
'index' => 'Blog de noticias',
'autor' => 'Autor',
'views' => 'número de vista',
'created_at' => 'hora de lanzamiento',
'text_summary' => 'aperçu des catégories',
];

View File

@ -12,6 +12,7 @@
return [
'heading_title' => 'édition de pages',
'page_category' => 'classification des articles',
'modules_instructions' => 'Modules disponibles, cliquez pour ajouter à la page',
'text_floor_prompt' => 'Paramètres (la configuration de l\'étage doit être enregistrée et actualisée pour mettre à jour la page)',
'text_new_page' => 'Ajouter une nouvelle page',

View File

@ -0,0 +1,18 @@
<?php
/**
* page.php
*
* @copyright 2022 beikeshop.com - All Rights Reserved
* @link https://beikeshop.com
* @author Edward Yang <yangjin@guangda.work>
* @created 2022-08-02 14:22:41
* @modified 2022-08-02 14:22:41
*/
return [
'index' => 'classification des articles',
'info_title' => 'titre des informations',
'info_content' => 'contenu',
'pages_index' => 'liste des catégories',
];

View File

@ -0,0 +1,16 @@
<?php
/**
* page.php
*
* @copyright 2022 beikeshop.com - All Rights Reserved
* @link https://beikeshop.com
* @author TL <mengwb@guangda.work>
* @created 2022-07-28 20:59:38
* @modified 2022-07-28 20:59:38
*/
return [
'index' => 'Blog d\'actualités',
'author' => 'Auteur',
'views' => 'numéro de vue',
'created_at' => 'heure de sortie',
];

View File

@ -12,6 +12,7 @@
return [
'heading_title' => 'modifica della pagina',
'page_category' => 'classificazione articolo',
'modules_instructions' => 'Moduli disponibili, fare clic su Aggiungi alla pagina',
'text_floor_prompt' => 'Impostazioni (la configurazione del piano deve essere salvata e aggiornata per aggiornare la pagina)',
'text_new_page' => 'Aggiungi nuova pagina',

View File

@ -0,0 +1,18 @@
<?php
/**
* page.php
*
* @copyright 2022 beikeshop.com - All Rights Reserved
* @link https://beikeshop.com
* @author Edward Yang <yangjin@guangda.work>
* @created 2022-08-02 14:22:41
* @modified 2022-08-02 14:22:41
*/
return [
'index' => 'classificazione articolo',
'info_title' => 'titolo informazioni',
'info_content' => 'contenuto',
'pages_index' => 'elenco di categorie',
];

View File

@ -0,0 +1,18 @@
<?php
/**
* page.php
*
* @copyright 2022 beikeshop.com - All Rights Reserved
* @link https://beikeshop.com
* @author TL <mengwb@guangda.work>
* @created 2022-07-28 20:59:38
* @modified 2022-07-28 20:59:38
*/
return [
'index' => 'Blog di notizie',
'author' => 'Autore',
'views' => 'visualizza numero',
'created_at' => 'tempo di rilascio',
'text_summary' => 'panoramica categoria',
'text_summary' => 'aperçu des catégories',
];

View File

@ -12,6 +12,7 @@
return [
'heading_title' => 'ページ編集',
'page_category' => '記事分類',
'modules_instructions' => '利用可能なモジュール、クリックしてページに追加',
'text_floor_prompt' => '設定 (ページを更新するには、フロア構成を保存して更新する必要があります)',
'text_new_page' => '新しいページを追加',

View File

@ -0,0 +1,18 @@
<?php
/**
* page.php
*
* @copyright 2022 beikeshop.com - All Rights Reserved
* @link https://beikeshop.com
* @author Edward Yang <yangjin@guangda.work>
* @created 2022-08-02 14:22:41
* @modified 2022-08-02 14:22:41
*/
return [
'index' => '記事分類',
'info_title' => '情報タイトル',
'info_content' => 'コンテンツ',
'pages_index' => 'カテゴリーリスト',
];

View File

@ -0,0 +1,17 @@
<?php
/**
* page.php
*
* @copyright 2022 beikeshop.com - All Rights Reserved
* @link https://beikeshop.com
* @author TL <mengwb@guangda.work>
* @created 2022-07-28 20:59:38
* @modified 2022-07-28 20:59:38
*/
return [
'index' => 'ニュースブログ',
'author' => '作者',
'views' => 'ビュー番号',
'created_at' => 'リリース時間',
'text_summary' => 'カテゴリ概要',
];

View File

@ -12,6 +12,7 @@
return [
'heading_title' => 'редактирование страницы',
'page_category' => 'классификация статей',
'modules_instructions' => 'Доступные модули, нажмите Добавить на страницу',
'text_floor_prompt' => 'Настройки (конфигурация этажа должна быть сохранена и обновлена ​​для обновления страницы)',
'text_new_page' => 'Добавить новую страницу',

View File

@ -0,0 +1,18 @@
<?php
/**
* page.php
*
* @copyright 2022 beikeshop.com - All Rights Reserved
* @link https://beikeshop.com
* @author Edward Yang <yangjin@guangda.work>
* @created 2022-08-02 14:22:41
* @modified 2022-08-02 14:22:41
*/
return [
'index' => 'Artikelklassifizierung',
'info_title' => 'Informationstitel',
'info_content' => 'Inhalt',
'pages_index' => 'Kategorieliste',
];

View File

@ -0,0 +1,17 @@
<?php
/**
* page.php
*
* @copyright 2022 beikeshop.com - All Rights Reserved
* @link https://beikeshop.com
* @author TL <mengwb@guangda.work>
* @created 2022-07-28 20:59:38
* @modified 2022-07-28 20:59:38
*/
return [
'index' => 'Блог новостей',
'author' => 'Автор',
'views' => 'номер просмотра',
'created_at' => 'время выпуска',
'text_summary' => 'обзор категорий',
];

View File

@ -12,6 +12,7 @@
return [
'heading_title' => '页面编辑',
'page_category' => '文章分类',
'modules_instructions' => '可用模块,点击添加至页面',
'text_floor_prompt' => '设置(楼层配置需要保存刷新页面才会更新)',
'text_new_page' => '添加新页面',
@ -69,9 +70,9 @@ return [
'text_disable' => '禁用',
'text_no_data' => '数据不存在或已被删除',
'text_to_add' => '去添加',
'text_category' => '分类',
'text_information' => '信息页面',
'text_manufacturer' => '品牌',
'text_category' => '商品分类',
'text_information' => '特定页面',
'text_manufacturer' => '商品品牌',
'text_static' => '固定连接',
'text_custom' => '自定义',
'text_top_text' => '顶部文字',

View File

@ -60,7 +60,8 @@ return [
'currencies_index' => '货币管理',
'languages_index' => '语言管理',
'design_index' => '首页装修',
'pages_index' => '信息页面',
'pages_index' => '文章管理',
'page_categories_index' => '文章分类',
'design_footer_index' => '页尾装修',
'design_menu_index' => '导航配置',
'categories_index' => '商品分类',

View File

@ -10,13 +10,13 @@
*/
return [
'index' => '信息页面',
'index' => '文章管理',
'info_title' => '信息标题',
'info_content' => '内容',
'pages_index' => '单页列表',
'pages_create' => '创建单页',
'pages_show' => '单页详情',
'pages_update' => '单页编辑',
'pages_delete' => '删除单页',
'pages_index' => '文章列表',
'pages_create' => '创建文章',
'pages_show' => '文章详情',
'pages_update' => '文章编辑',
'pages_delete' => '删除文章',
];

View File

@ -0,0 +1,18 @@
<?php
/**
* page.php
*
* @copyright 2022 beikeshop.com - All Rights Reserved
* @link https://beikeshop.com
* @author Edward Yang <yangjin@guangda.work>
* @created 2022-08-02 14:22:41
* @modified 2022-08-02 14:22:41
*/
return [
'index' => '文章分类',
'info_title' => '信息标题',
'info_content' => '内容',
'pages_index' => '分类列表',
];

View File

@ -0,0 +1,17 @@
<?php
/**
* page.php
*
* @copyright 2022 beikeshop.com - All Rights Reserved
* @link https://beikeshop.com
* @author TL <mengwb@guangda.work>
* @created 2022-07-28 20:59:38
* @modified 2022-07-28 20:59:38
*/
return [
'index' => '新闻博客',
'author' => '作者',
'views' => '查看数',
'created_at' => '发布时间',
'text_summary' => '摘要',
];

View File

@ -12,6 +12,7 @@
return [
'heading_title' => '頁面編輯',
'page_category' => '文章分類',
'modules_instructions' => '可用模塊,點擊添加至頁面',
'text_floor_prompt' => '設置(樓層配置需要保存刷新頁面才會更新)',
'text_new_page' => '添加新頁面',

View File

@ -10,13 +10,13 @@
*/
return [
'index' => '信息頁面',
'index' => '文章管理',
'info_title' => '信息標題',
'info_content' => '內容',
'pages_index' => '單頁列表',
'pages_create' => '創建單頁',
'pages_show' => '單頁詳情',
'pages_update' => '單頁編輯',
'pages_delete' => '刪除單頁',
'pages_index' => '文章列表',
'pages_create' => '創建文章',
'pages_show' => '文章詳情',
'pages_update' => '文章編輯',
'pages_delete' => '刪除文章',
];

View File

@ -0,0 +1,18 @@
<?php
/**
* page.php
*
* @copyright 2022 beikeshop.com - All Rights Reserved
* @link https://beikeshop.com
* @author Edward Yang <yangjin@guangda.work>
* @created 2022-08-02 14:22:41
* @modified 2022-08-02 14:22:41
*/
return [
'index' => '文章分類',
'info_title' => '信息標題',
'info_content' => '內容',
'pages_index' => '分類列表',
];

View File

@ -0,0 +1,17 @@
<?php
/**
* page.php
*
* @copyright 2022 beikeshop.com - All Rights Reserved
* @link https://beikeshop.com
* @author TL <mengwb@guangda.work>
* @created 2022-07-28 20:59:38
* @modified 2022-07-28 20:59:38
*/
return [
'index' => '新聞博客',
'author' => '作者',
'views' => '查看數',
'created_at' => '發佈時間',
'text_summary' => '分類概述',
];

View File

@ -0,0 +1,50 @@
@extends('layout.master')
@section('body-class', 'page-categories-home')
@section('content')
<div class="container">
{{ $breadcrumb->render() }}
<div class="row">
<div class="col-lg-9 col-12">
<div class="card mb-4 shadow-sm p-3 h-min-600">
<div class="card-body">
@foreach ($active_pages as $page)
<div>
<h5 class="card-title mb-2"><a class="text-black" href="{{ shop_route('pages.show', [$page->id]) }}">{{ $page->description->title }}</a></h5>
<p class="fs-6 mb-3 text-secondary">{{ $page->created_at }}</p>
@if ($page->description->summary)
<p class="card-text mb-3">{{ $page->description->summary ?? '' }}</p>
@endif
<div class="text-danger"><a href="{{ shop_route('pages.show', [$page->id]) }}">{{ __('shop/account.check_details') }}<i class="bi bi-arrow-right-short"></i></a></div>
</div>
@if (!$loop->last)
<hr class="my-4">
@endif
@endforeach
</div>
</div>
</div>
@if ($active_page_categories)
<div class="col-lg-3 col-12">
<div class="card mb-3 shadow-sm">
<div class="card-header d-flex justify-content-between align-items-center">
<h5 class="card-title">{{ __('product.category') }}</h5>
</div>
<div class="card-body">
<ul class="list-group list-group-flush">
@foreach ($active_page_categories as $category)
<li class="list-group-item p-0">
<a href="{{ shop_route('page_categories.show', [$category->id]) }}"
class="p-2 list-group-item-action nav-link">{{ $category->description->title }}</a>
</li>
@endforeach
</ul>
</div>
</div>
</div>
@endif
</div>
</div>
@endsection

View File

@ -0,0 +1,70 @@
@extends('layout.master')
@section('body-class', 'page-categories-home')
@section('title', $category->description->meta_title ?: system_setting('base.meta_title', 'BeikeShop开源好用的跨境电商系统 -
BeikeShop官网') . ' - ' . $category->description->name)
@section('keywords', $category->description->meta_keywords ?: system_setting('base.meta_keyword'))
@section('description', $category->description->meta_description ?: system_setting('base.meta_description'))
@section('content')
<div class="container">
<x-shop-breadcrumb type="page_category" :value="$category['id']" />
<div class="row">
<div class="col-lg-9 col-12">
<div class="card mb-4 shadow-sm">
<div class="card-body">
<h3>{{ $category->description->title }}</h3>
<div>{{ $category->description->summary }}</div>
</div>
</div>
<div class="card mb-4 shadow-sm">
<div class="card-body">
@if ($category_pages->count() > 0)
@foreach ($category_pages as $page)
<div>
<h5 class="card-title mb-2"><a class="text-black"
href="{{ shop_route('pages.show', [$page->id]) }}">{{ $page->description->title }}</a></h5>
<p class="fs-6 mb-3 text-secondary">{{ $page->created_at }}</p>
@if ($page->description->summary)
<p class="card-text mb-4">{{ $page->description->summary ?? '' }}</p>
@endif
<div class="text-danger"><a
href="{{ shop_route('pages.show', [$page->id]) }}">{{ __('shop/account.check_details') }}<i
class="bi bi-arrow-right-short"></i></a></div>
</div>
@if (!$loop->last)
<hr class="my-4">
@endif
@endforeach
@else
<x-shop-no-data />
@endif
{{ $category_pages->links('shared/pagination/bootstrap-4') }}
</div>
</div>
</div>
@if ($active_page_categories)
<div class="col-lg-3 col-12">
<div class="card mb-3 shadow-sm">
<div class="card-header d-flex justify-content-between align-items-center">
<h5 class="card-title">{{ __('product.category') }}</h5>
</div>
<div class="card-body">
<ul class="list-group list-group-flush">
@foreach ($active_page_categories as $category)
<li class="list-group-item p-0">
<a href="{{ shop_route('page_categories.show', [$category->id]) }}"
class="p-2 list-group-item-action nav-link">{{ $category->description->title }}</a>
</li>
@endforeach
</ul>
</div>
</div>
</div>
@endif
</div>
</div>
@endsection

View File

@ -0,0 +1,104 @@
@extends('layout.master')
@section('body-class', 'page-pages')
@section('title', $page->description->meta_title ?: $page->description->title)
@section('keywords', $page->description->meta_keywords)
@section('description', $page->description->meta_description)
@push('header')
<script src="{{ asset('vendor/swiper/swiper-bundle.min.js') }}"></script>
<link rel="stylesheet" href="{{ asset('vendor/swiper/swiper-bundle.min.css') }}">
@endpush
@section('content')
<div class="container">
<x-shop-breadcrumb type="page" :value="$page['id']" />
<div class="row">
<div class="{{ $page->category ? "col-lg-9 col-12" : 'col-12' }}">
<div class="card shadow-sm">
<div class="card-body h-min-600 p-lg-4">
<h2 class="mb-3">{{ $page->description->title }}</h2>
<div class="text-secondary opacity-75 mb-4">
<span class="me-3"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-user"><path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"></path><circle cx="12" cy="7" r="4"></circle></svg> {{ __('page_category.author') }}: {{ $page->author }}</span>
<span class="me-3"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-clock"><circle cx="12" cy="12" r="10"></circle><polyline points="12 6 12 12 16 14"></polyline></svg> {{ __('page_category.created_at') }}: {{ $page->created_at }}</span>
<span><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-eye"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"></path><circle cx="12" cy="12" r="3"></circle></svg> {{ __('page_category.views') }}: {{ $page->views }}</span>
</div>
{!! $page_format['content'] !!}
@if ($products)
<div class="relations-wrap mt-5">
<div class="container position-relative">
<div class="title text-center fs-4 mb-4">{{ __('admin/product.product_relations') }}</div>
<div class="product swiper-style-plus">
<div class="swiper relations-swiper">
<div class="swiper-wrapper">
@foreach ($products as $item)
<div class="swiper-slide">
@include('shared.product', ['product' => $item])
</div>
@endforeach
</div>
</div>
<div class="swiper-pagination relations-pagination"></div>
<div class="swiper-button-prev relations-swiper-prev"></div>
<div class="swiper-button-next relations-swiper-next"></div>
</div>
</div>
</div>
@endif
</div>
</div>
</div>
@if ($page->category)
<div class="col-lg-3 col-12">
<div class="card mb-3 shadow-sm">
<div class="card-header d-flex justify-content-between align-items-center">
<h5 class="card-title">{{ __('product.category') }}</h5>
</div>
<div class="card-body">
<ul class="list-group list-group-flush">
@foreach ($active_page_categories as $category)
<li class="list-group-item p-0">
<a href="{{ shop_route('page_categories.show', [$category->id]) }}"
class="p-2 list-group-item-action nav-link">{{ $category->description->title }}</a>
</li>
@endforeach
</ul>
</div>
</div>
</div>
@endif
</div>
</div>
@endsection
@push('add-scripts')
<script>
var relationsSwiper = new Swiper ('.relations-swiper', {
watchSlidesProgress: true,
breakpoints:{
320: {
slidesPerView: 2,
spaceBetween: 10,
},
768: {
slidesPerView: 4,
spaceBetween: 30,
},
},
spaceBetween: 30,
// 如果需要前进后退按钮
navigation: {
nextEl: '.relations-swiper-next',
prevEl: '.relations-swiper-prev',
},
// 如果需要分页器
pagination: {
el: '.relations-pagination',
clickable: true,
},
})
</script>
@endpush

View File

@ -7,8 +7,10 @@
@section('content')
<div class="container">
<x-shop-breadcrumb type="page" :value="$page['id']" />
<div class="row">
{!! $page['content'] !!}
<div class="card">
<div class="card-body">
{!! $page_format['content'] !!}
</div>
</div>
</div>
@endsection