分拆 beike namespace (#2)
* 分拆 beike namespace Co-authored-by: Sam Chen <samchen945@gmail.com>
This commit is contained in:
parent
bc91c4a323
commit
22b7783312
|
|
@ -15,3 +15,5 @@ yarn-error.log
|
||||||
/.vscode
|
/.vscode
|
||||||
mix-manifest.json
|
mix-manifest.json
|
||||||
package-lock.json
|
package-lock.json
|
||||||
|
public/beike
|
||||||
|
beike/node_modules
|
||||||
|
|
|
||||||
|
|
@ -1,67 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Http\Controllers\Admin;
|
|
||||||
|
|
||||||
use App\Http\Controllers\Controller;
|
|
||||||
use App\Http\Requests\Admin\CategoryRequest;
|
|
||||||
use App\Http\Resources\Admin\CategoryResource;
|
|
||||||
use App\Models\Category;
|
|
||||||
use App\Services\CategoryService;
|
|
||||||
use Illuminate\Http\Request;
|
|
||||||
|
|
||||||
class CategoriesController extends Controller
|
|
||||||
{
|
|
||||||
public function index()
|
|
||||||
{
|
|
||||||
$categories = Category::with('description', 'children.description', 'children.children.description')
|
|
||||||
->where('parent_id', 0)
|
|
||||||
->get();
|
|
||||||
|
|
||||||
$data = [
|
|
||||||
'categories' => CategoryResource::collection($categories),
|
|
||||||
];
|
|
||||||
|
|
||||||
return view('admin.pages.categories.index', $data);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function create(Request $request)
|
|
||||||
{
|
|
||||||
$category = new Category();
|
|
||||||
|
|
||||||
$data = [
|
|
||||||
'category' => $category,
|
|
||||||
];
|
|
||||||
|
|
||||||
return view('admin.pages.categories.form', $data);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function store(CategoryRequest $request)
|
|
||||||
{
|
|
||||||
$redirect = $request->_redirect ?? admin_route('categories.index');
|
|
||||||
|
|
||||||
$category = (new CategoryService())->create($request->all());
|
|
||||||
|
|
||||||
return redirect($redirect)->with('success', 'Category created successfully');
|
|
||||||
}
|
|
||||||
|
|
||||||
public function edit(Category $category)
|
|
||||||
{
|
|
||||||
|
|
||||||
$descriptions = $category->descriptions->keyBy('locale');
|
|
||||||
$data = [
|
|
||||||
'category' => $category,
|
|
||||||
'descriptions' => $descriptions,
|
|
||||||
];
|
|
||||||
|
|
||||||
return view('admin.pages.categories.form', $data);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function update(CategoryRequest $request, Category $category)
|
|
||||||
{
|
|
||||||
$redirect = $request->_redirect ?? admin_route('categories.index');
|
|
||||||
|
|
||||||
$category = (new CategoryService())->update($category, $request->all());
|
|
||||||
|
|
||||||
return redirect($redirect)->with('success', 'Category created successfully');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,81 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Http\Controllers\Admin;
|
|
||||||
|
|
||||||
use App\Http\Controllers\Controller;
|
|
||||||
use App\Http\Resources\Admin\ProductResource;
|
|
||||||
use App\Models\Product;
|
|
||||||
use App\Services\ProductService;
|
|
||||||
use Illuminate\Http\Request;
|
|
||||||
|
|
||||||
class ProductsController extends Controller
|
|
||||||
{
|
|
||||||
public function index(Request $request)
|
|
||||||
{
|
|
||||||
$query = Product::query()
|
|
||||||
->with('description')
|
|
||||||
->withCount('skus');
|
|
||||||
if ($request->trashed) {
|
|
||||||
$query->onlyTrashed();
|
|
||||||
}
|
|
||||||
|
|
||||||
$products = $query->paginate();
|
|
||||||
|
|
||||||
$data = [
|
|
||||||
'products' => ProductResource::collection($products),
|
|
||||||
];
|
|
||||||
|
|
||||||
return view('admin.pages.products.index', $data);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function create(Request $request)
|
|
||||||
{
|
|
||||||
|
|
||||||
return view('admin.pages.products.form.form');
|
|
||||||
}
|
|
||||||
|
|
||||||
public function store(Request $request)
|
|
||||||
{
|
|
||||||
$product = (new ProductService)->create($request->all());
|
|
||||||
|
|
||||||
$redirect = $request->_redirect ?? route('admin.products.index');
|
|
||||||
return redirect($redirect)->with('success', 'product created');
|
|
||||||
}
|
|
||||||
|
|
||||||
public function show($id)
|
|
||||||
{
|
|
||||||
//
|
|
||||||
}
|
|
||||||
|
|
||||||
public function edit(Product $product)
|
|
||||||
{
|
|
||||||
$product->loadMissing('descriptions');
|
|
||||||
|
|
||||||
$data = [
|
|
||||||
'product' => $product,
|
|
||||||
];
|
|
||||||
return view('admin.pages.products.form.form', $data);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function update(Request $request, Product $product)
|
|
||||||
{
|
|
||||||
$product = (new ProductService)->update($product, $request->all());
|
|
||||||
|
|
||||||
return redirect()->route('admin.products.index')->with('success', 'product updated');
|
|
||||||
}
|
|
||||||
|
|
||||||
public function destroy(Request $request, Product $product)
|
|
||||||
{
|
|
||||||
$product->delete();
|
|
||||||
|
|
||||||
return ['success' => true];
|
|
||||||
}
|
|
||||||
|
|
||||||
public function restore(Request $request)
|
|
||||||
{
|
|
||||||
$productId = $request->id ?? 0;
|
|
||||||
Product::withTrashed()->find($productId)->restore();
|
|
||||||
|
|
||||||
return ['success' => true];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -2,7 +2,6 @@
|
||||||
|
|
||||||
namespace App\Providers;
|
namespace App\Providers;
|
||||||
|
|
||||||
use App\Models\Setting;
|
|
||||||
use Illuminate\Support\ServiceProvider;
|
use Illuminate\Support\ServiceProvider;
|
||||||
|
|
||||||
class AppServiceProvider extends ServiceProvider
|
class AppServiceProvider extends ServiceProvider
|
||||||
|
|
@ -24,15 +23,6 @@ class AppServiceProvider extends ServiceProvider
|
||||||
*/
|
*/
|
||||||
public function boot()
|
public function boot()
|
||||||
{
|
{
|
||||||
$settingsFromDB = Setting::all(['name', 'value', 'json'])
|
|
||||||
->keyBy('name')
|
|
||||||
->transform(function ($setting) {
|
|
||||||
if ($setting->json) {
|
|
||||||
return \json_decode($setting->value, true);
|
|
||||||
}
|
|
||||||
return $setting->value;
|
|
||||||
})
|
|
||||||
->toArray();
|
|
||||||
config(['global' => $settingsFromDB]);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -43,15 +43,9 @@ class RouteServiceProvider extends ServiceProvider
|
||||||
->namespace($this->namespace)
|
->namespace($this->namespace)
|
||||||
->group(base_path('routes/api.php'));
|
->group(base_path('routes/api.php'));
|
||||||
|
|
||||||
// Shop
|
|
||||||
Route::middleware('web')
|
Route::middleware('web')
|
||||||
->namespace($this->namespace)
|
->namespace($this->namespace)
|
||||||
->group(base_path('routes/web_shop.php'));
|
->group(base_path('routes/web.php'));
|
||||||
|
|
||||||
// Admin
|
|
||||||
Route::middleware('web')
|
|
||||||
->namespace($this->namespace)
|
|
||||||
->group(base_path('routes/web_admin.php'));
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,70 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Beike\Admin\Http\Controllers;
|
||||||
|
|
||||||
|
use Beike\Admin\Http\Requests\CategoryRequest;
|
||||||
|
use Beike\Admin\Http\Resources\CategoryResource;
|
||||||
|
use Beike\Admin\Repositories\CategoryRepo;
|
||||||
|
use Beike\Models\Category;
|
||||||
|
use Beike\Admin\Services\CategoryService;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
|
class CategoryController extends Controller
|
||||||
|
{
|
||||||
|
protected string $defaultRoute = 'categories.index';
|
||||||
|
|
||||||
|
public function index()
|
||||||
|
{
|
||||||
|
$categories = Category::with('description', 'children.description', 'children.children.description')
|
||||||
|
->where('parent_id', 0)
|
||||||
|
->get();
|
||||||
|
|
||||||
|
$data = [
|
||||||
|
'categories' => CategoryResource::collection($categories),
|
||||||
|
];
|
||||||
|
|
||||||
|
return view('admin::pages.categories.index', $data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function create(Request $request)
|
||||||
|
{
|
||||||
|
return $this->form($request);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function store(CategoryRequest $request)
|
||||||
|
{
|
||||||
|
return $this->save($request);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function edit(Request $request, Category $category)
|
||||||
|
{
|
||||||
|
return $this->form($request, $category);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function update(CategoryRequest $request, Category $category)
|
||||||
|
{
|
||||||
|
return $this->save($request, $category);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function form(Request $request, Category $category = null)
|
||||||
|
{
|
||||||
|
if ($category) {
|
||||||
|
$descriptions = $category->descriptions->keyBy('locale');
|
||||||
|
}
|
||||||
|
|
||||||
|
$data = [
|
||||||
|
'category' => $category ?? new Category(),
|
||||||
|
'descriptions' => $descriptions ?? null,
|
||||||
|
'categories' => CategoryRepo::flatten(locale()),
|
||||||
|
'_redirect' => $this->getRedirect(),
|
||||||
|
];
|
||||||
|
|
||||||
|
return view('admin::pages.categories.form', $data);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function save(Request $request, ?Category $category = null)
|
||||||
|
{
|
||||||
|
(new CategoryService())->createOrUpdate($request->all(), $category);
|
||||||
|
return redirect($this->getRedirect())->with('success', 'Category created successfully');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Beike\Admin\Http\Controllers;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller as BaseController;
|
||||||
|
|
||||||
|
abstract class Controller extends BaseController
|
||||||
|
{
|
||||||
|
protected string $defaultRoute;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 表单页面获跳转页面链接
|
||||||
|
* @return array|\Illuminate\Contracts\Foundation\Application|\Illuminate\Http\Request|string|null
|
||||||
|
*/
|
||||||
|
public function getRedirect()
|
||||||
|
{
|
||||||
|
return request('_redirect') ?? request()->header('referer', admin_route($this->defaultRoute));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Beike\Admin\Http\Controllers;
|
||||||
|
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
|
class FileController extends Controller
|
||||||
|
{
|
||||||
|
public function upload(Request $request)
|
||||||
|
{
|
||||||
|
$path = $request->file('file')->store('avatars');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace App\Http\Controllers\Admin;
|
namespace Beike\Admin\Http\Controllers;
|
||||||
|
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
|
|
@ -9,6 +9,6 @@ class HomeController extends Controller
|
||||||
{
|
{
|
||||||
public function index()
|
public function index()
|
||||||
{
|
{
|
||||||
return view('admin.pages.home');
|
return view('admin::pages.home');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace App\Http\Controllers\Admin;
|
namespace Beike\Admin\Http\Controllers;
|
||||||
|
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
use App\Models\AdminUser;
|
use Beike\Models\AdminUser;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Support\Facades\Auth;
|
use Illuminate\Support\Facades\Auth;
|
||||||
|
|
||||||
|
|
@ -11,7 +11,7 @@ class LoginController extends Controller
|
||||||
{
|
{
|
||||||
public function show()
|
public function show()
|
||||||
{
|
{
|
||||||
return view('admin.pages.login.login');
|
return view('admin::pages.login.login');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function store(Request $request)
|
public function store(Request $request)
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace App\Http\Controllers\Admin;
|
namespace Beike\Admin\Http\Controllers;
|
||||||
|
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
use App\Models\AdminUser;
|
use Beike\Models\AdminUser;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Support\Facades\Auth;
|
use Illuminate\Support\Facades\Auth;
|
||||||
|
|
||||||
|
|
@ -0,0 +1,124 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Beike\Admin\Http\Controllers;
|
||||||
|
|
||||||
|
use Beike\Admin\Http\Resources\ProductResource;
|
||||||
|
use Beike\Admin\Repositories\CategoryRepo;
|
||||||
|
use Beike\Models\Product;
|
||||||
|
use Beike\Models\ProductDescription;
|
||||||
|
use Beike\Models\ProductSku;
|
||||||
|
use Beike\Admin\Services\ProductService;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
|
class ProductController extends Controller
|
||||||
|
{
|
||||||
|
protected string $defaultRoute = 'products.index';
|
||||||
|
|
||||||
|
public function index(Request $request)
|
||||||
|
{
|
||||||
|
if ($request->expectsJson()) {
|
||||||
|
$query = Product::query()
|
||||||
|
->select('products.*')
|
||||||
|
->with('description')
|
||||||
|
->withCount('skus');
|
||||||
|
|
||||||
|
if ($request->sku) {
|
||||||
|
$query->whereHas('skus', function ($q) {
|
||||||
|
$q->where('sku', 'like', '%'.request('sku').'%');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 关键字搜索:名称
|
||||||
|
if ($request->keyword) {
|
||||||
|
$query->whereHas('description', function ($q) {
|
||||||
|
$q->where('name', 'like', '%'.request('keyword').'%');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
$query->when($request->active !== null, function ($q) {
|
||||||
|
$q->where('active', (int)request('active'));
|
||||||
|
});
|
||||||
|
|
||||||
|
// 回收站
|
||||||
|
if ($request->trashed) {
|
||||||
|
$query->onlyTrashed();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 排序
|
||||||
|
$orderBy = $request->orderBy ?? 'products.id:desc';
|
||||||
|
$orderBy = explode(':', $orderBy);
|
||||||
|
$query->orderBy($orderBy[0], $orderBy[1] ?? 'desc');
|
||||||
|
|
||||||
|
$products = $query->paginate($request->per_page ?? 10);
|
||||||
|
|
||||||
|
return ProductResource::collection($products);
|
||||||
|
}
|
||||||
|
|
||||||
|
$data = [
|
||||||
|
'categories' => CategoryRepo::flatten(locale()),
|
||||||
|
];
|
||||||
|
return view('admin::pages.products.index', $data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function create(Request $request)
|
||||||
|
{
|
||||||
|
return $this->form($request, new Product());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function store(Request $request)
|
||||||
|
{
|
||||||
|
return $this->save($request, new Product());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function edit(Request $request, Product $product)
|
||||||
|
{
|
||||||
|
return $this->form($request, $product);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function update(Request $request, Product $product)
|
||||||
|
{
|
||||||
|
return $this->save($request, $product);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function destroy(Request $request, Product $product)
|
||||||
|
{
|
||||||
|
$product->delete();
|
||||||
|
|
||||||
|
return ['success' => true];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function restore(Request $request)
|
||||||
|
{
|
||||||
|
$productId = $request->id ?? 0;
|
||||||
|
Product::withTrashed()->find($productId)->restore();
|
||||||
|
|
||||||
|
return ['success' => true];
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function form(Request $request, Product $product)
|
||||||
|
{
|
||||||
|
if ($product->id) {
|
||||||
|
$descriptions = $product->descriptions->keyBy('locale');
|
||||||
|
}
|
||||||
|
|
||||||
|
$data = [
|
||||||
|
'product' => $product,
|
||||||
|
'descriptions' => $descriptions ?? [],
|
||||||
|
'categories' => CategoryRepo::flatten(locale()),
|
||||||
|
'_redirect' => $this->getRedirect(),
|
||||||
|
];
|
||||||
|
|
||||||
|
return view('admin::pages.products.form.form', $data);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function save(Request $request, Product $product)
|
||||||
|
{
|
||||||
|
if ($product->id) {
|
||||||
|
$product = (new ProductService)->update($product, $request->all());
|
||||||
|
} else {
|
||||||
|
$product = (new ProductService)->create($request->all());
|
||||||
|
}
|
||||||
|
|
||||||
|
return redirect($this->getRedirect())->with('success', 'product created');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace App\Http\Requests\Admin;
|
namespace Beike\Admin\Http\Requests;
|
||||||
|
|
||||||
use Illuminate\Foundation\Http\FormRequest;
|
use Illuminate\Foundation\Http\FormRequest;
|
||||||
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace App\Http\Resources\Admin;
|
namespace Beike\Admin\Http\Resources;
|
||||||
|
|
||||||
use Illuminate\Http\Resources\Json\JsonResource;
|
use Illuminate\Http\Resources\Json\JsonResource;
|
||||||
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace App\Http\Resources\Admin;
|
namespace Beike\Admin\Http\Resources;
|
||||||
|
|
||||||
use Illuminate\Http\Resources\Json\JsonResource;
|
use Illuminate\Http\Resources\Json\JsonResource;
|
||||||
|
|
||||||
|
|
@ -0,0 +1,81 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Beike\Admin\Providers;
|
||||||
|
|
||||||
|
use Beike\Console\Commands\MakeRootAdminUser;
|
||||||
|
use Beike\Models\AdminUser;
|
||||||
|
use Beike\Models\Setting;
|
||||||
|
use Beike\Admin\View\Components\Filter;
|
||||||
|
use Beike\Admin\View\Components\Header;
|
||||||
|
use Beike\Admin\View\Components\Sidebar;
|
||||||
|
use Beike\Admin\View\Components\Form\Input;
|
||||||
|
use Beike\Admin\View\Components\Form\InputLocale;
|
||||||
|
use Beike\Admin\View\Components\Form\SwitchRadio;
|
||||||
|
use Illuminate\Support\Facades\Config;
|
||||||
|
use Illuminate\Support\ServiceProvider;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
|
||||||
|
class AdminServiceProvider extends ServiceProvider
|
||||||
|
{
|
||||||
|
public function boot()
|
||||||
|
{
|
||||||
|
$uri = request()->getRequestUri();
|
||||||
|
|
||||||
|
if (! Str::startsWith($uri, '/admin')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// $this->loadRoutesFrom(__DIR__ . '/../Routes/shop.php');
|
||||||
|
$this->loadRoutesFrom(__DIR__ . '/../Routes/admin.php');
|
||||||
|
|
||||||
|
$this->mergeConfigFrom(__DIR__ . '/../../Config/beike.php', 'beike');
|
||||||
|
|
||||||
|
$this->loadViewsFrom(__DIR__ . '/../Resources/views', 'admin');
|
||||||
|
|
||||||
|
$this->loadViewComponentsAs('admin', [
|
||||||
|
'header' => Header::class,
|
||||||
|
'sidebar' => Sidebar::class,
|
||||||
|
'filter' => Filter::class,
|
||||||
|
'form-input-locale' => InputLocale::class,
|
||||||
|
'form-switch' => SwitchRadio::class,
|
||||||
|
'form-input' => Input::class,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->loadSettings();
|
||||||
|
$this->registerGuard();
|
||||||
|
|
||||||
|
if ($this->app->runningInConsole()) {
|
||||||
|
$this->commands([
|
||||||
|
MakeRootAdminUser::class,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function loadSettings()
|
||||||
|
{
|
||||||
|
$settings = Setting::all(['name', 'value', 'json'])
|
||||||
|
->keyBy('name')
|
||||||
|
->transform(function ($setting) {
|
||||||
|
if ($setting->json) {
|
||||||
|
return \json_decode($setting->value, true);
|
||||||
|
}
|
||||||
|
return $setting->value;
|
||||||
|
})
|
||||||
|
->toArray();
|
||||||
|
config(['global' => $settings]);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function registerGuard()
|
||||||
|
{
|
||||||
|
Config::set('auth.guards.'.AdminUser::AUTH_GUARD, [
|
||||||
|
'driver' => 'session',
|
||||||
|
'provider' => 'admin_users',
|
||||||
|
]);
|
||||||
|
|
||||||
|
Config::set('auth.providers.admin_users', [
|
||||||
|
'driver' => 'eloquent',
|
||||||
|
'model' => AdminUser::class,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Beike\Admin\Repositories;
|
||||||
|
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
|
||||||
|
class CategoryRepo
|
||||||
|
{
|
||||||
|
public static function flatten(string $locale, $separator = ' > '): array
|
||||||
|
{
|
||||||
|
$sql = "SELECT cp.category_id AS id, TRIM(LOWER(GROUP_CONCAT(cd1.name ORDER BY cp.level SEPARATOR '{$separator}'))) AS name, c1.parent_id, c1.position";
|
||||||
|
$sql .= " FROM category_paths cp";
|
||||||
|
$sql .= " LEFT JOIN categories c1 ON (cp.category_id = c1.id)";
|
||||||
|
$sql .= " LEFT JOIN categories c2 ON (cp.path_id = c2.id)";
|
||||||
|
$sql .= " LEFT JOIN category_descriptions cd1 ON (cp.path_id = cd1.category_id)";
|
||||||
|
$sql .= " WHERE cd1.locale = '" . $locale . "' GROUP BY cp.category_id ORDER BY name ASC";
|
||||||
|
$results = DB::select($sql);
|
||||||
|
|
||||||
|
return $results;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,154 @@
|
||||||
|
@charset "UTF-8";
|
||||||
|
|
||||||
|
//
|
||||||
|
// @copyright 2020 opencart.cn - All Rights Reserved
|
||||||
|
// @link http://www.guangdawangluo.com
|
||||||
|
// @author Sam Chen <sam.chen@opencart.cn>
|
||||||
|
// @created 2021-08-24 14:38:09
|
||||||
|
// @modified 2021-10-13 10:38:17
|
||||||
|
//
|
||||||
|
|
||||||
|
hr.horizontal {
|
||||||
|
background-color: transparent;
|
||||||
|
height: 1px;
|
||||||
|
margin: 1rem 0;
|
||||||
|
color: inherit;
|
||||||
|
border: 0;
|
||||||
|
opacity: .25;
|
||||||
|
}
|
||||||
|
|
||||||
|
hr.horizontal.dark {
|
||||||
|
background-image: linear-gradient(90deg,transparent,rgba(0,0,0,.4),transparent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.card {
|
||||||
|
// box-shadow: 0 20px 27px 0 rgba(0, 0, 0, .05);
|
||||||
|
box-shadow: 0 0.75rem 1.5rem rgb(18 38 63 / 3%);
|
||||||
|
// border: 0 solid rgba(0,0,0,.125);
|
||||||
|
border: 1px solid #edf2f9;
|
||||||
|
// border-radius: 0.5rem;
|
||||||
|
|
||||||
|
.card-header {
|
||||||
|
padding: 1rem 1rem .3rem;
|
||||||
|
background-color: #fff;
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: .8rem;
|
||||||
|
border-bottom: 0 solid rgba(0,0,0,.125);
|
||||||
|
|
||||||
|
&:first-child {
|
||||||
|
border-radius: 1rem 1rem 0 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-body {
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
table.table {
|
||||||
|
thead {
|
||||||
|
th {
|
||||||
|
background-color: #f9fbfd;
|
||||||
|
color: #74859e;
|
||||||
|
font-size: .825rem;
|
||||||
|
border-top-width: 0;
|
||||||
|
border-bottom: none;
|
||||||
|
white-space:nowrap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
td {
|
||||||
|
border-bottom: 0;
|
||||||
|
font-size: .8125rem;
|
||||||
|
vertical-align: middle;
|
||||||
|
border-top: 1px solid #edf2f9;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.table-striped>tbody>tr {
|
||||||
|
&:nth-of-type(odd) {
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
&:nth-of-type(2n) {
|
||||||
|
background: #f9fbfd;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group {
|
||||||
|
margin-bottom: 1.375rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn {
|
||||||
|
font-size: 0.8rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-check {
|
||||||
|
position: absolute;
|
||||||
|
clip: rect(0,0,0,0);
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-group-radio {
|
||||||
|
.btn-group {
|
||||||
|
label {
|
||||||
|
&:first-of-type {
|
||||||
|
border-top-left-radius: .25rem;
|
||||||
|
border-bottom-left-radius: .25rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-check:active+.btn-outline-primary, .btn-check:checked+.btn-outline-primary, .btn-outline-primary.active, .btn-outline-primary.dropdown-toggle.show, .btn-outline-primary:active {
|
||||||
|
color: #fff;
|
||||||
|
background-color: #0d6efd;
|
||||||
|
border-color: #0d6efd;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.btn-group-radio-pay {
|
||||||
|
.btn-check:active+.btn, .btn-check:checked+.btn, .btn.active, .btn.dropdown-toggle.show, .btn:active {
|
||||||
|
color: #fff;
|
||||||
|
background-color: transparent;
|
||||||
|
padding: .32rem .70rem;
|
||||||
|
border: 2px solid #0d6efd;
|
||||||
|
// border-color: #0d6efd;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-group-radios {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
label.btn {
|
||||||
|
height: 56px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
border-color: #0d6efd;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
img {
|
||||||
|
max-width: 140px;
|
||||||
|
height: 42px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.bd-callout {
|
||||||
|
padding: 1.25rem;
|
||||||
|
margin-top: 1.25rem;
|
||||||
|
margin-bottom: 1.25rem;
|
||||||
|
border: 1px solid #eee;
|
||||||
|
border-left-width: .25rem;
|
||||||
|
border-radius: .25rem;
|
||||||
|
|
||||||
|
&.bd-callout-info {
|
||||||
|
border-left-color: #5bc0de;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
@charset "UTF-8";
|
||||||
|
|
||||||
|
//
|
||||||
|
// @copyright 2020 opencart.cn - All Rights Reserved
|
||||||
|
// @link http://www.guangdawangluo.com
|
||||||
|
// @author Sam Chen <sam.chen@opencart.cn>
|
||||||
|
// @created 2021-08-24 14:38:09
|
||||||
|
// @modified 2021-08-24 15:07:05
|
||||||
|
//
|
||||||
|
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
@charset "UTF-8";
|
||||||
|
|
||||||
|
//
|
||||||
|
// @copyright 2020 opencart.cn - All Rights Reserved
|
||||||
|
// @link http://www.guangdawangluo.com
|
||||||
|
// @author Sam Chen <sam.chen@opencart.cn>
|
||||||
|
// @created 2022-05-09 11:35:44
|
||||||
|
// @modified 2022-05-09 13:48:22
|
||||||
|
//
|
||||||
|
|
||||||
|
.form-group {
|
||||||
|
.form-control {
|
||||||
|
&.short {
|
||||||
|
max-width: 300px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-group {
|
||||||
|
&.short {
|
||||||
|
max-width: 300px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.col-form-label {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,170 @@
|
||||||
|
@charset "UTF-8";
|
||||||
|
|
||||||
|
//
|
||||||
|
// @copyright 2020 opencart.cn - All Rights Reserved
|
||||||
|
// @link http://www.guangdawangluo.com
|
||||||
|
// @author Sam Chen <sam.chen@opencart.cn>
|
||||||
|
// @created 2021-08-24 14:38:09
|
||||||
|
// @modified 2021-08-24 15:07:05
|
||||||
|
//
|
||||||
|
|
||||||
|
body {
|
||||||
|
// min-height: 100vh;
|
||||||
|
// background-color: #f9f9f9;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 1.6;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
background-color: #f9fbfd;
|
||||||
|
}
|
||||||
|
|
||||||
|
@for $i from 1 through 6 {
|
||||||
|
.min-h#{$i} {
|
||||||
|
min-height: #{$i}00px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.main-content {
|
||||||
|
display: flex;
|
||||||
|
// flex-direction: column;
|
||||||
|
// min-height: 100vh;
|
||||||
|
transition: margin-left .25s ease-in-out,left .25s ease-in-out,margin-right .25s ease-in-out,right .25s ease-in-out;
|
||||||
|
width: 100%;
|
||||||
|
height: calc(100vh - 60px);
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
@media screen and (max-width: 991px) {
|
||||||
|
margin-left: 260px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:not(.active) {
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
> #content {
|
||||||
|
flex: 1;
|
||||||
|
padding: 1rem;
|
||||||
|
overflow-y: auto;
|
||||||
|
|
||||||
|
@media screen and (max-width: 991px) {
|
||||||
|
padding: 0 1rem 1.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.page-title-box {
|
||||||
|
.page-title {
|
||||||
|
font-size: 15px;
|
||||||
|
margin: 0;
|
||||||
|
// line-height: 35px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.switch-plus {
|
||||||
|
position: relative;
|
||||||
|
width: 50px;
|
||||||
|
height: 24px;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
|
||||||
|
input {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
z-index: 2;
|
||||||
|
opacity: 0;
|
||||||
|
cursor: pointer;
|
||||||
|
height: 20px;
|
||||||
|
width: 50px;
|
||||||
|
left: 0;
|
||||||
|
margin: 0;
|
||||||
|
|
||||||
|
&:checked {
|
||||||
|
z-index: 1;
|
||||||
|
|
||||||
|
& + label {
|
||||||
|
opacity: 1;
|
||||||
|
cursor: default;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
~ .toggle-outside .toggle-inside {
|
||||||
|
left: 0.25rem;
|
||||||
|
background-color: #fff;
|
||||||
|
box-shadow: 0 3px 6px 0 rgba(140,152,164,.25);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
& ~ input:checked ~ .toggle-outside {
|
||||||
|
background-color: $main_color;
|
||||||
|
|
||||||
|
.toggle-inside {
|
||||||
|
left: 23px;
|
||||||
|
background-color: #fff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
label {
|
||||||
|
color: #fff;
|
||||||
|
opacity: 0.33;
|
||||||
|
transition: opacity 0.25s ease;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 1.5rem;
|
||||||
|
line-height: 3rem;
|
||||||
|
display: inline-block;
|
||||||
|
width: 50px;
|
||||||
|
height: 100%;
|
||||||
|
margin: 0;
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
&:last-of-type {
|
||||||
|
margin-left: 50px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle-outside {
|
||||||
|
height: 100%;
|
||||||
|
border-radius: 2rem;
|
||||||
|
padding: 2px;
|
||||||
|
overflow: hidden;
|
||||||
|
transition: 0.25s ease all;
|
||||||
|
// background: #f1f3fa;
|
||||||
|
background-color: #e7eaf3;
|
||||||
|
position: absolute;
|
||||||
|
width: 46px;
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle-inside {
|
||||||
|
border-radius: 50%;
|
||||||
|
// background: #4a4a4a;
|
||||||
|
position: absolute;
|
||||||
|
transition: 0.25s ease all;
|
||||||
|
height: 20px;
|
||||||
|
width: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
body.page-seller-product {
|
||||||
|
.share-link-pop {
|
||||||
|
.share-links-td {
|
||||||
|
padding-top: 4px;
|
||||||
|
padding-bottom: 4px;
|
||||||
|
}
|
||||||
|
.share-links-code {
|
||||||
|
width: 80px;
|
||||||
|
height: 80px;
|
||||||
|
overflow: hidden;
|
||||||
|
img {
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,75 @@
|
||||||
|
@charset "UTF-8";
|
||||||
|
|
||||||
|
//
|
||||||
|
// @copyright 2020 opencart.cn - All Rights Reserved
|
||||||
|
// @link http://www.guangdawangluo.com
|
||||||
|
// @author Sam Chen <sam.chen@opencart.cn>
|
||||||
|
// @created 2021-08-24 14:38:09
|
||||||
|
// @modified 2021-08-24 15:07:05
|
||||||
|
//
|
||||||
|
|
||||||
|
.header-wrap {
|
||||||
|
background-color: #fff;
|
||||||
|
height: 60px;
|
||||||
|
border-bottom: 1px solid #f1f1f1;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.header-left {
|
||||||
|
width: 190px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-right {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
flex: 1;
|
||||||
|
|
||||||
|
.navbar {
|
||||||
|
margin-bottom: 0;
|
||||||
|
list-style: none;
|
||||||
|
li {
|
||||||
|
padding: 0.5rem 1.3rem;
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: #333;
|
||||||
|
padding: 0.5rem 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar.navbar-right {
|
||||||
|
li {
|
||||||
|
a {
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
&:after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
display: none;
|
||||||
|
bottom: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 3px;
|
||||||
|
background-color: $main_color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.active, &:hover {
|
||||||
|
a {
|
||||||
|
&:after {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar {
|
||||||
|
height: 36px;
|
||||||
|
margin-bottom: -15px;
|
||||||
|
margin-top: -15px;
|
||||||
|
width: 36px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
@charset "UTF-8";
|
||||||
|
|
||||||
|
//
|
||||||
|
// @copyright 2020 opencart.cn - All Rights Reserved
|
||||||
|
// @link http://www.guangdawangluo.com
|
||||||
|
// @author Sam Chen <sam.chen@opencart.cn>
|
||||||
|
// @created 2021-08-24 14:38:09
|
||||||
|
// @modified 2021-08-24 15:07:05
|
||||||
|
//
|
||||||
|
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'iconfont'; /* Project id 2787822 */
|
||||||
|
src: url('//at.alicdn.com/t/font_2787822_7mtbg56vojp.woff2?t=1634612961708') format('woff2'),
|
||||||
|
url('//at.alicdn.com/t/font_2787822_7mtbg56vojp.woff?t=1634612961708') format('woff'),
|
||||||
|
url('//at.alicdn.com/t/font_2787822_7mtbg56vojp.ttf?t=1634612961708') format('truetype');
|
||||||
|
}
|
||||||
|
|
||||||
|
.iconfont {
|
||||||
|
font-family:"iconfont" !important;
|
||||||
|
font-size:16px;
|
||||||
|
font-style:normal;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
// -webkit-text-stroke-width: 0.2px;
|
||||||
|
-webkit-text-stroke-width: 0;
|
||||||
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,61 @@
|
||||||
|
@charset "UTF-8";
|
||||||
|
|
||||||
|
//
|
||||||
|
// @copyright 2020 opencart.cn - All Rights Reserved
|
||||||
|
// @link http://www.guangdawangluo.com
|
||||||
|
// @author Sam Chen <sam.chen@opencart.cn>
|
||||||
|
// @created 2021-08-24 14:38:09
|
||||||
|
// @modified 2021-08-24 15:07:05
|
||||||
|
//
|
||||||
|
|
||||||
|
.sidebar {
|
||||||
|
background: #293042;
|
||||||
|
direction: ltr;
|
||||||
|
width: 190px;
|
||||||
|
transition: all .2s ease-in-out;
|
||||||
|
background: #fff;
|
||||||
|
border-right: 1px solid #f1f1f1;
|
||||||
|
|
||||||
|
@media screen and (max-width: 991px) {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-nav {
|
||||||
|
> li.nav-item {
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
a {
|
||||||
|
padding: .6rem 1rem .6rem;
|
||||||
|
// color: rgba(58,65,111, .9);
|
||||||
|
color: #333;
|
||||||
|
transition: all .1s ease-in-out;
|
||||||
|
|
||||||
|
i {
|
||||||
|
margin-right: 7px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.active, &:hover {
|
||||||
|
a {
|
||||||
|
color: $main_color;
|
||||||
|
background-color: #f4f4f4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
&:before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
width: 2px;
|
||||||
|
height: 100%;
|
||||||
|
z-index: 1;
|
||||||
|
background: $main_color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
@charset "UTF-8";
|
||||||
|
|
||||||
|
//
|
||||||
|
// @copyright 2020 opencart.cn - All Rights Reserved
|
||||||
|
// @link http://www.guangdawangluo.com
|
||||||
|
// @author Sam Chen <sam.chen@opencart.cn>
|
||||||
|
// @created 2021-08-24 14:38:09
|
||||||
|
// @modified 2021-08-24 15:07:05
|
||||||
|
//
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
@charset "UTF-8";
|
||||||
|
|
||||||
|
//
|
||||||
|
// @copyright 2020 opencart.cn - All Rights Reserved
|
||||||
|
// @link http://www.guangdawangluo.com
|
||||||
|
// @author Sam Chen <sam.chen@opencart.cn>
|
||||||
|
// @created 2021-10-25 11:40:51
|
||||||
|
// @modified 2021-10-25 11:41:02
|
||||||
|
//
|
||||||
|
|
||||||
|
[v-cloak] {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
@charset "UTF-8";
|
||||||
|
|
||||||
|
//
|
||||||
|
// @copyright 2020 opencart.cn - All Rights Reserved
|
||||||
|
// @link http://www.guangdawangluo.com
|
||||||
|
// @author Sam Chen <sam.chen@opencart.cn>
|
||||||
|
// @created 2021-08-24 14:38:09
|
||||||
|
// @modified 2022-05-09 11:36:06
|
||||||
|
//
|
||||||
|
|
||||||
|
$main_color: #fd560f;
|
||||||
|
|
||||||
|
@import 'global';
|
||||||
|
@import 'vue';
|
||||||
|
@import 'sidebar';
|
||||||
|
@import 'header';
|
||||||
|
@import 'bootstrap-extra';
|
||||||
|
@import 'iconfont';
|
||||||
|
@import 'dashboard';
|
||||||
|
@import 'subscription';
|
||||||
|
@import 'form';
|
||||||
|
|
@ -0,0 +1,120 @@
|
||||||
|
@charset "UTF-8";
|
||||||
|
|
||||||
|
//
|
||||||
|
// @copyright 2020 opencart.cn - All Rights Reserved
|
||||||
|
// @link http://www.guangdawangluo.com
|
||||||
|
// @author Sam Chen <sam.chen@opencart.cn>
|
||||||
|
// @created 2021-08-24 14:38:09
|
||||||
|
// @modified 2021-08-24 15:07:05
|
||||||
|
//
|
||||||
|
|
||||||
|
$main_color: #fd560f;
|
||||||
|
|
||||||
|
.form-text.text-danger {
|
||||||
|
font-size: 0.8rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
background-image: url("/images/login-bg.svg");
|
||||||
|
background-size: cover;
|
||||||
|
background-position: 50%;
|
||||||
|
|
||||||
|
&.seller-register, &.password-reset {
|
||||||
|
.form-group {
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
|
||||||
|
label {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.card {
|
||||||
|
box-shadow: 0 20px 27px 0 rgba(0, 0, 0, .05);
|
||||||
|
border: none;
|
||||||
|
border-radius: .85rem;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-header {
|
||||||
|
border-bottom: none;
|
||||||
|
background: #fff;
|
||||||
|
font-size: 17px;
|
||||||
|
padding-top: 40px;
|
||||||
|
|
||||||
|
h5 {
|
||||||
|
color: #344767;
|
||||||
|
font-weight: 400;
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
button[type="submit"] {
|
||||||
|
background-image: linear-gradient(310deg, $main_color, #fdb504);
|
||||||
|
// background-image: linear-gradient(310deg, $main_color, #fdb504);
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group {
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
|
||||||
|
input.form-control {
|
||||||
|
border: 1px solid #d2d6da;
|
||||||
|
border-radius: .5rem;
|
||||||
|
font-size: .875rem;
|
||||||
|
font-weight: 400;
|
||||||
|
color: #495057;
|
||||||
|
line-height: 1.4rem;
|
||||||
|
padding: .5rem .75rem;
|
||||||
|
height: 40px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.partition {
|
||||||
|
font-size: 12px;
|
||||||
|
|
||||||
|
&:after, &:before {
|
||||||
|
content: "";
|
||||||
|
display: inline-block;
|
||||||
|
width: 30%;
|
||||||
|
height: 1px;
|
||||||
|
position: relative;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:after {
|
||||||
|
left: .5em;
|
||||||
|
margin-right: -50%;
|
||||||
|
background: linear-gradient(90deg,rgba(117, 117, 117, .40),rgb(117, 117, 117, .40), transparent);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:before {
|
||||||
|
background: linear-gradient(90deg,transparent,rgba(117, 117, 117, .40),rgb(117, 117, 117, .40));
|
||||||
|
right: .5em;
|
||||||
|
margin-left: -50%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn:not(.btn-link) {
|
||||||
|
padding: .50rem 1.5rem;
|
||||||
|
border-radius: .5rem;
|
||||||
|
transition: all .15s ease-in;
|
||||||
|
box-shadow: 0 4px 7px -1px rgba(0, 0, 0, .11), 0 2px 4px -1px rgba(0, 0, 0, .07);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
transform: scale(1.02);
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.bg-gradient-dark {
|
||||||
|
background-image: linear-gradient(310deg,#141727,#3a416f);
|
||||||
|
background-color: #344767;
|
||||||
|
border: none;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
body {
|
||||||
|
background: #f2f2f2;
|
||||||
|
// color: #fff;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
@charset "UTF-8";
|
||||||
|
|
||||||
|
//
|
||||||
|
// @copyright 2020 opencart.cn - All Rights Reserved
|
||||||
|
// @link http://www.guangdawangluo.com
|
||||||
|
// @author Sam Chen <sam.chen@opencart.cn>
|
||||||
|
// @created 2021-08-24 14:38:09
|
||||||
|
// @modified 2021-08-24 15:07:05
|
||||||
|
//
|
||||||
|
|
||||||
|
$primary: #fd560f;
|
||||||
|
|
||||||
|
@import '../../../../node_modules/bootstrap/scss/bootstrap';
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
<div class="form-group">
|
||||||
|
<div class="row">
|
||||||
|
<label for="" class="col-sm-2 col-form-label">{{ $title }}</label>
|
||||||
|
<div class="col-sm-10">
|
||||||
|
@foreach (locales() as $index => $locale)
|
||||||
|
<div class="input-group input-group-sm short mb-1">
|
||||||
|
<input type="text" class="form-control" name="{{ $formatName($locale['code']) }}" placeholder="{{ $locale['name'] }}" value="{{ $formatValue($locale['code']) }}">
|
||||||
|
<div class="input-group-append">
|
||||||
|
<span class="input-group-text" id="input-{{ $name }}-{{ $locale['code'] }}">{{ $locale['name'] }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@if ($attributes->has('required'))
|
||||||
|
@error($errorKey($locale['code']))
|
||||||
|
<x-admin::form.error :message="$message" />
|
||||||
|
@enderror
|
||||||
|
@endif
|
||||||
|
@endforeach
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
<x-admin::form.row :title="$title">
|
||||||
|
<input type="text" name="{{ $name }}" class="form-control form-control-sm short" value="{{ $value }}" placeholder="{{ $title }}">
|
||||||
|
</x-admin::form.row>
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
<div class="form-group">
|
||||||
|
<div class="row">
|
||||||
|
<label for="" class="col-sm-2 col-form-label">{{ $title }}</label>
|
||||||
|
<div class="col-sm-10">
|
||||||
|
{{ $slot }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
<div class="form-group">
|
||||||
|
<div class="row">
|
||||||
|
<label for="" class="col-sm-2 col-form-label">{{ $title }}</label>
|
||||||
|
<div class="col-sm-10">
|
||||||
|
<div class="form-check form-check-inline">
|
||||||
|
<input class="form-check-input" type="radio" name="{{ $name }}" id="{{ $name }}-1" value="1" {{ $value ? 'checked' : '' }}>
|
||||||
|
<label class="form-check-label" for="{{ $name }}-1">启用</label>
|
||||||
|
</div>
|
||||||
|
<div class="form-check form-check-inline">
|
||||||
|
<input class="form-check-input" type="radio" name="{{ $name }}" id="{{ $name }}-0" value="0" {{ !$value ? 'checked' : '' }}>
|
||||||
|
<label class="form-check-label" for="{{ $name }}-0">禁用</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
@ -10,14 +10,14 @@
|
||||||
<script src="{{ asset('vendor/axios/0.21.1/axios.min.js') }}"></script>
|
<script src="{{ asset('vendor/axios/0.21.1/axios.min.js') }}"></script>
|
||||||
<script src="{{ asset('vendor/layer/3.5.1/layer.js') }}"></script>
|
<script src="{{ asset('vendor/layer/3.5.1/layer.js') }}"></script>
|
||||||
<script src="{{ mix('build/js/app.js') }}"></script> --}}
|
<script src="{{ mix('build/js/app.js') }}"></script> --}}
|
||||||
<script src="https://unpkg.com/element-ui/lib/index.js"></script>
|
<script src="https://cdn.bootcdn.net/ajax/libs/element-ui/2.15.8/index.min.js"></script>
|
||||||
|
|
||||||
<link href="{{ mix('build/css/bootstrap.css') }}" rel="stylesheet">
|
<link href="{{ mix('beike/build/css/bootstrap.css') }}" rel="stylesheet">
|
||||||
<link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css">
|
<link href="https://cdn.bootcdn.net/ajax/libs/element-ui/2.15.8/theme-chalk/index.min.css" rel="stylesheet">
|
||||||
@if (0)
|
@if (0)
|
||||||
<link rel="stylesheet" href="{{ asset('vendor/element-ui/2.15.6/css.css') }}">
|
<link rel="stylesheet" href="{{ asset('vendor/element-ui/2.15.6/css.css') }}">
|
||||||
@endif
|
@endif
|
||||||
<link href="{{ mix('build/css/admin/app.css') }}" rel="stylesheet">
|
<link href="{{ mix('beike/build/css/admin/app.css') }}" rel="stylesheet">
|
||||||
<meta name="csrf-token" content="{{ csrf_token() }}">
|
<meta name="csrf-token" content="{{ csrf_token() }}">
|
||||||
<title>beike admin</title>
|
<title>beike admin</title>
|
||||||
@stack('header')
|
@stack('header')
|
||||||
|
|
@ -26,11 +26,11 @@
|
||||||
<body class="@yield('body-class')">
|
<body class="@yield('body-class')">
|
||||||
<!-- <div style="height: 80px; background: white;"></div> -->
|
<!-- <div style="height: 80px; background: white;"></div> -->
|
||||||
|
|
||||||
<x-admin.header />
|
<x-admin-header />
|
||||||
|
|
||||||
<div class="main-content">
|
<div class="main-content">
|
||||||
<aside class="sidebar navbar-expand-xs border-radius-xl">
|
<aside class="sidebar navbar-expand-xs border-radius-xl">
|
||||||
<x-admin.sidebar />
|
<x-admin-sidebar />
|
||||||
</aside>
|
</aside>
|
||||||
<div id="content">
|
<div id="content">
|
||||||
<div class="container-fluid p-0">
|
<div class="container-fluid p-0">
|
||||||
|
|
@ -1,35 +1,45 @@
|
||||||
@extends('admin.layouts.master')
|
@extends('admin::layouts.master')
|
||||||
|
|
||||||
@section('title', '分类管理')
|
@section('title', '分类管理')
|
||||||
|
|
||||||
@section('content')
|
@section('content')
|
||||||
<div id="category-app" class="card">
|
<div id="category-app" class="card">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
所有分类
|
编辑分类
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<form action="{{ admin_route($category->id ? 'categories.update' : 'categories.store', $category) }}" method="POST">
|
<form action="{{ admin_route($category->id ? 'categories.update' : 'categories.store', $category) }}" method="POST">
|
||||||
@csrf
|
@csrf
|
||||||
@method($category->id ? 'PUT' : 'POST')
|
@method($category->id ? 'PUT' : 'POST')
|
||||||
<input type="hidden" name="_redirect" value="{{ request()->header('referer') ?? admin_route('categories.index') }}">
|
<input type="hidden" name="_redirect" value="{{ $_redirect }}">
|
||||||
|
|
||||||
@foreach (locales() as $index => $locale)
|
@foreach (locales() as $index => $locale)
|
||||||
<input type="hidden" name="descriptions[{{ $index }}][locale]" value="{{ $locale['code'] }}">
|
{{-- <input type="hidden" name="descriptions[{{ $index }}][locale]" value="{{ $locale['code'] }}"> --}}
|
||||||
@endforeach
|
@endforeach
|
||||||
|
|
||||||
@foreach (locales() as $index => $locale)
|
<x-admin-form-input-locale name="descriptions.*.name" title="名称" :value="$descriptions" required />
|
||||||
<input type="text" name="descriptions[{{ $index }}][name]" placeholder="Name {{ $locale['name'] }}" value="{{ old('descriptions.'.$index.'.name', $descriptions[$locale['code']]->name ?? '') }}">
|
<x-admin-form-input-locale name="descriptions.*.content" title="内容" :value="$descriptions" />
|
||||||
@error('descriptions.'.$index.'.name')
|
|
||||||
<x-form.error :message="$message" />
|
|
||||||
@enderror
|
|
||||||
<input type="text" name="descriptions[{{ $index }}][content]" placeholder="content {{ $locale['name'] }}" value="{{ old('descriptions.'.$index.'.content', $descriptions[$locale['code']]->content ?? '') }}">
|
|
||||||
<hr>
|
|
||||||
@endforeach
|
|
||||||
|
|
||||||
<input type="text" name="parent_id" value="{{ old('parent_id', $category->parent_id ?? 0) }}" placeholder="上级分类">
|
<x-admin::form.row title="上级分类">
|
||||||
<input type="text" name="active" value="{{ old('active', $category->active ?? 1) }}" placeholder="状态">
|
@php
|
||||||
|
$_parent_id = old('parent_id', $category->parent_id ?? 0);
|
||||||
|
@endphp
|
||||||
|
<select name="parent_id" id="" class="form-control form-control-sm short">
|
||||||
|
<option value="0">--请选择--</option>
|
||||||
|
@foreach ($categories as $_category)
|
||||||
|
<option value="{{ $_category->id }}" {{ $_parent_id == $_category->id ? 'selected' : ''}}>
|
||||||
|
{{ $_category->name }}
|
||||||
|
</option>
|
||||||
|
@endforeach
|
||||||
|
</select>
|
||||||
|
</x-admin::form.row>
|
||||||
|
|
||||||
<button type="submit" class="btn btn-primary">保存</button>
|
<x-admin-form-switch title="状态" name="active" :value="old('active', $category->active ?? 1)" />
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<button type="submit" class="btn btn-primary">保存</button>
|
||||||
|
<a href="{{ $_redirect }}" class="btn btn-danger">返回</a>
|
||||||
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
@extends('admin.layouts.master')
|
@extends('admin::layouts.master')
|
||||||
|
|
||||||
@section('title', '分类管理')
|
@section('title', '分类管理')
|
||||||
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
@extends('admin.layouts.master')
|
@extends('admin::layouts.master')
|
||||||
|
|
||||||
@section('title', '后台管理')
|
@section('title', '后台管理')
|
||||||
|
|
||||||
|
|
@ -12,4 +12,4 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@endfor
|
@endfor
|
||||||
@endsection
|
@endsection
|
||||||
|
|
@ -17,7 +17,7 @@
|
||||||
<input type="text" name="email" class="form-control" value="{{ old('email') }}" placeholder="邮箱地址">
|
<input type="text" name="email" class="form-control" value="{{ old('email') }}" placeholder="邮箱地址">
|
||||||
</div>
|
</div>
|
||||||
@error('email')
|
@error('email')
|
||||||
<x-form.error :message="$message" />
|
<x-admin::form.error :message="$message" />
|
||||||
@enderror
|
@enderror
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -29,7 +29,7 @@
|
||||||
<input type="password" name="password" class="form-control" placeholder="密码">
|
<input type="password" name="password" class="form-control" placeholder="密码">
|
||||||
</div>
|
</div>
|
||||||
@error('password')
|
@error('password')
|
||||||
<x-form.error :message="$message" />
|
<x-admin::form.error :message="$message" />
|
||||||
@enderror
|
@enderror
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -0,0 +1,269 @@
|
||||||
|
@extends('admin::layouts.master')
|
||||||
|
|
||||||
|
@push('header')
|
||||||
|
<script src="https://cdn.bootcdn.net/ajax/libs/vue/2.6.14/vue.js"></script>
|
||||||
|
@endpush
|
||||||
|
|
||||||
|
@section('content')
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-body">
|
||||||
|
<h2>product</h2>
|
||||||
|
<form action="{{ $product->id ? route('admin.products.update', $product) : route('admin.products.store') }}"
|
||||||
|
method="POST" id="app">
|
||||||
|
@csrf
|
||||||
|
@method($product->id ? 'PUT' : 'POST')
|
||||||
|
<input type="hidden" name="_redirect" value="{{ $_redirect }}"/>
|
||||||
|
|
||||||
|
@foreach (locales() as $index => $locale)
|
||||||
|
{{-- <input type="hidden" name="descriptions[{{ $index }}][locale]" value="{{ $locale['code'] }}"> --}}
|
||||||
|
@endforeach
|
||||||
|
|
||||||
|
<x-admin-form-input-locale name="descriptions.*.name" title="名称" :value="$descriptions" required/>
|
||||||
|
<x-admin-form-input name="image" title="主图" :value="old('image', $product->image ?? '')"/>
|
||||||
|
<x-admin-form-input name="video" title="视频" :value="old('video', $product->video ?? '')"/>
|
||||||
|
<x-admin-form-input name="position" title="排序" :value="old('position', $product->position ?? '')"/>
|
||||||
|
<x-admin-form-switch name="active" title="状态" :value="old('active', $product->active ?? 1)"/>
|
||||||
|
|
||||||
|
<x-admin::form.row title="分类">
|
||||||
|
<select name="category_id" id="" class="form-control form-control-sm short">
|
||||||
|
@foreach ($categories as $category)
|
||||||
|
<option value="{{ $category->id }}">{{ $category->name }}</option>
|
||||||
|
@endforeach
|
||||||
|
</select>
|
||||||
|
</x-admin::form.row>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<h2>skus</h2>
|
||||||
|
<input type="radio" v-model="editing.isVariable" :value="false"> 单规格
|
||||||
|
<input type="radio" v-model="editing.isVariable" :value="true"> 多规格
|
||||||
|
<div v-if="editing.isVariable">
|
||||||
|
<div>
|
||||||
|
<div v-for="(variant, variantIndex) in source.variables">
|
||||||
|
<div>
|
||||||
|
<input type="text" v-model="variant.name" placeholder="variant name">
|
||||||
|
|
||||||
|
<div v-for="(value, valueIndex) in variant.values">
|
||||||
|
<input v-model="variant.values[valueIndex].name" type="text"
|
||||||
|
placeholder="variant value name">
|
||||||
|
</div>
|
||||||
|
<button type="button" @click="addVariantValue(variantIndex)">Add value</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button type="button" @click="addVariant">Add variant</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="form.skus.length">
|
||||||
|
<input v-if="form.skus.length" type="hidden" name="variables"
|
||||||
|
:value="JSON.stringify(form.variables)">
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<th v-for="(variant, index) in form.variables" :key="'pv-header-'+index">
|
||||||
|
@{{ variant.name || 'No name' }}
|
||||||
|
</th>
|
||||||
|
<th>image</th>
|
||||||
|
<th>model</th>
|
||||||
|
<th>sku</th>
|
||||||
|
<th>price</th>
|
||||||
|
<th>orgin price</th>
|
||||||
|
<th>cost price</th>
|
||||||
|
<th>quantity</th>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr v-for="(sku, skuIndex) in form.skus">
|
||||||
|
<template v-for="(variantValueIndex, j) in sku.variants">
|
||||||
|
<td v-if="skuIndex % variantValueRepetitions[j] == 0"
|
||||||
|
:key="'pvv'+skuIndex+'-'+j" :rowspan="variantValueRepetitions[j]">
|
||||||
|
<span>@{{ form.variables[j].values[variantValueIndex].name || 'No name' }}</span>
|
||||||
|
</td>
|
||||||
|
</template>
|
||||||
|
<td>
|
||||||
|
<input type="text" v-model="sku.image" :name="'skus[' + skuIndex + '][image]'"
|
||||||
|
placeholder="image">
|
||||||
|
<input type="hidden" :name="'skus[' + skuIndex + '][is_default]'"
|
||||||
|
:value="skuIndex == 0 ? 1 : 0">
|
||||||
|
<input v-for="(variantValueIndex, j) in sku.variants" type="hidden"
|
||||||
|
:name="'skus[' + skuIndex + '][variants][' + j + ']'"
|
||||||
|
:value="variantValueIndex">
|
||||||
|
</td>
|
||||||
|
<td><input type="text" v-model="sku.model" :name="'skus[' + skuIndex + '][model]'"
|
||||||
|
placeholder="model"></td>
|
||||||
|
<td><input type="text" v-model="sku.sku" :name="'skus[' + skuIndex + '][sku]'"
|
||||||
|
placeholder="sku"></td>
|
||||||
|
<td><input type="text" v-model="sku.price" :name="'skus[' + skuIndex + '][price]'"
|
||||||
|
placeholder="price"></td>
|
||||||
|
<td><input type="text" v-model="sku.origin_price"
|
||||||
|
:name="'skus[' + skuIndex + '][origin_price]'"
|
||||||
|
placeholder="origin_price"></td>
|
||||||
|
<td><input type="text" v-model="sku.cost_price"
|
||||||
|
:name="'skus[' + skuIndex + '][cost_price]'" placeholder="cost_price">
|
||||||
|
</td>
|
||||||
|
<td><input type="text" v-model="sku.quantity"
|
||||||
|
:name="'skus[' + skuIndex + '][quantity]'" placeholder="quantity"></td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="!editing.isVariable">
|
||||||
|
<div>
|
||||||
|
<input type="text" name="skus[0][image]" placeholder="image"
|
||||||
|
value="{{ old('skus.0.image', $product->skus[0]->image ?? '') }}">
|
||||||
|
<input type="text" name="skus[0][model]" placeholder="model"
|
||||||
|
value="{{ old('skus.0.model', $product->skus[0]->model ?? '') }}">
|
||||||
|
<input type="text" name="skus[0][sku]" placeholder="sku"
|
||||||
|
value="{{ old('skus.0.sku', $product->skus[0]->sku ?? '') }}">
|
||||||
|
<input type="text" name="skus[0][price]" placeholder="price"
|
||||||
|
value="{{ old('skus.0.price', $product->skus[0]->price ?? '') }}">
|
||||||
|
<input type="text" name="skus[0][origin_price]" placeholder="origin_price"
|
||||||
|
value="{{ old('skus.0.origin_price', $product->skus[0]->origin_price ?? '') }}">
|
||||||
|
<input type="text" name="skus[0][cost_price]" placeholder="cost_price"
|
||||||
|
value="{{ old('skus.0.cost_price', $product->skus[0]->cost_price ?? '') }}">
|
||||||
|
<input type="text" name="skus[0][quantity]" placeholder="quantity"
|
||||||
|
value="{{ old('skus.0.quantity', $product->skus[0]->quantity ?? '') }}">
|
||||||
|
<input type="hidden" name="skus[0][variants]" placeholder="variants" value="">
|
||||||
|
<input type="hidden" name="skus[0][position]" placeholder="position" value="0">
|
||||||
|
<input type="hidden" name="skus[0][is_default]" placeholder="is_default" value="1">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button type="submit" class="btn btn-primary">Save</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endsection
|
||||||
|
|
||||||
|
@push('footer')
|
||||||
|
<script>
|
||||||
|
var app = new Vue({
|
||||||
|
el: '#app',
|
||||||
|
data: {
|
||||||
|
form: {
|
||||||
|
variables: @json($product->variables_decoded ?? []),
|
||||||
|
skus: @json($product->skus ?? []),
|
||||||
|
},
|
||||||
|
source: {
|
||||||
|
variables: @json($product->variables_decoded ?? []),
|
||||||
|
},
|
||||||
|
editing: {
|
||||||
|
isVariable: @json(($product->variables ?? null) != null),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
// variant value 重复次数
|
||||||
|
variantValueRepetitions() {
|
||||||
|
var repeats = [];
|
||||||
|
var repeat = 1;
|
||||||
|
for (var index = this.form.variables.length - 2; index >= 0; index--) {
|
||||||
|
repeat *= this.form.variables[index + 1].values.length;
|
||||||
|
repeats[index] = repeat;
|
||||||
|
}
|
||||||
|
// 最后一组只重复1次
|
||||||
|
repeats.push(1);
|
||||||
|
return repeats;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
'source.variables': {
|
||||||
|
deep: true,
|
||||||
|
handler: function () {
|
||||||
|
// 原始规格数据变动,过滤有效规格并同步至 form.variables
|
||||||
|
let variants = [];
|
||||||
|
const sourceVariants = JSON.parse(JSON.stringify(this.source.variables));
|
||||||
|
for (var i = 0; i < sourceVariants.length; i++) {
|
||||||
|
const sourceVariant = sourceVariants[i];
|
||||||
|
// 排除掉没有规格值的
|
||||||
|
if (sourceVariant.values.length > 0) {
|
||||||
|
variants.push(sourceVariant);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.form.variables = variants;
|
||||||
|
this.remakeSkus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
addVariant() {
|
||||||
|
this.source.variables.push({name: '', values: []});
|
||||||
|
},
|
||||||
|
|
||||||
|
addVariantValue(variantIndex) {
|
||||||
|
this.source.variables[variantIndex].values.push({name: '', image: ''});
|
||||||
|
},
|
||||||
|
|
||||||
|
remakeSkus() {
|
||||||
|
const combos = makeVariableIndexes();
|
||||||
|
|
||||||
|
if (combos.length < 1) {
|
||||||
|
this.form.skus = [];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 找出已存在的组合
|
||||||
|
const productVariantCombos = this.form.skus.map(v => v.variants.join()); // ['0,0,0', '0,0,1']
|
||||||
|
let skus = [];
|
||||||
|
for (var i = 0; i < combos.length; i++) {
|
||||||
|
const combo = combos[i]; // 0,0,0
|
||||||
|
const index = productVariantCombos.indexOf(combo.join());
|
||||||
|
if (index > -1) {
|
||||||
|
skus.push(this.form.skus[index]);
|
||||||
|
} else {
|
||||||
|
skus.push({
|
||||||
|
product_sku_id: 0,
|
||||||
|
position: i,
|
||||||
|
variants: combo,
|
||||||
|
image: '',
|
||||||
|
model: '',
|
||||||
|
sku: '',
|
||||||
|
price: null,
|
||||||
|
quantity: null,
|
||||||
|
is_default: i == 0,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 第一个子商品用主商品的值
|
||||||
|
skus[0].model = this.form.model;
|
||||||
|
skus[0].sku = this.form.sku;
|
||||||
|
skus[0].price = this.form.price;
|
||||||
|
skus[0].quantity = this.form.quantity;
|
||||||
|
skus[0].status = this.form.status;
|
||||||
|
|
||||||
|
this.form.skus = skus;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function makeVariableIndexes() {
|
||||||
|
// 每组值重复次数
|
||||||
|
var repeats = app.variantValueRepetitions;
|
||||||
|
var results = [];
|
||||||
|
|
||||||
|
if (app.form.variables.length < 1) {
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 0; i < repeats[0] * app.form.variables[0].values.length; i++) {
|
||||||
|
results.push([]);
|
||||||
|
}
|
||||||
|
for (let xIndex = 0; xIndex < repeats.length; xIndex++) { // 0 - 3
|
||||||
|
let repeat = 0;
|
||||||
|
let itemIndex = 0;
|
||||||
|
for (let yIndex = 0; yIndex < results.length; yIndex++) { // 0 - 36
|
||||||
|
results[yIndex].push(itemIndex);
|
||||||
|
repeat++;
|
||||||
|
if (repeat >= repeats[xIndex]) {
|
||||||
|
repeat = 0;
|
||||||
|
itemIndex++;
|
||||||
|
if (itemIndex >= app.form.variables[xIndex].values.length) {
|
||||||
|
itemIndex = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
@endpush
|
||||||
|
|
@ -0,0 +1,192 @@
|
||||||
|
@extends('admin::layouts.master')
|
||||||
|
|
||||||
|
@section('title', '商品管理')
|
||||||
|
|
||||||
|
@push('header')
|
||||||
|
<script src="https://cdn.bootcdn.net/ajax/libs/axios/0.27.2/axios.min.js"></script>
|
||||||
|
<script src="https://cdn.bootcdn.net/ajax/libs/underscore.js/1.13.3/underscore.min.js"></script>
|
||||||
|
@endpush
|
||||||
|
|
||||||
|
@section('content')
|
||||||
|
<div id="product-app">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="form-inline">
|
||||||
|
<input type="text" v-model="filter.keyword" class="form-control mr-2" placeholder="keyword">
|
||||||
|
<input type="text" v-model="filter.sku" class="form-control mr-2" placeholder="sku">
|
||||||
|
<select v-model="filter.category_id" class="form-control">
|
||||||
|
<option value="0">全部</option>
|
||||||
|
@foreach ($categories as $_category)
|
||||||
|
<option :value="{{ $_category->id }}">{{ $_category->name }}</option>
|
||||||
|
@endforeach
|
||||||
|
</select>
|
||||||
|
<select v-model="filter.active" class="form-control">
|
||||||
|
<option value="">全部</option>
|
||||||
|
<option value="1">上架</option>
|
||||||
|
<option value="0">下架</option>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<button type="button" @click="search" class="btn btn-primary">筛选</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card mt-4">
|
||||||
|
<div class="card-header">
|
||||||
|
<a href="{{ route('admin.products.create') }}" class="btn btn-primary">Create</a>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<template v-if="items.length">
|
||||||
|
<table class="table" v-loading="loading">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th></th>
|
||||||
|
<th>ID</th>
|
||||||
|
<th>图片</th>
|
||||||
|
<th>商品名称</th>
|
||||||
|
<th>价格</th>
|
||||||
|
<th>创建时间</th>
|
||||||
|
<th>上架</th>
|
||||||
|
<th>操作</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tr v-for="(item, index) in items" :key="item.id">
|
||||||
|
<td>
|
||||||
|
<input type="checkbox" :value="item.id" v-model="selected" />
|
||||||
|
</td>
|
||||||
|
<td>@{{ item.id }}</td>
|
||||||
|
<td><img :src="item.image" alt="" srcset=""></td>
|
||||||
|
<td>@{{ item.name || '无名称' }}</td>
|
||||||
|
<td>@{{ item.price_formatted }}</td>
|
||||||
|
<td>@{{ item.created_at }}</td>
|
||||||
|
<td>@{{ item.active ? '上架' : '下架' }}</td>
|
||||||
|
<td>
|
||||||
|
<a :href="item.url_edit">编辑</a>
|
||||||
|
<template>
|
||||||
|
<a v-if="item.deleted_at == ''" href="javascript:void(0)" @click.prevent="deleteProduct(index)">删除</a>
|
||||||
|
<a v-else href="javascript:void(0)" @click.prevent="restoreProduct(index)">恢复</a>
|
||||||
|
</template>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<el-pagination
|
||||||
|
layout="prev, pager, next"
|
||||||
|
background
|
||||||
|
:page-size="perPage"
|
||||||
|
:current-page.sync="page"
|
||||||
|
:total="totals"
|
||||||
|
></el-pagination>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<p v-else>无商品</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endsection
|
||||||
|
|
||||||
|
@push('footer')
|
||||||
|
<script>
|
||||||
|
new Vue({
|
||||||
|
el: '#product-app',
|
||||||
|
data: {
|
||||||
|
filter: {
|
||||||
|
keyword: @json(request('keyword') ?? ''),
|
||||||
|
category_id: @json(request('category_id') ?? ''),
|
||||||
|
sku: @json(request('sku') ?? ''),
|
||||||
|
active: @json(request('active') ?? ''),
|
||||||
|
},
|
||||||
|
items: [],
|
||||||
|
selected: [],
|
||||||
|
page: @json((int)request('page') ?? 1),
|
||||||
|
totals: 0,
|
||||||
|
perPage: @json((int)(request('per_page') ?? 1)),
|
||||||
|
loading: false,
|
||||||
|
orderBy: @json(request('order_by', 'products.id:desc')),
|
||||||
|
},
|
||||||
|
mounted: function () {
|
||||||
|
this.load();
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
url: function () {
|
||||||
|
let filter = {};
|
||||||
|
filter.per_page = this.perPage;
|
||||||
|
if (this.orderBy != 'products.id:desc') {
|
||||||
|
filter.order_by = this.orderBy;
|
||||||
|
}
|
||||||
|
if (this.page > 1) {
|
||||||
|
filter.page = this.page;
|
||||||
|
}
|
||||||
|
for (key in this.filter) {
|
||||||
|
const value = this.filter[key];
|
||||||
|
if (value !== '' && value !== null) {
|
||||||
|
filter[key] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const query = Object.keys(filter).map(key => key + '=' + filter[key]).join('&');
|
||||||
|
const url = @json(admin_route('products.index'));
|
||||||
|
if (query) {
|
||||||
|
return url + '?' + query;
|
||||||
|
}
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
page: function () {
|
||||||
|
this.load();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
load: function () {
|
||||||
|
const url = this.url;
|
||||||
|
window.history.pushState('', '', url);
|
||||||
|
this.loading = true;
|
||||||
|
axios.get(url).then(response => {
|
||||||
|
this.loading = false;
|
||||||
|
this.items = response.data.data;
|
||||||
|
this.totals = response.data.meta.total;
|
||||||
|
}).catch(error => {
|
||||||
|
// this.$message.error(error.response.data.message);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
search: function () {
|
||||||
|
this.page = 1;
|
||||||
|
this.load();
|
||||||
|
},
|
||||||
|
|
||||||
|
deleteProduct: function (index) {
|
||||||
|
const product = this.items[index];
|
||||||
|
|
||||||
|
this.$confirm('确认要删除选中的商品吗?', '删除商品', {
|
||||||
|
// confirmButtonText: '确定',
|
||||||
|
// cancelButtonText: '取消',
|
||||||
|
type: 'warning'
|
||||||
|
}).then(() => {
|
||||||
|
axios.delete('products/' + product.id).then(response => {
|
||||||
|
location.reload();
|
||||||
|
}).catch(error => {
|
||||||
|
// this.$message.error(error.response.data.message);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
restoreProduct: function (index) {
|
||||||
|
const product = this.items[index];
|
||||||
|
|
||||||
|
this.$confirm('确认要恢复选中的商品吗?', '恢复商品', {
|
||||||
|
type: 'warning'
|
||||||
|
}).then(() => {
|
||||||
|
axios.put('products/restore', {
|
||||||
|
id: product.id
|
||||||
|
}).then(response => {
|
||||||
|
location.reload();
|
||||||
|
}).catch(error => {
|
||||||
|
// this.$message.error(error.response.data.message);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
@endpush
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Support\Facades\Route;
|
||||||
|
|
||||||
|
Route::prefix('admin')
|
||||||
|
->middleware(['web'])
|
||||||
|
->name('admin.')
|
||||||
|
->group(function () {
|
||||||
|
Route::get('login', [\Beike\Admin\Http\Controllers\LoginController::class, 'show'])->name('login.show');
|
||||||
|
Route::post('login', [\Beike\Admin\Http\Controllers\LoginController::class, 'store'])->name('login.store');
|
||||||
|
|
||||||
|
Route::middleware('auth:'.\Beike\Models\AdminUser::AUTH_GUARD)
|
||||||
|
->group(function () {
|
||||||
|
Route::get('/', [\Beike\Admin\Http\Controllers\HomeController::class, 'index'])->name('home.index');
|
||||||
|
|
||||||
|
Route::Resource('categories', \Beike\Admin\Http\Controllers\CategoryController::class);
|
||||||
|
|
||||||
|
Route::put('products/restore', [\Beike\Admin\Http\Controllers\ProductController::class, 'restore']);
|
||||||
|
Route::resource('products', \Beike\Admin\Http\Controllers\ProductController::class);
|
||||||
|
|
||||||
|
Route::get('logout', [\Beike\Admin\Http\Controllers\LogoutController::class, 'index'])->name('logout.index');
|
||||||
|
|
||||||
|
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -9,58 +9,31 @@
|
||||||
* @modified 2022-05-07 15:15:25
|
* @modified 2022-05-07 15:15:25
|
||||||
*/
|
*/
|
||||||
|
|
||||||
namespace App\Services;
|
namespace Beike\Admin\Services;
|
||||||
|
|
||||||
use App\Models\Category;
|
use Beike\Models\Category;
|
||||||
use App\Models\CategoryPath;
|
use Beike\Models\CategoryPath;
|
||||||
use Illuminate\Support\Facades\DB;
|
use Illuminate\Support\Facades\DB;
|
||||||
|
|
||||||
class CategoryService
|
class CategoryService
|
||||||
{
|
{
|
||||||
public function create(array $data)
|
public function createOrUpdate(array $data, ?Category $category)
|
||||||
{
|
{
|
||||||
try {
|
$isUpdating = $category !== null;
|
||||||
DB::beginTransaction();
|
if ($category === null) {
|
||||||
|
$category = new Category();
|
||||||
$category = new \App\Models\Category();
|
|
||||||
$category->fill($data);
|
|
||||||
$category->saveOrFail();
|
|
||||||
|
|
||||||
$descriptions = [];
|
|
||||||
foreach ($data['descriptions'] as $description) {
|
|
||||||
$descriptions[] = [
|
|
||||||
'locale' => $description['locale'],
|
|
||||||
'name' => $description['name'],
|
|
||||||
'content' => $description['content'] ?? '',
|
|
||||||
'meta_title' => $description['meta_title'] ?? '',
|
|
||||||
'meta_description' => $description['meta_description'] ?? '',
|
|
||||||
'meta_keyword' => $description['meta_keyword'] ?? '',
|
|
||||||
];
|
|
||||||
}
|
|
||||||
$category->descriptions()->createMany($descriptions);
|
|
||||||
|
|
||||||
$this->createPath($category);
|
|
||||||
|
|
||||||
DB::commit();
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
DB::rollBack();
|
|
||||||
throw $e;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return $category;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function update(Category $category, array $data)
|
|
||||||
{
|
|
||||||
try {
|
try {
|
||||||
DB::beginTransaction();
|
DB::beginTransaction();
|
||||||
|
|
||||||
$category->updateOrFail($data);
|
$category->fill($data);
|
||||||
|
$category->save();
|
||||||
|
|
||||||
$descriptions = [];
|
$descriptions = [];
|
||||||
foreach ($data['descriptions'] as $description) {
|
foreach ($data['descriptions'] as $locale => $description) {
|
||||||
$descriptions[] = [
|
$descriptions[] = [
|
||||||
'locale' => $description['locale'],
|
'locale' => $locale,
|
||||||
'name' => $description['name'],
|
'name' => $description['name'],
|
||||||
'content' => $description['content'] ?? '',
|
'content' => $description['content'] ?? '',
|
||||||
'meta_title' => $description['meta_title'] ?? '',
|
'meta_title' => $description['meta_title'] ?? '',
|
||||||
|
|
@ -68,10 +41,16 @@ class CategoryService
|
||||||
'meta_keyword' => $description['meta_keyword'] ?? '',
|
'meta_keyword' => $description['meta_keyword'] ?? '',
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
$category->descriptions()->delete();
|
if ($isUpdating) {
|
||||||
|
$category->descriptions()->delete();
|
||||||
|
}
|
||||||
$category->descriptions()->createMany($descriptions);
|
$category->descriptions()->createMany($descriptions);
|
||||||
|
|
||||||
$this->updatePath($category);
|
if ($isUpdating) {
|
||||||
|
$this->updatePath($category);
|
||||||
|
} else {
|
||||||
|
$this->createPath($category);
|
||||||
|
}
|
||||||
|
|
||||||
DB::commit();
|
DB::commit();
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace App\Services;
|
namespace Beike\Admin\Services;
|
||||||
|
|
||||||
use App\Models\Product;
|
use Beike\Models\Product;
|
||||||
use App\Models\ProductDescription;
|
use Beike\Models\ProductDescription;
|
||||||
use Illuminate\Support\Facades\DB;
|
use Illuminate\Support\Facades\DB;
|
||||||
|
|
||||||
class ProductService
|
class ProductService
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace App\View\Components;
|
namespace Beike\Admin\View\Components;
|
||||||
|
|
||||||
use Illuminate\View\Component;
|
use Illuminate\View\Component;
|
||||||
|
|
||||||
|
|
@ -27,6 +27,6 @@ class Filter extends Component
|
||||||
*/
|
*/
|
||||||
public function render()
|
public function render()
|
||||||
{
|
{
|
||||||
return view('components.filter');
|
return view('Resources::components.admin.filter');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Beike\Admin\View\Components\Form;
|
||||||
|
|
||||||
|
use Illuminate\View\Component;
|
||||||
|
|
||||||
|
class Input extends Component
|
||||||
|
{
|
||||||
|
public string $name;
|
||||||
|
public string $title;
|
||||||
|
public string $value;
|
||||||
|
|
||||||
|
public function __construct(string $name, string $title, ?string $value)
|
||||||
|
{
|
||||||
|
$this->name = $name;
|
||||||
|
$this->title = $title;
|
||||||
|
$this->value = $value ?? '';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function render()
|
||||||
|
{
|
||||||
|
return view('admin::components.form.input');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,60 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Beike\Admin\View\Components\Form;
|
||||||
|
|
||||||
|
use Illuminate\Support\Arr;
|
||||||
|
use Illuminate\View\Component;
|
||||||
|
|
||||||
|
class InputLocale extends Component
|
||||||
|
{
|
||||||
|
public string $name;
|
||||||
|
public string $title;
|
||||||
|
public $value;
|
||||||
|
|
||||||
|
public function __construct(string $name, string $title, $value)
|
||||||
|
{
|
||||||
|
$this->name = $name;
|
||||||
|
$this->title = $title;
|
||||||
|
$this->value = $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function render()
|
||||||
|
{
|
||||||
|
return view('admin::components.form.input-locale');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function formatName(string $code)
|
||||||
|
{
|
||||||
|
// descriptions.*.name => descriptions[zh_cn][name]
|
||||||
|
|
||||||
|
$segments = explode('.', $this->name);
|
||||||
|
$key = $segments[0];
|
||||||
|
for ($i = 1; $i < count($segments); $i++) {
|
||||||
|
$segment = $segments[$i];
|
||||||
|
if ($segment == '*') {
|
||||||
|
$key .= '[' . $code . ']';
|
||||||
|
} else {
|
||||||
|
$key .= '[' . $segment . ']';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $key;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function formatValue($code)
|
||||||
|
{
|
||||||
|
$oldKey = str_replace('*', $code, $this->name);
|
||||||
|
|
||||||
|
// descriptions.*.name
|
||||||
|
$segments = explode('.', $this->name);
|
||||||
|
array_shift($segments);
|
||||||
|
$valueKey = implode('.', $segments);
|
||||||
|
$valueKey = str_replace('*', $code, $valueKey);
|
||||||
|
|
||||||
|
return old($oldKey, Arr::get($this->value, $valueKey, ''));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function errorKey($code)
|
||||||
|
{
|
||||||
|
return str_replace('*', $code, $this->name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Beike\Admin\View\Components\Form;
|
||||||
|
|
||||||
|
use Illuminate\View\Component;
|
||||||
|
|
||||||
|
class SwitchRadio extends Component
|
||||||
|
{
|
||||||
|
public string $name;
|
||||||
|
public string $value;
|
||||||
|
public string $title;
|
||||||
|
|
||||||
|
public function __construct(string $name, string $value, string $title)
|
||||||
|
{
|
||||||
|
$this->name = $name;
|
||||||
|
$this->title = $title;
|
||||||
|
$this->value = $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function render()
|
||||||
|
{
|
||||||
|
return view('admin::components.form.switch-radio');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace App\View\Components\Admin;
|
namespace Beike\Admin\View\Components;
|
||||||
|
|
||||||
use Illuminate\View\Component;
|
use Illuminate\View\Component;
|
||||||
|
|
||||||
|
|
@ -30,7 +30,7 @@ class Header extends Component
|
||||||
*/
|
*/
|
||||||
public function render()
|
public function render()
|
||||||
{
|
{
|
||||||
return view('components.admin.header');
|
return view('admin::components.header');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function addLink($title, $url, $active = false)
|
public function addLink($title, $url, $active = false)
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace App\View\Components\Admin;
|
namespace Beike\Admin\View\Components;
|
||||||
|
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
use Illuminate\View\Component;
|
use Illuminate\View\Component;
|
||||||
|
|
@ -34,7 +34,7 @@ class Sidebar extends Component
|
||||||
$this->addLink('回收站', admin_route('products.index', ['trashed' => 1]), 'fa fa-tachometer-alt', false);
|
$this->addLink('回收站', admin_route('products.index', ['trashed' => 1]), 'fa fa-tachometer-alt', false);
|
||||||
}
|
}
|
||||||
|
|
||||||
return view('components.admin.sidebar');
|
return view('admin::components.sidebar');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function addLink($title, $url, $icon, $active)
|
public function addLink($title, $url, $icon, $active)
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Resources.php
|
||||||
|
*
|
||||||
|
* @copyright 2022 opencart.cn - All Rights Reserved
|
||||||
|
* @link http://www.guangdawangluo.com
|
||||||
|
* @author Sam Chen <sam.chen@opencart.cn>
|
||||||
|
* @created 2022-05-09 10:32:41
|
||||||
|
* @modified 2022-05-09 10:32:41
|
||||||
|
*/
|
||||||
|
|
||||||
|
return [
|
||||||
|
//
|
||||||
|
];
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace App\Console\Commands;
|
namespace Beike\Console\Commands;
|
||||||
|
|
||||||
use App\Models\AdminUser;
|
use Beike\Models\AdminUser;
|
||||||
use Illuminate\Console\Command;
|
use Illuminate\Console\Command;
|
||||||
|
|
||||||
class MakeRootAdminUser extends Command
|
class MakeRootAdminUser extends Command
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace App\Models;
|
namespace Beike\Models;
|
||||||
|
|
||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
use Illuminate\Foundation\Auth\User as Authenticatable;
|
use Illuminate\Foundation\Auth\User as Authenticatable;
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace App\Models;
|
namespace Beike\Models;
|
||||||
|
|
||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace App\Models;
|
namespace Beike\Models;
|
||||||
|
|
||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace App\Models;
|
namespace Beike\Models;
|
||||||
|
|
||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace App\Models;
|
namespace Beike\Models;
|
||||||
|
|
||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace App\Models;
|
namespace Beike\Models;
|
||||||
|
|
||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace App\Models;
|
namespace Beike\Models;
|
||||||
|
|
||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace App\Models;
|
namespace Beike\Models;
|
||||||
|
|
||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace App\Models;
|
namespace Beike\Models;
|
||||||
|
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
|
@ -1,13 +1,12 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace App\Http\Controllers\Shop;
|
namespace Beike\Shop\Http\Controllers;
|
||||||
|
|
||||||
use App\Http\Controllers\Controller;
|
use Beike\Models\ProductSku;
|
||||||
use App\Models\ProductSku;
|
use Beike\Services\CartService;
|
||||||
use App\Services\CartService;
|
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
class CartsController extends Controller
|
class CartController extends Controller
|
||||||
{
|
{
|
||||||
public function store(Request $request)
|
public function store(Request $request)
|
||||||
{
|
{
|
||||||
|
|
@ -1,13 +1,12 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace App\Http\Controllers\Shop;
|
namespace Beike\Shop\Http\Controllers;
|
||||||
|
|
||||||
use App\Http\Controllers\Controller;
|
use Beike\Models\Category;
|
||||||
use App\Models\Category;
|
use Beike\Models\Product;
|
||||||
use App\Models\Product;
|
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
class CategoriesController extends Controller
|
class CategoryController extends Controller
|
||||||
{
|
{
|
||||||
public function show(Request $request, Category $category)
|
public function show(Request $request, Category $category)
|
||||||
{
|
{
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Beike\Shop\Http\Controllers;
|
||||||
|
|
||||||
|
class Controller extends \App\Http\Controllers\Controller
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -1,9 +1,8 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace App\Http\Controllers\Shop;
|
namespace Beike\Shop\Http\Controllers;
|
||||||
|
|
||||||
use App\Http\Controllers\Controller;
|
use Beike\Models\Category;
|
||||||
use App\Models\Category;
|
|
||||||
use Plugin\Guangda\Seller\Models\Product;
|
use Plugin\Guangda\Seller\Models\Product;
|
||||||
|
|
||||||
class HomeController extends Controller
|
class HomeController extends Controller
|
||||||
|
|
@ -1,12 +1,11 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace App\Http\Controllers\Shop;
|
namespace Beike\Shop\Http\Controllers;
|
||||||
|
|
||||||
use App\Http\Controllers\Controller;
|
use Beike\Models\Product;
|
||||||
use App\Models\Product;
|
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
class ProductsController extends Controller
|
class ProductController extends Controller
|
||||||
{
|
{
|
||||||
public function show(Request $request, Product $product)
|
public function show(Request $request, Product $product)
|
||||||
{
|
{
|
||||||
|
|
@ -0,0 +1,49 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Beike\Shop\Providers;
|
||||||
|
|
||||||
|
use Beike\Console\Commands\MakeRootAdminUser;
|
||||||
|
use Beike\Models\AdminUser;
|
||||||
|
use Beike\Models\Setting;
|
||||||
|
use Beike\Admin\View\Components\Filter;
|
||||||
|
use Beike\Admin\View\Components\Header;
|
||||||
|
use Beike\Admin\View\Components\Sidebar;
|
||||||
|
use Beike\Admin\View\Components\Form\Input;
|
||||||
|
use Beike\Admin\View\Components\Form\InputLocale;
|
||||||
|
use Beike\Admin\View\Components\Form\SwitchRadio;
|
||||||
|
use Illuminate\Support\Facades\Config;
|
||||||
|
use Illuminate\Support\ServiceProvider;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
|
||||||
|
class ShopServiceProvider extends ServiceProvider
|
||||||
|
{
|
||||||
|
public function boot()
|
||||||
|
{
|
||||||
|
$uri = request()->getRequestUri();
|
||||||
|
|
||||||
|
if (Str::startsWith($uri, '/admin')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->loadRoutesFrom(__DIR__ . '/../Routes/shop.php');
|
||||||
|
|
||||||
|
$this->mergeConfigFrom(__DIR__ . '/../../Config/beike.php', 'beike');
|
||||||
|
|
||||||
|
$this->loadSettings();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function loadSettings()
|
||||||
|
{
|
||||||
|
$settings = Setting::all(['name', 'value', 'json'])
|
||||||
|
->keyBy('name')
|
||||||
|
->transform(function ($setting) {
|
||||||
|
if ($setting->json) {
|
||||||
|
return \json_decode($setting->value, true);
|
||||||
|
}
|
||||||
|
return $setting->value;
|
||||||
|
})
|
||||||
|
->toArray();
|
||||||
|
config(['global' => $settings]);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Support\Facades\Route;
|
||||||
|
|
||||||
|
Route::prefix('/')
|
||||||
|
->name('shop.')
|
||||||
|
->middleware(['web'])
|
||||||
|
->group(function () {
|
||||||
|
Route::get('/', [Beike\Shop\Http\Controllers\HomeController::class, 'index'])->name('home.index');
|
||||||
|
|
||||||
|
Route::get('carts', [Beike\Shop\Http\Controllers\CartController::class, 'store'])->name('carts.store');
|
||||||
|
|
||||||
|
Route::get('categories/{category}', [Beike\Shop\Http\Controllers\CategoryController::class, 'show'])->name('categories.show');
|
||||||
|
|
||||||
|
Route::get('products/{product}', [Beike\Shop\Http\Controllers\ProductController::class, 'show'])->name('products.show');
|
||||||
|
});
|
||||||
|
|
@ -9,11 +9,11 @@
|
||||||
* @modified 2022-01-05 10:12:57
|
* @modified 2022-01-05 10:12:57
|
||||||
*/
|
*/
|
||||||
|
|
||||||
namespace App\Services;
|
namespace Beike\Services;
|
||||||
|
|
||||||
|
|
||||||
use App\Models\Cart;
|
use Beike\Models\Cart;
|
||||||
use App\Models\ProductSku;
|
use Beike\Models\ProductSku;
|
||||||
|
|
||||||
class CartService
|
class CartService
|
||||||
{
|
{
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
{
|
||||||
|
"private": true,
|
||||||
|
"scripts": {
|
||||||
|
"dev": "npm run development",
|
||||||
|
"development": "mix",
|
||||||
|
"watch": "mix watch",
|
||||||
|
"watch-poll": "mix watch -- --watch-options-poll=1000",
|
||||||
|
"hot": "mix watch --hot",
|
||||||
|
"prod": "npm run production",
|
||||||
|
"production": "mix --production"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"axios": "^0.21",
|
||||||
|
"bootstrap": "^4.6.1",
|
||||||
|
"laravel-mix": "^6.0.6",
|
||||||
|
"lodash": "^4.17.19",
|
||||||
|
"resolve-url-loader": "^4.0.0",
|
||||||
|
"sass": "^1.38.1",
|
||||||
|
"sass-loader": "^12.1.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
const mix = require('laravel-mix');
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Mix Asset Management
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Mix provides a clean, fluent API for defining some Webpack build steps
|
||||||
|
| for your Laravel applications. By default, we are compiling the CSS
|
||||||
|
| file for the application as well as bundling up all the JS files.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
// mix.js('resources/js/app.js', 'public/build/js')
|
||||||
|
// .postCss('resources/css/app.css', 'public/build/css', [
|
||||||
|
// //
|
||||||
|
// ]);
|
||||||
|
|
||||||
|
mix.setPublicPath('../public');
|
||||||
|
|
||||||
|
mix.sass('Resources/css/app.scss', 'Resources/build/css/shop/app.css');
|
||||||
|
mix.sass('Resources/css/bootstrap/bootstrap.scss', 'Resources/build/css/bootstrap.css');
|
||||||
|
|
||||||
|
mix.sass('Resources/css/admin/app.scss', 'Resources/build/css/admin/app.css');
|
||||||
|
|
||||||
|
if (mix.inProduction()) {
|
||||||
|
mix.version();
|
||||||
|
}
|
||||||
|
|
@ -26,10 +26,11 @@
|
||||||
"App\\": "app/",
|
"App\\": "app/",
|
||||||
"Database\\Factories\\": "database/factories/",
|
"Database\\Factories\\": "database/factories/",
|
||||||
"Database\\Seeders\\": "database/seeders/",
|
"Database\\Seeders\\": "database/seeders/",
|
||||||
"Plugin\\": "plugins/"
|
"Plugin\\": "plugins/",
|
||||||
|
"Beike\\": "beike/"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"app/Helpers.php"
|
"beike/Helpers.php"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"autoload-dev": {
|
"autoload-dev": {
|
||||||
|
|
|
||||||
|
|
@ -175,6 +175,9 @@ return [
|
||||||
App\Providers\EventServiceProvider::class,
|
App\Providers\EventServiceProvider::class,
|
||||||
App\Providers\RouteServiceProvider::class,
|
App\Providers\RouteServiceProvider::class,
|
||||||
|
|
||||||
|
\Beike\Admin\Providers\AdminServiceProvider::class,
|
||||||
|
\Beike\Shop\Providers\ShopServiceProvider::class,
|
||||||
|
|
||||||
],
|
],
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,5 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
use App\Models\AdminUser;
|
|
||||||
|
|
||||||
return [
|
return [
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
@ -42,11 +40,6 @@ return [
|
||||||
'driver' => 'session',
|
'driver' => 'session',
|
||||||
'provider' => 'users',
|
'provider' => 'users',
|
||||||
],
|
],
|
||||||
|
|
||||||
AdminUser::AUTH_GUARD => [
|
|
||||||
'driver' => 'session',
|
|
||||||
'provider' => 'admins',
|
|
||||||
],
|
|
||||||
],
|
],
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
@ -76,11 +69,6 @@ return [
|
||||||
// 'driver' => 'database',
|
// 'driver' => 'database',
|
||||||
// 'table' => 'users',
|
// 'table' => 'users',
|
||||||
// ],
|
// ],
|
||||||
|
|
||||||
'admins' => [
|
|
||||||
'driver' => 'eloquent',
|
|
||||||
'model' => AdminUser::class,
|
|
||||||
],
|
|
||||||
],
|
],
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
|
||||||
|
|
@ -1,2 +0,0 @@
|
||||||
*
|
|
||||||
!.gitignore
|
|
||||||
|
|
@ -1,250 +0,0 @@
|
||||||
@extends('admin.layouts.master')
|
|
||||||
|
|
||||||
@push('header')
|
|
||||||
<script src="https://cdn.bootcdn.net/ajax/libs/vue/2.6.14/vue.js"></script>
|
|
||||||
@endpush
|
|
||||||
|
|
||||||
@section('content')
|
|
||||||
<div class="card">
|
|
||||||
<div class="card-body">
|
|
||||||
<h2>product</h2>
|
|
||||||
<form action="{{ ($product ?? null) ? route('admin.products.update', $product) : route('admin.products.store') }}" method="POST" id="app">
|
|
||||||
@csrf
|
|
||||||
@method(($product ?? null) ? 'PUT' : 'POST')
|
|
||||||
|
|
||||||
<input type="hidden" name="_redirect" value="{{ old('_redirect', request()->header('referer')) }}" />
|
|
||||||
|
|
||||||
@php
|
|
||||||
if ($product ?? null) {
|
|
||||||
$descriptions = $product->descriptions->keyBy('locale');
|
|
||||||
} else {
|
|
||||||
$descriptions = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
@endphp
|
|
||||||
|
|
||||||
<div>
|
|
||||||
@foreach (locales() as $locale)
|
|
||||||
<input type="text" name="descriptions[{{ $locale['code'] }}][name]" placeholder="Name {{ $locale['name'] }}" value="{{ old('descriptions.'.$locale['code'].'.name', $descriptions[$locale['code']]->name ?? '') }}">
|
|
||||||
@endforeach
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<input type="text" name="image" placeholder="image" value="{{ old('image', $product->image ?? '') }}">
|
|
||||||
<input type="text" name="video" placeholder="video" value="{{ old('video', $product->video ?? '') }}">
|
|
||||||
<input type="text" name="position" placeholder="position" value="{{ old('position', $product->position ?? 0) }}">
|
|
||||||
<input type="text" name="active" placeholder="active" value="{{ old('active', $product->active ?? 1) }}">
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<h2>skus</h2>
|
|
||||||
<input type="radio" v-model="editing.isVariable" :value="false"> 单规格
|
|
||||||
<input type="radio" v-model="editing.isVariable" :value="true"> 多规格
|
|
||||||
<div v-if="editing.isVariable">
|
|
||||||
<div>
|
|
||||||
<div v-for="(variant, variantIndex) in source.variables">
|
|
||||||
<div>
|
|
||||||
<input type="text" v-model="variant.name" placeholder="variant name">
|
|
||||||
|
|
||||||
<div v-for="(value, valueIndex) in variant.values">
|
|
||||||
<input v-model="variant.values[valueIndex].name" type="text" placeholder="variant value name">
|
|
||||||
</div>
|
|
||||||
<button type="button" @click="addVariantValue(variantIndex)">Add value</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button type="button" @click="addVariant">Add variant</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-if="form.skus.length">
|
|
||||||
<input v-if="form.skus.length" type="hidden" name="variables" :value="JSON.stringify(form.variables)">
|
|
||||||
<table>
|
|
||||||
<thead>
|
|
||||||
<th v-for="(variant, index) in form.variables" :key="'pv-header-'+index">
|
|
||||||
@{{ variant.name || 'No name' }}
|
|
||||||
</th>
|
|
||||||
<th>image</th>
|
|
||||||
<th>model</th>
|
|
||||||
<th>sku</th>
|
|
||||||
<th>price</th>
|
|
||||||
<th>orgin price</th>
|
|
||||||
<th>cost price</th>
|
|
||||||
<th>quantity</th>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<tr v-for="(sku, skuIndex) in form.skus">
|
|
||||||
<template v-for="(variantValueIndex, j) in sku.variants">
|
|
||||||
<td v-if="skuIndex % variantValueRepetitions[j] == 0" :key="'pvv'+skuIndex+'-'+j" :rowspan="variantValueRepetitions[j]">
|
|
||||||
<span>@{{ form.variables[j].values[variantValueIndex].name || 'No name' }}</span>
|
|
||||||
</td>
|
|
||||||
</template>
|
|
||||||
<td>
|
|
||||||
<input type="text" v-model="sku.image" :name="'skus[' + skuIndex + '][image]'" placeholder="image">
|
|
||||||
<input type="hidden" :name="'skus[' + skuIndex + '][is_default]'" :value="skuIndex == 0 ? 1 : 0">
|
|
||||||
<input v-for="(variantValueIndex, j) in sku.variants" type="hidden" :name="'skus[' + skuIndex + '][variants][' + j + ']'" :value="variantValueIndex">
|
|
||||||
</td>
|
|
||||||
<td><input type="text" v-model="sku.model" :name="'skus[' + skuIndex + '][model]'" placeholder="model"></td>
|
|
||||||
<td><input type="text" v-model="sku.sku" :name="'skus[' + skuIndex + '][sku]'" placeholder="sku"></td>
|
|
||||||
<td><input type="text" v-model="sku.price" :name="'skus[' + skuIndex + '][price]'" placeholder="price"></td>
|
|
||||||
<td><input type="text" v-model="sku.origin_price" :name="'skus[' + skuIndex + '][origin_price]'" placeholder="origin_price"></td>
|
|
||||||
<td><input type="text" v-model="sku.cost_price" :name="'skus[' + skuIndex + '][cost_price]'" placeholder="cost_price"></td>
|
|
||||||
<td><input type="text" v-model="sku.quantity" :name="'skus[' + skuIndex + '][quantity]'" placeholder="quantity"></td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-if="!editing.isVariable">
|
|
||||||
<div>
|
|
||||||
<input type="text" name="skus[0][image]" placeholder="image" value="{{ old('skus.0.image', $product->skus[0]->image ?? '') }}">
|
|
||||||
<input type="text" name="skus[0][model]" placeholder="model" value="{{ old('skus.0.model', $product->skus[0]->model ?? '') }}">
|
|
||||||
<input type="text" name="skus[0][sku]" placeholder="sku" value="{{ old('skus.0.sku', $product->skus[0]->sku ?? '') }}">
|
|
||||||
<input type="text" name="skus[0][price]" placeholder="price" value="{{ old('skus.0.price', $product->skus[0]->price ?? '') }}">
|
|
||||||
<input type="text" name="skus[0][origin_price]" placeholder="origin_price" value="{{ old('skus.0.origin_price', $product->skus[0]->origin_price ?? '') }}">
|
|
||||||
<input type="text" name="skus[0][cost_price]" placeholder="cost_price" value="{{ old('skus.0.cost_price', $product->skus[0]->cost_price ?? '') }}">
|
|
||||||
<input type="text" name="skus[0][quantity]" placeholder="quantity" value="{{ old('skus.0.quantity', $product->skus[0]->quantity ?? '') }}">
|
|
||||||
<input type="hidden" name="skus[0][variants]" placeholder="variants" value="">
|
|
||||||
<input type="hidden" name="skus[0][position]" placeholder="position" value="0">
|
|
||||||
<input type="hidden" name="skus[0][is_default]" placeholder="is_default" value="1">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button type="submit" class="btn btn-primary">Save</button>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
@endsection
|
|
||||||
|
|
||||||
@push('footer')
|
|
||||||
<script>
|
|
||||||
var app = new Vue({
|
|
||||||
el: '#app',
|
|
||||||
data: {
|
|
||||||
form: {
|
|
||||||
variables: @json($product->variables_decoded ?? []),
|
|
||||||
skus: @json($product->skus ?? []),
|
|
||||||
},
|
|
||||||
source: {
|
|
||||||
variables: @json($product->variables_decoded ?? []),
|
|
||||||
},
|
|
||||||
editing: {
|
|
||||||
isVariable: @json(($product->variables ?? null) != null),
|
|
||||||
}
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
// variant value 重复次数
|
|
||||||
variantValueRepetitions() {
|
|
||||||
var repeats = [];
|
|
||||||
var repeat = 1;
|
|
||||||
for (var index = this.form.variables.length - 2; index >= 0; index--) {
|
|
||||||
repeat *= this.form.variables[index + 1].values.length;
|
|
||||||
repeats[index] = repeat;
|
|
||||||
}
|
|
||||||
// 最后一组只重复1次
|
|
||||||
repeats.push(1);
|
|
||||||
return repeats;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
'source.variables': {
|
|
||||||
deep: true,
|
|
||||||
handler: function () {
|
|
||||||
// 原始规格数据变动,过滤有效规格并同步至 form.variables
|
|
||||||
let variants = [];
|
|
||||||
const sourceVariants = JSON.parse(JSON.stringify(this.source.variables));
|
|
||||||
for (var i = 0; i < sourceVariants.length; i++) {
|
|
||||||
const sourceVariant = sourceVariants[i];
|
|
||||||
// 排除掉没有规格值的
|
|
||||||
if (sourceVariant.values.length > 0) {
|
|
||||||
variants.push(sourceVariant);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.form.variables = variants;
|
|
||||||
this.remakeSkus();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
addVariant() {
|
|
||||||
this.source.variables.push({name: '', values: []});
|
|
||||||
},
|
|
||||||
|
|
||||||
addVariantValue(variantIndex) {
|
|
||||||
this.source.variables[variantIndex].values.push({name: '', image: ''});
|
|
||||||
},
|
|
||||||
|
|
||||||
remakeSkus() {
|
|
||||||
const combos = makeVariableIndexes();
|
|
||||||
|
|
||||||
if (combos.length < 1) {
|
|
||||||
this.form.skus = [];
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 找出已存在的组合
|
|
||||||
const productVariantCombos = this.form.skus.map(v => v.variants.join()); // ['0,0,0', '0,0,1']
|
|
||||||
let skus = [];
|
|
||||||
for (var i = 0; i < combos.length; i++) {
|
|
||||||
const combo = combos[i]; // 0,0,0
|
|
||||||
const index = productVariantCombos.indexOf(combo.join());
|
|
||||||
if (index > -1) {
|
|
||||||
skus.push(this.form.skus[index]);
|
|
||||||
} else {
|
|
||||||
skus.push({
|
|
||||||
product_sku_id: 0,
|
|
||||||
position: i,
|
|
||||||
variants: combo,
|
|
||||||
image: '',
|
|
||||||
model: '',
|
|
||||||
sku: '',
|
|
||||||
price: null,
|
|
||||||
quantity: null,
|
|
||||||
is_default: i == 0,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// 第一个子商品用主商品的值
|
|
||||||
skus[0].model = this.form.model;
|
|
||||||
skus[0].sku = this.form.sku;
|
|
||||||
skus[0].price = this.form.price;
|
|
||||||
skus[0].quantity = this.form.quantity;
|
|
||||||
skus[0].status = this.form.status;
|
|
||||||
|
|
||||||
this.form.skus = skus;
|
|
||||||
},
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
function makeVariableIndexes() {
|
|
||||||
// 每组值重复次数
|
|
||||||
var repeats = app.variantValueRepetitions;
|
|
||||||
var results = [];
|
|
||||||
|
|
||||||
if (app.form.variables.length < 1) {
|
|
||||||
return results;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let i = 0; i < repeats[0] * app.form.variables[0].values.length; i++) {
|
|
||||||
results.push([]);
|
|
||||||
}
|
|
||||||
for (let xIndex = 0; xIndex < repeats.length; xIndex++) { // 0 - 3
|
|
||||||
let repeat = 0;
|
|
||||||
let itemIndex = 0;
|
|
||||||
for (let yIndex = 0; yIndex < results.length; yIndex++) { // 0 - 36
|
|
||||||
results[yIndex].push(itemIndex);
|
|
||||||
repeat++;
|
|
||||||
if (repeat >= repeats[xIndex]) {
|
|
||||||
repeat = 0;
|
|
||||||
itemIndex++;
|
|
||||||
if (itemIndex >= app.form.variables[xIndex].values.length) {
|
|
||||||
itemIndex = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return results;
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
@endpush
|
|
||||||
|
|
@ -1,105 +0,0 @@
|
||||||
@extends('admin.layouts.master')
|
|
||||||
|
|
||||||
@section('title', '商品管理')
|
|
||||||
|
|
||||||
@push('header')
|
|
||||||
<script src="https://cdn.bootcdn.net/ajax/libs/axios/0.27.2/axios.min.js"></script>
|
|
||||||
@endpush
|
|
||||||
|
|
||||||
@section('content')
|
|
||||||
<div class="card">
|
|
||||||
<div class="card-body">
|
|
||||||
<x-filter :url="route('admin.products.index')" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="card mt-4" id="product-app">
|
|
||||||
<div class="card-header">
|
|
||||||
<a href="{{ route('admin.products.create') }}" class="btn btn-primary">Create</a>
|
|
||||||
</div>
|
|
||||||
<div class="card-body">
|
|
||||||
<table v-if="items.length" class="table">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th></th>
|
|
||||||
<th>ID</th>
|
|
||||||
<th>图片</th>
|
|
||||||
<th>商品名称</th>
|
|
||||||
<th>价格</th>
|
|
||||||
<th>创建时间</th>
|
|
||||||
<th>上架</th>
|
|
||||||
<th>操作</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tr v-for="(item, index) in items" :key="item.id">
|
|
||||||
<td>
|
|
||||||
<input type="checkbox" :value="item.id" v-model="selected" />
|
|
||||||
</td>
|
|
||||||
<td>@{{ item.id }}</td>
|
|
||||||
<td><img :src="item.image" alt="" srcset=""></td>
|
|
||||||
<td>@{{ item.name || '无名称' }}</td>
|
|
||||||
<td>@{{ item.price_formatted }}</td>
|
|
||||||
<td>@{{ item.created_at }}</td>
|
|
||||||
<td>@{{ item.active ? '上架' : '下架' }}</td>
|
|
||||||
<td>
|
|
||||||
<a :href="item.url_edit">编辑</a>
|
|
||||||
<template>
|
|
||||||
<a v-if="item.deleted_at == ''" href="javascript:void(0)" @click.prevent="deleteProduct(index)">删除</a>
|
|
||||||
<a v-else href="javascript:void(0)" @click.prevent="restoreProduct(index)">恢复</a>
|
|
||||||
</template>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
|
|
||||||
{{ $products->links() }}
|
|
||||||
|
|
||||||
<p v-if="items.length < 1">无商品</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
@endsection
|
|
||||||
|
|
||||||
@push('footer')
|
|
||||||
<script>
|
|
||||||
new Vue({
|
|
||||||
el: '#product-app',
|
|
||||||
data: {
|
|
||||||
items: @json($products->items()),
|
|
||||||
selected: [],
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
deleteProduct: function (index) {
|
|
||||||
const product = this.items[index];
|
|
||||||
|
|
||||||
this.$confirm('确认要删除选中的商品吗?', '删除商品', {
|
|
||||||
// confirmButtonText: '确定',
|
|
||||||
// cancelButtonText: '取消',
|
|
||||||
type: 'warning'
|
|
||||||
}).then(() => {
|
|
||||||
axios.delete('products/' + product.id).then(response => {
|
|
||||||
location.reload();
|
|
||||||
}).catch(error => {
|
|
||||||
// this.$message.error(error.response.data.message);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
restoreProduct: function (index) {
|
|
||||||
const product = this.items[index];
|
|
||||||
|
|
||||||
this.$confirm('确认要恢复选中的商品吗?', '恢复商品', {
|
|
||||||
type: 'warning'
|
|
||||||
}).then(() => {
|
|
||||||
axios.put('products/restore', {
|
|
||||||
id: product.id
|
|
||||||
}).then(response => {
|
|
||||||
location.reload();
|
|
||||||
}).catch(error => {
|
|
||||||
// this.$message.error(error.response.data.message);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
@endpush
|
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
<?php
|
||||||
|
|
@ -1,24 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
use Illuminate\Support\Facades\Route;
|
|
||||||
|
|
||||||
Route::prefix('admin')
|
|
||||||
->name('admin.')
|
|
||||||
->group(function () {
|
|
||||||
Route::get('login', [\App\Http\Controllers\Admin\LoginController::class, 'show'])->name('login.show');
|
|
||||||
Route::post('login', [\App\Http\Controllers\Admin\LoginController::class, 'store'])->name('login.store');
|
|
||||||
|
|
||||||
Route::middleware('auth:'.\App\Models\AdminUser::AUTH_GUARD)
|
|
||||||
->group(function () {
|
|
||||||
Route::get('/', [\App\Http\Controllers\Admin\HomeController::class, 'index'])->name('home.index');
|
|
||||||
|
|
||||||
Route::Resource('categories', \App\Http\Controllers\Admin\CategoriesController::class);
|
|
||||||
|
|
||||||
Route::put('products/restore', [\App\Http\Controllers\Admin\ProductsController::class, 'restore']);
|
|
||||||
Route::resource('products', \App\Http\Controllers\Admin\ProductsController::class);
|
|
||||||
|
|
||||||
Route::get('logout', [\App\Http\Controllers\Admin\LogoutController::class, 'index'])->name('logout.index');
|
|
||||||
|
|
||||||
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
@ -1,15 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
use Illuminate\Support\Facades\Route;
|
|
||||||
|
|
||||||
Route::prefix('/')
|
|
||||||
->name('shop.')
|
|
||||||
->group(function () {
|
|
||||||
Route::get('/', [App\Http\Controllers\Shop\HomeController::class, 'index'])->name('home.index');
|
|
||||||
|
|
||||||
Route::get('carts', [App\Http\Controllers\Shop\CartsController::class, 'store'])->name('carts.store');
|
|
||||||
|
|
||||||
Route::get('categories/{category}', [App\Http\Controllers\Shop\CategoriesController::class, 'show'])->name('categories.show');
|
|
||||||
|
|
||||||
Route::get('products/{product}', [App\Http\Controllers\Shop\ProductsController::class, 'show'])->name('products.show');
|
|
||||||
});
|
|
||||||
Loading…
Reference in New Issue