技术栈
项目坚持零框架、零构建的部署体验,但内部仍然按清晰层次组织代码,便于后续扩展。
| 层次 | 技术 | 说明 |
|---|---|---|
| 后端 | PHP 8.1+ | 纯 PHP 实现,不依赖 Composer 包。 |
| 数据库 | SQLite3 | 文件数据库,默认存储在 storage/site.db,支持 WAL 模式。 |
| 架构 | MVC + Router | 控制器、模型、视图分层,集中路由注册。 |
| 前台主题 | Tailwind CSS CDN | 主题模板优先使用 Tailwind 类名,避免重样式侵入核心。 |
| 后台编辑 | Jodit / SortableJS / Font Awesome | 富文本、拖拽排序、图标和后台界面能力按需加载。 |
系统架构
请求从 Web 服务器重写到 index.php,入口文件完成环境初始化、自动加载、辅助函数加载、数据库连接和路由分发。
入口与路由
index.php 是唯一入口;app/routes.php 注册前台、后台、媒体、更新、主题、区块、菜单和轮播图路由。
核心模块
Router、Controller、Database、Migrator、AuthManager、MediaManager 构成系统基础层。
模型层
模型继承 BaseModel,封装 SQLite 操作。产品、文章、分类、询盘、留言、用户、菜单、轮播图等均有独立模型。
视图层
前台视图位于 themes/default/,后台视图位于 app/views/admin/,主题与后台管理界面互相隔离。
HTTP 请求
-> Apache/Nginx 重写到 index.php
-> 定义常量、Session、错误处理
-> PSR-4 自动加载 App\ 命名空间
-> 加载 app/Helpers/Helpers.php
-> Database::getInstance() 并执行待处理迁移
-> Router 匹配 app/routes.php
-> Controller 调用 Model
-> 渲染后台视图或 themes/{theme} 前台模板
路由与页面扩展
新增前台页面通常只需要注册路由、增加控制器方法和创建主题模板。不要把页面逻辑直接写进入口文件。
// 1. app/routes.php 注册路由
$router->add('GET', '/faq', [SiteController::class, 'faq']);
// 2. SiteController 添加方法
public function faq(): void
{
$this->renderSite('faq', [
'seo' => ['title' => 'FAQ']
]);
}
// 3. themes/default/faq.php 创建模板
// 使用 h()、url()、asset_url() 输出动态内容
functions.php。
数据库迁移
数据库结构演进通过 app/Migrations/ 下的时间戳文件管理。系统初始化数据库或发现新迁移时会自动执行,避免手动导入 SQL。
// app/Migrations/20260412120000_create_faqs_table.php
return new class {
public function up(SQLite3 $db): void
{
$db->exec('CREATE TABLE IF NOT EXISTS faqs (
id INTEGER PRIMARY KEY AUTOINCREMENT,
question TEXT NOT NULL,
answer TEXT NOT NULL,
sort_order INTEGER DEFAULT 0,
created_at TEXT NOT NULL
)');
}
public function down(SQLite3 $db): void
{
$db->exec('DROP TABLE IF EXISTS faqs');
}
};
命名方式
文件名使用时间戳前缀,保证迁移执行顺序稳定,例如 20260617000000_create_app_store_theme_installs_table.php。
更新维护
后台程序更新页可查看迁移状态,必要时手动触发待处理迁移。
数据模型
系统围绕外贸官网常见业务建模:用户、站点设置、产品、内容、询盘、留言、媒体、菜单、轮播和更新记录。
| 模型 / 表 | 用途 | 扩展提示 |
|---|---|---|
User / users |
后台管理员与 Staff 账号,包含角色和权限。 | 新增后台模块时同步检查权限映射。 |
Product / products |
产品标题、Slug、分类、图片、摘要、SEO 等。 | 价格区间使用 product_prices 独立表。 |
PostModel / posts |
文章、案例、自定义页面共用内容表。 | 通过 post_type 区分 post、case、page。 |
Inquiry / inquiries |
产品询盘、状态流转、导出和详情查看。 | 接 CRM 时优先从这里对接。 |
Slider / sliders |
轮播图区块和轮播图片管理。 | 主题首屏轮播应读取此模型,不要在区块里逐张硬编码。 |
Menu / menus |
多菜单、多级菜单和拖拽排序。 | 常用 Slug 包括 main-nav 与 footer。 |
主题开发
主题是 SHOPAGG 的主要扩展面。主题目录只负责前台展示和可配置区块,不应修改核心表结构或破坏用户数据。
目录结构
完整主题建议包含 header.php、footer.php、home.php、产品/文章/案例/页面模板、blocks.php、functions.php、style.css 和 screenshot.jpg。
区块系统
blocks.php 返回区块定义,后台用户值保存在 storage/blocks/{theme}.php,用户值覆盖主题默认值。
产品选择器
所有产品展示区块应使用 product_picker,不要让用户在文本框手填产品 ID。
媒体选择器
图片、视频、文件使用 image 或 media 字段;轮播使用 select 选择后台轮播图。
/*
Theme Name: Furniture
Theme URI: https://example.com
Author: Your Name
Description: B2B furniture website theme.
Version: 1.0.0
License: MIT
*/
// blocks.php
return [
'home_featured' => [
'label' => '首页 - 热销产品',
'description' => '使用产品选择器勾选展示商品。',
'fields' => [
'heading' => ['type' => 'text', 'label' => '标题', 'default' => 'Hot Products'],
'product_ids' => [
'type' => 'product_picker',
'label' => '勾选商品列表',
'default' => '',
'limit' => 8,
'status' => 'active',
],
],
],
];
安全规范
模板和扩展代码必须尊重系统的安全边界,尤其是后台配置值、媒体路径、URL 和上传文件。
输出转义
文本和属性使用 h(),多行文本使用 nl2br(h(...)),避免直接输出后台配置值。
URL 与媒体
站内链接使用 url('/path'),上传文件或相对路径使用 asset_url($path)。
数据操作
不要在主题代码中写数据库、删文件、重置配置或执行破坏性操作。
上传校验
主题 zip 必须包含合法 style.css 元数据和必需模板文件,上传媒体由 MediaManager 统一处理。
<h2><?= h(block('home_about', 'heading')) ?></h2>
<p><?= nl2br(h(block('home_about', 'text'))) ?></p>
<a href="<?= h(url('/products')) ?>">Products</a>
<img src="<?= h(asset_url($image)) ?>" alt="<?= h($title) ?>">
验收清单
新增功能或主题交付前,至少完成语法、字段、数据和响应式检查。以下命令来自项目文档中的主题质量要求。
# PHP 语法检查
find themes/{theme_name} -name '*.php' -print0 | xargs -0 -n1 php -l
# 检查产品选择器、媒体字段和轮播下拉
rg "product_picker|media_type_key|slider_slug|type' => 'select'" themes/{theme_name}/blocks.php
# 检查是否存在可疑静态产品字段
rg "item[0-9]+_(title|image|url)" themes/{theme_name}/blocks.php
style.css 有主题元数据,screenshot.jpg 存在。product_picker,未选择产品时有真实数据兜底或空状态。image 或 media 字段,轮播使用后台轮播数据。storage/blocks/{theme}.php 配置。