refactor(utils&form): 优化设备信息获取与表单组件样式配置

1.  为设备信息相关方法添加可选链操作符,避免空值报错
2.  重构getDeviceInfo和getWindowInfo的异常处理逻辑,简化代码
3.  重构ns-form表单组件,新增全局样式配置支持,兼容多端样式自定义
4.  优化表单布局样式、必填项和选中态颜色,修复鸿蒙元服务TextArea适配问题
5.  新增标签宽度自动计算功能,优化表单排版对齐效果
This commit is contained in:
2026-07-17 10:43:49 +08:00
parent 4044070867
commit 61495b5e90
2 changed files with 427 additions and 86 deletions

View File

@@ -988,14 +988,14 @@ export default {
*/
isIPhoneX() {
let res = this.getDeviceInfo();
if (res.model.search('iPhone X') != -1) {
if (res?.model?.search('iPhone X') != -1) {
return true;
}
return false;
},
//判断安卓还是iOS
isAndroid() {
let platform = this.getDeviceInfo().platform;
let platform = this.getDeviceInfo()?.platform;
if (platform == 'ios') {
return false;
} else if (platform == 'android') {
@@ -1006,21 +1006,21 @@ export default {
* 获取设备信息(包含降级处理)
*/
getDeviceInfo() {
try {
return uni.getDeviceInfo();
} catch (e) {
return uni.getSystemInfoSync();
let info = {};
try { info = uni.getDeviceInfo(); } catch (e) {
try { info = uni.getSystemInfoSync(); } catch (e) {}
}
return info;
},
/**
* 获取窗口信息(包含降级处理)
*/
getWindowInfo() {
try {
return uni.getWindowInfo();
} catch (e) {
return uni.getSystemInfoSync();
let info;
try {info = uni.getWindowInfo();} catch (e) {
try {info = uni.getSystemInfoSync();} catch (e) {}
}
return info;
},
/**
* 深度拷贝对象
@@ -1118,10 +1118,10 @@ export default {
// auto: host 完全一致才还原
const appHosts = [];
// #ifdef H5
try { if (typeof location !== 'undefined' && location.hostname) appHosts.push(location.hostname); } catch (e) {}
try { if (typeof location !== 'undefined' && location.hostname) appHosts.push(location.hostname); } catch (e) { }
// #endif
try { if (Config.h5Domain) appHosts.push(Config.h5Domain); } catch (e) {}
try { if (Config.baseUrl) appHosts.push(Config.baseUrl); } catch (e) {}
try { if (Config.h5Domain) appHosts.push(Config.h5Domain); } catch (e) { }
try { if (Config.baseUrl) appHosts.push(Config.baseUrl); } catch (e) { }
internalPath = _extractInternalAppPath(link.wap_url, appHosts);
}
if (internalPath) {
@@ -1152,7 +1152,7 @@ export default {
// #endif
// #ifdef MP
console.log(`外部链接,跳转到webview`, url);
console.log(`外部链接,跳转到webview`, url);
this.redirectTo(PAGE_KEY_MAP.PAGE_WEBVIEW, {
...link,
src: url
@@ -1312,7 +1312,7 @@ export default {
*/
uniappIsIPhone11() {
let isIphone11 = false;
let systemInfo = this.getDeviceInfo();
let systemInfo = this?.getDeviceInfo();
// #ifdef MP
if (systemInfo.model.search('iPhone 11') != -1) {
isIphone11 = true;
@@ -1323,8 +1323,7 @@ export default {
// #ifdef H5
//判断该浏览器是否为safaria浏览器
isSafari() {
let res = this.getDeviceInfo();
var ua = navigator.userAgent.toLowerCase();
var ua = navigator?.userAgent?.toLowerCase();
if (ua.indexOf('applewebkit') > -1 && ua.indexOf('mobile') > -1 && ua.indexOf('safari') > -1 &&
ua.indexOf('linux') === -1 && ua.indexOf('android') === -1 && ua.indexOf('chrome') === -1 &&
ua.indexOf('ios') === -1 && ua.indexOf('browser') === -1) {

View File

@@ -1,36 +1,59 @@
<template>
<view class="form-wrap form-component">
<view class="form-wrap form-component" :style="formWrapStyle">
<block v-for="(item, index) in formData">
<view class="order-wrap" v-if="item.controller == 'Text'">
<view class="order-cell">
<view class="name">
<view class="order-wrap" :class="rowWrapClass" :style="rowWrapStyle" v-if="item.controller == 'Text'">
<view class="order-cell" :style="rowCellStyle">
<view class="name" :style="nameWidthStyle">
<text class="tit">{{ item.value.title }}</text>
<text class="required">{{ item.value.required ? '*' : '' }}</text>
<text class="required" :style="{ color: requiredColor }">{{ item.value.required ? '*' : '' }}</text>
</view>
<view class="box">
<input type="text" :placeholder="item.value.placeholder" placeholder-class="placeholder color-tip" v-model="item.val" />
<input
type="text"
:placeholder="item.value.placeholder"
placeholder-class="placeholder color-tip"
v-model="item.val"
:style="inputStyle"
/>
</view>
</view>
</view>
<view class="order-wrap" v-if="item.controller == 'Textarea'">
<view class="order-cell flex-box textarea">
<view class="name">
<view class="order-wrap" :class="rowWrapClass" :style="rowWrapStyle" v-if="item.controller == 'Textarea'">
<view class="order-cell flex-box textarea" :style="rowCellStyle">
<view class="name" :style="nameWidthStyle">
<text class="tit">{{ item.value.title }}</text>
<text class="required">{{ item.value.required ? '*' : '' }}</text>
<text class="required" :style="{ color: requiredColor }">{{ item.value.required ? '*' : '' }}</text>
</view>
<view class="box">
<textarea :placeholder="item.value.placeholder" placeholder-class="placeholder color-tip" v-model="item.val"></textarea>
<!-- #ifdef MP-HARMONY -->
<!-- 鸿蒙元服务渲染为原生 ArkUI TextArea,必须用 :rows 控制初始显示行数;CSS height 在原生组件上不生效 -->
<textarea
:rows="item.value.rows || 2"
:placeholder="item.value.placeholder"
placeholder-class="placeholder color-tip"
v-model="item.val"
:style="inputStyle"
></textarea>
<!-- #endif -->
<!-- #ifndef MP-HARMONY -->
<textarea
:placeholder="item.value.placeholder"
placeholder-class="placeholder color-tip"
v-model="item.val"
:style="inputStyle"
></textarea>
<!-- #endif -->
</view>
</view>
</view>
<picker mode="selector" :range="item.value.options" v-if="item.controller == 'Select'" @change="pickerChange($event, index)">
<view class="order-wrap">
<view class="order-cell">
<view class="name">
<view class="order-wrap" :class="rowWrapClass" :style="rowWrapStyle">
<view class="order-cell" :style="rowCellStyle">
<view class="name" :style="nameWidthStyle">
<text class="tit">{{ item.value.title }}</text>
<text class="required">{{ item.value.required ? '*' : '' }}</text>
<text class="required" :style="{ color: requiredColor }">{{ item.value.required ? '*' : '' }}</text>
</view>
<view class="box">
<text v-if="item.val != ''">{{ item.val }}</text>
@@ -41,18 +64,18 @@
</view>
</picker>
<view class="order-wrap" v-if="item.controller == 'Checkbox'">
<view class="order-cell">
<view class="name">
<view class="order-wrap" :class="rowWrapClass" :style="rowWrapStyle" v-if="item.controller == 'Checkbox'">
<view class="order-cell" :style="rowCellStyle">
<view class="name" :style="nameWidthStyle">
<text class="tit">{{ item.value.title }}</text>
<text class="required">{{ item.value.required ? '*' : '' }}</text>
<text class="required" :style="{ color: requiredColor }">{{ item.value.required ? '*' : '' }}</text>
</view>
<view class="box check-group-box">
<checkbox-group @change="checkboxChange($event, index)">
<label v-for="(v, k) in item.option_lists" :key="k">
<checkbox :value="v.value" :checked="v.checked" />
<view class="checkbox">
<text class="iconfont" :class="{ 'icon-fuxuankuang2': !v.checked, 'icon-fuxuankuang1 color-base-text': v.checked }"></text>
<text class="iconfont" :class="{ 'icon-fuxuankuang2': !v.checked, 'icon-fuxuankuang1 color-base-text': v.checked }" :style="{ color: v.checked ? checkedColor : '' }"></text>
{{ v.value }}
</view>
</label>
@@ -61,18 +84,18 @@
</view>
</view>
<view class="order-wrap" v-if="item.controller == 'Radio'">
<view class="order-cell">
<view class="name">
<view class="order-wrap" :class="rowWrapClass" :style="rowWrapStyle" v-if="item.controller == 'Radio'">
<view class="order-cell" :style="rowCellStyle">
<view class="name" :style="nameWidthStyle">
<text class="tit">{{ item.value.title }}</text>
<text class="required">{{ item.value.required ? '*' : '' }}</text>
<text class="required" :style="{ color: requiredColor }">{{ item.value.required ? '*' : '' }}</text>
</view>
<view class="box radio-group-box">
<radio-group @change="radioChange($event, index)">
<label v-for="(v, k) in item.option_lists" :key="k">
<radio :value="v.value" :checked="item.val == v.value" />
<view class="radio-box">
<text class="iconfont" :class="{ 'icon-yuan_checkbox': item.val != v.value, 'icon-yuan_checked color-base-text': item.val == v.value }"></text>
<text class="iconfont" :class="{ 'icon-yuan_checkbox': item.val != v.value, 'icon-yuan_checked color-base-text': item.val == v.value }" :style="{ color: item.val == v.value ? checkedColor : '' }"></text>
{{ v.value }}
</view>
</label>
@@ -81,11 +104,11 @@
</view>
</view>
<view class="order-wrap" v-if="item.controller == 'Img'">
<view class="order-cell flex-box">
<view class="name">
<view class="order-wrap" :class="rowWrapClass" :style="rowWrapStyle" v-if="item.controller == 'Img'">
<view class="order-cell flex-box" :style="rowCellStyle">
<view class="name" :style="nameWidthStyle">
<text class="tit">{{ item.value.title }}</text>
<text class="required">{{ item.value.required ? '*' : '' }}</text>
<text class="required" :style="{ color: requiredColor }">{{ item.value.required ? '*' : '' }}</text>
</view>
<view class="box img-boxs">
<view class="img-box" v-for="(v, k) in item.img_lists" :key="k" @click="uploadImg(index)">
@@ -97,11 +120,11 @@
</view>
</view>
<view class="order-wrap" v-if="item.controller == 'Date'">
<view class="order-cell">
<view class="name">
<view class="order-wrap" :class="rowWrapClass" :style="rowWrapStyle" v-if="item.controller == 'Date'">
<view class="order-cell" :style="rowCellStyle">
<view class="name" :style="nameWidthStyle">
<text class="tit">{{ item.value.title }}</text>
<text class="required">{{ item.value.required ? '*' : '' }}</text>
<text class="required" :style="{ color: requiredColor }">{{ item.value.required ? '*' : '' }}</text>
</view>
<view class="box box-flex">
<picker mode="date" :value="item.val" @change="bindDateChange($event, index)">
@@ -112,11 +135,11 @@
</view>
</view>
<view class="order-wrap" v-if="item.controller == 'Datelimit'">
<view class="order-cell flex-box">
<view class="name">
<view class="order-wrap" :class="rowWrapClass" :style="rowWrapStyle" v-if="item.controller == 'Datelimit'">
<view class="order-cell flex-box" :style="rowCellStyle">
<view class="name" :style="nameWidthStyle">
<text class="tit">{{ item.value.title }}</text>
<text class="required">{{ item.value.required ? '*' : '' }}</text>
<text class="required" :style="{ color: requiredColor }">{{ item.value.required ? '*' : '' }}</text>
</view>
<view class="box date-boxs">
<view class="date-box">
@@ -138,11 +161,11 @@
</view>
</view>
<view class="order-wrap" v-if="item.controller == 'Time'">
<view class="order-cell">
<view class="name">
<view class="order-wrap" :class="rowWrapClass" :style="rowWrapStyle" v-if="item.controller == 'Time'">
<view class="order-cell" :style="rowCellStyle">
<view class="name" :style="nameWidthStyle">
<text class="tit">{{ item.value.title }}</text>
<text class="required">{{ item.value.required ? '*' : '' }}</text>
<text class="required" :style="{ color: requiredColor }">{{ item.value.required ? '*' : '' }}</text>
</view>
<view class="box box-flex">
<picker mode="time" :value="item.val" @change="bindTimeChange($event, index)">
@@ -153,11 +176,11 @@
</view>
</view>
<view class="order-wrap" v-if="item.controller == 'Timelimit'">
<view class="order-cell flex-box">
<view class="name">
<view class="order-wrap" :class="rowWrapClass" :style="rowWrapStyle" v-if="item.controller == 'Timelimit'">
<view class="order-cell flex-box" :style="rowCellStyle">
<view class="name" :style="nameWidthStyle">
<text class="tit">{{ item.value.title }}</text>
<text class="required">{{ item.value.required ? '*' : '' }}</text>
<text class="required" :style="{ color: requiredColor }">{{ item.value.required ? '*' : '' }}</text>
</view>
<view class="box date-boxs">
<view class="date-box">
@@ -179,11 +202,11 @@
</view>
</view>
<view class="order-wrap" v-if="item.controller == 'City'">
<view class="order-cell box-flex">
<view class="name">
<view class="order-wrap" :class="rowWrapClass" :style="rowWrapStyle" v-if="item.controller == 'City'">
<view class="order-cell box-flex" :style="rowCellStyle">
<view class="name" :style="nameWidthStyle">
<text class="tit">{{ item.value.title }}</text>
<text class="required">{{ item.value.required ? '*' : '' }}</text>
<text class="required" :style="{ color: requiredColor }">{{ item.value.required ? '*' : '' }}</text>
</view>
<view class="box">
<pick-regions :default-regions="item.default_regions" :select-arr="item.select_arr" @getRegions="handleGetRegions($event, index)">
@@ -205,32 +228,262 @@
props: {
data: {
type: Array,
default: {}
default: () => []
},
customAttr: {
type: Object,
default: function() {
return {};
}
},
/**
* 表单样式配置(由后端 designer 通过 API 传入)。
* 字段名同时支持 camelCase(如 formPaddingX / inputHeight / labelColor)
* 与 snake_case(如 form_padding_x / input_height / label_color),camelCase 优先。
* 不传则使用组件内置默认(与重构前硬编码值一致,无视觉回归)。
*
* 支持的键:
* formPaddingX, formBgColor, rowPaddingY, rowMinHeight, labelGap,
* inputHeight, inputRadius, labelWidthMode ('auto'|'fixed'), labelFixedWidth,
* labelFontSize, labelColor, requiredColor, checkedColor,
* placeholderColor, showRowDivider, rowDividerColor
*
* 平台差异化:formConfig.terminal.{h5|weapp|harmony|app|quickapp} 只放差异字段
*/
formConfig: {
type: Object,
default: () => ({})
}
},
data() {
return {
formData: this.data
formData: [],
maxLabelWidth: 0
};
},
computed: {
// 当前运行平台标识(与后端 formConfig.terminal 键名对齐)
platform() {
// #ifdef H5
return 'h5';
// #endif
// #ifdef MP-WEIXIN
return 'weapp';
// #endif
// #ifdef MP-ALIPAY
return 'mp-alipay';
// #endif
// #ifdef MP-BAIDU
return 'mp-baidu';
// #endif
// #ifdef MP-TOUTIAO
return 'mp-toutiao';
// #endif
// #ifdef APP-PLUS
return 'app';
// #endif
// #ifdef MP-HARMONY
return 'harmony';
// #endif
// #ifdef QUICKAPP-WEBVIEW
return 'quickapp';
// #endif
return 'h5';
},
// 后端键名 → 内部 platform 名 兼容层
platformAliases() {
return {
'h5': 'h5',
'weapp': 'weapp',
'mp-weixin': 'weapp',
'wechat': 'weapp',
'harmony': 'harmony',
'app': 'app',
'app-plus': 'app'
};
},
// 当前平台的有效配置键集合
platformConfigKeys() {
const cur = this.platform;
const aliases = this.platformAliases;
const keys = [];
Object.keys(aliases).forEach(k => {
if (aliases[k] === cur) keys.push(k);
});
if (keys.indexOf(cur) === -1) keys.push(cur);
return keys;
},
// 合并配置:内置默认 < 全局 formConfig < 当前平台 formConfig.terminal[platform]
// 同时兼容 snake_case 与 camelCase 字段名
config() {
// 1) 内置默认值(最低优先级,与重构前的硬编码值一一对应,确保未传 formConfig 时零回归)
const defaults = {
formPaddingX: 0,
formBgColor: '#ffffff',
rowPaddingY: 20,
rowMinHeight: 60,
labelGap: 16,
inputHeight: 60,
inputRadius: 0,
labelWidthMode: 'fixed', // 旧版 width: 80vw 用 fixed 180rpx 等价替代
labelFixedWidth: 180,
labelFontSize: 28,
labelColor: '#1f2937',
requiredColor: '#ef4444',
checkedColor: '#ff4544',
placeholderColor: '#909399',
showRowDivider: true,
rowDividerColor: '#f0f0f0'
};
const fc = this.formConfig || {};
// 同时读 camelCase 与 snake_case,camelCase 优先
const pick = (camel, snake) => {
const v1 = fc[camel];
if (v1 !== undefined && v1 !== null && v1 !== '') return v1;
return fc[snake];
};
const base = {
formPaddingX: pick('formPaddingX', 'form_padding_x'),
formBgColor: pick('formBgColor', 'form_bg_color'),
rowPaddingY: pick('rowPaddingY', 'row_padding_y'),
rowMinHeight: pick('rowMinHeight', 'row_min_height'),
labelGap: pick('labelGap', 'label_gap'),
inputHeight: pick('inputHeight', 'input_height'),
inputRadius: pick('inputRadius', 'input_radius'),
labelWidthMode: pick('labelWidthMode', 'label_width_mode'),
labelFixedWidth: pick('labelFixedWidth', 'label_fixed_width'),
labelFontSize: pick('labelFontSize', 'label_font_size'),
labelColor: pick('labelColor', 'label_color'),
requiredColor: pick('requiredColor', 'required_color'),
checkedColor: pick('checkedColor', 'checked_color'),
placeholderColor: pick('placeholderColor', 'placeholder_color'),
showRowDivider: pick('showRowDivider', 'show_row_divider'),
rowDividerColor: pick('rowDividerColor', 'row_divider_color')
};
// 移除 undefined / 空字符串(保留 0 / false)
Object.keys(base).forEach(k => {
if (base[k] === undefined || base[k] === null || base[k] === '') delete base[k];
});
// showRowDivider 在前后端可能传数字 0/1,统一转 boolean
if ('showRowDivider' in base) {
base.showRowDivider = base.showRowDivider === true || base.showRowDivider === 1 || base.showRowDivider === '1';
}
// 2) 当前平台差异化配置(最高优先级),从 formConfig.terminal 取出当前平台 + 所有别名键,合并
const termBlock = fc.terminal || {};
const platformKeys = this.platformConfigKeys || [];
const platformCfg = Object.assign(
{},
...platformKeys.map(k => termBlock[k] || fc[k] || {})
);
// 平台块可能也是 snake_case,统一转 camelCase
const platformCfgCamel = {};
if (this.$util && typeof this.$util.snakeToCamel === 'function') {
Object.keys(platformCfg).forEach(k => {
platformCfgCamel[this.$util.snakeToCamel(k)] = platformCfg[k];
});
} else {
Object.assign(platformCfgCamel, platformCfg);
}
// 3) 三层合并:defaults < base < platformCfgCamel
return Object.assign({}, defaults, base, platformCfgCamel);
},
// 容器样式:内边距 + 背景 + 16 个 CSS 自定义属性
formWrapStyle() {
const cfg = this.config;
const num = (v, fb) => (typeof v === 'number' ? v : parseInt(v) || fb);
return this.$util.objToStyle({
padding: `0 ${cfg.formPaddingX}rpx`,
background: cfg.formBgColor,
'--form-padding-x': `${cfg.formPaddingX}rpx`,
'--form-bg-color': cfg.formBgColor,
'--row-padding-y': `${cfg.rowPaddingY}rpx`,
'--row-min-height': `${cfg.rowMinHeight}rpx`,
'--label-gap': `${cfg.labelGap}rpx`,
'--input-height': `${cfg.inputHeight}rpx`,
'--input-radius': `${cfg.inputRadius}rpx`,
'--label-fixed-width': `${cfg.labelFixedWidth}rpx`,
'--label-font-size': `${num(cfg.labelFontSize, 28)}rpx`,
'--label-color': cfg.labelColor,
'--required-color': cfg.requiredColor,
'--checked-color': cfg.checkedColor,
'--placeholder-color': cfg.placeholderColor,
'--row-divider-color': cfg.rowDividerColor,
'--show-row-divider': cfg.showRowDivider === false ? '0' : '1'
});
},
rowWrapStyle() {
return this.$util.objToStyle({
padding: `${this.config.rowPaddingY}rpx 0`
});
},
rowWrapClass() {
return this.config.showRowDivider === false ? 'order-wrap no-divider' : 'order-wrap';
},
rowCellStyle() {
return this.$util.objToStyle({
'min-height': `${this.config.rowMinHeight}rpx`
});
},
// 标签宽度:根据 labelWidthMode 决定;fixed 用 --label-fixed-width,auto 用 maxLabelWidth
nameWidthStyle() {
const cfg = this.config;
if (cfg.labelWidthMode === 'fixed') {
return { width: `${cfg.labelFixedWidth}rpx` };
}
return this.maxLabelWidth ? { width: `${this.maxLabelWidth}rpx` } : { width: 'auto' };
},
// 输入框样式(input / textarea 共用)
inputStyle() {
return this.$util.objToStyle({
'min-height': `${this.config.inputHeight}rpx`,
'border-radius': `${this.config.inputRadius}rpx`,
'font-size': `${this.config.labelFontSize}rpx`,
'color': this.config.labelColor,
'line-height': `${this.config.inputHeight}rpx`
});
},
// 必填星号颜色
requiredColor() {
return this.config.requiredColor;
},
// 复选 / 单选 选中色
checkedColor() {
return this.config.checkedColor;
}
},
created() {
this.setFormData();
this.initFormData();
this.$nextTick(() => this.calculateMaxLabelWidth());
},
watch: {
data: function() {
this.setFormData();
data: {
deep: true,
handler() {
this.initFormData();
this.$nextTick(() => this.calculateMaxLabelWidth());
}
},
formConfig: {
deep: true,
handler() {
this.$nextTick(() => this.calculateMaxLabelWidth());
}
}
},
methods: {
initFormData() {
// 深拷贝 prop 数据到本地状态,避免 mutate 上游
this.formData = JSON.parse(JSON.stringify(this.data || []));
this.processFormData();
},
setFormData() {
this.formData = this.data;
this.initFormData();
this.$nextTick(() => this.calculateMaxLabelWidth());
},
processFormData() {
this.formData.forEach(item => {
if (!item.val) item.val = item.value.default ? item.value.default : '';
@@ -356,6 +609,25 @@
}
});
},
// 计算最长 label 宽度(与后端 design.js calculateMaxLabelWidth 算法对齐)
// 用于 labelWidthMode === 'auto' 时把 .name 的 width 撑开,避免长短 label 错位
calculateMaxLabelWidth() {
if (!this.formData || this.formData.length === 0) {
this.maxLabelWidth = 0;
return;
}
const fontSize = parseInt(this.config.labelFontSize) || 28;
let maxChars = 0;
this.formData.forEach(item => {
if (!item.value || !item.value.title) return;
let len = (item.value.title || '').length;
if (item.value.required) len += 1;
if (len > maxChars) maxChars = len;
});
// 公式: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);
},
verify() {
let verify = true;
@@ -643,9 +915,33 @@
};
</script>
<style lang="scss">
<style lang="scss" scoped>
/* CSS 自定义属性:默认值,与组件内 config.defaults 一一对应,
formConfig 未传时作为兜底;运行时会被 formWrapStyle 注入的 inline style 覆盖 */
.form-wrap {
--form-padding-x: 0rpx;
--form-bg-color: #ffffff;
--row-padding-y: 20rpx;
--row-min-height: 60rpx;
--label-gap: 16rpx;
--input-height: 60rpx;
--input-radius: 0rpx;
--label-fixed-width: 180rpx;
--label-font-size: 28rpx;
--label-color: #1f2937;
--required-color: #ef4444;
--checked-color: #ff4544;
--placeholder-color: #909399;
--row-divider-color: #f0f0f0;
--show-row-divider: 1;
}
.order-wrap {
padding: 20rpx 0;
padding: var(--row-padding-y, 20rpx) 0;
&.no-divider {
border-bottom: 0;
}
&:last-child {
margin-bottom: 0;
@@ -658,9 +954,10 @@
align-items: center;
background: #fff;
position: relative;
min-height: var(--row-min-height, 60rpx);
&.textarea {
align-items: unset;
align-items: flex-start;
}
&.clear-flex {
@@ -674,7 +971,8 @@
&:last-child {
margin-bottom: 0;
border-bottom: solid 1px #eee;
// calc + 0/1 控制分割线显隐(formConfig.showRowDivider = false 时整条隐藏)
border-bottom: calc(var(--show-row-divider, 1) * 2rpx) solid var(--row-divider-color, #f0f0f0);
}
&.align-top {
@@ -682,28 +980,30 @@
}
text {
font-size: $font-size-base;
font-size: var(--label-font-size, $font-size-base);
}
.name {
width: 80vw;
width: var(--label-fixed-width, 180rpx);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
margin-right: var(--label-gap, 16rpx);
}
.tit {
text-align: left;
font-size: $font-size-base;
font-size: var(--label-font-size, $font-size-base);
color: var(--label-color, #1f2937);
text {
font-size: $font-size-base;
font-size: var(--label-font-size, $font-size-base);
}
}
.required {
color: red;
font-size: 28rpx;
color: var(--required-color, #ef4444);
font-size: var(--label-font-size, 28rpx);
margin-left: 4rpx;
width: 14rpx;
text-align: left;
@@ -717,16 +1017,58 @@
text-align: left;
input {
font-size: $font-size-base;
font-size: var(--label-font-size, $font-size-base);
text-align: left;
color: var(--label-color, #1f2937);
// 显式 height / line-height / min-height 同步覆盖 WebView 默认与鸿蒙原生 TextInput 的固有高度,
// 避免 placeholder / 文字下半部分被裁切
line-height: var(--input-height, 60rpx);
height: var(--input-height, 60rpx);
min-height: var(--input-height, 60rpx);
border-radius: var(--input-radius, 0rpx);
box-sizing: border-box;
padding: 0;
background: transparent;
}
// placeholder-class 钩子:在鸿蒙原生 TextInput / WebView 中都生效,
// 限定在 .form-wrap 内避免污染全局 .placeholder / .color-tip
.placeholder {
color: var(--placeholder-color, #909399);
font-size: var(--label-font-size, 28rpx);
line-height: var(--input-height, 60rpx);
}
textarea {
font-size: $font-size-base;
font-size: var(--label-font-size, $font-size-base);
width: 100%;
color: var(--label-color, #1f2937);
// 鸿蒙元服务渲染为原生 ArkUI TextArea,不识别 CSS height,由 :rows 控制行数;
// 这里给所有端都设一个最小高度基线,避免空内容时塌缩
min-height: 88rpx;
height: 88rpx;
line-height: 44rpx;
text-align: left;
border-radius: var(--input-radius, 0rpx);
box-sizing: border-box;
padding: 0;
background: transparent;
}
.uni-input {
font-size: var(--label-font-size, $font-size-base);
color: var(--label-color, #1f2937);
line-height: var(--input-height, 60rpx);
min-height: var(--input-height, 60rpx);
}
.select-address {
font-size: var(--label-font-size, $font-size-base);
color: var(--label-color, #1f2937);
}
.color-tip {
color: var(--placeholder-color, #909399);
}
checkbox-group {
@@ -847,7 +1189,7 @@
.iconfont {
color: $color-tip;
font-size: $font-size-base;
font-size: var(--label-font-size, $font-size-base);
}
.box-flex picker {