refactor(ns-newform): 优化表单样式计算与渲染逻辑
1. 移除模板内动态调用的样式方法,改用预计算的响应式样式属性 2. 重构行分割线实现,改用border-bottom替代伪元素提升兼容性 3. 新增兜底样式解决小程序占位符样式失效问题 4. 统一表单样式的计算时机与响应式更新逻辑
This commit is contained in:
@@ -4,11 +4,11 @@
|
||||
<!-- 文本输入框 -->
|
||||
<view v-if="item.controller == 'Text'" class="order-wrap" :class="rowWrapClass" :style="rowWrapStyle">
|
||||
<view :class="['order-cell', { 'vertical-layout': mode === 'vertical' }]" :style="rowCellStyle">
|
||||
<view class="name" :style="[nameWidthStyle, getItemLabelStyle(item), nameStyle]">
|
||||
<view class="name" :style="item._labelStyle">
|
||||
<text class="tit">{{ item.value.title }}</text>
|
||||
<text class="required" :style="{ color: requiredColor }">{{ item.value.required ? '*' : '' }}</text>
|
||||
</view>
|
||||
<view :class="['box', 'align-left']" :style="getItemBoxAlignStyle(item)">
|
||||
<view :class="['box', 'align-left']" :style="item._boxAlignStyle">
|
||||
<input
|
||||
type="text"
|
||||
:placeholder="item.value.placeholder || '请输入提示语'"
|
||||
@@ -22,11 +22,11 @@
|
||||
<!-- 多行文本输入框 -->
|
||||
<view v-if="item.controller == 'Textarea'" class="order-wrap" :class="rowWrapClass" :style="rowWrapStyle">
|
||||
<view :class="['order-cell', 'flex-box', 'textarea', { 'vertical-layout': mode === 'vertical' }]">
|
||||
<view class="name" :style="[nameWidthStyle, getItemLabelStyle(item), nameStyle]">
|
||||
<view class="name" :style="item._labelStyle">
|
||||
<text class="tit">{{ item.value.title }}</text>
|
||||
<text class="required" :style="{ color: requiredColor }">{{ item.value.required ? '*' : '' }}</text>
|
||||
</view>
|
||||
<view :class="['box', 'align-left']" :style="getItemBoxAlignStyle(item)">
|
||||
<view :class="['box', 'align-left']" :style="item._boxAlignStyle">
|
||||
<textarea
|
||||
:placeholder="item.value.placeholder || '请输入提示语'"
|
||||
placeholder-class="placeholder color-tip"
|
||||
@@ -44,7 +44,7 @@
|
||||
@change="pickerChange($event, index)"
|
||||
>
|
||||
<view :class="['order-cell', { 'vertical-layout': mode === 'vertical' }]">
|
||||
<view class="name" :style="[nameWidthStyle, getItemLabelStyle(item), nameStyle]">
|
||||
<view class="name" :style="item._labelStyle">
|
||||
<text class="tit">{{ item.value.title }}</text>
|
||||
<text class="required" :style="{ color: requiredColor }">{{ item.value.required ? '*' : '' }}</text>
|
||||
</view>
|
||||
@@ -60,7 +60,7 @@
|
||||
<!-- 复选框 -->
|
||||
<view v-if="item.controller == 'Checkbox'" class="order-wrap" :class="rowWrapClass" :style="rowWrapStyle">
|
||||
<view :class="['order-cell', { 'vertical-layout': mode === 'vertical' }]">
|
||||
<view class="name" :style="[nameWidthStyle, getItemLabelStyle(item), nameStyle]">
|
||||
<view class="name" :style="item._labelStyle">
|
||||
<text class="tit">{{ item.value.title }}</text>
|
||||
<text class="required" :style="{ color: requiredColor }">{{ item.value.required ? '*' : '' }}</text>
|
||||
</view>
|
||||
@@ -88,7 +88,7 @@
|
||||
<!-- 单选框 -->
|
||||
<view v-if="item.controller == 'Radio'" class="order-wrap" :class="rowWrapClass" :style="rowWrapStyle">
|
||||
<view :class="['order-cell', { 'vertical-layout': mode === 'vertical' }]">
|
||||
<view class="name" :style="[nameWidthStyle, getItemLabelStyle(item), nameStyle]">
|
||||
<view class="name" :style="item._labelStyle">
|
||||
<text class="tit">{{ item.value.title }}</text>
|
||||
<text class="required" :style="{ color: requiredColor }">{{ item.value.required ? '*' : '' }}</text>
|
||||
</view>
|
||||
@@ -116,11 +116,11 @@
|
||||
<!-- 图片上传 -->
|
||||
<view v-if="item.controller == 'Img'" class="order-wrap" :class="rowWrapClass" :style="rowWrapStyle">
|
||||
<view :class="['order-cell', 'flex-box', { 'vertical-layout': mode === 'vertical' }]">
|
||||
<view class="name" :style="[nameWidthStyle, getItemLabelStyle(item), nameStyle]">
|
||||
<view class="name" :style="item._labelStyle">
|
||||
<text class="tit">{{ item.value.title }}</text>
|
||||
<text class="required" :style="{ color: requiredColor }">{{ item.value.required ? '*' : '' }}</text>
|
||||
</view>
|
||||
<view :class="['box', 'img-boxs', 'align-left']" :style="getItemBoxAlignStyle(item)">
|
||||
<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)">
|
||||
<image :src="$util.img(v)" mode="aspectFill" show-menu-by-longpress></image>
|
||||
<text class="iconfont icon-guanbi" @tap.stop="delImg(k, index)"></text>
|
||||
@@ -135,7 +135,7 @@
|
||||
<!-- 日期选择器 -->
|
||||
<view v-if="item.controller == 'Date'" class="order-wrap" :class="rowWrapClass" :style="rowWrapStyle">
|
||||
<view :class="['order-cell', { 'vertical-layout': mode === 'vertical' }]">
|
||||
<view class="name" :style="[nameWidthStyle, getItemLabelStyle(item), nameStyle]">
|
||||
<view class="name" :style="item._labelStyle">
|
||||
<text class="tit">{{ item.value.title }}</text>
|
||||
<text class="required" :style="{ color: requiredColor }">{{ item.value.required ? '*' : '' }}</text>
|
||||
</view>
|
||||
@@ -157,11 +157,11 @@
|
||||
<!-- 日期范围选择器 -->
|
||||
<view v-if="item.controller == 'Datelimit'" class="order-wrap" :class="rowWrapClass" :style="rowWrapStyle">
|
||||
<view :class="['order-cell', 'flex-box', { 'vertical-layout': mode === 'vertical' }]">
|
||||
<view class="name" :style="[nameWidthStyle, getItemLabelStyle(item), nameStyle]">
|
||||
<view class="name" :style="item._labelStyle">
|
||||
<text class="tit">{{ item.value.title }}</text>
|
||||
<text class="required" :style="{ color: requiredColor }">{{ item.value.required ? '*' : '' }}</text>
|
||||
</view>
|
||||
<view :class="['box', 'date-boxs', 'align-center']" :style="getItemBoxAlignStyle(item)">
|
||||
<view :class="['box', 'date-boxs', 'align-center']" :style="item._boxAlignStyle">
|
||||
<view class="date-box">
|
||||
<picker
|
||||
mode="date"
|
||||
@@ -196,7 +196,7 @@
|
||||
<!-- 时间选择器 -->
|
||||
<view v-if="item.controller == 'Time'" class="order-wrap" :class="rowWrapClass" :style="rowWrapStyle">
|
||||
<view :class="['order-cell', { 'vertical-layout': mode === 'vertical' }]">
|
||||
<view class="name" :style="[nameWidthStyle, getItemLabelStyle(item), nameStyle]">
|
||||
<view class="name" :style="item._labelStyle">
|
||||
<text class="tit">{{ item.value.title }}</text>
|
||||
<text class="required" :style="{ color: requiredColor }">{{ item.value.required ? '*' : '' }}</text>
|
||||
</view>
|
||||
@@ -218,11 +218,11 @@
|
||||
<!-- 时间范围选择器 -->
|
||||
<view v-if="item.controller == 'Timelimit'" class="order-wrap" :class="rowWrapClass" :style="rowWrapStyle">
|
||||
<view :class="['order-cell', 'flex-box', { 'vertical-layout': mode === 'vertical' }]">
|
||||
<view class="name" :style="[nameWidthStyle, getItemLabelStyle(item), nameStyle]">
|
||||
<view class="name" :style="item._labelStyle">
|
||||
<text class="tit">{{ item.value.title }}</text>
|
||||
<text class="required" :style="{ color: requiredColor }">{{ item.value.required ? '*' : '' }}</text>
|
||||
</view>
|
||||
<view :class="['box', 'date-boxs', 'align-center']" :style="getItemBoxAlignStyle(item)">
|
||||
<view :class="['box', 'date-boxs', 'align-center']" :style="item._boxAlignStyle">
|
||||
<view class="date-box">
|
||||
<picker
|
||||
mode="time"
|
||||
@@ -257,7 +257,7 @@
|
||||
<!-- 城市选择器 -->
|
||||
<view v-if="item.controller == 'City'" class="order-wrap" :class="rowWrapClass" :style="rowWrapStyle">
|
||||
<view :class="['order-cell', 'box-flex', { 'vertical-layout': mode === 'vertical' }]">
|
||||
<view class="name" :style="[nameWidthStyle, getItemLabelStyle(item), nameStyle]">
|
||||
<view class="name" :style="item._labelStyle">
|
||||
<text class="tit">{{ item.value.title }}</text>
|
||||
<text class="required" :style="{ color: requiredColor }">{{ item.value.required ? '*' : '' }}</text>
|
||||
</view>
|
||||
@@ -571,26 +571,39 @@ export default {
|
||||
}
|
||||
return v;
|
||||
},
|
||||
// 组件级 label 样式:通过 CSS 变量覆盖,驱动子元素 .tit / .required
|
||||
getItemLabelStyle(item) {
|
||||
const override = (item && item.value) || {};
|
||||
const style = {};
|
||||
if (override.label_font_size !== undefined && override.label_font_size !== null && override.label_font_size !== '') {
|
||||
let fs = override.label_font_size;
|
||||
if (typeof fs === 'number') fs = `${fs}rpx`;
|
||||
else if (typeof fs === 'string' && !fs.endsWith('rpx') && !fs.endsWith('px')) fs = `${fs}rpx`;
|
||||
style['--item-label-font-size'] = fs;
|
||||
}
|
||||
if (override.label_color) {
|
||||
style['--item-label-color'] = override.label_color;
|
||||
}
|
||||
return style;
|
||||
},
|
||||
// 组件级输入框对齐方式:left / center / right
|
||||
getItemBoxAlignStyle(item) {
|
||||
const align = item && item.value && item.value.input_align;
|
||||
if (!align) return {};
|
||||
return { 'text-align': align };
|
||||
// 预计算每项的样式(写到 item._labelStyle / item._boxAlignStyle),
|
||||
// 避免在模板里以方法形式调用:微信小程序 WXML 不支持 :style="getXxx(item)" 语法。
|
||||
// 触发时机:processFormData 末尾 / calculateMaxLabelWidth 末尾 / config 变化 / mode 变化。
|
||||
recomputeItemStyles() {
|
||||
if (!this.formData || this.formData.length === 0) return;
|
||||
|
||||
const widthStyle = this.nameWidthStyle || {};
|
||||
const nameMarginStyle = this.nameStyle || {};
|
||||
|
||||
this.formData.forEach((item) => {
|
||||
const itemValue = (item && item.value) || {};
|
||||
const style = Object.assign({}, widthStyle);
|
||||
|
||||
// item 级别 label 字号 / 颜色覆盖(与原 getItemLabelStyle 逻辑一致)
|
||||
if (itemValue.label_font_size !== undefined && itemValue.label_font_size !== null && itemValue.label_font_size !== '') {
|
||||
let fs = itemValue.label_font_size;
|
||||
if (typeof fs === 'number') fs = `${fs}rpx`;
|
||||
else if (typeof fs === 'string' && !fs.endsWith('rpx') && !fs.endsWith('px')) fs = `${fs}rpx`;
|
||||
style['--item-label-font-size'] = fs;
|
||||
}
|
||||
if (itemValue.label_color) {
|
||||
style['--item-label-color'] = itemValue.label_color;
|
||||
}
|
||||
// 合并全局的 nameStyle(margin-right)
|
||||
Object.assign(style, nameMarginStyle);
|
||||
|
||||
// $set 包裹以确保新属性是响应式的(Vue 2 对动态加的属性需要 $set)
|
||||
this.$set(item, '_labelStyle', style);
|
||||
|
||||
// item 级别 box 对齐方式(与原 getItemBoxAlignStyle 逻辑一致)
|
||||
const align = itemValue.input_align;
|
||||
this.$set(item, '_boxAlignStyle', align ? { 'text-align': align } : {});
|
||||
});
|
||||
},
|
||||
// 容器样式(提供给外部 diy-form 使用)
|
||||
getFormWrapStyle() {
|
||||
@@ -615,6 +628,8 @@ export default {
|
||||
// 与后端公式一致:chars * (fontSize/2) * 1.1 + 12,单位 px → 转 rpx(×2)
|
||||
const widthPx = Math.max(40, maxChars * (fontSize / 2) * 1.1 + 12);
|
||||
this.maxLabelWidth = Math.round(widthPx * 2);
|
||||
// 宽度变了,需要把全局 nameWidthStyle 重新合到每项 _labelStyle 里
|
||||
this.recomputeItemStyles();
|
||||
},
|
||||
|
||||
initFormData() {
|
||||
@@ -759,8 +774,11 @@ export default {
|
||||
}
|
||||
}
|
||||
});
|
||||
// 数据处理完后再算每项的 _labelStyle / _boxAlignStyle(依赖 nameWidthStyle,而 maxLabelWidth 此时可能还没算)
|
||||
// 这里先算一次;calculateMaxLabelWidth 完成后会再算一次(带正确的 maxLabelWidth)
|
||||
this.recomputeItemStyles();
|
||||
},
|
||||
|
||||
|
||||
verify() {
|
||||
for (let i = 0; i < this.formData.length; i++) {
|
||||
let item = this.formData[i];
|
||||
@@ -1011,8 +1029,8 @@ export default {
|
||||
|
||||
// 与后端 form-preview-wrap.no-divider-all 对齐:整组隐藏所有行分割线
|
||||
&.no-divider-all {
|
||||
.order-wrap::after {
|
||||
display: none !important;
|
||||
.order-wrap {
|
||||
border-bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1022,31 +1040,18 @@ export default {
|
||||
transition: all 0.3s ease;
|
||||
padding: 0;
|
||||
background: var(--form-bg-color);
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
height: 1rpx;
|
||||
background: var(--row-divider-color, #f0f0f0);
|
||||
}
|
||||
box-sizing: border-box;
|
||||
// 行分割线:与后端 design.css .form-preview-wrap .form-gorup 对齐
|
||||
// 用 border-bottom 而非 ::after 伪元素(亚像素不可见 + 多端兼容性差)
|
||||
border-bottom: 2rpx solid var(--row-divider-color, #f0f0f0);
|
||||
|
||||
&:last-child {
|
||||
&::after {
|
||||
display: none;
|
||||
}
|
||||
border-bottom: 0;
|
||||
}
|
||||
|
||||
// 后端 show_row_divider = 0:隐藏所有行分割线
|
||||
&.no-divider {
|
||||
&::after {
|
||||
display: none !important;
|
||||
}
|
||||
&:last-child::after {
|
||||
display: none !important;
|
||||
}
|
||||
border-bottom: 0;
|
||||
}
|
||||
|
||||
.order-cell {
|
||||
@@ -1383,4 +1388,22 @@ export default {
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<!--
|
||||
非 scoped 兜底样式:
|
||||
1) .placeholder / .color-tip 走的是 mp/小程序原生的占位符元素(placeholder-class),
|
||||
scoped 编译后的 [data-v-xxx] 属性选择器匹配不到,必须在全局作用域定义。
|
||||
2) 与 common/css/main.scss 的 .color-tip 协同,确保占位符在 H5 / mp / 鸿蒙 各端都可显;
|
||||
若页面未引入 main.scss,本兜底也能保证 Select/Date/Time 等 .color-tip 提示文字可见。
|
||||
-->
|
||||
<style lang="scss">
|
||||
.placeholder {
|
||||
color: #909399;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
.color-tip {
|
||||
color: #909399;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user