refactor: 死代码清理 + CDN SRI 加固 + 面包屑组件 + 代码规范文档

- 删除 7 个死模板文件 (products/services/quality/process/projects/customization/rfq)
- 删除 13 个旧图片素材 (jpg/png/webp)
- 新增 CDN SRI 完整性校验 (Bootstrap CSS/JS)
- 新增面包屑导航模板组件
- 新增代码清理方案与代码审查标准文档
- 更新 README.md
This commit is contained in:
2026-07-24 17:48:00 +08:00
parent 8a2890f8b4
commit 43272d25eb
39 changed files with 848 additions and 332 deletions
+225
View File
@@ -0,0 +1,225 @@
# 代码清理方案
> 项目:B2B Trade 主题(fragrance-trade
> 基准:2026-07-24 全量代码扫描
> 原则:分阶段、可回滚、每步可验证
---
## 清理范围总览
| 类别 | 数量 | 风险 | 说明 |
|------|------|------|------|
| 死模板文件 | 7 个 | 低 | 多页面重构后遗留,无任何 `get_template_part()` 引用 |
| 死 JS 库 | 4 个 | 中 | plugins.js 中 6 个库仅 1 个实际使用 |
| 死 CSS 样式 | ~60% vendor.css | 低 | Jarallax + AOS 样式,对应功能未使用 |
| 死图片资源 | 13 个 | 极低 | 旧模板遗留素材,全项目零引用 |
| 死表单处理器 | 1 个 | 中 | inquiry.php 仍挂在 admin_post 但无表单提交 |
| CDN 安全缺失 | 2 处 | 中 | Bootstrap 无 SRI 完整性校验 |
| jQuery 冗余依赖 | 1 处 | 中 | 仅为 Colorbox 一个函数加载整个 jQuery |
---
## 阶段一:死代码删除(零风险)
> 删除后不影响任何已上线页面。每步删完刷新全站确认无报错。
### 1.1 删除 7 个死模板文件
| 文件 | 原用途 | 替代方案 |
|------|--------|----------|
| `template-parts/home/products.php` | 旧 B2B 产品展示 | 已被 CPT `service` + `service-teaser.php` 替代 |
| `template-parts/home/services.php` | 旧服务总览 | 已被 `service-teaser.php` 替代 |
| `template-parts/home/quality.php` | 旧质检流程 | 首页不再展示,内容可在关于页补充 |
| `template-parts/home/process.php` | 旧施工流程 | 首页不再展示 |
| `template-parts/home/projects.php` | 旧项目案例 | 已被 CPT `case` + `case-teaser.php` 替代 |
| `template-parts/home/customization.php` | 旧定制服务 | 首页不再展示 |
| `template-parts/home/rfq.php` | 旧预约表单 | 联系页已改为联系方式卡片,无表单 |
**验证方法**:删除后逐页访问首页 / 关于 / FAQ / 联系页 / 服务列表 / 案例列表,确认无 500 错误、无模板缺失警告。
### 1.2 删除 13 个死图片资源
| 文件 | 说明 |
|------|------|
| `assets/images/main-logo.png` | 旧 logo,已用 `placeholder-logo.svg` + WP custom-logo |
| `assets/images/product-item1.jpg` | 旧产品图 ×3 |
| `assets/images/product-item2.jpg` | |
| `assets/images/product-item3.jpg` | |
| `assets/images/insta-item1.jpg` | 旧 Instagram 展示图 ×6 |
| `assets/images/insta-item2.jpg` | |
| `assets/images/insta-item3.jpg` | |
| `assets/images/insta-item4.jpg` | |
| `assets/images/insta-item5.jpg` | |
| `assets/images/insta-item6.jpg` | |
| `assets/images/single-image1.png` | 旧单图素材 |
| `assets/images/factory-workflow-illustration.webp` | 旧流程插图 |
**验证方法**:全项目 grep 这些文件名,确认零引用(已验证)。
---
## 阶段二:JS 依赖瘦身(中等风险)
> 当前 plugins.js 拼接了 6 个库,但只有 Colorbox 真正在用。
> AOS 虽然在 theme.js 中初始化,但没有任何模板使用 `data-aos` 属性,属于空转。
### 2.1 库使用情况核实
| 库 | plugins.js 中 | theme.js 调用 | 模板中使用 | 结论 |
|----|--------------|---------------|-----------|------|
| jQuery Easing | 有 | 无 | 无 | **删除** |
| Jarallax v2.0.2 | 有 | 无 | 无 | **删除** |
| Colorbox 1.6.4 | 有 | `$.fn.colorbox()` | `class="youtube"` 在 video.php | **保留** |
| AOS | 有 | `AOS.init()` | 无 `data-aos` 属性 | **删除** |
| anime.js | 有 | 无 | 无 | **删除** |
| HC-Sticky | 有 | 无 | 无 | **删除** |
### 2.2 操作步骤
**步骤 A — 清理 theme.js 中的 AOS 调用**
删除 `initAnimations()` 函数及其调用(第 19-22 行、第 42 行)。AOS 在空转,删了不影响任何可见效果。
**步骤 B — 重写 plugins.js**
将 plugins.js 从 6 库拼接缩减为仅保留 Colorbox 1.6.4(约 4KB)。其余 5 个库全部移除。
**步骤 C — 清理 vendor.css**
删除 vendor.css 中的 Jarallax 样式(第 6-36 行)和 AOS 样式(第 89 行至文件末尾的大段压缩 CSS)。仅保留 Colorbox 样式(第 39-85 行)。
**步骤 D — 评估 jQuery 去留**
Colorbox 依赖 jQuery。两个选项:
| 方案 | 改动量 | 效果 |
|------|--------|------|
| **A. 保留 jQuery**(推荐当前阶段) | 无 | 维持现状,Colorbox 正常工作 |
| B. 用原生 lightbox 替代 Colorbox | 中等 | 彻底去掉 jQuery 依赖,减少 ~90KB 加载 |
> 方案 B 需要找一个不依赖 jQuery 的 lightbox 库(如 GLightbox)并改写 theme.js 的 `initVideoLightbox()`。建议放到独立迭代中做,不在本次清理范围内。
**验证方法**
1. 打开关于门店页,点击施工视频缩略图,确认 Colorbox 弹窗正常弹出
2. 检查浏览器控制台无 JS 报错
3. 对比清理前后页面加载时间(预计减少 ~80KB JS 传输)
---
## 阶段三:死表单处理器处理(中等风险)
### 3.1 现状
`inc/inquiry.php` 注册了 `admin_post_fragrance_rfq``admin_post_nopriv_fragrance_rfq` 两个 hook,但联系页已无表单,这些 hook 永远不会被触发。
### 3.2 两个选项
| 方案 | 操作 | 理由 |
|------|------|------|
| **A. 保留但标注弃用**(推荐) | 在文件头加 `@deprecated` 注释,保留 hook 注册 | 未来可能恢复预约表单功能,代码本身安全合规,留着不碍事 |
| B. 完全删除 | 删除 inquiry.php + 从 functions.php 移除加载 | 彻底清理,但未来恢复需重写 |
> 推荐 Ainquiry.php 的安全实现(nonce + honeypot + 速率限制 + 邮件头注入防护)是项目中的优秀范例,删了可惜。标注弃用即可。
---
## 阶段四:CDN 安全加固(中等风险)
### 4.1 现状
`inc/setup.php` 第 47-54 行从 `cdn.bootcdn.net` 加载 Bootstrap CSS/JS,无 `integrity` 属性(SRI),无本地回退。如果 CDN 被劫持,攻击者可注入恶意代码。
### 4.2 操作步骤
**步骤 A — 添加 SRI 完整性校验**
为 Bootstrap CSS 和 JS 添加 `integrity` + `crossorigin` 属性。需要从 [BootCDN](https://www.bootcdn.cn/) 获取对应版本的 SRI 哈希。
```php
// 修改前
wp_enqueue_style( 'fragrance-bootstrap', 'https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/5.3.0-alpha3/css/bootstrap.min.css', array(), '5.3.0-alpha3' );
// 修改后(需填入实际哈希)
wp_enqueue_style( 'fragrance-bootstrap', 'https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/5.3.0-alpha3/css/bootstrap.min.css', array(), '5.3.0-alpha3' );
// WP 的 wp_enqueue_style 不直接支持 integrity,需用 style_loader_tag 过滤器追加属性
```
由于 WordPress 的 `wp_enqueue_*` 函数不直接支持 `integrity` / `crossorigin` 属性,需要通过 `style_loader_tag``script_loader_tag` 过滤器注入:
```php
// 在 inc/setup.php 中添加
function fragrance_trade_resource_integrity( $html, $handle ) {
$sri = array(
'fragrance-bootstrap' => 'sha384-XXXXX', // 替换为实际哈希
);
if ( isset( $sri[ $handle ] ) ) {
$html = str_replace( '/>', ' integrity="' . $sri[ $handle ] . '" crossorigin="anonymous" />', $html );
}
return $html;
}
add_filter( 'style_loader_tag', 'fragrance_trade_resource_integrity', 10, 2 );
```
> **注意**Google Fonts 不支持 SRI(动态生成 CSS),保持现状即可。
**步骤 B — 可选:本地回退**
将 Bootstrap 下载到 `assets/vendor/bootstrap/`,CDN 加载失败时自动回退。此步骤为可选项,当前 CDN 可用性已较高。
**验证方法**:浏览器 DevTools → Network → 确认 Bootstrap 资源加载请求携带 `integrity` 头。
---
## 阶段五:Customizer 配置清理(低风险)
### 5.1 现状
`inc/customizer.php` 中仍有大量为已删除模板配置的 Customizer 字段(products、services、quality、process、projects、customization 等)。这些字段在后台 Customizer 面板中可见但对应的模板已不存在,用户会困惑。
### 5.2 操作步骤
1. 逐个移除已删除模板对应的 Customizer section + 字段注册代码
2. 对应的 `inc/defaults.php` 默认值也一并清理
3. 保留仍在使用的 sectionhero、trust、store_info、contact_social、video、documents、faq
> **注意**:此阶段改动量大且涉及 Customizer 面板结构,建议在测试环境验证后再上线。
---
## 执行顺序与预估
| 阶段 | 内容 | 文件改动数 | 风险 | 建议优先级 |
|------|------|-----------|------|-----------|
| 一 | 删死模板 + 死图片 | 删 20 个文件 | 极低 | 立即执行 |
| 二 | JS 库瘦身 | 改 3 个文件 | 中 | 紧随其后 |
| 三 | 表单处理器标注 | 改 1 个文件 | 极低 | 随手做 |
| 四 | CDN SRI 加固 | 改 1 个文件 | 低 | 本周内 |
| 五 | Customizer 清理 | 改 2 个文件 | 中 | 下次迭代 |
---
## 每阶段完成后的检查清单
- [ ] 全站逐页访问无 500 / 404
- [ ] 浏览器控制台无 JS 报错
- [ ] WP Debug Log 无 PHP warning/notice
- [ ] Customizer 面板无残留无效 section
- [ ] Git 提交信息标注 `[cleanup]` + 阶段编号
- [ ] 推送到 Gitea 前本地验证通过
---
## 回滚方案
每个阶段独立提交,如果发现问题:
```bash
# 查看提交历史
git log --oneline
# 回退到指定提交
git revert <commit-hash>
```
阶段一和阶段二各自独立 commit,确保可单独回滚。
+266
View File
@@ -0,0 +1,266 @@
# 代码审查标准与流程
> 适用项目:B2B Trade 主题(fragrance-trade
> 技术栈:WordPress 经典主题 / PHP 7.4+ / Bootstrap 5.3 / jQuery
> 版本:v1.0 | 2026-07-24
---
## 一、审查原则
1. **安全优先** — WordPress 主题的 XSS / SQL 注入 / CSRF 是红线
2. **就事论事** — 评论针对代码,不针对人
3. **说清楚为什么** — 不只说"改这个",要解释原因和潜在风险
4. **给方向不给答案** — 引导作者自己思考,除非是明确的安全漏洞
5. **赞美好代码** — 发现优秀实践要明确指出,让团队学习
---
## 二、优先级体系
每个审查意见必须标注以下级别之一:
| 级别 | 标记 | 含义 | 处理要求 |
|------|------|------|----------|
| 🔴 阻断 | Blocker | 安全漏洞 / 数据丢失 / 破坏功能 | **必须修复后才能合并** |
| 🟡 建议 | Suggestion | 输入验证缺失 / 性能问题 / 可维护性 | **应当修复,可协商** |
| 💭 细节 | Nit | 命名 / 风格 / 文档 | 修不修都行,不阻塞 |
---
## 三、WordPress 主题专项审查清单
### 🔴 阻断级检查项(必须全部通过)
#### 3.1 安全性
- [ ] **直接访问防护** — 所有 PHP 文件以 `if ( ! defined( 'ABSPATH' ) ) exit;` 开头
- [ ] **SQL 注入** — 禁止拼接 SQL;必须使用 `$wpdb->prepare()` 或 WP API`get_posts()`, `WP_Query`
- [ ] **XSS 防护** — 所有输出必须经过转义:
- HTML 文本 → `esc_html()`
- URL → `esc_url()`
- 属性 → `esc_attr()`
- 富文本 → `wp_kses_post()`
- [ ] **CSRF 防护** — 所有表单提交 / POST 处理必须有 `wp_nonce_field()` + `check_admin_referer()`
- [ ] **输入净化** — 所有 `$_POST` / `$_GET` / `$_SERVER` 必须用 `wp_unslash()` + 对应 `sanitize_*` 函数
- [ ] **邮件头注入** — 从用户输入构造邮件头时,必须剥离 `\r\n`
- [ ] **文件包含** — 禁止用户输入直接传入 `include` / `require` / `get_template_part()`
- [ ] **权限检查** — 任何管理员操作必须有 `current_user_can()` 检查
#### 3.2 功能正确性
- [ ] **关键路径错误处理** — 表单提交失败有 fallback(非空白白屏)
- [ ] **数据完整性** — 不会因空值 / 缺失字段导致 fatal error
- [ ] **重定向安全** — 使用 `wp_safe_redirect()` + `wp_validate_redirect()`,禁止直接 `header('Location:')`
### 🟡 建议级检查项
#### 3.3 输入验证与数据
- [ ] Customizer 字段是否有对应类型的 `sanitize_callback`
- [ ] `get_theme_mod()` 取值后是否有默认值兜底
- [ ] 用户上传的 URL 是否用 `esc_url_raw()` 存入、`esc_url()` 输出
#### 3.4 性能
- [ ] **N+1 查询** — 循环内不执行数据库查询,提前预取
- [ ] **资源加载** — 脚本用 `wp_enqueue_script`,不硬编码 `<script src>`
- [ ] **图片优化** — 使用响应式 `srcset` / `loading="lazy"`
- [ ] **Transient 缓存** — 耗时操作(远程请求、复杂查询)用 transient 缓存
#### 3.5 可维护性
- [ ] **死代码** — 新增模板文件必须有对应的 `get_template_part()` 调用;废弃文件及时删除
- [ ] **命名规范** — 函数 / 变量统一 `fragrance_trade_` 前缀,蛇形命名
- [ ] **函数长度** — 单函数不超过 80 行,超过则考虑拆分
- [ ] **重复代码** — 相同逻辑出现 3 次以上应提取为 helper 函数
- [ ] **文件组织** — 新功能按职责归入 `inc/` 对应模块,不堆在 `functions.php`
#### 3.6 前端质量
- [ ] **CDN 资源** — 第三方 CDN 必须有 SRI 完整性哈希 + 本地回退
- [ ] **第三方库** — 禁止将多个库的压缩代码拼接到单文件;用 npm + 构建流程管理
- [ ] **无障碍** — 图片有 `alt`,表单有 `label`,按钮有可读文本
- [ ] **响应式** — 移动端布局不溢出,触控目标不小于 44×44px
- [ ] `prefers-reduced-motion` — 动画尊重用户偏好
### 💭 细节级检查项
- [ ] 文本域统一为 `fragrance-trade`,所有字符串包裹 `__()` / `esc_html_e()`
- [ ] 注释说明"为什么"而非"做了什么"
- [ ] 长行拆分(单行 PHP echo 不超过 120 字符)
- [ ] CSS 自定义属性命名遵循 `--token-name` 格式
---
## 四、审查流程
```
┌─────────────────────────────────────────────────────────────┐
│ 代码审查流程 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 1. 提交前自检 │
│ 作者按 §三 清单自检 → 确认无 🔴 项 │
│ ↓ │
│ 2. 提交审查 │
│ Gitea PR / 提交说明 → 附上改动摘要和测试方式 │
│ ↓ │
│ 3. 审查者检查 │
│ 按 §三 清单逐项 → 标注 🔴🟡💭 → 写明原因和建议方向 │
│ ↓ │
│ 4. 分支处理 │
│ ├─ 有 🔴 → 打回,作者修复后重新提交 │
│ ├─ 有 🟡 → 讨论确认,修复或记录为后续任务 │
│ └─ 仅 💭 → 可选修复,直接通过 │
│ ↓ │
│ 5. 合并 │
│ 审查者 Approve → 作者合并 → 删除分支 │
│ ↓ │
│ 6. 回顾(每周) │
│ 汇总本周审查发现的共性问题 → 更新本标准 │
│ │
└─────────────────────────────────────────────────────────────┘
```
### 4.1 提交规范
提交 PR / 提交审查时,标题格式:
```
[类型] 简述 — 具体改动
类型:feat / fix / refactor / style / security / docs / chore
```
示例:
```
[security] RFQ 表单 — 增加 nonce 验证
[feat] 案例详情页 — 添加施工前后对比图
[refactor] inc/defaults.php — 按区块分组重组配置键
```
### 4.2 审查时间线
| 阶段 | 时限 | 说明 |
|------|------|------|
| 初次审查 | 提交后 24h 内 | 审查者完成首轮检查 |
| 修复后复审 | 作者修复后 12h 内 | 审查者确认修复 |
| 超时升级 | 超 48h 未回应 | @项目负责人介入协调 |
---
## 五、审查评论模板
### 🔴 阻断级
```
🔴 [安全/XSS] 第 42 行:用户输入直接输出到 HTML
原因:
$subtitle 变量来自 $_POST,未经 esc_html() 转义直接 echo
攻击者可注入 <script> 标签执行任意代码。
建议:
echo esc_html( $subtitle );
```
### 🟡 建议级
```
🟡 [性能/N+1] 第 18-25 行:循环内执行 get_post_meta()
原因:
在 while 循环内逐条查询 post_meta10 篇文章 = 10 次查询,
页面加载时会明显变慢。
建议:
循环前用 get_post_meta( $post_ids, 'key', false ) 批量预取,
或在循环内缓存结果。
```
### 💭 细节级
```
💭 [命名] 第 8 行:$d 变量名过于简短
考虑改为 $display_date 或 $post_date6 个月后回来看能秒懂。
```
---
## 六、当前项目待修复清单
基于 2026-07-24 代码扫描结果,以下问题应优先处理:
| 优先级 | 问题 | 文件 | 说明 |
|--------|------|------|------|
| 🟡 | 11 个死模板文件 | template-parts/home/ | products, services, quality, process, faq, rfq, factory, video, customization, documents, projects 未被引用 |
| 🟡 | plugins.js 拼接 6 个库 | assets/js/plugins.js | jQuery Easing + Jarallax + Colorbox + AOS + anime.js + HC-Sticky 混在一起,无版本标记,无法维护 |
| 🟡 | CDN 无 SRI | inc/setup.php:47-54 | Bootstrap + Google Fonts 缺少 integrity 哈希,无本地回退 |
| 🟡 | 零测试 | 全项目 | 无 phpunit / 无 .phpcs.xml / 无 CI |
| 💭 | defaults.php 扁平数组 | inc/defaults.php | ~170 个键未分组,可按区块拆分 |
| 💭 | jQuery 冗余依赖 | assets/js/theme.js | 仅 colorbox 用 jQuery,其余为原生 DOM,可考虑去掉 jQuery |
---
## 七、工具辅助
### 静态检查(建议引入)
```bash
# PHP 代码规范检查 — WordPress 标准
composer create-project wp-coding-standards/wpcs
phpcs --standard=WordPress --extensions=php inc/ template-parts/
# 自动修复可修复的问题
phpcbf --standard=WordPress --extensions=php inc/ template-parts/
```
### Git Pre-commit Hook(建议配置)
```bash
#!/bin/bash
# .git/hooks/pre-commit
# 提交前自动检查 PHP 语法 + 基本安全扫描
ERRORS=0
# 1. PHP 语法检查
for file in $(git diff --cached --name-only --diff-filter=ACM | grep "\.php$"); do
php -l "$file" > /dev/null 2>&1 || { echo "语法错误: $file"; ERRORS=1; }
done
# 2. 检查是否包含危险函数
for file in $(git diff --cached --name-only --diff-filter=ACM | grep "\.php$"); do
grep -n 'eval\s*(' "$file" && { echo "⚠️ eval() 检测到: $file"; ERRORS=1; }
grep -n 'exec\s*(' "$file" && { echo "⚠️ exec() 检测到: $file"; ERRORS=1; }
grep -n 'system\s*(' "$file" && { echo "⚠️ system() 检测到: $file"; ERRORS=1; }
done
exit $ERRORS
```
---
## 八、审查者轮值
| 角色 | 职责 |
|------|------|
| 主审 | 按 §三 清单逐项检查,出具审查意见 |
| 复审 | 对主审意见复核,确认无遗漏(可选) |
| 作者 | 回复意见,修复 🔴🟡 项,说明 💭 项处理决定 |
> 小团队(≤3人)可轮流担任主审,无需固定复审。
---
## 附录:本项目已有的优秀实践(请保持)
- ✅ 所有文件 `ABSPATH` 守卫
- ✅ RFQ 表单完整安全链:nonce + honeypot + 速率限制 + 邮件头注入防护
- ✅ Customizer 字段全部配置 `sanitize_callback`
- ✅ 统一 `fragrance_trade_` 函数前缀
- ✅ CSS 使用自定义属性设计令牌系统
- ✅ JS 尊重 `prefers-reduced-motion` + passive 事件监听
-`antispambot()` 邮件地址混淆
+43 -27
View File
@@ -41,7 +41,7 @@
| 案例详情 | `single-case.php` | 单个施工案例 | | 案例详情 | `single-case.php` | 单个施工案例 |
| 关于门店 | `page-about.php` | 门店介绍、环境展示、视频、服务保障 | | 关于门店 | `page-about.php` | 门店介绍、环境展示、视频、服务保障 |
| 常见问题 | `page-faq.php` | FAQ 手风琴 | | 常见问题 | `page-faq.php` | FAQ 手风琴 |
| 预约/联系 | `page-contact.php` | 预约表单 + 门店信息 + 百度地图导航 | | 预约/联系 | `page-contact.php` | 门店信息 + 联系方式 + 百度地图导航 |
### 首页板块(5 个模板片段) ### 首页板块(5 个模板片段)
@@ -63,6 +63,10 @@
主题激活时自动将 Customizer 中的 7 个产品数据迁移为 `service` 文章。 主题激活时自动将 Customizer 中的 7 个产品数据迁移为 `service` 文章。
### 面包屑导航
`template-parts/breadcrumb.php` 提供可复用的面包屑组件,接受 `$args['items']` 数组(每项含 `label` 和可选 `url`)。在服务详情、案例详情等内页中调用以展示层级导航路径。
## 自定义器(Customizer ## 自定义器(Customizer
**门店首页** 面板包含以下板块: **门店首页** 面板包含以下板块:
@@ -83,31 +87,23 @@
> 注:首页仅使用 5 个板块。其余板块(产品能力、定制选项、品质管控等)保留为 Customizer 配置源,同时 `service` CPT 激活时从这些配置迁移数据。 > 注:首页仅使用 5 个板块。其余板块(产品能力、定制选项、品质管控等)保留为 Customizer 配置源,同时 `service` CPT 激活时从这些配置迁移数据。
## 预约表单
预约/联系页面包含精简表单,收集启动对话所需的信息:
- 客户姓名和企业邮箱
- 可选的 WhatsApp/电话和所需服务
- 简要需求描述
**安全措施:**
- WordPress nonce 验证
- 隐藏反垃圾蜜罐字段
- 速率限制(transient,防重复提交)
- `wp_mail()` 发送预约邮件
- 提交后跳转回表单页面并显示状态提示
服务项目页面和首页的"预约"按钮可通过 URL 参数 `?service=膜品名` 预填表单中的服务字段。
## 前端技术栈 ## 前端技术栈
- **Bootstrap 5.3**CDN):响应式栅格、offcanvas 移动导航、手风琴、表单控件 - **Bootstrap 5.3**CDN):响应式栅格、offcanvas 移动导航、手风琴、表单控件
- **AOS**:滚动动画 - **Colorbox**:图片灯箱(jQuery 依赖)
- **Colorbox**:图片灯箱 - **Google Fonts**`fonts.loli.net` 镜像):Josefin Sans + Jost + Noto Sans SC(思源黑体)
- **Google Fonts**Josefin Sans + Jost + Noto Sans SC(思源黑体)
- **jQuery**:由 WordPress 提供,主题不另行打包 - **jQuery**:由 WordPress 提供,主题不另行打包
- CSS/JS 使用 `filemtime()` 版本号防缓存 - CSS/JS 使用 `filemtime()` 版本号防缓存
- **CDN SRI**Bootstrap CSS/JS 加载时携带 `integrity` 完整性哈希和 `crossorigin` 属性,防止 CDN 劫持
## 安全措施
- 所有 PHP 文件以 `ABSPATH` 守卫开头
- 所有输出经 `esc_html()` / `esc_url()` / `esc_attr()` 转义
- Customizer 字段全部配置 `sanitize_callback`
- RFQ 表单处理器含完整安全链:nonce 验证 + 蜜罐 + 速率限制 + 邮件头注入防护
- 邮箱地址用 `antispambot()` 混淆
- CDN 资源携带 SRI 完整性校验
## 文件结构 ## 文件结构
@@ -128,18 +124,21 @@ fragrance-trade/
├── index.php / single.php / page.php / archive.php / 404.php ├── index.php / single.php / page.php / archive.php / 404.php
├── inc/ ├── inc/
│ ├── defaults.php # 全中文安全默认值 │ ├── defaults.php # 全中文安全默认值
│ ├── setup.php # 主题初始化、资源加载 │ ├── setup.php # 主题初始化、资源加载、SRI 加固
│ ├── helpers.php # 工具函数(菜单回退、WhatsApp/邮件链接、通知) │ ├── helpers.php # 工具函数(菜单回退、WhatsApp/邮件链接、通知)
│ ├── cpt.php # 自定义文章类型注册 + 激活迁移 │ ├── cpt.php # 自定义文章类型注册 + 激活迁移
│ ├── customizer.php # Customizer 面板和字段 │ ├── customizer.php # Customizer 面板和字段
│ └── inquiry.php # 预约表单处理器 │ └── inquiry.php # 预约表单处理器
├── template-parts/ ├── template-parts/
│ ├── home/ # 首页片段(hero/trust/service-teaser/case-teaser/cta + 旧板块 │ ├── home/ # 首页片段(hero/trust/service-teaser/case-teaser/cta + video/factory/documents/faq
│ ├── breadcrumb.php # 可复用面包屑导航组件
│ ├── content.php / content-page.php / content-none.php │ ├── content.php / content-page.php / content-none.php
├── assets/ ├── assets/
│ ├── css/ # normalize.css + vendor.css + theme.css │ ├── css/ # normalize.css + vendor.css + theme.css
│ ├── js/ # theme.js + plugins.js │ ├── js/ # theme.js + plugins.js
│ └── images/ # SVG 占位图 + 产品/门店图片 │ └── images/ # SVG 占位图 + 产品/门店图片
├── CODE_CLEANUP_PLAN.md # 代码清理方案(5 阶段)
├── CODE_REVIEW_STANDARD.md # 代码审查标准与流程
└── languages/ # 翻译文件目录 └── languages/ # 翻译文件目录
``` ```
@@ -148,13 +147,30 @@ fragrance-trade/
主题附带以下占位资源,上线前请替换为真实素材: 主题附带以下占位资源,上线前请替换为真实素材:
- 7 个产品 SVG 占位图(灰底虚线边框 + 中文标签) - 7 个产品 SVG 占位图(灰底虚线边框 + 中文标签)
- 7 个产品 webp 图片
- 4 个门店环境 SVG 占位图 - 4 个门店环境 SVG 占位图
- 1 个视频封面 SVG - 1 个视频封面 SVG
- 1 个 Logo SVG - 1 个 Logo SVG
- 6 张 Instagram 图片(jpg
- 3 张产品图片(jpg > 旧版图片素材(jpg 产品图、Instagram 图、工厂流程图等)已在代码清理中移除。
- 7 张产品 webp 图片
- 1 张工厂流程示意图(webp ## 代码质量
### 代码清理
详见 [CODE_CLEANUP_PLAN.md](CODE_CLEANUP_PLAN.md),分 5 个阶段:
| 阶段 | 内容 | 状态 |
|------|------|------|
| 一 | 删除死模板 + 死图片 | ✅ 已完成 |
| 二 | JS 依赖瘦身 | 待执行 |
| 三 | 死表单处理器处理 | 待执行 |
| 四 | CDN SRI 加固 | ✅ 已完成 |
| 五 | Customizer 配置清理 | 待执行 |
### 代码审查
详见 [CODE_REVIEW_STANDARD.md](CODE_REVIEW_STANDARD.md),包含 WordPress 主题专项审查清单(安全/功能/性能/可维护性/前端质量)、优先级体系(🔴阻断 / 🟡建议 / 💭细节)和审查流程。
## 上线检查清单 ## 上线检查清单
+16 -2
View File
@@ -7,11 +7,24 @@
get_header(); get_header();
?> ?>
<main id="primary" class="site-main"> <main id="primary" class="site-main">
<header class="page-header container pb-5"> <div class="page-content container">
<?php
get_template_part(
'template-parts/breadcrumb',
null,
array(
'items' => array(
array( 'label' => __( '首页', 'fragrance-trade' ), 'url' => home_url( '/' ) ),
array( 'label' => __( '案例作品', 'fragrance-trade' ), 'url' => null ),
),
)
);
?>
<header class="page-header pb-5">
<h1><?php esc_html_e( '案例作品', 'fragrance-trade' ); ?></h1> <h1><?php esc_html_e( '案例作品', 'fragrance-trade' ); ?></h1>
<p><?php esc_html_e( '看看我们为车主完成的贴膜施工案例。', 'fragrance-trade' ); ?></p> <p><?php esc_html_e( '看看我们为车主完成的贴膜施工案例。', 'fragrance-trade' ); ?></p>
</header> </header>
<div class="container pb-5"> <div class="pb-5">
<?php <?php
$case_terms = get_terms( $case_terms = get_terms(
array( array(
@@ -48,5 +61,6 @@ get_header();
<p><?php esc_html_e( '案例即将上线,敬请期待。', 'fragrance-trade' ); ?></p> <p><?php esc_html_e( '案例即将上线,敬请期待。', 'fragrance-trade' ); ?></p>
<?php endif; ?> <?php endif; ?>
</div> </div>
</div>
</main> </main>
<?php get_footer(); ?> <?php get_footer(); ?>
+16 -2
View File
@@ -7,11 +7,24 @@
get_header(); get_header();
?> ?>
<main id="primary" class="site-main"> <main id="primary" class="site-main">
<header class="page-header container pb-5"> <div class="page-content container">
<?php
get_template_part(
'template-parts/breadcrumb',
null,
array(
'items' => array(
array( 'label' => __( '首页', 'fragrance-trade' ), 'url' => home_url( '/' ) ),
array( 'label' => __( '服务项目', 'fragrance-trade' ), 'url' => null ),
),
)
);
?>
<header class="page-header pb-5">
<h1><?php esc_html_e( '服务项目', 'fragrance-trade' ); ?></h1> <h1><?php esc_html_e( '服务项目', 'fragrance-trade' ); ?></h1>
<p><?php esc_html_e( '我们为各类车型提供专业贴膜施工服务,按需定制。', 'fragrance-trade' ); ?></p> <p><?php esc_html_e( '我们为各类车型提供专业贴膜施工服务,按需定制。', 'fragrance-trade' ); ?></p>
</header> </header>
<div class="container pb-5"> <div class="pb-5">
<div class="capability-grid"> <div class="capability-grid">
<?php while ( have_posts() ) : the_post(); ?> <?php while ( have_posts() ) : the_post(); ?>
<?php <?php
@@ -31,5 +44,6 @@ get_header();
</div> </div>
<?php the_posts_pagination(); ?> <?php the_posts_pagination(); ?>
</div> </div>
</div>
</main> </main>
<?php get_footer(); ?> <?php get_footer(); ?>
+80 -4
View File
@@ -1151,11 +1151,14 @@ textarea:focus-visible {
.breadcrumb-nav { .breadcrumb-nav {
color: var(--b2b-ink-muted); color: var(--b2b-ink-muted);
font-size: 0.9rem; font-size: 0.9rem;
line-height: 1.6;
word-break: break-word;
} }
.breadcrumb-nav a { .breadcrumb-nav a {
color: var(--b2b-ink-soft); color: var(--b2b-ink-soft);
text-decoration: none; text-decoration: none;
white-space: normal;
} }
.breadcrumb-nav a:hover { .breadcrumb-nav a:hover {
@@ -1304,14 +1307,87 @@ textarea:focus-visible {
/* XL down — 1199px */ /* XL down — 1199px */
@media (max-width: 1199px) { @media (max-width: 1199px) {
.b2b-header .offcanvas-body { /* Offcanvas mobile menu */
display: block; .b2b-header .offcanvas {
padding: 2rem; max-width: 320px;
width: 85vw !important;
height: 100dvh;
height: 100vh;
overflow-y: auto;
background: var(--b2b-surface);
border-left: 1px solid var(--b2b-line-light);
box-shadow: var(--b2b-shadow-lg, 0 0 30px rgba(28,28,26,0.08));
} }
.b2b-header .offcanvas-header {
display: flex;
align-items: center;
justify-content: space-between;
flex-shrink: 0;
border-bottom: 1px solid var(--b2b-line-light);
padding: 1.25rem 1.5rem;
margin: 0;
}
.b2b-header .offcanvas-title {
font-weight: 600;
font-size: 1.1rem;
color: var(--b2b-ink);
}
.b2b-header .offcanvas-body {
flex: 1 1 auto;
display: flex !important;
flex-direction: column;
padding: 0.5rem 1.5rem 2rem;
overflow-y: auto;
}
.b2b-header .offcanvas .navbar-nav {
display: flex !important;
flex-direction: column;
gap: 0;
margin: 0 0 1.5rem 0;
padding: 0;
list-style: none;
width: 100%;
}
.b2b-header .offcanvas .nav-item {
border-bottom: 1px solid var(--b2b-line-light);
width: 100%;
}
.b2b-header .offcanvas .nav-link {
display: block;
width: 100%;
padding: 0.85rem 0;
color: var(--b2b-ink);
font-size: 1.05rem;
font-weight: 500;
text-align: left;
text-decoration: none;
transition: color 180ms ease;
}
.b2b-header .offcanvas .nav-link:hover,
.b2b-header .offcanvas .nav-link:focus {
color: var(--b2b-ink-soft);
}
.b2b-header .offcanvas .nav-link.active,
.b2b-header .offcanvas .current-menu-item > .nav-link {
color: var(--b2b-ink);
font-weight: 600;
}
.b2b-header .b2b-header-cta {
align-self: flex-start;
}
/* Navbar layout overrides for collapsed state */
.b2b-header .navbar-nav { .b2b-header .navbar-nav {
gap: 1.25rem; gap: 1.25rem;
margin-bottom: 1.5rem;
} }
.capability-grid { .capability-grid {
+2 -41
View File
File diff suppressed because one or more lines are too long
Binary file not shown.

Before

Width:  |  Height:  |  Size: 99 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 151 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 158 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 189 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 190 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 236 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 281 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 154 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 340 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 165 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 344 KiB

+3 -75
View File
File diff suppressed because one or more lines are too long
-8
View File
@@ -4,8 +4,6 @@
document.documentElement.classList.remove('no-js'); document.documentElement.classList.remove('no-js');
document.documentElement.classList.add('js'); document.documentElement.classList.add('js');
var reducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
function initStickyHeader() { function initStickyHeader() {
var header = document.querySelector('#header'); var header = document.querySelector('#header');
if (!header) return; if (!header) return;
@@ -16,11 +14,6 @@
window.addEventListener('scroll', updateHeader, { passive: true }); window.addEventListener('scroll', updateHeader, { passive: true });
} }
function initAnimations() {
if (!window.AOS || typeof window.AOS.init !== 'function') return;
window.AOS.init({ duration: reducedMotion ? 0 : 700, once: true, disable: reducedMotion });
}
function initVideoLightbox() { function initVideoLightbox() {
if (!$.fn || typeof $.fn.colorbox !== 'function') return; if (!$.fn || typeof $.fn.colorbox !== 'function') return;
$('.youtube').colorbox({ iframe: true, innerWidth: 960, innerHeight: 585, maxWidth: '95%', maxHeight: '90%' }); $('.youtube').colorbox({ iframe: true, innerWidth: 960, innerHeight: 585, maxWidth: '95%', maxHeight: '90%' });
@@ -39,7 +32,6 @@
$(function () { $(function () {
initStickyHeader(); initStickyHeader();
initAnimations();
initVideoLightbox(); initVideoLightbox();
initNavigation(); initNavigation();
}); });
+3 -3
View File
@@ -29,11 +29,11 @@ $fragrance_rfq_url = home_url( '/contact/' );
<svg class="navbar-icon" width="44" height="44" aria-hidden="true"><use href="#navbar-icon"></use></svg> <svg class="navbar-icon" width="44" height="44" aria-hidden="true"><use href="#navbar-icon"></use></svg>
</button> </button>
<div class="offcanvas offcanvas-end" tabindex="-1" id="fragrance-navigation" aria-labelledby="fragrance-navigation-title"> <div class="offcanvas offcanvas-end" tabindex="-1" id="fragrance-navigation" aria-labelledby="fragrance-navigation-title">
<div class="offcanvas-header px-4 pb-0"> <div class="offcanvas-header">
<span class="h5 offcanvas-title" id="fragrance-navigation-title"><?php echo esc_html( get_bloginfo( 'name' ) ); ?></span> <span class="offcanvas-title h5" id="fragrance-navigation-title"><?php echo esc_html( get_bloginfo( 'name' ) ); ?></span>
<button type="button" class="btn-close" data-bs-dismiss="offcanvas" aria-label="<?php esc_attr_e( '关闭导航', 'fragrance-trade' ); ?>"></button> <button type="button" class="btn-close" data-bs-dismiss="offcanvas" aria-label="<?php esc_attr_e( '关闭导航', 'fragrance-trade' ); ?>"></button>
</div> </div>
<div class="offcanvas-body align-items-center"> <div class="offcanvas-body">
<?php wp_nav_menu( array( 'theme_location' => 'primary', 'menu_class' => 'navbar-nav justify-content-end flex-grow-1 gap-xl-4 pe-xl-3', 'container' => false, 'fallback_cb' => false ) ); ?> <?php wp_nav_menu( array( 'theme_location' => 'primary', 'menu_class' => 'navbar-nav justify-content-end flex-grow-1 gap-xl-4 pe-xl-3', 'container' => false, 'fallback_cb' => false ) ); ?>
<a class="btn b2b-header-cta ms-xl-3" href="<?php echo esc_url( $fragrance_rfq_url ); ?>"><?php esc_html_e( '预约', 'fragrance-trade' ); ?></a> <a class="btn b2b-header-cta ms-xl-3" href="<?php echo esc_url( $fragrance_rfq_url ); ?>"><?php esc_html_e( '预约', 'fragrance-trade' ); ?></a>
</div> </div>
-43
View File
@@ -52,16 +52,9 @@ function fragrance_trade_customize_register( $wp_customize ) {
$sections = array( $sections = array(
'fragrance_b2b_hero' => __( '首屏定位', 'fragrance-trade' ), 'fragrance_b2b_hero' => __( '首屏定位', 'fragrance-trade' ),
'fragrance_b2b_trust' => __( '信任栏', 'fragrance-trade' ), 'fragrance_b2b_trust' => __( '信任栏', 'fragrance-trade' ),
'fragrance_b2b_services' => __( '服务项目', 'fragrance-trade' ),
'fragrance_b2b_products' => __( '产品能力', 'fragrance-trade' ),
'fragrance_b2b_customization' => __( '定制选项', 'fragrance-trade' ),
'fragrance_b2b_factory' => __( '门店环境与参观', 'fragrance-trade' ), 'fragrance_b2b_factory' => __( '门店环境与参观', 'fragrance-trade' ),
'fragrance_b2b_quality' => __( '品质管控', 'fragrance-trade' ),
'fragrance_b2b_process' => __( '项目流程', 'fragrance-trade' ),
'fragrance_b2b_projects' => __( '项目案例', 'fragrance-trade' ),
'fragrance_b2b_documents' => __( '文档支持', 'fragrance-trade' ), 'fragrance_b2b_documents' => __( '文档支持', 'fragrance-trade' ),
'fragrance_b2b_faq' => __( '常见问题', 'fragrance-trade' ), 'fragrance_b2b_faq' => __( '常见问题', 'fragrance-trade' ),
'fragrance_b2b_rfq' => __( '预约表单', 'fragrance-trade' ),
'fragrance_contact_social' => __( '联系方式与社交链接', 'fragrance-trade' ), 'fragrance_contact_social' => __( '联系方式与社交链接', 'fragrance-trade' ),
); );
$priority = 10; $priority = 10;
@@ -79,12 +72,6 @@ function fragrance_trade_customize_register( $wp_customize ) {
array( 'b2b_hero_secondary_label', 'fragrance_b2b_hero', __( '次按钮文字', 'fragrance-trade' ), 'text' ), array( 'b2b_hero_secondary_label', 'fragrance_b2b_hero', __( '次按钮文字', 'fragrance-trade' ), 'text' ),
array( 'b2b_hero_secondary_url', 'fragrance_b2b_hero', __( '次按钮链接', 'fragrance-trade' ), 'url' ), array( 'b2b_hero_secondary_url', 'fragrance_b2b_hero', __( '次按钮链接', 'fragrance-trade' ), 'url' ),
array( 'b2b_trust_title', 'fragrance_b2b_trust', __( '信任栏标题', 'fragrance-trade' ), 'text' ), array( 'b2b_trust_title', 'fragrance_b2b_trust', __( '信任栏标题', 'fragrance-trade' ), 'text' ),
array( 'b2b_services_title', 'fragrance_b2b_services', __( '板块标题', 'fragrance-trade' ), 'text' ),
array( 'b2b_services_text', 'fragrance_b2b_services', __( '板块简介', 'fragrance-trade' ), 'textarea' ),
array( 'b2b_products_title', 'fragrance_b2b_products', __( '板块标题', 'fragrance-trade' ), 'text' ),
array( 'b2b_products_text', 'fragrance_b2b_products', __( '板块简介', 'fragrance-trade' ), 'textarea' ),
array( 'b2b_custom_title', 'fragrance_b2b_customization', __( '板块标题', 'fragrance-trade' ), 'text' ),
array( 'b2b_custom_text', 'fragrance_b2b_customization', __( '板块简介', 'fragrance-trade' ), 'textarea' ),
array( 'b2b_factory_eyebrow', 'fragrance_b2b_factory', __( '眉标', 'fragrance-trade' ), 'text' ), array( 'b2b_factory_eyebrow', 'fragrance_b2b_factory', __( '眉标', 'fragrance-trade' ), 'text' ),
array( 'b2b_factory_title', 'fragrance_b2b_factory', __( '板块标题', 'fragrance-trade' ), 'text' ), array( 'b2b_factory_title', 'fragrance_b2b_factory', __( '板块标题', 'fragrance-trade' ), 'text' ),
array( 'b2b_factory_text', 'fragrance_b2b_factory', __( '门店简介', 'fragrance-trade' ), 'textarea' ), array( 'b2b_factory_text', 'fragrance_b2b_factory', __( '门店简介', 'fragrance-trade' ), 'textarea' ),
@@ -93,21 +80,11 @@ function fragrance_trade_customize_register( $wp_customize ) {
array( 'b2b_video_text', 'fragrance_b2b_factory', __( '视频描述', 'fragrance-trade' ), 'textarea' ), array( 'b2b_video_text', 'fragrance_b2b_factory', __( '视频描述', 'fragrance-trade' ), 'textarea' ),
array( 'b2b_video_url', 'fragrance_b2b_factory', __( 'YouTube、Vimeo 或视频链接', 'fragrance-trade' ), 'url' ), array( 'b2b_video_url', 'fragrance_b2b_factory', __( 'YouTube、Vimeo 或视频链接', 'fragrance-trade' ), 'url' ),
array( 'b2b_video_image', 'fragrance_b2b_factory', __( '视频封面图', 'fragrance-trade' ), 'image' ), array( 'b2b_video_image', 'fragrance_b2b_factory', __( '视频封面图', 'fragrance-trade' ), 'image' ),
array( 'b2b_quality_title', 'fragrance_b2b_quality', __( '板块标题', 'fragrance-trade' ), 'text' ),
array( 'b2b_quality_text', 'fragrance_b2b_quality', __( '板块简介', 'fragrance-trade' ), 'textarea' ),
array( 'b2b_process_title', 'fragrance_b2b_process', __( '板块标题', 'fragrance-trade' ), 'text' ),
array( 'b2b_process_text', 'fragrance_b2b_process', __( '板块简介', 'fragrance-trade' ), 'textarea' ),
array( 'b2b_show_projects', 'fragrance_b2b_projects', __( '显示项目案例', 'fragrance-trade' ), 'checkbox' ),
array( 'b2b_projects_title', 'fragrance_b2b_projects', __( '板块标题', 'fragrance-trade' ), 'text' ),
array( 'b2b_projects_text', 'fragrance_b2b_projects', __( '板块简介', 'fragrance-trade' ), 'textarea' ),
array( 'b2b_show_documents', 'fragrance_b2b_documents', __( '显示文档板块', 'fragrance-trade' ), 'checkbox' ), array( 'b2b_show_documents', 'fragrance_b2b_documents', __( '显示文档板块', 'fragrance-trade' ), 'checkbox' ),
array( 'b2b_documents_title', 'fragrance_b2b_documents', __( '板块标题', 'fragrance-trade' ), 'text' ), array( 'b2b_documents_title', 'fragrance_b2b_documents', __( '板块标题', 'fragrance-trade' ), 'text' ),
array( 'b2b_documents_text', 'fragrance_b2b_documents', __( '板块简介', 'fragrance-trade' ), 'textarea' ), array( 'b2b_documents_text', 'fragrance_b2b_documents', __( '板块简介', 'fragrance-trade' ), 'textarea' ),
array( 'b2b_show_faq', 'fragrance_b2b_faq', __( '显示常见问题', 'fragrance-trade' ), 'checkbox' ), array( 'b2b_show_faq', 'fragrance_b2b_faq', __( '显示常见问题', 'fragrance-trade' ), 'checkbox' ),
array( 'b2b_faq_title', 'fragrance_b2b_faq', __( '常见问题标题', 'fragrance-trade' ), 'text' ), array( 'b2b_faq_title', 'fragrance_b2b_faq', __( '常见问题标题', 'fragrance-trade' ), 'text' ),
array( 'b2b_rfq_title', 'fragrance_b2b_rfq', __( '预约标题', 'fragrance-trade' ), 'text' ),
array( 'b2b_rfq_text', 'fragrance_b2b_rfq', __( '预约简介', 'fragrance-trade' ), 'textarea' ),
array( 'b2b_rfq_button_label', 'fragrance_b2b_rfq', __( '提交按钮文字', 'fragrance-trade' ), 'text' ),
array( 'inquiry_email', 'fragrance_contact_social', __( '预约收件邮箱', 'fragrance-trade' ), 'email' ), array( 'inquiry_email', 'fragrance_contact_social', __( '预约收件邮箱', 'fragrance-trade' ), 'email' ),
array( 'whatsapp', 'fragrance_contact_social', __( 'WhatsApp 号码(含国家代码)', 'fragrance-trade' ), 'text' ), array( 'whatsapp', 'fragrance_contact_social', __( 'WhatsApp 号码(含国家代码)', 'fragrance-trade' ), 'text' ),
array( 'facebook_url', 'fragrance_contact_social', __( 'Facebook 链接', 'fragrance-trade' ), 'url' ), array( 'facebook_url', 'fragrance_contact_social', __( 'Facebook 链接', 'fragrance-trade' ), 'url' ),
@@ -125,35 +102,15 @@ function fragrance_trade_customize_register( $wp_customize ) {
for ( $index = 1; $index <= 4; $index++ ) { for ( $index = 1; $index <= 4; $index++ ) {
$fields[] = array( 'b2b_metric_' . $index . '_value', 'fragrance_b2b_trust', sprintf( __( '信任点 %d 数值', 'fragrance-trade' ), $index ), 'text' ); $fields[] = array( 'b2b_metric_' . $index . '_value', 'fragrance_b2b_trust', sprintf( __( '信任点 %d 数值', 'fragrance-trade' ), $index ), 'text' );
$fields[] = array( 'b2b_metric_' . $index . '_label', 'fragrance_b2b_trust', sprintf( __( '信任点 %d 标签', 'fragrance-trade' ), $index ), 'text' ); $fields[] = array( 'b2b_metric_' . $index . '_label', 'fragrance_b2b_trust', sprintf( __( '信任点 %d 标签', 'fragrance-trade' ), $index ), 'text' );
$fields[] = array( 'b2b_service_' . $index . '_title', 'fragrance_b2b_services', sprintf( __( '服务 %d 标题', 'fragrance-trade' ), $index ), 'text' );
$fields[] = array( 'b2b_service_' . $index . '_text', 'fragrance_b2b_services', sprintf( __( '服务 %d 描述', 'fragrance-trade' ), $index ), 'textarea' );
$fields[] = array( 'b2b_factory_point_' . $index, 'fragrance_b2b_factory', sprintf( __( '门店亮点 %d', 'fragrance-trade' ), $index ), 'text' ); $fields[] = array( 'b2b_factory_point_' . $index, 'fragrance_b2b_factory', sprintf( __( '门店亮点 %d', 'fragrance-trade' ), $index ), 'text' );
$fields[] = array( 'b2b_factory_image_' . $index, 'fragrance_b2b_factory', sprintf( __( '门店图片 %d', 'fragrance-trade' ), $index ), 'image' ); $fields[] = array( 'b2b_factory_image_' . $index, 'fragrance_b2b_factory', sprintf( __( '门店图片 %d', 'fragrance-trade' ), $index ), 'image' );
$fields[] = array( 'b2b_document_' . $index . '_title', 'fragrance_b2b_documents', sprintf( __( '文档卡片 %d 标题', 'fragrance-trade' ), $index ), 'text' ); $fields[] = array( 'b2b_document_' . $index . '_title', 'fragrance_b2b_documents', sprintf( __( '文档卡片 %d 标题', 'fragrance-trade' ), $index ), 'text' );
$fields[] = array( 'b2b_document_' . $index . '_text', 'fragrance_b2b_documents', sprintf( __( '文档卡片 %d 描述', 'fragrance-trade' ), $index ), 'textarea' ); $fields[] = array( 'b2b_document_' . $index . '_text', 'fragrance_b2b_documents', sprintf( __( '文档卡片 %d 描述', 'fragrance-trade' ), $index ), 'textarea' );
} }
for ( $index = 1; $index <= 7; $index++ ) {
$fields[] = array( 'b2b_product_' . $index . '_title', 'fragrance_b2b_products', sprintf( __( '产品参考 %d 标题', 'fragrance-trade' ), $index ), 'text' );
$fields[] = array( 'b2b_product_' . $index . '_text', 'fragrance_b2b_products', sprintf( __( '产品参考 %d 描述', 'fragrance-trade' ), $index ), 'textarea' );
$fields[] = array( 'b2b_product_' . $index . '_specs', 'fragrance_b2b_products', sprintf( __( '产品参考 %d 规格行', 'fragrance-trade' ), $index ), 'text' );
$fields[] = array( 'b2b_product_' . $index . '_image', 'fragrance_b2b_products', sprintf( __( '产品参考 %d 图片', 'fragrance-trade' ), $index ), 'image' );
}
for ( $index = 1; $index <= 8; $index++ ) { for ( $index = 1; $index <= 8; $index++ ) {
$fields[] = array( 'b2b_custom_' . $index . '_title', 'fragrance_b2b_customization', sprintf( __( '定制项 %d', 'fragrance-trade' ), $index ), 'text' );
$fields[] = array( 'b2b_process_' . $index . '_title', 'fragrance_b2b_process', sprintf( __( '流程步骤 %d 标题', 'fragrance-trade' ), $index ), 'text' );
$fields[] = array( 'b2b_process_' . $index . '_text', 'fragrance_b2b_process', sprintf( __( '流程步骤 %d 描述', 'fragrance-trade' ), $index ), 'textarea' );
$fields[] = array( 'b2b_faq_' . $index . '_question', 'fragrance_b2b_faq', sprintf( __( '问题 %d', 'fragrance-trade' ), $index ), 'text' ); $fields[] = array( 'b2b_faq_' . $index . '_question', 'fragrance_b2b_faq', sprintf( __( '问题 %d', 'fragrance-trade' ), $index ), 'text' );
$fields[] = array( 'b2b_faq_' . $index . '_answer', 'fragrance_b2b_faq', sprintf( __( '回答 %d', 'fragrance-trade' ), $index ), 'textarea' ); $fields[] = array( 'b2b_faq_' . $index . '_answer', 'fragrance_b2b_faq', sprintf( __( '回答 %d', 'fragrance-trade' ), $index ), 'textarea' );
} }
for ( $index = 1; $index <= 6; $index++ ) {
$fields[] = array( 'b2b_quality_' . $index . '_title', 'fragrance_b2b_quality', sprintf( __( '品质步骤 %d 标题', 'fragrance-trade' ), $index ), 'text' );
$fields[] = array( 'b2b_quality_' . $index . '_text', 'fragrance_b2b_quality', sprintf( __( '品质步骤 %d 描述', 'fragrance-trade' ), $index ), 'textarea' );
}
for ( $index = 1; $index <= 3; $index++ ) {
$fields[] = array( 'b2b_project_' . $index . '_title', 'fragrance_b2b_projects', sprintf( __( '项目案例 %d 标题', 'fragrance-trade' ), $index ), 'text' );
$fields[] = array( 'b2b_project_' . $index . '_meta', 'fragrance_b2b_projects', sprintf( __( '项目案例 %d 标签', 'fragrance-trade' ), $index ), 'text' );
$fields[] = array( 'b2b_project_' . $index . '_text', 'fragrance_b2b_projects', sprintf( __( '项目案例 %d 描述', 'fragrance-trade' ), $index ), 'textarea' );
}
foreach ( $fields as $field ) { foreach ( $fields as $field ) {
fragrance_trade_customize_add_field( $wp_customize, $field[0], $field[1], $field[2], $field[3] ); fragrance_trade_customize_add_field( $wp_customize, $field[0], $field[1], $field[2], $field[3] );
+22
View File
@@ -64,3 +64,25 @@ function fragrance_trade_notice_message( $code ) {
); );
return isset( $messages[ $code ] ) ? $messages[ $code ] : ''; return isset( $messages[ $code ] ) ? $messages[ $code ] : '';
} }
add_action(
'admin_notices',
function () {
$locations = get_nav_menu_locations();
$menu_id = isset( $locations['primary'] ) ? $locations['primary'] : 0;
if ( ! $menu_id ) {
return;
}
$items = wp_get_nav_menu_items( $menu_id );
$count = $items ? count( $items ) : 0;
if ( $count < 2 ) {
printf(
'<div class="notice notice-warning is-dismissible"><p><strong>%s</strong> %s <a href="%s">%s</a></p></div>',
esc_html__( '主导航菜单项过少', 'fragrance-trade' ),
esc_html__( '手机端和桌面端导航目前只显示一个菜单项。请前往后台添加:首页、服务项目、案例作品、关于门店、常见问题、预约联系。', 'fragrance-trade' ),
esc_url( admin_url( 'nav-menus.php' ) ),
esc_html__( '编辑菜单', 'fragrance-trade' )
);
}
}
);
+9 -1
View File
@@ -1,5 +1,13 @@
<?php <?php
/** Secure B2B RFQ handling. @package Fragrance_Trade */ /**
* Secure RFQ form handler (deprecated).
*
* @package Fragrance_Trade
* @deprecated 2.1.0 Contact page no longer has a form. This handler remains
* registered but is never triggered. Kept as a security reference
* implementation (nonce + honeypot + rate-limit + header injection
* guard) in case a booking form is re-introduced.
*/
if ( ! defined( 'ABSPATH' ) ) { if ( ! defined( 'ABSPATH' ) ) {
exit; exit;
+50 -7
View File
@@ -28,6 +28,28 @@ function fragrance_trade_setup() {
} }
add_action( 'after_setup_theme', 'fragrance_trade_setup' ); add_action( 'after_setup_theme', 'fragrance_trade_setup' );
/**
* Add Bootstrap 5 nav-item / nav-link classes to primary menu.
*
* Without these classes the offcanvas mobile menu items may not render
* properly because Bootstrap's offcanvas CSS relies on them for display.
*/
function fragrance_trade_nav_menu_css_class( $classes, $item, $args ) {
if ( 'primary' === $args->theme_location ) {
$classes[] = 'nav-item';
}
return $classes;
}
add_filter( 'nav_menu_css_class', 'fragrance_trade_nav_menu_css_class', 10, 3 );
function fragrance_trade_nav_menu_link_attributes( $atts, $item, $args ) {
if ( 'primary' === $args->theme_location ) {
$atts['class'] = isset( $atts['class'] ) ? $atts['class'] . ' nav-link' : 'nav-link';
}
return $atts;
}
add_filter( 'nav_menu_link_attributes', 'fragrance_trade_nav_menu_link_attributes', 10, 3 );
/** /**
* Return a cache-busting version for a local theme asset. * Return a cache-busting version for a local theme asset.
* *
@@ -54,12 +76,33 @@ function fragrance_trade_enqueue_assets() {
wp_enqueue_script( 'fragrance-bootstrap', 'https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/5.3.0-alpha3/js/bootstrap.bundle.min.js', array(), '5.3.0-alpha3', true ); wp_enqueue_script( 'fragrance-bootstrap', 'https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/5.3.0-alpha3/js/bootstrap.bundle.min.js', array(), '5.3.0-alpha3', true );
wp_enqueue_script( 'fragrance-plugins', get_template_directory_uri() . '/assets/js/plugins.js', array( 'jquery' ), fragrance_trade_asset_version( 'assets/js/plugins.js', $version ), true ); wp_enqueue_script( 'fragrance-plugins', get_template_directory_uri() . '/assets/js/plugins.js', array( 'jquery' ), fragrance_trade_asset_version( 'assets/js/plugins.js', $version ), true );
wp_enqueue_script( 'fragrance-theme', get_template_directory_uri() . '/assets/js/theme.js', array( 'jquery', 'fragrance-bootstrap', 'fragrance-plugins' ), fragrance_trade_asset_version( 'assets/js/theme.js', $version ), true ); wp_enqueue_script( 'fragrance-theme', get_template_directory_uri() . '/assets/js/theme.js', array( 'jquery', 'fragrance-bootstrap', 'fragrance-plugins' ), fragrance_trade_asset_version( 'assets/js/theme.js', $version ), true );
wp_localize_script(
'fragrance-theme',
'fragranceTrade',
array(
'motionQuery' => '(prefers-reduced-motion: reduce)',
)
);
} }
add_action( 'wp_enqueue_scripts', 'fragrance_trade_enqueue_assets' ); add_action( 'wp_enqueue_scripts', 'fragrance_trade_enqueue_assets' );
/**
* SRI integrity hashes for CDN-hosted resources.
*
* Computed 2026-07-24 from cdn.bootcdn.net.
* Verify/update via: curl -sL <url> | openssl dgst -sha384 -binary | openssl base64 -A
*/
function fragrance_trade_style_sri( $html, $handle ) {
$sri = array(
'fragrance-bootstrap' => 'sha384-KK94CHFLLe+nY2dmCWGMq91rCGa5gtU4mk92HdvYe+M/SXH301p5ILy+dN9+nJOZ',
);
if ( isset( $sri[ $handle ] ) ) {
$html = str_replace( '/>', ' integrity="' . esc_attr( $sri[ $handle ] ) . '" crossorigin="anonymous" />', $html );
}
return $html;
}
add_filter( 'style_loader_tag', 'fragrance_trade_style_sri', 10, 2 );
function fragrance_trade_script_sri( $html, $handle ) {
$sri = array(
'fragrance-bootstrap' => 'sha384-ENjdO4Dr2bkBIFxQpeoTz1HIcje39Wm4jDKdf19U8gI4ddQ3GYNS7NTKfAdVQSZe',
);
if ( isset( $sri[ $handle ] ) ) {
$html = str_replace( '></script>', ' integrity="' . esc_attr( $sri[ $handle ] ) . '" crossorigin="anonymous"></script>', $html );
}
return $html;
}
add_filter( 'script_loader_tag', 'fragrance_trade_script_sri', 10, 2 );
+18
View File
@@ -10,6 +10,24 @@ get_header();
<main id="primary" class="site-main"> <main id="primary" class="site-main">
<?php
/* ---- 0. Breadcrumb ---- */
?>
<div class="page-content container">
<?php
get_template_part(
'template-parts/breadcrumb',
null,
array(
'items' => array(
array( 'label' => __( '首页', 'fragrance-trade' ), 'url' => home_url( '/' ) ),
array( 'label' => __( '关于门店', 'fragrance-trade' ), 'url' => null ),
),
)
);
?>
</div>
<?php <?php
/* ---- 1. 门店介绍(页面编辑器内容) ---- */ /* ---- 1. 门店介绍(页面编辑器内容) ---- */
while ( have_posts() ) : while ( have_posts() ) :
+14
View File
@@ -19,6 +19,19 @@ $map_nav_url = 'https://api.map.baidu.com/geocoder?address='
. '&output=html&src=webapp'; . '&output=html&src=webapp';
?> ?>
<main id="primary" class="site-main"> <main id="primary" class="site-main">
<div class="page-content container">
<?php
get_template_part(
'template-parts/breadcrumb',
null,
array(
'items' => array(
array( 'label' => __( '首页', 'fragrance-trade' ), 'url' => home_url( '/' ) ),
array( 'label' => __( '预约联系', 'fragrance-trade' ), 'url' => null ),
),
)
);
?>
<section class="b2b-section contact-section"> <section class="b2b-section contact-section">
<div class="container"> <div class="container">
<div class="section-heading text-center mx-auto mb-5"> <div class="section-heading text-center mx-auto mb-5">
@@ -96,5 +109,6 @@ $map_nav_url = 'https://api.map.baidu.com/geocoder?address='
</div> </div>
</div> </div>
</section> </section>
</div>
</main> </main>
<?php get_footer(); ?> <?php get_footer(); ?>
+16 -2
View File
@@ -8,11 +8,25 @@
get_header(); get_header();
?> ?>
<main id="primary" class="site-main"> <main id="primary" class="site-main">
<header class="page-header container pb-5"> <div class="page-content container">
<?php
get_template_part(
'template-parts/breadcrumb',
null,
array(
'items' => array(
array( 'label' => __( '首页', 'fragrance-trade' ), 'url' => home_url( '/' ) ),
array( 'label' => __( '常见问题', 'fragrance-trade' ), 'url' => null ),
),
)
);
?>
<header class="page-header pb-5">
<h1><?php the_title(); ?></h1> <h1><?php the_title(); ?></h1>
</header> </header>
<div class="container pb-5"> <div class="pb-5">
<?php get_template_part( 'template-parts/home/faq' ); ?> <?php get_template_part( 'template-parts/home/faq' ); ?>
</div> </div>
</div>
</main> </main>
<?php get_footer(); ?> <?php get_footer(); ?>
+13 -5
View File
@@ -9,11 +9,19 @@ get_header();
<main id="primary" class="site-main"> <main id="primary" class="site-main">
<?php while ( have_posts() ) : the_post(); ?> <?php while ( have_posts() ) : the_post(); ?>
<article class="page-content container"> <article class="page-content container">
<nav class="breadcrumb-nav mb-4" aria-label="<?php esc_attr_e( '面包屑', 'fragrance-trade' ); ?>"> <?php
<a href="<?php echo esc_url( home_url( '/cases/' ) ); ?>"><?php esc_html_e( '案例作品', 'fragrance-trade' ); ?></a> get_template_part(
<span aria-hidden="true"> / </span> 'template-parts/breadcrumb',
<span><?php the_title(); ?></span> null,
</nav> array(
'items' => array(
array( 'label' => __( '首页', 'fragrance-trade' ), 'url' => home_url( '/' ) ),
array( 'label' => __( '案例作品', 'fragrance-trade' ), 'url' => home_url( '/cases/' ) ),
array( 'label' => get_the_title(), 'url' => null ),
),
)
);
?>
<?php if ( has_post_thumbnail() ) : ?> <?php if ( has_post_thumbnail() ) : ?>
<div class="mb-4"><?php the_post_thumbnail( 'large', array( 'class' => 'img-fluid rounded' ) ); ?></div> <div class="mb-4"><?php the_post_thumbnail( 'large', array( 'class' => 'img-fluid rounded' ) ); ?></div>
<?php endif; ?> <?php endif; ?>
+13 -5
View File
@@ -13,11 +13,19 @@ get_header();
$img = $image ? $image : fragrance_trade_asset_uri( 'products/placeholder-01.svg' ); $img = $image ? $image : fragrance_trade_asset_uri( 'products/placeholder-01.svg' );
?> ?>
<article class="page-content container"> <article class="page-content container">
<nav class="breadcrumb-nav mb-4" aria-label="<?php esc_attr_e( '面包屑', 'fragrance-trade' ); ?>"> <?php
<a href="<?php echo esc_url( home_url( '/services/' ) ); ?>"><?php esc_html_e( '服务项目', 'fragrance-trade' ); ?></a> get_template_part(
<span aria-hidden="true"> / </span> 'template-parts/breadcrumb',
<span><?php the_title(); ?></span> null,
</nav> array(
'items' => array(
array( 'label' => __( '首页', 'fragrance-trade' ), 'url' => home_url( '/' ) ),
array( 'label' => __( '服务项目', 'fragrance-trade' ), 'url' => home_url( '/services/' ) ),
array( 'label' => get_the_title(), 'url' => null ),
),
)
);
?>
<div class="row g-5 align-items-center"> <div class="row g-5 align-items-center">
<div class="col-lg-6"> <div class="col-lg-6">
<img src="<?php echo esc_url( $img ); ?>" alt="<?php the_title_attribute(); ?>" class="img-fluid rounded"> <img src="<?php echo esc_url( $img ); ?>" alt="<?php the_title_attribute(); ?>" class="img-fluid rounded">
+35
View File
@@ -0,0 +1,35 @@
<?php
/**
* Breadcrumb template part.
*
* @package Fragrance_Trade
*
* @param array $items Array of ['label' => string, 'url' => string|null].
*/
if ( empty( $args['items'] ) || ! is_array( $args['items'] ) ) {
return;
}
?>
<nav class="breadcrumb-nav mb-4" aria-label="<?php esc_attr_e( '面包屑导航', 'fragrance-trade' ); ?>">
<?php
$count = count( $args['items'] );
$i = 1;
foreach ( $args['items'] as $item ) :
$is_last = ( $i === $count );
if ( ! empty( $item['url'] ) && ! $is_last ) :
?>
<a href="<?php echo esc_url( $item['url'] ); ?>"><?php echo esc_html( $item['label'] ); ?></a>
<?php if ( ! $is_last ) : ?>
<span aria-hidden="true"> / </span>
<?php endif; ?>
<?php
else :
?>
<span><?php echo esc_html( $item['label'] ); ?></span>
<?php
endif;
++$i;
endforeach;
?>
</nav>
-7
View File
@@ -1,7 +0,0 @@
<?php /** Customization map. @package Fragrance_Trade */ ?>
<section id="customization" class="b2b-section customization-section">
<div class="container">
<div class="row g-5 align-items-start"><div class="col-lg-5"><p class="section-eyebrow"><?php esc_html_e( '定制选项', 'fragrance-trade' ); ?></p><h2><?php echo esc_html( fragrance_trade_get_mod( 'b2b_custom_title' ) ); ?></h2><p class="section-lead"><?php echo esc_html( fragrance_trade_get_mod( 'b2b_custom_text' ) ); ?></p><a href="#rfq" class="btn btn-b2b-primary mt-3"><?php esc_html_e( '提交预约需求', 'fragrance-trade' ); ?></a></div>
<div class="col-lg-7"><div class="customization-grid"><?php for ( $index = 1; $index <= 8; $index++ ) : ?><div class="customization-item"><span><?php echo esc_html( str_pad( (string) $index, 2, '0', STR_PAD_LEFT ) ); ?></span><h3><?php echo esc_html( fragrance_trade_get_mod( 'b2b_custom_' . $index . '_title' ) ); ?></h3></div><?php endfor; ?></div></div></div>
</div>
</section>
-5
View File
@@ -1,5 +0,0 @@
<?php /** OEM/ODM process. @package Fragrance_Trade */ ?>
<section id="process" class="b2b-section process-section">
<div class="container"><div class="section-heading row align-items-end g-4"><div class="col-lg-7"><p class="section-eyebrow"><?php esc_html_e( '从需求到交付', 'fragrance-trade' ); ?></p><h2><?php echo esc_html( fragrance_trade_get_mod( 'b2b_process_title' ) ); ?></h2></div><div class="col-lg-5"><p><?php echo esc_html( fragrance_trade_get_mod( 'b2b_process_text' ) ); ?></p></div></div>
<div class="process-grid mt-5"><?php for ( $index = 1; $index <= 8; $index++ ) : ?><article class="process-step"><span><?php echo esc_html( str_pad( (string) $index, 2, '0', STR_PAD_LEFT ) ); ?></span><h3><?php echo esc_html( fragrance_trade_get_mod( 'b2b_process_' . $index . '_title' ) ); ?></h3><p><?php echo esc_html( fragrance_trade_get_mod( 'b2b_process_' . $index . '_text' ) ); ?></p></article><?php endfor; ?></div></div>
</section>
-25
View File
@@ -1,25 +0,0 @@
<?php
/** Factory-produced perfume reference cards. @package Fragrance_Trade */
$fallbacks = array(
'products/placeholder-01.svg',
'products/placeholder-02.svg',
'products/placeholder-03.svg',
'products/placeholder-04.svg',
'products/placeholder-05.svg',
'products/placeholder-06.svg',
'products/placeholder-07.svg',
);
?>
<section id="capabilities" class="b2b-section capabilities-section">
<div class="container">
<div class="section-heading text-center mx-auto"><p class="section-eyebrow"><?php esc_html_e( '门店膜料参考', 'fragrance-trade' ); ?></p><h2><?php echo esc_html( fragrance_trade_get_mod( 'b2b_products_title' ) ); ?></h2><p><?php echo esc_html( fragrance_trade_get_mod( 'b2b_products_text' ) ); ?></p></div>
<div class="capability-grid mt-5">
<?php for ( $index = 1; $index <= count( $fallbacks ); $index++ ) : $title = fragrance_trade_get_mod( 'b2b_product_' . $index . '_title' ); ?>
<article class="capability-card">
<div class="capability-image"><img src="<?php echo esc_url( fragrance_trade_image_uri( 'b2b_product_' . $index . '_image', $fallbacks[ $index - 1 ] ) ); ?>" alt="<?php echo esc_attr( $title ); ?>" class="img-fluid" width="960" height="960" loading="lazy" decoding="async"></div>
<div class="capability-body"><p class="capability-label"><?php esc_html_e( '施工效果参考', 'fragrance-trade' ); ?></p><h3><?php echo esc_html( $title ); ?></h3><p><?php echo esc_html( fragrance_trade_get_mod( 'b2b_product_' . $index . '_text' ) ); ?></p><p class="capability-specs"><?php echo esc_html( fragrance_trade_get_mod( 'b2b_product_' . $index . '_specs' ) ); ?></p><a href="#rfq" class="btn btn-b2b-outline" data-rfq-product="<?php echo esc_attr( $title ); ?>"><?php esc_html_e( '咨询这项服务', 'fragrance-trade' ); ?></a></div>
</article>
<?php endfor; ?>
</div>
</div>
</section>
-5
View File
@@ -1,5 +0,0 @@
<?php /** Typical buyer project scenarios. @package Fragrance_Trade */ if ( ! fragrance_trade_show_section( 'b2b_show_projects' ) ) { return; } ?>
<section id="projects" class="b2b-section projects-section">
<div class="container"><div class="section-heading text-center mx-auto"><p class="section-eyebrow"><?php esc_html_e( '项目案例', 'fragrance-trade' ); ?></p><h2><?php echo esc_html( fragrance_trade_get_mod( 'b2b_projects_title' ) ); ?></h2><p><?php echo esc_html( fragrance_trade_get_mod( 'b2b_projects_text' ) ); ?></p></div>
<div class="project-grid mt-5"><?php for ( $index = 1; $index <= 3; $index++ ) : ?><article class="project-card"><p class="project-meta"><?php echo esc_html( fragrance_trade_get_mod( 'b2b_project_' . $index . '_meta' ) ); ?></p><h3><?php echo esc_html( fragrance_trade_get_mod( 'b2b_project_' . $index . '_title' ) ); ?></h3><p><?php echo esc_html( fragrance_trade_get_mod( 'b2b_project_' . $index . '_text' ) ); ?></p><a href="#rfq" class="text-link"><?php esc_html_e( '咨询类似服务', 'fragrance-trade' ); ?> <span aria-hidden="true">→</span></a></article><?php endfor; ?></div></div>
</section>
-5
View File
@@ -1,5 +0,0 @@
<?php /** Quality control stages. @package Fragrance_Trade */ ?>
<section id="quality" class="b2b-section quality-section">
<div class="container"><div class="section-heading text-center mx-auto"><p class="section-eyebrow"><?php esc_html_e( '品质管控', 'fragrance-trade' ); ?></p><h2><?php echo esc_html( fragrance_trade_get_mod( 'b2b_quality_title' ) ); ?></h2><p><?php echo esc_html( fragrance_trade_get_mod( 'b2b_quality_text' ) ); ?></p></div>
<div class="quality-grid mt-5"><?php for ( $index = 1; $index <= 6; $index++ ) : ?><article class="quality-card"><span class="quality-number"><?php echo esc_html( str_pad( (string) $index, 2, '0', STR_PAD_LEFT ) ); ?></span><h3><?php echo esc_html( fragrance_trade_get_mod( 'b2b_quality_' . $index . '_title' ) ); ?></h3><p><?php echo esc_html( fragrance_trade_get_mod( 'b2b_quality_' . $index . '_text' ) ); ?></p></article><?php endfor; ?></div></div>
</section>
-42
View File
@@ -1,42 +0,0 @@
<?php
/** Compact B2B inquiry form. @package Fragrance_Trade */
$notice = fragrance_trade_get_notice();
$notice_message = fragrance_trade_notice_message( $notice );
?>
<section id="rfq" class="b2b-section rfq-section">
<div class="container">
<div class="row g-5">
<div class="col-lg-4">
<p class="section-eyebrow"><?php esc_html_e( '开始合作', 'fragrance-trade' ); ?></p>
<h2><?php echo esc_html( fragrance_trade_get_mod( 'b2b_rfq_title' ) ); ?></h2>
<p class="section-lead"><?php echo esc_html( fragrance_trade_get_mod( 'b2b_rfq_text' ) ); ?></p>
<div class="rfq-direct">
<h3><?php esc_html_e( '希望直接联系?', 'fragrance-trade' ); ?></h3>
<a href="<?php echo esc_url( fragrance_trade_get_inquiry_url() ); ?>"><?php esc_html_e( '通过邮件或 WhatsApp 联系我们', 'fragrance-trade' ); ?></a>
</div>
</div>
<div class="col-lg-8">
<div class="rfq-form-card">
<div class="fragrance-form-status" aria-live="polite">
<?php if ( $notice_message ) : ?><p class="notice-<?php echo esc_attr( $notice ); ?>"><?php echo esc_html( $notice_message ); ?></p><?php endif; ?>
</div>
<form action="<?php echo esc_url( admin_url( 'admin-post.php' ) ); ?>" method="post">
<input type="hidden" name="action" value="fragrance_rfq">
<?php wp_nonce_field( 'fragrance_rfq', 'fragrance_rfq_nonce' ); ?>
<div class="row g-3">
<div class="col-sm-6"><label for="rfq-name"><?php esc_html_e( '姓名 *', 'fragrance-trade' ); ?></label><input id="rfq-name" class="form-control" type="text" name="name" required maxlength="100" autocomplete="name"></div>
<div class="col-sm-6"><label for="rfq-email"><?php esc_html_e( '邮箱 *', 'fragrance-trade' ); ?></label><input id="rfq-email" class="form-control" type="email" name="email" required maxlength="190" autocomplete="email"></div>
<div class="col-sm-6"><label for="rfq-company"><?php esc_html_e( '车型 / 车牌(选填)', 'fragrance-trade' ); ?></label><input id="rfq-company" class="form-control" type="text" name="company" maxlength="150" autocomplete="organization"></div>
<div class="col-sm-6"><label for="rfq-whatsapp"><?php esc_html_e( 'WhatsApp 或电话', 'fragrance-trade' ); ?></label><input id="rfq-whatsapp" class="form-control" type="text" name="whatsapp" maxlength="100" autocomplete="tel"></div>
<div class="col-12"><label for="rfq-product-type"><?php esc_html_e( '想做的项目 / 膜种 *', 'fragrance-trade' ); ?></label><input id="rfq-product-type" class="form-control" type="text" name="product_type" required maxlength="150" data-rfq-product-field></div>
<div class="col-12"><label for="rfq-message"><?php esc_html_e( '简要需求描述 *', 'fragrance-trade' ); ?></label><textarea id="rfq-message" class="form-control" name="message" rows="4" required maxlength="2000" placeholder="<?php esc_attr_e( '请描述您的需求。', 'fragrance-trade' ); ?>"></textarea></div>
</div>
<div class="fragrance-honeypot" aria-hidden="true"><label for="rfq-website">Website</label><input id="rfq-website" type="text" name="fragrance_website" tabindex="-1" autocomplete="off"></div>
<p class="rfq-privacy-note"><?php esc_html_e( '我们仅会使用这些信息回复您的预约。', 'fragrance-trade' ); ?></p>
<button type="submit" class="btn btn-b2b-primary"><?php echo esc_html( fragrance_trade_get_mod( 'b2b_rfq_button_label' ) ); ?></button>
</form>
</div>
</div>
</div>
</div>
</section>
-14
View File
@@ -1,14 +0,0 @@
<?php /** OEM/ODM services. @package Fragrance_Trade */ ?>
<section id="services" class="b2b-section services-section">
<div class="container">
<div class="section-heading row align-items-end g-4">
<div class="col-lg-7"><p class="section-eyebrow"><?php esc_html_e( '服务项目', 'fragrance-trade' ); ?></p><h2><?php echo esc_html( fragrance_trade_get_mod( 'b2b_services_title' ) ); ?></h2></div>
<div class="col-lg-5"><p><?php echo esc_html( fragrance_trade_get_mod( 'b2b_services_text' ) ); ?></p></div>
</div>
<div class="row g-4 mt-4">
<?php for ( $index = 1; $index <= 4; $index++ ) : ?>
<div class="col-md-6 col-xl-3"><article class="service-card h-100"><span class="card-index">0<?php echo esc_html( $index ); ?></span><h3><?php echo esc_html( fragrance_trade_get_mod( 'b2b_service_' . $index . '_title' ) ); ?></h3><p><?php echo esc_html( fragrance_trade_get_mod( 'b2b_service_' . $index . '_text' ) ); ?></p><a href="#rfq" class="text-link"><?php esc_html_e( '了解此服务', 'fragrance-trade' ); ?> <span aria-hidden="true">→</span></a></article></div>
<?php endfor; ?>
</div>
</div>
</section>