// +---------------------------------------------------------------------- declare(strict_types=1); namespace ywxapp\controller; use ywxapp\library\SkinVariables; use ywxapp\model\MemberUser; /** * 会员中心控制器基类(MemberBase) * * 同时服务于两类场景: * 1) 会员中心本体(app/member 应用):view_path = app/member/view/,layout = common/layout * 2) 插件用户中心(addon/<插件>/controller/member/):view_path = addon/<插件>/view/member/,layout = member/layout * * 鉴权:容器 auth 在 AppService 中默认已绑定 \ywxapp\library\Auth(会员鉴权), * 故会员登录态由 BaseController::verifyAuth 自动校验(设置 $noNeedLogin 可放行)。 * * 用法: * // 会员中心本体控制器 * namespace app\member\controller; * use ywxapp\controller\MemberBase; * class Index extends MemberBase { ... } * * // 插件用户中心控制器 * namespace addon\blog\controller\member; * use ywxapp\controller\MemberBase; * class Index extends MemberBase { ... } */ class MemberBase extends BaseController { /** * 视图类实例 * @var \think\View */ protected $view; /** * 控制器初始化 */ public function _initialize() { MemberUser::ensureSchema(); // 用户中心核心表自愈(缺表/缺列兜底) $this->view = $this->app->view; $appName = $this->app->http->getName(); // 全局 layout 模式:会员中心本体套 view/layout,插件用户中心套各自的 view/member/layout。 // 不需要套 layout 的页面(如会员中心 iframe 主框架页)可在控制器内调用 $this->view->layout(false) 关闭。 $layout = 'common/layout'; if ($appName === 'member') { // 会员中心本体:视图根目录为 app/member/view/,layout 为 view/layout.html $viewPath = $this->app->getAppPath() . 'view' . DIRECTORY_SEPARATOR; } else { // 插件用户中心:视图根目录为 addon/<插件>/view/member/ $addon = $this->currentAddon(); $viewPath = $addon ? root_path() . 'addon' . DIRECTORY_SEPARATOR . $addon . DIRECTORY_SEPARATOR . 'view' . DIRECTORY_SEPARATOR . 'member' . DIRECTORY_SEPARATOR : $this->app->getAppPath() . 'view' . DIRECTORY_SEPARATOR . 'member' . DIRECTORY_SEPARATOR; // 插件内使用插件自己的 view/member/layout.html(view_path 已含 member/) $layout = 'common/layout'; } $this->view->config([ 'view_suffix' => 'html', 'view_path' => $viewPath, 'layout_on' => true, 'layout_name' => $layout, ]); // 登录与权限校验:统一走 Base 的 verifyAuth + $noNeedLogin 机制 // (未登录且非免登录 action 时整页 302 跳登录页,AJAX 返回 401,与后台一致)。 // 注意:verifyAuth 内部会 tokenParse() 还原登录态,无需在中间件重复处理。 $this->auth->verifyAuth($this->noNeedLogin, $this->noNeedVerify); if ($this->auth && $this->auth->isLogin) { $this->view->assign('member', $this->auth->info); } $siteConf = \ywxapp\model\Configure::cache(true)->column('value', 'name'); $this->view->assign('siteConf', $siteConf); $links = \ywxapp\model\Links::cache(true)->where('status', 1)->order('sort desc')->select(); $this->view->assign('links', $links); // 注入站点信息(route_base / site.module / site.controller 等),供前端 route.js 使用 $this->assignSite(); $this->initialize(); } /** * 子类初始化钩子 */ protected function initialize() {} /** * 获取当前登录会员信息(未登录直接报错) * @return \ywxapp\model\Member */ protected function user() { if ($this->auth && $this->auth->isLogin) { return $this->auth->info; } $this->result->error('请先登录会员中心'); } /** * 当前插件名(addon\\...) * 由 appPath(addon/<插件>/)剥 rootPath 取首段,无需正则。 */ protected function currentAddon(): string { $rel = trim(substr($this->app->getAppPath(), strlen($this->app->getRootPath())), DIRECTORY_SEPARATOR); $seg = explode(DIRECTORY_SEPARATOR, $rel); return $seg[0] === 'addon' && isset($seg[1]) ? $seg[1] : ''; } /** * 渲染模板:优先使用已激活模板的覆盖页(Discuz 式局部覆盖),未命中回退插件默认视图。 */ protected function fetch($template = '', $vars = [], $replace = [], $config = []) { $addon = $this->currentAddon(); $restore = null; if ($addon && $overlay = \ywxapp\library\SkinOverlay::resolve($addon, 'member')) { // 把视图根目录切到「插件视图 + 皮肤覆盖」合并层,使 extend 的 layout 也走皮肤 $restore = \think\facade\View::getConfig('view_path'); \think\facade\View::config(['view_path' => $overlay]); } $result = \think\facade\View::fetch($template, $vars, $replace, $config); if ($restore !== null) { \think\facade\View::config(['view_path' => $restore]); } // 注入皮肤变量 CSS(Discuz 式配色层,无需改 HTML) $style = SkinVariables::styleTag($addon, 'member'); if ($style !== '' && ($pos = stripos($result, '')) !== false) { $result = substr($result, 0, $pos) . $style . "\n" . substr($result, $pos); } elseif ($style !== '') { $result = $style . $result; } return $result; } }