小氢云商城模板开发完全指南:接口调用、目录规范与提审清单

admin
15
2026-05-29

小氢云商城模板开发完全指南

本文面向第三方模板开发者站点二次开发者,系统梳理小氢云商城(Go 架构)下的模板开发规范、接口调用方式与上架流程。读完本文,你应该能独立完成一个可安装的静态模板,并正确对接商城核心业务接口。


一、先搞清楚:模板是什么

在小氢云商城里,「模板」指的是替换或扩展前台展示层的前端工程。用户访问商城时,系统通过路由映射把 URL 指向你打包好的 index.html,你的页面再通过 REST API 与后端通信,完成商品展示、下单、支付、查单等完整链路。

当前系统支持两类模板形态:

形态 技术栈 适用场景
静态 SPA 模板(推荐) Vue / React / UniApp H5 打包产物 现代商城前台、高度自定义 UI
经典 PHP 模板(兼容晴玖规范) PHP + HTML + route.php 轻量改造、沿用旧版模板生态

小氢云商城后端为 Go + Gin,接口统一走 /api/*。PHP 模板中的 /?mod=route&p=Goods 等旧式路由,在新系统中应改为前端路由 + REST API 的方式实现。


二、模板目录结构规范

2.1 静态 SPA 模板(主流方式)

模板商店安装后,系统会将 zip 包解压到 doc/{template_slug}/ 目录,并自动创建路由映射。

标准目录结构:

my-template/
├── index.html          # 入口文件(必需,文件名必须为 index)
├── index.png           # 预览图(建议 800×600px,上架用)
├── assets/             # JS / CSS 打包产物(UniApp / Vite 默认目录)
│   ├── index-xxx.js
│   └── index-xxx.css
├── static/             # 图片、字体等静态资源(可选)
│   └── images/
└── conf.json           # 模板配置(可选,见下文)

硬性要求:

  1. 包内必须存在名为 index 的首页文件(index.html / index.htm / index.php,优先 .html
  2. template_slug 在授权站配置,仅允许英文和数字,全局唯一
  3. 静态资源与 index.html 放在同一目录树下,便于路由中间件自动解析

2.2 经典 PHP 模板(晴玖兼容)

若你沿用晴玖商城模板规范,目录如下:

template/{应用标识}/
├── index.php         # 模板入口
├── index.png         # 预览图
├── route.php         # 路由跳转(旧式页面导航)
├── conf.json         # 模板配置
└── help.html         # 帮助文档(有扩展功能时建议提供)

route.php 示例(旧式跳转,新开发建议用前端路由替代):

<?php
switch ($_GET['p']) {
    case 'Goods':
        header("Location: " . ROOT_DIR . "#/pages/shop/shop?gid=" . $_GET['gid']);
        break;
    case 'Order':
        header("Location: " . ROOT_DIR . "#/pages/order/order");
        break;
    default:
        header("Location: " . ROOT_DIR);
        break;
}

三、模板配置文件 conf.json

conf.json 让站长在后台可视化修改模板参数,无需改代码。

{
    "name": "极简商城模板",
    "version": "1.0.0",
    "type": "1",
    "content": "一款简洁现代的商城前台模板",
    "extend": [
        {
            "type": 1,
            "name": "主题色",
            "value": "#1890ff",
            "Tips": "全局主色调,支持 HEX 色值"
        },
        {
            "type": 2,
            "name": "首页布局",
            "value": "1",
            "data": {
                "1": "单列瀑布流",
                "2": "双列网格",
                "3": "分类优先"
            }
        }
    ]
}
字段 说明
name 模板名称
version 版本号
type 模板类型标识
content 模板描述
extend[].type=1 文本输入框
extend[].type=2 下拉选择框,需配 data 选项

四、接口调用规范

4.1 基础约定

项目 规范
基础路径 {站点域名}/api
请求格式 Content-Type: application/json
成功标识 code === 200(部分旧接口兼容 code === 1
失败标识 code === -1,错误信息在 msg 字段
登录认证 请求头 user-token: Bearer {token}

统一响应结构:

{
  "code": 200,
  "msg": "操作成功",
  "data": { ... }
}

4.2 获取站点基础信息(模板初始化必调)

模板加载后,第一件事应拉取站点配置,用于渲染 Logo、公告、默认商品图、登录状态等。

// 获取网站基础信息(含 sitename、notice、def_img、User 等)
const res = await fetch('/api/inform', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' }
});
const { code, data } = await res.json();

// 获取前端公共配置
const config = await fetch('/api/getConfig', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' }
});

/api/inform 支持可选认证:携带 user-token 时返回当前用户信息;不传则为游客模式。

4.3 用户登录与 Token 管理

登录:

const res = await fetch('/api/login', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    username: 'demo',
    password: '123456',
    code: ''        // 验证码(若后台开启)
  })
});
const { code, data } = await res.json();
if (code === 200) {
  localStorage.setItem('user-token', data.token);
}

携带 Token 发起请求:

const token = localStorage.getItem('user-token');

const res = await fetch('/api/getUserInfo', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'user-token': `Bearer ${token}`
  }
});

Token 无感刷新:

当接口返回 code === 401 时,调用 POST /api/refresh-token(同样携带 user-token 头),拿到新 token 后重试原请求。

4.4 封装通用请求函数(推荐)

const API_BASE = window.location.origin;

async function api(url, params = {}, method = 'POST') {
  const token = localStorage.getItem('user-token');
  const headers = { 'Content-Type': 'application/json' };
  if (token) headers['user-token'] = `Bearer ${token.trim()}`;

  const options = { method, headers };
  if (method !== 'GET') options.body = JSON.stringify(params);

  const res = await fetch(API_BASE + url, options);
  const data = await res.json();

  if (data.code === 401) {
    // 触发登录弹窗或跳转登录页
    throw new Error('登录已过期');
  }
  if (data.code !== 200 && data.code !== 1) {
    throw new Error(data.msg || '请求失败');
  }
  return data;
}

// 使用示例
const goods = await api('/api/getGoods', { cid: 1, page: 1, limit: 20 });
const detail = await api('/api/getGoodsById', { id: 123 });

4.5 核心业务接口速查

商品与分类

接口 方法 说明
/api/class Any 获取商品分类
/api/getGoods Any 分页商品列表(cid, page, limit, keyword
/api/getGoodsById Any 商品详情(id
/api/searchCourse Any 搜索商品
/api/review/list Any 商品评价列表

下单与订单

接口 方法 说明
/api/add Any 创建订单
/api/OrderPrice Any 计算订单价格
/api/PreviewPrice Any 价格预览(含优惠券等)
/api/queryOrder Any 查询订单
/api/getOrderDetail Any 订单详情
/api/orderByKey Any 游客查单(按 key)

支付

接口 方法 说明
/api/PaymentWay Any 获取可用支付方式
/api/pay GET 统一支付入口

用户中心

接口 方法 说明
/api/UserData Any 用户数据概览
/api/getUserInfo Any 当前用户信息
/api/coupon/my Any 我的优惠券
/api/cart/list GET 购物车列表

完整接口清单见项目 doc/api/client/ 目录,按模块分为商品、订单、支付、认证等文档。

4.6 图片地址处理

  • 本地上传图片路径形如 /static/img/xxx.png直接拼接站点域名即可,无需走代理
  • 相对路径统一用 window.location.origin 拼接,避免硬编码域名
  • 商品默认图优先取 /api/inform 返回的 def_img 字段
function resolveImage(url) {
  if (!url) return '';
  if (url.startsWith('http')) return url;
  return window.location.origin + (url.startsWith('/') ? url : '/' + url);
}

五、路由映射与静态资源(关键!)

模板安装后,系统会在路由映射中创建一条记录(默认路由 /初始状态为关闭,需站长手动启用)。

5.1 子路由下的静态资源自动重写

若你的模板映射到 /myshop(非根路径),系统会自动重写 HTML 中的绝对路径:

<!-- 打包原始 -->
<script src="/assets/index.js"></script>

<!-- 访问 /myshop 时自动变为 -->
<script src="/myshop/assets/index.js"></script>

支持自动重写的目录前缀:/assets//static//js//css//images//img//fonts//libs//lib/

5.2 获取当前路由前缀

HTML 中可使用 [router] 占位符,服务端渲染时替换为当前路由:

<div id="app" data-base="[router]"></div>
<!-- 映射到 /myshop 时变为 data-base="/myshop" -->

JS 中读取:

const basePath = document.getElementById('app').dataset.base || '';
// 用于 Vue Router 的 base 配置

5.3 常见踩坑

问题 原因 解决
页面白屏,JS 404 静态资源绝对路径未重写 确保 HTML 引用为 /assets/xxx 格式;或映射到根路由 /
JS 动态加载 404 代码中硬编码了 /assets/ 改为相对路径,或用 [router] 获取前缀
接口跨域 开发时前后端不同端口 开发环境配置代理,或直连后端 127.0.0.1:8080
登录态丢失 Token 未持久化 写入 localStorage,请求头带 user-token

六、开发流程与上架规范

6.1 完整开发流程

1. 在授权站创建模板项目,配置 template_slug
       ↓
2. 本地开发前端(Vue / UniApp H5 等)
       ↓
3. 打包产物确保含 index.html + assets/
       ↓
4. 本地测试:手动创建路由映射指向打包目录
       ↓
5. 打包 zip 上传授权站,提交审核
       ↓
6. 站长在「模板商店」安装 → 启用路由映射

6.2 本地调试建议

方式一:路由映射调试(推荐)

  1. 将打包产物放到后端 doc/my-template/ 目录
  2. 管理后台 → 路由映射 → 新建映射
    • 路由:/my-template
    • 文件路径:my-template/index.html
    • 设备类型:pc / pe / def
    • 状态:启用
  3. 访问 http://127.0.0.1:8080/my-template 验证

方式二:开发服务器 + API 直连

// vite.config.js
export default {
  server: {
    proxy: {
      '/api': 'http://127.0.0.1:8080'
    }
  }
}

6.3 提审清单

提交模板商店审核前,请逐项确认:

  • 包含 index.html(或 index.htm / index.php)入口文件
  • 预览图 index.png(建议 800×600px)
  • 路由映射测试通过,静态资源全部 200
  • 商品列表、详情、下单、查单核心链路跑通
  • 登录 / 游客双模式均正常
  • 移动端适配(若 template_device=mobile
  • conf.json 格式正确(若有配置项)
  • 帮助文档 help.html(若有扩展功能)
  • 无硬编码第三方域名、无安全隐患

上架参数:

字段 要求
项目类型 选择「模板」
安装路径 填写 /
设备类型 universal / mobile / desktop
template_slug 英文数字,全局唯一

七、扩展开发方式

7.1 插件式扩展(复杂功能)

需要拼团、砍价、自定义支付等能力时,建议开发应用商店插件,在模板中通过接口调用:

GET ?AppApies&identification={插件标识}&name={方法名}

插件控制器规范:

/includes/lib/soft/controller/{标识}/index.php

7.2 内置式扩展(简单功能)

在模板包内直接新增 PHP 文件,通过 URL 访问:

/?mod={扩展文件名}

查找顺序:

  1. /template/{应用标识}/{扩展文件}.php
  2. /template/default/{扩展文件}.php

新架构下更推荐纯前端 + REST API 方案,PHP 扩展仅作兼容保留。


八、开发规范与最佳实践

8.1 代码规范

  1. 不要硬编码站点域名,统一用 window.location.origin
  2. 图片路径/static/img/ 直链,不走 /api/image/proxy
  3. 富文本渲染(UniApp PE 端)统一用 <u-parse :content="xxx" :selectable="true">
  4. 登录态优先读 localStorage,401 时引导重新登录
  5. 接口兼容同时判断 code === 200code === 1

8.2 性能建议

  • 静态资源打包压缩,图片适当 WebP
  • 商品列表分页加载,避免一次拉全量
  • 站点配置(/api/inform)可缓存 60 秒,开发模式建议 1 秒
  • 按需加载路由模块,控制首屏 JS 体积

8.3 安全建议

  • 不在前端存储敏感信息(密码、API 密钥)
  • 用户输入做 XSS 过滤后再渲染
  • 涉及资金的接口必须带 Token,不做前端金额计算终裁

九、快速上手示例:最小可用模板

<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>我的商城模板</title>
  <style>
    * { margin: 0; padding: 0; box-sizing: border-box; }
    body { font-family: -apple-system, sans-serif; background: #f5f5f5; }
    .header { background: #fff; padding: 16px; text-align: center; box-shadow: 0 1px 4px rgba(0,0,0,.08); }
    .goods-list { display: grid; grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); gap: 12px; padding: 16px; }
    .goods-card { background: #fff; border-radius: 8px; overflow: hidden; cursor: pointer; }
    .goods-card img { width: 100%; aspect-ratio: 1; object-fit: cover; }
    .goods-card .info { padding: 8px 12px; }
    .goods-card .name { font-size: 14px; color: #333; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
    .goods-card .price { color: #ff4d4f; font-weight: 600; margin-top: 4px; }
  </style>
</head>
<body>
  <div class="header"><h1 id="site-name">加载中...</h1></div>
  <div class="goods-list" id="goods-list"></div>

  <script>
    const API = window.location.origin;

    async function api(url, params = {}) {
      const token = localStorage.getItem('user-token');
      const headers = { 'Content-Type': 'application/json' };
      if (token) headers['user-token'] = 'Bearer ' + token.trim();
      const res = await fetch(API + url, { method: 'POST', headers, body: JSON.stringify(params) });
      return res.json();
    }

    async function init() {
      // 1. 站点信息
      const site = await api('/api/inform');
      if (site.code === 200 || site.code === 1) {
        document.getElementById('site-name').textContent = site.data?.sitename || '商城';
      }

      // 2. 商品列表
      const goods = await api('/api/getGoods', { page: 1, limit: 20 });
      const list = goods.data?.list || goods.data || [];
      const container = document.getElementById('goods-list');

      list.forEach(item => {
        const img = item.image?.startsWith('http') ? item.image : API + (item.image || '/static/img/default.png');
        container.innerHTML += `
          <div class="goods-card" onclick="location.href='?id=${item.id}'">
            <img src="${img}" alt="${item.name}" loading="lazy">
            <div class="info">
              <div class="name">${item.name}</div>
              <div class="price">¥${item.price}</div>
            </div>
          </div>`;
      });
    }

    init().catch(err => console.error('初始化失败:', err));
  </script>
</body>
</html>

将上述文件保存为 index.html,配合路由映射即可在本地验证。


十、相关资源

资源 说明
前台接口文档 项目 doc/api/client/ 目录
接口测试守则 doc/api/接口测试守则.md
路由映射静态资源说明 后端 UniApp路由映射静态资源解决方案.txt
应用商店开发平台 https://appstor.79tian.com/
开发文档中心 https://appstor.79tian.com/docs

总结

小氢云商城模板开发的本质是:用任意前端技术栈打包出 SPA,通过路由映射挂载到商城域名下,用标准 REST API 驱动业务

记住三个关键点:

  1. 入口文件必须叫 index,静态资源与之间目录
  2. 接口走 /api/*,认证头用 user-token: Bearer {token}
  3. 非根路由映射时,依赖系统自动重写 /assets/ 等静态路径

按本文规范开发并通过提审清单自检,你的模板就能顺利上架并被站长一键安装使用。


如有疑问,欢迎在评论区留言或加入开发者交流群。