parent
71be6d9bfd
commit
bc91c4a323
|
|
@ -14,3 +14,4 @@ yarn-error.log
|
|||
/.idea
|
||||
/.vscode
|
||||
mix-manifest.json
|
||||
package-lock.json
|
||||
|
|
|
|||
|
|
@ -0,0 +1,32 @@
|
|||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Models\AdminUser;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class MakeRootAdminUser extends Command
|
||||
{
|
||||
protected $signature = 'make:admin';
|
||||
protected $description = '生成第 1 个 root admin 账号';
|
||||
|
||||
public function handle()
|
||||
{
|
||||
$email = $this->ask('请输入登录邮箱地址');
|
||||
$password = $this->ask('请输入密码');
|
||||
|
||||
if (!$email || !$password) {
|
||||
$this->info('邮箱地址/手机号码不能为空,退出');
|
||||
return;
|
||||
}
|
||||
|
||||
$admin = AdminUser::create([
|
||||
'name' => 'John Doe',
|
||||
'email' => $email,
|
||||
'password' => bcrypt($password),
|
||||
'active' => true,
|
||||
]);
|
||||
|
||||
$this->info('账号创建成功,退出');
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,20 @@
|
|||
<?php
|
||||
|
||||
function admin_route($route, $params = []): string
|
||||
{
|
||||
return route('admin.' . $route, $params);
|
||||
}
|
||||
|
||||
function shop_route($route, $params = []): string
|
||||
{
|
||||
return route('shop.' . $route, $params);
|
||||
}
|
||||
|
||||
function thumbnail($path): string
|
||||
{
|
||||
return 'https://dummyimage.com/100.jpg';
|
||||
}
|
||||
|
||||
function locales(): array
|
||||
{
|
||||
$locales = [];
|
||||
|
|
@ -14,3 +29,9 @@ function locales(): array
|
|||
|
||||
return $locales;
|
||||
}
|
||||
|
||||
function locale(): string
|
||||
{
|
||||
return 'zh_cn';
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,67 @@
|
|||
<?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');
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Admin;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\AdminUser;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
|
||||
class LoginController extends Controller
|
||||
{
|
||||
public function show()
|
||||
{
|
||||
return view('admin.pages.login.login');
|
||||
}
|
||||
|
||||
public function store(Request $request)
|
||||
{
|
||||
$this->validate($request, [
|
||||
'email' => 'required|email',
|
||||
'password' => 'required'
|
||||
]);
|
||||
|
||||
$credentials = $request->only('email', 'password');
|
||||
|
||||
if (auth(AdminUser::AUTH_GUARD)->attempt($credentials)) {
|
||||
return redirect(admin_route('home.index'));
|
||||
}
|
||||
|
||||
return redirect()->back()->withErrors(['email' => 'Invalid credentials']);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Admin;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\AdminUser;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
|
||||
class LogoutController extends Controller
|
||||
{
|
||||
public function index(Request $request)
|
||||
{
|
||||
Auth::guard(AdminUser::AUTH_GUARD)->logout();
|
||||
return redirect(admin_route('login.show'));
|
||||
}
|
||||
}
|
||||
|
|
@ -3,29 +3,34 @@
|
|||
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;
|
||||
use Illuminate\Http\Response;
|
||||
|
||||
class ProductsController extends Controller
|
||||
{
|
||||
public function index()
|
||||
public function index(Request $request)
|
||||
{
|
||||
$products = Product::query()
|
||||
$query = Product::query()
|
||||
->with('description')
|
||||
->withCount('skus')
|
||||
->paginate();
|
||||
->withCount('skus');
|
||||
if ($request->trashed) {
|
||||
$query->onlyTrashed();
|
||||
}
|
||||
|
||||
$products = $query->paginate();
|
||||
|
||||
$data = [
|
||||
'products' => $products,
|
||||
'products' => ProductResource::collection($products),
|
||||
];
|
||||
|
||||
return view('admin.pages.products.index', $data);
|
||||
}
|
||||
|
||||
public function create()
|
||||
public function create(Request $request)
|
||||
{
|
||||
|
||||
return view('admin.pages.products.form.form');
|
||||
}
|
||||
|
||||
|
|
@ -33,7 +38,8 @@ class ProductsController extends Controller
|
|||
{
|
||||
$product = (new ProductService)->create($request->all());
|
||||
|
||||
return redirect()->route('admin.products.index')->with('success', 'product created');
|
||||
$redirect = $request->_redirect ?? route('admin.products.index');
|
||||
return redirect($redirect)->with('success', 'product created');
|
||||
}
|
||||
|
||||
public function show($id)
|
||||
|
|
@ -58,8 +64,18 @@ class ProductsController extends Controller
|
|||
return redirect()->route('admin.products.index')->with('success', 'product updated');
|
||||
}
|
||||
|
||||
public function destroy($id)
|
||||
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];
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,23 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Shop;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Category;
|
||||
use App\Models\Product;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class CategoriesController extends Controller
|
||||
{
|
||||
public function show(Request $request, Category $category)
|
||||
{
|
||||
$products = Product::query()->paginate();
|
||||
|
||||
$data = [
|
||||
'category' => $category,
|
||||
'products' => $products,
|
||||
];
|
||||
|
||||
return view('category', $data);
|
||||
}
|
||||
}
|
||||
|
|
@ -3,30 +3,19 @@
|
|||
namespace App\Http\Controllers\Shop;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Category;
|
||||
use Plugin\Guangda\Seller\Models\Product;
|
||||
|
||||
class HomeController extends Controller
|
||||
{
|
||||
public function index()
|
||||
{
|
||||
$categories = Category::with('description')->where('active', 1)->get();
|
||||
|
||||
$data = [
|
||||
'price' => 1,
|
||||
'status' => true,
|
||||
'seller_id' => 100,
|
||||
'categories' => $categories,
|
||||
];
|
||||
|
||||
$product = new Product($data);
|
||||
|
||||
$payments = [
|
||||
'\Plugin\Guangda\WeChat\WeChat',
|
||||
'\Plugin\Guangda\Alipay\Alipay',
|
||||
];
|
||||
|
||||
$data['payments'] = [];
|
||||
foreach ($payments as $payment) {
|
||||
$data['payments'][] = (new $payment)->handle();
|
||||
}
|
||||
|
||||
return view('home', $data);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ class Kernel extends HttpKernel
|
|||
\App\Http\Middleware\PreventRequestsDuringMaintenance::class,
|
||||
\Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
|
||||
\App\Http\Middleware\TrimStrings::class,
|
||||
// \Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
|
||||
\Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
|
||||
];
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -15,7 +15,8 @@ class Authenticate extends Middleware
|
|||
protected function redirectTo($request)
|
||||
{
|
||||
if (! $request->expectsJson()) {
|
||||
return route('login');
|
||||
// 后台
|
||||
return admin_route('login.show');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,37 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Requests\Admin;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class CategoryRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function authorize()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
'descriptions.*.name' => 'required|max:255',
|
||||
];
|
||||
}
|
||||
|
||||
public function attributes()
|
||||
{
|
||||
return [
|
||||
'descriptions.*.name' => '名称',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Resources\Admin;
|
||||
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
|
||||
class CategoryResource extends JsonResource
|
||||
{
|
||||
/**
|
||||
* Transform the resource into an array.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @return array|\Illuminate\Contracts\Support\Arrayable|\JsonSerializable
|
||||
*/
|
||||
public function toArray($request)
|
||||
{
|
||||
$data = [
|
||||
'id' => $this->id,
|
||||
'name' => $this->description->name ?? '',
|
||||
'parent_id' => $this->parent_id,
|
||||
'position' => $this->position,
|
||||
'active' => $this->active,
|
||||
'url_edit' => admin_route('categories.edit', $this),
|
||||
'children' => CategoryResource::collection($this->children),
|
||||
];
|
||||
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Resources\Admin;
|
||||
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
|
||||
class ProductResource extends JsonResource
|
||||
{
|
||||
/**
|
||||
* Transform the resource into an array.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @return array|\Illuminate\Contracts\Support\Arrayable|\JsonSerializable
|
||||
*/
|
||||
public function toArray($request)
|
||||
{
|
||||
$data = [
|
||||
'id' => $this->id,
|
||||
'image' => thumbnail($this->image),
|
||||
'name' => $this->description->name ?? '',
|
||||
'price_formatted' => $this->price_formatted,
|
||||
'active' => $this->active,
|
||||
'created_at' => (string)$this->created_at,
|
||||
'deleted_at' => (string)$this->deleted_at,
|
||||
'url_edit' => route('admin.products.edit', $this->id),
|
||||
];
|
||||
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Foundation\Auth\User as Authenticatable;
|
||||
|
||||
class AdminUser extends Authenticatable
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
const AUTH_GUARD = 'web_admin';
|
||||
|
||||
protected $fillable = ['name', 'email', 'password', 'active'];
|
||||
}
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class Category extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $fillable = [
|
||||
'parent_id',
|
||||
'position',
|
||||
'active',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'active' => 'boolean',
|
||||
];
|
||||
|
||||
public function children()
|
||||
{
|
||||
return $this->hasMany(Category::class, 'parent_id');
|
||||
}
|
||||
|
||||
public function descriptions()
|
||||
{
|
||||
return $this->hasMany(CategoryDescription::class);
|
||||
}
|
||||
|
||||
public function description()
|
||||
{
|
||||
return $this->hasOne(CategoryDescription::class)->where('locale', locale());
|
||||
}
|
||||
|
||||
public function paths()
|
||||
{
|
||||
return $this->hasMany(CategoryPath::class);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class CategoryDescription extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $fillable = [
|
||||
'category_id',
|
||||
'locale',
|
||||
'name',
|
||||
'content',
|
||||
'meta_title',
|
||||
'meta_description',
|
||||
'meta_keywords',
|
||||
];
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class CategoryPath extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $fillable = [
|
||||
'category_id',
|
||||
'path_id',
|
||||
'level',
|
||||
];
|
||||
}
|
||||
|
|
@ -4,16 +4,24 @@ namespace App\Models;
|
|||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
|
||||
class Product extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
use SoftDeletes;
|
||||
|
||||
protected $fillable = ['image', 'video', 'position', 'active', 'variables'];
|
||||
protected $attributes = [
|
||||
'image' => ''
|
||||
];
|
||||
protected $casts = [
|
||||
'active' => 'boolean',
|
||||
];
|
||||
|
||||
public function description()
|
||||
{
|
||||
return $this->hasOne(ProductDescription::class)->where('locale', 'zh_cn');
|
||||
return $this->hasOne(ProductDescription::class)->where('locale', locale());
|
||||
}
|
||||
|
||||
public function descriptions()
|
||||
|
|
@ -26,6 +34,11 @@ class Product extends Model
|
|||
return $this->hasMany(ProductSku::class);
|
||||
}
|
||||
|
||||
public function getPriceFormattedAttribute(): string
|
||||
{
|
||||
return '$' . $this->price;
|
||||
}
|
||||
|
||||
public function getVariablesDecodedAttribute()
|
||||
{
|
||||
return json_decode($this->variables, true);
|
||||
|
|
|
|||
|
|
@ -1,44 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Contracts\Auth\MustVerifyEmail;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Foundation\Auth\User as Authenticatable;
|
||||
use Illuminate\Notifications\Notifiable;
|
||||
use Laravel\Sanctum\HasApiTokens;
|
||||
|
||||
class User extends Authenticatable
|
||||
{
|
||||
use HasApiTokens, HasFactory, Notifiable;
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
protected $fillable = [
|
||||
'name',
|
||||
'email',
|
||||
'password',
|
||||
];
|
||||
|
||||
/**
|
||||
* The attributes that should be hidden for serialization.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $hidden = [
|
||||
'password',
|
||||
'remember_token',
|
||||
];
|
||||
|
||||
/**
|
||||
* The attributes that should be cast.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $casts = [
|
||||
'email_verified_at' => 'datetime',
|
||||
];
|
||||
}
|
||||
|
|
@ -46,7 +46,7 @@ class RouteServiceProvider extends ServiceProvider
|
|||
// Shop
|
||||
Route::middleware('web')
|
||||
->namespace($this->namespace)
|
||||
->group(base_path('routes/web.php'));
|
||||
->group(base_path('routes/web_shop.php'));
|
||||
|
||||
// Admin
|
||||
Route::middleware('web')
|
||||
|
|
|
|||
|
|
@ -0,0 +1,156 @@
|
|||
<?php
|
||||
/**
|
||||
* CategoryService.php
|
||||
*
|
||||
* @copyright 2022 opencart.cn - All Rights Reserved
|
||||
* @link http://www.guangdawangluo.com
|
||||
* @author Sam Chen <sam.chen@opencart.cn>
|
||||
* @created 2022-05-07 15:15:25
|
||||
* @modified 2022-05-07 15:15:25
|
||||
*/
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Models\Category;
|
||||
use App\Models\CategoryPath;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class CategoryService
|
||||
{
|
||||
public function create(array $data)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
return $category;
|
||||
}
|
||||
|
||||
public function update(Category $category, array $data)
|
||||
{
|
||||
try {
|
||||
DB::beginTransaction();
|
||||
|
||||
$category->updateOrFail($data);
|
||||
|
||||
$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()->delete();
|
||||
$category->descriptions()->createMany($descriptions);
|
||||
|
||||
$this->updatePath($category);
|
||||
|
||||
DB::commit();
|
||||
} catch (\Exception $e) {
|
||||
DB::rollBack();
|
||||
throw $e;
|
||||
}
|
||||
|
||||
return $category;
|
||||
}
|
||||
|
||||
public function createPath(Category $category)
|
||||
{
|
||||
// Paths
|
||||
$paths = [];
|
||||
// 复制上级分类的 paths
|
||||
$level = 0;
|
||||
$parentPaths = CategoryPath::query()->where('category_id', $category->parent_id)->orderBy('level')->get();
|
||||
foreach ($parentPaths as $path) {
|
||||
$paths[] = [
|
||||
'path_id' => $path->path_id,
|
||||
'level' => $level,
|
||||
];
|
||||
$level++;
|
||||
}
|
||||
// 自身
|
||||
$paths[] = [
|
||||
'path_id' => $category->id,
|
||||
'level' => $level,
|
||||
];
|
||||
$category->paths()->createMany($paths);
|
||||
}
|
||||
|
||||
public function updatePath(Category $category)
|
||||
{
|
||||
$categoryPaths = CategoryPath::query()
|
||||
->where('path_id', $category->id)
|
||||
->orderBy('level')
|
||||
->get();
|
||||
|
||||
// Get the nodes new parents
|
||||
$newParentPathIds = CategoryPath::query()
|
||||
->where('category_id', $category->parent_id)
|
||||
->orderBy('level')
|
||||
->pluck('path_id')
|
||||
->toArray();
|
||||
|
||||
$paths = [];
|
||||
if ($categoryPaths->count()) {
|
||||
foreach ($categoryPaths as $category_path) {
|
||||
$newPathIds = $newParentPathIds;
|
||||
|
||||
$results = CategoryPath::query()
|
||||
->where('category_id', (int)$category_path->category_id)
|
||||
->where('level', '>=', $category_path->level)
|
||||
->orderBy('level')
|
||||
->get();
|
||||
|
||||
foreach ($results as $result) {
|
||||
$newPathIds[] = $result->path_id;
|
||||
}
|
||||
|
||||
$level = 0;
|
||||
foreach ($newPathIds as $path_id) {
|
||||
$paths[] = [
|
||||
'category_id' => $category_path->category_id,
|
||||
'path_id' => $path_id,
|
||||
'level' => $level,
|
||||
'created_at' => now(),
|
||||
'updated_at' => now(),
|
||||
];
|
||||
$level++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CategoryPath::query()
|
||||
->whereIn('category_id', $categoryPaths->pluck('category_id'))
|
||||
->delete();
|
||||
CategoryPath::insert($paths);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
<?php
|
||||
|
||||
namespace App\View\Components\Admin;
|
||||
|
||||
use Illuminate\View\Component;
|
||||
|
||||
class Header extends Component
|
||||
{
|
||||
public array $links = [];
|
||||
|
||||
/**
|
||||
* Create a new component instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->addLink('管理首页', admin_route('home.index'), true);
|
||||
$this->addLink('订单管理', admin_route('home.index'));
|
||||
$this->addLink('商品管理', admin_route('products.index'));
|
||||
$this->addLink('会员管理', admin_route('home.index'));
|
||||
$this->addLink('营销管理', admin_route('home.index'));
|
||||
$this->addLink('系统设置', admin_route('home.index'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the view / contents that represent the component.
|
||||
*
|
||||
* @return \Illuminate\Contracts\View\View|\Closure|string
|
||||
*/
|
||||
public function render()
|
||||
{
|
||||
return view('components.admin.header');
|
||||
}
|
||||
|
||||
public function addLink($title, $url, $active = false)
|
||||
{
|
||||
$this->links[] = [
|
||||
'title' => $title,
|
||||
'url' => $url,
|
||||
'active' => $active
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
<?php
|
||||
|
||||
namespace App\View\Components\Admin;
|
||||
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\View\Component;
|
||||
|
||||
class Sidebar extends Component
|
||||
{
|
||||
public array $links = [];
|
||||
|
||||
/**
|
||||
* Create a new component instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the view / contents that represent the component.
|
||||
*
|
||||
* @return \Illuminate\Contracts\View\View|\Closure|string
|
||||
*/
|
||||
public function render()
|
||||
{
|
||||
$routeName = request()->route()->getName();
|
||||
|
||||
if (Str::startsWith($routeName, ['admin.products.', 'admin.categories.'])) {
|
||||
$this->addLink('商品分类', admin_route('categories.index'), 'fa fa-tachometer-alt', false);
|
||||
$this->addLink('商品列表', admin_route('products.index'), 'fa fa-tachometer-alt', false);
|
||||
$this->addLink('回收站', admin_route('products.index', ['trashed' => 1]), 'fa fa-tachometer-alt', false);
|
||||
}
|
||||
|
||||
return view('components.admin.sidebar');
|
||||
}
|
||||
|
||||
public function addLink($title, $url, $icon, $active)
|
||||
{
|
||||
$this->links[] = [
|
||||
'title' => $title,
|
||||
'url' => $url,
|
||||
'icon' => $icon,
|
||||
'active' => $active
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,7 @@
|
|||
<?php
|
||||
|
||||
use App\Models\AdminUser;
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|
|
@ -40,6 +42,11 @@ return [
|
|||
'driver' => 'session',
|
||||
'provider' => 'users',
|
||||
],
|
||||
|
||||
AdminUser::AUTH_GUARD => [
|
||||
'driver' => 'session',
|
||||
'provider' => 'admins',
|
||||
],
|
||||
],
|
||||
|
||||
/*
|
||||
|
|
@ -69,6 +76,11 @@ return [
|
|||
// 'driver' => 'database',
|
||||
// 'table' => 'users',
|
||||
// ],
|
||||
|
||||
'admins' => [
|
||||
'driver' => 'eloquent',
|
||||
'model' => AdminUser::class,
|
||||
],
|
||||
],
|
||||
|
||||
/*
|
||||
|
|
|
|||
|
|
@ -13,19 +13,56 @@ class CreateTables extends Migration
|
|||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::create('admin_users', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('name');
|
||||
$table->string('email')->unique();
|
||||
$table->string('password');
|
||||
$table->boolean('active');
|
||||
$table->timestamps();
|
||||
});
|
||||
|
||||
Schema::create('categories', function (Blueprint $table) {
|
||||
$table->id()->startingValue(100_000);
|
||||
$table->unsignedBigInteger('parent_id')->default(0);
|
||||
$table->integer('position')->default(0);
|
||||
$table->boolean('active');
|
||||
$table->timestamps();
|
||||
});
|
||||
|
||||
Schema::create('category_descriptions', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->unsignedBigInteger('category_id');
|
||||
$table->string('locale');
|
||||
$table->string('name');
|
||||
$table->text('content');
|
||||
$table->string('meta_title')->default('');
|
||||
$table->string('meta_description')->default('');
|
||||
$table->string('meta_keyword')->default('');
|
||||
$table->timestamps();
|
||||
});
|
||||
|
||||
Schema::create('category_paths', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->unsignedBigInteger('category_id');
|
||||
$table->unsignedBigInteger('path_id');
|
||||
$table->integer('level');
|
||||
$table->timestamps();
|
||||
});
|
||||
|
||||
Schema::create('products', function (Blueprint $table) {
|
||||
$table->id()->startingValue(100_000);
|
||||
$table->string('image')->default('');
|
||||
$table->string('video')->default('');
|
||||
$table->integer('position')->default(0);
|
||||
$table->boolean('active');
|
||||
$table->boolean('active')->default(0);
|
||||
$table->json('variables')->nullable();
|
||||
$table->timestamps();
|
||||
$table->softDeletes();
|
||||
});
|
||||
|
||||
Schema::create('product_descriptions', function (Blueprint $table) {
|
||||
$table->id()->startingValue(100_000);
|
||||
$table->id();
|
||||
$table->unsignedBigInteger('product_id');
|
||||
$table->string('locale');
|
||||
$table->string('name');
|
||||
|
|
@ -39,7 +76,7 @@ class CreateTables extends Migration
|
|||
Schema::create('product_skus', function (Blueprint $table) {
|
||||
$table->id()->startingValue(100_000);
|
||||
$table->unsignedBigInteger('product_id');
|
||||
$table->string('variants')->default(0);
|
||||
$table->string('variants')->nullable();
|
||||
$table->integer('position')->default(0);
|
||||
$table->string('image')->default('');
|
||||
$table->string('model')->default('');
|
||||
|
|
@ -61,6 +98,14 @@ class CreateTables extends Migration
|
|||
$table->unsignedInteger('quantity');
|
||||
$table->timestamps();
|
||||
});
|
||||
|
||||
Schema::create('settings', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('name');
|
||||
$table->string('value');
|
||||
$table->boolean('json')->default(false);
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -70,9 +115,14 @@ class CreateTables extends Migration
|
|||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::dropIfExists('admin_users');
|
||||
Schema::dropIfExists('categories');
|
||||
Schema::dropIfExists('category_descriptions');
|
||||
Schema::dropIfExists('category_paths');
|
||||
Schema::dropIfExists('products');
|
||||
Schema::dropIfExists('product_descriptions');
|
||||
Schema::dropIfExists('product_skus');
|
||||
Schema::dropIfExists('carts');
|
||||
Schema::dropIfExists('settings');
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,34 +0,0 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class CreateSettingsTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::create('settings', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('name');
|
||||
$table->string('value');
|
||||
$table->boolean('json')->default(false);
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::dropIfExists('settings');
|
||||
}
|
||||
}
|
||||
|
|
@ -11,8 +11,11 @@
|
|||
},
|
||||
"devDependencies": {
|
||||
"axios": "^0.21",
|
||||
"bootstrap": "^4.6.1",
|
||||
"laravel-mix": "^6.0.6",
|
||||
"lodash": "^4.17.19",
|
||||
"postcss": "^8.1.14"
|
||||
"resolve-url-loader": "^4.0.0",
|
||||
"sass": "^1.38.1",
|
||||
"sass-loader": "^12.1.0"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,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,20 @@
|
|||
@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-25 11:41:09
|
||||
//
|
||||
|
||||
$main_color: #fd560f;
|
||||
|
||||
@import 'global';
|
||||
@import 'vue';
|
||||
@import 'sidebar';
|
||||
@import 'header';
|
||||
@import 'bootstrap-extra';
|
||||
@import 'iconfont';
|
||||
@import 'dashboard';
|
||||
@import 'subscription';
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
body {
|
||||
background: #000;
|
||||
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.scss';
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
<div class="header-wrap">
|
||||
<div class="header-left">
|
||||
<div class="logo">
|
||||
{{-- <img src="http://dummyimage.com/200x50" class="img-fluid"> --}}
|
||||
<div class="text-center"><h5 class="mb-0">beike admin</h5></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<ul class="navbar navbar-right">
|
||||
<li class="nav-item"><a href="{{ admin_route('home.index') }}" class="nav-link">管理首页</a></li>
|
||||
<li class="nav-item"><a href="" class="nav-link">订单管理</a></li>
|
||||
<li class="nav-item active"><a href="{{ admin_route('products.index') }}" class="nav-link">商品管理</a></li>
|
||||
<li class="nav-item"><a href="" class="nav-link">会员管理</a></li>
|
||||
<li class="nav-item"><a href="" class="nav-link">营销管理</a></li>
|
||||
<li class="nav-item"><a href="" class="nav-link">系统设置</a></li>
|
||||
</ul>
|
||||
<ul class="navbar">
|
||||
<li class="nav-item"><a href="{{ admin_route('logout.index') }}" class="nav-link">退出登录</a></li>
|
||||
<li class="nav-item">
|
||||
|
||||
<a href="" class="nav-link">
|
||||
<img src="http://dummyimage.com/100x100" class="avatar img-fluid rounded-circle me-1">
|
||||
<span class="text-dark ml-2">{{ auth()->user()->name }}</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -1,16 +1,44 @@
|
|||
<!doctype html>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
|
||||
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||||
<title>Document</title>
|
||||
<script src="https://cdn.bootcdn.net/ajax/libs/vue/2.6.14/vue.js"></script>
|
||||
@stack('header')
|
||||
</head>
|
||||
<body>
|
||||
@yield('content')
|
||||
</body>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1">
|
||||
<script src="//cdn.bootcdn.net/ajax/libs/vue/2.6.14/vue.js"></script>
|
||||
{{-- <script src="{{ asset('vendor/vue/2.6.12/vue' . (config('app.debug') ? '' : '.min') . '.js') }}"></script>
|
||||
<script src="{{ asset('vendor/element-ui/2.15.6/js.js') }}"></script>
|
||||
<script src="{{ asset('vendor/jquery/3.6.0/jquery.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="{{ mix('build/js/app.js') }}"></script> --}}
|
||||
<script src="https://unpkg.com/element-ui/lib/index.js"></script>
|
||||
|
||||
@stack('footer')
|
||||
<link href="{{ mix('build/css/bootstrap.css') }}" rel="stylesheet">
|
||||
<link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css">
|
||||
@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">
|
||||
<meta name="csrf-token" content="{{ csrf_token() }}">
|
||||
<title>beike admin</title>
|
||||
@stack('header')
|
||||
{{-- <x-analytics /> --}}
|
||||
</head>
|
||||
<body class="@yield('body-class')">
|
||||
<!-- <div style="height: 80px; background: white;"></div> -->
|
||||
|
||||
<x-admin.header />
|
||||
|
||||
<div class="main-content">
|
||||
<aside class="sidebar navbar-expand-xs border-radius-xl">
|
||||
<x-admin.sidebar />
|
||||
</aside>
|
||||
<div id="content">
|
||||
<div class="container-fluid p-0">
|
||||
<div class="page-title-box"><h4 class="page-title">@yield('title')</h4></div>
|
||||
@yield('content')
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@stack('footer')
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,94 @@
|
|||
@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') }}">
|
||||
|
||||
@foreach (locales() as $index => $locale)
|
||||
<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
|
||||
|
||||
<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="状态">
|
||||
|
||||
<button type="submit" class="btn btn-primary">保存</button>
|
||||
</form>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
|
||||
@push('footer')
|
||||
<script>
|
||||
new Vue({
|
||||
el: '#category-app',
|
||||
data() {
|
||||
return {
|
||||
data: [{
|
||||
label: '一级 1',
|
||||
children: [{
|
||||
label: '二级 1-1',
|
||||
children: [{
|
||||
label: '三级 1-1-1'
|
||||
}]
|
||||
}]
|
||||
}, {
|
||||
label: '一级 2',
|
||||
children: [{
|
||||
label: '二级 2-1',
|
||||
children: [{
|
||||
label: '三级 2-1-1'
|
||||
}]
|
||||
}, {
|
||||
label: '二级 2-2',
|
||||
children: [{
|
||||
label: '三级 2-2-1'
|
||||
}]
|
||||
}]
|
||||
}, {
|
||||
label: '一级 3',
|
||||
children: [{
|
||||
label: '二级 3-1',
|
||||
children: [{
|
||||
label: '三级 3-1-1'
|
||||
}]
|
||||
}, {
|
||||
label: '二级 3-2',
|
||||
children: [{
|
||||
label: '三级 3-2-1'
|
||||
}]
|
||||
}]
|
||||
}],
|
||||
defaultProps: {
|
||||
children: 'children',
|
||||
label: 'label'
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
methods: {
|
||||
handleNodeClick(data) {
|
||||
console.log(data);
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
@endpush
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
@extends('admin.layouts.master')
|
||||
|
||||
@section('title', '分类管理')
|
||||
|
||||
@section('content')
|
||||
<div id="category-app" class="card">
|
||||
<div class="card-header">
|
||||
所有分类
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<a href="{{ admin_route('categories.create') }}" class="btn btn-primary">创建分类</a>
|
||||
<div class="mt-4" style="max-width: 800px;">
|
||||
<el-tree :data="categories" default-expand-all :expand-on-click-node="false">
|
||||
<div class="custom-tree-node" slot-scope="{ node, data }" style="flex:1;display:flex">
|
||||
<span>@{{ data.name }}</span>
|
||||
<div style="flex:1"></div>
|
||||
<span class="mr-4">@{{ data.active ? '启用' : '禁用' }}</span>
|
||||
<div>
|
||||
<a :href="data.url_edit">Edit</a>
|
||||
<a>Delete</a>
|
||||
</div>
|
||||
</div>
|
||||
</el-tree>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
|
||||
@push('footer')
|
||||
<script>
|
||||
new Vue({
|
||||
el: '#category-app',
|
||||
data: {
|
||||
categories: @json($categories),
|
||||
defaultProps: {
|
||||
children: 'children',
|
||||
label: 'name'
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
handleNodeClick(data) {
|
||||
console.log(data);
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
@endpush
|
||||
|
|
@ -1,5 +1,15 @@
|
|||
@extends('admin.layouts.master')
|
||||
|
||||
@section('title', '后台管理')
|
||||
|
||||
@section('content')
|
||||
<a href="{{ route('admin.products.index') }}">Products</a>
|
||||
{{-- <a href="{{ route('admin.products.index') }}">Products</a> --}}
|
||||
@for ($i = 0; $i < 10; $i++)
|
||||
<div class="card mb-3">
|
||||
<div class="card-header">订单统计</div>
|
||||
<div class="card-body">
|
||||
<div>11</div>
|
||||
</div>
|
||||
</div>
|
||||
@endfor
|
||||
@endsection
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Document</title>
|
||||
</head>
|
||||
<body>
|
||||
<form action="{{ admin_route('login.store') }}" method="post">
|
||||
@csrf
|
||||
|
||||
<div class="form-group">
|
||||
<div class="input-group">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text" id="email">邮箱</span>
|
||||
</div>
|
||||
<input type="text" name="email" class="form-control" value="{{ old('email') }}" placeholder="邮箱地址">
|
||||
</div>
|
||||
@error('email')
|
||||
<x-form.error :message="$message" />
|
||||
@enderror
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="input-group">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text" id="password">密码</span>
|
||||
</div>
|
||||
<input type="password" name="password" class="form-control" placeholder="密码">
|
||||
</div>
|
||||
@error('password')
|
||||
<x-form.error :message="$message" />
|
||||
@enderror
|
||||
</div>
|
||||
|
||||
@if (session('error'))
|
||||
<div class="alert alert-success">
|
||||
{{ session('error') }}
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<button type="submit" class="btn btn-primary btn-block mb-4">登录</button>
|
||||
</form>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -1,244 +1,250 @@
|
|||
@extends('admin.layouts.master')
|
||||
|
||||
@push('header')
|
||||
<script src="https://cdn.bootcdn.net/ajax/libs/vue/2.6.14/vue.js"></script>
|
||||
<script src="https://cdn.bootcdn.net/ajax/libs/vue/2.6.14/vue.js"></script>
|
||||
@endpush
|
||||
|
||||
@section('content')
|
||||
<h2>product</h2>
|
||||
<form action="{{ ($product ?? null) ? route('admin.products.update', $product) : route('admin.products.store') }}" method="POST" id="app">
|
||||
<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 = [];
|
||||
}
|
||||
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
|
||||
@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) }}">
|
||||
<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">
|
||||
<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>
|
||||
<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>
|
||||
<input type="text" v-model="variant.name" placeholder="variant name">
|
||||
|
||||
<button type="button" @click="addVariant">Add variant</button>
|
||||
<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>
|
||||
|
||||
<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>
|
||||
<button type="button" @click="addVariant">Add variant</button>
|
||||
</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 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">Save</button>
|
||||
</form>
|
||||
<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>
|
||||
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),
|
||||
}
|
||||
</script>
|
||||
},
|
||||
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,21 +1,105 @@
|
|||
@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')
|
||||
<a href="{{ route('admin.products.create') }}">Create</a>
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<x-filter :url="route('admin.products.index')" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<x-filter :url="route('admin.products.index')" />
|
||||
<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>
|
||||
|
||||
<table>
|
||||
@foreach ($products as $product)
|
||||
<tr>
|
||||
<td>{{ $product->id }}</td>
|
||||
<td>{{ $product->description->name ?? '--' }}</td>
|
||||
<td>{{ $product->variables ? '多规格' : '单规格' }}</td>
|
||||
<td>
|
||||
<a href="{{ route('admin.products.edit', $product) }}">编辑</a>
|
||||
</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</table>
|
||||
@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,25 @@
|
|||
<div class="header-wrap">
|
||||
<div class="header-left">
|
||||
<div class="logo">
|
||||
{{-- <img src="http://dummyimage.com/200x50" class="img-fluid"> --}}
|
||||
<div class="text-center"><h5 class="mb-0">beike admin</h5></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<ul class="navbar navbar-right">
|
||||
@foreach ($links as $link)
|
||||
<li class="nav-item {{ $link['active'] ? 'active' : '' }}"><a href="{{ $link['url'] }}" class="nav-link">{{ $link['title'] }}</a></li>
|
||||
@endforeach
|
||||
</ul>
|
||||
<ul class="navbar">
|
||||
<li class="nav-item"><a href="{{ admin_route('logout.index') }}" class="nav-link">退出登录</a></li>
|
||||
<li class="nav-item">
|
||||
|
||||
<a href="" class="nav-link">
|
||||
<img src="http://dummyimage.com/100x100" class="avatar img-fluid rounded-circle me-1">
|
||||
<span class="text-dark ml-2">{{ auth()->user()->name }}</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
<div class="">
|
||||
<ul class="list-unstyled navbar-nav">
|
||||
@foreach ($links as $link)
|
||||
<li class="nav-item {{ $link['active'] ? 'active' : '' }}">
|
||||
<a class="nav-link" href="{{ $link['url'] }}"><i class="iconfont"></i> {{ $link['title'] }}</a>
|
||||
</li>
|
||||
@endforeach
|
||||
</ul>
|
||||
</div>
|
||||
|
|
@ -1,6 +1,9 @@
|
|||
<div id="filter-app">
|
||||
<input type="text" v-model="keyword">
|
||||
<button type="button" @click="search">筛选</button>
|
||||
<form class="form-inline">
|
||||
<input type="text" v-model="keyword" class="form-control mr-2">
|
||||
<button type="button" @click="search" class="btn btn-primary">筛选</button>
|
||||
</form>
|
||||
|
||||
</div>
|
||||
|
||||
@push('footer')
|
||||
|
|
|
|||
|
|
@ -0,0 +1,3 @@
|
|||
<div class="form-text text-danger">
|
||||
{{ $message }}
|
||||
</div>
|
||||
|
|
@ -5,7 +5,20 @@ use Illuminate\Support\Facades\Route;
|
|||
Route::prefix('admin')
|
||||
->name('admin.')
|
||||
->group(function () {
|
||||
Route::get('/', [\App\Http\Controllers\Admin\HomeController::class, 'index'])->name('home.index');
|
||||
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::resource('products', \App\Http\Controllers\Admin\ProductsController::class);
|
||||
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');
|
||||
|
||||
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -9,5 +9,7 @@ Route::prefix('/')
|
|||
|
||||
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');
|
||||
});
|
||||
|
|
@ -1 +1,3 @@
|
|||
<h2>child</h2>
|
||||
<div>
|
||||
<a href="{{ shop_route('home.index') }}">Home</a>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,11 @@
|
|||
@extends('layout.master')
|
||||
|
||||
@section('content')
|
||||
@foreach ($products as $product)
|
||||
<a href="{{ shop_route('products.show', $product) }}">
|
||||
<img src="{{ thumbnail($product->image) }}" alt="{{ $product->description->name }}">
|
||||
{{ $product->description->name }}
|
||||
</a>
|
||||
@endforeach
|
||||
|
||||
@endsection
|
||||
|
|
@ -1,14 +1,10 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport"
|
||||
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
|
||||
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||||
<title>Document</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>welcome acme</h1>
|
||||
@include('header')
|
||||
</body>
|
||||
</html>
|
||||
@extends('layout.master')
|
||||
|
||||
@section('content')
|
||||
<h1>Acme theme - Home</h1>
|
||||
|
||||
<h2>Categories</h2>
|
||||
@foreach ($categories as $category)
|
||||
<a href="{{ shop_route('categories.show', $category) }}">{{ $category->description->name }}</a>
|
||||
@endforeach
|
||||
@endsection
|
||||
|
|
|
|||
|
|
@ -0,0 +1,17 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
|
||||
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||||
<title>Document</title>
|
||||
|
||||
@stack('header')
|
||||
</head>
|
||||
<body>
|
||||
@include('header')
|
||||
|
||||
@yield('content')
|
||||
@stack('footer')
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -1,13 +1,7 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||||
<title>Document</title>
|
||||
</head>
|
||||
<body>
|
||||
<p>{{ $product->description->name }}</p>
|
||||
@extends('layout.master')
|
||||
|
||||
@section('content')
|
||||
<p>{{ $product->description->name }}</p>
|
||||
|
||||
<h2>SKUs</h2>
|
||||
<table>
|
||||
|
|
@ -32,5 +26,4 @@
|
|||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
</body>
|
||||
</html>
|
||||
@endsection
|
||||
|
|
|
|||
|
|
@ -11,12 +11,17 @@ const mix = require('laravel-mix');
|
|||
|
|
||||
*/
|
||||
|
||||
mix.js('resources/js/app.js', 'public/build/js')
|
||||
.postCss('resources/css/app.css', 'public/build/css', [
|
||||
//
|
||||
]);
|
||||
// mix.js('resources/js/app.js', 'public/build/js')
|
||||
// .postCss('resources/css/app.css', 'public/build/css', [
|
||||
// //
|
||||
// ]);
|
||||
|
||||
mix.sass('resources/css/app.scss', 'public/build/css');
|
||||
mix.sass('resources/css/bootstrap/bootstrap.scss', 'public/build/css/bootstrap.css');
|
||||
|
||||
mix.sass('resources/css/admin/app.scss', 'public/build/css/admin');
|
||||
|
||||
|
||||
if (mix.inProduction()) {
|
||||
mix.version();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue