Skip to content

主题开发指南

说明

本文档旨在指导开发者如何开发和发布新的主题,以供 InnoShop 电商系统使用。

发布主题

使用以下 Artisan 命令将 innopacks/front 目录下的主题发布为默认主题到 /themes/default

bash
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.phpDemo 数据种子,后台一键导入
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 是主题的配置文件,示例如下:

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
<?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
<?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
<?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/

创建分类示例

php
$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...'],
    ],
]);

设置首页商品楼层

php
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],
        ],
    ],
]);

设置轮播图

php
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
<?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 中使用

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 中注册的路由访问。

页面模板通常继承主布局:

blade
@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>

@endsection

6. 配置文件(config.json)

config.json 是主题的配置文件,包含主题的元数据信息:

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 环境变量指定要编译的主题:

shell
# 编译指定主题
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() 替换原有的资源引用,该函数会自动检查主题文件是否存在:

blade
{{-- 智能主题资源加载 - 自动回退机制 --}}
<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 开发

scss
// themes/factory/css/app.scss
@import '@front/css/app';
@import './page-product';  // 自定义样式

JS 开发

javascript
// 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: 完全使用默认资源

如果主题没有任何自定义文件,系统会:

  1. 清理编译目录
  2. 不编译任何主题文件
  3. theme_asset() 自动回退到默认资源

场景2: 只自定义样式

创建主题样式文件 themes/factory/css/app.scss

scss
// 导入默认样式
@import '../../../innopacks/front/resources/css/app.scss';

// 自定义样式
.product-title {
  color: red !important;
}

系统会:

  1. 清理编译目录
  2. 编译 app.scss 到主题目录
  3. 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. 创建主题目录

bash
mkdir -p themes/factory/css
mkdir -p themes/factory/js
mkdir -p themes/factory/views

2. 复制默认布局文件

bash
cp innopacks/front/resources/views/layouts/app.blade.php themes/factory/views/layouts/

3. 修改布局文件

使用 theme_asset() 替换原有的资源引用:

blade
{{-- 替换原有的 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. 创建自定义样式(推荐方式)

scss
// themes/factory/css/app.scss
@import '@front/css/app';
@import './page-product';  // 只添加需要的自定义样式

5. 编译资源

bash
THEME=factory npm run dev

6. 测试主题

访问前台页面查看效果。

前台 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.scss
    • footer.scss
  • components/ - 小组件(通常跨页面复用)
    • breadcrumb.scss
    • filter-sidebar.scss
  • modules/ - 功能模块(可跨页面复用的业务模块)
    • swiper.scss
    • product-item.scss
    • product-tab.scss
    • news.scss
    • product-review.scss
    • social.scss
  • pages/ - 页面层(只在特定页面生效)
    • home.scss
    • login.scss
    • account.scss
    • categories.scss
    • product.scss
    • cart.scss
    • checkout.scss
    • order.scss
    • addresses.scss
    • brands.scss
    • checkout-success.scss
    • wallet.scss
    • transaction.scss

app.scss 加载顺序

入口文件 innopacks/front/resources/css/app.scss 建议遵循以下顺序(同类型放一起,且越"基础"的越靠前):

  1. base/variablesbase/globalbase/font
  2. layout/*
  3. components/*
  4. modules/*
  5. pages/*

如果默认入口 @front/css/app 未覆盖某些样式,可以在主题侧按需额外引入对应的 @front/css/<group>/<file>

自定义主题如何复用默认 SCSS

主题侧入口示例:themes/<theme>/css/app.scss

方式 1:引入入口(推荐)

适用场景:大多数主题(希望完整复用默认样式,只写覆盖/增量)。

scss
@import './_variables';

@import '@front/css/app';

@import './global';
@import './header';
@import './footer';

如果主题需要额外引入某个默认入口未包含的样式,可以按需追加:

scss
@import '@front/css/<group>/<file>';

方式 2:按需引入单个文件(精细化)

适用场景:landing/轻量主题、只覆盖少量页面,或者希望尽量减小 CSS 体积。

scss
@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 提供了别名:

  • @frontinnopacks/front/resources

示例:

scss
@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 - 自定义脚本文件

最佳实践

  1. 使用推荐方式 - 先导入默认,再覆盖自定义
  2. 保持简洁 - 只创建需要的自定义文件
  3. 版本控制 - 使用 Git 管理主题文件
  4. 测试充分 - 在不同场景下测试主题效果
  5. 文档完善 - 为自定义功能添加注释说明

注意事项

  1. 使用 THEME 环境变量指定编译主题,如 THEME=factory npm run build
  2. 推荐使用"先导入默认,再覆盖自定义"的方式
  3. 建议使用版本控制管理主题文件
  4. 每次编译都会清理旧文件,确保编译结果的纯净性
  5. theme_asset() 函数会自动处理文件存在性检查

故障排除

问题1: 编译失败

  • 检查主题目录是否存在
  • 检查 SCSS 语法是否正确
  • 检查文件路径是否正确

问题2: 样式没有生效

  • 检查编译是否成功
  • 检查浏览器缓存
  • 检查 theme_asset() 是否正确回退

问题3: 默认样式被覆盖

  • 检查主题文件是否正确导入了默认文件
  • 检查 CSS 优先级是否正确
  • 确保自定义样式在默认样式之后导入

问题4: 文件不存在错误

  • 检查 theme_asset() 函数是否正常工作
  • 确保默认文件存在
  • 检查文件路径是否正确

问题5: 清理失败

  • 检查文件权限
  • 确保没有其他进程占用文件
  • 手动删除编译目录后重试

帆连科技 · 基于 OSL 3.0 许可发布