增加企业产品分类
This commit is contained in:
parent
9281c1dc35
commit
2a6b14f698
206
app/admin/controller/Cms/Products/ProductsTypesController.php
Normal file
206
app/admin/controller/Cms/Products/ProductsTypesController.php
Normal file
@ -0,0 +1,206 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\admin\controller\Cms\Products;
|
||||
|
||||
use app\admin\BaseController;
|
||||
use app\model\Cms\ProductsTypes;
|
||||
use think\exception\ValidateException;
|
||||
use think\response\Json;
|
||||
|
||||
/**
|
||||
* 产品分类管理控制器
|
||||
*/
|
||||
class ProductsTypesController extends BaseController
|
||||
{
|
||||
/**
|
||||
* 获取产品分类列表
|
||||
* @return Json
|
||||
*/
|
||||
public function productsTypesList(): Json
|
||||
{
|
||||
try {
|
||||
$page = (int)$this->request->param('page', 1);
|
||||
$limit = (int)$this->request->param('limit', 10);
|
||||
$keyword = (string)$this->request->param('keyword', '');
|
||||
|
||||
$query = ProductsTypes::where('delete_time', null)
|
||||
->where('tid', $this->getTenantId());
|
||||
|
||||
if ($keyword !== '') {
|
||||
$query->where('title', 'like', '%' . $keyword . '%');
|
||||
}
|
||||
|
||||
$total = $query->count();
|
||||
|
||||
$list = $query->order('sort', 'asc')
|
||||
->order('id', 'desc')
|
||||
->page($page, $limit)
|
||||
->select()
|
||||
->toArray();
|
||||
|
||||
return json([
|
||||
'code' => 200,
|
||||
'msg' => 'success',
|
||||
'data' => [
|
||||
'list' => $list,
|
||||
'total' => $total
|
||||
]
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
return json([
|
||||
'code' => 500,
|
||||
'msg' => '获取失败:' . $e->getMessage(),
|
||||
'data' => [
|
||||
'list' => [],
|
||||
'total' => 0
|
||||
]
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加产品分类
|
||||
* @return Json
|
||||
*/
|
||||
public function addProductsTypes(): Json
|
||||
{
|
||||
try {
|
||||
$data = $this->request->param();
|
||||
|
||||
$title = (string)($data['title'] ?? '');
|
||||
if ($title === '') {
|
||||
return json([
|
||||
'code' => 400,
|
||||
'msg' => '分类名称不能为空'
|
||||
]);
|
||||
}
|
||||
|
||||
$type = new ProductsTypes();
|
||||
$type->tid = $this->getTenantId();
|
||||
$type->pid = isset($data['pid']) ? (int)$data['pid'] : 0;
|
||||
$type->title = $title;
|
||||
$type->desc = (string)($data['desc'] ?? '');
|
||||
$type->sort = isset($data['sort']) ? (int)$data['sort'] : 0;
|
||||
$type->create_time = date('Y-m-d H:i:s');
|
||||
$type->update_time = date('Y-m-d H:i:s');
|
||||
$type->save();
|
||||
|
||||
$this->logSuccess('产品分类管理', '添加分类', ['id' => $type->id]);
|
||||
|
||||
return json([
|
||||
'code' => 200,
|
||||
'msg' => '添加成功',
|
||||
'data' => $type->toArray()
|
||||
]);
|
||||
} catch (ValidateException $e) {
|
||||
return json([
|
||||
'code' => 400,
|
||||
'msg' => $e->getError()
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
$this->logFail('产品分类管理', '添加分类', $e->getMessage());
|
||||
return json([
|
||||
'code' => 500,
|
||||
'msg' => '添加失败:' . $e->getMessage()
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新产品分类
|
||||
* @param int $id
|
||||
* @return Json
|
||||
*/
|
||||
public function editProductsTypes(int $id): Json
|
||||
{
|
||||
try {
|
||||
$data = $this->request->param();
|
||||
|
||||
$type = ProductsTypes::where('id', $id)
|
||||
->where('tid', $this->getTenantId())
|
||||
->where('delete_time', null)
|
||||
->find();
|
||||
|
||||
if (!$type) {
|
||||
return json([
|
||||
'code' => 404,
|
||||
'msg' => '分类不存在'
|
||||
]);
|
||||
}
|
||||
|
||||
if (isset($data['title'])) $type->title = (string)$data['title'];
|
||||
if (isset($data['pid'])) $type->pid = (int)$data['pid'];
|
||||
if (isset($data['desc'])) $type->desc = (string)$data['desc'];
|
||||
if (isset($data['sort'])) $type->sort = (int)$data['sort'];
|
||||
|
||||
if ((string)$type->title === '') {
|
||||
return json([
|
||||
'code' => 400,
|
||||
'msg' => '分类名称不能为空'
|
||||
]);
|
||||
}
|
||||
|
||||
$type->update_time = date('Y-m-d H:i:s');
|
||||
$type->save();
|
||||
|
||||
$this->logSuccess('产品分类管理', '更新分类', ['id' => $id]);
|
||||
|
||||
return json([
|
||||
'code' => 200,
|
||||
'msg' => '更新成功',
|
||||
'data' => $type->toArray()
|
||||
]);
|
||||
} catch (ValidateException $e) {
|
||||
return json([
|
||||
'code' => 400,
|
||||
'msg' => $e->getError()
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
$this->logFail('产品分类管理', '更新分类', $e->getMessage());
|
||||
return json([
|
||||
'code' => 500,
|
||||
'msg' => '更新失败:' . $e->getMessage()
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除产品分类(软删除)
|
||||
* @param int $id
|
||||
* @return Json
|
||||
*/
|
||||
public function deleteProductsTypes(int $id): Json
|
||||
{
|
||||
try {
|
||||
$type = ProductsTypes::where('id', $id)
|
||||
->where('tid', $this->getTenantId())
|
||||
->where('delete_time', null)
|
||||
->find();
|
||||
|
||||
if (!$type) {
|
||||
return json([
|
||||
'code' => 404,
|
||||
'msg' => '分类不存在'
|
||||
]);
|
||||
}
|
||||
|
||||
$type->delete();
|
||||
|
||||
$this->logSuccess('产品分类管理', '删除分类', ['id' => $id]);
|
||||
|
||||
return json([
|
||||
'code' => 200,
|
||||
'msg' => '删除成功'
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
$this->logFail('产品分类管理', '删除分类', $e->getMessage());
|
||||
return json([
|
||||
'code' => 500,
|
||||
'msg' => '删除失败:' . $e->getMessage()
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -107,7 +107,7 @@ class MenuController extends BaseController
|
||||
$menus = SystemMenu::where('delete_time', null)
|
||||
->where('status', 1)
|
||||
->whereIn('id', $allMenuIds)
|
||||
->field('id,pid,title,path,component_path,icon,sort')
|
||||
->field('id,pid,title,path,component_path,icon,sort,is_visible')
|
||||
->order('sort', 'asc')
|
||||
->select();
|
||||
|
||||
@ -261,6 +261,7 @@ class MenuController extends BaseController
|
||||
'title|菜单名称' => 'require|max:50',
|
||||
'type|菜单类型' => 'require|in:1,2,3',
|
||||
'status|菜单状态' => 'require|in:0,1',
|
||||
'is_visible|是否显示' => 'require|in:0,1',
|
||||
'sort|排序号' => 'integer',
|
||||
'path|路由路径' => 'max:200',
|
||||
'icon|菜单图标' => 'max:100',
|
||||
@ -274,6 +275,7 @@ class MenuController extends BaseController
|
||||
'title' => $data['title'],
|
||||
'type' => $data['type'],
|
||||
'status' => $data['status'] ?? 1,
|
||||
'is_visible' => $data['is_visible'] ?? 1,
|
||||
'sort' => $data['sort'] ?? 0,
|
||||
'path' => $data['path'] ?? '',
|
||||
'component_path' => $data['component_path'] ?? '',
|
||||
@ -329,6 +331,7 @@ class MenuController extends BaseController
|
||||
'pid|上级菜单ID' => 'integer',
|
||||
'type|菜单类型' => 'require|in:1,2,3',
|
||||
'status|菜单状态' => 'require|in:0,1',
|
||||
'is_visible|是否显示' => 'require|in:0,1',
|
||||
'sort|排序号' => 'integer',
|
||||
'path|路由路径' => 'max:200',
|
||||
'icon|菜单图标' => 'max:100',
|
||||
@ -346,6 +349,7 @@ class MenuController extends BaseController
|
||||
'icon' => $data['icon'] ?? null,
|
||||
'sort' => $data['sort'] ?? 0,
|
||||
'status' => $data['status'],
|
||||
'is_visible' => $data['is_visible'],
|
||||
'permission' => $data['permission'] ?? null,
|
||||
'remark' => $data['remark'] ?? null,
|
||||
'update_time' => date('Y-m-d H:i:s')
|
||||
|
||||
@ -6,3 +6,9 @@ Route::get('productsList', 'app\admin\controller\Cms\Products\ProductsController
|
||||
Route::post('addProducts', 'app\admin\controller\Cms\Products\ProductsController@addProducts');
|
||||
Route::put('editProducts/:id', 'app\admin\controller\Cms\Products\ProductsController@editProducts');
|
||||
Route::delete('deleteProducts/:id', 'app\admin\controller\Cms\Products\ProductsController@deleteProducts');
|
||||
|
||||
// 产品分类
|
||||
Route::get('productsTypesList', 'app\admin\controller\Cms\Products\ProductsTypesController@productsTypesList');
|
||||
Route::post('addProductsTypes', 'app\admin\controller\Cms\Products\ProductsTypesController@addProductsTypes');
|
||||
Route::put('editProductsTypes/:id', 'app\admin\controller\Cms\Products\ProductsTypesController@editProductsTypes');
|
||||
Route::delete('deleteProductsTypes/:id', 'app\admin\controller\Cms\Products\ProductsTypesController@deleteProductsTypes');
|
||||
|
||||
@ -103,11 +103,18 @@ class ArticleController extends BaseController
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取文章分类列表
|
||||
* 获取文章分类列表(mete_articles_category:当前租户 tid、启用状态)
|
||||
* 支持 baseUrl=租户前台域名,与 getCenterNews 一致,便于 api 域名跨域调用时解析 tid
|
||||
*
|
||||
* @return Json
|
||||
*/
|
||||
public function getArticleCategories(): Json
|
||||
{
|
||||
$baseUrl = $this->request->get('baseUrl', '');
|
||||
if (!empty($baseUrl)) {
|
||||
$this->tenantId = BaseController::getTenantIdByDomain($baseUrl);
|
||||
}
|
||||
|
||||
$tid = $this->getTenantId();
|
||||
|
||||
if (empty($tid)) {
|
||||
@ -120,6 +127,9 @@ class ArticleController extends BaseController
|
||||
|
||||
$articleCategories = ArticlesCategory::where('delete_time', null)
|
||||
->where('tid', $tid)
|
||||
->where('status', 1)
|
||||
->order('sort', 'asc')
|
||||
->order('id', 'asc')
|
||||
->select()
|
||||
->toArray();
|
||||
|
||||
|
||||
@ -14,19 +14,22 @@ use think\db\exception\DbException;
|
||||
|
||||
use app\model\Cms\Articles;
|
||||
use app\model\Cms\ArticlesCategory;
|
||||
|
||||
use tidy;
|
||||
|
||||
class NewsCenterController extends BaseController
|
||||
{
|
||||
/**
|
||||
* 根据域名获取新闻数据
|
||||
*
|
||||
* 返回字段:list 列表;total 当前租户下已发布新闻总数;count 当前筛选(全部或 cate)下的条数,翻页按 count 与 page_size。
|
||||
*
|
||||
* @return Json
|
||||
*/
|
||||
public function getCenterNews(): Json
|
||||
{
|
||||
$baseUrl = $this->request->get('baseUrl', '');
|
||||
|
||||
if (!empty($baseUrl)) {
|
||||
$baseUrl = (string) $this->request->param('baseUrl', '');
|
||||
|
||||
if ($baseUrl !== '') {
|
||||
$this->tenantId = BaseController::getTenantIdByDomain($baseUrl);
|
||||
}
|
||||
|
||||
@ -40,15 +43,67 @@ class NewsCenterController extends BaseController
|
||||
]);
|
||||
}
|
||||
|
||||
// 查询该租户下的文章
|
||||
$articles = Articles::published()
|
||||
// param 合并 GET,避免个别环境下仅 get 取不到查询串
|
||||
$cateId = (int) $this->request->param('cate', 0);
|
||||
$listPage = (int) $this->request->param('page', 0);
|
||||
$pageSizeReq = (int) $this->request->param('page_size', 0);
|
||||
|
||||
// 点击左侧分类:文章 cate 可能挂在子分类上,需包含「该分类 + 其下所有子分类」id
|
||||
$categoryIdsForFilter = [];
|
||||
if ($cateId > 0) {
|
||||
$categoryIdsForFilter = $this->resolveCategoryBranchIds($cateId, $tid);
|
||||
if ($categoryIdsForFilter === []) {
|
||||
$categoryIdsForFilter = [$cateId];
|
||||
}
|
||||
}
|
||||
|
||||
$baseQuery = static function () use ($tid, $cateId, $categoryIdsForFilter) {
|
||||
$q = Articles::published()
|
||||
->where('tid', $tid)
|
||||
->where('delete_time', null);
|
||||
if ($cateId > 0 && $categoryIdsForFilter !== []) {
|
||||
$q->whereIn('cate', $categoryIdsForFilter);
|
||||
}
|
||||
|
||||
return $q;
|
||||
};
|
||||
|
||||
// 当前租户下「全部」已发布新闻数(不受 cate 筛选影响)
|
||||
$totalTenantNews = (int) Articles::published()
|
||||
->where('tid', $tid)
|
||||
->where('delete_time', null)
|
||||
->order('publish_date', 'desc')
|
||||
->limit(8)
|
||||
->select();
|
||||
->count();
|
||||
|
||||
// 处理图片:如果文章image为空,则取分类的image
|
||||
// 当前列表条件下的总数:tid +(可选)cate,用于翻页总页数 = ceil(count / page_size)
|
||||
$countFiltered = (int) $baseQuery()->count();
|
||||
|
||||
$useListPagination = $listPage > 0 && $pageSizeReq > 0;
|
||||
|
||||
if ($useListPagination) {
|
||||
// 新闻中心列表页:服务端分页;不要用查询参数名 page,避免与路由占位符冲突
|
||||
$pageSizeReq = max(1, min($pageSizeReq, 50));
|
||||
$listPage = max(1, $listPage);
|
||||
$articles = $baseQuery()
|
||||
->order('publish_date', 'desc')
|
||||
->page($listPage, $pageSizeReq)
|
||||
->select();
|
||||
} else {
|
||||
// 首页等:按 limit 取前 N 条(默认 8)
|
||||
$limit = (int) $this->request->param('limit', 8);
|
||||
if ($limit < 1) {
|
||||
$limit = 8;
|
||||
}
|
||||
if ($limit > 200) {
|
||||
$limit = 200;
|
||||
}
|
||||
|
||||
$articles = $baseQuery()
|
||||
->order('publish_date', 'desc')
|
||||
->limit($limit)
|
||||
->select();
|
||||
}
|
||||
|
||||
// 处理图片:如果文章 image 为空,则取分类的 image
|
||||
foreach ($articles as &$article) {
|
||||
if (empty($article['image']) && !empty($article['cate'])) {
|
||||
$category = ArticlesCategory::where('id', $article['cate'])
|
||||
@ -59,14 +114,96 @@ class NewsCenterController extends BaseController
|
||||
}
|
||||
}
|
||||
}
|
||||
unset($article);
|
||||
|
||||
return json([
|
||||
'code' => 200,
|
||||
'msg' => 'success',
|
||||
'list' => $articles,
|
||||
'code' => 200,
|
||||
'msg' => 'success',
|
||||
'list' => $articles,
|
||||
// 当前租户下新闻总条数(全部状态为已发布)
|
||||
'total' => $totalTenantNews,
|
||||
// 当前筛选(全部或某分类)下的条数,列表翻页按 count 与 page_size 计算
|
||||
'count' => $countFiltered,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析分类筛选用的 id 列表:自身 + mete_articles_category 中 cid=该 id 的所有子孙(同 tid、未删除)
|
||||
*/
|
||||
private function resolveCategoryBranchIds(int $rootId, int $tid): array
|
||||
{
|
||||
if ($rootId <= 0 || $tid <= 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$root = ArticlesCategory::where('id', $rootId)
|
||||
->where('tid', $tid)
|
||||
->where('delete_time', null)
|
||||
->find();
|
||||
if (!$root) {
|
||||
return [$rootId];
|
||||
}
|
||||
|
||||
$ids = [$rootId];
|
||||
$queue = [$rootId];
|
||||
while ($queue !== []) {
|
||||
$pid = array_shift($queue);
|
||||
$children = ArticlesCategory::where('cid', $pid)
|
||||
->where('tid', $tid)
|
||||
->where('delete_time', null)
|
||||
->column('id');
|
||||
foreach ($children as $id) {
|
||||
$id = (int) $id;
|
||||
if ($id > 0 && !in_array($id, $ids, true)) {
|
||||
$ids[] = $id;
|
||||
$queue[] = $id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $ids;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取新闻详情
|
||||
* @param int $id 文章ID
|
||||
* @return Json
|
||||
*/
|
||||
public function getNewsDetail(int $id): Json
|
||||
{
|
||||
try {
|
||||
$baseUrl = $this->request->get('baseUrl', '');
|
||||
if (!empty($baseUrl)) {
|
||||
$this->tenantId = BaseController::getTenantIdByDomain($baseUrl);
|
||||
}
|
||||
|
||||
$tid = $this->getTenantId();
|
||||
$query = Articles::published()->where('id', $id);
|
||||
if ($tid > 0) {
|
||||
$query->where('tid', $tid);
|
||||
}
|
||||
$article = $query->find();
|
||||
if (!$article) {
|
||||
return json([
|
||||
'code' => 404,
|
||||
'msg' => '文章不存在',
|
||||
'list' => [],
|
||||
]);
|
||||
}
|
||||
return json([
|
||||
'code' => 200,
|
||||
'msg' => 'success',
|
||||
'list' => $article,
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
return json([
|
||||
'code' => 500,
|
||||
'msg' => '获取新闻详情失败:' . $e->getMessage(),
|
||||
'list' => [],
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取上一篇下一篇
|
||||
* @param int $id 文章ID
|
||||
|
||||
@ -18,6 +18,8 @@ use app\model\Cms\Friendlink;
|
||||
use app\model\Cms\Services;
|
||||
use app\model\Cms\Products;
|
||||
use app\model\Tenant\Tenant;
|
||||
use app\model\Cms\Articles;
|
||||
use app\model\Cms\ArticlesCategory;
|
||||
|
||||
class Index extends BaseController
|
||||
{
|
||||
@ -154,6 +156,12 @@ class Index extends BaseController
|
||||
die('tenantId未获取到,请检查域名解析');
|
||||
}
|
||||
|
||||
// 与 NewsCenterController::getNewsDetail 一致:已发布 + 当前租户(网页直出,非 JSON)
|
||||
$templateVars = $this->buildArticleDetailTemplateVars($id, $tid);
|
||||
if ($templateVars === null) {
|
||||
return response('文章不存在', 404, ['Content-Type' => 'text/html; charset=utf-8']);
|
||||
}
|
||||
|
||||
// 获取租户选择的模板
|
||||
$themeKey = 'default';
|
||||
if ($tid > 0) {
|
||||
@ -176,13 +184,13 @@ class Index extends BaseController
|
||||
|
||||
// 优先使用 article_detail 模板
|
||||
if (is_file($templateFile)) {
|
||||
return $this->renderPhpTemplate($templateFile, $themeUrlPath);
|
||||
return $this->renderPhpTemplate($templateFile, $themeUrlPath, $templateVars);
|
||||
} 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);
|
||||
return $this->renderPhpTemplate($blogDetailsFile, $themeUrlPath, $templateVars);
|
||||
} elseif (is_file($blogDetailsHtmlFile)) {
|
||||
$content = file_get_contents($blogDetailsHtmlFile);
|
||||
$content = $this->fixTemplateAssets($content, $themeUrlPath);
|
||||
@ -192,6 +200,81 @@ class Index extends BaseController
|
||||
return response('文章不存在', 404, ['Content-Type' => 'text/html; charset=utf-8']);
|
||||
}
|
||||
|
||||
/**
|
||||
* 组装文章详情页模板变量(数据源与 getNewsDetail 一致,并限定租户 + 已发布)
|
||||
*
|
||||
* @return array<string, mixed>|null
|
||||
*/
|
||||
private function buildArticleDetailTemplateVars(int $id, int $tid): ?array
|
||||
{
|
||||
$article = Articles::published()
|
||||
->where('id', $id)
|
||||
->where('tid', $tid)
|
||||
->find();
|
||||
|
||||
if (!$article) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$row = $article->toArray();
|
||||
|
||||
$categoryName = '未分类';
|
||||
if (!empty($row['cate'])) {
|
||||
$cat = ArticlesCategory::where('id', $row['cate'])
|
||||
->where('delete_time', null)
|
||||
->find();
|
||||
if ($cat) {
|
||||
$categoryName = (string) $cat['name'];
|
||||
}
|
||||
}
|
||||
|
||||
$coverPath = $row['image'] ?? '';
|
||||
if ($coverPath === '' && !empty($row['cate'])) {
|
||||
$category = ArticlesCategory::where('id', $row['cate'])
|
||||
->where('delete_time', null)
|
||||
->find();
|
||||
if ($category && !empty($category['image'])) {
|
||||
$coverPath = (string) $category['image'];
|
||||
}
|
||||
}
|
||||
|
||||
$apiUrl = rtrim((string) (Env::get('app.api_url', '') ?: 'https://api.yunzer.cn'), '/');
|
||||
$articleCoverUrl = $this->resolveArticleMediaUrl($coverPath, $apiUrl);
|
||||
|
||||
$descPlain = strip_tags((string) ($row['desc'] ?? ''));
|
||||
$title = (string) ($row['title'] ?? '详情');
|
||||
$descShort = $descPlain !== ''
|
||||
? (function_exists('mb_substr') ? mb_substr($descPlain, 0, 160) : substr($descPlain, 0, 160))
|
||||
: $title;
|
||||
|
||||
return [
|
||||
'article' => $row,
|
||||
'articleCoverUrl' => $articleCoverUrl,
|
||||
'articleCategoryName' => $categoryName,
|
||||
'pageTitle' => $title . ' - 新闻详情',
|
||||
'pageDescription' => $descShort,
|
||||
'pageKeywords' => $title,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 文章封面等资源 URL(与列表页逻辑一致:相对路径拼 API 域名)
|
||||
*/
|
||||
private function resolveArticleMediaUrl(string $path, string $apiUrl): string
|
||||
{
|
||||
if ($path === '') {
|
||||
return '';
|
||||
}
|
||||
if (strpos($path, 'http://') === 0 || strpos($path, 'https://') === 0) {
|
||||
return $path;
|
||||
}
|
||||
if (strpos($path, '/') === 0) {
|
||||
return $apiUrl . $path;
|
||||
}
|
||||
|
||||
return $apiUrl . '/' . $path;
|
||||
}
|
||||
|
||||
/**
|
||||
* 修复模板中的资源路径
|
||||
* 将相对路径 (assets/, css/, js/, images/) 转换为绝对路径 (/themes/xxx/)
|
||||
@ -223,7 +306,7 @@ class Index extends BaseController
|
||||
/**
|
||||
* 渲染PHP模板文件
|
||||
*/
|
||||
private function renderPhpTemplate(string $templateFile, string $themeUrlPath = '')
|
||||
private function renderPhpTemplate(string $templateFile, string $themeUrlPath = '', array $templateVars = [])
|
||||
{
|
||||
// 定义模板基础URL常量,供模板使用
|
||||
if (!defined('THEME_URL')) {
|
||||
@ -234,6 +317,7 @@ class Index extends BaseController
|
||||
// 获取URL参数
|
||||
$getParams = Request::get();
|
||||
extract($getParams, EXTR_OVERWRITE);
|
||||
extract($templateVars, EXTR_OVERWRITE);
|
||||
include $templateFile;
|
||||
$content = ob_get_clean();
|
||||
|
||||
|
||||
@ -22,10 +22,10 @@ Route::get('companyInfos', 'app\index\controller\Index@getCompanyInfos');
|
||||
Route::post('requirement', 'app\index\controller\Index@requirement');
|
||||
Route::get('homeData', 'app\index\controller\Index@getHomeData');
|
||||
|
||||
// --- 客户需求路由 ---
|
||||
|
||||
// --- 新闻中心列表路由 ---
|
||||
Route::get('getCenterNews', 'app\index\controller\Article\NewsCenterController@getCenterNews');
|
||||
Route::get('getNewsDetail/:id', 'app\index\controller\Article\NewsCenterController@getNewsDetail');
|
||||
|
||||
|
||||
// --- 文章分类路由 ---
|
||||
Route::get('getArticleCategories', 'app\index\controller\Article\ArticleController@getArticleCategories');
|
||||
|
||||
42
app/model/Cms/ProductsTypes.php
Normal file
42
app/model/Cms/ProductsTypes.php
Normal file
@ -0,0 +1,42 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2006-2018 http://thinkphp.cn All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: Liu21st <liu21st@gmail.com>
|
||||
// +----------------------------------------------------------------------
|
||||
|
||||
namespace app\model\Cms;
|
||||
|
||||
use think\Model;
|
||||
use think\model\concern\SoftDelete;
|
||||
|
||||
/**
|
||||
* 特色服务模型
|
||||
*/
|
||||
class ProductsTypes extends Model
|
||||
{
|
||||
// 启用软删除
|
||||
use SoftDelete;
|
||||
|
||||
// 数据库表名
|
||||
protected $name = 'mete_apps_cms_products_types';
|
||||
|
||||
// 字段类型转换
|
||||
protected $type = [
|
||||
'id' => 'integer',
|
||||
'tid' => 'integer',
|
||||
'pid' => 'integer',
|
||||
'title' => 'string',
|
||||
'desc' => 'string',
|
||||
'sort' => 'integer',
|
||||
'create_time' => 'datetime',
|
||||
'update_time' => 'datetime',
|
||||
'delete_time' => 'datetime',
|
||||
];
|
||||
|
||||
|
||||
}
|
||||
@ -35,6 +35,7 @@ class SystemMenu extends Model
|
||||
'icon' => 'string',
|
||||
'sort' => 'integer',
|
||||
'status' => 'integer',
|
||||
'is_visible' => 'integer',
|
||||
'type' => 'integer',
|
||||
'permission' => 'string',
|
||||
'remark' => 'string',
|
||||
|
||||
@ -12,13 +12,16 @@
|
||||
--------------------------------------------------------------*/
|
||||
/* Fonts */
|
||||
:root {
|
||||
--default-font: "Roboto", system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", "Liberation Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
|
||||
--heading-font: "Montserrat", sans-serif;
|
||||
--nav-font: "Raleway", sans-serif;
|
||||
--default-font:
|
||||
"Roboto", system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue",
|
||||
Arial, "Noto Sans", "Liberation Sans", sans-serif, "Apple Color Emoji",
|
||||
"Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
|
||||
--heading-font: "Montserrat", sans-serif;
|
||||
--nav-font: "Raleway", sans-serif;
|
||||
}
|
||||
|
||||
/* Global Colors - The following color variables are used throughout the website. Updating them here will change the color scheme of the entire website */
|
||||
:root {
|
||||
:root {
|
||||
--background-color: #ffffff; /* Background color for the entire website, including individual sections */
|
||||
--default-color: #2b180d; /* Default color used for the majority of the text content across the entire website */
|
||||
--heading-color: #1b2f45; /* Color for headings, subheadings and title throughout the website */
|
||||
@ -29,7 +32,12 @@
|
||||
|
||||
/* Nav Menu Colors - The following color variables are used specifically for the navigation menu. They are separate from the global colors to allow for more customization options */
|
||||
:root {
|
||||
--nav-color: rgba(255, 255, 255, 0.6); /* The default color of the main navmenu links */
|
||||
--nav-color: rgba(
|
||||
255,
|
||||
255,
|
||||
255,
|
||||
0.6
|
||||
); /* The default color of the main navmenu links */
|
||||
--nav-hover-color: #ffffff; /* Applied to main navmenu links when they are hovered over or active */
|
||||
--nav-mobile-background-color: #ffffff; /* Used as the background color for mobile navigation menu */
|
||||
--nav-dropdown-background-color: #ffffff; /* Used as the background color for dropdown items that appear when hovering over primary navigation items */
|
||||
@ -85,8 +93,12 @@ h5,
|
||||
h6 {
|
||||
color: var(--heading-color);
|
||||
font-family: var(--heading-font);
|
||||
line-height: 40px;
|
||||
}
|
||||
|
||||
.h520 {
|
||||
height: 520px;
|
||||
}
|
||||
/* PHP Email Form Messages
|
||||
------------------------------*/
|
||||
.php-email-form .error-message {
|
||||
@ -225,7 +237,7 @@ h6 {
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
.navmenu li:hover>a,
|
||||
.navmenu li:hover > a,
|
||||
.navmenu .active,
|
||||
.navmenu .active:focus {
|
||||
color: var(--nav-hover-color);
|
||||
@ -264,11 +276,11 @@ h6 {
|
||||
|
||||
.navmenu .dropdown ul a:hover,
|
||||
.navmenu .dropdown ul .active:hover,
|
||||
.navmenu .dropdown ul li:hover>a {
|
||||
.navmenu .dropdown ul li:hover > a {
|
||||
color: var(--nav-dropdown-hover-color);
|
||||
}
|
||||
|
||||
.navmenu .dropdown:hover>ul {
|
||||
.navmenu .dropdown:hover > ul {
|
||||
opacity: 1;
|
||||
top: 100%;
|
||||
visibility: visible;
|
||||
@ -280,7 +292,7 @@ h6 {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.navmenu .dropdown .dropdown:hover>ul {
|
||||
.navmenu .dropdown .dropdown:hover > ul {
|
||||
opacity: 1;
|
||||
top: 0;
|
||||
left: -100%;
|
||||
@ -383,7 +395,7 @@ h6 {
|
||||
background-color: rgba(33, 37, 41, 0.1);
|
||||
}
|
||||
|
||||
.navmenu .dropdown>.dropdown-active {
|
||||
.navmenu .dropdown > .dropdown-active {
|
||||
display: block;
|
||||
background-color: rgba(33, 37, 41, 0.03);
|
||||
}
|
||||
@ -410,7 +422,7 @@ h6 {
|
||||
transition: 0.3s;
|
||||
}
|
||||
|
||||
.mobile-nav-active .navmenu>ul {
|
||||
.mobile-nav-active .navmenu > ul {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
@ -627,7 +639,11 @@ h6 {
|
||||
|
||||
.page-title:before {
|
||||
content: "";
|
||||
background-color: color-mix(in srgb, var(--background-color), transparent 40%);
|
||||
background-color: color-mix(
|
||||
in srgb,
|
||||
var(--background-color),
|
||||
transparent 40%
|
||||
);
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
}
|
||||
@ -653,11 +669,11 @@ h6 {
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.page-title .breadcrumbs ol li+li {
|
||||
.page-title .breadcrumbs ol li + li {
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
.page-title .breadcrumbs ol li+li::before {
|
||||
.page-title .breadcrumbs ol li + li::before {
|
||||
content: "/";
|
||||
display: inline-block;
|
||||
padding-right: 10px;
|
||||
@ -677,7 +693,6 @@ section,
|
||||
}
|
||||
|
||||
@media (max-width: 1199px) {
|
||||
|
||||
section,
|
||||
.section {
|
||||
scroll-margin-top: 66px;
|
||||
@ -735,7 +750,13 @@ section,
|
||||
position: absolute;
|
||||
content: "";
|
||||
width: 44%;
|
||||
background-image: linear-gradient(180deg, color-mix(in srgb, var(--background-color), transparent 15%), color-mix(in srgb, var(--background-color), transparent 15%) 100%), linear-gradient(180deg, rgb(0, 0, 0), rgb(0, 0, 0) 100%);
|
||||
background-image:
|
||||
linear-gradient(
|
||||
180deg,
|
||||
color-mix(in srgb, var(--background-color), transparent 15%),
|
||||
color-mix(in srgb, var(--background-color), transparent 15%) 100%
|
||||
),
|
||||
linear-gradient(180deg, rgb(0, 0, 0), rgb(0, 0, 0) 100%);
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
z-index: 2;
|
||||
@ -1441,14 +1462,15 @@ section,
|
||||
box-shadow: 0px 2px 20px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.service-details .service-box+.service-box {
|
||||
.service-details .service-box + .service-box {
|
||||
margin-top: 30px;
|
||||
}
|
||||
|
||||
.service-details .service-box h4 {
|
||||
font-size: 20px;
|
||||
font-weight: 700;
|
||||
border-bottom: 2px solid color-mix(in srgb, var(--default-color), transparent 92%);
|
||||
border-bottom: 2px solid
|
||||
color-mix(in srgb, var(--default-color), transparent 92%);
|
||||
padding-bottom: 15px;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
@ -1497,7 +1519,8 @@ section,
|
||||
align-items: center;
|
||||
padding: 10px 0;
|
||||
transition: 0.3s;
|
||||
border-top: 1px solid color-mix(in srgb, var(--default-color), transparent 90%);
|
||||
border-top: 1px solid
|
||||
color-mix(in srgb, var(--default-color), transparent 90%);
|
||||
}
|
||||
|
||||
.service-details .download-catalog a:first-child {
|
||||
@ -1676,14 +1699,20 @@ section,
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.portfolio-details .portfolio-details-slider .swiper-pagination .swiper-pagination-bullet {
|
||||
.portfolio-details
|
||||
.portfolio-details-slider
|
||||
.swiper-pagination
|
||||
.swiper-pagination-bullet {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
background-color: color-mix(in srgb, var(--default-color), transparent 85%);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.portfolio-details .portfolio-details-slider .swiper-pagination .swiper-pagination-bullet-active {
|
||||
.portfolio-details
|
||||
.portfolio-details-slider
|
||||
.swiper-pagination
|
||||
.swiper-pagination-bullet-active {
|
||||
background-color: var(--accent-color);
|
||||
}
|
||||
|
||||
@ -1698,7 +1727,8 @@ section,
|
||||
font-weight: 700;
|
||||
margin-bottom: 20px;
|
||||
padding-bottom: 20px;
|
||||
border-bottom: 1px solid color-mix(in srgb, var(--default-color), transparent 85%);
|
||||
border-bottom: 1px solid
|
||||
color-mix(in srgb, var(--default-color), transparent 85%);
|
||||
}
|
||||
|
||||
.portfolio-details .portfolio-info ul {
|
||||
@ -1707,7 +1737,7 @@ section,
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.portfolio-details .portfolio-info ul li+li {
|
||||
.portfolio-details .portfolio-info ul li + li {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
@ -1824,7 +1854,7 @@ section,
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.contact .info-item+.info-item {
|
||||
.contact .info-item + .info-item {
|
||||
margin-top: 40px;
|
||||
}
|
||||
|
||||
@ -1859,31 +1889,35 @@ section,
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.contact .php-email-form input[type=text],
|
||||
.contact .php-email-form input[type=email],
|
||||
.contact .php-email-form input[type="text"],
|
||||
.contact .php-email-form input[type="email"],
|
||||
.contact .php-email-form textarea {
|
||||
font-size: 14px;
|
||||
padding: 10px 15px;
|
||||
box-shadow: none;
|
||||
border-radius: 0;
|
||||
color: var(--default-color);
|
||||
background-color: color-mix(in srgb, var(--background-color), transparent 50%);
|
||||
background-color: color-mix(
|
||||
in srgb,
|
||||
var(--background-color),
|
||||
transparent 50%
|
||||
);
|
||||
border-color: color-mix(in srgb, var(--default-color), transparent 80%);
|
||||
}
|
||||
|
||||
.contact .php-email-form input[type=text]:focus,
|
||||
.contact .php-email-form input[type=email]:focus,
|
||||
.contact .php-email-form input[type="text"]:focus,
|
||||
.contact .php-email-form input[type="email"]:focus,
|
||||
.contact .php-email-form textarea:focus {
|
||||
border-color: var(--accent-color);
|
||||
}
|
||||
|
||||
.contact .php-email-form input[type=text]::placeholder,
|
||||
.contact .php-email-form input[type=email]::placeholder,
|
||||
.contact .php-email-form input[type="text"]::placeholder,
|
||||
.contact .php-email-form input[type="email"]::placeholder,
|
||||
.contact .php-email-form textarea::placeholder {
|
||||
color: color-mix(in srgb, var(--default-color), transparent 70%);
|
||||
}
|
||||
|
||||
.contact .php-email-form button[type=submit] {
|
||||
.contact .php-email-form button[type="submit"] {
|
||||
color: var(--contrast-color);
|
||||
background: var(--accent-color);
|
||||
border: 0;
|
||||
@ -1892,17 +1926,97 @@ section,
|
||||
border-radius: 50px;
|
||||
}
|
||||
|
||||
.contact .php-email-form button[type=submit]:hover {
|
||||
.contact .php-email-form button[type="submit"]:hover {
|
||||
background: color-mix(in srgb, var(--accent-color), transparent 20%);
|
||||
}
|
||||
|
||||
/*--------------------------------------------------------------
|
||||
# Blog Layout — 左侧分类导航
|
||||
--------------------------------------------------------------*/
|
||||
.blog-layout-sidebar {
|
||||
position: sticky;
|
||||
top: 100px;
|
||||
margin-top: 60px;
|
||||
}
|
||||
|
||||
.blog-category-nav {
|
||||
background-color: var(--surface-color);
|
||||
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.08);
|
||||
border-radius: 10px;
|
||||
padding: 0 0 8px;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.blog-category-nav .nav-title {
|
||||
font-size: 18px;
|
||||
font-weight: 700;
|
||||
padding: 16px 20px 12px;
|
||||
margin: 0;
|
||||
color: var(--heading-color);
|
||||
border-bottom: 1px solid
|
||||
color-mix(in srgb, var(--default-color), transparent 90%);
|
||||
}
|
||||
|
||||
.blog-category-nav ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.blog-category-nav li a {
|
||||
display: block;
|
||||
padding: 10px 20px;
|
||||
color: var(--default-color);
|
||||
text-decoration: none;
|
||||
transition: 0.25s;
|
||||
border-left: 3px solid transparent;
|
||||
}
|
||||
|
||||
.blog-category-nav li a:hover {
|
||||
color: var(--accent-color);
|
||||
background: color-mix(in srgb, var(--accent-color), transparent 94%);
|
||||
}
|
||||
|
||||
.blog-category-nav li a.active {
|
||||
color: var(--accent-color);
|
||||
font-weight: 600;
|
||||
border-left-color: var(--accent-color);
|
||||
background: color-mix(in srgb, var(--accent-color), transparent 92%);
|
||||
}
|
||||
|
||||
.blog-category-nav li ul.children {
|
||||
padding-left: 16px;
|
||||
}
|
||||
|
||||
.blog-category-nav li ul.children li a {
|
||||
font-size: 14px;
|
||||
padding: 8px 20px;
|
||||
color: color-mix(in srgb, var(--default-color), transparent 30%);
|
||||
}
|
||||
|
||||
.blog-category-nav li ul.children li a:hover {
|
||||
color: var(--accent-color);
|
||||
}
|
||||
|
||||
.blog-category-nav li ul.children li a.active {
|
||||
color: var(--accent-color);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
@media (max-width: 991px) {
|
||||
.blog-layout-sidebar {
|
||||
position: static;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
/*--------------------------------------------------------------
|
||||
# Blog Posts Section
|
||||
--------------------------------------------------------------*/
|
||||
.blog-posts article {
|
||||
background-color: var(--surface-color);
|
||||
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1);
|
||||
padding: 30px;
|
||||
padding: 15px;
|
||||
height: 100%;
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
@ -1927,7 +2041,7 @@ section,
|
||||
|
||||
/* 新结构:blog.php 中的预览图容器 */
|
||||
.blog-posts .post-media {
|
||||
height: 240px;
|
||||
height: 125px;
|
||||
margin: -30px -30px 15px -30px;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
@ -1971,7 +2085,7 @@ section,
|
||||
}
|
||||
|
||||
.blog-posts .post-author-img {
|
||||
width: 50px;
|
||||
width: 35px;
|
||||
border-radius: 50%;
|
||||
margin-right: 15px;
|
||||
}
|
||||
@ -1979,10 +2093,11 @@ section,
|
||||
.blog-posts .post-author {
|
||||
font-weight: 600;
|
||||
margin-bottom: 5px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.blog-posts .post-date {
|
||||
font-size: 14px;
|
||||
font-size: 12px;
|
||||
color: color-mix(in srgb, var(--default-color), transparent 40%);
|
||||
margin-bottom: 0;
|
||||
}
|
||||
@ -2092,9 +2207,99 @@ section,
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.blog-details .content span {
|
||||
border-radius: 4px;
|
||||
padding: 0.15em 0.45em;
|
||||
margin: 5px;
|
||||
}
|
||||
|
||||
.blog-details .content code {
|
||||
font-family:
|
||||
ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono",
|
||||
"Courier New", monospace;
|
||||
font-size: 0.92em;
|
||||
color: var(--heading-color);
|
||||
background-color: color-mix(in srgb, var(--default-color), transparent 92%);
|
||||
padding: 0.15em 0.45em;
|
||||
border-radius: 4px;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.blog-details .content pre {
|
||||
font-family:
|
||||
ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono",
|
||||
"Courier New", monospace;
|
||||
font-size: 14px;
|
||||
line-height: 1.55;
|
||||
color: var(--default-color);
|
||||
background-color: color-mix(in srgb, var(--default-color), transparent 94%);
|
||||
border: 1px solid color-mix(in srgb, var(--default-color), transparent 88%);
|
||||
border-radius: 6px;
|
||||
padding: 18px 20px;
|
||||
margin: 1.25em 0;
|
||||
overflow-x: auto;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
|
||||
.blog-details .content pre code {
|
||||
font-size: inherit;
|
||||
color: inherit;
|
||||
background: none;
|
||||
padding: 0;
|
||||
border-radius: 0;
|
||||
word-break: normal;
|
||||
white-space: pre;
|
||||
}
|
||||
|
||||
.blog-details .content .article-code-block {
|
||||
position: relative;
|
||||
margin: 1.25em 0;
|
||||
}
|
||||
|
||||
.blog-details .content .article-code-block pre {
|
||||
margin: 0;
|
||||
padding-top: 42px;
|
||||
}
|
||||
|
||||
.blog-details .content .article-code-copy {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 10px;
|
||||
z-index: 2;
|
||||
font-family: var(--default-font, system-ui, sans-serif);
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
line-height: 1;
|
||||
padding: 6px 12px;
|
||||
color: var(--contrast-color);
|
||||
background-color: var(--accent-color);
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
transition:
|
||||
opacity 0.2s ease,
|
||||
transform 0.15s ease;
|
||||
}
|
||||
|
||||
.blog-details .content .article-code-copy:hover {
|
||||
opacity: 0.92;
|
||||
}
|
||||
|
||||
.blog-details .content .article-code-copy:focus-visible {
|
||||
outline: 2px solid var(--accent-color);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
.blog-details .content .article-code-copy.is-done {
|
||||
background-color: color-mix(in srgb, var(--accent-color), #000 18%);
|
||||
}
|
||||
|
||||
.blog-details .meta-top {
|
||||
margin-top: 20px;
|
||||
color: color-mix(in srgb, var(--default-color), transparent 40%);
|
||||
padding-bottom: 25px;
|
||||
border-bottom: 1px solid
|
||||
color-mix(in srgb, var(--default-color), transparent 90%);
|
||||
}
|
||||
|
||||
.blog-details .meta-top ul {
|
||||
@ -2106,7 +2311,7 @@ section,
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.blog-details .meta-top ul li+li {
|
||||
.blog-details .meta-top ul li + li {
|
||||
padding-left: 20px;
|
||||
}
|
||||
|
||||
@ -2126,7 +2331,8 @@ section,
|
||||
|
||||
.blog-details .meta-bottom {
|
||||
padding-top: 10px;
|
||||
border-top: 1px solid color-mix(in srgb, var(--default-color), transparent 90%);
|
||||
border-top: 1px solid
|
||||
color-mix(in srgb, var(--default-color), transparent 90%);
|
||||
}
|
||||
|
||||
.blog-details .meta-bottom i {
|
||||
@ -2165,7 +2371,7 @@ section,
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.blog-details .meta-bottom .tags li+li::before {
|
||||
.blog-details .meta-bottom .tags li + li::before {
|
||||
padding-right: 6px;
|
||||
color: var(--default-color);
|
||||
content: ",";
|
||||
@ -2414,7 +2620,7 @@ section,
|
||||
transition: 0.3s;
|
||||
}
|
||||
|
||||
.search-widget form input[type=text] {
|
||||
.search-widget form input[type="text"] {
|
||||
border: 0;
|
||||
padding: 4px;
|
||||
border-radius: 4px;
|
||||
@ -2423,7 +2629,7 @@ section,
|
||||
color: var(--default-color);
|
||||
}
|
||||
|
||||
.search-widget form input[type=text]:focus {
|
||||
.search-widget form input[type="text"]:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
@ -2554,4 +2760,4 @@ section,
|
||||
padding-left: 5px;
|
||||
color: color-mix(in srgb, var(--default-color), transparent 60%);
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,107 +1,166 @@
|
||||
<?php
|
||||
$pageTitle = '新闻中心详情 - Nova';
|
||||
$pageDescription = 'Nova Bootstrap Template 的新闻中心详情页面';
|
||||
$pageKeywords = 'blog, details, nova';
|
||||
require_once __DIR__ . '/header.php';
|
||||
?>
|
||||
|
||||
<!-- Page Title -->
|
||||
<div class="page-title dark-background" data-aos="fade" style="background-image: url(<?php echo $baseUrl; ?>/themes/template3/assets/img/blog-page-title-bg.jpg);">
|
||||
<div class="container">
|
||||
<h1>博客详情</h1>
|
||||
<nav class="breadcrumbs">
|
||||
<ol>
|
||||
<li><a href="<?php echo $baseUrl; ?>/">主页</a></li>
|
||||
<li class="current">博客详情</li>
|
||||
</ol>
|
||||
</nav>
|
||||
</div>
|
||||
</div><!-- End Page Title -->
|
||||
<?php
|
||||
// 支持 Index@articleDetail 注入 $pageTitle 等;直接访问 /blog-details 时用默认文案
|
||||
$pageTitle = (isset($pageTitle) && $pageTitle !== '') ? $pageTitle : '新闻中心详情 - Nova';
|
||||
$pageDescription = (isset($pageDescription) && $pageDescription !== '') ? $pageDescription : 'Nova Bootstrap Template 的新闻中心详情页面';
|
||||
$pageKeywords = (isset($pageKeywords) && $pageKeywords !== '') ? $pageKeywords : 'blog, details, nova';
|
||||
$article = (isset($article) && is_array($article)) ? $article : [];
|
||||
$hasArticle = !empty($article['id']);
|
||||
// 面包屑第三段:文章分类名(与控制器注入的 $articleCategoryName 一致)
|
||||
$cateName = (isset($articleCategoryName) && (string) $articleCategoryName !== '') ? (string) $articleCategoryName : '未分类';
|
||||
require_once __DIR__ . '/header.php';
|
||||
?>
|
||||
|
||||
<!-- Page Title -->
|
||||
<div class="page-title dark-background" data-aos="fade" style="background-image: url(<?php echo $baseUrl; ?>/themes/template3/assets/img/blog-page-title-bg.jpg);">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<h1><?php echo $hasArticle ? htmlspecialchars($article['title'] ?? '博客详情') : '博客详情'; ?></h1>
|
||||
<nav class="breadcrumbs">
|
||||
<ol>
|
||||
<li><a href="<?php echo htmlspecialchars($baseUrl); ?>/">主页</a></li>
|
||||
<li><a href="<?php echo htmlspecialchars($baseUrl . '/blog'); ?>">新闻中心</a></li>
|
||||
<li class="current"><?php echo htmlspecialchars($cateName); ?></li>
|
||||
</ol>
|
||||
</nav>
|
||||
</div>
|
||||
</div><!-- End Page Title -->
|
||||
|
||||
<div class="col-lg-12">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
|
||||
<!-- Blog Details Section -->
|
||||
<section id="blog-details" class="blog-details section">
|
||||
<div class="container">
|
||||
<div class="col-lg-12">
|
||||
|
||||
<article class="article">
|
||||
<!-- Blog Details Section -->
|
||||
<section id="blog-details" class="blog-details section">
|
||||
<div class="container">
|
||||
|
||||
<div class="post-img">
|
||||
<img src="<?php echo $baseUrl; ?>/themes/template3/assets/img/blog/blog-1.jpg" alt="" class="img-fluid">
|
||||
</div>
|
||||
<article class="article">
|
||||
<?php if ($hasArticle): ?>
|
||||
<?php
|
||||
$detailTitle = $article['title'] ?? '无标题';
|
||||
$detailAuthor = $article['author'] ?? '';
|
||||
$publishRaw = $article['publish_date'] ?? ($article['create_time'] ?? '');
|
||||
$publishTs = ($publishRaw !== '') ? strtotime((string) $publishRaw) : false;
|
||||
$publishDateAttr = ($publishTs !== false) ? date('c', $publishTs) : '';
|
||||
$publishDateText = ($publishTs !== false) ? date('Y年m月d日', $publishTs) : '';
|
||||
$views = (int) ($article['views'] ?? 0);
|
||||
$coverSrc = !empty($articleCoverUrl)
|
||||
? $articleCoverUrl
|
||||
: ($baseUrl . '/themes/template3/assets/img/blog/blog-1.jpg');
|
||||
?>
|
||||
|
||||
<h2 class="title">Dolorum optio tempore voluptas dignissimos cumque fuga qui quibusdam quia</h2>
|
||||
<h2 class="title"><?php echo htmlspecialchars($detailTitle); ?></h2>
|
||||
|
||||
<div class="meta-top">
|
||||
<ul>
|
||||
<li class="d-flex align-items-center"><i class="bi bi-person"></i> <a href="<?php echo $baseUrl; ?>/blog-details">John Doe</a></li>
|
||||
<li class="d-flex align-items-center"><i class="bi bi-clock"></i> <a href="<?php echo $baseUrl; ?>/blog-details"><time datetime="2020-01-01">Jan 1, 2022</time></a></li>
|
||||
<li class="d-flex align-items-center"><i class="bi bi-chat-dots"></i> <a href="<?php echo $baseUrl; ?>/blog-details">12 Comments</a></li>
|
||||
<li class="d-flex align-items-center"><i class="bi bi-person"></i> <span><?php echo htmlspecialchars($detailAuthor !== '' ? $detailAuthor : '—'); ?></span></li>
|
||||
<li class="d-flex align-items-center"><i class="bi bi-clock"></i> <?php if ($publishDateText !== ''): ?><time datetime="<?php echo htmlspecialchars($publishDateAttr); ?>"><?php echo htmlspecialchars($publishDateText); ?></time><?php else: ?><span>—</span><?php endif; ?></li>
|
||||
<li class="d-flex align-items-center"><i class="bi bi-eye"></i> <span id="article-views-count">阅读 <?php echo $views; ?></span></li>
|
||||
</ul>
|
||||
</div><!-- End meta top -->
|
||||
|
||||
<div class="content">
|
||||
<p>
|
||||
Similique neque nam consequuntur ad non maxime aliquam quas. Quibusdam animi praesentium. Aliquam et laboriosam eius aut nostrum quidem aliquid dicta.
|
||||
Et eveniet enim. Qui velit est ea dolorem doloremque deleniti aperiam unde soluta. Est cum et quod quos aut ut et sit sunt. Voluptate porro consequatur assumenda perferendis dolore.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Sit repellat hic cupiditate hic ut nemo. Quis nihil sunt non reiciendis. Sequi in accusamus harum vel aspernatur. Excepturi numquam nihil cumque odio. Et voluptate cupiditate.
|
||||
</p>
|
||||
|
||||
<blockquote>
|
||||
<p>
|
||||
Et vero doloremque tempore voluptatem ratione vel aut. Deleniti sunt animi aut. Aut eos aliquam doloribus minus autem quos.
|
||||
</p>
|
||||
</blockquote>
|
||||
|
||||
<p>
|
||||
Sed quo laboriosam qui architecto. Occaecati repellendus omnis dicta inventore tempore provident voluptas mollitia aliquid. Id repellendus quia. Asperiores nihil magni dicta est suscipit perspiciatis. Voluptate ex rerum assumenda dolores nihil quaerat.
|
||||
Dolor porro tempora et quibusdam voluptas. Beatae aut at ad qui tempore corrupti velit quisquam rerum. Omnis dolorum exercitationem harum qui qui blanditiis neque.
|
||||
Iusto autem itaque. Repudiandae hic quae aspernatur ea neque qui. Architecto voluptatem magni. Vel magnam quod et tempora deleniti error rerum nihil tempora.
|
||||
</p>
|
||||
|
||||
<h3>Et quae iure vel ut odit alias.</h3>
|
||||
<p>
|
||||
Officiis animi maxime nulla quo et harum eum quis a. Sit hic in qui quos fugit ut rerum atque. Optio provident dolores atque voluptatem rem excepturi molestiae qui. Voluptatem laborum omnis ullam quibusdam perspiciatis nulla nostrum. Voluptatum est libero eum nesciunt aliquid qui.
|
||||
Quia et suscipit non sequi. Maxime sed odit. Beatae nesciunt nesciunt accusamus quia aut ratione aspernatur dolor. Sint harum eveniet dicta exercitationem minima. Exercitationem omnis asperiores natus aperiam dolor consequatur id ex sed. Quibusdam rerum dolores sint consequatur quidem ea.
|
||||
Beatae minima sunt libero soluta sapiente in rem assumenda. Et qui odit voluptatem. Cum quibusdam voluptatem voluptatem accusamus mollitia aut atque aut.
|
||||
</p>
|
||||
<img src="<?php echo $baseUrl; ?>/themes/template3/assets/img/blog/blog-inside-post.jpg" class="img-fluid" alt="">
|
||||
|
||||
<h3>Ut repellat blanditiis est dolore sunt dolorum quae.</h3>
|
||||
<p>
|
||||
Rerum ea est assumenda pariatur quasi et quam. Facilis nam porro amet nostrum. In assumenda quia quae a id praesentium. Quos deleniti libero sed occaecati aut porro autem. Consectetur sed excepturi sint non placeat quia repellat incidunt labore. Autem facilis hic dolorum dolores vel.
|
||||
Consectetur quasi id et optio praesentium aut asperiores eaque aut. Explicabo omnis quibusdam esse. Ex libero illum iusto totam et ut aut blanditiis. Veritatis numquam ut illum ut a quam vitae.
|
||||
</p>
|
||||
<p>
|
||||
Alias quia non aliquid. Eos et ea velit. Voluptatem maxime enim omnis ipsa voluptas incidunt. Nulla sit eaque mollitia nisi asperiores est veniam.
|
||||
</p>
|
||||
|
||||
<div class="content article-body">
|
||||
<?php echo $article['content'] ?? ''; ?>
|
||||
</div><!-- End post content -->
|
||||
|
||||
<div class="meta-bottom">
|
||||
<i class="bi bi-folder"></i>
|
||||
<ul class="cats">
|
||||
<li><a href="#">Business</a></li>
|
||||
<li><span><?php echo htmlspecialchars($cateName); ?></span></li>
|
||||
</ul>
|
||||
</div><!-- End meta bottom -->
|
||||
<?php else: ?>
|
||||
<p class="text-center py-5">暂无文章数据,请从<a href="<?php echo htmlspecialchars($baseUrl . '/blog'); ?>">新闻列表</a>进入详情。</p>
|
||||
<?php endif; ?>
|
||||
|
||||
</article>
|
||||
</article>
|
||||
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</section><!-- /Blog Details Section -->
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</main>
|
||||
|
||||
<?php require_once __DIR__ . '/footer.php'; ?>
|
||||
<?php if ($hasArticle): ?>
|
||||
<script>
|
||||
(function () {
|
||||
var id = <?php echo (int) ($article['id'] ?? 0); ?>;
|
||||
if (!id) return;
|
||||
var url = <?php echo json_encode(rtrim($baseUrl, '/') . '/articleViews/', JSON_UNESCAPED_SLASHES); ?> + id;
|
||||
var el = document.getElementById('article-views-count');
|
||||
fetch(url, { method: 'POST', credentials: 'same-origin', headers: { 'Accept': 'application/json' } })
|
||||
.then(function (r) { return r.json(); })
|
||||
.then(function (res) {
|
||||
if (res && res.code === 200 && res.data && typeof res.data.views !== 'undefined' && el) {
|
||||
el.textContent = '阅读 ' + res.data.views;
|
||||
}
|
||||
})
|
||||
.catch(function () { /* 静默失败,保留服务端渲染的阅读量 */ });
|
||||
})();
|
||||
</script>
|
||||
<script>
|
||||
(function () {
|
||||
var root = document.querySelector('.blog-details .content.article-body');
|
||||
if (!root) return;
|
||||
root.querySelectorAll('pre').forEach(function (pre) {
|
||||
if (pre.closest('.article-code-block')) return;
|
||||
var wrap = document.createElement('div');
|
||||
wrap.className = 'article-code-block';
|
||||
pre.parentNode.insertBefore(wrap, pre);
|
||||
wrap.appendChild(pre);
|
||||
var btn = document.createElement('button');
|
||||
btn.type = 'button';
|
||||
btn.className = 'article-code-copy';
|
||||
btn.setAttribute('aria-label', '复制代码');
|
||||
btn.textContent = '复制';
|
||||
wrap.insertBefore(btn, pre);
|
||||
btn.addEventListener('click', function () {
|
||||
var text = (pre.innerText !== undefined ? pre.innerText : pre.textContent) || '';
|
||||
function showDone() {
|
||||
btn.textContent = '已复制';
|
||||
btn.classList.add('is-done');
|
||||
clearTimeout(btn._copyReset);
|
||||
btn._copyReset = setTimeout(function () {
|
||||
btn.textContent = '复制';
|
||||
btn.classList.remove('is-done');
|
||||
}, 2000);
|
||||
}
|
||||
function showFail() {
|
||||
btn.textContent = '复制失败';
|
||||
clearTimeout(btn._copyReset);
|
||||
btn._copyReset = setTimeout(function () { btn.textContent = '复制'; }, 2000);
|
||||
}
|
||||
if (navigator.clipboard && typeof navigator.clipboard.writeText === 'function') {
|
||||
navigator.clipboard.writeText(text).then(showDone).catch(function () {
|
||||
fallbackCopy();
|
||||
});
|
||||
} else {
|
||||
fallbackCopy();
|
||||
}
|
||||
function fallbackCopy() {
|
||||
var ta = document.createElement('textarea');
|
||||
ta.value = text;
|
||||
ta.setAttribute('readonly', '');
|
||||
ta.style.position = 'fixed';
|
||||
ta.style.left = '-9999px';
|
||||
document.body.appendChild(ta);
|
||||
ta.select();
|
||||
try {
|
||||
if (document.execCommand('copy')) showDone();
|
||||
else showFail();
|
||||
} catch (e) {
|
||||
showFail();
|
||||
}
|
||||
document.body.removeChild(ta);
|
||||
}
|
||||
});
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php require_once __DIR__ . '/footer.php'; ?>
|
||||
@ -1,162 +1,271 @@
|
||||
<?php
|
||||
$pageTitle = '新闻中心 - Nova';
|
||||
$pageDescription = 'Nova Bootstrap Template 的新闻中心页面';
|
||||
$pageKeywords = 'blog, news, nova';
|
||||
header('Cache-Control: no-store, no-cache, must-revalidate');
|
||||
header('Pragma: no-cache');
|
||||
header('Expires: 0');
|
||||
|
||||
$pageTitle = '新闻中心 - 云泽';
|
||||
$pageDescription = '云泽的新闻中心页面';
|
||||
$pageKeywords = 'blog, news, 云泽';
|
||||
require_once __DIR__ . '/header.php';
|
||||
?>
|
||||
<?php
|
||||
// 本页使用本地分页:根据 query 参数 ?page=n 只展示 newsList 的切片
|
||||
$allNewsList = is_array($newsList ?? null) ? $newsList : [];
|
||||
|
||||
$currentPage = (int)($_GET['page'] ?? 1);
|
||||
if ($currentPage < 1) {
|
||||
$currentPage = 1;
|
||||
use think\facade\Request;
|
||||
|
||||
// 确保 baseUrl 不带查询参数,避免分页链接出现双 ? 的问题
|
||||
$scheme = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on') ? 'https' : 'http';
|
||||
$host = $_SERVER['HTTP_HOST'] ?? '';
|
||||
$baseUrlBlog = $scheme . '://' . $host;
|
||||
|
||||
// 新闻列表:服务端分页
|
||||
// Nginx 未传递 QUERY_STRING,从 REQUEST_URI 解析
|
||||
$requestUri = $_SERVER['REQUEST_URI'] ?? '';
|
||||
$queryString = '';
|
||||
if (strpos($requestUri, '?') !== false) {
|
||||
$queryString = substr($requestUri, strpos($requestUri, '?') + 1);
|
||||
}
|
||||
parse_str($queryString, $queryArr);
|
||||
$selectedCateId = isset($queryArr['cate']) ? (int) $queryArr['cate'] : 0;
|
||||
if ($selectedCateId < 0) {
|
||||
$selectedCateId = 0;
|
||||
}
|
||||
|
||||
$pageSize = 8; // 每页展示 8 条(grid 中每条占 col-lg-6,所以刚好四行)
|
||||
$totalCount = count($allNewsList);
|
||||
$totalPages = (int)ceil($totalCount / $pageSize);
|
||||
if ($totalPages < 1) {
|
||||
$totalPages = 1;
|
||||
$listPage = isset($queryArr['page']) ? max(1, (int) $queryArr['page']) : 1;
|
||||
$pageSize = 8;
|
||||
|
||||
$host = $_SERVER['HTTP_HOST'] ?? '';
|
||||
$blogNewsQuery = [
|
||||
'baseUrl' => $host,
|
||||
'page' => $listPage,
|
||||
'page_size' => $pageSize,
|
||||
];
|
||||
if ($selectedCateId > 0) {
|
||||
$blogNewsQuery['cate'] = $selectedCateId;
|
||||
}
|
||||
$blogNewsUrl = $apiUrl . '/getCenterNews?' . http_build_query($blogNewsQuery);
|
||||
$blogNewsRes = fetchApiData($blogNewsUrl);
|
||||
// count:当前「全部」或某分类下的条数(用于分页);total:当前租户下新闻总条数
|
||||
$listTotal = array_key_exists('count', $blogNewsRes ?? [])
|
||||
? (int) $blogNewsRes['count']
|
||||
: (int) ($blogNewsRes['total'] ?? 0);
|
||||
$allNewsList = is_array($blogNewsRes['list'] ?? null) ? $blogNewsRes['list'] : [];
|
||||
|
||||
$totalPages = max(1, (int) ceil($listTotal / $pageSize));
|
||||
if ($listPage > $totalPages && $listTotal > 0) {
|
||||
$listPage = $totalPages;
|
||||
$blogNewsQuery['page'] = $listPage;
|
||||
$blogNewsUrl = $apiUrl . '/getCenterNews?' . http_build_query($blogNewsQuery);
|
||||
$blogNewsRes = fetchApiData($blogNewsUrl);
|
||||
$listTotal = array_key_exists('count', $blogNewsRes ?? [])
|
||||
? (int) $blogNewsRes['count']
|
||||
: (int) ($blogNewsRes['total'] ?? 0);
|
||||
$allNewsList = is_array($blogNewsRes['list'] ?? null) ? $blogNewsRes['list'] : [];
|
||||
}
|
||||
|
||||
if ($currentPage > $totalPages) {
|
||||
$currentPage = $totalPages;
|
||||
// 分类排序:sort 升序,其次 id
|
||||
$categories = is_array($articleCategoryList ?? null) ? $articleCategoryList : [];
|
||||
usort($categories, static function ($a, $b) {
|
||||
$sa = (int) ($a['sort'] ?? 0);
|
||||
$sb = (int) ($b['sort'] ?? 0);
|
||||
if ($sa !== $sb) {
|
||||
return $sa <=> $sb;
|
||||
}
|
||||
|
||||
return ((int) ($a['id'] ?? 0)) <=> ((int) ($b['id'] ?? 0));
|
||||
});
|
||||
|
||||
// 构建分类父子结构
|
||||
$rootCategories = [];
|
||||
$childrenMap = [];
|
||||
foreach ($categories as $cat) {
|
||||
$catId = (int) ($cat['id'] ?? 0);
|
||||
$catCid = (int) ($cat['cid'] ?? 0);
|
||||
if ($catId <= 0) continue;
|
||||
if ($catCid === 0) {
|
||||
$rootCategories[] = $cat;
|
||||
} else {
|
||||
if (!isset($childrenMap[$catCid])) {
|
||||
$childrenMap[$catCid] = [];
|
||||
}
|
||||
$childrenMap[$catCid][] = $cat;
|
||||
}
|
||||
}
|
||||
|
||||
$newsListForPage = array_slice($allNewsList, ($currentPage - 1) * $pageSize, $pageSize);
|
||||
/** @return array<string, int|string> */
|
||||
$blogPageQuery = static function (int $lp, int $cateId): array {
|
||||
return ['cate' => $cateId, 'page' => $lp];
|
||||
};
|
||||
?>
|
||||
|
||||
<!-- Page Title -->
|
||||
<div class="page-title dark-background" data-aos="fade" style="background-image: url(<?php echo $baseUrl; ?>/themes/template3/assets/img/blog-page-title-bg.jpg);">
|
||||
<div class="container">
|
||||
<h1>新闻中心</h1>
|
||||
<nav class="breadcrumbs">
|
||||
<ol>
|
||||
<li><a href="<?php echo $baseUrl; ?>/">主页</a></li>
|
||||
<li class="current">新闻中心</li>
|
||||
</ol>
|
||||
</nav>
|
||||
</div>
|
||||
</div><!-- End Page Title -->
|
||||
<!-- Page Title -->
|
||||
<div class="page-title dark-background" data-aos="fade" style="background-image: url(<?php echo $baseUrl; ?>/themes/template3/assets/img/blog-page-title-bg.jpg);">
|
||||
<div class="container">
|
||||
<h1>新闻中心</h1>
|
||||
<nav class="breadcrumbs">
|
||||
<ol>
|
||||
<li><a href="<?php echo htmlspecialchars($baseUrl); ?>/">主页</a></li>
|
||||
<li class="current">新闻中心</li>
|
||||
</ol>
|
||||
</nav>
|
||||
</div>
|
||||
</div><!-- End Page Title -->
|
||||
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
|
||||
<div class="col-lg-12">
|
||||
|
||||
<!-- Blog Posts Section -->
|
||||
<section id="blog-posts" class="blog-posts section">
|
||||
|
||||
<div class="container">
|
||||
<div class="row gy-4">
|
||||
<?php if (!empty($newsList)): ?>
|
||||
<?php foreach ($newsListForPage as $i => $item): ?>
|
||||
<!-- 左侧:新闻分类 -->
|
||||
<aside class="col-lg-3 col-md-4 blog-layout-sidebar">
|
||||
<nav class="blog-category-nav" aria-label="新闻分类">
|
||||
<h2 class="nav-title">新闻分类</h2>
|
||||
<ul>
|
||||
<li>
|
||||
<a
|
||||
href="<?php echo htmlspecialchars($baseUrlBlog); ?>/blog?<?php echo http_build_query(['cate' => 0, 'page' => 1]); ?>"
|
||||
class="<?php echo $selectedCateId === 0 ? 'active' : ''; ?>">全部</a>
|
||||
</li>
|
||||
<?php foreach ($rootCategories as $rootCat): ?>
|
||||
<?php
|
||||
$rootId = (int) ($rootCat['id'] ?? 0);
|
||||
$rootName = (string) ($rootCat['name'] ?? '未命名');
|
||||
$rootLink = $baseUrlBlog . '/blog?' . http_build_query(['cate' => $rootId, 'page' => 1]);
|
||||
$children = $childrenMap[$rootId] ?? [];
|
||||
$hasChildren = !empty($children);
|
||||
$isParentActive = $selectedCateId === $rootId;
|
||||
?>
|
||||
<li>
|
||||
<a
|
||||
href="<?php echo htmlspecialchars($rootLink); ?>"
|
||||
class="<?php echo $isParentActive ? 'active' : ''; ?>"><?php echo htmlspecialchars($rootName); ?></a>
|
||||
<?php if ($hasChildren): ?>
|
||||
<ul class="children">
|
||||
<?php foreach ($children as $childCat): ?>
|
||||
<?php
|
||||
$title = $item['title'] ?? '无标题';
|
||||
$author = $item['author'] ?? '';
|
||||
$category = $item['category'] ?? ($item['category_name'] ?? '');
|
||||
|
||||
$publishDateRaw = $item['publish_date'] ?? '';
|
||||
$postDate = !empty($publishDateRaw) ? date('Y年m月d日', strtotime($publishDateRaw)) : '';
|
||||
|
||||
$thumbUrl = $item['thumb'] ?? ($item['image'] ?? '');
|
||||
if (!empty($thumbUrl)) {
|
||||
if (strpos($thumbUrl, 'http') === 0) {
|
||||
$imgSrc = $thumbUrl;
|
||||
} elseif (strpos($thumbUrl, '/') === 0) {
|
||||
$imgSrc = $apiUrl . $thumbUrl;
|
||||
} else {
|
||||
$imgSrc = $apiUrl . '/' . $thumbUrl;
|
||||
}
|
||||
} else {
|
||||
$imgIndex = ($i % 6) + 1; // blog-1.jpg ~ blog-6.jpg
|
||||
$imgSrc = $baseUrl . "/themes/template3/assets/img/blog/blog-{$imgIndex}.jpg";
|
||||
}
|
||||
|
||||
// 文章详情链接必须走前台域名,否则 api.yunzer.cn 上租户解析不生效
|
||||
$articleUrl = $baseUrl . '/article_detail/' . ($item['id'] ?? 0);
|
||||
$childId = (int) ($childCat['id'] ?? 0);
|
||||
$childName = (string) ($childCat['name'] ?? '未命名');
|
||||
$childLink = $baseUrlBlog . '/blog?' . http_build_query(['cate' => $childId, 'page' => 1]);
|
||||
?>
|
||||
|
||||
<div class="col-lg-3 col-md-6">
|
||||
<article>
|
||||
|
||||
<div class="post-media">
|
||||
<img src="<?php echo htmlspecialchars($imgSrc); ?>" alt="" class="post-media-img" />
|
||||
</div>
|
||||
|
||||
<p class="post-category"><?php echo htmlspecialchars($category); ?></p>
|
||||
|
||||
<h2 class="title">
|
||||
<a href="<?php echo htmlspecialchars($articleUrl); ?>">
|
||||
<?php echo htmlspecialchars($title); ?>
|
||||
</a>
|
||||
</h2>
|
||||
|
||||
<div class="d-flex align-items-center">
|
||||
<img src="<?php echo $baseUrl; ?>/themes/template3/assets/img/blog/blog-author.jpg" alt="" class="img-fluid post-author-img flex-shrink-0">
|
||||
<div class="post-meta">
|
||||
<p class="post-author"><?php echo htmlspecialchars($author); ?></p>
|
||||
<p class="post-date">
|
||||
<time datetime="<?php echo htmlspecialchars($publishDateRaw); ?>">
|
||||
<?php echo htmlspecialchars($postDate); ?>
|
||||
</time>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</article>
|
||||
</div><!-- End post list item -->
|
||||
<?php endforeach; ?>
|
||||
<?php else: ?>
|
||||
<div class="col-12 text-center">
|
||||
<p>暂无新闻数据</p>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</section><!-- /Blog Posts Section -->
|
||||
|
||||
<!-- Blog Pagination Section -->
|
||||
<section id="blog-pagination" class="blog-pagination section">
|
||||
|
||||
<div class="container">
|
||||
<div class="d-flex justify-content-center">
|
||||
<ul>
|
||||
<?php
|
||||
$prevPage = max(1, $currentPage - 1);
|
||||
$nextPage = min($totalPages, $currentPage + 1);
|
||||
$showMax = 10;
|
||||
$showTotal = min($totalPages, $showMax);
|
||||
?>
|
||||
<li><a href="<?php echo $baseUrl; ?>/blog?page=<?php echo $prevPage; ?>"><i class="bi bi-chevron-left"></i></a></li>
|
||||
<?php for ($p = 1; $p <= $showTotal; $p++): ?>
|
||||
<li>
|
||||
<a
|
||||
href="<?php echo $baseUrl; ?>/blog?page=<?php echo $p; ?>"
|
||||
<?php echo ($p === $currentPage) ? 'class="active"' : ''; ?>
|
||||
>
|
||||
<?php echo $p; ?>
|
||||
</a>
|
||||
href="<?php echo htmlspecialchars($childLink); ?>"
|
||||
class="<?php echo $selectedCateId === $childId ? 'active' : ''; ?>"><?php echo htmlspecialchars($childName); ?></a>
|
||||
</li>
|
||||
<?php endfor; ?>
|
||||
<li><a href="<?php echo $baseUrl; ?>/blog?page=<?php echo $nextPage; ?>"><i class="bi bi-chevron-right"></i></a></li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
<?php endif; ?>
|
||||
</li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
</nav>
|
||||
</aside>
|
||||
|
||||
<!-- 右侧:当前分类下的文章列表 -->
|
||||
<div class="col-lg-9 col-md-8">
|
||||
|
||||
<section id="blog-posts" class="blog-posts section">
|
||||
<div class="container h520 px-0">
|
||||
<div class="row gy-4">
|
||||
<?php if (!empty($allNewsList)): ?>
|
||||
<?php foreach ($allNewsList as $i => $item): ?>
|
||||
<?php
|
||||
$title = $item['title'] ?? '无标题';
|
||||
$author = $item['author'] ?? '';
|
||||
$cateKey = (int) ($item['cate'] ?? 0);
|
||||
$category = $item['category'] ?? ($item['category_name'] ?? '');
|
||||
|
||||
$publishDateRaw = $item['publish_date'] ?? '';
|
||||
$postDate = !empty($publishDateRaw) ? date('Y年m月d日', strtotime((string) $publishDateRaw)) : '';
|
||||
|
||||
$thumbUrl = $item['thumb'] ?? ($item['image'] ?? '');
|
||||
if (!empty($thumbUrl)) {
|
||||
if (strpos($thumbUrl, 'http') === 0) {
|
||||
$imgSrc = $thumbUrl;
|
||||
} elseif (strpos($thumbUrl, '/') === 0) {
|
||||
$imgSrc = $apiUrl . $thumbUrl;
|
||||
} else {
|
||||
$imgSrc = $apiUrl . '/' . $thumbUrl;
|
||||
}
|
||||
} else {
|
||||
$imgIndex = ($i % 6) + 1;
|
||||
$imgSrc = $baseUrl . "/themes/template3/assets/img/blog/blog-{$imgIndex}.jpg";
|
||||
}
|
||||
|
||||
$articleUrl = $baseUrl . '/article_detail/' . ($item['id'] ?? 0);
|
||||
?>
|
||||
|
||||
<div class="col-lg-3 col-md-6">
|
||||
<article>
|
||||
|
||||
<div class="post-media">
|
||||
<img src="<?php echo htmlspecialchars($imgSrc); ?>" alt="" class="post-media-img" />
|
||||
</div>
|
||||
|
||||
<!-- <p class="post-category"><?php echo htmlspecialchars($category); ?></p> -->
|
||||
|
||||
<h2 class="title">
|
||||
<a href="<?php echo htmlspecialchars($articleUrl); ?>">
|
||||
<?php echo htmlspecialchars($title); ?>
|
||||
</a>
|
||||
</h2>
|
||||
|
||||
<div class="d-flex align-items-center">
|
||||
<img src="<?php echo $baseUrl; ?>/themes/template3/assets/img/blog/blog-author.jpg" alt="" class="img-fluid post-author-img flex-shrink-0">
|
||||
<div class="post-meta">
|
||||
<p class="post-author"><?php echo htmlspecialchars($author); ?></p>
|
||||
<p class="post-date">
|
||||
<time datetime="<?php echo htmlspecialchars($publishDateRaw); ?>">
|
||||
<?php echo htmlspecialchars($postDate); ?>
|
||||
</time>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</article>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
<?php else: ?>
|
||||
<div class="col-12 text-center py-5">
|
||||
<p class="mb-0"><?php echo $selectedCateId > 0 ? '该分类下暂无新闻' : '暂无新闻数据'; ?></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</section><!-- /Blog Pagination Section -->
|
||||
<?php endif; ?>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</section><!-- /Blog Posts Section -->
|
||||
|
||||
<?php if (!empty($allNewsList) && $totalPages >= 1): ?>
|
||||
<section id="blog-pagination" class="blog-pagination section">
|
||||
<div class="container px-0">
|
||||
<div class="d-flex justify-content-center">
|
||||
<ul>
|
||||
<?php
|
||||
$prevPage = max(1, $listPage - 1);
|
||||
$nextPage = min($totalPages, $listPage + 1);
|
||||
$showMax = 10;
|
||||
$showTotal = min($totalPages, $showMax);
|
||||
$prevHref = $baseUrlBlog . '/blog?' . http_build_query($blogPageQuery($prevPage, $selectedCateId));
|
||||
$nextHref = $baseUrlBlog . '/blog?' . http_build_query($blogPageQuery($nextPage, $selectedCateId));
|
||||
?>
|
||||
<li><a href="<?php echo htmlspecialchars($prevHref); ?>"><i class="bi bi-chevron-left"></i></a></li>
|
||||
<?php for ($p = 1; $p <= $showTotal; $p++): ?>
|
||||
<?php $pHref = $baseUrlBlog . '/blog?' . http_build_query($blogPageQuery($p, $selectedCateId)); ?>
|
||||
<li>
|
||||
<a
|
||||
href="<?php echo htmlspecialchars($pHref); ?>"
|
||||
<?php echo ($p === $listPage) ? 'class="active"' : ''; ?>>
|
||||
<?php echo $p; ?>
|
||||
</a>
|
||||
</li>
|
||||
<?php endfor; ?>
|
||||
<li><a href="<?php echo htmlspecialchars($nextHref); ?>"><i class="bi bi-chevron-right"></i></a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</section><!-- /Blog Pagination Section -->
|
||||
<?php endif; ?>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</main>
|
||||
|
||||
<?php require_once __DIR__ . '/footer.php'; ?>
|
||||
@ -55,8 +55,6 @@ $articleCategoryUrl = $apiUrl . '/getArticleCategories?baseUrl=' . urlencode($ho
|
||||
$articleCategoryRes = fetchApiData($articleCategoryUrl);
|
||||
$articleCategoryList = $articleCategoryRes['list'] ?? [];
|
||||
|
||||
print_r($articleCategoryList);
|
||||
|
||||
// 获取特色服务
|
||||
$servicesList = $homeInfo['services'] ?? [];
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user