docs: 新增插件静态文件引用规范 + 经验条目18($public_host最佳实践)

- EXPERIENCES.md: 新增第18条(P2)— 插件模板静态文件引用规范
  - 根因一:cdn.jsdelivr.net 大陆阻断
  - 根因二:$public_host 在插件视图不可用
  - 规范:控制器显式传递 public_host,模板用 {{}}
  - 优先级:本地文件 > 国内CDN > 国际CDN
- EXPERIENCES.md: 检查清单新增2项(本地文件优先 + $public_host 显式传递)
- DEVELOPMENT_GUIDELINES.md: 新增第8条 — 插件模板静态文件引用规范(完整代码示例)
- README.md: 实现参考新增 DEVELOPMENT_GUIDELINES.md(置顶)
feat/phase-b-verification
Council 2026-04-25 08:55:00 +08:00
parent a673c09746
commit 5c433ea20e
3 changed files with 107 additions and 2 deletions

View File

@ -23,13 +23,14 @@
|------|------|
| **[docs/VR_GOODS_CONFIG_SPEC.md](docs/VR_GOODS_CONFIG_SPEC.md)** | ⚠️ **vr_goods_config JSON 格式 v3.0 完整规格**(商品配置核心) |
| **[docs/PHASE2_PLAN.md](docs/PHASE2_PLAN.md)** | Phase 2 当前状态 + 下一步工作计划 |
| **[docs/EXPERIENCES.md](docs/EXPERIENCES.md)** | ⚠️ **踩坑经验(必读)** — 16条核心教训 |
| **[docs/EXPERIENCES.md](docs/EXPERIENCES.md)** | ⚠️ **踩坑经验(必读)** — 18条核心教训 |
| **[docs/DEVELOPMENT_LOG.md](docs/DEVELOPMENT_LOG.md)** | 开发日志(完整变更记录) |
### 🔧 实现参考
| 文档 | 说明 |
|------|------|
| [docs/DEVELOPMENT_GUIDELINES.md](docs/DEVELOPMENT_GUIDELINES.md) | ⚠️ **插件开发规范**(静态文件引用、$public_host、铁律、pre-commit 自检) |
| [docs/GOODS_PHP_MODIFICATION.md](docs/GOODS_PHP_MODIFICATION.md) | Goods.php 1行改动说明 |
| [docs/09_SHOPXO_HOOKS_REFERENCE.md](docs/09_SHOPXO_HOOKS_REFERENCE.md) | ShopXO 全部钩子清单(从源码提取) |
| [docs/07_SHOPXO_PLUGIN_MECHANISM.md](docs/07_SHOPXO_PLUGIN_MECHANISM.md) | 插件开发机制 |

View File

@ -85,6 +85,56 @@ Council agents 和 Claude Code subagents 的 commit 行为**必须约束在 feat
- 所有敏感配置数据库密码、API Key、frp token 等)必须写入 `docs/PRIVATE_CONFIG.md`(不 commit`.env`(已 gitignore
- 禁止将真实密码写入代码或 commit message
### 8. 插件模板静态文件引用规范
ShopXO 插件模板引用 JS/CSS 时,必须遵循以下规范:
**背景**ShopXO 官方模板用 `{{$public_host}}` 引用静态文件,但插件控制器不继承 `index/Common.php``$public_host` 不会自动赋值给插件视图。
**正确写法**
```php
// 控制器:显式传递 public_host
class Index
{
public function wallet()
{
$user = UserService::LoginUserInfo();
return MyView('../../../plugins/vr_ticket/view/goods/ticket_wallet', [
'user' => $user,
// 关键:插件不继承 Common 基类,必须显式传递
'public_host' => \think\facade\Config::get('shopxo.host_url'),
]);
}
}
```
```html
<!-- 模板:使用 {{$public_host}} -->
<!-- ✅ 引用 ShopXO 自带库(优先) -->
<script src="{{$public_host}}static/common/lib/JsBarcode/JsBarcode.all.min.js"></script>
<!-- ✅ 引用插件自己的静态文件 -->
<link rel="stylesheet" href="{{$public_host}}plugins/vr_ticket/static/css/ticket.css">
<!-- ❌ 错误:插件模板里直接用 Config() 散落在模板各处 -->
<link href="<?php echo Config('shopxo.host_url'); ?>plugins/vr_ticket/static/css/ticket.css">
```
**静态文件来源优先级**
1. **ShopXO 自带**`{{$public_host}}static/common/lib/` 下已有 JsBarcode、jQuery、AmazeUI 等
2. **国内 CDN**`cdn.staticfile.net`ShopXO 无自带时)
3. **禁止**:国际 CDN`unpkg.com`、`jsdelivr.net`、`cdnjs.cloudflare.com`)— 大陆阻断
**验证方法**:容器内文件是否存在:
```bash
# ShopXO 自带 JsBarcode
docker exec shopxo-php ls /var/www/html/public/static/common/lib/JsBarcode/
# 应输出JsBarcode.all.min.js
```
**双目录陷阱**:插件 JS/CSS 文件在 `app/plugins/`PHP runtime`public/plugins/`Nginx webroot各有一份副本。修改后必须同步两边。详见 [docs/DEBUG_STATIC_FILE_SYNC.md](docs/DEBUG_STATIC_FILE_SYNC.md)。
---
## 三、已知特殊情况记录

