1
0
mirror of https://gitee.com/koogua/course-tencent-cloud.git synced 2025-06-20 19:07:03 +08:00

!33 开放登录阶段性合并

* Merge remote-tracking branch 'gitee/xiaochong0302/I280IZ' into xiaocho…
* 初步完成开放登录,待线上测试7
* Merge branch 'demo' of gitee.com:koogua/course-tencent-cloud into xiao…
* 初步完成开放登录,待线上测试6
* !30 开放登录线上测试5
* !29 开放登录线上测试5
* 初步完成开放登录,待线上测试5
* !28 开放登录线上测试4
* 初步完成开放登录,待线上测试4
* !27 开放登录线上测试3
* 初步完成开放登录,待线上测试3
* !26 开放登录线上测试2
* 初步完成开放登录,待线上测试2
* !25 开放登录线上测试
* 初步完成开放登录,待线上测试
* !22 验证更新h5支付
* Merge remote-tracking branch 'remotes/gitee/develop' into demo
* !20 验证更新h5支付
* Merge branch 'develop' of https://gitee.com/koogua/course-tencent-clou…
* !16 v1.2.0阶段性合并
* 删除调试断点代码
* 删除重复的signature方法
* Merge branch 'develop' of https://gitee.com/koogua/course-tencent-clou…
* demo后台增加统计
* !5 更新版本号1.1.0
* !4 v1.1.0版本develop->demo
* Merge branch 'develop' into demo
* 1.增加changelog.md
* Merge branch 'develop' into demo
* Merge branch 'develop' into demo
* Merge branch 'develop' into demo
* !1 精简优化代码
* Merge branch 'develop' into demo
* 合并修改
This commit is contained in:
koogua 2020-12-07 11:02:13 +08:00
parent 753f2203fa
commit dd99631f6e
35 changed files with 1475 additions and 112 deletions

View File

@ -21,6 +21,17 @@ class Controller extends \Phalcon\Mvc\Controller
public function beforeExecuteRoute(Dispatcher $dispatcher)
{
/**
* demo分支拒绝数据提交
*/
if ($this->isNotSafeRequest()) {
$dispatcher->forward([
'controller' => 'public',
'action' => 'forbidden',
]);
return false;
}
if ($this->isNotSafeRequest()) {
$this->checkHttpReferer();
$this->checkCsrfToken();

View File

@ -298,4 +298,33 @@ class SettingController extends Controller
}
}
/**
* @Route("/oauth", name="admin.setting.oauth")
*/
public function oauthAction()
{
$settingService = new SettingService();
if ($this->request->isPost()) {
$section = $this->request->getPost('section', 'string');
$data = $this->request->getPost();
$settingService->updateSettings($section, $data);
return $this->jsonSuccess(['msg' => '更新配置成功']);
} else {
$qqAuth = $settingService->getQQAuthSettings();
$weixinAuth = $settingService->getWeixinAuthSettings();
$weiboAuth = $settingService->getWeiboAuthSettings();
$this->view->setVar('qq_auth', $qqAuth);
$this->view->setVar('weixin_auth', $weixinAuth);
$this->view->setVar('weibo_auth', $weiboAuth);
}
}
}

View File

@ -744,6 +744,12 @@ class AuthNode extends Service
'type' => 'menu',
'route' => 'admin.setting.im',
],
[
'id' => '5-1-12',
'title' => '开放登录',
'type' => 'menu',
'route' => 'admin.setting.oauth',
],
],
],
],

View File

