social 后台编辑

wip

wip

wip

修改插件图标

add social controller.

add social controller.

add shop base url

wip

wip

save social setting

优化购物车加载

后台第三方登录插件

完善第三方登录按钮

fixed social setting

wip

wip

wip

wip

完善第三方登录

fixed social redirect

优化后台

修复google 登录

fixed window opener

Add 第三方登录类型

wip

wip

完善多语言

修复第三方登录前台多语言
This commit is contained in:
pushuo 2022-09-28 15:34:02 +08:00 committed by Edward Yang
parent 370364002b
commit 77951b54ee
47 changed files with 958 additions and 144 deletions

View File

@ -133,5 +133,6 @@ class AdminServiceProvider extends ServiceProvider
{
View::share('languages', languages());
View::share('admin_base_url', admin_route('home.index'));
View::share('shop_base_url', shop_route('home.index'));
}
}

View File

@ -162,6 +162,14 @@ class Plugin implements Arrayable, \ArrayAccess
return $this->enabled;
}
public function getSetting($name = '')
{
if ($name) {
return plugin_setting("{$this->code}.{$name}");
}
return plugin_setting($this->code);
}
/**
* 获取插件对应的设置字段, 并获取已存储在DB的字段值

View File

@ -29,7 +29,7 @@ class CustomerRepo
*/
public static function create($customerData)
{
$customerData['password'] = Hash::make($customerData['password']);
$customerData['password'] = Hash::make($customerData['password'] ?? '');
return Customer::query()->create($customerData);
}

View File

@ -27,7 +27,10 @@ class LoginController extends Controller
if (current_customer()) {
return redirect(shop_route('account.index'));
}
return view('account/login');
$loginData = [
'social_buttons' => hook_filter('login.social.buttons', []),
];
return view('account/login', $loginData);
}
public function store(LoginRequest $request)

View File

@ -1,39 +0,0 @@
<?php
/**
* LoginController.php
*
* @copyright 2022 beikeshop.com - All Rights Reserved
* @link https://beikeshop.com
* @author TL <mengwb@guangda.work>
* @created 2022-06-22 20:22:54
* @modified 2022-06-22 20:22:54
*/
namespace Beike\Shop\Http\Controllers;
use Beike\Models\Customer;
use Illuminate\Http\Request;
class LoginController extends Controller
{
public function index()
{
return view('login');
}
public function store(Request $request)
{
$credentials = $request->validate([
'email' => ['required', 'email'],
'password' => ['required'],
]);
if (auth(Customer::AUTH_GUARD)->attempt($credentials)) {
return redirect(route('home'));
}
return back()->withErrors([
'email' => 'The provided credentials do not match our records.',
]);
}
}

View File

