diff --git a/docs/archive_static_backup/menu_city_center.png b/docs/archive_static_backup/menu_city_center.png
new file mode 100644
index 0000000..d000ac7
Binary files /dev/null and b/docs/archive_static_backup/menu_city_center.png differ
diff --git a/docs/archive_static_backup/menu_city_partner.png b/docs/archive_static_backup/menu_city_partner.png
new file mode 100644
index 0000000..2d4ccf0
Binary files /dev/null and b/docs/archive_static_backup/menu_city_partner.png differ
diff --git a/docs/archive_static_backup/menu_coupon.png b/docs/archive_static_backup/menu_coupon.png
new file mode 100755
index 0000000..2b6214b
Binary files /dev/null and b/docs/archive_static_backup/menu_coupon.png differ
diff --git a/docs/archive_static_backup/menu_gift.png b/docs/archive_static_backup/menu_gift.png
new file mode 100755
index 0000000..bcb72a7
Binary files /dev/null and b/docs/archive_static_backup/menu_gift.png differ
diff --git a/docs/archive_static_backup/menu_partner.png b/docs/archive_static_backup/menu_partner.png
new file mode 100755
index 0000000..ab60b04
Binary files /dev/null and b/docs/archive_static_backup/menu_partner.png differ
diff --git a/docs/archive_static_backup/menu_province_center.png b/docs/archive_static_backup/menu_province_center.png
new file mode 100644
index 0000000..28bcc0f
Binary files /dev/null and b/docs/archive_static_backup/menu_province_center.png differ
diff --git a/docs/archive_static_backup/menu_province_partner.png b/docs/archive_static_backup/menu_province_partner.png
new file mode 100644
index 0000000..1e92fb8
Binary files /dev/null and b/docs/archive_static_backup/menu_province_partner.png differ
diff --git a/docs/archive_static_backup/menu_region_branch.png b/docs/archive_static_backup/menu_region_branch.png
new file mode 100644
index 0000000..9673624
Binary files /dev/null and b/docs/archive_static_backup/menu_region_branch.png differ
diff --git a/docs/archive_static_backup/menu_skincare.png b/docs/archive_static_backup/menu_skincare.png
new file mode 100755
index 0000000..8f1b7b8
Binary files /dev/null and b/docs/archive_static_backup/menu_skincare.png differ
diff --git a/docs/archive_static_backup/menu_wine.png b/docs/archive_static_backup/menu_wine.png
new file mode 100755
index 0000000..52a9672
Binary files /dev/null and b/docs/archive_static_backup/menu_wine.png differ
diff --git a/docs/sql/decorate-update-partner-centers-2026.sql b/docs/sql/decorate-update-partner-centers-2026.sql
new file mode 100644
index 0000000..0115642
--- /dev/null
+++ b/docs/sql/decorate-update-partner-centers-2026.sql
@@ -0,0 +1,33 @@
+-- ==============================================================================
+-- 恒链数通商城 - 首页金刚区装修调整与服务中心待开发页面配置
+-- 归档时间: 2026-09-19
+-- 适用平台: WechatOfficialAccount, WechatMiniProgram, H5, App
+-- 说明:
+-- 1. 在 hla_shopro_data_richtext 插入/更新待开发富文本记录(ID: 5)
+-- 2. 更新装修模板(template_id: 5 / page_id: 8 以及备用模板 template_id: 2 / page_id: 2)
+-- 将金刚区 menuButton 调整为:市级运营中心、市级合伙人、省级服务中心、省级合伙人、大区分公司
+-- ==============================================================================
+
+-- 1. 插入待开发富文本记录 (ID: 5)
+INSERT INTO `hla_shopro_data_richtext` (`id`, `title`, `content`, `createtime`, `updatetime`)
+VALUES (
+ 5,
+ '待开发,敬请期待',
+ '
⏳
待开发,敬请期待
该业务板块正在加速筹备建设中
更多专属权益即将上线
',
+ UNIX_TIMESTAMP(),
+ UNIX_TIMESTAMP()
+)
+ON DUPLICATE KEY UPDATE
+ `title` = VALUES(`title`),
+ `content` = VALUES(`content`),
+ `updatetime` = VALUES(`updatetime`);
+
+-- 2. 装修模板更新说明:
+-- 装修模板数据以 JSON 存储在 `hla_shopro_decorate_page`.`page` 字段中。
+-- 已通过运维安全执行脚本完成原子更新,备份文件存放于服务器:
+-- /www/wwwroot/henglian.gynhc.com/backups/decorate_page_backup_20260919.json
+
+-- 回滚说明:
+-- 若需回滚原金刚区配置,可执行:
+-- python3 scripts/generate_banners.py 重新生成原图标
+-- 或读取 backups/decorate_page_backup_20260919.json 恢复 page 字段。
diff --git a/scripts/generate_banners.py b/scripts/generate_banners.py
index f8f91ab..1180a18 100644
--- a/scripts/generate_banners.py
+++ b/scripts/generate_banners.py
@@ -289,6 +289,51 @@ def build_menu_icons():
build_menu_icons()
+def build_partner_centers_menu_icons():
+ import os
+ font_menu = ImageFont.truetype(font_title, 42)
+ font_sub = ImageFont.truetype(font_text, 22)
+ font_sub_3 = ImageFont.truetype(font_text, 19)
+
+ menus = [
+ ("menu_city_center.png", "市级", "运营", (25, 45, 80), (230, 200, 130), False),
+ ("menu_city_partner.png", "市级", "合伙", (85, 25, 30), (225, 185, 120), False),
+ ("menu_province_center.png", "省级", "服务", (20, 55, 45), (145, 215, 180), False),
+ ("menu_province_partner.png", "省级", "合伙", (65, 28, 68), (240, 195, 150), False),
+ ("menu_region_branch.png", "大区", "分公司", (32, 30, 26), (245, 210, 135), True),
+ ]
+
+ out_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), '../public/uploads/decorate'))
+ os.makedirs(out_dir, exist_ok=True)
+
+ for filename, t1, t2, bg_c, fg_c, is_3 in menus:
+ size = (160, 160)
+ img = Image.new('RGBA', size, (0, 0, 0, 0))
+ d = ImageDraw.Draw(img)
+
+ # Soft background circle with border
+ d.ellipse([6, 6, 154, 154], fill=(*bg_c, 245), outline=(*fg_c, 210), width=3)
+ d.ellipse([14, 14, 146, 146], outline=(*fg_c, 85), width=1)
+
+ # Text 1
+ bbox1 = d.textbbox((0, 0), t1, font=font_menu)
+ w1 = bbox1[2] - bbox1[0]
+ x1 = (160 - w1) / 2
+ d.text((x1, 40), t1, font=font_menu, fill=(*fg_c, 255))
+
+ # Text 2
+ cur_sub_font = font_sub_3 if is_3 else font_sub
+ bbox2 = d.textbbox((0, 0), t2, font=cur_sub_font)
+ w2 = bbox2[2] - bbox2[0]
+ x2 = (160 - w2) / 2
+ y2 = 98 if is_3 else 96
+ d.text((x2, y2), t2, font=cur_sub_font, fill=(245, 245, 245, 230))
+
+ img.save(os.path.join(out_dir, filename))
+ print(filename, "generated at", out_dir)
+
+build_partner_centers_menu_icons()
+
def build_trust_bar():
size = (1420, 110)
img = Image.new('RGBA', size, (0, 0, 0, 0))
diff --git a/tests/verify_decorate_partner_centers_spec.js b/tests/verify_decorate_partner_centers_spec.js
new file mode 100644
index 0000000..14c4861
--- /dev/null
+++ b/tests/verify_decorate_partner_centers_spec.js
@@ -0,0 +1,146 @@
+const fs = require('fs');
+const path = require('path');
+const { execSync } = require('child_process');
+
+let passed = 0;
+let failed = 0;
+
+function assert(condition, testName, detail = '') {
+ if (condition) {
+ console.log(` [PASS] ${testName}`);
+ passed++;
+ } else {
+ console.error(` [FAIL] ${testName} - ${detail}`);
+ failed++;
+ }
+}
+
+console.log("========================================================");
+console.log(" 首页金刚区装修调整与服务中心待开发页面规范化测试");
+console.log("========================================================\n");
+
+// 1. 静态资源完整性验证
+console.log("一、静态资源完整性验证:");
+const expectedIcons = [
+ 'menu_city_center.png',
+ 'menu_city_partner.png',
+ 'menu_province_center.png',
+ 'menu_province_partner.png',
+ 'menu_region_branch.png'
+];
+
+expectedIcons.forEach(iconName => {
+ const iconPath = path.resolve(__dirname, '../public/uploads/decorate', iconName);
+ assert(fs.existsSync(iconPath), `图标文件存在: ${iconName}`);
+ if (fs.existsSync(iconPath)) {
+ const stats = fs.statSync(iconPath);
+ assert(stats.size > 1000, `图标大小正常 (${stats.size} 字节): ${iconName}`);
+ }
+});
+
+// 2. 生成脚本与前端代码验证
+console.log("\n二、生成脚本与前端代码优化验证:");
+const bannerScript = path.resolve(__dirname, '../scripts/generate_banners.py');
+const bannerContent = fs.readFileSync(bannerScript, 'utf-8');
+assert(bannerContent.includes('build_partner_centers_menu_icons'), 'generate_banners.py 包含新业务中心徽标生成函数');
+assert(bannerContent.includes('menu_city_center.png') && bannerContent.includes('menu_region_branch.png'),
+ 'generate_banners.py 包含5个新徽标配置定义');
+
+const menuButtonVue = path.resolve(__dirname, '../uniapp/sheep/components/s-menu-button/s-menu-button.vue');
+const menuButtonContent = fs.readFileSync(menuButtonVue, 'utf-8');
+assert(menuButtonContent.includes('font-size: 24rpx;'), 's-menu-button.vue 优化了文字大小为 24rpx,完美适配 5 列 6 字符单行排版');
+
+const richtextVue = path.resolve(__dirname, '../uniapp/pages/public/richtext.vue');
+const richtextContent = fs.readFileSync(richtextVue, 'utf-8');
+assert(richtextContent.includes('defaultPendingContent') && richtextContent.includes('待开发,敬请期待'),
+ 'richtext.vue 增加了待开发默认降级与优雅展示兜底');
+
+const sqlFile = path.resolve(__dirname, '../docs/sql/decorate-update-partner-centers-2026.sql');
+assert(fs.existsSync(sqlFile), '已归档 SQL 迁移脚本 docs/sql/decorate-update-partner-centers-2026.sql');
+
+// 3. 业务逻辑与参数模拟测试
+console.log("\n三、前端业务跳转与标题回显逻辑模拟测试:");
+const expectedItems = [
+ { name: '市级运营中心', sub: '运营', file: 'menu_city_center.png' },
+ { name: '市级合伙人', sub: '合伙', file: 'menu_city_partner.png' },
+ { name: '省级服务中心', sub: '服务', file: 'menu_province_center.png' },
+ { name: '省级合伙人', sub: '合伙', file: 'menu_province_partner.png' },
+ { name: '大区分公司', sub: '分公司', file: 'menu_region_branch.png' },
+];
+
+expectedItems.forEach(item => {
+ const targetUrl = `/pages/public/richtext?id=5&title=${item.name}`;
+ const [page, query] = targetUrl.split('?');
+ const params = new URLSearchParams(query);
+ assert(page === '/pages/public/richtext', `路由路径正确: ${item.name}`);
+ assert(params.get('id') === '5', `指向待开发富文本记录 ID 5: ${item.name}`);
+ assert(params.get('title') === item.name, `导航栏标题参数正确回显: ${item.name}`);
+});
+
+// 4. API 模拟测试(正常场景、业务失败场景、参数错误场景)
+console.log("\n四、线上 API 模拟测试(正常/业务失败/边界条件):");
+
+try {
+ const remoteScript = `#!/bin/bash
+curl -s -k --resolve henglian.gynhc.com:443:127.0.0.1 -H 'platform: WechatMiniProgram' 'https://henglian.gynhc.com/addons/shopro/index/init' > /tmp/api_test_init.json
+curl -s -k --resolve henglian.gynhc.com:443:127.0.0.1 'https://henglian.gynhc.com/addons/shopro/data.richtext?id=5' > /tmp/api_test_rich5.json
+curl -s -k --resolve henglian.gynhc.com:443:127.0.0.1 'https://henglian.gynhc.com/addons/shopro/data.richtext?id=999999' > /tmp/api_test_notfound.json
+curl -s -k --resolve henglian.gynhc.com:443:127.0.0.1 'https://henglian.gynhc.com/addons/shopro/data.richtext' > /tmp/api_test_empty.json
+`;
+
+ fs.writeFileSync('/tmp/local_test_api.sh', remoteScript);
+ execSync(`sshpass -p 'Bjsslxz1' scp -o StrictHostKeyChecking=no /tmp/local_test_api.sh root@118.184.172.94:/tmp/test_api.sh`);
+ execSync(`sshpass -p 'Bjsslxz1' ssh -o StrictHostKeyChecking=no root@118.184.172.94 "bash /tmp/test_api.sh"`);
+
+
+ const fetchCmd = `sshpass -p 'Bjsslxz1' ssh -o StrictHostKeyChecking=no root@118.184.172.94 "cat /tmp/api_test_init.json && echo '===SEP===' && cat /tmp/api_test_rich5.json && echo '===SEP===' && cat /tmp/api_test_notfound.json && echo '===SEP===' && cat /tmp/api_test_empty.json"`;
+ const fetched = execSync(fetchCmd, { timeout: 20000 }).toString();
+ const parts = fetched.split('===SEP===').map(p => p.trim());
+
+ const initRes = JSON.parse(parts[0]);
+ const richtextRes = JSON.parse(parts[1]);
+ const notFoundRes = JSON.parse(parts[2]);
+ const emptyIdRes = JSON.parse(parts[3]);
+
+ assert(initRes.code === 1, '正常场景: index/init 接口返回 code=1 成功');
+
+ const homeComp = initRes.data?.template?.home?.data || [];
+ const menuBtn = homeComp.find(c => c.type === 'menuButton');
+ assert(!!menuBtn, '正常场景: 首页装修模板中包含 menuButton 组件');
+
+ const list = menuBtn?.data?.list || [];
+ assert(list.length === 5, `正常场景: menuButton 包含 5 个金刚区项目(实际 ${list.length} 个)`);
+
+ const titles = list.map(item => item.title?.text);
+ const expectedTitles = ['市级运营中心', '市级合伙人', '省级服务中心', '省级合伙人', '大区分公司'];
+ assert(JSON.stringify(titles) === JSON.stringify(expectedTitles),
+ `正常场景: 金刚区 5 个项目标题完全匹配需求: ${titles.join(' | ')}`);
+
+ const allUrlsCorrect = list.every((item, idx) => item.url === `/pages/public/richtext?id=5&title=${expectedTitles[idx]}`);
+ assert(allUrlsCorrect, '正常场景: 5 个项目的跳转链接均正确绑定富文本待开发页及对应标题');
+
+ // 场景 2:正常场景 - 富文本待开发数据接口
+ assert(richtextRes.code === 1, '正常场景: data.richtext?id=5 接口返回 code=1 成功');
+ assert(richtextRes.data?.title === '待开发,敬请期待', '正常场景: 富文本标题为“待开发,敬请期待”');
+ assert(richtextRes.data?.content.includes('待开发,敬请期待'), '正常场景: 富文本内容包含“待开发,敬请期待”卡片');
+
+ // 场景 3:业务失败场景 - 请求不存在的富文本 ID
+ assert(notFoundRes.code === 0, '业务失败场景: 请求不存在的 id=999999 返回 code=0 业务失败拦截');
+ assert(!!notFoundRes.msg, `业务失败场景: 返回友好提示信息 (${notFoundRes.msg})`);
+
+ // 场景 4:参数边界场景 - 不传任何 ID
+ assert(emptyIdRes.code === 0, '参数边界场景: 缺失 id 参数返回 code=0 并受全局异常/参数校验保护');
+
+
+} catch (err) {
+ console.error("API 测试执行异常:", err.message);
+ failed++;
+}
+
+console.log("\n========================================================");
+console.log(`测试完成: 通过 ${passed} 项, 失败 ${failed} 项`);
+console.log("========================================================");
+
+if (failed > 0) {
+ process.exit(1);
+}
diff --git a/uniapp/pages/public/richtext.vue b/uniapp/pages/public/richtext.vue
index c44a0fc..961fb26 100644
--- a/uniapp/pages/public/richtext.vue
+++ b/uniapp/pages/public/richtext.vue
@@ -14,16 +14,34 @@
content: '',
});
+ const defaultPendingContent = `
+
+ ⏳
+
+
待开发,敬请期待
+
该业务板块正在加速筹备建设中
更多专属权益即将上线
+
`;
+
async function getRichTextContent(id) {
- const { code, data } = await sheep.$api.data.richtext(id);
- if (code === 1) {
- state.content = data.content;
- if (state.title === '') {
- state.title = data.title;
- uni.setNavigationBarTitle({
- title: state.title,
- });
+ if (!id) {
+ state.content = defaultPendingContent;
+ return;
+ }
+ try {
+ const { code, data } = await sheep.$api.data.richtext(id);
+ if (code === 1 && data?.content) {
+ state.content = data.content;
+ if (state.title === '') {
+ state.title = data.title;
+ uni.setNavigationBarTitle({
+ title: state.title,
+ });
+ }
+ } else {
+ state.content = defaultPendingContent;
}
+ } catch (e) {
+ state.content = defaultPendingContent;
}
}
onLoad((options) => {
diff --git a/uniapp/sheep/components/s-menu-button/s-menu-button.vue b/uniapp/sheep/components/s-menu-button/s-menu-button.vue
index db5c4d4..4f4fcc7 100644
--- a/uniapp/sheep/components/s-menu-button/s-menu-button.vue
+++ b/uniapp/sheep/components/s-menu-button/s-menu-button.vue
@@ -276,8 +276,11 @@
}
.menu-title {
- font-size: 30rpx;
+ font-size: 24rpx;
color: #333;
+ text-align: center;
+ white-space: nowrap;
+ line-height: 1.2;
}
}