@ -9,6 +9,33 @@ use App\Repos\Vip as VipRepo;
class Setting extends Service
{
public function getQQAuthSettings()
{
$oauth = $this->getSettings('oauth.qq');
$oauth['redirect_uri'] = $oauth['redirect_uri'] ?: kg_full_url(['for' => 'home.oauth.qq_callback']);
return $oauth;
}
public function getWeixinAuthSettings()
{
$oauth = $this->getSettings('oauth.weixin');
$oauth['redirect_uri'] = $oauth['redirect_uri'] ?: kg_full_url(['for' => 'home.oauth.weixin_callback']);
return $oauth;
}
public function getWeiboAuthSettings()
{
$oauth = $this->getSettings('oauth.weibo');
$oauth['redirect_uri'] = $oauth['redirect_uri'] ?: kg_full_url(['for' => 'home.oauth.weibo_callback']);
return $oauth;
}
public function getAlipaySettings()
{
$alipay = $this->getSettings('pay.alipay');
@ -59,8 +86,16 @@ class Setting extends Service
$result = [];
/**
* demo分支过滤敏感数据
*/
if ($items->count() > 0) {
foreach ($items as $item) {
$case1 = preg_match('/(id|auth|key|secret|password|pwd)$/', $item->item_key);
$case2 = $this->dispatcher->getControllerName() == 'setting';
if ($case1 && $case2) {
$item->item_value = '***';
}
$result[$item->item_key] = $item->item_value;
}
}

View File

@ -0,0 +1,24 @@
{% extends 'templates/main.volt' %}
{% block content %}
<div class="layui-tab layui-tab-brief">
<ul class="layui-tab-title kg-tab-title">
<li class="layui-this">QQ登录</li>
<li>微信登录</li>
<li>新浪微博</li>
</ul>
<div class="layui-tab-content">
<div class="layui-tab-item layui-show">
{{ partial('setting/oauth_qq') }}
</div>
<div class="layui-tab-item">
{{ partial('setting/oauth_weixin') }}
</div>
<div class="layui-tab-item">
{{ partial('setting/oauth_weibo') }}
</div>
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,35 @@
<form class="layui-form kg-form" method="POST" action="{{ url({'for':'admin.setting.oauth'}) }}">
<div class="layui-form-item">
<label class="layui-form-label">开启登录</label>
<div class="layui-input-block">
<input type="radio" name="enabled" value="1" title="是" {% if qq_auth.enabled == "1" %}checked="checked"{% endif %}>
<input type="radio" name="enabled" value="0" title="否" {% if qq_auth.enabled == "0" %}checked="checked"{% endif %}>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">App ID</label>
<div class="layui-input-block">
<input class="layui-input" type="text" name="client_id" value="{{ qq_auth.client_id }}" lay-verify="required">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">App Secret</label>
<div class="layui-input-block">
<input class="layui-input" type="text" name="client_secret" value="{{ qq_auth.client_secret }}" lay-verify="required">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">Redirect Url</label>
<div class="layui-input-block">
<input class="layui-input" type="text" name="redirect_uri" value="{{ qq_auth.redirect_uri }}" lay-verify="required">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label"></label>
<div class="layui-input-block">
<button class="layui-btn" lay-submit="true" lay-filter="go">提交</button>
<button type="button" class="kg-back layui-btn layui-btn-primary">返回</button>
<input type="hidden" name="section" value="oauth.qq">
</div>
</div>
</form>

View File

@ -0,0 +1,35 @@
<form class="layui-form kg-form" method="POST" action="{{ url({'for':'admin.setting.oauth'}) }}">
<div class="layui-form-item">
<label class="layui-form-label">开启登录</label>
<div class="layui-input-block">
<input type="radio" name="enabled" value="1" title="是" {% if weibo_auth.enabled == "1" %}checked="checked"{% endif %}>
<input type="radio" name="enabled" value="0" title="否" {% if weibo_auth.enabled == "0" %}checked="checked"{% endif %}>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">App Key</label>
<div class="layui-input-block">
<input class="layui-input" type="text" name="client_id" value="{{ weibo_auth.client_id }}" lay-verify="required">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">App Secret</label>
<div class="layui-input-block">
<input class="layui-input" type="text" name="client_secret" value="{{ weibo_auth.client_secret }}" lay-verify="required">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">Redirect Uri</label>
<div class="layui-input-block">
<input class="layui-input" type="text" name="redirect_uri" value="{{ weibo_auth.redirect_uri }}" lay-verify="required">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label"></label>
<div class="layui-input-block">
<button class="layui-btn" lay-submit="true" lay-filter="go">提交</button>
<button type="button" class="kg-back layui-btn layui-btn-primary">返回</button>
<input type="hidden" name="section" value="oauth.weibo">
</div>
</div>
</form>

View File

@ -0,0 +1,35 @@
<form class="layui-form kg-form" method="POST" action="{{ url({'for':'admin.setting.oauth'}) }}">
<div class="layui-form-item">
<label class="layui-form-label">开启登录</label>
<div class="layui-input-block">
<input type="radio" name="enabled" value="1" title="是" {% if weixin_auth.enabled == "1" %}checked="checked"{% endif %}>
<input type="radio" name="enabled" value="0" title="否" {% if weixin_auth.enabled == "0" %}checked="checked"{% endif %}>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">App ID</label>
<div class="layui-input-block">
<input class="layui-input" type="text" name="client_id" value="{{ weixin_auth.client_id }}" lay-verify="required">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">App Secret</label>
<div class="layui-input-block">
<input class="layui-input" type="text" name="client_secret" value="{{ weixin_auth.client_secret }}" lay-verify="required">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">Redirect Url</label>
<div class="layui-input-block">
<input class="layui-input" type="text" name="redirect_uri" value="{{ weixin_auth.redirect_uri }}" lay-verify="required">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label"></label>
<div class="layui-input-block">
<button class="layui-btn" lay-submit="true" lay-filter="go">提交</button>
<button type="button" class="kg-back layui-btn layui-btn-primary">返回</button>
<input type="hidden" name="section" value="oauth.weixin">
</div>
</div>
</form>

View File

@ -22,5 +22,12 @@
{% block include_js %}{% endblock %}
{% block inline_js %}{% endblock %}
{% set site = setting('site') %}
{% if site['analytics_enabled'] == 1 %}
{{ site['analytics_script'] }}
{% endif %}
</body>
</html>

View File

@ -4,6 +4,7 @@ namespace App\Http\Home\Controllers;
use App\Http\Home\Services\Account as AccountService;
use App\Services\Logic\Account\EmailUpdate as EmailUpdateService;
use App\Services\Logic\Account\OAuthProvider as OAuthProviderService;
use App\Services\Logic\Account\PasswordReset as PasswordResetService;
use App\Services\Logic\Account\PasswordUpdate as PasswordUpdateService;
use App\Services\Logic\Account\PhoneUpdate as PhoneUpdateService;
@ -62,8 +63,13 @@ class AccountController extends Controller
$captcha = $service->getSettings('captcha');
$service = new OAuthProviderService();
$oauthProvider = $service->handle();
$returnUrl = $this->request->getHTTPReferer();
$this->view->setVar('oauth_provider', $oauthProvider);
$this->view->setVar('return_url', $returnUrl);
$this->view->setVar('captcha', $captcha);
}

View File

@ -0,0 +1,139 @@
<?php
namespace App\Http\Home\Controllers;
use App\Http\Home\Services\Connect as ConnectService;
use App\Models\Connect as ConnectModel;
/**
* @RoutePrefix("/oauth")
*/
class ConnectController extends Controller
{
/**
* @Get("/qq", name="home.oauth.qq")
*/
public function qqAction()
{
$service = new ConnectService();
$url = $service->getAuthorizeUrl(ConnectModel::PROVIDER_QQ);
return $this->response->redirect($url, true);
}
/**
* @Get("/weixin", name="home.oauth.weixin")
*/
public function weixinAction()
{
$service = new ConnectService();
$url = $service->getAuthorizeUrl(ConnectModel::PROVIDER_WEIXIN);
return $this->response->redirect($url, true);
}
/**
* @Get("/weibo", name="home.oauth.weibo")
*/
public function weiboAction()
{
$service = new ConnectService();
$url = $service->getAuthorizeUrl(ConnectModel::PROVIDER_WEIBO);
return $this->response->redirect($url, true);
}
/**
* @Get("/qq/callback", name="home.oauth.qq_callback")
*/
public function qqCallbackAction()
{
$this->handleCallback(ConnectModel::PROVIDER_QQ);
}
/**
* @Get("/weixin/callback", name="home.oauth.weixin_callback")
*/
public function weixinCallbackAction()
{
$this->handleCallback(ConnectModel::PROVIDER_WEIXIN);
}
/**
* @Get("/weibo/callback", name="home.oauth.weibo_callback")
*/
public function weiboCallbackAction()
{
$this->handleCallback(ConnectModel::PROVIDER_WEIBO);
}
/**
* @Get("/weibo/refuse", name="home.oauth.weibo_refuse")
*/
public function weiboRefuseAction()
{
return $this->response->redirect(['for' => 'home.account.login']);
}
/**
* @Post("/bind/login", name="home.oauth.bind_login")
*/
public function bindLoginAction()
{
$service = new ConnectService();
$service->bindLogin();
$location = $this->url->get(['for' => 'home.uc.account']);
return $this->jsonSuccess(['location' => $location]);
}
/**
* @Post("/bind/register", name="home.oauth.bind_register")
*/
public function bindRegisterAction()
{
$service = new ConnectService();
$service->bindRegister();
$location = $this->url->get(['for' => 'home.uc.account']);
return $this->jsonSuccess(['location' => $location]);
}
protected function handleCallback($provider)
{
$code = $this->request->getQuery('code');
$state = $this->request->getQuery('state');
$service = new ConnectService();
$openUser = $service->getOpenUserInfo($code, $state, $provider);
$connect = $service->getConnectRelation($openUser['id'], $provider);
if ($connect && $connect->deleted == 0) {
if ($this->authUser->id > 0) {
$service->bindUser($openUser, $provider);
return $this->response->redirect(['for' => 'home.uc.account']);
} else {
$service->authLogin($connect);
return $this->response->redirect(['for' => 'home.index']);
}
}
$captcha = $service->getSettings('captcha');
$this->view->pick('connect/bind');
$this->view->setVar('captcha', $captcha);
$this->view->setVar('provider', $provider);
$this->view->setVar('open_user', $openUser);
}
}

View File

@ -7,6 +7,7 @@ use App\Services\Logic\Order\OrderCancel as OrderCancelService;
use App\Services\Logic\Order\OrderConfirm as OrderConfirmService;
use App\Services\Logic\Order\OrderCreate as OrderCreateService;
use App\Services\Logic\Order\OrderInfo as OrderInfoService;
use App\Services\Logic\Order\PayProvider as PayProviderService;
use Phalcon\Mvc\Dispatcher;
use Phalcon\Mvc\View;
@ -82,6 +83,10 @@ class OrderController extends Controller
{
$sn = $this->request->getQuery('sn', 'string');
$service = new PayProviderService();
$payProvider = $service->handle();
$service = new OrderInfoService();
$order = $service->handle($sn);
@ -90,6 +95,7 @@ class OrderController extends Controller
$this->response->redirect(['for' => 'home.uc.orders']);
}
$this->view->setVar('pay_provider', $payProvider);
$this->view->setVar('order', $order);
}