@ -38,7 +38,7 @@ class PluginServiceProvider extends ServiceProvider
*/
public function boot()
{
if(!installed()) {
if (!installed()) {
return;
}
$manager = app('plugin');
@ -48,6 +48,7 @@ class PluginServiceProvider extends ServiceProvider
foreach ($plugins as $plugin) {
$pluginCode = $plugin->getDirname();
$this->bootPlugin($plugin);
$this->loadMigrations($pluginCode);
$this->loadRoutes($pluginCode);
$this->loadTranslations($pluginCode);
}
@ -78,6 +79,20 @@ class PluginServiceProvider extends ServiceProvider
}
/**
* 加载插件数据库迁移脚本
*
* @param $pluginCode
*/
private function loadMigrations($pluginCode)
{
$migrationPath = "{$this->pluginBasePath}/{$pluginCode}/Migrations";
if (is_dir($migrationPath)) {
$this->loadMigrationsFrom("{$migrationPath}");
}
}
/**
* 加载插件路由
*

View File

@ -26,9 +26,9 @@ class AccountService
* 注册用户
*
* @param array $data // ['email', 'password']
* @return Customer
* @return mixed
*/
public static function register(array $data): Customer
public static function register(array $data)
{
$data['customer_group_id'] = system_setting('base.default_customer_group_id', 0); // default_customer_group_id为默认客户组名称
$data['status'] = !system_setting('base.approve_customer'); // approve_customer为是否需要审核客户
@ -38,7 +38,7 @@ class AccountService
if ($data['email'] ?? 0) {
$data['name'] = substr($data['email'], 0, strrpos($data['email'], '@'));;
}
$data['avatar'] = '';
$data['avatar'] = $data['avatar'] ?? '';
return CustomerRepo::create($data);
}
@ -49,7 +49,8 @@ class AccountService
* @param $type
* @return void
*/
public static function sendVerifyCodeForForgotten($email, $type) {
public static function sendVerifyCodeForForgotten($email, $type)
{
$code = str_pad(mt_rand(10, 999999), 6, '0', STR_PAD_LEFT);
VerifyCodeRepo::deleteByAccount($email);
@ -68,7 +69,7 @@ class AccountService
* @param $code
* @param $account
* @param $password
* @param $type $account类型email代表$account为邮箱地址telephone代表$account为手机号码
* @param $type $account类型email代表$account为邮箱地址telephone代表$account为手机号码
* @return void
*/
public static function verifyAndChangePassword($code, $account, $password, $type = 'email')

View File

@ -17,6 +17,7 @@
"jenssegers/agent": "^2.6",
"laravel/framework": "^9.0",
"laravel/tinker": "^2.7",
"overtrue/socialite": "^4.5",
"spatie/laravel-permission": "^5.5",
"srmklive/paypal": "^3.0",
"stripe/stripe-php": "^8.8",

View File

@ -11,7 +11,7 @@
},
"devDependencies": {
"axios": "^0.21",
"bootstrap": "^4.6.1",
"bootstrap": "^5.2.1",
"bootstrap-5.1.3": "npm:bootstrap@5.1.3",
"browser-sync": "^2.27.10",
"browser-sync-webpack-plugin": "^2.3.0",

5
plugins/.gitignore vendored
View File

@ -1,5 +0,0 @@
*
!FlatShipping
!LatestProducts
!Paypal
!Stripe

View File

@ -0,0 +1,8 @@
/**
这里是插件css, 请在blade里面使用以下代码引入
<link rel="stylesheet" href="{{ asset('plugin/stripe/css/demo.css') }}">
*/
#bk-stripe-app .form-wrap {
max-width: 400px;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

View File

@ -0,0 +1,4 @@
/**
* 这里是插件js, 请在blade里面使用以下代码引入
* <script src="{{ asset('plugin/stripe/js/demo.js') }}"></script>
*/

View File

@ -4,7 +4,7 @@
"description": "按订单总额收取固定运费",
"type": "shipping",
"version": "v1.0.0",
"icon": "",
"icon": "/image/logo.png",
"author": {
"name": "成都光大网络科技有限公司",
"email": "yangjin@beikeshop.com"

View File

@ -0,0 +1,8 @@
/**
这里是插件css, 请在blade里面使用以下代码引入
<link rel="stylesheet" href="{{ asset('plugin/stripe/css/demo.css') }}">
*/
#bk-stripe-app .form-wrap {
max-width: 400px;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

View File

@ -0,0 +1,4 @@
/**
* 这里是插件js, 请在blade里面使用以下代码引入
* <script src="{{ asset('plugin/stripe/js/demo.js') }}"></script>
*/

View File

@ -4,7 +4,7 @@
"description": "首页菜单添加最新商品列表功能",
"type": "view",
"version": "v1.0.0",
"icon": "",
"icon": "/image/logo.png",
"author": {
"name": "成都光大网络科技有限公司",
"email": "yangjin@beikeshop.com"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 10 KiB

View File

@ -0,0 +1,39 @@
<?php
/**
* Bootstrap.php
*
* @copyright 2022 beikeshop.com - All Rights Reserved
* @link https://beikeshop.com
* @author Edward Yang <yangjin@guangda.work>
* @created 2022-10-12 17:33:29
* @modified 2022-10-12 17:33:29
*/
namespace Plugin\Social;
class Bootstrap
{
public function boot()
{
$this->addSocialData();
}
/**
* 增加第三方登录方式
*/
private function addSocialData()
{
add_filter('login.social.buttons', function ($buttons) {
$providers = plugin_setting('social.setting');
if (empty($providers)) {
return $buttons;
}
foreach ($providers as $provider) {
$buttons[] = view('Social::shop/social_button', ['provider' => $provider])->render();
}
return $buttons;
});
}
}

View File

@ -0,0 +1,35 @@
<?php
/**
* SocialController.php
*
* @copyright 2022 beikeshop.com - All Rights Reserved
* @link https://beikeshop.com
* @author Edward Yang <yangjin@guangda.work>
* @created 2022-09-30 18:46:38
* @modified 2022-09-30 18:46:38
*/
namespace Plugin\Social\Controllers;
use Illuminate\Http\Request;
use Beike\Repositories\SettingRepo;
use Beike\Admin\Http\Controllers\Controller;
class AdminSocialController extends Controller
{
/**
* @throws \Throwable
*/
public function saveSetting(Request $request): array
{
$socialData = [
'type' => 'plugin',
'space' => 'social',
'name' => 'setting',
'value' => json_encode($request->all()),
'json' => 1,
];
SettingRepo::createOrUpdate($socialData);
return json_success('保存成功');
}
}

View File

@ -0,0 +1,73 @@
<?php
/**
* SocialController.php
*
* @copyright 2022 beikeshop.com - All Rights Reserved
* @link https://beikeshop.com
* @author Edward Yang <yangjin@guangda.work>
* @created 2022-09-30 18:46:38
* @modified 2022-09-30 18:46:38
*/
namespace Plugin\Social\Controllers;
use Beike\Models\Customer;
use Illuminate\Support\Facades\Auth;
use Overtrue\Socialite\Exceptions\Exception;
use Overtrue\Socialite\SocialiteManager;
use Beike\Admin\Http\Controllers\Controller;
use Plugin\Social\Repositories\CustomerRepo;
class ShopSocialController extends Controller
{
private SocialiteManager $socialite;
public function __construct()
{
$config = [];
$providerSettings = plugin_setting('social.setting');
foreach ($providerSettings as $providerSetting) {
$provider = $providerSetting['provider'];
if (empty($provider)) {
continue;
}
$config[$provider] = [
'client_id' => $providerSetting['key'],
'client_secret' => $providerSetting['secret'],
'redirect' => plugin_route('social.callback', $provider),
];
}
$this->socialite = new SocialiteManager($config);
}
/**
* @param $provider
* @return mixed
*/
public function redirect($provider)
{
$url = $this->socialite->create($provider)->redirect();
return redirect($url);
}
/**
* @param $provider
* @return mixed
* @throws Exception
*/
public function callback($provider)
{
$code = request('code');
$driver = $this->socialite->create($provider);
// For google, facebook, twitter in China.
$driver->setGuzzleOptions([
'proxy' => '127.0.0.1:7890'
]);
$userData = $driver->userFromCode($code);
$customer = CustomerRepo::createCustomer($provider, $userData);
Auth::guard(Customer::AUTH_GUARD)->login($customer);
return view('Social::shop/callback');
}
}

View File

@ -0,0 +1,35 @@
<?php
/**
* providers.php
*
* @copyright 2022 beikeshop.com - All Rights Reserved
* @link https://beikeshop.com
* @author Edward Yang <yangjin@guangda.work>
* @created 2022-10-13 11:30:33
* @modified 2022-10-13 11:30:33
*/
return [
'alipay' => 'Alipay',
'azure' => 'Azure',
'dingtalk' => 'Dingtalk',
'douyin' => 'Douyin',
'douban' => 'Douban',
'facebook' => 'Facebook',
'feishu' => 'Feishu',
'figma' => 'Figma',
'github' => 'Github',
'gitee' => 'Gitee',
'google' => 'Google',
'line' => 'Line',
'linkedin' => 'Linkedin',
'open-wework' => 'Open-wework',
'outlook' => 'Outlook',
'qcloud' => 'Qcloud',
'qq' => 'QQ',
'taobao' => 'Taobao',
'tapd' => 'Tapd',
'wechat' => 'Wechat',
'wework' => 'Wework',
'weibo' => 'Weibo',
];

View File

@ -0,0 +1,58 @@
<?php
/**
* setting.php
*
* @copyright 2022 beikeshop.com - All Rights Reserved
* @link https://beikeshop.com
* @author Edward Yang <yangjin@guangda.work>
* @created 2022-08-11 15:26:18
* @modified 2022-08-11 15:26:18
*/
return [
// Text
'text_module' => 'Modules',
'text_success' => 'Success: You have modified module OpenCart OmniAuth!',
'text_copyright' => 'OpenCart.cn <a href="http://www.opencart.cn" target="_blank">OmniAuth</a> &copy; %s',
'text_omni_explain' => 'This plugin supports WeChat scan code, WeChat Official, QQ, Weibo, Facebook, Google, Twitter and others Social Network',
'text_omni_explain_2' => 'To use a third-party login, you need to apply to the corresponding platform, and fill in the obtained ID and key to the corresponding input box.',
'text_wechat_title' => 'WeChat scan code login application address',
'text_wechat_info' => 'WeChat open platform',
'text_qq_title' => 'QQ login application address',
'text_qq_info' => 'QQ interconnection',
'text_weibo_title' => 'Weibo login application address',
'text_weibo_info' => 'Weibo open platform',
'text_facebook_title' => 'Facebook login application address',
'text_google_title' => 'Google login application address',
'text_Twitter_title' => 'Twitter login application address',
'text_help_msg' => 'help information',
// Entry
'entry_status' => 'Status',
'entry_bind' => 'Force Bind',
'entry_debug' => 'Debug Mode',
'entry_provider' => 'Provider',
'entry_key' => 'Client ID',
'entry_secret' => 'Client Secret',
'entry_scope' => 'Flags (optional)',
'entry_callback' => 'Callback URL',
'entry_sort_order' => 'Sort Order',
// Button
'button_add_row' => 'Add Provider',
// Error
'error_permission' => 'Warning: You do not have permission to modify module OpenCart OmniAuth!',
// Providers
'wechat' => 'WeChat',
'wechatofficial' => 'WeChatOfficial',
'qq' => 'QQ',
'weibo' => 'Weibo',
'facebook' => 'Facebook',
'google' => 'Google',
'twitter' => 'Twitter',
'instagram' => 'Instagram',
];

View File

@ -0,0 +1,35 @@
<?php
/**
* providers.php
*
* @copyright 2022 beikeshop.com - All Rights Reserved
* @link https://beikeshop.com
* @author Edward Yang <yangjin@guangda.work>
* @created 2022-10-13 11:30:33
* @modified 2022-10-13 11:30:33
*/
return [
'alipay' => '支付宝',
'azure' => 'Azure',
'dingtalk' => '钉钉',
'douyin' => '抖音',
'douban' => '豆瓣',
'facebook' => 'Facebook',
'feishu' => '飞书',
'figma' => 'Figma',
'github' => 'GitHub',
'gitee' => 'Gitee',
'google' => 'Google',
'line' => 'Line',
'linkedin' => 'Linkedin',
'open-wework' => 'open-wework',
'outlook' => 'Outlook',
'qcloud' => '腾讯云',
'qq' => 'QQ',
'taobao' => '淘宝',
'tapd' => 'tapd',
'wechat' => '微信',
'wework' => '企业微信',
'weibo' => '微博',
];

View File

@ -0,0 +1,47 @@
<?php
/**
* setting.php
*
* @copyright 2022 beikeshop.com - All Rights Reserved
* @link https://beikeshop.com
* @author Edward Yang <yangjin@guangda.work>
* @created 2022-08-11 15:26:18
* @modified 2022-08-11 15:26:18
*/
return [
// Text
'text_module' => '模块',
'text_success' => '成功: 您成功修改第三方登录配置!',
'text_copyright' => 'OpenCart.cn <a href="http://www.opencart.cn" target="_blank">获取帮助</a> &copy; %s',
'text_omni_explain' => '本模块支持微信扫码, 微信公众号, QQ, 微博FacebookGoogleTwitter等第三方登录',
'text_omni_explain_2' => '要使用第三方登录, 需要到对应平台申请开通, 并把获取到的 ID 和密钥填写到上面对应的输入框',
'text_wechat_title' => '微信扫码登录申请地址',
'text_wechat_info' => '微信开放平台',
'text_qq_title' => 'QQ登录申请地址',
'text_qq_info' => 'QQ互联',
'text_weibo_title' => '微博登录申请地址',
'text_weibo_info' => '微博开放平台',
'text_facebook_title' => 'Facebook登录申请地址',
'text_google_title' => 'Google登录申请地址',
'text_Twitter_title' => 'Twitter登录申请地址',
'text_help_msg' => '帮助信息',
// Entry
'entry_status' => '状态',
'entry_bind' => '强制绑定',
'entry_debug' => '调试模式',
'entry_provider' => '类型',
'entry_key' => 'Client ID',
'entry_secret' => 'Client Secret',
'entry_scope' => 'Flags (可选项)',
'entry_callback' => '回调地址',
'entry_sort_order' => '排序',
// Button
'button_add_row' => '添加类型',
// Error
'error_permission' => '警告: 您没有权限修改此配置!',
];

View File

@ -0,0 +1,37 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('customer_socials', function (Blueprint $table) {
$table->id();
$table->integer('customer_id');
$table->string('provider');
$table->string('user_id');
$table->string('union_id');
$table->string('access_token');
$table->text('extra');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('customer_socials');
}
};

View File

@ -0,0 +1,30 @@
<?php
/**
* CustomerSocial.php
*
* @copyright 2022 beikeshop.com - All Rights Reserved
* @link https://beikeshop.com
* @author Edward Yang <yangjin@guangda.work>
* @created 2022-10-13 10:35:44
* @modified 2022-10-13 10:35:44
*/
namespace Plugin\Social\Models;
use Beike\Models\Customer;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class CustomerSocial extends Model
{
public $table = 'customer_socials';
public $fillable = [
'customer_id', 'provider', 'user_id', 'union_id', 'access_token', 'extra'
];
public function customer(): BelongsTo
{
return $this->belongsTo(Customer::class);
}
}

View File

@ -0,0 +1,138 @@
<?php
/**
* CustomerRepo.php
*
* @copyright 2022 beikeshop.com - All Rights Reserved
* @link https://beikeshop.com
* @author Edward Yang <yangjin@guangda.work>
* @created 2022-10-13 09:57:13
* @modified 2022-10-13 09:57:13
*/
namespace Plugin\Social\Repositories;
use Beike\Models\Customer;
use Overtrue\Socialite\User;
use Overtrue\Socialite\Providers;
use Beike\Shop\Services\AccountService;
use Illuminate\Database\Eloquent\Model;
use Plugin\Social\Models\CustomerSocial;
use Illuminate\Database\Eloquent\Builder;
use Overtrue\Socialite\Exceptions\Exception;
class CustomerRepo
{
/**
* 允许的第三方服务
*/
private const PROVIDERS = [
Providers\Alipay::NAME,
Providers\Azure::NAME,
Providers\DingTalk::NAME,
Providers\DouYin::NAME,
Providers\Douban::NAME,
Providers\Facebook::NAME,
Providers\FeiShu::NAME,
Providers\Figma::NAME,
Providers\GitHub::NAME,
Providers\Gitee::NAME,
Providers\Google::NAME,
Providers\Line::NAME,
Providers\Linkedin::NAME,
Providers\OpenWeWork::NAME,
Providers\Outlook::NAME,
Providers\QCloud::NAME,
Providers\QQ::NAME,
Providers\Taobao::NAME,
Providers\Tapd::NAME,
Providers\WeChat::NAME,
Providers\WeWork::NAME,
Providers\Weibo::NAME,
];
public static function allProviders(): array
{
$items = [];
foreach (self::PROVIDERS as $provider) {
$items[] = [
'code' => $provider,
'label' => trans("Social::providers.{$provider}")
];
}
return $items;
}
/**
* 创建客户, 关联第三方用户数据
*
* @param $provider
* @param User $userData
* @return Customer
* @throws Exception
*/
public static function createCustomer($provider, User $userData): Customer
{
$social = self::getCustomerByProvider($provider, $userData->getId());
$customer = $social->customer ?? null;
if ($customer) {
return $customer;
}
$email = $userData->getEmail();
$customer = Customer::query()->where('email', $email)->first();
if (empty($customer)) {
$customerData = [
'from' => $provider,
'email' => $userData->getEmail(),
'name' => $userData->getNickname(),
'avatar' => $userData->getAvatar(),
];
$customer = AccountService::register($customerData);
}
self::createSocial($customer, $provider, $userData);
return $customer;
}
/**
* @param $customer
* @param $provider
* @param User $userData
* @return Model|Builder
* @throws Exception
*/
public static function createSocial($customer, $provider, User $userData): Model|Builder
{
$social = self::getCustomerByProvider($provider, $userData->getId());
if ($social) {
return $social;
}
$socialData = [
'customer_id' => $customer->id,
'provider' => $provider,
'user_id' => $userData->getId(),
'union_id' => '',
'access_token' => $userData->getAccessToken(),
'extra' => $userData->toJSON()
];
return CustomerSocial::query()->create($socialData);
}
/**
* 通过 provider user_id 获取已存在 social
* @param $provider
* @param $userId
* @return Model|Builder|null
*/
private static function getCustomerByProvider($provider, $userId): Model|Builder|null
{
return CustomerSocial::query()
->with(['customer'])
->where('provider', $provider)
->where('user_id', $userId)
->first();
}
}

View File

@ -0,0 +1,15 @@
<?php
/**
* admin.php
*
* @copyright 2022 beikeshop.com - All Rights Reserved
* @link https://beikeshop.com
* @author Edward Yang <yangjin@guangda.work>
* @created 2022-08-04 16:17:53
* @modified 2022-08-04 16:17:53
*/
use Illuminate\Support\Facades\Route;
use Plugin\Social\Controllers\AdminSocialController;
Route::post('/social/setting', [AdminSocialController::class, 'saveSetting'])->name('plugin.social.setting');

View File

@ -0,0 +1,16 @@
<?php
/**
* shop.php
*
* @copyright 2022 beikeshop.com - All Rights Reserved
* @link https://beikeshop.com
* @author Edward Yang <yangjin@guangda.work>
* @created 2022-09-30 18:52:54
* @modified 2022-09-30 18:52:54
*/
use Illuminate\Support\Facades\Route;
use Plugin\Social\Controllers\ShopSocialController;
Route::get('/social/redirect/{provider}', [ShopSocialController::class, 'redirect'])->name('plugin.social.redirect');
Route::get('/social/callbacks/{provider}', [ShopSocialController::class, 'callback'])->name('plugin.social.callback');

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

View File

@ -2,24 +2,181 @@
@section('title', __('admin/plugin.plugins_show'))
@section('page-title-right')
<button type="button" class="btn btn-primary save-btn" onclick="app.submit('form')">{{ __('common.save') }}</button>
@endsection
@section('content')
<div class="card">
<div class="card mb-4" id="app">
<div class="card-body">
<h6 class="border-bottom pb-3 mb-4">{{ $plugin->name }}</h6>
<div class="d-flex justify-content-between align-items-center border-bottom pb-3 mb-4">
<h6 class="mb-0">{{ $plugin->name }}</h6>
<button type="button" @click="addRow()" class="btn btn-sm btn-outline-primary">{{ __('common.add') }}</button>
</div>
@if (session('success'))
<x-admin-alert type="success" msg="{{ session('success') }}" class="mt-4"/>
@endif
<form class="needs-validation" novalidate action="{{ admin_route('plugins.update', [$plugin->code]) }}" method="POST">
@csrf
{{ method_field('put') }}
这里是social配置模板
<x-admin::form.row title="">
<button type="submit" class="btn btn-primary btn-lg mt-4">{{ __('common.submit') }}</button>
</x-admin::form.row>
</form>
<el-form ref="form" :model="form" class="form-wrap" :inline-message="true">
<table class="table table-bordered">
<thead>
<tr>
<th style="width: 150px">{{ __('Social::setting.entry_provider') }}</th>
<th>{{ __('Social::setting.entry_status') }}</th>
<th>{{ __('Social::setting.entry_key') }}</th>
<th>{{ __('Social::setting.entry_secret') }}</th>
<th>{{ __('Social::setting.entry_callback') }}</th>
<th style="width: 100px">{{ __('Social::setting.entry_sort_order') }}</th>
<th class="text-end"></th>
</tr>
</thead>
<tbody v-if="form.social.length">
<tr v-for="(item, index) in form.social" :key="index">
<td>
<el-form-item prop="provider" class="mb-0">
<el-select size="small" v-model="item.provider" @change="(e) => {providerChange(e, index)}" placeholder="{{ __('Social::setting.provider') }}">
<el-option
v-for="item in source.providers"
:key="item.code"
:label="item.label"
:value="item.code">
</el-option>
</el-select>
</el-form-item>
</td>
<td>
<el-form-item label="" prop="entry_status" class="mb-0">
<el-switch v-model="item.status" :active-value="1" :inactive-value="0"></el-switch>
</el-form-item>
</td>
<td>
<el-form-item
label="" :prop="`social[${index}].key`" class="mb-0"
:rules="[
{ required: true, message: '{{ __('common.error_required', ['name' => __('Social::setting.entry_key')]) }}', trigger: ['blur', 'change'] },]"
>
<el-input size="small" v-model="item.key" placeholder="{{ __('Social::setting.entry_key') }}"></el-input>
</el-form-item>
</td>
<td>
<el-form-item
label="" :prop="`social[${index}].secret`" class="mb-0"
:rules="[
{ required: true, message: '{{ __('common.error_required', ['name' => __('Social::setting.entry_secret')]) }}', trigger: ['blur', 'change'] },]"
>
<el-input size="small" v-model="item.secret" placeholder="{{ __('Social::setting.entry_secret') }}"></el-input>
</el-form-item>
</td>
<td>
<el-form-item label="" class="mb-0">
<div class="input-group">
<input size="small" class="form-control" :value="callbackUrl(item.callback)" disabled placeholder="{{ __('Social::setting.entry_callback') }}"></input>
<a href="javascript:void(0)" class="btn btn-outline-secondary opacity-75 copy-code" :data-clipboard-text="item.callback"><i class="bi bi-front"></i></a>
</div>
</el-form-item>
</td>
<td>
<el-form-item label="" prop="sort_order" class="mb-0">
<el-input size="small" v-model="item.sort_order" placeholder="{{ __('Social::setting.entry_sort_order') }}"></el-input>
</el-form-item>
</td>
<td class="text-end">
<button type="button" @click="form.social.splice(index, 1)" class="btn btn-outline-danger btn-sm ml-1"><i class="bi bi-x-lg"></i></button>
</td>
</tr>
</tbody>
<tbody v-else><td colspan="7"><x-admin-no-data /></td></tbody>
</table>
</el-form>
</div>
</div>
<div class="card">
<div class="card-body">
<h6 class="border-bottom pb-3 mb-4">{{ __('Social::setting.text_help_msg') }}</h6>
<ol class="list-group list-group-numbered lh-lg text-secondary">
<li>{{ __('Social::setting.text_omni_explain') }}</li>
<li>{{ __('Social::setting.text_omni_explain_2') }}</li>
<li>{{ __('Social::setting.text_wechat_title') }}
<a target="_blank" href="https://open.weixin.qq.com/">{{ __('Social::setting.text_wechat_info') }}</a>
</li>
<li>{{ __('Social::setting.text_qq_title') }}
<a target="_blank" href="https://connect.qq.com/">{{ __('Social::setting.text_qq_info') }}</a>
</li>
<li>{{ __('Social::setting.text_weibo_title') }}
<a target="_blank" href="http://open.weibo.com/">{{ __('Social::setting.text_weibo_info') }}</a>
</li>
<li>{{ __('Social::setting.text_facebook_title') }}
<a target="_blank" href="https://developers.facebook.com/">Facebook</a>
</li>
<li>{{ __('Social::setting.text_google_title') }}
<a target="_blank" href="https://console.developers.google.com/projectcreate/">Google</a>
</li>
<li>{{ __('Social::setting.text_Twitter_title') }}
<a target="_blank" href="https://apps.twitter.com/">Twitter</a>
</li>
.......
</ol>
</div>
</div>
<style>
.el-form-item__error--inline {
margin-left: 0;
}
.el-form-item__content {
line-height: 1;
}
</style>
<script src="{{ asset('vendor/clipboard/clipboard.min.js') }}"></script>
<script>
new ClipboardJS('.copy-code')
let app = new Vue({
el: '#app',
data: {
form: {
social: @json($plugin->getSetting('setting') ?? []),
},
source: {
providers: @json(Plugin\Social\Repositories\CustomerRepo::allProviders())
},
rules: {
}
},
methods: {
submit(form) {
this.$refs[form].validate((valid) => {
if (!valid) {
this.$message.error('{{ __('common.error_form') }}');
return;
}
$http.post("{{ admin_route('plugin.social.setting') }}", this.form.social).then((res) => {
layer.msg(res.message)
})
});
},
callbackUrl(code) {
return `{{ shop_route('home.index') }}/${code}`;
},
providerChange(e, index) {
this.form.social[index].callback = 'plugin/social/callbacks/' + e
},
addRow() {
this.form.social.push({provider: this.source.providers[1].code, status: 1, key: '', secret: '', callback: `plugin/social/callbacks/${this.source.providers[1].code}`, sort_order: this.form.social.length})
}
}
})
</script>
@endsection

View File

@ -0,0 +1,9 @@
<script>
const url = "{{ shop_route('account.index') }}";
if (window.opener === null) {
window.location.href = url;
} else {
window.opener.location = url;
window.close();
}
</script>

View File

@ -0,0 +1,4 @@
<button type="button" class="btn border fw-bold w-100 mb-3" class="provider-btn" onclick="bk.openWin('{{ plugin_route('social.redirect', $provider['provider']) }}')">
<img src="{{ plugin_resize('social' , '/image/' . $provider['provider'] . '.png') }}" class="img-fluid wh-20 me-2">
{{ __("Social::providers.".$provider['provider']) }}
</button>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 11 KiB

File diff suppressed because one or more lines are too long

View File

@ -9,12 +9,6 @@
*/
body.page-marketing {
.col-xl-3 {
@media (min-width: 1200px) {
width: 20%;
}
}
.marketing-item {
box-shadow: none;
border: 1px solid #eee;

View File

@ -52,7 +52,7 @@ $alert-padding-y: 0.5rem;
}
.btn-primary {
color: #fff;
color: #fff !important;
}
.form-switch {

View File

@ -48,7 +48,7 @@
<div class="marketing-wrap" v-if="plugins.data.length">
<div class="row">
<div class="col-xl-3 col-md-4 col-6" v-for="plugin, index in plugins.data" :key="index">
<div class="col-xxl-2 col-xl-3 col-md-4 col-6" v-for="plugin, index in plugins.data" :key="index">
<div class="card mb-4 marketing-item">
<div class="card-body">
<div class="plugin-img mb-3"><a :href="'admin/marketing/' + plugin.code"><img :src="plugin.icon_big"

View File

@ -72,10 +72,10 @@ $form-feedback-icon-valid-color: inherit;
// $dropdown-divider-bg: rgba(0, 0, 0, 0.1);
@import './bootstrap-icons';
@import 'node_modules/bootstrap-5.1.3/scss/bootstrap';
@import 'node_modules/bootstrap/scss/bootstrap';
.btn-primary {
color: #fff;
color: #fff !important;
}
.container-fluid {

View File

@ -13,6 +13,23 @@ body.page-login, body.page-forgotten {
overflow-x: hidden;
}
.el-form-item__error--inline {
margin-left: 0;
}
.forgotten-link {
display: block;
margin-top: -14px;
}
.el-form-item {
margin-bottom: 18px;
.el-form-item__content {
line-height: 1;
}
}
.login-item-header {
background: #f8f9fa;
border-bottom: none;
@ -23,14 +40,66 @@ body.page-login, body.page-forgotten {
}
}
.card {
border: none;
.login-wrap {
@media (min-width: 768px) {
display: flex;
justify-content: center;
}
.card {
border: none;
@media (min-width: 768px) {
width: 340px;
margin: 0 60px;
}
}
}
.form-iframe {
margin-bottom: 30px;
@media (max-width: 768px) {
padding: 0;
margin-top: 0 !important;
}
.card {
border: none;
@media (min-width: 768px) {
margin: 0 30px;
}
}
}
.social-wrap {
.title {
position: relative;
text-align: center;
color: #999;
&::before {
content: "";
position: absolute;
width: 100%;
left: 0;
height: 1px;
top: 47%;
background: #e5e5e5;
}
span {
background-color: #fff;
position: relative;
padding: 0 5px;
z-index: 1;
}
}
.btn {
border-color: #ccc !important;
&:hover {
background-color: #eee;
}
}
}
}

View File

@ -3,7 +3,7 @@
* @link https://beikeshop.com
* @Author pu shuo <pushuo@guangda.work>
* @Date 2022-09-09 19:16:39
* @LastEditTime 2022-09-16 20:56:53
* @LastEditTime 2022-09-28 17:23:48
*/
export default {
@ -12,13 +12,15 @@ export default {
* @return {*}
*/
getCarts() {
$http.get('carts/mini', null, {hload: true}).then((res) => {
$('#offcanvas-right-cart').html(res.data.html);
if (!res.data.quantity) {
$('.cart-badge-quantity').hide();
} else {
$('.cart-badge-quantity').show().html(res.data.quantity > 99 ? '99+' : res.data.quantity);
}
$(document).ready(() => {
$http.get('carts/mini', null, {hload: true}).then((res) => {
$('#offcanvas-right-cart').html(res.data.html);
if (!res.data.quantity) {
$('.cart-badge-quantity').hide();
} else {
$('.cart-badge-quantity').show().html(res.data.quantity > 99 ? '99+' : res.data.quantity);
}
})
})
},
@ -118,9 +120,18 @@ export default {
type: 2,
title: '',
shadeClose: true,
scrollbar: false,
area: ['900px', '600px'],
skin: 'login-pop-box',
content: 'login?iframe=true' //iframe的url
});
},
openWin(url, name = '', iWidth = 700, iHeight = 500) {
var iTop = (window.screen.height - 30 - iHeight) / 2;;
var iLeft = (window.screen.width - 10 - iWidth) / 2;;
window.open(url, name, 'height=' + iHeight + ',innerHeight=' + iHeight
    + ',width=' + iWidth + ',innerWidth=' + iWidth + ',top=' + iTop + ',left=' + iLeft
    + ',toolbar=no,menubar=no,scrollbars=auto,resizeable=no,location=no,status=no');
}
}

View File

@ -16,69 +16,65 @@
<div class="hero-content pb-5 text-center"><h1 class="hero-heading">{{ __('shop/login.index') }}</h1></div>
@endif
<div class="justify-content-center row {{ !request('iframe') ? 'mb-5' : '' }}">
<div class="col-lg-{{ request('iframe') ? '6' : '5' }} col-md-6 col-sm-12">
<div class="card">
<el-form ref="loginForm" :model="loginForm" :rules="loginRules">
<div class="login-item-header card-header">
<h6 class="text-uppercase mb-0">{{ __('shop/login.login') }}</h6>
</div>
<div class="card-body">
<p class="lead">{{ __('shop/login.already') }}</p>
{{-- <p class="text-muted">Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis
egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit
amet quam egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend leo.</p> --}}
<hr>
<el-form-item label="{{ __('shop/login.email') }}" prop="email">
<el-input @keyup.enter.native="checkedBtnLogin('loginForm')" v-model="loginForm.email" placeholder="{{ __('shop/login.email_address') }}"></el-input>
</el-form-item>
<el-form-item label="{{ __('shop/login.password') }}" prop="password" class="mb-4">
<el-input @keyup.enter.native="checkedBtnLogin('loginForm')" type="password" v-model="loginForm.password" placeholder="{{ __('shop/login.password') }}"></el-input>
</el-form-item>
<a class="text-muted" href="{{ shop_route('forgotten.index') }}"><i class="bi bi-question-circle"></i> {{ __('shop/login.forget_password') }}</a>
<div class="mt-4 mb-3">
<button type="button" @click="checkedBtnLogin('loginForm')" class="btn btn-outline-dark"><i class="bi bi-box-arrow-in-right"></i> {{ __('shop/login.login') }}</button>
</div>
</div>
</el-form>
</div>
</div>
<div class="col-lg-{{ request('iframe') ? '6' : '5' }} col-md-6 col-sm-12">
<div class="card">
<div class="login-wrap">
<div class="card">
<el-form ref="loginForm" :model="loginForm" :rules="loginRules" :inline-message="true">
<div class="login-item-header card-header">
<h6 class="text-uppercase mb-0">{{ __('shop/login.new') }}</h6>
<h6 class="text-uppercase mb-0">{{ __('shop/login.login') }}</h6>
</div>
<div class="card-body">
<p class="lead">{{ __('shop/login.not_already') }}</p>
{{-- <p class="text-muted">With registration with us new world of fashion, fantastic discounts and much more opens to
you! The whole process will not take you more than a minute!</p>
<p class="text-muted">If you have any questions, please feel free to <a href="/contact">contact us</a>, our
customer service center is working for you 24/7.</p> --}}
<hr>
<div class="card-body px-md-2">
<el-form-item label="{{ __('shop/login.email') }}" prop="email">
<el-input @keyup.enter.native="checkedBtnLogin('loginForm')" v-model="loginForm.email" placeholder="{{ __('shop/login.email_address') }}"></el-input>
</el-form-item>
<el-form ref="registerForm" :model="registerForm" :rules="registeRules">
<el-form-item label="{{ __('shop/login.email') }}" prop="email">
<el-input @keyup.enter.native="checkedBtnLogin('registerForm')" v-model="registerForm.email" placeholder="{{ __('shop/login.email_address') }}"></el-input>
</el-form-item>
<el-form-item label="{{ __('shop/login.password') }}" prop="password">
<el-input @keyup.enter.native="checkedBtnLogin('loginForm')" type="password" v-model="loginForm.password" placeholder="{{ __('shop/login.password') }}"></el-input>
</el-form-item>
<el-form-item label="{{ __('shop/login.password') }}" prop="password">
<el-input @keyup.enter.native="checkedBtnLogin('registerForm')" type="password" v-model="registerForm.password" placeholder="{{ __('shop/login.password') }}"></el-input>
</el-form-item>
<a class="text-muted forgotten-link" href="{{ shop_route('forgotten.index') }}"><i class="bi bi-question-circle"></i> {{ __('shop/login.forget_password') }}</a>
<el-form-item label="{{ __('shop/login.confirm_password') }}" prop="password_confirmation">
<el-input @keyup.enter.native="checkedBtnLogin('registerForm')" type="password" v-model="registerForm.password_confirmation" placeholder="{{ __('shop/login.confirm_password') }}"></el-input>
</el-form-item>
<div class="mt-5 mb-3">
<button type="button" @click="checkedBtnLogin('registerForm')" class="btn btn-outline-dark"><i class="bi bi-person"></i> {{ __('shop/login.register') }}</button>
</div>
</el-form>
<div class="mt-4 mb-3">
<button type="button" @click="checkedBtnLogin('loginForm')" class="btn btn-dark btn-lg w-100 fw-bold"><i class="bi bi-box-arrow-in-right"></i> {{ __('shop/login.login') }}</button>
</div>
</div>
</el-form>
@if($social_buttons)
<div class="social-wrap px-2">
<div class="title mb-4"><span>第三方登录</span></div>
@foreach($social_buttons as $button)
{!! $button !!}
@endforeach
</div>
@endif
</div>
<div class="d-flex">
<div class="vr bg-secondary"></div>
</div>
<div class="card">
<div class="login-item-header card-header">
<h6 class="text-uppercase mb-0">{{ __('shop/login.new') }}</h6>
</div>
<div class="card-body px-md-2">
<el-form ref="registerForm" :model="registerForm" :rules="registeRules">
<el-form-item label="{{ __('shop/login.email') }}" prop="email">
<el-input @keyup.enter.native="checkedBtnLogin('registerForm')" v-model="registerForm.email" placeholder="{{ __('shop/login.email_address') }}"></el-input>
</el-form-item>
<el-form-item label="{{ __('shop/login.password') }}" prop="password">
<el-input @keyup.enter.native="checkedBtnLogin('registerForm')" type="password" v-model="registerForm.password" placeholder="{{ __('shop/login.password') }}"></el-input>
</el-form-item>
<el-form-item label="{{ __('shop/login.confirm_password') }}" prop="password_confirmation">
<el-input @keyup.enter.native="checkedBtnLogin('registerForm')" type="password" v-model="registerForm.password_confirmation" placeholder="{{ __('shop/login.confirm_password') }}"></el-input>
</el-form-item>
<div class="mt-5 mb-3">
<button type="button" @click="checkedBtnLogin('registerForm')" class="btn btn-dark btn-lg w-100 fw-bold"><i class="bi bi-person"></i> {{ __('shop/login.register') }}</button>
</div>
</el-form>
</div>
</div>
</div>
@ -184,4 +180,4 @@
}
})
</script>
@endpush
@endpush