分拆 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
|
||||
mix-manifest.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;
|
||||
|
||||
use App\Models\Setting;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
|
||||
class AppServiceProvider extends ServiceProvider
|
||||
|
|
@ -24,15 +23,6 @@ class AppServiceProvider extends ServiceProvider
|
|||
*/
|
||||
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)
|
||||
->group(base_path('routes/api.php'));
|
||||
|
||||
// Shop
|
||||
Route::middleware('web')
|
||||
->namespace($this->namespace)
|
||||
->group(base_path('routes/web_shop.php'));
|
||||
|
||||
// Admin
|
||||
Route::middleware('web')
|
||||
->namespace($this->namespace)
|
||||
->group(base_path('routes/web_admin.php'));
|
||||
->group(base_path('routes/web.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
|
||||
|
||||
namespace App\Http\Controllers\Admin;
|
||||
namespace Beike\Admin\Http\Controllers;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\Request;
|
||||
|
|
@ -9,6 +9,6 @@ class HomeController extends Controller
|
|||
{
|
||||
public function index()
|
||||
{
|
||||
return view('admin.pages.home');
|
||||
return view('admin::pages.home');
|
||||
}
|
||||
}
|
||||
|
|
@ -1,9 +1,9 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Admin;
|
||||
namespace Beike\Admin\Http\Controllers;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\AdminUser;
|
||||
use Beike\Models\AdminUser;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
|
||||
|
|
@ -11,7 +11,7 @@ class LoginController extends Controller
|
|||
{
|
||||
public function show()
|
||||
{
|
||||
return view('admin.pages.login.login');
|
||||
return view('admin::pages.login.login');
|
||||
}
|
||||
|
||||
public function store(Request $request)
|
||||
|
|
@ -1,9 +1,9 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Admin;
|
||||
namespace Beike\Admin\Http\Controllers;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\AdminUser;
|
||||
use Beike\Models\AdminUser;
|
||||
use Illuminate\Http\Request;
|
||||
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
|
||||
|
||||
namespace App\Http\Requests\Admin;
|
||||
namespace Beike\Admin\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Resources\Admin;
|
||||
namespace Beike\Admin\Http\Resources;
|
||||
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Resources\Admin;
|
||||
namespace Beike\Admin\Http\Resources;
|
||||
|
||||
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/layer/3.5.1/layer.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 rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css">
|
||||
<link href="{{ mix('beike/build/css/bootstrap.css') }}" rel="stylesheet">
|
||||
<link href="https://cdn.bootcdn.net/ajax/libs/element-ui/2.15.8/theme-chalk/index.min.css" rel="stylesheet">
|
||||
@if (0)
|
||||
<link rel="stylesheet" href="{{ asset('vendor/element-ui/2.15.6/css.css') }}">
|
||||
@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() }}">
|
||||
<title>beike admin</title>
|
||||
@stack('header')
|
||||
|
|
@ -26,11 +26,11 @@
|
|||
<body class="@yield('body-class')">
|
||||
<!-- <div style="height: 80px; background: white;"></div> -->
|
||||
|
||||
<x-admin.header />
|
||||
<x-admin-header />
|
||||
|
||||
<div class="main-content">
|
||||
<aside class="sidebar navbar-expand-xs border-radius-xl">
|
||||
<x-admin.sidebar />
|
||||
<x-admin-sidebar />
|
||||
</aside>
|
||||
<div id="content">
|
||||
<div class="container-fluid p-0">
|
||||
|
|
@ -1,35 +1,45 @@
|
|||
@extends('admin.layouts.master')
|
||||
@extends('admin::layouts.master')
|
||||
|
||||
@section('title', '分类管理')
|
||||
|
||||
@section('content')
|
||||
<div id="category-app" class="card">
|
||||
<div class="card-header">
|
||||
所有分类
|
||||
编辑分类
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form action="{{ admin_route($category->id ? 'categories.update' : 'categories.store', $category) }}" method="POST">
|
||||
@csrf
|
||||
@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)
|
||||
<input type="hidden" name="descriptions[{{ $index }}][locale]" value="{{ $locale['code'] }}">
|
||||
{{-- <input type="hidden" name="descriptions[{{ $index }}][locale]" value="{{ $locale['code'] }}"> --}}
|
||||
@endforeach
|
||||
|
||||
@foreach (locales() as $index => $locale)
|
||||
<input type="text" name="descriptions[{{ $index }}][name]" placeholder="Name {{ $locale['name'] }}" value="{{ old('descriptions.'.$index.'.name', $descriptions[$locale['code']]->name ?? '') }}">
|
||||
@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
|
||||
<x-admin-form-input-locale name="descriptions.*.name" title="名称" :value="$descriptions" required />
|
||||
<x-admin-form-input-locale name="descriptions.*.content" title="内容" :value="$descriptions" />
|
||||
|
||||
<input type="text" name="parent_id" value="{{ old('parent_id', $category->parent_id ?? 0) }}" placeholder="上级分类">
|
||||
<input type="text" name="active" value="{{ old('active', $category->active ?? 1) }}" placeholder="状态">
|
||||
<x-admin::form.row title="上级分类">
|
||||
@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>
|
||||
|
||||
</div>
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
@extends('admin.layouts.master')
|
||||
@extends('admin::layouts.master')
|
||||
|
||||
@section('title', '分类管理')
|
||||
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
@extends('admin.layouts.master')
|
||||
@extends('admin::layouts.master')
|
||||
|
||||
@section('title', '后台管理')
|
||||
|
||||
|
|
@ -12,4 +12,4 @@
|
|||
</div>
|
||||
</div>
|
||||
@endfor
|
||||
@endsection
|
||||
@endsection
|
||||
|
|
@ -17,7 +17,7 @@
|
|||
<input type="text" name="email" class="form-control" value="{{ old('email') }}" placeholder="邮箱地址">
|
||||
</div>
|
||||
@error('email')
|
||||
<x-form.error :message="$message" />
|
||||
<x-admin::form.error :message="$message" />
|
||||
@enderror
|
||||
</div>
|
||||
|
||||
|
|
@ -29,7 +29,7 @@
|
|||
<input type="password" name="password" class="form-control" placeholder="密码">
|
||||
</div>
|
||||
@error('password')
|
||||
<x-form.error :message="$message" />
|
||||
<x-admin::form.error :message="$message" />
|
||||
@enderror
|
||||
</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
|
||||
*/
|
||||
|
||||
namespace App\Services;
|
||||
namespace Beike\Admin\Services;
|
||||
|
||||
use App\Models\Category;
|
||||
use App\Models\CategoryPath;
|
||||
use Beike\Models\Category;
|
||||
use Beike\Models\CategoryPath;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class CategoryService
|
||||
{
|
||||
public function create(array $data)
|
||||
public function createOrUpdate(array $data, ?Category $category)
|
||||
{
|
||||
try {
|
||||
DB::beginTransaction();
|
||||
|
||||
$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;
|
||||
$isUpdating = $category !== null;
|
||||
if ($category === null) {
|
||||
$category = new Category();
|
||||
}
|
||||
|
||||
return $category;
|
||||
}
|
||||
|
||||
public function update(Category $category, array $data)
|
||||
{
|
||||
try {
|
||||
DB::beginTransaction();
|
||||
|
||||
$category->updateOrFail($data);
|
||||
$category->fill($data);
|
||||
$category->save();
|
||||
|
||||
$descriptions = [];
|
||||
foreach ($data['descriptions'] as $description) {
|
||||
foreach ($data['descriptions'] as $locale => $description) {
|
||||
$descriptions[] = [
|
||||
'locale' => $description['locale'],
|
||||
'locale' => $locale,
|
||||
'name' => $description['name'],
|
||||
'content' => $description['content'] ?? '',
|
||||
'meta_title' => $description['meta_title'] ?? '',
|
||||
|
|
@ -68,10 +41,16 @@ class CategoryService
|
|||
'meta_keyword' => $description['meta_keyword'] ?? '',
|
||||
];
|
||||
}
|
||||
$category->descriptions()->delete();
|
||||
if ($isUpdating) {
|
||||
$category->descriptions()->delete();
|
||||
}
|
||||
$category->descriptions()->createMany($descriptions);
|
||||
|
||||
$this->updatePath($category);
|
||||
if ($isUpdating) {
|
||||
$this->updatePath($category);
|
||||
} else {
|
||||
$this->createPath($category);
|
||||
}
|
||||
|
||||
DB::commit();
|
||||
} catch (\Exception $e) {
|
||||
|
|
@ -1,9 +1,9 @@
|
|||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
namespace Beike\Admin\Services;
|
||||
|
||||
use App\Models\Product;
|
||||
use App\Models\ProductDescription;
|
||||
use Beike\Models\Product;
|
||||
use Beike\Models\ProductDescription;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class ProductService
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
namespace App\View\Components;
|
||||
namespace Beike\Admin\View\Components;
|
||||
|
||||
use Illuminate\View\Component;
|
||||
|
||||
|
|
@ -27,6 +27,6 @@ class Filter extends Component
|
|||
*/
|
||||
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
|
||||
|
||||
namespace App\View\Components\Admin;
|
||||
namespace Beike\Admin\View\Components;
|
||||
|
||||
use Illuminate\View\Component;
|
||||
|
||||
|
|
@ -30,7 +30,7 @@ class Header extends Component
|
|||
*/
|
||||
public function render()
|
||||
{
|
||||
return view('components.admin.header');
|
||||
return view('admin::components.header');
|
||||
}
|
||||
|
||||
public function addLink($title, $url, $active = false)
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
namespace App\View\Components\Admin;
|
||||
namespace Beike\Admin\View\Components;
|
||||
|
||||
use Illuminate\Support\Str;
|
||||
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);
|
||||
}
|
||||
|
||||
return view('components.admin.sidebar');
|
||||
return view('admin::components.sidebar');
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
namespace App\Console\Commands;
|
||||
namespace Beike\Console\Commands;
|
||||
|
||||
use App\Models\AdminUser;
|
||||
use Beike\Models\AdminUser;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class MakeRootAdminUser extends Command
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
namespace Beike\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Foundation\Auth\User as Authenticatable;
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
namespace Beike\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
namespace Beike\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
namespace Beike\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
namespace Beike\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
namespace Beike\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
namespace Beike\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
namespace Beike\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
namespace Beike\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
|
|
@ -1,13 +1,12 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Shop;
|
||||
namespace Beike\Shop\Http\Controllers;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\ProductSku;
|
||||
use App\Services\CartService;
|
||||
use Beike\Models\ProductSku;
|
||||
use Beike\Services\CartService;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class CartsController extends Controller
|
||||
class CartController extends Controller
|
||||
{
|
||||
public function store(Request $request)
|
||||
{
|
||||
|
|
@ -1,13 +1,12 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Shop;
|
||||
namespace Beike\Shop\Http\Controllers;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Category;
|
||||
use App\Models\Product;
|
||||
use Beike\Models\Category;
|
||||
use Beike\Models\Product;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class CategoriesController extends Controller
|
||||
class CategoryController extends Controller
|
||||
{
|
||||
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
|
||||
|
||||
namespace App\Http\Controllers\Shop;
|
||||
namespace Beike\Shop\Http\Controllers;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Category;
|
||||
use Beike\Models\Category;
|
||||
use Plugin\Guangda\Seller\Models\Product;
|
||||
|
||||
class HomeController extends Controller
|
||||
|
|
@ -1,12 +1,11 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Shop;
|
||||
namespace Beike\Shop\Http\Controllers;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Product;
|
||||
use Beike\Models\Product;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class ProductsController extends Controller
|
||||
class ProductController extends Controller
|
||||
{
|
||||
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
|
||||
*/
|
||||
|
||||
namespace App\Services;
|
||||
namespace Beike\Services;
|
||||
|
||||
|
||||
use App\Models\Cart;
|
||||
use App\Models\ProductSku;
|
||||
use Beike\Models\Cart;
|
||||
use Beike\Models\ProductSku;
|
||||
|
||||
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/",
|
||||
"Database\\Factories\\": "database/factories/",
|
||||
"Database\\Seeders\\": "database/seeders/",
|
||||
"Plugin\\": "plugins/"
|
||||
"Plugin\\": "plugins/",
|
||||
"Beike\\": "beike/"
|
||||
},
|
||||
"files": [
|
||||
"app/Helpers.php"
|
||||
"beike/Helpers.php"
|
||||
]
|
||||
},
|
||||
"autoload-dev": {
|
||||
|
|
|
|||
|
|
@ -175,6 +175,9 @@ return [
|
|||
App\Providers\EventServiceProvider::class,
|
||||
App\Providers\RouteServiceProvider::class,
|
||||
|
||||
\Beike\Admin\Providers\AdminServiceProvider::class,
|
||||
\Beike\Shop\Providers\ShopServiceProvider::class,
|
||||
|
||||
],
|
||||
|
||||
/*
|
||||
|
|
|
|||
|
|
@ -1,7 +1,5 @@
|
|||
<?php
|
||||
|
||||
use App\Models\AdminUser;
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|
|
@ -42,11 +40,6 @@ return [
|
|||
'driver' => 'session',
|
||||
'provider' => 'users',
|
||||
],
|
||||
|
||||
AdminUser::AUTH_GUARD => [
|
||||
'driver' => 'session',
|
||||
'provider' => 'admins',
|
||||
],
|
||||
],
|
||||
|
||||
/*
|
||||
|
|
@ -76,11 +69,6 @@ return [
|
|||
// 'driver' => 'database',
|
||||
// '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