From 34c712db32a1c18f4f65457e33237618cef61c2e Mon Sep 17 00:00:00 2001
From: jing <1683105490@qq.com>
Date: Sat, 19 Sep 2026 16:03:13 +0800
Subject: [PATCH] =?UTF-8?q?chore:=20=E5=90=8C=E6=AD=A5=E6=90=9C=E7=B4=A2?=
=?UTF-8?q?=E6=A1=86=E4=B8=8E=E6=BB=91=E5=8A=A8=E5=9B=BE=E7=89=87=E7=9B=B8?=
=?UTF-8?q?=E5=85=B35=E4=B8=AA=E6=96=87=E4=BB=B6=E5=88=B0=20dev/1.0?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.local.config.js | 52 +++--
components-diy/diy-category-item.vue | 147 ++++++++----
components-diy/diy-category.vue | 202 ++++++++++++++++-
components-diy/diy-group.vue | 41 +++-
components-diy/diy-image-nav.vue | 325 ++++++++++++++++++++++++++-
5 files changed, 677 insertions(+), 90 deletions(-)
diff --git a/.local.config.js b/.local.config.js
index bc43ffd..564de8a 100644
--- a/.local.config.js
+++ b/.local.config.js
@@ -68,7 +68,7 @@ const localDevConfig = ({
uniacid: 11,
domain: 'http://localhost:8050/',
},
- '100': {
+ '100': {// 口译教学
uniacid: 100,
domain: 'https://5g.aigc-quickapp.com/',
},
@@ -76,15 +76,15 @@ const localDevConfig = ({
uniacid: 1,
domain: 'https://5g.aigc-quickapp.com/',
},
- '115': {
+ '115': {// 低压断路器
uniacid: 115,
domain: 'https://5g.aigc-quickapp.com/',
},
- '5g-11': {
+ '5g-11': {// 铝箔袋包装
uniacid: 11,
domain: 'https://5g.aigc-quickapp.com/',
},
- '5g-206': {
+ '5g-206': {// 气体探测
uniacid: 206,
domain: 'https://5g.aigc-quickapp.com/',
},
@@ -92,19 +92,19 @@ const localDevConfig = ({
uniacid: 165,
domain: 'https://5g.aigc-quickapp.com/',
},
- '47': { // 修改弹窗
+ '47': { // 免疫细胞干细胞外泌体 ,修改弹窗
uniacid: 47,
domain: 'https://5g.aigc-quickapp.com/',
},
- '145': { // 表单链接转义问题
+ '145': { //昆山昆创净化科技有限公司 , 表单链接转义问题
uniacid: 145,
domain: 'https://5g.aigc-quickapp.com/',
},
- '5g-213': { // 单图组点击链接无法跳转
+ '5g-213': { // 碱性康养水天然泡茶水 , 单图组点击链接无法跳转
uniacid: 213,
domain: 'https://5g.aigc-quickapp.com/',
},
- '5g-4': { // 公告点击链接无法跳转
+ '5g-4': { // 高端硅钢制造装备 , 公告点击链接无法跳转
uniacid: 4,
domain: 'https://5g.aigc-quickapp.com/',
},
@@ -116,11 +116,11 @@ const localDevConfig = ({
uniacid: 50,
domain: 'https://5g.aigc-quickapp.com/',
},
- '5g-237': { // 商品分类,没有立即咨询文字
+ '5g-237': { //高活性氧化镁 商品分类,没有立即咨询文字
uniacid: 237,
domain: 'https://5g.aigc-quickapp.com/',
},
- '2847': { //
+ '2847': { // 家居装饰品
uniacid: 2847,
domain: 'https://xcx.aigc-quickapp.com/',
},
@@ -128,7 +128,7 @@ const localDevConfig = ({
uniacid: 2482,
domain: 'https://xcx6.aigc-quickapp.com/',
},
- '2485': { // 验证电子表单前后端数据一致性
+ '2485': { // 智能物流仓储 , 验证电子表单前后端数据一致性
uniacid: 2485,
domain: 'https://xcx6.aigc-quickapp.com/',
},
@@ -152,11 +152,11 @@ const localDevConfig = ({
uniacid: 12,
domain: 'https://test.aigc-quickapp.com',
},
- '248': { // 大宗商品贸易,支持自定义字体大小、颜色
+ '248': { // 工业称重系统垃圾吊电子秤,支持自定义字体大小、颜色
uniacid: 248,
domain: 'https://5g.aigc-quickapp.com/',
},
- '228': { // 商品分类,没有立即咨询文字
+ '228': { // 反渗透阻垢剂RO装置 商品分类,没有立即咨询文字
uniacid: 228,
domain: 'https://5g.aigc-quickapp.com/',
},
@@ -164,7 +164,7 @@ const localDevConfig = ({
uniacid: 2797,
domain: 'https://xcx.aigc-quickapp.com/',
},
- '5g-95': { // 商品分类,没有立即咨询文字
+ '5g-95': { // 加热器 ,商品分类,没有立即咨询文字
uniacid: 95,
domain: 'https://5g.aigc-quickapp.com/',
},
@@ -185,7 +185,7 @@ const localDevConfig = ({
domain: 'http://192.168.8.118:8050/',
// domain: 'http://localhost:8050/',
},
- '5g-59': { // 合成孔径雷达,一级分类背景字体颜色
+ '5g-59': { // 脱模剂粘合剂乐瑞固Lorechem
uniacid: 59,
domain: 'https://5g.aigc-quickapp.com/',
},
@@ -210,6 +210,18 @@ const localDevConfig = ({
uniacid: 2505,
domain: 'https://xcx.aigc-quickapp.com/',
},
+ '3007': { // 钎焊材料
+ uniacid: 3007,
+ domain: 'https://xcx.aigc-quickapp.com/',
+ },
+ '5g-317': { // 电磁兼容,模板显示不全商品
+ uniacid: 317,
+ domain: 'https://5g.aigc-quickapp.com/',
+ },
+ '5g-312': { // 天然植物功效活性物
+ uniacid: 312,
+ domain: 'https://5g.aigc-quickapp.com/',
+ },
// })['5g-237']; // 商品分类,没有立即咨询文字
// })['5g-97']; // 大宗商品贸易,支持自定义字体大小、颜色
// })['5g-50']; // 燃气安全产品机器人,购物车无反应
@@ -222,10 +234,16 @@ const localDevConfig = ({
// })['2881']; // 鸡肉调理品, 优惠卷显示不全,及逻辑错误
// })['2724']; // 生物菌肥
// })['2811']; // 选择要使用的环境配置
+// })['5g-351']; //洗车机
+// })['5g-204']; //热固性塑料
+// })['5g-59']; //合成孔径雷达
+// })['2709']; //中央厨房
+// })['3007']; //钎焊材料
+// })['2505']; //煤矿钻机
// })['local-11']; // 选择要使用的环境配置
// })['2811']; // 本地
-
-})['2821']; // 本地
+//})['5g-317']; // 电磁兼容
+})['5g-351']; //
// })['local-2']; // 本地
// })['local-11']; // 本地
// })['1-test']; // test
diff --git a/components-diy/diy-category-item.vue b/components-diy/diy-category-item.vue
index 3e4e2c6..4fe9db6 100644
--- a/components-diy/diy-category-item.vue
+++ b/components-diy/diy-category-item.vue
@@ -98,7 +98,8 @@
-
+
{{ category.category_name }}
@@ -139,8 +140,51 @@
-
-
+
+
+
+ {{ category.category_name }}
+
+
+
+
+
+ {{ item.label_name }}
+
+
+
+ {{ isEnEnv ? item.en_goods_name : item.goods_name }}
+
+
+
+
+ {{ $lang('common.currencySymbol') }}
+ {{ parseFloat(showPrice(item)).toFixed(2).split('.')[0] }}
+ .{{ parseFloat(showPrice(item)).toFixed(2).split('.')[1] }}
+
+
+
+ {{ $lang('common.currencySymbol') }}
+ {{ showMarketPrice(item) }}
+
+
+
+
+
+
+
+
+ 暂无商品
+
+
+
+
+
{{ category.category_name }}
-
-
-
- {{ one.category_name }}
-
-
-
-
-
- {{ two.category_name }}
-
-
-
-
+
-
+
{{ category.category_name }}
@@ -617,13 +647,13 @@ export default {
* 三级分类 (value.level == 3) → true → 按二级分类分别显示各自商品
*/
isGroupedDisplay() {
+ // 仅模板1 走"按二级分类分组显示商品"; 模板5 的层级由 value.level 直接决定(见下方 value.template == 5 分支)
return this.value.template == 1 && this.value.level == 3;
},
/**
- * 模板1三级分类: 渲染所有二级分类(包括无商品的空分类)
- * 不再按"是否有商品"过滤, 保证一级分类下的二级分类全部显示,
- * 避免 10 个二级分类只显示 8 个(2 个被当成空分类过滤掉)的问题
- * 加载中(template1ChildLoading[id] === true)的二级分类暂不渲染, 避免闪烁
+ * 模板1三级分类: 只渲染"有商品"的二级分类
+ * 无商品的二级分类整块(标题 + 网格)都不渲染; 加载中的二级分类也暂不渲染(避免闪烁)
+ * 判定时机: 该二级分类加载完成(template1ChildLoading[id] === false)且商品列表非空
*/
filteredGroupedChildren() {
if (!this.isGroupedDisplay) return [];
@@ -631,8 +661,9 @@ export default {
return this.category.child_list.filter(one => {
// 还在加载中: 暂不渲染(避免闪烁)
if (this.template1ChildLoading[one.category_id]) return false;
- // 加载完成(无论是否有商品)都渲染, 无商品时显示空状态
- return true;
+ // 加载完成: 只有该二级分类下有商品时才显示, 无商品则整块隐藏
+ const list = this.template1ChildGoodsList[one.category_id];
+ return !!(list && list.length);
});
},
/**
@@ -642,7 +673,7 @@ export default {
* 若 type 不跟随更新, 模板1 会误走 goods 分支, 导致"资讯类商品=否"的商品不显示立即咨询按钮
*/
type() {
- return this.value.template == 1 || this.value.template == 99 ? 'category' : 'goods';
+ return this.value.template == 1 || this.value.template == 5 || this.value.template == 99 ? 'category' : 'goods';
},
},
created() {
@@ -664,7 +695,14 @@ export default {
// 模板1:根据后台"展示分类"配置 + 数据层级决定加载方式
// 三级分类模式 (isGroupedDisplay) + 有二级分类时,按二级分类分别加载各自商品
// 其他情况(二级分类模式 / 无二级分类),直接加载当前一级分类下的所有商品
- if (this.value.template == 1 && this.category && this.category.category_id) {
+ // 模板5 渲染规则:
+ // 二级分类(value.level == 2) → 商品网格(图2), 需要加载该一级分类下所有商品(合并其二级分类)
+ // 三级分类(value.level == 3) → 二级分类网格(图3), 静态网格无需加载商品
+ // 若该一级分类下没有子分类(兜底), 仍需要加载商品
+ const template5NeedGoods = this.value.template == 5 &&
+ (this.value.level == 2 ||
+ !(this.category && this.category.child_list && this.category.child_list.length));
+ if ((this.value.template == 1 || template5NeedGoods) && this.category && this.category.category_id) {
if (this.isGroupedDisplay && this.category.child_list && this.category.child_list.length) {
this.category.child_list.forEach(child => {
this.getTemplate1ChildGoods(child.category_id);
@@ -739,6 +777,8 @@ export default {
}
// 模板1:切换一级分类时重新加载该分类下的商品
// 三级分类模式 + 有二级分类时,按二级分类分别重新加载,否则直接加载当前分类
+ // 注意: 模板5 是"全部分类纵向平铺", 商品由 created 首次加载 + 父级链式懒加载提供,
+ // 滚动时 select 会随 listenScroll 变化, 若此处也重载会导致滚动中反复清空/重复请求, 故排除 5
if (this.value.template == 1 && this.index == this.select && this.category && this.category.category_id) {
this.template1GoodsList = [];
this.template1ChildGoodsList = {};
@@ -1489,42 +1529,42 @@ export default {
}
}
+// 分类网格(模板5 / legacy 商品分类): 白底卡片 + 3 列 图片 + 名称
+// 与 Branch_5cfee417 的商品分类保持一致
.category-list {
- display: grid !important;
- grid-template-columns: repeat(3, 1fr) !important;
- background: transparent;
+ display: flex;
+ flex-wrap: wrap;
+ background: #fff;
margin: 0 20rpx;
- border-radius: 0;
- padding: 0;
- gap: 20rpx 0;
+ border-radius: 10rpx;
+ padding: 10rpx 0;
.category-item {
- width: 100% !important;
+ width: 33.33%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
- padding: 0;
+ padding: 16rpx;
box-sizing: border-box;
.img-box {
- width: 70% !important;
+ width: 80%;
height: 0;
- padding-bottom: 70%;
+ padding-top: 80%;
margin: 0 auto;
overflow: hidden;
- margin-bottom: 16rpx;
+ margin-bottom: 20rpx;
position: relative;
- background: transparent !important;
- border-radius: 0 !important;
+ background: transparent;
image {
position: absolute;
- width: 100% !important;
- height: 100% !important;
- left: 0 !important;
- top: 0 !important;
- transform: none !important;
+ width: 100%;
+ height: 100%;
+ left: 0;
+ top: 0;
+ transform: none;
}
}
@@ -1539,7 +1579,6 @@ export default {
font-size: var(--diy-category-item-name-size, 24rpx);
}
- // 模板1选中状态
&.selected {
.img-box {
border: 2rpx solid var(--base-color, #FF6B35);
@@ -2490,18 +2529,28 @@ export default {
.screen-category-wrap {
display: flex;
+ align-items: center;
+ position: relative;
.icon-unfold {
- font-size: 24rpx;
+ position: absolute;
+ right: 15rpx;
+ top: 0;
+ z-index: 2;
+ width: 70rpx;
+ height: 70rpx;
+ line-height: 62rpx;
+ text-align: center;
+ font-size: 36rpx;
color: #999;
- padding: 0 0 0 20rpx;
+ padding: 0;
}
}
.screen-category {
flex: 1;
width: 0;
- padding: 0 0 20rpx 0;
+ padding: 0 70rpx 20rpx 0;
white-space: nowrap;
height: 60rpx;
diff --git a/components-diy/diy-category.vue b/components-diy/diy-category.vue
index 4664df9..c8faf84 100644
--- a/components-diy/diy-category.vue
+++ b/components-diy/diy-category.vue
@@ -122,7 +122,7 @@
{ select: select == index },
{ 'border-bottom': value.template == 4 && select + 1 === index },
{ 'border-top': value.template == 4 && select - 1 === index },
- { 'first-category-item': index == 0 && (value.template == 99 || value.template == 1) }
+ { 'first-category-item': index == 0 && (value.template == 99 || value.template == 1 || value.template == 5) }
]" @tap.stop="switchOneCategory(index)">
{{ item.category_name }}
@@ -152,11 +152,44 @@
+ @saveSecondCategory="saveSecondCategoryName">
-
+
-
+
+
+
+
+
+
+
+
+
+
+
+ {{ $lang('components-diy.diy-category.reachedBottom') }}
+
+
+ {
if (!this.heightArea.length) this.getHeightArea(-1);
+ // 分类渲染完成后重新对齐到当前选中的一级分类顶部,
+ // 避免首屏第一个分类的标题被顶出可视区
+ if (this.categoryTree && this.categoryTree.length) this.alignToCategory(this.select);
});
}
}
@@ -536,8 +573,30 @@ export default {
switchOneCategory(index) {
if (index >= this.categoryTree.length) return;
this.select = index;
- this.categoryId = 'category-' + index;
this.scrollLock = true;
+ this.alignToCategory(index);
+ },
+ /**
+ * 把右侧滚动区定位到指定一级分类的顶部
+ *
+ * uni-app 只在 scroll-into-view 的值"发生变化"时才重新滚动,
+ * 而 categoryId 的初值就是 'category-0', 且只有点击左侧分类才会被改写;
+ * 用户在右侧手动滚动后 categoryId 仍是 'category-0', 此时再点第一个分类(index 0)
+ * 值时没有变化, 右侧不会回滚到该分类, 该分类的标题就被顶出可视区
+ * (看起来像"标题丢了 / 卡片被切了一半"), 而点其它分类因为值变了所以都正常。
+ * 这里先置空、下一帧再设置目标 id, 保证每次点击都能重新定位到分类顶部。
+ * @param {Number} index 一级分类下标
+ */
+ alignToCategory(index) {
+ const target = 'category-' + index;
+ if (this.categoryId === target) {
+ this.categoryId = '';
+ this.$nextTick(() => {
+ this.categoryId = target;
+ });
+ } else {
+ this.categoryId = target;
+ }
},
saveSecondCategoryName(name) {
this.lastSecondCategoryName = name;
@@ -559,7 +618,10 @@ export default {
break;
}
}
- if (this.value.template != 1 && this.value.loadType == 'all' && this.heightArea[this.select][1] -
+ // 模板5 三级分类为静态分类网格平铺, 不需要懒加载商品; 这里只保留 loadType=all 的懒加载,
+ // 并补上 heightArea[this.select] 越界保护
+ if (this.value.template != 1 && this.value.loadType == 'all' &&
+ this.heightArea[this.select] && this.heightArea[this.select][1] -
scrollTop - contentWrapHeight < 300) {
if (Array.isArray(this.$refs.categoryItem) && this.$refs.categoryItem[this.select]) this.$refs.categoryItem[this.select].getGoodsList();
}
@@ -1674,6 +1736,134 @@ export default {
}
}
+// ===================== 模板5 商品分类样式(新增: 基于模板1双栏布局) =====================
+.category-template-5 {
+ .search-box {
+ padding: 20rpx 24rpx;
+ background: #fff;
+
+ .search-content {
+ background-color: #f5f5f5;
+ border-radius: 40rpx;
+
+ input {
+ background: #f5f5f5;
+ border-radius: 40rpx;
+ padding: 0 30rpx;
+ font-size: 26rpx;
+ color: #333;
+ }
+
+ .iconfont {
+ right: 16rpx;
+ color: #89899a;
+ font-size: 32rpx;
+ }
+ }
+ }
+
+ .content-box {
+ // 模板5 + 三级分类: 标题行所在整片右半边去掉白色, 与模板1一致
+ background: transparent;
+
+ .tree-wrap {
+ width: 170rpx;
+ background-color: #fff;
+ }
+
+ .right-flex-wrap {
+ background: #f5f5f5;
+
+ .content-wrap {
+ background: transparent;
+ }
+
+ // 模板5 纵向平铺: 每个一级分类独占一行(与模板99"全部分类"结构一致)
+ .child-category {
+ width: 100%;
+ box-sizing: border-box;
+ }
+ }
+ }
+
+ // 穿透到子组件 diy-category-item: 调整 banner 横向内边距 + 分类网格容器为透明
+ ::v-deep .category-adv {
+ padding: 0 20rpx;
+ margin-top: 0;
+ }
+
+ // 模板5: .category-list 直接沿用子组件的 legacy 白底卡片样式(不再覆盖为透明)
+
+ // 穿透到子组件 diy-category-item: 模板5标题行定高, 与左侧第一个一级分类等高对齐
+ ::v-deep .category-title-with-lines {
+ height: 100rpx;
+ box-sizing: border-box;
+ padding: 30rpx 0 20rpx;
+ }
+
+ // 模板5: 左侧第一个一级分类定高, 与右侧标题行垂直对齐
+ .tree-wrap .category-item.first-category-item {
+ height: 100rpx;
+ box-sizing: border-box;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ white-space: nowrap;
+ overflow: hidden;
+
+ view {
+ width: 100%;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ }
+ }
+
+ // 模板5: 分类网格的 .img-box / .name 直接沿用子组件 legacy 尺寸(80% + 名称),
+ // 不再做 70%/透明 覆盖, 以与 Branch_5cfee417 的商品分类保持一致
+
+ // 左侧分类栏样式
+ .tree-wrap .category-item {
+ padding: 28rpx 16rpx;
+ background-color: var(--diy-category-tree-normal-bg-color, #f5f5f5);
+ border-radius: 0;
+
+ view {
+ color: var(--diy-category-tree-normal-text-color, #222222);
+ font-size: 26rpx;
+ line-height: 1.3;
+ }
+
+ &.border-top,
+ &.border-bottom {
+ border-radius: 0;
+ }
+
+ &.select {
+ background: var(--diy-category-tree-select-bg-color, #fff);
+
+ view {
+ color: var(--diy-category-tree-select-text-color, #333);
+ font-weight: bold;
+ font-size: 28rpx;
+ }
+
+ &::before {
+ content: ' ';
+ width: 6rpx;
+ height: 32rpx;
+ background: var(--base-color, #FF6B35);
+ display: block;
+ position: absolute;
+ left: 0;
+ top: 50%;
+ transform: translateY(-50%);
+ border-radius: 0 6rpx 6rpx 0;
+ }
+ }
+ }
+}
+
// ===================== 模板99 商品分类样式 =====================
.category-template-99 {
.search-box {
diff --git a/components-diy/diy-group.vue b/components-diy/diy-group.vue
index 4169969..7183878 100644
--- a/components-diy/diy-group.vue
+++ b/components-diy/diy-group.vue
@@ -377,14 +377,41 @@ export default {
: index + 1;
if (newItem.componentName !== 'Search' || newItem.positionWay !== 'fixed') {
- newItem.pageStyle = `background-color:${newItem.pageBgColor};`;
+ // PR-DIY-SEARCH-MARGIN-COLOR (2026-09-17): 搜索组件的上下左右边距区域颜色
+ // 与搜索框背景色(componentBgColor, 后台自定义)保持一致, 使边距和搜索框融为一体;
+ // 兼容旧数据: 未配置 componentBgColor 时回退 pageBgColor。其余组件仍用 pageBgColor。
+ const wrapBgColor = (newItem.componentName === 'Search' && newItem.componentBgColor)
+ ? newItem.componentBgColor
+ : newItem.pageBgColor;
+ newItem.pageStyle = `background-color:${wrapBgColor};`;
- if (newItem.margin) {
- newItem.pageStyle += `padding-top:${newItem.margin.top * 2}rpx;`;
- newItem.pageStyle += `padding-bottom:${newItem.margin.bottom * 2}rpx;`;
- newItem.pageStyle += `padding-right:${newItem.margin.both * 2}rpx;`;
- newItem.pageStyle += `padding-left:${newItem.margin.both * 2}rpx;`;
- }
+ // 边距归一化: 兼容后台两种数据结构(否则边距不生效)
+ // ① 标准对象形态 margin = { top, bottom, both } —— 大多数组件
+ // ② 扁平字段形态 marginTop / marginBottom / marginBoth
+ // —— 如"滑动图片"(ImageNav), 其 margin 是空数组 [], 左右边距存在 marginBoth 里
+ // both / marginBoth 表示"左右边距"; 数值 → rpx 统一 × 2 (与其它组件一致)
+ const rawMargin = newItem.margin;
+ const objMargin = (rawMargin && typeof rawMargin === 'object' && !Array.isArray(rawMargin)) ? rawMargin : null;
+ const toMarginNum = (v) => { const n = Number(v); return isNaN(n) ? 0 : n; };
+ const pickMargin = (objKey, flatKey) => {
+ if (objMargin && objMargin[objKey] !== undefined && objMargin[objKey] !== null && objMargin[objKey] !== '') {
+ return toMarginNum(objMargin[objKey]);
+ }
+ return toMarginNum(newItem[flatKey]);
+ };
+ const marginTopNum = pickMargin('top', 'marginTop');
+ const marginBottomNum = pickMargin('bottom', 'marginBottom');
+ const marginBothNum = pickMargin('both', 'marginBoth');
+ // 左右: 若有独立的 left/right 字段则优先, 否则用 both(左右一致)
+ const marginLeftNum = (objMargin && objMargin.left !== undefined) ? toMarginNum(objMargin.left)
+ : (newItem.marginLeft !== undefined ? toMarginNum(newItem.marginLeft) : marginBothNum);
+ const marginRightNum = (objMargin && objMargin.right !== undefined) ? toMarginNum(objMargin.right)
+ : (newItem.marginRight !== undefined ? toMarginNum(newItem.marginRight) : marginBothNum);
+
+ newItem.pageStyle += `padding-top:${marginTopNum * 2}rpx;`;
+ newItem.pageStyle += `padding-bottom:${marginBottomNum * 2}rpx;`;
+ newItem.pageStyle += `padding-left:${marginLeftNum * 2}rpx;`;
+ newItem.pageStyle += `padding-right:${marginRightNum * 2}rpx;`;
}
return newItem;
diff --git a/components-diy/diy-image-nav.vue b/components-diy/diy-image-nav.vue
index e36e4d8..37fcd4d 100644
--- a/components-diy/diy-image-nav.vue
+++ b/components-diy/diy-image-nav.vue
@@ -1,10 +1,16 @@
-
+
+
+ :style="imageItemStyle">
String(k).toLowerCase().replace(/[\s_-]/g, '');
+ const asColor = val => {
+ if (Array.isArray(val)) val = val[0];
+ if (typeof val !== 'string') return '';
+ const s = val.trim();
+ if (!s) return '';
+ // 排除图片/URL 等非颜色值
+ if (/^(url\(|https?:|data:|upload\/|addon\/|\/)/i.test(s)) return '';
+ return s;
+ };
+ const sources = [v];
+ ['style', 'config', 'setting', 'data'].forEach(k => {
+ if (v[k] && typeof v[k] === 'object' && !Array.isArray(v[k])) sources.push(v[k]);
+ });
+ const candidates = [
+ 'componentbgcolor', 'componentbackgroundcolor', 'componentcolor', 'componentbg',
+ 'bgcolor', 'backgroundcolor', 'background',
+ 'pagebgcolor'
+ ];
+ for (let c = 0; c < candidates.length; c++) {
+ for (let s = 0; s < sources.length; s++) {
+ for (const key in sources[s]) {
+ if (norm(key) === candidates[c]) {
+ const color = asColor(sources[s][key]);
+ if (color) return color;
+ }
+ }
+ }
+ }
+ return '';
+ },
componentStyle() {
let style = '';
- style += 'background-color:' + this.value.componentBgColor + ';';
+ if (this.componentBgColor) style += 'background-color:' + this.componentBgColor + ';';
if (this.value.componentAngle == 'round') {
style += 'border-top-left-radius:' + (2 * this.value.topAroundRadius) + 'rpx;';
@@ -123,9 +172,219 @@ export default {
isIndicatorDots() {
return this.value.carousel.type != 'hide' &&
1 != Math.ceil(this.value.list.length / (this.value.pageCount * this.value.rowCount));
+ },
+
+ // ========== PR-FRONTEND-DIY-SCROLLBAR (2026-09-18): 水平滚动条显隐三态 ==========
+ // 后台"水平滚动条"配置: 滑动时显示 / 始终显示 / 永不显示 → 归一化为 slide / always / never
+ // 字段名兼容: scrollBar / scrollbar / scrollBarType / horizontalScrollBar / horizontalScrollbar
+ // (忽略大小写/下划线/中划线差异, 并向下探测 value.scrollBar / value.style 等嵌套容器)
+ // 值兼容: 英文枚举(slide/auto/always/never/none...) / 中文枚举(滑动时显示/始终显示/永不显示)
+ // / 数字(0=滑动时显示 1=始终显示 2=永不显示) / 布尔(true=始终显示 false=永不显示)
+ // 兜底: 未下发或无法识别 → 'slide' (滑动时显示, 与历史行为一致)
+ scrollBarMode() {
+ const v = this.value || {};
+ const norm = k => String(k).toLowerCase().replace(/[\s_-]/g, '');
+ // 从配置对象中取"滚动条"字段的原始值
+ const pickRaw = obj => {
+ if (!obj || typeof obj != 'object') return undefined;
+ for (const key in obj) {
+ const k = norm(key);
+ if (k.indexOf('scrollbar') != -1 || k.indexOf('scrolltype') != -1) {
+ const val = obj[key];
+ if (val && typeof val === 'object') {
+ // 子配置对象形态: { scrollBar: { type: 'always' } }
+ return val.type !== undefined ? val.type : (val.value !== undefined ? val.value : val.mode);
+ }
+ if (val !== undefined && val !== null && val !== '') return val;
+ }
+ }
+ return undefined;
+ };
+
+ let raw = pickRaw(v);
+ // 嵌套容器兜底
+ if (raw === undefined) {
+ const containers = ['scrollBar', 'scrollbar', 'style', 'config', 'setting', 'data'];
+ for (let i = 0; i < containers.length; i++) {
+ if (v[containers[i]] && typeof v[containers[i]] === 'object') {
+ raw = pickRaw(v[containers[i]]);
+ if (raw !== undefined) break;
+ }
+ }
+ }
+
+ if (raw === undefined || raw === null || raw === '') return 'slide';
+ if (typeof raw === 'boolean') return raw ? 'always' : 'never';
+ if (raw === 0 || raw === '0') return 'slide';
+ if (raw === 1 || raw === '1') return 'always';
+ if (raw === 2 || raw === '2') return 'never';
+
+ const map = {
+ slide: 'slide', scroll: 'slide', auto: 'slide', scrolling: 'slide', whilescrolling: 'slide', moveslide: 'slide',
+ always: 'always', alwaysdisplay: 'always', show: 'always',
+ never: 'never', none: 'never', hidden: 'never', hide: 'never',
+ // 后台直出中文枚举时兜底
+ '滑动时显示': 'slide', '滑动时': 'slide', '滚动时显示': 'slide',
+ '始终显示': 'always', '一直显示': 'always', '永久显示': 'always',
+ '永不显示': 'never', '从不显示': 'never', '不显示': 'never'
+ };
+ const hit = map[String(raw).toLowerCase().trim()] || map[norm(raw)];
+ return hit || 'slide';
+ },
+
+ // scroll-view 上的滚动条显隐 class: scrollbar-slide / scrollbar-always / scrollbar-never
+ // "滑动时显示"在滚动中追加 is-scrolling (由 scroll 监听维护)
+ scrollBarClass() {
+ const cls = ['scrollbar-' + this.scrollBarMode];
+ if (this.scrollBarMode == 'slide' && this.slideScrolling) cls.push('is-scrolling');
+ return cls;
+ },
+
+ // ========== PR-FRONTEND-DIY-IMAGE-NAV-SPACING (2026-09-18): 图片自定义间距 ==========
+ // 后台"滑动图片"组件(componentName = ImageNav)的 3 项间距配置。
+ // 字段名已用线上接口 POST /api/diyview/info(uniacid=312, DIY_VIEW_INDEX) 核对, 实际为:
+ // imageSpacing 图片横向间距: 相邻图片项之间的横向间隔
+ // imageSpacingTop 图片上间距 : 图片区域内侧的上内边距
+ // imageSpacingBottom 图片下间距 : 图片区域内侧的下内边距
+ // 单位: 按项目惯例(与 diy-group 的 margin.top、图片轮播的 margin.both 一致)换算 rpx = 后台数值 × 2
+ // 容错: 除后台现用命名外, 仍按归一化(忽略大小写/下划线/中划线)兼容其它命名,
+ // 并向下探测 value.imageSpacing / value.margin / value.padding 等嵌套容器。
+ // 兜底: 字段缺失/非法时返回 null, 由 imageSpacingStyle / imageItemStyle 回退历史视觉
+ // (横向 28rpx, 上/下沿用 CSS 的 16rpx), 老装修数据不受影响。
+ imageSpacingConfig() {
+ const v = this.value || {};
+ const RATIO = 2; // 后台数值 → rpx 换算比例
+
+ const norm = k => String(k).toLowerCase().replace(/[\s_-]/g, '');
+ // 非数字(如 'diy'/枚举)或空值 → null, 交给兜底
+ const toRpx = val => {
+ if (val === undefined || val === null || val === '') return null;
+ const n = Number(val);
+ if (isNaN(n)) return null;
+ return Math.max(0, n) * RATIO;
+ };
+
+ // 查找源: value 自身 + 常见嵌套容器(顶层优先)
+ // 注意: 后端偶发把 margin 存成数组(线上该组件 margin: []), 数组不能当容器取裸键, 直接跳过
+ const sources = [v];
+ ['imageSpacing', 'imageSpace', 'spacing', 'space', 'margin', 'padding', 'style', 'config', 'setting']
+ .forEach(k => {
+ if (v[k] && typeof v[k] === 'object' && !Array.isArray(v[k])) sources.push(v[k]);
+ });
+
+ // 三级查找: ① 全源精确名 → ② 全源模糊包含名 → ③ 嵌套容器内的裸键
+ // (如 imageSpacing: { horizontal: 3, top: 0, bottom: 0 })
+ const pick = (exact, fuzzy, nested) => {
+ for (let s = 0; s < sources.length; s++) {
+ const src = sources[s];
+ for (const key in src) {
+ if (exact.indexOf(norm(key)) != -1) {
+ const rpx = toRpx(src[key]);
+ if (rpx !== null) return rpx;
+ }
+ }
+ }
+ for (let s = 0; s < sources.length; s++) {
+ const src = sources[s];
+ for (const key in src) {
+ const k = norm(key);
+ if (fuzzy.some(f => k.indexOf(f) != -1)) {
+ const rpx = toRpx(src[key]);
+ if (rpx !== null) return rpx;
+ }
+ }
+ }
+ // 裸键(top/bottom/both...)只在嵌套容器内解释, 避免误取顶层同名字段
+ for (let s = 1; s < sources.length; s++) {
+ const src = sources[s];
+ for (const key in src) {
+ if (nested.indexOf(norm(key)) != -1) {
+ const rpx = toRpx(src[key]);
+ if (rpx !== null) return rpx;
+ }
+ }
+ }
+ return null;
+ };
+
+ return {
+ // 图片横向间距 —— 后台现用命名 imageSpacing(首项命中即可, 后面都是兼容别名)
+ horizontal: pick(
+ ['imagespacing', 'imagespacinghorizontal', 'imagehorizontalspacing', 'imagespacingboth', 'imagespacenum',
+ 'imagemargin', 'imagemarginnum', 'imagespace', 'horizontalspacing', 'horizontalspacenum', 'imagegap',
+ 'spacenum', 'marginnum', 'imageverticalspace', 'space', 'gap'],
+ ['horizontalspacing', 'imagespacing', 'imagespace', 'imagemargin', 'spacenum', 'marginnum', 'imagegap', 'gap'],
+ ['horizontal', 'horizontalnum', 'lr', 'leftright', 'both', 'bothnum', 'imagespacing', 'num', 'margin', 'space', 'gap']
+ ),
+ // 图片上间距 —— 后台现用命名 imageSpacingTop
+ paddingTop: pick(
+ ['imagespacingtop', 'imagetopspacing', 'imagespacetop', 'imagetopspace', 'imagepaddingtop',
+ 'paddingtop', 'spacingtop', 'topspace', 'margintop'],
+ ['imagespacingtop', 'paddingtop', 'spacingtop', 'topspacing', 'topspace', 'margintop'],
+ ['top', 'topnum', 'paddingtop', 'margintop', 'spacetop', 'spacingtop']
+ ),
+ // 图片下间距 —— 后台现用命名 imageSpacingBottom
+ paddingBottom: pick(
+ ['imagespacingbottom', 'imagebottomspacing', 'imagespacebottom', 'imagebottomspace', 'imagepaddingbottom',
+ 'paddingbottom', 'spacingbottom', 'bottomspace', 'marginbottom'],
+ ['imagespacingbottom', 'paddingbottom', 'spacingbottom', 'bottomspacing', 'bottomspace', 'marginbottom'],
+ ['bottom', 'bottomnum', 'paddingbottom', 'marginbottom', 'spacebottom', 'spacingbottom']
+ )
+ };
+ },
+
+ // 图片区域上/下内边距(作用在滚动容器上); 未配置时不输出内联样式, 沿用 CSS padding
+ imageSpacingStyle() {
+ const cfg = this.imageSpacingConfig;
+ let style = '';
+ if (cfg.paddingTop !== null) style += 'padding-top:' + cfg.paddingTop + 'rpx;';
+ if (cfg.paddingBottom !== null) style += 'padding-bottom:' + cfg.paddingBottom + 'rpx;';
+ return style;
+ },
+
+ // 图片项之间的横向间距; 未配置时回退原写死的 28rpx
+ imageItemStyle() {
+ const horizontal = this.imageSpacingConfig.horizontal;
+ return 'margin-right:' + (horizontal === null ? 28 : horizontal) + 'rpx;';
}
},
methods: {
+ // ========== PR-FRONTEND-DIY-SCROLLBAR (2026-09-18): H5 "滑动时显示"滚动条控制 ==========
+ // 背景: H5 端 scroll-view 的横向滚动条默认被 CSS 隐藏; "滑动时显示"需要监听 scroll,
+ // 滚动中给容器加 is-scrolling 类展示滚动条, 停止滚动 800ms 后移除类再隐藏。
+ // "始终显示/永不显示"为纯 CSS 类控制(scrollbar-always/scrollbar-never), 无需 JS。
+ bindSlideScrollbar() {
+ // #ifdef H5
+ if (this._slideScrollBound) return;
+ var el = this.getSlideContainer();
+ if (!el) return;
+ this._slideScrollEl = el;
+ this._slideScrollBound = true;
+ var self = this;
+ this._onSlideScroll = function() {
+ if (self.scrollBarMode != 'slide') return;
+ if (!self.slideScrolling) self.slideScrolling = true;
+ clearTimeout(self._slideScrollTimer);
+ self._slideScrollTimer = setTimeout(function() {
+ self.slideScrolling = false;
+ }, 800);
+ };
+ el.addEventListener('scroll', this._onSlideScroll, { passive: true });
+ // #endif
+ },
+ unbindSlideScrollbar() {
+ // #ifdef H5
+ if (this._slideScrollEl && this._onSlideScroll) {
+ this._slideScrollEl.removeEventListener('scroll', this._onSlideScroll);
+ }
+ clearTimeout(this._slideScrollTimer);
+ this._slideScrollEl = null;
+ this._onSlideScroll = null;
+ this._slideScrollBound = false;
+ this.slideScrolling = false;
+ // #endif
+ },
+
// 页面跳转
redirectTo(link) {
if (!link.wap_url || this.$util.getCurrRoute() != this.$util.MEMBER_PAGE_URL || this.storeToken) {
@@ -327,17 +586,61 @@ export default {
// #ifdef H5
overflow-x: auto;
overflow-y: hidden;
- // 隐藏滚动条,视觉上仍保持单页布局;用户用鼠标拖拽/滚轮即可滚动
- scrollbar-width: none; // Firefox
- -ms-overflow-style: none; // IE/Edge legacy
- &::-webkit-scrollbar {
- display: none;
- }
// 禁止鼠标选中文字(拖拽时选中会显得很别扭)
user-select: none;
-webkit-user-select: none;
// 拖拽时关闭过渡,避免 Chrome 的惯性滚动与 JS 抢控制权
-webkit-overflow-scrolling: touch;
+
+ // ========== PR-FRONTEND-DIY-SCROLLBAR (2026-09-18): 水平滚动条显隐三态 ==========
+ // 默认态(滑动时显示的静止态 / 永不显示): 隐藏滚动条,视觉上仍保持单页布局;
+ // 用户用鼠标拖拽/滚轮即可滚动(滚动能力不受滚动条显隐影响)
+ scrollbar-width: none; // Firefox
+ -ms-overflow-style: none; // IE/Edge legacy
+ &::-webkit-scrollbar {
+ display: none;
+ }
+ // uni scroll-view 在 H5 编译后真滚动容器是内层 .uni-scroll-view, 同步隐藏
+ ::v-deep .uni-scroll-view {
+ scrollbar-width: none;
+ -ms-overflow-style: none;
+ }
+ ::v-deep .uni-scroll-view::-webkit-scrollbar {
+ display: none;
+ }
+
+ // "始终显示" 与 "滑动时显示"(滚动中, 由 is-scrolling 类驱动): 展示细滚动条
+ &.scrollbar-always,
+ &.scrollbar-slide.is-scrolling {
+ scrollbar-width: thin; // Firefox
+ scrollbar-color: rgba(0, 0, 0, 0.25) transparent;
+ &::-webkit-scrollbar {
+ display: block;
+ height: 4px;
+ }
+ &::-webkit-scrollbar-thumb {
+ background-color: rgba(0, 0, 0, 0.25);
+ border-radius: 2px;
+ }
+ &::-webkit-scrollbar-track {
+ background-color: transparent;
+ }
+ ::v-deep .uni-scroll-view {
+ scrollbar-width: thin;
+ scrollbar-color: rgba(0, 0, 0, 0.25) transparent;
+ }
+ ::v-deep .uni-scroll-view::-webkit-scrollbar {
+ display: block;
+ height: 4px;
+ }
+ ::v-deep .uni-scroll-view::-webkit-scrollbar-thumb {
+ background-color: rgba(0, 0, 0, 0.25);
+ border-radius: 2px;
+ }
+ ::v-deep .uni-scroll-view::-webkit-scrollbar-track {
+ background-color: transparent;
+ }
+ }
// #endif
}