View File

@ -2,7 +2,10 @@
namespace App\Http\Home\Controllers;
use App\Services\Logic\Account\OAuthProvider as OAuthProviderService;
use App\Services\Logic\User\Console\AccountInfo as AccountInfoService;
use App\Services\Logic\User\Console\ConnectDelete as ConnectDeleteService;
use App\Services\Logic\User\Console\ConnectList as ConnectListService;
use App\Services\Logic\User\Console\ConsultList as ConsultListService;
use App\Services\Logic\User\Console\CourseList as CourseListService;
use App\Services\Logic\User\Console\FavoriteList as FavoriteListService;
@ -59,13 +62,21 @@ class UserConsoleController extends Controller
*/
public function accountAction()
{
$type = $this->request->getQuery('type', 'string', 'info');
$service = new AccountInfoService();
$captcha = $service->getSettings('captcha');
$account = $service->handle();
$type = $this->request->getQuery('type', 'string', 'info');
$service = new OAuthProviderService();
$oauthProvider = $service->handle();
$service = new ConnectListService();
$connects = $service->handle();
if ($type == 'info') {
$this->view->pick('user/console/account_info');
@ -77,6 +88,8 @@ class UserConsoleController extends Controller
$this->view->pick('user/console/account_password');
}
$this->view->setVar('oauth_provider', $oauthProvider);
$this->view->setVar('connects', $connects);
$this->view->setVar('captcha', $captcha);
$this->view->setVar('account', $account);
}
@ -207,4 +220,23 @@ class UserConsoleController extends Controller
return $this->jsonSuccess($content);
}
/**
* @Post("/connect/{id:[0-9]+}/delete", name="home.uc.unconnect")
*/
public function deleteConnectAction($id)
{
$service = new ConnectDeleteService();
$service->handle($id);
$location = $this->url->get(['for' => 'home.uc.account']);
$content = [
'location' => $location,
'msg' => '解除登录绑定成功',
];
return $this->jsonSuccess($content);
}
}

View File

@ -0,0 +1,208 @@
<?php
namespace App\Http\Home\Services;
use App\Models\Connect as ConnectModel;
use App\Models\User as UserModel;
use App\Repos\Connect as ConnectRepo;
use App\Repos\User as UserRepo;
use App\Services\Auth\Home as AuthService;
use App\Services\Logic\Account\Register as RegisterService;
use App\Services\OAuth\QQ as QQAuth;
use App\Services\OAuth\WeiBo as WeiBoAuth;
use App\Services\OAuth\WeiXin as WeiXinAuth;
use App\Validators\Account as AccountValidator;
class Connect extends Service
{
public function bindLogin()
{
$post = $this->request->getPost();
$auth = $this->getConnectAuth($post['provider']);
$auth->checkState($post['state']);
$validator = new AccountValidator();
$user = $validator->checkUserLogin($post['account'], $post['password']);
$openUser = json_decode($post['open_user'], true);
$this->handleConnectRelation($user, $openUser, $post['provider']);
$auth = $this->getAppAuth();
$auth->saveAuthInfo($user);
}
public function bindRegister()
{
$post = $this->request->getPost();
$auth = $this->getConnectAuth($post['provider']);
$auth->checkState($post['state']);
$openUser = json_decode($post['open_user'], true);
$registerService = new RegisterService();
$account = $registerService->handle();
$userRepo = new UserRepo();
$user = $userRepo->findById($account->id);
$this->handleConnectRelation($user, $openUser, $post['provider']);
$auth = $this->getAppAuth();
$auth->saveAuthInfo($user);
}
public function bindUser($openUser, $provider)
{
$user = $this->getLoginUser();
$this->handleConnectRelation($user, $openUser, $provider);
}
public function authLogin(ConnectModel $connect)
{
$userRepo = new UserRepo();
$user = $userRepo->findById($connect->user_id);
$auth = $this->getAppAuth();
$auth->saveAuthInfo($user);
}
public function getAuthorizeUrl($provider)
{
$auth = $this->getConnectAuth($provider);
return $auth->getAuthorizeUrl();
}
public function getOpenUserInfo($code, $state, $provider)
{
$auth = $this->getConnectAuth($provider);
$auth->checkState($state);
$token = $auth->getAccessToken($code);
$openId = $auth->getOpenId($token);
return $auth->getUserInfo($token, $openId);
}
public function getConnectRelation($openId, $provider)
{
$connectRepo = new ConnectRepo();
return $connectRepo->findByOpenId($openId, $provider);
}
public function getConnectAuth($provider)
{
$auth = null;
switch ($provider) {
case ConnectModel::PROVIDER_QQ:
$auth = $this->getQQAuth();
break;
case ConnectModel::PROVIDER_WEIXIN:
$auth = $this->getWeiXinAuth();
break;
case ConnectModel::PROVIDER_WEIBO:
$auth = $this->getWeiBoAuth();
break;
}
if (!$auth) {
throw new \Exception('Invalid OAuth Provider');
}
return $auth;
}
protected function getQQAuth()
{
$settings = $this->getSettings('oauth.qq');
return new QQAuth(
$settings['client_id'],
$settings['client_secret'],
$settings['redirect_uri']
);
}
protected function getWeiXinAuth()
{
$settings = $this->getSettings('oauth.weixin');
return new WeiXinAuth(
$settings['client_id'],
$settings['client_secret'],
$settings['redirect_uri']
);
}
protected function getWeiBoAuth()
{
$settings = $this->getSettings('oauth.weibo');
return new WeiBoAuth(
$settings['client_id'],
$settings['client_secret'],
$settings['redirect_uri']
);
}
protected function getAppAuth()
{
/**
* @var $auth AuthService
*/
$auth = $this->getDI()->get('auth');
return $auth;
}
protected function handleConnectRelation(UserModel $user, array $openUser, $provider)
{
$connectRepo = new ConnectRepo();
$connect = $connectRepo->findByOpenId($openUser['id'], $provider);
if ($connect) {
if (time() - $connect->update_time > 86400) {
$connect->open_name = $openUser['name'];
$connect->open_avatar = $openUser['avatar'];
}
if ($connect->deleted == 1) {
$connect->deleted = 0;
$connect->update();
}
} else {
$connect = new ConnectModel();
$connect->user_id = $user->id;
$connect->open_id = $openUser['id'];
$connect->open_name = $openUser['name'];
$connect->open_avatar = $openUser['avatar'];
$connect->provider = $provider;
$connect->create();
}
}
}