View File

@ -123,6 +123,8 @@ return [
**教训**:国内项目 CDN 必须用 `cdn.staticfile.net``cdn.bootcdn.net`,禁止 `unpkg.com`/`cdnjs.cloudflare.com`。
**更优方案**:优先使用 ShopXO 源码自带的本地文件(见第 18 条)。
---
### 7. PHP 注释块污染:未闭合 `/*` 导致语法错误
@ -289,13 +291,65 @@ const zone = zones.find(z => z.char === seatChar);
---
## 🟢 P2 — 重要经验
### 18. 插件模板静态文件引用:`$public_host` 最佳实践
**背景**`ticket_wallet.html` 的 JsBarcode 条形码不显示,原因是 `cdn.jsdelivr.net` 在大陆阻断。
**案例**:票夹页面用 `<script src="https://cdn.jsdelivr.net/npm/jsbarcode@3.11.0/...">` → JsBarcode 加载失败 → `typeof JsBarcode === 'undefined'` → 条形码静默不渲染。
**ShopXO 源码已自带 JsBarcode v3.11.5**
```
public/static/common/lib/JsBarcode/JsBarcode.all.min.js
```
**根本原因一CDN 阻断)**jsdelivr.net 在大陆不可用。应优先使用 ShopXO 自带库或国内 CDN`cdn.staticfile.net`)。
**根本原因二(`$public_host` 不可用)**ShopXO 官方模板用 `{{$public_host}}` 引用静态文件,但插件控制器不继承 `index/Common.php``$public_host` 不会自动赋值。
```php
// ❌ 错误:插件控制器不继承 Common模板里 $public_host 是空的
// 模板中用 {{$public_host}}static/... → 变成 /static/...(丢域名)
// ✅ 正确:在控制器显式传递 public_host
class Index
{
public function wallet()
{
return MyView('../../../plugins/vr_ticket/view/goods/ticket_wallet', [
'user' => $user,
'public_host' => \think\facade\Config::get('shopxo.host_url'),
]);
}
}
// 模板中用 {{$public_host}}static/...
```
**静态文件引用优先级**
1. **ShopXO 自带文件**(如 JsBarcode直接引用 `{{$public_host}}static/common/lib/...`,无需下载
2. **国内 CDN**`cdn.staticfile.net` / `cdn.bootcdn.net`ShopXO 无自带时)
3. **国际 CDN**`unpkg.com` / `jsdelivr.net` — 禁止在国内项目使用
**教训**:引用 JS/CSS 前先查 ShopXO 源码是否已自带(`public/static/common/lib/`),优先使用本地文件。
**规范写法(插件模板)**
- 控制器:`MyView('path', ['public_host' => \think\facade\Config::get('shopxo.host_url'), ...])`
- 模板:`{{$public_host}}static/...`
> ⚠️ **高危**:插件 JS/CSS 文件在 `app/`PHP runtime`public/`Nginx webroot各有一份副本。修改后必须同步两边并验证 MD5。详情见专项文档。
---
## 📌 开发前检查清单
接手本插件时,逐项确认以下内容:
- [ ] `save.html` 有完整的 `{{:ModuleInclude('public/header')}}``{{:ModuleInclude('public/footer')}}`
- [ ] Vue 3 的 `<textarea>` 没有使用 `[[ ]]` 插值绑定 value
- [ ] Vue CDN 使用 `cdn.staticfile.net` 而非 `unpkg.com`
- [ ] 引用 JS/CSS 前先查 ShopXO 是否已自带(`public/static/common/lib/`),优先本地文件而非 CDN
- [ ] 插件模板使用 `$public_host` 时,控制器已显式传递(不依赖框架自动赋值)
- [ ] Hook.php 返回数组包含 `id`、`url`、`name`、`is_show`
- [ ] Admin.php 有缓存锁机制保护 `initialize()` 免于每次请求检查表
- [ ] 改字段名之前查过 Service 层源码或实际表结构