diff --git a/.local.config.js b/.local.config.js index bb6b23c..58413e0 100644 --- a/.local.config.js +++ b/.local.config.js @@ -202,7 +202,8 @@ const localDevConfig = ({ // })['2724']; // 生物菌肥 // })['2811']; // 选择要使用的环境配置 // })['local-11']; // 选择要使用的环境配置 -})['2811']; // 本地 +// })['2811']; // 本地 +})['local-1']; // 本地 // })['local-2']; // 本地 // })['local-11']; // 本地 // })['1-test']; // test diff --git a/components-diy/diy-form.vue b/components-diy/diy-form.vue index 2cebf85..ec2199d 100644 --- a/components-diy/diy-form.vue +++ b/components-diy/diy-form.vue @@ -7,31 +7,41 @@ r=function(){var o=this.value.show_banner...this.$mp.data...}) 报 "Cannot read properties of undefined (reading 'show_banner')"。 --> - - - - - - {{ value && value.form_tip }} - - - - + + @@ -546,6 +569,10 @@ export default { requiredColor() { return this.config.requiredColor; }, + // 必填星号位置(before=标签前(默认)/ after=标签后;与后端 form_config.required_position 对齐) + requiredPosition() { + return this.config.requiredPosition === 'after' ? 'after' : 'before'; + }, checkedColor() { return this.config.checkedColor; }, @@ -607,6 +634,50 @@ export default { optStyle['gap'] = '20rpx'; } this.$set(item, '_optionLayoutStyle', optStyle); + + // 信息卡片:容器底色 + 空白判定 + 分割线样式(与后台 design.js 的 + // getInfoCardContainerStyle / getInfoCardDividerStyle 对齐;数值单位为 px 直出, + // 与编辑器输入单位一致,375 宽屏上与后台 PC 预览视觉一致) + if (item.controller == 'InfoCard') { + // 容器:bg_color 配置且非 transparent 时输出底色 + 圆角 + 内边距 + const wrapStyle = {}; + const bg = itemValue.bg_color; + if (bg && bg !== 'transparent') { + wrapStyle['background-color'] = bg; + wrapStyle['border-radius'] = '6px'; + wrapStyle['padding'] = '8px 10px'; + } + this.$set(item, '_infoCardWrapStyle', wrapStyle); + // 空白判定:去标签 +   + 去空白后为空 → 不渲染富文本(与后台一致,避免空白块) + const raw = itemValue.content == null ? '' : String(itemValue.content); + const textOnly = raw.replace(/<[^>]+>/g, '').replace(/ /gi, ' ').replace(/\s/g, ''); + this.$set(item, '_infoCardHasContent', !!textOnly); + // 分割线:border-top 渲染支持 solid/dashed/dotted/double; + // 宽度 calc(w% - 2*px),w<100 且 px=0 时 margin auto 居中(与后台同规则) + if (itemValue.show_divider == 1) { + const h = parseInt(itemValue.divider_height); + const mt = parseInt(itemValue.divider_margin_top); + let w = parseInt(itemValue.divider_width); + let px = parseInt(itemValue.divider_padding_x); + if (!Number.isFinite(w) || w <= 0 || w > 100) w = 100; + if (!Number.isFinite(px) || px < 0) px = 0; + const dStyle = { + height: '0', + 'border-top': (Number.isFinite(h) && h > 0 ? h : 1) + 'px ' + (itemValue.divider_style || 'solid') + ' ' + (itemValue.divider_color || '#f0f0f0'), + 'margin-top': (Number.isFinite(mt) && mt >= 0 ? mt : 8) + 'px', + 'margin-left': px + 'px', + 'margin-right': px + 'px', + width: 'calc(' + w + '% - ' + (px * 2) + 'px)' + }; + if (w < 100 && px === 0) { + dStyle['margin-left'] = 'auto'; + dStyle['margin-right'] = 'auto'; + } + this.$set(item, '_infoCardDividerStyle', dStyle); + } else { + this.$set(item, '_infoCardDividerStyle', {}); + } + } }); }, // 容器样式(提供给外部 diy-form 使用) @@ -793,7 +864,12 @@ export default { verify() { for (let i = 0; i < this.formData.length; i++) { let item = this.formData[i]; - + + // 信息卡片:不参与必填/校验(设计如此:仅展示活动/事件描述) + if (item.controller == 'InfoCard') { + continue; + } + if (item.controller == 'Text') { if (item.value.required && !item.val) { this.$util.showToast({ title: this.$lang('components.ns-newform.pleaseInput', { title: item.value.title }) }); @@ -1170,6 +1246,11 @@ export default { display: inline-block; vertical-align: middle; } + /* 必填星号位置=标签后(required_position=after):margin 方向反转,与标签前视觉对称 */ + .required.required-after { + margin-right: 0; + margin-left: 6rpx; + } } .box { @@ -1526,6 +1607,55 @@ export default { } } } + +// 信息卡片(与 addon/form/shop/view/public/css/form.css 的 .form-info-card* 对齐) +// 展示型组件:富文本内容 + 可选底部分割线;不参与数据校验/存储 +.info-card-wrap { + width: 100%; + box-sizing: border-box; + padding: 4rpx 0 2rpx; + + // 富文本内容(编辑器产出的 p/strong/em/u/span/a/img/video/hr/iframe) + .info-card-content { + display: block; + font-size: 28rpx; + color: #303133; + line-height: 1.7; + word-break: break-all; + + ::v-deep p { margin: 0; } + ::v-deep p + p { margin-top: 6rpx; } + ::v-deep img { max-width: 100%; } + ::v-deep video { max-width: 100%; display: block; } + ::v-deep hr { border: none; border-top: 1px solid #e0e0e0; margin: 16rpx 0; } + ::v-deep a { color: #1278FE; } + } + + // 底部分割线:线样式/颜色/粗细/上边距/宽度/左右边距由内联 _infoCardDividerStyle 控制 + .info-card-divider { + box-sizing: border-box; + } + + // 老数据结构(title + items)兜底渲染 + .info-card-title { + line-height: 1.4; + // 字号 / 颜色由模板内联 style 控制 + } + + .info-card-items { + margin-top: 16rpx; + } + + .info-card-item { + line-height: 1.6; + word-break: break-all; + // 字号 / 颜色由模板内联 style 控制 + + & + .info-card-item { + margin-top: 4rpx; + } + } +} diff --git a/pages_tool/form/formdata.vue b/pages_tool/form/formdata.vue index 8a77570..c42824a 100644 --- a/pages_tool/form/formdata.vue +++ b/pages_tool/form/formdata.vue @@ -178,8 +178,9 @@ export default { // 即使消费节点都在 v-if="detail" 内,detail 置空瞬间(提交成功/登录回跳等)也会求值, // 必须做空值守卫,否则真机抛 Cannot read properties of null (reading 'json_data') 导致渲染中断 diyValue() { - // 新视觉默认样式:强制覆盖后端 form_config 中可能存在的旧默认(horizontal / 红按钮 / 灰背景等), - // 使表单在前端统一呈现为新设计稿样式;后台仍保留行为类配置(如 captcha / behavior / submitText)。 + // 新视觉默认样式:仅作兜底(后端 form_config 未配置的字段才生效)。 + // 注意合并顺序:detail.form_config 在后 —— 后台显式配置的值(submit_bg_color / input_radius / form_layout 等) + // 必须覆盖前端默认,否则商家端样式配置完全不生效(曾因此导致提交按钮恒为蓝色)。 const styleDefaults = { formLayout: 'vertical', formPaddingX: 28, @@ -213,7 +214,7 @@ export default { } return { jsonData: this.detail.json_data, - formConfig: Object.assign({}, this.detail.form_config || {}, styleDefaults), + formConfig: Object.assign({}, styleDefaults, this.detail.form_config || {}), // 全屏底图模式下隐藏卡片内 banner,避免与底图重复(见 hideInnerBanner) show_banner: this.hideInnerBanner ? 0 : this.detail.show_banner, form_banner: this.detail.form_banner || 'public/uniapp/form/banner.png', @@ -266,15 +267,10 @@ export default { return 'padding-left:' + this.containerInset + 'rpx;padding-right:' + this.containerInset + 'rpx;'; }, // 表单风格(与 diy-form.vue hasBannerMode 对齐):优先读显式 form_style,未设置时回退到原 bgImage 隐式判定 - // - card :顶部图条替代内联 banner → 隐藏内联 banner + // - card :卡片浮起需要 banner 作为被压对象 → 保留内联 banner(与后端预览一致);仅 bgImage 全屏底图时隐藏避免重复 // - classic:保留内联 banner(与原行为一致),仅当后台显式配 bgImage 才隐藏 // 老表单无 form_style 字段时 → undefined → 走原隐式判定,零迁移风险 - // 如需保留卡片内 banner:把下面这行改成 `return false;` 即可 hideInnerBanner() { - const fc = (this.detail && this.detail.form_config) || {}; - const style = fc.formStyle !== undefined ? fc.formStyle : fc.form_style; - if (style === 'card') return true; - if (style === 'classic') return !!this.bgImage; return !!this.bgImage; }, // 行为设置:提交验证码开启时 diy-form 隐藏内置提交按钮,本页渲染验证码 + 自有提交按钮