View File

@ -27,6 +27,17 @@
<span class="separator">·</span>
<a class="forget-link" href="{{ url({'for':'home.account.forget_pwd'}) }}">忘记密码</a>
</div>
<div class="oauth">
{% if oauth_provider.qq.enabled == 1 %}
<a class="layui-icon layui-icon-login-qq login-qq" href="{{ url({'for':'home.oauth.qq'}) }}"></a>
{% endif %}
{% if oauth_provider.weixin.enabled == 1 %}
<a class="layui-icon layui-icon-login-wechat login-wechat" href="{{ url({'for':'home.oauth.weixin'}) }}"></a>
{% endif %}
{% if oauth_provider.weibo.enabled == 1 %}
<a class="layui-icon layui-icon-login-weibo login-weibo" href="{{ url({'for':'home.oauth.weibo'}) }}"></a>
{% endif %}
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,34 @@
{% extends 'templates/main.volt' %}
{% block content %}
<div class="layui-breadcrumb breadcrumb">
<a href="/">首页</a>
<a><cite>登录绑定</cite></a>
</div>
<div class="login-wrap wrap">
<div class="layui-tab layui-tab-brief login-tab">
<ul class="layui-tab-title login-tab-title">
<li class="layui-this">绑定已有帐号</li>
<li>注册并绑定帐号</li>
</ul>
<div class="layui-tab-content">
<div class="layui-tab-item layui-show">
{{ partial('connect/bind_login') }}
</div>
<div class="layui-tab-item">
{{ partial('connect/bind_register') }}
</div>
</div>
</div>
</div>
{% endblock %}
{% block include_js %}
{{ js_include('https://ssl.captcha.qq.com/TCaptcha.js',false) }}
{{ js_include('home/js/captcha.verify.js') }}
{% endblock %}

View File

@ -0,0 +1,21 @@
<form class="layui-form account-form" method="POST" action="{{ url({'for':'home.oauth.bind_login'}) }}">
<div class="layui-form-item">
<div class="layui-input-block">
<input class="layui-input" type="text" name="account" autocomplete="off" placeholder="手机 / 邮箱" lay-verify="required">
</div>
</div>
<div class="layui-form-item">
<div class="layui-input-block">
<input class="layui-input" type="password" name="password" autocomplete="off" placeholder="密码" lay-verify="required">
</div>
</div>
<div class="layui-form-item">
<div class="layui-input-block">
<button id="submit-btn" class="layui-btn layui-btn-fluid" lay-submit="true" lay-filter="go">登录并绑定已有帐号</button>
<input type="hidden" name="provider" value="{{ provider }}">
<input type="hidden" name="code" value="{{ request.get('code') }}">
<input type="hidden" name="state" value="{{ request.get('state') }}">
<input type="hidden" name="open_user" value='{{ open_user|json_encode }}'>
</div>
</div>
</form>

View File

@ -0,0 +1,32 @@
<form class="layui-form account-form" method="POST" action="{{ url({'for':'home.oauth.bind_register'}) }}">
<div class="layui-form-item">
<div class="layui-input-block">
<input id="cv-account" class="layui-input" type="text" name="account" autocomplete="off" placeholder="手机 / 邮箱" lay-verify="required">
</div>
</div>
<div class="layui-form-item">
<div class="layui-input-block">
<input class="layui-input" type="password" name="password" autocomplete="off" placeholder="密码字母数字特殊字符6-16位" lay-verify="required">
</div>
</div>
<div class="layui-form-item">
<div class="layui-input-inline verify-input-inline">
<input class="layui-input" type="text" name="verify_code" placeholder="验证码" lay-verify="required">
</div>
<div class="layui-input-inline verify-btn-inline">
<button id="cv-verify-emit" class="layui-btn layui-btn-primary layui-btn-disabled" type="button" disabled="disabled">获取验证码</button>
</div>
</div>
<div class="layui-form-item">
<div class="layui-input-block">
<button id="cv-submit-btn" class="layui-btn layui-btn-fluid layui-btn-disabled" disabled="disabled" lay-submit="true" lay-filter="go">注册并绑定帐号</button>
<input type="hidden" name="provider" value="{{ provider }}">
<input type="hidden" name="code" value="{{ request.get('code') }}">
<input type="hidden" name="state" value="{{ request.get('state') }}">
<input type="hidden" name="open_user" value='{{ open_user|json_encode }}'>
<input id="cv-app-id" type="hidden" value="{{ captcha.app_id }}">
<input id="cv-ticket" type="hidden" name="ticket">
<input id="cv-rand" type="hidden" name="rand">
</div>
</div>
</form>

