chore:表单样式更新

This commit is contained in:
2026-08-28 11:58:23 +08:00
parent e693e4273c
commit ecb0789780
3 changed files with 186 additions and 105 deletions

View File

@@ -237,9 +237,33 @@ export default {
delete base[k]; delete base[k];
} }
}); });
// 前端视觉默认:强制覆盖后端 formConfig 中可能存在的旧默认(horizontal / 红按钮 / 灰背景等),
// 与 formdata.vue 的 styleDefaults 保持一致,使所有 diy-form(含 diy-group 内的表单)统一呈现新设计稿样式。
// 后端仍保留行为/文案类配置(behavior / submitText / banner / tip 等),本合并仅覆盖视觉字段。
const styleDefaults = {
formLayout: 'vertical',
formPaddingX: 28,
rowPaddingY: 24,
labelGap: 20,
labelFontSize: 32,
labelFontWeight: 700,
labelColor: '#1f2937',
inputHeight: 84,
inputRadius: 16,
inputBgColor: '#ffffff',
inputBorderColor: '#e5e7eb',
inputFocusBorderColor: '#3b82f6',
inputFocusBgColor: '#ffffff',
placeholderColor: '#9ca3af',
placeholderFontSize: 28,
checkedColor: '#3b82f6',
showRowDivider: false,
submitColor: '#ffffff',
submitBgColor: '#3b82f6'
};
// 平台差异化配置(terminal 块在 formConfig 子对象内) // 平台差异化配置(terminal 块在 formConfig 子对象内)
// 优先级:v.formConfig.terminal > v.form_config_terminal > v.terminal > v.form_config_platform // 优先级:v.formConfig.terminal > v.form_config_terminal > v.terminal > v.form_config_platform
const result = Object.assign({}, base); const result = Object.assign({}, base, styleDefaults);
const termBlock = (v.formConfig && v.formConfig.terminal) || v.form_config_terminal || v.terminal || v.form_config_platform; const termBlock = (v.formConfig && v.formConfig.terminal) || v.form_config_terminal || v.terminal || v.form_config_platform;
if (termBlock && typeof termBlock === 'object') { if (termBlock && typeof termBlock === 'object') {
Object.keys(termBlock).forEach(function(platform) { Object.keys(termBlock).forEach(function(platform) {

View File

@@ -4,8 +4,8 @@
<view class="order-wrap" :class="rowWrapClass" :style="rowWrapStyle" v-if="item.controller == 'Text'"> <view class="order-wrap" :class="rowWrapClass" :style="rowWrapStyle" v-if="item.controller == 'Text'">
<view class="order-cell" :style="rowCellStyle"> <view class="order-cell" :style="rowCellStyle">
<view class="name" :style="nameWidthStyle"> <view class="name" :style="nameWidthStyle">
<text class="tit">{{ item.value.title }}</text>
<text class="required" :style="{ color: requiredColor }">{{ item.value.required ? '*' : '' }}</text> <text class="required" :style="{ color: requiredColor }">{{ item.value.required ? '*' : '' }}</text>
<text class="tit">{{ item.value.title }}</text>
</view> </view>
<view class="box"> <view class="box">
<input <input
@@ -22,8 +22,8 @@
<view class="order-wrap" :class="rowWrapClass" :style="rowWrapStyle" v-if="item.controller == 'Textarea'"> <view class="order-wrap" :class="rowWrapClass" :style="rowWrapStyle" v-if="item.controller == 'Textarea'">
<view class="order-cell flex-box textarea" :style="rowCellStyle"> <view class="order-cell flex-box textarea" :style="rowCellStyle">
<view class="name" :style="nameWidthStyle"> <view class="name" :style="nameWidthStyle">
<text class="tit">{{ item.value.title }}</text>
<text class="required" :style="{ color: requiredColor }">{{ item.value.required ? '*' : '' }}</text> <text class="required" :style="{ color: requiredColor }">{{ item.value.required ? '*' : '' }}</text>
<text class="tit">{{ item.value.title }}</text>
</view> </view>
<view class="box"> <view class="box">
<!-- 所有平台统一通过 formConfig.textareaRows / item.value.rows 控制显示行数: <!-- 所有平台统一通过 formConfig.textareaRows / item.value.rows 控制显示行数:
@@ -48,20 +48,20 @@
<text class="tit">{{ item.value.title }}</text> <text class="tit">{{ item.value.title }}</text>
<text class="required" :style="{ color: requiredColor }">{{ item.value.required ? '*' : '' }}</text> <text class="required" :style="{ color: requiredColor }">{{ item.value.required ? '*' : '' }}</text>
</view> </view>
<view class="box"> <view :class="['box', 'box-flex']">
<text v-if="item.val != ''">{{ item.val }}</text> <text v-if="item.val != ''">{{ item.val }}</text>
<text v-else class="color-tip">{{ $lang('components.nsForm.selectPlaceholder') }}</text> <text v-else class="color-tip">{{ $lang('components.nsForm.selectPlaceholder') }}</text>
</view>
<text class="iconfont icon-right"></text> <text class="iconfont icon-right"></text>
</view> </view>
</view> </view>
</view>
</picker> </picker>
<view class="order-wrap" :class="rowWrapClass" :style="rowWrapStyle" v-if="item.controller == 'Checkbox'"> <view class="order-wrap" :class="rowWrapClass" :style="rowWrapStyle" v-if="item.controller == 'Checkbox'">
<view class="order-cell" :style="rowCellStyle"> <view class="order-cell" :style="rowCellStyle">
<view class="name" :style="nameWidthStyle"> <view class="name" :style="nameWidthStyle">
<text class="tit">{{ item.value.title }}</text>
<text class="required" :style="{ color: requiredColor }">{{ item.value.required ? '*' : '' }}</text> <text class="required" :style="{ color: requiredColor }">{{ item.value.required ? '*' : '' }}</text>
<text class="tit">{{ item.value.title }}</text>
</view> </view>
<view :class="['box', 'check-group-box', 'option-layout-' + (item.value.option_layout || 'wrap')]"> <view :class="['box', 'check-group-box', 'option-layout-' + (item.value.option_layout || 'wrap')]">
<checkbox-group @change="checkboxChange($event, index)" :style="item._optionLayoutStyle"> <checkbox-group @change="checkboxChange($event, index)" :style="item._optionLayoutStyle">
@@ -80,8 +80,8 @@
<view class="order-wrap" :class="rowWrapClass" :style="rowWrapStyle" v-if="item.controller == 'Radio'"> <view class="order-wrap" :class="rowWrapClass" :style="rowWrapStyle" v-if="item.controller == 'Radio'">
<view class="order-cell" :style="rowCellStyle"> <view class="order-cell" :style="rowCellStyle">
<view class="name" :style="nameWidthStyle"> <view class="name" :style="nameWidthStyle">
<text class="tit">{{ item.value.title }}</text>
<text class="required" :style="{ color: requiredColor }">{{ item.value.required ? '*' : '' }}</text> <text class="required" :style="{ color: requiredColor }">{{ item.value.required ? '*' : '' }}</text>
<text class="tit">{{ item.value.title }}</text>
</view> </view>
<view :class="['box', 'radio-group-box', 'option-layout-' + (item.value.option_layout || 'wrap')]"> <view :class="['box', 'radio-group-box', 'option-layout-' + (item.value.option_layout || 'wrap')]">
<radio-group @change="radioChange($event, index)" :style="item._optionLayoutStyle"> <radio-group @change="radioChange($event, index)" :style="item._optionLayoutStyle">
@@ -100,8 +100,8 @@
<view class="order-wrap" :class="rowWrapClass" :style="rowWrapStyle" v-if="item.controller == 'Img'"> <view class="order-wrap" :class="rowWrapClass" :style="rowWrapStyle" v-if="item.controller == 'Img'">
<view class="order-cell flex-box" :style="rowCellStyle"> <view class="order-cell flex-box" :style="rowCellStyle">
<view class="name" :style="nameWidthStyle"> <view class="name" :style="nameWidthStyle">
<text class="tit">{{ item.value.title }}</text>
<text class="required" :style="{ color: requiredColor }">{{ item.value.required ? '*' : '' }}</text> <text class="required" :style="{ color: requiredColor }">{{ item.value.required ? '*' : '' }}</text>
<text class="tit">{{ item.value.title }}</text>
</view> </view>
<view class="box img-boxs"> <view class="box img-boxs">
<view class="img-box" v-for="(v, k) in item.img_lists" :key="k" @click="uploadImg(index)"> <view class="img-box" v-for="(v, k) in item.img_lists" :key="k" @click="uploadImg(index)">
@@ -116,23 +116,23 @@
<view class="order-wrap" :class="rowWrapClass" :style="rowWrapStyle" v-if="item.controller == 'Date'"> <view class="order-wrap" :class="rowWrapClass" :style="rowWrapStyle" v-if="item.controller == 'Date'">
<view class="order-cell" :style="rowCellStyle"> <view class="order-cell" :style="rowCellStyle">
<view class="name" :style="nameWidthStyle"> <view class="name" :style="nameWidthStyle">
<text class="tit">{{ item.value.title }}</text>
<text class="required" :style="{ color: requiredColor }">{{ item.value.required ? '*' : '' }}</text> <text class="required" :style="{ color: requiredColor }">{{ item.value.required ? '*' : '' }}</text>
<text class="tit">{{ item.value.title }}</text>
</view> </view>
<view class="box box-flex"> <view :class="['box', 'box-flex']">
<picker mode="date" :value="item.val" @change="bindDateChange($event, index)"> <picker mode="date" :value="item.val" @change="bindDateChange($event, index)">
<view class="uni-input" :class="{ 'color-tip': !item.val }">{{ item.val ? item.val : item.value.placeholder }}</view> <view class="uni-input" :class="{ 'color-tip': !item.val }">{{ item.val ? item.val : item.value.placeholder }}</view>
</picker> </picker>
</view>
<text class="iconfont icon-right"></text> <text class="iconfont icon-right"></text>
</view> </view>
</view> </view>
</view>
<view class="order-wrap" :class="rowWrapClass" :style="rowWrapStyle" v-if="item.controller == 'Datelimit'"> <view class="order-wrap" :class="rowWrapClass" :style="rowWrapStyle" v-if="item.controller == 'Datelimit'">
<view class="order-cell flex-box" :style="rowCellStyle"> <view class="order-cell flex-box" :style="rowCellStyle">
<view class="name" :style="nameWidthStyle"> <view class="name" :style="nameWidthStyle">
<text class="tit">{{ item.value.title }}</text>
<text class="required" :style="{ color: requiredColor }">{{ item.value.required ? '*' : '' }}</text> <text class="required" :style="{ color: requiredColor }">{{ item.value.required ? '*' : '' }}</text>
<text class="tit">{{ item.value.title }}</text>
</view> </view>
<view class="box date-boxs"> <view class="box date-boxs">
<view class="date-box"> <view class="date-box">
@@ -157,23 +157,23 @@
<view class="order-wrap" :class="rowWrapClass" :style="rowWrapStyle" v-if="item.controller == 'Time'"> <view class="order-wrap" :class="rowWrapClass" :style="rowWrapStyle" v-if="item.controller == 'Time'">
<view class="order-cell" :style="rowCellStyle"> <view class="order-cell" :style="rowCellStyle">
<view class="name" :style="nameWidthStyle"> <view class="name" :style="nameWidthStyle">
<text class="tit">{{ item.value.title }}</text>
<text class="required" :style="{ color: requiredColor }">{{ item.value.required ? '*' : '' }}</text> <text class="required" :style="{ color: requiredColor }">{{ item.value.required ? '*' : '' }}</text>
<text class="tit">{{ item.value.title }}</text>
</view> </view>
<view class="box box-flex"> <view :class="['box', 'box-flex']">
<picker mode="time" :value="item.val" @change="bindTimeChange($event, index)"> <picker mode="time" :value="item.val" @change="bindTimeChange($event, index)">
<view class="uni-input" :class="{ 'color-tip': !item.val }">{{ item.val ? item.val : item.value.placeholder }}</view> <view class="uni-input" :class="{ 'color-tip': !item.val }">{{ item.val ? item.val : item.value.placeholder }}</view>
</picker> </picker>
</view>
<text class="iconfont icon-right"></text> <text class="iconfont icon-right"></text>
</view> </view>
</view> </view>
</view>
<view class="order-wrap" :class="rowWrapClass" :style="rowWrapStyle" v-if="item.controller == 'Timelimit'"> <view class="order-wrap" :class="rowWrapClass" :style="rowWrapStyle" v-if="item.controller == 'Timelimit'">
<view class="order-cell flex-box" :style="rowCellStyle"> <view class="order-cell flex-box" :style="rowCellStyle">
<view class="name" :style="nameWidthStyle"> <view class="name" :style="nameWidthStyle">
<text class="tit">{{ item.value.title }}</text>
<text class="required" :style="{ color: requiredColor }">{{ item.value.required ? '*' : '' }}</text> <text class="required" :style="{ color: requiredColor }">{{ item.value.required ? '*' : '' }}</text>
<text class="tit">{{ item.value.title }}</text>
</view> </view>
<view class="box date-boxs"> <view class="box date-boxs">
<view class="date-box"> <view class="date-box">
@@ -196,21 +196,21 @@
</view> </view>
<view class="order-wrap" :class="rowWrapClass" :style="rowWrapStyle" v-if="item.controller == 'City'"> <view class="order-wrap" :class="rowWrapClass" :style="rowWrapStyle" v-if="item.controller == 'City'">
<view class="order-cell box-flex" :style="rowCellStyle"> <view class="order-cell" :style="rowCellStyle">
<view class="name" :style="nameWidthStyle"> <view class="name" :style="nameWidthStyle">
<text class="tit">{{ item.value.title }}</text>
<text class="required" :style="{ color: requiredColor }">{{ item.value.required ? '*' : '' }}</text> <text class="required" :style="{ color: requiredColor }">{{ item.value.required ? '*' : '' }}</text>
<text class="tit">{{ item.value.title }}</text>
</view> </view>
<view class="box"> <view :class="['box', 'box-flex']">
<pick-regions :default-regions="item.default_regions" :select-arr="item.select_arr" @getRegions="handleGetRegions($event, index)"> <pick-regions :default-regions="item.default_regions" :select-arr="item.select_arr" @getRegions="handleGetRegions($event, index)">
<view class="select-address " :class="{ empty: !item.val, 'color-tip': !item.val }"> <view class="select-address " :class="{ empty: !item.val, 'color-tip': !item.val }">
{{ item.val ? item.val : (item.select_arr == '2' ? $lang('components.nsForm.selectProvinceCity') : $lang('components.nsForm.selectProvinceCityDistrict')) }} {{ item.val ? item.val : (item.select_arr == '2' ? $lang('components.nsForm.selectProvinceCity') : $lang('components.nsForm.selectProvinceCityDistrict')) }}
</view> </view>
</pick-regions> </pick-regions>
</view>
<text class="iconfont icon-right"></text> <text class="iconfont icon-right"></text>
</view> </view>
</view> </view>
</view>
</block> </block>
</view> </view>
</template> </template>
@@ -263,22 +263,22 @@
config() { config() {
// 1) 内置默认值(最低优先级,与重构前的硬编码值一一对应,确保未传 formConfig 时零回归) // 1) 内置默认值(最低优先级,与重构前的硬编码值一一对应,确保未传 formConfig 时零回归)
const defaults = { const defaults = {
formPaddingX: 0, formPaddingX: 28,
formBgColor: '#ffffff', formBgColor: '#ffffff',
rowPaddingY: 20, rowPaddingY: 24,
rowMinHeight: 60, rowMinHeight: 60,
labelGap: 16, labelGap: 20,
inputHeight: 60, inputHeight: 84,
inputRadius: 0, inputRadius: 16,
labelWidthMode: 'fixed', // 旧版 width: 80vw 用 fixed 180rpx 等价替代 labelWidthMode: 'auto', // 与 ns-newform 对齐:标签在上、输入框在下,整行宽度
labelFixedWidth: 180, labelFixedWidth: 180,
labelFontSize: 28, labelFontSize: 32,
labelColor: '#1f2937', labelColor: '#1f2937',
labelFontWeight: 600, labelFontWeight: 700,
labelAutoMaxWidth: 200, labelAutoMaxWidth: 200,
requiredColor: '#ef4444', requiredColor: '#ef4444',
checkedColor: '#ff4544', checkedColor: '#3b82f6',
placeholderColor: '#909399', placeholderColor: '#9ca3af',
placeholderFontSize: 28, placeholderFontSize: 28,
// textarea 显示行数 / 行高(与 ns-newform 对齐) // textarea 显示行数 / 行高(与 ns-newform 对齐)
// 改用 :rows + line-height 驱动高度:rows 给原生 <textarea>(H5/微信原生层/鸿蒙 ArkUI 都识别), // 改用 :rows + line-height 驱动高度:rows 给原生 <textarea>(H5/微信原生层/鸿蒙 ArkUI 都识别),
@@ -404,13 +404,10 @@
'min-height': this.$util.rpx(this.config.rowMinHeight) 'min-height': this.$util.rpx(this.config.rowMinHeight)
}); });
}, },
// 标签宽度:根据 labelWidthMode 决定;fixed 用 --label-fixed-width,auto 用 maxLabelWidth // 标签宽度:垂直布局下标签整行显示,宽度交由 CSS(.name { width:100% })控制;
// 字号 / 颜色 / 字重已由 .form-wrap 注入的 --label-* 变量统一驱动,无需 inline 注入。
nameWidthStyle() { nameWidthStyle() {
const cfg = this.config; return {};
if (cfg.labelWidthMode === 'fixed') {
return { width: this.$util.rpx(cfg.labelFixedWidth) };
}
return this.maxLabelWidth ? { width: this.$util.rpx(this.maxLabelWidth) } : { width: 'auto' };
}, },
// 输入框样式(input / textarea 共用) // 输入框样式(input / textarea 共用)
inputStyle() { inputStyle() {
@@ -948,31 +945,31 @@
后端 formConfig 一律以 rpx 传递,前端按 rpx 写入; 后端 formConfig 一律以 rpx 传递,前端按 rpx 写入;
仅 H5 端需要在 JS computed(formWrapStyle 等)里通过 _rpx() 转 px。 */ 仅 H5 端需要在 JS computed(formWrapStyle 等)里通过 _rpx() 转 px。 */
.form-wrap { .form-wrap {
--form-padding-x: 0rpx; --form-padding-x: 28rpx;
--form-bg-color: #ffffff; --form-bg-color: #ffffff;
--row-padding-y: 20rpx; --row-padding-y: 24rpx;
--row-min-height: 60rpx; --row-min-height: 60rpx;
--label-gap: 16rpx; --label-gap: 20rpx;
--input-height: 60rpx; --input-height: 84rpx;
--input-radius: 0rpx; --input-radius: 16rpx;
--label-fixed-width: 180rpx; --label-fixed-width: 180rpx;
--label-font-size: 28rpx; --label-font-size: 32rpx;
--label-color: #1f2937; --label-color: #1f2937;
--label-font-weight: 600; --label-font-weight: 700;
--required-color: #ef4444; --required-color: #ef4444;
--placeholder-font-size: 28rpx; --placeholder-font-size: 28rpx;
--checked-color: #ff4544; --checked-color: #3b82f6;
--placeholder-color: #909399; --placeholder-color: #9ca3af;
// textarea 行数 / 行高(运行时被 formWrapStyle 注入的 inline 覆盖;这里是兜底) // textarea 行数 / 行高(运行时被 formWrapStyle 注入的 inline 覆盖;这里是兜底)
--textarea-rows: 4; --textarea-rows: 4;
--textarea-line-height: 44rpx; --textarea-line-height: 48rpx;
// 输入框视觉增强(与 ns-newform 对齐) // 输入框视觉增强(与 ns-newform 对齐)
--input-border-color: #e5e7eb; --input-border-color: #e5e7eb;
--input-bg-color: #f9fafb; --input-bg-color: #ffffff;
--input-focus-border-color: #ff4544; --input-focus-border-color: #3b82f6;
--input-focus-bg-color: #ffffff; --input-focus-bg-color: #ffffff;
--row-divider-color: #f0f0f0; --row-divider-color: #f0f0f0;
--show-row-divider: 1; --show-row-divider: 0;
} }
.order-wrap { .order-wrap {
@@ -990,7 +987,8 @@
.order-cell { .order-cell {
display: flex; display: flex;
align-items: center; flex-direction: column; // 与 ns-newform vertical 对齐:标签在上、输入框在下
align-items: flex-start;
background: #fff; background: #fff;
position: relative; position: relative;
min-height: var(--row-min-height, 60rpx); min-height: var(--row-min-height, 60rpx);
@@ -1023,18 +1021,23 @@
} }
.name { .name {
width: var(--label-fixed-width, 180rpx); width: 100%;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: nowrap; white-space: nowrap;
margin-right: var(--label-gap, 16rpx); display: flex;
align-items: center;
min-height: var(--input-height, 60rpx);
line-height: 1.5;
margin-bottom: var(--label-gap, 20rpx);
margin-right: 0;
} }
.tit { .tit {
text-align: left; text-align: left;
font-size: var(--label-font-size, $font-size-base); font-size: var(--label-font-size, $font-size-base);
color: var(--label-color, #1f2937); color: var(--label-color, #1f2937);
font-weight: var(--label-font-weight, 600); font-weight: var(--label-font-weight, 700);
text { text {
font-size: var(--label-font-size, $font-size-base); font-size: var(--label-font-size, $font-size-base);
@@ -1044,15 +1047,17 @@
.required { .required {
color: var(--required-color, #ef4444); color: var(--required-color, #ef4444);
font-size: var(--label-font-size, 28rpx); font-size: var(--label-font-size, 28rpx);
margin-left: 4rpx; margin-right: 6rpx;
width: 14rpx; font-weight: bold;
text-align: left; text-align: left;
display: inline-block; display: inline-block;
vertical-align: middle;
} }
.box { .box {
flex: 1; flex: 1;
padding: 0 10rpx; width: 100%;
padding: 0;
line-height: inherit; line-height: inherit;
text-align: left; text-align: left;
@@ -1064,26 +1069,27 @@
color: var(--label-color, #1f2937); color: var(--label-color, #1f2937);
// 显式 height / line-height / min-height 同步覆盖 WebView 默认与鸿蒙原生 TextInput 的固有高度, // 显式 height / line-height / min-height 同步覆盖 WebView 默认与鸿蒙原生 TextInput 的固有高度,
// 避免 placeholder / 文字下半部分被裁切 // 避免 placeholder / 文字下半部分被裁切
line-height: var(--input-height, 60rpx); line-height: var(--input-height, 84rpx);
height: var(--input-height, 60rpx); height: var(--input-height, 84rpx);
min-height: var(--input-height, 60rpx); min-height: var(--input-height, 84rpx);
border-radius: var(--input-radius, 0rpx); border-radius: var(--input-radius, 16rpx);
box-sizing: border-box; box-sizing: border-box;
padding: 0 12rpx; width: 100%;
padding: 0 24rpx;
// 输入框视觉增强(与 ns-newform 对齐) // 输入框视觉增强(与 ns-newform 对齐)
border: 1rpx solid var(--input-border-color, #e5e7eb); border: 1rpx solid var(--input-border-color, #e5e7eb);
background: var(--input-bg-color, #f9fafb); background: var(--input-bg-color, #ffffff);
transition: all 0.3s ease; transition: all 0.3s ease;
&:focus { &:focus {
background: var(--input-focus-bg-color, #ffffff); background: var(--input-focus-bg-color, #ffffff);
border-color: var(--input-focus-border-color, #ff4544); border-color: var(--input-focus-border-color, #3b82f6);
} }
// 原生 placeholder 样式(WebView / H5 生效;小程序/鸿蒙的 placeholder // 原生 placeholder 样式(WebView / H5 生效;小程序/鸿蒙的 placeholder
// 通过 placeholder-class="placeholder" 钩子走 .placeholder 规则) // 通过 placeholder-class="placeholder" 钩子走 .placeholder 规则)
&::placeholder { &::placeholder {
color: var(--placeholder-color, #909399); color: var(--placeholder-color, #9ca3af);
font-size: var(--placeholder-font-size, 28rpx); font-size: var(--placeholder-font-size, 28rpx);
} }
} }
@@ -1091,9 +1097,9 @@
// placeholder-class 钩子:在鸿蒙原生 TextInput / WebView 中都生效, // placeholder-class 钩子:在鸿蒙原生 TextInput / WebView 中都生效,
// 限定在 .form-wrap 内避免污染全局 .placeholder / .color-tip // 限定在 .form-wrap 内避免污染全局 .placeholder / .color-tip
.placeholder { .placeholder {
color: var(--placeholder-color, #909399); color: var(--placeholder-color, #9ca3af);
font-size: var(--placeholder-font-size, 28rpx); font-size: var(--placeholder-font-size, 28rpx);
line-height: var(--input-height, 60rpx); line-height: var(--input-height, 84rpx);
} }
::v-deep textarea, ::v-deep textarea,
@@ -1101,36 +1107,36 @@
font-size: var(--label-font-size, $font-size-base); font-size: var(--label-font-size, $font-size-base);
width: 100%; width: 100%;
color: var(--label-color, #1f2937); color: var(--label-color, #1f2937);
// 行高由 formConfig.textareaLineHeight(兜底 44rpx)控制; // 行高由 formConfig.textareaLineHeight(兜底 48rpx)控制;
// 高度 = rows × line-height + padding + border,所有值都从 CSS 变量读取, // 高度 = rows × line-height + padding + border,所有值都从 CSS 变量读取,
// 真正做到了"rows 改 → 高度自动改"。 // 真正做到了"rows 改 → 高度自动改"。
line-height: var(--textarea-line-height, 44rpx); line-height: var(--textarea-line-height, 48rpx);
min-height: calc(var(--textarea-rows, 4) * var(--textarea-line-height, 44rpx) + 26rpx); min-height: calc(var(--textarea-rows, 4) * var(--textarea-line-height, 48rpx) + 26rpx);
height: calc(var(--textarea-rows, 4) * var(--textarea-line-height, 44rpx) + 26rpx); height: calc(var(--textarea-rows, 4) * var(--textarea-line-height, 48rpx) + 26rpx);
text-align: left; text-align: left;
border-radius: var(--input-radius, 0rpx); border-radius: var(--input-radius, 16rpx);
box-sizing: border-box; box-sizing: border-box;
padding: 12rpx; padding: 12rpx 24rpx;
// 输入框视觉增强(与 ns-newform 对齐) // 输入框视觉增强(与 ns-newform 对齐)
border: 1rpx solid var(--input-border-color, #e5e7eb); border: 1rpx solid var(--input-border-color, #e5e7eb);
background: var(--input-bg-color, #f9fafb); background: var(--input-bg-color, #ffffff);
transition: all 0.3s ease; transition: all 0.3s ease;
&:focus { &:focus {
background: var(--input-focus-bg-color, #ffffff); background: var(--input-focus-bg-color, #ffffff);
border-color: var(--input-focus-border-color, #ff4544); border-color: var(--input-focus-border-color, #3b82f6);
} }
} }
.uni-input { .uni-input {
font-size: var(--label-font-size, $font-size-base); font-size: var(--label-font-size, $font-size-base);
color: var(--label-color, #1f2937); color: var(--label-color, #1f2937);
line-height: var(--input-height, 60rpx); line-height: var(--input-height, 84rpx);
min-height: var(--input-height, 60rpx); min-height: var(--input-height, 84rpx);
// 输入框视觉增强(与 ns-newform 对齐) // 输入框视觉增强(与 ns-newform 对齐)
border: 1rpx solid var(--input-border-color, #e5e7eb); border: 1rpx solid var(--input-border-color, #e5e7eb);
background: var(--input-bg-color, #f9fafb); background: var(--input-bg-color, #ffffff);
border-radius: var(--input-radius, 0rpx); border-radius: var(--input-radius, 16rpx);
transition: all 0.3s ease; transition: all 0.3s ease;
} }
@@ -1140,7 +1146,29 @@
} }
.color-tip { .color-tip {
color: var(--placeholder-color, #909399); color: var(--placeholder-color, #9ca3af);
}
// 让原生 picker / 自定义 pick-regions 撑满父容器(纵向布局下 label 在上、控件在下)
picker,
.pick-regions {
display: block;
width: 100%;
min-width: 0;
}
// 在 box-flex(带右侧箭头)场景下,picker 不应占满,留出右侧箭头空间
&.box-flex {
picker,
.pick-regions {
width: auto;
}
.iconfont.icon-right {
flex-shrink: 0;
margin-left: 8rpx;
line-height: 1;
}
} }
checkbox-group { checkbox-group {
@@ -1245,8 +1273,9 @@
line-height: 1; line-height: 1;
.iconfont { .iconfont {
font-size: 32rpx; font-size: 40rpx;
margin-right: 10rpx; margin-right: 10rpx;
color: #d1d5db;
} }
} }
} }
@@ -1262,8 +1291,9 @@
line-height: 1; line-height: 1;
.iconfont { .iconfont {
font-size: 32rpx; font-size: 40rpx;
margin-right: 10rpx; margin-right: 10rpx;
color: #d1d5db;
} }
} }
} }

View File

@@ -6,7 +6,7 @@
</view> </view>
<view class="system-form-wrap"> <view class="system-form-wrap">
<view class="form-title">{{ $lang('form.form.title') }}</view> <view class="form-title">{{ $lang('form.form.title') }}</view>
<ns-form :data="detail.json_data" :formConfig="detail.form_config" ref="form"></ns-form> <ns-form :data="detail.json_data" :formConfig="mergedFormConfig" ref="form"></ns-form>
<!-- 行为设置:提交验证码 --> <!-- 行为设置:提交验证码 -->
<view class="captcha-wrap" v-if="behavior.captcha == 1"> <view class="captcha-wrap" v-if="behavior.captcha == 1">
<input class="captcha-input" type="text" v-model="captcha.code" :placeholder="$lang('form.form.captchaPlaceholder')" /> <input class="captcha-input" type="text" v-model="captcha.code" :placeholder="$lang('form.form.captchaPlaceholder')" />
@@ -55,6 +55,33 @@
}; };
}, },
computed: { computed: {
// 前端视觉默认:强制覆盖后端 form_config 中可能存在的旧默认(horizontal / 红按钮 / 灰背景等),
// 与 diy-form 路径(formdata.vue 的 styleDefaults)保持一致,使系统表单页也统一呈现新设计稿样式。
// 后端仍保留行为类配置(behavior / submitText 等),本合并仅覆盖视觉字段。
mergedFormConfig() {
const styleDefaults = {
formLayout: 'vertical',
formPaddingX: 28,
rowPaddingY: 24,
labelGap: 20,
labelFontSize: 32,
labelFontWeight: 700,
labelColor: '#1f2937',
inputHeight: 84,
inputRadius: 16,
inputBgColor: '#ffffff',
inputBorderColor: '#e5e7eb',
inputFocusBorderColor: '#3b82f6',
inputFocusBgColor: '#ffffff',
placeholderColor: '#9ca3af',
placeholderFontSize: 28,
checkedColor: '#3b82f6',
showRowDivider: false,
submitColor: '#ffffff',
submitBgColor: '#3b82f6'
};
return Object.assign({}, this.detail ? (this.detail.form_config || {}) : {}, styleDefaults);
},
emptyText() { emptyText() {
return this.complete ? this.$lang('form.form.submitSuccess') : (this.formError || this.$lang('form.form.noFormInfo')); return this.complete ? this.$lang('form.form.submitSuccess') : (this.formError || this.$lang('form.form.noFormInfo'));
} }