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; } }