View File

@ -17,8 +17,12 @@
支付金额:<span class="amount">{{ '¥%0.2f'|format(order.amount) }}</span>
</div>
<div class="channel">
<a class="alipay btn-pay" href="javascript:" data-channel="alipay">{{ image('home/img/alipay.png') }}</a>
<a class="wxpay btn-pay" href="javascript:" data-channel="wxpay">{{ image('home/img/wxpay.png') }}</a>
{% if pay_provider.alipay.enabled == 1 %}
<a class="alipay btn-pay" href="javascript:" data-channel="alipay">{{ image('home/img/alipay.png') }}</a>
{% endif %}
{% if pay_provider.wxpay.enabled == 1 %}
<a class="wxpay btn-pay" href="javascript:" data-channel="wxpay">{{ image('home/img/wxpay.png') }}</a>
{% endif %}
</div>
<div class="footer">
<span class="tips">友情提示请在12小时内完成支付有问题请联系客服</span>

View File

@ -2,6 +2,23 @@
{% block content %}
{%- macro connect_provider(item) %}
{% if item.provider == 1 %}
<i class="layui-icon layui-icon-login-qq login-qq"></i>
{% elseif item.provider == 2 %}
<i class="layui-icon layui-icon-login-wechat login-wechat"></i>
{% elseif item.provider == 3 %}
<i class="layui-icon layui-icon-login-weibo login-weibo"></i>
{% endif %}
{%- endmacro %}
{%- macro connect_user(item) %}
{% if item.open_avatar %}
<span class="open-avatar"><img src="{{ item.open_avatar }}"></span>
{% endif %}
<span class="open-name">{{ item.open_name }}</span>
{%- endmacro %}
{% set edit_pwd_url = url({'for':'home.uc.account'},{'type':'password'}) %}
{% set edit_phone_url = url({'for':'home.uc.account'},{'type':'phone'}) %}
{% set edit_email_url = url({'for':'home.uc.account'},{'type':'email'}) %}
@ -43,6 +60,45 @@
{% endif %}
</div>
</div>
<div class="my-nav">
<span class="title">开放登录</span>
</div>
{% if connects %}
<div class="connect-tips">已经绑定的第三方帐号</div>
<div class="connect-list">
<table class="layui-table">
<tr>
<td>序号</td>
<td>提供方</td>
<td>用户信息</td>
<td>创建日期</td>
<td width="15%">操作</td>
</tr>
{% for connect in connects %}
{% set url = url({'for':'home.uc.unconnect','id':connect.id}) %}
<tr>
<td>{{ loop.index }}</td>
<td>{{ connect_provider(connect) }}</td>
<td>{{ connect_user(connect) }}</td>
<td>{{ date('Y-m-d H:i',connect.create_time) }}</td>
<td><a class="layui-btn layui-btn-danger layui-btn-sm kg-delete" href="javascript:" data-url="{{ url }}" data-tips="确定要解除绑定吗?">解除绑定</a></td>
</tr>
{% endfor %}
</table>
</div>
{% endif %}
<div class="connect-tips">支持绑定的第三方帐号</div>
<div class="oauth-list">
{% if oauth_provider.qq.enabled == 1 %}
<a class="layui-icon layui-icon-login-qq login-qq" href="{{ url({'for':'home.oauth.qq'}) }}"></a>
{% endif %}
{% if oauth_provider.qq.enabled == 1 %}
<a class="layui-icon layui-icon-login-wechat login-wechat" href="{{ url({'for':'home.oauth.weixin'}) }}"></a>
{% endif %}
{% if oauth_provider.qq.enabled == 1 %}
<a class="layui-icon layui-icon-login-weibo login-weibo" href="{{ url({'for':'home.oauth.weibo'}) }}"></a>
{% endif %}
</div>
</div>
</div>
</div>

View File

@ -1,53 +0,0 @@
<?php
namespace App\Library;
use GuzzleHttp\Client as HttpClient;
abstract class OAuth
{
protected $appId;
protected $appSecret;
protected $redirectUri;
protected $accessToken;
protected $openId;
public function __construct($appId, $appSecret, $redirectUri)
{
$this->appId = $appId;
$this->appSecret = $appSecret;
$this->redirectUri = $redirectUri;
}
public function httpGet($uri, $params = [], $headers = [])
{
$client = new HttpClient();
$options = ['query' => $params, 'headers' => $headers];
$response = $client->get($uri, $options);
return $response->getBody();
}
public function httpPost($uri, $params = [], $headers = [])
{
$client = new HttpClient();
$options = ['query' => $params, 'headers' => $headers];
$response = $client->post($uri, $options);
return $response->getBody();
}
abstract public function getAuthorizeUrl();
abstract public function getAccessToken($code);
abstract public function getOpenId($accessToken);
abstract public function getUserInfo($accessToken, $openId);
}

104
app/Models/Connect.php Normal file
View File

@ -0,0 +1,104 @@
<?php
namespace App\Models;
use Phalcon\Mvc\Model\Behavior\SoftDelete;
class Connect extends Model
{
const PROVIDER_QQ = 1; // QQ
const PROVIDER_WEIXIN = 2; // 微信
const PROVIDER_WEIBO = 3; // 微博
/**
* 主键编号
*
* @var int
*/
public $id;
/**
* 用户编号
*
* @var int
*/
public $user_id;
/**
* 开放ID
*
* @var string
*/
public $open_id;
/**
* 开放名称
*
* @var string
*/
public $open_name;
/**
* 开放头像
*
* @var string
*/
public $open_avatar;
/**
* 提供商
*
* @var int
*/
public $provider;
/**
* 删除标识
*
* @var int
*/
public $deleted;
/**
* 创建时间
*
* @var int
*/
public $create_time;
/**
* 更新时间
*
* @var int
*/
public $update_time;
public function getSource(): string
{
return 'kg_connect';
}
public function initialize()
{
parent::initialize();
$this->addBehavior(
new SoftDelete([
'field' => 'deleted',
'value' => 1,
])
);
}
public function beforeCreate()
{
$this->create_time = time();
}
public function beforeUpdate()
{
$this->update_time = time();
}
}

