主题开发指南
说明
本文档旨在指导开发者如何开发和发布新的主题,以供 InnoShop 电商系统使用。
发布主题
使用以下 Artisan 命令将 innopacks/front 目录下的主题发布为默认主题到 /themes/default。
php artisan inno:publish-theme在后台的"设计-模板主题列表"中,选择并启用名为 default 的主题,即可轻松切换至该主题。
主题文件结构
您可以基于 /themes/default 文件夹改名或复制以创建新的主题。
最简主题(仅需两个目录)
一个可用的主题最少只需要 views/layouts 和样式文件:
themes/my-theme/
├── config.json # 主题信息(发布到市场必须)
├── assets/scss/app.scss # 主题样式
└── views/
└── layouts/
└── app.blade.php # 布局文件(必须)完整主题(含自定义路由、Demo 数据、多语言)
以 GansuHub 主题为例,完整目录结构如下:
themes/gansuhub/
├── config.json # 主题配置
├── README.md # 主题说明
├── setup/ # 高级:引导与数据
│ ├── boot.php # 主题启动钩子
│ └── seeder.php # Demo 数据导入
├── routes/ # 高级:自定义路由
│ └── front.php # 前台路由
├── lang/ # 高级:多语言
│ ├── en/
│ │ ├── common.php # 公共翻译
│ │ ├── landing.php # 首页翻译
│ │ ├── landport.php # 自定义页面翻译
│ │ └── ...
│ ├── zh-cn/
│ │ └── ...
│ └── ...
├── assets/ # 前端资源源码
│ ├── scss/app.scss # SCSS 样式入口
│ └── js/app.js # JS 入口
├── public/ # 静态资源(编译后/图片等)
│ ├── css/
│ ├── js/
│ └── images/
└── views/ # Blade 模板(必须)
├── layouts/
│ └── app.blade.php # 主布局
├── home.blade.php # 首页
├── components/ # 可复用组件
│ ├── header.blade.php
│ ├── footer.blade.php
│ └── breadcrumb.blade.php
├── pages/ # 自定义页面
│ ├── about.blade.php
│ ├── contact.blade.php
│ └── landport.blade.php
├── categories/
├── products/
├── account/
└── shared/各目录说明
| 目录 | 必须 | 说明 |
|---|---|---|
views/layouts/ | 是 | 主布局模板,系统渲染页面时必须 |
assets/scss/ | 否 | SCSS 源码,编译后输出到 public/css/ |
assets/js/ | 否 | JS 源码,编译后输出到 public/js/ |
public/ | 否 | 静态资源(图片、编译后 CSS/JS),通过 theme_asset() 引用 |
config.json | 否 | 主题元数据,发布到市场必须包含 |
setup/boot.php | 否 | 主题启动钩子,注册 Hook、Filter |
setup/seeder.php | 否 | Demo 数据种子,后台一键导入 |
routes/front.php | 否 | 自定义前台路由 |
lang/ | 否 | 主题多语言文件 |
若您需要撤销对主题模板的更改并恢复系统默认的模板,可以通过删除 views 目录下自定义的 blade.php 文件来实现。
一旦删除,系统将自动重新采用 innopacks/front 目录中的原始 Blade 模板进行渲染。
1. CSS 样式
在 /css 目录下,您可以组织多个 CSS 或者 SCSS 文件,以模块化的方式管理样式。
2. JavaScript 脚本
/js 目录用于存放 JavaScript 文件,可以包含主题的交互逻辑和第三方库。
3. 公共静态资源
/public 目录用于存放主题的静态资源,这些资源可以通过 Web 访问,在 blade 中用 theme_asset() 引入。
4. 模板文件
/views 目录包含主题的 Blade 模板文件,这些模板定义了主题的布局和页面结构。
5. 配置文件
config.json 是主题的配置文件,示例如下:
{
"code": "default",
"name": {
"zh_cn": "InnoShop 默认模板",
"en": "InnoShop Default Theme"
},
"description": {
"zh_cn": "InnoShop 默认模板",
"en": "InnoShop Default Theme"
},
"version": "v1.0.0",
"author": {
"name": "InnoShop",
"email": "team@innoshop.com"
}
}高级功能
1. 自定义路由(routes/front.php)
当主题需要额外的前台页面(如关于我们、物流专线、基础设施展示等),可以通过 routes/front.php 注册自定义路由。
<?php
// themes/gansuhub/routes/front.php
use Illuminate\Support\Facades\Route;
Route::get('/landport', function () {
return view('pages.landport');
})->name('pages.landport');
Route::get('/freight-routes', function () {
return view('pages.freight-routes');
})->name('pages.freight_routes');对应的视图文件放在 views/pages/ 目录下,如 views/pages/landport.blade.php。
路由文件放在 routes/ 目录下,目前支持 front.php(前台路由),系统会自动加载。
2. 主题启动钩子(setup/boot.php)
boot.php 在每次请求时通过 FrontServiceProvider 自动加载,用于注册 Hook 和 Filter。
<?php
// themes/gansuhub/setup/boot.php
return function () {
// 在首页内容顶部插入自定义 HTML
listen_blade_insert('home.content.top', function ($data) {
return '<div class="custom-banner">...</div>';
});
// 注册其他 Hook...
};常用 Hook 函数:
listen_blade_insert($hookName, $callback)— 在指定位置插入 HTML 内容add_filter($hookName, $callback)— 过滤数据
3. Demo 数据导入(setup/seeder.php)
seeder.php 用于在后台"一键导入 Demo 数据"时执行,通常包括:
- 清理已有测试数据
- 创建分类(Category)
- 创建商品(Product)
- 创建文章(Article)
- 创建页面(Page)
- 设置首页配置(分类、热销商品、轮播图等)
- 设置 SEO Meta 信息
基本结构:
<?php
// themes/gansuhub/setup/seeder.php
use InnoShop\Common\Repositories\CategoryRepo;
use InnoShop\Common\Repositories\ProductRepo;
use InnoShop\Common\Repositories\PageRepo;
use InnoShop\Common\Repositories\SettingRepo;
return function (string $dir): void {
$themeCode = basename($dir);
// 1. 清理已有数据
clearExistingData();
// 2. 创建分类
$categories = createCategories($themeCode);
// 3. 创建商品
$products = createProducts($categories, $themeCode);
// 4. 创建页面
createPages();
// 5. 设置首页布局
setupHomeSettings($categories, $products);
// 6. 设置轮播图
updateSlideshowSettings($themeCode);
};图片路径约定:Demo 数据中的图片引用使用 static/themes/{themeCode}/images/ 前缀,导入时主题 public/images/ 下的图片会被复制到 public/static/themes/{themeCode}/images/。
创建分类示例:
$category = CategoryRepo::getInstance()->create([
'slug' => 'new-energy-vehicles',
'position' => 1,
'active' => 1,
'image' => 'static/themes/gansuhub/images/home/category-nev.webp',
'translations' => [
['locale' => 'zh-cn', 'name' => '新能源汽车', 'summary' => '描述...'],
['locale' => 'en', 'name' => 'New Energy Vehicles', 'summary' => 'Description...'],
],
]);设置首页商品楼层:
SettingRepo::getInstance()->updateSystemValue('home_hot_products', [
'display_mode' => 'flat',
'title_align' => 'center',
'floors' => [
[
'name' => ['zh-cn' => '楼层名称', 'en' => 'Floor Name'],
'subtitle' => ['zh-cn' => '副标题', 'en' => 'Subtitle'],
'products' => [$productId1, $productId2],
],
],
]);设置轮播图:
SettingRepo::getInstance()->updateSystemValue('slideshow', [
[
'image' => ['zh-cn' => $base.'slideshow/hero-01.webp', 'en' => $base.'slideshow/hero-01.webp'],
'link' => 'product:byd-atto3-export', // 商品链接用 product:{slug}
'title' => ['zh-cn' => '标题', 'en' => 'Title'],
'subtitle' => ['zh-cn' => '副标题', 'en' => 'Subtitle'],
],
]);4. 主题多语言(lang/)
主题支持独立的多语言文件,用于自定义页面的翻译。语言文件放在 lang/{locale}/ 目录下,在 Blade 模板中通过 theme_trans() 函数调用。
目录结构:
lang/
├── en/
│ ├── common.php # 全站公共翻译(导航、页脚等)
│ ├── landing.php # 首页翻译
│ ├── landport.php # 陆港页面翻译
│ ├── freight.php # 物流页面翻译
│ └── ...
├── zh-cn/
│ ├── common.php
│ └── ...
└── ru/
└── ...语言文件示例(lang/en/landport.php):
<?php
return [
'hero_title' => 'Gansu (Lanzhou) International Land Port',
'hero_subtitle' => 'A national Class-I land port...',
'stat_1_val' => '4D6R',
'stat_1_label' => 'International Logistics Corridors',
];在 Blade 中使用:
<h1>{{ theme_trans('landport.hero_title') }}</h1>
<p>{{ theme_trans('landport.hero_subtitle') }}</p>theme_trans() 会自动根据当前语言环境加载对应 lang/{locale}/ 下的文件。
5. 自定义页面视图
自定义页面的视图放在 views/pages/ 目录下,通过 routes/front.php 中注册的路由访问。
页面模板通常继承主布局:
@extends('layouts.app')
@section('title', theme_trans('landport.hero_title'))
@section('body-class', 'page-landport')
@section('content')
<x-front-breadcrumb type="static" value="" title="{{ theme_trans('landport.hero_title') }}" />
<section class="gh-landport-hero">
<div class="gh-landport-hero__inner tf-container">
<h1>{{ theme_trans('landport.hero_title') }}</h1>
<p>{{ theme_trans('landport.hero_subtitle') }}</p>
</div>
</section>
@endsection6. 配置文件(config.json)
config.json 是主题的配置文件,包含主题的元数据信息:
{
"code": "gansuhub",
"name": {
"zh-cn": "陇贸优选",
"en": "GansuHub"
},
"description": {
"zh-cn": "甘肃跨境电商平台",
"en": "Gansu cross-border platform"
},
"version": "v1.0.0",
"icon": "/images/logo.png",
"author": {
"name": "帆连科技",
"email": "team@innoshop.com"
}
}code— 主题唯一标识,必须与目录名一致name— 多语言主题名称,在后台显示description— 多语言主题描述version— 语义化版本号icon— 主题缩略图路径(相对于public/目录)author— 作者信息
简化资源加载系统
InnoShop 支持简化的主题资源加载系统,让主题开发者可以轻松自定义样式和脚本,同时保持系统的简洁性。系统采用"先导入默认,再覆盖自定义"的方式,通过 theme_asset() 函数的智能回退机制实现资源加载。
工作原理
编译逻辑
- 只编译存在的文件: 只有当主题目录中存在相应的 SCSS/JS 文件时,才会编译到主题目录
- 简单直接: 无需复杂的智能导入逻辑,主题开发者完全控制导入内容
- 自动清理: 每次编译前自动删除主题编译目录,避免旧文件残留
编译输出
- 主题 CSS 文件 →
public/static/themes/{theme}/css/app.css - 主题 JS 文件 →
public/static/themes/{theme}/js/app.js - 主题 Bootstrap →
public/static/themes/{theme}/css/bootstrap.css
运行时加载
- 使用
theme_asset()函数智能加载资源 - 如果主题文件存在,加载主题文件
- 如果主题文件不存在,自动回退到默认文件
- 无需手动判断文件存在性
设置与编译
编译自定义主题的 CSS 和 JS
InnoShop 使用 Vite 构建前端资源,通过 THEME 环境变量指定要编译的主题:
# 编译指定主题
THEME=factory npm run build
# 同时编译后台模块 + 主题
TARGET=panel THEME=factory npm run build详细的前端编译说明请参考 前端编译指南。
编译过程由 build.js 脚本驱动,逐个编译主题的 SCSS 和 JS 入口,输出到 public/static/themes/{theme}/ 目录。
编译后的文件会输出到:
public/static/themes/factory/
├── css/
│ ├── app.css # 主题SCSS编译
│ └── bootstrap.css # 主题Bootstrap编译
└── js/
└── app.js # 主题JS编译在布局文件中使用
使用 theme_asset() 替换原有的资源引用,该函数会自动检查主题文件是否存在:
{{-- 智能主题资源加载 - 自动回退机制 --}}
<link rel="stylesheet" href="{{ theme_asset('css/bootstrap.css') }}">
<link rel="stylesheet" href="{{ theme_asset('css/app.css') }}">
<script src="{{ theme_asset('js/app.js') }}"></script>- 如果存在 → 返回主题文件路径
- 如果不存在 → 返回默认文件路径
主题开发模式
推荐方式:先导入默认,再覆盖自定义
这是最简洁和高效的开发方式。
CSS 开发
// themes/factory/css/app.scss
@import '@front/css/app';
@import './page-product'; // 自定义样式JS 开发
// themes/factory/js/app.js
import '@front/js/inno-bootstrap'
import { initInno, bindGlobalEvents } from '@front/js/inno-init'
import '@front/js/inno-validation'
import '@front/js/inno-header'
import '@front/js/inno-footer'
import '@front/js/inno-autocomplete'
initInno()
$(function () {
bindGlobalEvents()
})
import './custom'优势
- 代码极简 - 只需要 2 行代码
- 维护简单 - 自动跟随默认样式更新
- 覆盖完美 - CSS 层叠确保自定义样式优先级
- 开发友好 - 主题开发者只需关注自定义部分
主题开发示例
场景1: 完全使用默认资源
如果主题没有任何自定义文件,系统会:
- 清理编译目录
- 不编译任何主题文件
theme_asset()自动回退到默认资源
场景2: 只自定义样式
创建主题样式文件 themes/factory/css/app.scss:
// 导入默认样式
@import '../../../innopacks/front/resources/css/app.scss';
// 自定义样式
.product-title {
color: red !important;
}系统会:
- 清理编译目录
- 编译
app.scss到主题目录 theme_asset()使用主题 CSS,默认 JS
场景3: 完全自定义主题
创建所有需要的文件:
themes/factory/
├── css/
│ ├── app.scss # 主样式文件
│ ├── bootstrap/
│ │ └── bootstrap.scss # 自定义Bootstrap
│ └── page-product.scss # 自定义页面样式
├── js/
│ ├── app.js # 主脚本文件
│ └── custom.js # 自定义脚本
└── views/
├── layouts/
│ └── app.blade.php # 布局文件
└── products/
└── show.blade.php # 自定义页面模板开发工作流
1. 创建主题目录
mkdir -p themes/factory/css
mkdir -p themes/factory/js
mkdir -p themes/factory/views2. 复制默认布局文件
cp innopacks/front/resources/views/layouts/app.blade.php themes/factory/views/layouts/3. 修改布局文件
使用 theme_asset() 替换原有的资源引用:
{{-- 替换原有的 mix() 调用 --}}
<link rel="stylesheet" href="{{ theme_asset('css/bootstrap.css') }}">
<link rel="stylesheet" href="{{ theme_asset('css/app.css') }}">
<script src="{{ theme_asset('js/app.js') }}"></script>4. 创建自定义样式(推荐方式)
// themes/factory/css/app.scss
@import '@front/css/app';
@import './page-product'; // 只添加需要的自定义样式5. 编译资源
THEME=factory npm run dev6. 测试主题
访问前台页面查看效果。
前台 SCSS 组织架构
前台 SCSS 已按 base/layout/components/modules/pages 分层组织,主题复用默认样式时推荐使用 @front/css/app 作为入口。
目录结构
路径:innopacks/front/resources/css/
app.scss- 入口文件,仅负责按顺序组织引入base/- 基础层(变量、全局、字体)variables.scss- 主题变量(使用!default,允许主题覆盖)global.scss- 全局通用样式/工具类font.scss- 字体相关样式
layout/- 布局层(Header/Footer 等)header.scssfooter.scss
components/- 小组件(通常跨页面复用)breadcrumb.scssfilter-sidebar.scss
modules/- 功能模块(可跨页面复用的业务模块)swiper.scssproduct-item.scssproduct-tab.scssnews.scssproduct-review.scsssocial.scss
pages/- 页面层(只在特定页面生效)home.scsslogin.scssaccount.scsscategories.scssproduct.scsscart.scsscheckout.scssorder.scssaddresses.scssbrands.scsscheckout-success.scsswallet.scsstransaction.scss
app.scss 加载顺序
入口文件 innopacks/front/resources/css/app.scss 建议遵循以下顺序(同类型放一起,且越"基础"的越靠前):
base/variables→base/global→base/fontlayout/*components/*modules/*pages/*
如果默认入口 @front/css/app 未覆盖某些样式,可以在主题侧按需额外引入对应的 @front/css/<group>/<file>。
自定义主题如何复用默认 SCSS
主题侧入口示例:themes/<theme>/css/app.scss
方式 1:引入入口(推荐)
适用场景:大多数主题(希望完整复用默认样式,只写覆盖/增量)。
@import './_variables';
@import '@front/css/app';
@import './global';
@import './header';
@import './footer';如果主题需要额外引入某个默认入口未包含的样式,可以按需追加:
@import '@front/css/<group>/<file>';方式 2:按需引入单个文件(精细化)
适用场景:landing/轻量主题、只覆盖少量页面,或者希望尽量减小 CSS 体积。
@import './_variables';
@import '@front/css/base/variables';
@import '@front/css/base/global';
@import '@front/css/base/font';
@import '@front/css/layout/header';
@import '@front/css/layout/footer';
@import '@front/css/components/breadcrumb';
@import '@front/css/components/filter-sidebar';
@import '@front/css/modules/swiper';
@import '@front/css/modules/product-item';
@import '@front/css/pages/home';
@import './header';
@import './pages/home';两种常用策略:
- 覆盖 - 仍然引入默认同名模块/页面样式,然后在主题侧追加同类样式覆盖(放在后面即可)
- 替换 - 不引入默认的某个模块/页面样式,由主题完全实现该部分样式,避免默认样式干扰
关键点:
- 主题变量文件要在
@front/css/app之前引入 - 默认
base/variables.scss使用!default,主题先定义的$primary/$price/$default不会被默认覆盖 - 额外能力按需额外引入(从
@front/css/<group>/<file>引入)
@front 别名说明
构建配置为 SCSS/JS 提供了别名:
@front→innopacks/front/resources
示例:
@import '@front/css/app';迁移/新增文件规则
- 新文件按用途放入对应目录:
base/layout/components/modules/pages - 文件名用小写 kebab-case:
checkout-success.scss - 一个模块如果增长为多个文件:在
modules/<module>/下分拆,并由modules/<module>.scss汇总引入
支持的文件类型
CSS/SCSS 文件
app.scss- 主样式文件bootstrap/bootstrap.scss- Bootstrap 样式page-*.scss- 页面特定样式
JavaScript 文件
app.js- 主脚本文件custom.js- 自定义脚本文件
最佳实践
- 使用推荐方式 - 先导入默认,再覆盖自定义
- 保持简洁 - 只创建需要的自定义文件
- 版本控制 - 使用 Git 管理主题文件
- 测试充分 - 在不同场景下测试主题效果
- 文档完善 - 为自定义功能添加注释说明
注意事项
- 使用
THEME环境变量指定编译主题,如THEME=factory npm run build - 推荐使用"先导入默认,再覆盖自定义"的方式
- 建议使用版本控制管理主题文件
- 每次编译都会清理旧文件,确保编译结果的纯净性
theme_asset()函数会自动处理文件存在性检查
故障排除
问题1: 编译失败
- 检查主题目录是否存在
- 检查 SCSS 语法是否正确
- 检查文件路径是否正确
问题2: 样式没有生效
- 检查编译是否成功
- 检查浏览器缓存
- 检查
theme_asset()是否正确回退
问题3: 默认样式被覆盖
- 检查主题文件是否正确导入了默认文件
- 检查 CSS 优先级是否正确
- 确保自定义样式在默认样式之后导入
问题4: 文件不存在错误
- 检查
theme_asset()函数是否正常工作 - 确保默认文件存在
- 检查文件路径是否正确
问题5: 清理失败
- 检查文件权限
- 确保没有其他进程占用文件
- 手动删除编译目录后重试