tp/app/index/controller/Index.php
2026-03-20 19:48:32 +08:00

526 lines
17 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
declare(strict_types=1);
namespace app\index\controller;
use app\model\Banner;
use app\index\BaseController;
use app\model\Cms\FrontMenu;
use app\model\Cms\OnePage;
use app\model\System\SystemSiteSetting;
use app\service\ThemeService;
use think\db\exception\DbException;
use think\facade\Env;
use think\facade\Request;
use app\model\Cms\TemplateSiteConfig;
use app\model\Cms\Friendlink;
use app\model\Cms\Services;
use app\model\Cms\Products;
use app\model\Tenant\Tenant;
class Index extends BaseController
{
private ThemeService $themeService;
public function __construct()
{
$this->themeService = new ThemeService();
}
public function index()
{
// 获取请求对象
$request = $this->request ?? Request::instance();
// 优先从请求对象获取中间件设置的租户ID通过域名解析
$tid = $request->tenantId ?? 0;
// 兼容从header获取
if ($tid == 0) {
$headerTid = $request->header('X-Tenant-Id');
$tid = $headerTid ? (int) $headerTid : 0;
}
// 调试检查tid
if ($tid === 0) {
die('tenantId未获取到请检查域名解析');
}
// 获取租户选择的模板
$themeKey = 'default';
if ($tid > 0) {
// 不使用软删除过滤
$config = TemplateSiteConfig::where('tid', $tid)
->where('key', 'current_theme')
->withoutField('delete_time')
->find();
$themeKey = $config['value'] ?? 'default';
}
// 模板路径:/themes/{theme_key}/优先使用index.php其次index.html
$themeBasePath = root_path() . 'public' . DIRECTORY_SEPARATOR . 'themes' . DIRECTORY_SEPARATOR . $themeKey;
$themeUrlPath = '/themes/' . $themeKey . '/';
if (is_file($themeBasePath . DIRECTORY_SEPARATOR . 'index.php')) {
// 渲染PHP模板直接include
return $this->renderPhpTemplate($themeBasePath . DIRECTORY_SEPARATOR . 'index.php', $themeUrlPath);
} elseif (is_file($themeBasePath . DIRECTORY_SEPARATOR . 'index.html')) {
// 读取HTML模板内容
$content = file_get_contents($themeBasePath . DIRECTORY_SEPARATOR . 'index.html');
// 修复资源路径:将相对路径转换为绝对路径
$content = $this->fixTemplateAssets($content, $themeUrlPath);
return response($content, 200, ['Content-Type' => 'text/html; charset=utf-8']);
}
// 模板不存在,返回默认
return view('index/index');
}
/**
* 通用页面渲染
* 根据页面名称(如 about, blog, portfolio 等)渲染对应模板
* @param string $page 页面名称
* @return \think\response|\think\response\View
*/
public function page(string $page = 'index')
{
$request = $this->request ?? Request::instance();
// 获取租户ID
$tid = $request->tenantId ?? 0;
if ($tid == 0) {
$headerTid = $request->header('X-Tenant-Id');
$tid = $headerTid ? (int) $headerTid : 0;
}
if ($tid === 0) {
die('tenantId未获取到请检查域名解析');
}
// 获取租户选择的模板
$themeKey = 'default';
if ($tid > 0) {
$config = TemplateSiteConfig::where('tid', $tid)
->where('key', 'current_theme')
->withoutField('delete_time')
->find();
$themeKey = $config['value'] ?? 'default';
}
// 模板路径
$themeBasePath = root_path() . 'public' . DIRECTORY_SEPARATOR . 'themes' . DIRECTORY_SEPARATOR . $themeKey;
$themeUrlPath = '/themes/' . $themeKey . '/';
// 安全检查:只允许字母、数字、中划线
$page = preg_replace('/[^a-zA-Z0-9\-]/', '', $page);
if (empty($page)) {
$page = 'index';
}
// 查找 PHP 模板(优先)或 HTML 模板
$templateFile = $themeBasePath . DIRECTORY_SEPARATOR . $page . '.php';
$templateHtmlFile = $themeBasePath . DIRECTORY_SEPARATOR . $page . '.html';
if (is_file($templateFile)) {
return $this->renderPhpTemplate($templateFile, $themeUrlPath);
} elseif (is_file($templateHtmlFile)) {
$content = file_get_contents($templateHtmlFile);
$content = $this->fixTemplateAssets($content, $themeUrlPath);
return response($content, 200, ['Content-Type' => 'text/html; charset=utf-8']);
}
// 模板不存在
return response('页面不存在', 404, ['Content-Type' => 'text/html; charset=utf-8']);
}
/**
* 文章详情页
* @param int $id 文章ID
* @return \think\response
*/
public function articleDetail(int $id = 0)
{
$request = $this->request ?? Request::instance();
// 获取租户ID
$tid = $request->tenantId ?? 0;
if ($tid == 0) {
$headerTid = $request->header('X-Tenant-Id');
$tid = $headerTid ? (int) $headerTid : 0;
}
if ($tid === 0) {
die('tenantId未获取到请检查域名解析');
}
// 获取租户选择的模板
$themeKey = 'default';
if ($tid > 0) {
$config = TemplateSiteConfig::where('tid', $tid)
->where('key', 'current_theme')
->withoutField('delete_time')
->find();
$themeKey = $config['value'] ?? 'default';
}
// 模板路径
$themeBasePath = root_path() . 'public' . DIRECTORY_SEPARATOR . 'themes' . DIRECTORY_SEPARATOR . $themeKey;
$themeUrlPath = '/themes/' . $themeKey . '/';
// 查找文章详情模板
$templateFile = $themeBasePath . DIRECTORY_SEPARATOR . 'article_detail.php';
$templateHtmlFile = $themeBasePath . DIRECTORY_SEPARATOR . 'article_detail.html';
$blogDetailsFile = $themeBasePath . DIRECTORY_SEPARATOR . 'blog-details.php';
$blogDetailsHtmlFile = $themeBasePath . DIRECTORY_SEPARATOR . 'blog-details.html';
// 优先使用 article_detail 模板
if (is_file($templateFile)) {
return $this->renderPhpTemplate($templateFile, $themeUrlPath);
} elseif (is_file($templateHtmlFile)) {
$content = file_get_contents($templateHtmlFile);
$content = $this->fixTemplateAssets($content, $themeUrlPath);
return response($content, 200, ['Content-Type' => 'text/html; charset=utf-8']);
} elseif (is_file($blogDetailsFile)) {
return $this->renderPhpTemplate($blogDetailsFile, $themeUrlPath);
} elseif (is_file($blogDetailsHtmlFile)) {
$content = file_get_contents($blogDetailsHtmlFile);
$content = $this->fixTemplateAssets($content, $themeUrlPath);
return response($content, 200, ['Content-Type' => 'text/html; charset=utf-8']);
}
return response('文章不存在', 404, ['Content-Type' => 'text/html; charset=utf-8']);
}
/**
* 修复模板中的资源路径
* 将相对路径 (assets/, css/, js/, images/) 转换为绝对路径 (/themes/xxx/)
*/
private function fixTemplateAssets(string $content, string $themeUrlPath): string
{
// 需要修复的资源目录
$assetsDirs = ['assets', 'css', 'js', 'images', 'img', 'fonts', 'plugins', 'libs'];
foreach ($assetsDirs as $dir) {
// 匹配 src="assets/..." 或 href="css/..." 等相对路径
// 不匹配已以 / 开头的绝对路径
$content = preg_replace(
'/(src|href)=["\'](' . $dir . '\/[^"\']*)["\']/',
'$1="' . $themeUrlPath . '$2"',
$content
);
// 匹配 src='assets/...'
$content = preg_replace(
'/(src|href)=[\']([^\/][^\'\"]*\/[^\'\"]*)[\']/',
'$1="' . $themeUrlPath . '$2"',
$content
);
}
return $content;
}
/**
* 渲染PHP模板文件
*/
private function renderPhpTemplate(string $templateFile, string $themeUrlPath = '')
{
// 定义模板基础URL常量供模板使用
if (!defined('THEME_URL')) {
define('THEME_URL', $themeUrlPath);
}
ob_start();
// 获取URL参数
$getParams = Request::get();
extract($getParams, EXTR_OVERWRITE);
include $templateFile;
$content = ob_get_clean();
// 修复PHP模板输出中的资源路径
$content = $this->fixTemplateAssets($content, $themeUrlPath);
return response($content, 200, ['Content-Type' => 'text/html; charset=utf-8']);
}
/**
* 前端初始化接口 - 返回当前模板和填充数据
* @return \think\response\Json
*/
public function init()
{
// 优先从请求对象获取中间件设置的租户ID通过域名解析
$tid = $this->request->tenantId ?? 0;
// 兼容URL参数传递的tid作为备用
$tid = $tid > 0 ? $tid : Request::param('tid', 0, 'int');
// 从TemplateSiteConfig获取配置根据租户ID
$config = null;
if ($tid > 0) {
$config = TemplateSiteConfig::where('tid', $tid)
->where('key', 'current_theme')
->find();
}
$themeKey = $config['value'] ?? 'default';
// 模板路径:/themes/{theme_key}/优先使用index.php其次index.html
$themeBasePath = root_path() . 'public' . DIRECTORY_SEPARATOR . 'themes' . DIRECTORY_SEPARATOR . $themeKey;
if (is_file($themeBasePath . DIRECTORY_SEPARATOR . 'index.php')) {
$themePath = '/themes/' . $themeKey . '/index.php';
} else {
$themePath = '/themes/' . $themeKey . '/index.html';
}
return json([
'code' => 200,
'msg' => 'success',
'data' => [
'theme_key' => $themeKey,
'theme_path' => $themePath
]
]);
}
/**
* 获取日志列表
*/
public function getLogs()
{
$logPath = Env::get('runtime_path') . 'log/';
$logs = [];
// 读取最近的日志文件
$files = glob($logPath . '*.log');
if ($files) {
// 按修改时间排序,最新的在前
usort($files, function ($a, $b) {
return filemtime($b) - filemtime($a);
});
// 读取最新的日志文件
$latestFile = reset($files);
if (file_exists($latestFile)) {
$content = file_get_contents($latestFile);
$lines = explode("\n", $content);
// 倒序遍历只取最近的100条
$count = 0;
for ($i = count($lines) - 1; $i >= 0 && $count < 100; $i--) {
if (!empty(trim($lines[$i]))) {
$logs[] = $lines[$i];
$count++;
}
}
// 再倒序回来,使最新的日志在最后
$logs = array_reverse($logs);
}
}
return json([
'code' => 200,
'msg' => 'success',
'data' => $logs
]);
}
/**
* 获取前端导航接口
* @return \think\response\Json
*/
public function getHeadMenu()
{
try {
// 获取所有未删除的菜单
$frontMenus = FrontMenu::where('delete_time', null)
->field('id,pid,title,type,image,path,component_path,sort,desc')
->order('sort', 'desc')
->select()
->toArray(); // 转换为数组
// 使用 buildFrontMenuTree 构建树形结构
$treeFrontMenus = $this->buildFrontMenuTree($frontMenus);
return json([
'code' => 200,
'msg' => 'success',
'data' => $treeFrontMenus
]);
} catch (DbException $e) {
return json([
'code' => 500,
'msg' => 'fail' . $e->getMessage(),
'data' => $e->getTraceAsString()
]);
}
}
/**
* 构建前端导航树形结构
* @param array $frontMenus 前端导航列表
* @param int $pid 父前端导航ID
* @return array
*/
private function buildFrontMenuTree(array $frontMenus, int $pid = 0): array
{
$tree = [];
foreach ($frontMenus as $frontMenu) {
// 将null的pid视为0处理
$menuPid = is_null($frontMenu['pid']) ? 0 : $frontMenu['pid'];
if ($menuPid == $pid) {
$children = $this->buildFrontMenuTree($frontMenus, $frontMenu['id']);
if (!empty($children)) {
$frontMenu['children'] = $children;
}
$tree[] = $frontMenu;
}
}
// 按 sort 字段降序排序
usort($tree, function ($a, $b) {
$sortA = $a['sort'] ?? 0;
$sortB = $b['sort'] ?? 0;
return $sortB - $sortA;
});
return $tree;
}
/**
* 根据路径获取单页内容
* @param string $path 路由路径
* @return \think\response\Json
*/
public function getOnePageByPath($path = '')
{
try {
// 从路由参数获取路径
if (empty($path)) {
// 如果没有路由参数,尝试从 pathinfo 获取
$requestPath = $this->request->pathinfo();
$path = str_replace('onepage/', '', $requestPath);
}
// 确保路径以 / 开头
if (empty($path) || $path[0] !== '/') {
$path = '/' . $path;
}
// 解码路径
$path = urldecode($path);
// 查找对应路径的单页
$onePage = OnePage::where('path', $path)
->where('status', 1)
->where('delete_time', null)
->find();
if (!$onePage) {
return json([
'code' => 404,
'msg' => '单页不存在或已禁用',
'data' => null
]);
}
return json([
'code' => 200,
'msg' => 'success',
'data' => $onePage->toArray()
]);
} catch (DbException $e) {
return json([
'code' => 500,
'msg' => 'fail' . $e->getMessage(),
'data' => null
]);
}
}
/**
* 获取首页综合数据
* @return \think\response\Json
*/
public function getHomeData()
{
$baseUrl = Request::param('baseUrl', '');
if (empty($baseUrl)) {
return json(['code' => 400, 'msg' => '缺少 baseUrl 参数']);
}
try {
// 通过域名获取租户ID
$tid = BaseController::getTenantIdByDomain($baseUrl);
if (empty($tid)) {
return json([
'code' => 400,
'msg' => '无法识别租户信息',
'data' => []
]);
}
// 获取站点基础信息 (normalinfos)
$normalInfos = SystemSiteSetting::where('tid', $tid)
->field('sitename,logo,logow,ico,description,copyright,companyname,icp,companyintroduction')
->find();
// 获取特色服务列表
$servicesList = Services::where('delete_time', null)
->where('tid', $tid)
->order('sort', 'asc')
->field('id,title,desc,thumb,url')
->select()
->toArray();
// 获取企业产品列表
$productsList = Products::where('delete_time', null)
->where('tid', $tid)
->order('sort', 'asc')
->field('id,title,desc,content,thumb,url')
->select()
->toArray();
// 获取友情链接列表
$friendlinkList = Friendlink::where('delete_time', null)
->where('tid', $tid)
->where('status', 1)
->order('sort', 'asc')
->field('id,link_name,link_url,link_logo')
->select()
->toArray();
// 获取联系方式
$contact = Tenant::where('id', $tid)
->where('status', 1)
->where('delete_time', null)
->field('contact_phone,contact_email,address,worktime')
->find();
// 合并返回
return json([
'code' => 200,
'msg' => 'success',
'data' => [
'normal' => $normalInfos ?: (object) [],
'contact' => $contact ?: (object) [],
'services' => $servicesList,
'products' => $productsList,
'links' => $friendlinkList
]
]);
} catch (\Exception $e) {
return json([
'code' => 500,
'msg' => '服务器错误:' . $e->getMessage(),
'data' => null
]);
}
}
}