62
app/Repos/Connect.php Normal file
View File

@ -0,0 +1,62 @@
<?php
namespace App\Repos;
use App\Models\Connect as ConnectModel;
use Phalcon\Mvc\Model;
use Phalcon\Mvc\Model\Resultset;
use Phalcon\Mvc\Model\ResultsetInterface;
class Connect extends Repository
{
/**
* @param array $where
* @return ResultsetInterface|Resultset|ConnectModel[]
*/
public function findAll($where = [])
{
$query = ConnectModel::query();
$query->where('1 = 1');
if (isset($where['user_id'])) {
$query->andWhere('user_id = :user_id:', ['user_id' => $where['user_id']]);
}
if (isset($where['provider'])) {
$query->andWhere('provider = :provider:', ['provider' => $where['provider']]);
}
if (isset($where['deleted'])) {
$query->andWhere('deleted = :deleted:', ['deleted' => $where['deleted']]);
}
$query->orderBy('id DESC');
return $query->execute();
}
/**
* @param int $id
* @return ConnectModel|Model|bool
*/
public function findById($id)
{
return ConnectModel::findFirst($id);
}
/**
* @param string $openId
* @param int $provider
* @return ConnectModel|Model|bool
*/
public function findByOpenId($openId, $provider)
{
return ConnectModel::findFirst([
'conditions' => 'open_id = ?1 and provider = ?2',
'bind' => [1 => $openId, 2 => $provider],
]);
}
}

View File

@ -0,0 +1,23 @@
<?php
namespace App\Services\Logic\Account;
use App\Services\Logic\Service;
class OAuthProvider extends Service
{
public function handle()
{
$weixin = $this->getSettings('oauth.weixin');
$weibo = $this->getSettings('oauth.weibo');
$qq = $this->getSettings('oauth.qq');
return [
'weixin' => ['enabled' => $weixin['enabled']],
'weibo' => ['enabled' => $weibo['enabled']],
'qq' => ['enabled' => $qq['enabled']],
];
}
}

View File

@ -0,0 +1,21 @@
<?php
namespace App\Services\Logic\Order;
use App\Services\Logic\Service;
class PayProvider extends Service
{
public function handle()
{
$alipay = $this->getSettings('pay.alipay');
$wxpay = $this->getSettings('pay.wxpay');
return [
'alipay' => ['enabled' => $alipay['enabled']],
'wxpay' => ['enabled' => $wxpay['enabled']],
];
}
}

View File

@ -0,0 +1,26 @@
<?php
namespace App\Services\Logic\User\Console;
use App\Services\Logic\Service;
use App\Validators\Connect as ConnectValidator;
class ConnectDelete extends Service
{
public function handle($id)
{
$user = $this->getLoginUser();
$validator = new ConnectValidator();
$connect = $validator->checkConnect($id);
$validator->checkOwner($user->id, $connect->user_id);
$connect->deleted = 1;
$connect->update();
}
}

View File

@ -0,0 +1,45 @@
<?php
namespace App\Services\Logic\User\Console;
use App\Repos\Connect as ConnectRepo;
use App\Services\Logic\Service;
class ConnectList extends Service
{
public function handle()
{
$user = $this->getLoginUser();
$params = [
'user_id' => $user->id,
'deleted' => 0,
];
$connectRepo = new ConnectRepo();
$connects = $connectRepo->findAll($params);
if ($connects->count() == 0) {
return [];
}
$items = [];
foreach ($connects as $connect) {
$items[] = [
'id' => $connect->id,
'open_id' => $connect->open_id,
'open_name' => $connect->open_name,
'open_avatar' => $connect->open_avatar,
'provider' => $connect->provider,
'create_time' => $connect->create_time,
'update_time' => $connect->update_time,
];
}
return $items;
}
}

107
app/Services/OAuth.php Normal file
View File

@ -0,0 +1,107 @@
<?php
namespace App\Services;
use GuzzleHttp\Client as HttpClient;
use Phalcon\Crypt;
use Phalcon\Di;
abstract class OAuth extends Service
{
/**
* @var string
*/
protected $clientId;
/**
* @var string
*/
protected $clientSecret;
/**
* @var string
*/
protected $redirectUri;
/**
* @var string
*/
protected $accessToken;
/**
* @var string
*/
protected $openId;
public function __construct($clientId, $clientSecret, $redirectUri)
{
$this->clientId = $clientId;
$this->clientSecret = $clientSecret;
$this->redirectUri = $redirectUri;
}
public function httpGet($uri, $params = [], $headers = [])
{
$client = new HttpClient();
$options = ['query' => $params, 'headers' => $headers];
$response = $client->get($uri, $options);
return $response->getBody();
}
public function httpPost($uri, $params = [], $headers = [])
{
$client = new HttpClient();
$options = ['query' => $params, 'headers' => $headers];
$response = $client->post($uri, $options);
return $response->getBody();
}
public function getState()
{
/**
* @var $crypt Crypt
*/
$crypt = Di::getDefault()->get('crypt');
return $crypt->encryptBase64(rand(1000, 9999));
}
public function checkState($state)
{
/**
* 注意事项:
* callback中的state参数并未做encode处理参数中含有"+"
* 获取参数的时候却自动做了decode处理"+"变成了空格
*/
$state = str_replace(' ', '+', $state);
/**
* @var $crypt Crypt
*/
$crypt = Di::getDefault()->get('crypt');
$value = $crypt->decryptBase64($state);
if ($value < 1000 || $value > 9999) {
throw new \Exception('Invalid OAuth State Value');
}
return true;
}
abstract public function getAuthorizeUrl();
abstract public function getAccessToken($code);
abstract public function getOpenId($accessToken);
abstract public function getUserInfo($accessToken, $openId);
}

View File

