refactor(formdata.vue): update default styles and config merging logic

- Adjusted the merging order of form configuration to prioritize frontend defaults when backend configurations are absent.
- Clarified comments regarding the purpose of new visual styles and their application.
- Modified the hideInnerBanner logic to ensure consistent behavior with the updated form styles.
This commit is contained in:
2026-09-03 17:50:11 +08:00
parent b4c47fcc26
commit b2211edeab
6 changed files with 1186 additions and 691 deletions

View File

@@ -6,7 +6,7 @@
<view :class="['order-cell', { 'vertical-layout': mode === 'vertical' }]" :style="rowCellStyle">
<view class="name" :style="item._labelStyle">
<text class="required" :style="{ color: requiredColor }">{{ item.value.required ? '*' : '' }}</text>
<text class="tit">{{ item.value.title }}</text>
<text class="tit">{{ item.value.title }}</text><text v-if="requiredPosition === 'after'" class="required required-after" :style="{ color: requiredColor }">{{ item.value.required ? '*' : '' }}</text>
</view>
<view :class="['box', 'align-left']" :style="item._boxAlignStyle">
<input
@@ -24,7 +24,7 @@
<view :class="['order-cell', 'flex-box', 'textarea', { 'vertical-layout': mode === 'vertical' }]">
<view class="name" :style="item._labelStyle">
<text class="required" :style="{ color: requiredColor }">{{ item.value.required ? '*' : '' }}</text>
<text class="tit">{{ item.value.title }}</text>
<text class="tit">{{ item.value.title }}</text><text v-if="requiredPosition === 'after'" class="required required-after" :style="{ color: requiredColor }">{{ item.value.required ? '*' : '' }}</text>
</view>
<view :class="['box', 'align-left']" :style="item._boxAlignStyle">
<!-- 所有平台统一通过 formConfig.textareaRows / item.value.rows 控制显示行数:
@@ -52,7 +52,7 @@
<view :class="['order-cell', { 'vertical-layout': mode === 'vertical' }]">
<view class="name" :style="item._labelStyle">
<text class="required" :style="{ color: requiredColor }">{{ item.value.required ? '*' : '' }}</text>
<text class="tit">{{ item.value.title }}</text>
<text class="tit">{{ item.value.title }}</text><text v-if="requiredPosition === 'after'" class="required required-after" :style="{ color: requiredColor }">{{ item.value.required ? '*' : '' }}</text>
</view>
<view :class="['box', 'box-flex', 'align-right']">
<text v-if="item.val != ''">{{ item.val }}</text>
@@ -68,7 +68,7 @@
<view :class="['order-cell', { 'vertical-layout': mode === 'vertical' }]">
<view class="name" :style="item._labelStyle">
<text class="required" :style="{ color: requiredColor }">{{ item.value.required ? '*' : '' }}</text>
<text class="tit">{{ item.value.title }}</text>
<text class="tit">{{ item.value.title }}</text><text v-if="requiredPosition === 'after'" class="required required-after" :style="{ color: requiredColor }">{{ item.value.required ? '*' : '' }}</text>
</view>
<view :class="['box', 'check-group-box', 'align-left', 'option-layout-' + (item.value.option_layout || 'vertical')]">
<checkbox-group @change="checkboxChange($event, index)" :style="item._optionLayoutStyle">
@@ -97,7 +97,7 @@
<view :class="['order-cell', { 'vertical-layout': mode === 'vertical' }]">
<view class="name" :style="item._labelStyle">
<text class="required" :style="{ color: requiredColor }">{{ item.value.required ? '*' : '' }}</text>
<text class="tit">{{ item.value.title }}</text>
<text class="tit">{{ item.value.title }}</text><text v-if="requiredPosition === 'after'" class="required required-after" :style="{ color: requiredColor }">{{ item.value.required ? '*' : '' }}</text>
</view>
<view :class="['box', 'radio-group-box', 'align-left', 'option-layout-' + (item.value.option_layout || 'vertical')]">
<radio-group @change="radioChange($event, index)" :style="item._optionLayoutStyle">
@@ -126,7 +126,7 @@
<view :class="['order-cell', 'flex-box', { 'vertical-layout': mode === 'vertical' }]">
<view class="name" :style="item._labelStyle">
<text class="required" :style="{ color: requiredColor }">{{ item.value.required ? '*' : '' }}</text>
<text class="tit">{{ item.value.title }}</text>
<text class="tit">{{ item.value.title }}</text><text v-if="requiredPosition === 'after'" class="required required-after" :style="{ color: requiredColor }">{{ item.value.required ? '*' : '' }}</text>
</view>
<view :class="['box', 'img-boxs', 'align-left']" :style="item._boxAlignStyle">
<view v-for="(v, k) in item.img_lists" :key="k" class="img-box" @tap="uploadImg(index)">
@@ -145,7 +145,7 @@
<view :class="['order-cell', { 'vertical-layout': mode === 'vertical' }]">
<view class="name" :style="item._labelStyle">
<text class="required" :style="{ color: requiredColor }">{{ item.value.required ? '*' : '' }}</text>
<text class="tit">{{ item.value.title }}</text>
<text class="tit">{{ item.value.title }}</text><text v-if="requiredPosition === 'after'" class="required required-after" :style="{ color: requiredColor }">{{ item.value.required ? '*' : '' }}</text>
</view>
<view :class="['box', 'box-flex', 'align-right']">
<picker
@@ -167,7 +167,7 @@
<view :class="['order-cell', 'flex-box', { 'vertical-layout': mode === 'vertical' }]">
<view class="name" :style="item._labelStyle">
<text class="required" :style="{ color: requiredColor }">{{ item.value.required ? '*' : '' }}</text>
<text class="tit">{{ item.value.title }}</text>
<text class="tit">{{ item.value.title }}</text><text v-if="requiredPosition === 'after'" class="required required-after" :style="{ color: requiredColor }">{{ item.value.required ? '*' : '' }}</text>
</view>
<view :class="['box', 'date-boxs', 'align-center']" :style="item._boxAlignStyle">
<view class="date-box">
@@ -206,7 +206,7 @@
<view :class="['order-cell', { 'vertical-layout': mode === 'vertical' }]">
<view class="name" :style="item._labelStyle">
<text class="required" :style="{ color: requiredColor }">{{ item.value.required ? '*' : '' }}</text>
<text class="tit">{{ item.value.title }}</text>
<text class="tit">{{ item.value.title }}</text><text v-if="requiredPosition === 'after'" class="required required-after" :style="{ color: requiredColor }">{{ item.value.required ? '*' : '' }}</text>
</view>
<view :class="['box', 'box-flex', 'align-right']">
<picker
@@ -228,7 +228,7 @@
<view :class="['order-cell', 'flex-box', { 'vertical-layout': mode === 'vertical' }]">
<view class="name" :style="item._labelStyle">
<text class="required" :style="{ color: requiredColor }">{{ item.value.required ? '*' : '' }}</text>
<text class="tit">{{ item.value.title }}</text>
<text class="tit">{{ item.value.title }}</text><text v-if="requiredPosition === 'after'" class="required required-after" :style="{ color: requiredColor }">{{ item.value.required ? '*' : '' }}</text>
</view>
<view :class="['box', 'date-boxs', 'align-center']" :style="item._boxAlignStyle">
<view class="date-box">
@@ -267,7 +267,7 @@
<view :class="['order-cell', { 'vertical-layout': mode === 'vertical' }]">
<view class="name" :style="item._labelStyle">
<text class="required" :style="{ color: requiredColor }">{{ item.value.required ? '*' : '' }}</text>
<text class="tit">{{ item.value.title }}</text>
<text class="tit">{{ item.value.title }}</text><text v-if="requiredPosition === 'after'" class="required required-after" :style="{ color: requiredColor }">{{ item.value.required ? '*' : '' }}</text>
</view>
<view :class="['box', 'box-flex', 'align-right']">
<pick-regions
@@ -283,6 +283,29 @@
</view>
</view>
</view>
<!-- 信息卡片(展示型组件,与 addon/form 新版 INFO_CARD 对齐)
- 无标签/必填/校验/存储/导出,verify() 跳过
- 内容存 value.content(富文本 HTML),rich-text 跨端渲染
- 容器底色 value.bg_color;底部分割线 show_divider + divider_* 五参数
- 老数据兼容:content 为空但存在 title/items 时按旧结构渲染 -->
<view v-if="item.controller == 'InfoCard'" class="info-card-wrap" :style="item._infoCardWrapStyle">
<rich-text v-if="item._infoCardHasContent" class="info-card-content" :nodes="item.value.content"></rich-text>
<template v-else-if="item.value.title || (item.value.items && item.value.items.length)">
<view v-if="item.value.title" class="info-card-title" :style="{ fontSize: ((item.value.title_font_size || 36) / 2) + 'px', color: item.value.title_color || '#1f2937', fontWeight: 700 }">
{{ item.value.title }}
</view>
<view v-if="item.value.items && item.value.items.length" class="info-card-items">
<view
v-for="(it, iidx) in item.value.items"
:key="iidx"
class="info-card-item"
:style="{ fontSize: ((item.value.item_font_size || 26) / 2) + 'px', color: item.value.item_color || '#6b7280' }"
>{{ it }}</view>
</view>
</template>
<view v-if="item.value.show_divider == 1" class="info-card-divider" :style="item._infoCardDividerStyle"></view>
</view>
</view>
</view>
</template>
@@ -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);
// 空白判定:去标签 + &nbsp; + 去空白后为空 → 不渲染富文本(与后台一致,避免空白块)
const raw = itemValue.content == null ? '' : String(itemValue.content);
const textOnly = raw.replace(/<[^>]+>/g, '').replace(/&nbsp;/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;
}
}
}
</style>
<style lang="scss" scoped>