@ -1,8 +1,8 @@
<?php
namespace App\Library\OAuth;
namespace App\Services\OAuth;
use App\Library\OAuth;
use App\Services\OAuth;
class QQ extends OAuth
{
@ -15,10 +15,11 @@ class QQ extends OAuth
public function getAuthorizeUrl()
{
$params = [
'client_id' => $this->appId,
'client_id' => $this->clientId,
'redirect_uri' => $this->redirectUri,
'state' => $this->getState(),
'response_type' => 'code',
'scope' => '',
'scope' => 'get_user_info',
];
return self::AUTHORIZE_URL . '?' . http_build_query($params);
@ -28,11 +29,10 @@ class QQ extends OAuth
{
$params = [
'code' => $code,
'client_id' => $this->appId,
'client_secret' => $this->appSecret,
'client_id' => $this->clientId,
'client_secret' => $this->clientSecret,
'redirect_uri' => $this->redirectUri,
'grant_type' => 'authorization_code',
'state' => 'ok',
];
$response = $this->httpPost(self::ACCESS_TOKEN_URL, $params);
@ -56,14 +56,14 @@ class QQ extends OAuth
public function getUserInfo($accessToken, $openId)
{
$params = [
'oauth_consumer_key' => $this->clientId,
'access_token' => $accessToken,
'openid' => $openId,
'oauth_consumer_key' => $this->appId,
];
$response = $this->httpGet(self::USER_INFO_URL, $params);
$this->parseUserInfo($response);
return $this->parseUserInfo($response);
}
protected function parseAccessToken($response)
@ -81,15 +81,15 @@ class QQ extends OAuth
protected function parseOpenId($response)
{
$result = $match = [];
$result = $matches = [];
if (!empty($response)) {
preg_match('/callback\(\s+(.*?)\s+\)/i', $response, $match);
$result = json_decode($match[1], true);
preg_match('/callback\(\s+(.*?)\s+\)/i', $response, $matches);
$result = json_decode($matches[1], true);
}
if (!isset($result['openid'])) {
throw new \Exception("Fetch Openid Failed:{$response}");
throw new \Exception("Fetch OpenId Failed:{$response}");
}
return $result['openid'];
@ -98,16 +98,15 @@ class QQ extends OAuth
protected function parseUserInfo($response)
{
$data = json_decode($response, true);
if ($data['ret'] != 0) {
throw new \Exception("Fetch User Info Failed{$data['msg']}");
if (isset($data['ret']) && $data['ret'] != 0) {
throw new \Exception("Fetch User Info Failed:{$response}");
}
$userInfo['type'] = 'QQ';
$userInfo['id'] = $this->openId;
$userInfo['name'] = $data['nickname'];
$userInfo['nick'] = $data['nickname'];
$userInfo['head'] = $data['figureurl_2'];
$userInfo['avatar'] = $data['figureurl'];
return $userInfo;
}

View File

@ -1,21 +1,22 @@
<?php
namespace App\Library\OAuth;
namespace App\Services\OAuth;
use App\Library\OAuth;
use App\Services\OAuth;
class WeiBo extends OAuth
{
const AUTHORIZE_URL = 'https://api.weibo.com/oauth2/authorize';
const ACCESS_TOKEN_URL = 'https://api.weibo.com/oauth2/oauth2/access_token';
const ACCESS_TOKEN_URL = 'https://api.weibo.com/oauth2/access_token';
const USER_INFO_URL = 'https://api.weibo.com/2/users/show.json';
public function getAuthorizeUrl()
{
$params = [
'client_id' => $this->appId,
'client_id' => $this->clientId,
'redirect_uri' => $this->redirectUri,
'state' => $this->getState(),
'response_type' => 'code',
];
@ -26,8 +27,8 @@ class WeiBo extends OAuth
{
$params = [
'code' => $code,
'client_id' => $this->appId,
'client_secret' => $this->appSecret,
'client_id' => $this->clientId,
'client_secret' => $this->clientSecret,
'redirect_uri' => $this->redirectUri,
'grant_type' => 'authorization_code',
];
@ -59,8 +60,8 @@ class WeiBo extends OAuth
private function parseAccessToken($response)
{
$data = json_decode($response, true);
if (!isset($data['access_token']) || isset($data['uid'])) {
if (!isset($data['access_token']) || !isset($data['uid'])) {
throw new \Exception("Fetch Access Token Failed:{$response}");
}
@ -72,16 +73,15 @@ class WeiBo extends OAuth
private function parseUserInfo($response)
{
$data = json_decode($response, true);
if ($data['error_code'] != 0) {
throw new \Exception("Fetch User Info Failed:{$data['error']}");
if (isset($data['error_code']) && $data['error_code'] != 0) {
throw new \Exception("Fetch User Info Failed:{$response}");
}
$userInfo['type'] = 'WEIBO';
$userInfo['id'] = $data['id'];
$userInfo['name'] = $data['name'];
$userInfo['nick'] = $data['screen_name'];
$userInfo['head'] = $data['avatar_large'];
$userInfo['avatar'] = $data['profile_image_url'];
return $userInfo;
}

View File

@ -1,8 +1,8 @@
<?php
namespace App\Library\OAuth;
namespace App\Services\OAuth;
use App\Library\OAuth;
use App\Services\OAuth;
class WeiXin extends OAuth
{
@ -14,11 +14,11 @@ class WeiXin extends OAuth
public function getAuthorizeUrl()
{
$params = [
'appid' => $this->appId,
'appid' => $this->clientId,
'redirect_uri' => $this->redirectUri,
'state' => $this->getState(),
'response_type' => 'code',
'scope' => 'snsapi_login',
'state' => 'dev',
];
return self::AUTHORIZE_URL . '?' . http_build_query($params);
@ -28,8 +28,8 @@ class WeiXin extends OAuth
{
$params = [
'code' => $code,
'appid' => $this->appId,
'secret' => $this->appSecret,
'appid' => $this->clientId,
'secret' => $this->clientSecret,
'grant_type' => 'authorization_code',
];
@ -62,7 +62,7 @@ class WeiXin extends OAuth
$data = json_decode($response, true);
if (isset($data['errcode']) && $data['errcode'] != 0) {
throw new \Exception("Fetch Access Token Failed:{$data['errmsg']}");
throw new \Exception("Fetch Access Token Failed:{$response}");
}
$this->openId = $data['openid'];
@ -73,16 +73,15 @@ class WeiXin extends OAuth
private function parseUserInfo($response)
{
$data = json_decode($response, true);
if (isset($data['errcode']) && $data['errcode'] != 0) {
throw new \Exception("Fetch User Info Failed:{$data['errmsg']}");
throw new \Exception("Fetch User Info Failed:{$response}");
}
$userInfo['type'] = 'WEIXIN';
$userInfo['name'] = $data['name'];
$userInfo['nick'] = $data['screen_name'];
$userInfo['head'] = $data['avatar_large'];
$userInfo['id'] = $data['openid'];
$userInfo['name'] = $data['nickname'];
$userInfo['avatar'] = $data['headimgurl'];
return $userInfo;
}

View File

@ -0,0 +1,41 @@
<?php
namespace App\Validators;
use App\Exceptions\BadRequest as BadRequestException;
use App\Repos\Connect as ConnectRepo;
class Connect extends Validator
{
public function checkConnect($id)
{
return $this->checkConnectById($id);
}
public function checkConnectById($id)
{
$connectRepo = new ConnectRepo();
$connect = $connectRepo->findById($id);
if (!$connect) {
throw new BadRequestException('connect.not_found');
}
return $connect;
}
public function checkConnectByOpenId($openId, $provider)
{
$connectRepo = new ConnectRepo();
$connect = $connectRepo->findByOpenId($openId, $provider);
if (!$connect) {
throw new BadRequestException('connect.not_found');
}
return $connect;
}
}

View File

@ -0,0 +1,92 @@
<?php
use Phinx\Db\Adapter\MysqlAdapter;
class CreateConnectTable extends Phinx\Migration\AbstractMigration
{
public function change()
{
$this->table('kg_connect', [
'id' => false,
'primary_key' => ['id'],
'engine' => 'InnoDB',
'encoding' => 'utf8mb4',
'collation' => 'utf8mb4_general_ci',
'comment' => '',
'row_format' => 'DYNAMIC',
])
->addColumn('id', 'integer', [
'null' => false,
'limit' => MysqlAdapter::INT_REGULAR,
'identity' => 'enable',
'comment' => '主键编号',
])
->addColumn('user_id', 'integer', [
'null' => false,
'default' => '0',
'limit' => MysqlAdapter::INT_REGULAR,
'comment' => '用户编号',
'after' => 'id',
])
->addColumn('open_id', 'string', [
'null' => false,
'default' => '',
'limit' => 50,
'collation' => 'utf8mb4_general_ci',
'encoding' => 'utf8mb4',
'comment' => '开放ID',
'after' => 'user_id',
])
->addColumn('open_name', 'string', [
'null' => false,
'default' => '',
'limit' => 30,
'collation' => 'utf8mb4_general_ci',
'encoding' => 'utf8mb4',
'comment' => '开放名称',
'after' => 'open_id',
])
->addColumn('open_avatar', 'string', [
'null' => false,
'default' => '',
'limit' => 150,
'collation' => 'utf8mb4_general_ci',
'encoding' => 'utf8mb4',
'comment' => '开放头像',
'after' => 'open_name',
])
->addColumn('provider', 'integer', [
'null' => false,
'default' => '0',
'limit' => MysqlAdapter::INT_REGULAR,
'comment' => '提供方',
'after' => 'open_avatar',
])
->addColumn('deleted', 'integer', [
'null' => false,
'default' => '0',
'limit' => MysqlAdapter::INT_REGULAR,
'comment' => '删除标识',
'after' => 'provider',
])
->addColumn('create_time', 'integer', [
'null' => false,
'default' => '0',
'limit' => MysqlAdapter::INT_REGULAR,
'comment' => '创建时间',
'after' => 'deleted',
])
->addColumn('update_time', 'integer', [
'null' => false,
'default' => '0',
'limit' => MysqlAdapter::INT_REGULAR,
'comment' => '更新时间',
'after' => 'create_time',
])
->addIndex(['open_id', 'provider'], [
'name' => 'openid_provider',
'unique' => false,
])
->create();
}
}

View File

@ -0,0 +1,85 @@
<?php
declare(strict_types=1);
use Phinx\Migration\AbstractMigration;
final class InsertOauthSettingData extends AbstractMigration
{
public function up()
{
$rows = [
[
'section' => 'oauth.qq',
'item_key' => 'enabled',
'item_value' => '0',
],
[
'section' => 'oauth.qq',
'item_key' => 'client_id',
'item_value' => '',
],
[
'section' => 'oauth.qq',
'item_key' => 'client_secret',
'item_value' => '',
],
[
'section' => 'oauth.qq',
'item_key' => 'redirect_uri',
'item_value' => '',
],
[
'section' => 'oauth.weixin',
'item_key' => 'enabled',
'item_value' => '0',
],
[
'section' => 'oauth.weixin',
'item_key' => 'client_id',
'item_value' => '',
],
[
'section' => 'oauth.weixin',
'item_key' => 'client_secret',
'item_value' => '',
],
[
'section' => 'oauth.weixin',
'item_key' => 'redirect_uri',
'item_value' => '',
],
[
'section' => 'oauth.weibo',
'item_key' => 'enabled',
'item_value' => '0',
],
[
'section' => 'oauth.weibo',
'item_key' => 'client_id',
'item_value' => '',
],
[
'section' => 'oauth.weibo',
'item_key' => 'client_secret',
'item_value' => '',
],
[
'section' => 'oauth.weibo',
'item_key' => 'redirect_uri',
'item_value' => '',
],
];
$this->table('kg_setting')->insert($rows)->save();
}
public function down()
{
$this->execute("DELETE FROM kg_setting WHERE section = 'oauth.qq'");
$this->execute("DELETE FROM kg_setting WHERE section = 'oauth.weixin'");
$this->execute("DELETE FROM kg_setting WHERE section = 'oauth.weibo'");
}
}

View File

@ -1188,7 +1188,7 @@
}
.login-wrap .link {
margin-bottom: 30px;
margin-bottom: 20px;
font-size: 12px;
text-align: center;
}
@ -1202,6 +1202,26 @@
color: #999;
}
.login-wrap .oauth {
text-align: center;
}
.login-wrap .oauth a {
margin: 0 10px;
}
.login-qq {
color: dodgerblue;
}
.login-wechat {
color: green;
}
.login-weibo {
color: red;
}
.user-profile {
position: relative;
}
@ -1537,9 +1557,12 @@
margin-right: 0;
}
.security-item-list {
margin-top: -20px;
}
.security-item {
padding: 15px;
line-height: 50px;
line-height: 80px;
border-bottom: 1px dashed #ccc;
}
@ -1563,6 +1586,29 @@
float: right;
}
.connect-list {
margin-bottom: 20px;
}
.open-avatar img {
width: 16px;
height: 16px;
border-radius: 100%;
}
.connect-tips {
color: #666;
margin-bottom: 20px;
}
.oauth-list {
margin-bottom: 20px;
}
.oauth-list a {
margin: 0 10px;
}
.order-filter {
padding: 15px 20px;
}