chore:微信小程序和后台表单样式一致
This commit is contained in:
@@ -6,13 +6,15 @@
|
||||
<view :class="['order-cell', { 'vertical-layout': mode === 'vertical' }]" :style="rowCellStyle">
|
||||
<view class="name" :style="item._labelStyle">
|
||||
<text class="required" :style="item._starStyle">{{ item.value.required ? '*' : '' }}</text>
|
||||
<text class="tit">{{ item.value.title }}</text><text v-if="requiredPosition === 'after'" class="required" :style="item._starStyle">{{ item.value.required ? '*' : '' }}</text>
|
||||
<text class="tit" :style="item._titStyle">{{ item.value.title }}</text><text v-if="requiredPosition === 'after'" class="required" :style="item._starStyle">{{ item.value.required ? '*' : '' }}</text>
|
||||
</view>
|
||||
<view :class="['box', 'align-left']" :style="item._boxAlignStyle">
|
||||
<input
|
||||
type="text"
|
||||
:placeholder="item.value.placeholder"
|
||||
placeholder-class="placeholder color-tip"
|
||||
:placeholder-style="item._placeholderStyle"
|
||||
:style="item._inputStyle"
|
||||
v-model="item.val"
|
||||
/>
|
||||
</view>
|
||||
@@ -21,10 +23,10 @@
|
||||
|
||||
<!-- 多行文本输入框 -->
|
||||
<view v-if="item.controller == 'Textarea'" class="order-wrap" :class="rowWrapClass" :style="item._fieldWrapStyle">
|
||||
<view :class="['order-cell', 'flex-box', 'textarea', { 'vertical-layout': mode === 'vertical' }]">
|
||||
<view :class="['order-cell', 'flex-box', 'textarea', { 'vertical-layout': mode === 'vertical' }]" :style="rowCellStyle">
|
||||
<view class="name" :style="item._labelStyle">
|
||||
<text class="required" :style="item._starStyle">{{ item.value.required ? '*' : '' }}</text>
|
||||
<text class="tit">{{ item.value.title }}</text><text v-if="requiredPosition === 'after'" class="required" :style="item._starStyle">{{ item.value.required ? '*' : '' }}</text>
|
||||
<text class="tit" :style="item._titStyle">{{ item.value.title }}</text><text v-if="requiredPosition === 'after'" class="required" :style="item._starStyle">{{ item.value.required ? '*' : '' }}</text>
|
||||
</view>
|
||||
<view :class="['box', 'align-left']" :style="item._boxAlignStyle">
|
||||
<!-- 所有平台统一通过 formConfig.textareaRows / item.value.rows 控制显示行数:
|
||||
@@ -35,6 +37,7 @@
|
||||
:rows="item.value.rows || textareaRowsValue"
|
||||
:placeholder="item.value.placeholder"
|
||||
placeholder-class="placeholder color-tip"
|
||||
:placeholder-style="item._placeholderStyle"
|
||||
v-model="item.val"
|
||||
:style="item._textareaStyle"
|
||||
></textarea>
|
||||
@@ -49,14 +52,14 @@
|
||||
:range="item.value.options"
|
||||
@change="pickerChange($event, index)"
|
||||
>
|
||||
<view :class="['order-cell', { 'vertical-layout': mode === 'vertical' }]">
|
||||
<view :class="['order-cell', { 'vertical-layout': mode === 'vertical' }]" :style="rowCellStyle">
|
||||
<view class="name" :style="item._labelStyle">
|
||||
<text class="required" :style="item._starStyle">{{ item.value.required ? '*' : '' }}</text>
|
||||
<text class="tit">{{ item.value.title }}</text><text v-if="requiredPosition === 'after'" class="required" :style="item._starStyle">{{ item.value.required ? '*' : '' }}</text>
|
||||
<text class="tit" :style="item._titStyle">{{ item.value.title }}</text><text v-if="requiredPosition === 'after'" class="required" :style="item._starStyle">{{ item.value.required ? '*' : '' }}</text>
|
||||
</view>
|
||||
<view :class="['box', 'box-flex', 'align-right']" :style="item._controlBoxStyle">
|
||||
<text v-if="item.val != ''">{{ item.val }}</text>
|
||||
<text v-else class="color-tip">{{ item.value.placeholder || $lang('components.ns-newform.selectPlaceholder') }}</text>
|
||||
<text v-if="item.val != ''" :style="item._valueTextStyle">{{ item.val }}</text>
|
||||
<text v-else class="color-tip" :style="item._tipTextStyle">{{ item.value.placeholder || $lang('components.ns-newform.selectPlaceholder') }}</text>
|
||||
<text class="iconfont icon-right"></text>
|
||||
</view>
|
||||
</view>
|
||||
@@ -65,14 +68,14 @@
|
||||
|
||||
<!-- 复选框 -->
|
||||
<view v-if="item.controller == 'Checkbox'" class="order-wrap" :class="rowWrapClass" :style="item._fieldWrapStyle">
|
||||
<view :class="['order-cell', { 'vertical-layout': mode === 'vertical' }]">
|
||||
<view :class="['order-cell', { 'vertical-layout': mode === 'vertical' }]" :style="rowCellStyle">
|
||||
<view class="name" :style="item._labelStyle">
|
||||
<text class="required" :style="item._starStyle">{{ item.value.required ? '*' : '' }}</text>
|
||||
<text class="tit">{{ item.value.title }}</text><text v-if="requiredPosition === 'after'" class="required" :style="item._starStyle">{{ item.value.required ? '*' : '' }}</text>
|
||||
<text class="tit" :style="item._titStyle">{{ item.value.title }}</text><text v-if="requiredPosition === 'after'" class="required" :style="item._starStyle">{{ item.value.required ? '*' : '' }}</text>
|
||||
</view>
|
||||
<view :class="['box', 'check-group-box', 'align-left', 'option-layout-' + (item.value.option_layout || 'vertical')]" :style="item._controlWidthStyle">
|
||||
<checkbox-group @change="checkboxChange($event, index)" :style="item._optionLayoutStyle">
|
||||
<label v-for="(v, k) in item.option_lists" :key="k">
|
||||
<label v-for="(v, k) in item.option_lists" :key="k" :style="item._optionLabelStyle">
|
||||
<checkbox :value="v.value" :checked="v.checked"></checkbox>
|
||||
<view class="checkbox">
|
||||
<text
|
||||
@@ -82,8 +85,8 @@
|
||||
!v.checked ? 'icon-fuxuankuang2' : '',
|
||||
v.checked ? 'icon-fuxuankuang1' : ''
|
||||
]"
|
||||
:style="{ color: v.checked ? checkedColor : '' }"
|
||||
></text>
|
||||
:style="v.checked ? item._checkedColorStyle : ''"
|
||||
></text>
|
||||
{{ v.value }}
|
||||
</view>
|
||||
</label>
|
||||
@@ -94,14 +97,14 @@
|
||||
|
||||
<!-- 单选框 -->
|
||||
<view v-if="item.controller == 'Radio'" class="order-wrap" :class="rowWrapClass" :style="item._fieldWrapStyle">
|
||||
<view :class="['order-cell', { 'vertical-layout': mode === 'vertical' }]">
|
||||
<view :class="['order-cell', { 'vertical-layout': mode === 'vertical' }]" :style="rowCellStyle">
|
||||
<view class="name" :style="item._labelStyle">
|
||||
<text class="required" :style="item._starStyle">{{ item.value.required ? '*' : '' }}</text>
|
||||
<text class="tit">{{ item.value.title }}</text><text v-if="requiredPosition === 'after'" class="required" :style="item._starStyle">{{ item.value.required ? '*' : '' }}</text>
|
||||
<text class="tit" :style="item._titStyle">{{ item.value.title }}</text><text v-if="requiredPosition === 'after'" class="required" :style="item._starStyle">{{ item.value.required ? '*' : '' }}</text>
|
||||
</view>
|
||||
<view :class="['box', 'radio-group-box', 'align-left', 'option-layout-' + (item.value.option_layout || 'vertical')]" :style="item._controlWidthStyle">
|
||||
<radio-group @change="radioChange($event, index)" :style="item._optionLayoutStyle">
|
||||
<label v-for="(v, k) in item.option_lists" :key="k">
|
||||
<label v-for="(v, k) in item.option_lists" :key="k" :style="item._optionLabelStyle">
|
||||
<radio :value="v.value" :checked="item.val == v.value"></radio>
|
||||
<view class="radio-box">
|
||||
<text
|
||||
@@ -111,8 +114,8 @@
|
||||
item.val != v.value ? 'icon-yuan_checkbox' : '',
|
||||
item.val == v.value ? 'icon-yuan_checked' : ''
|
||||
]"
|
||||
:style="{ color: item.val == v.value ? checkedColor : '' }"
|
||||
></text>
|
||||
:style="item.val == v.value ? item._checkedColorStyle : ''"
|
||||
></text>
|
||||
{{ v.value }}
|
||||
</view>
|
||||
</label>
|
||||
@@ -123,10 +126,10 @@
|
||||
|
||||
<!-- 图片上传 -->
|
||||
<view v-if="item.controller == 'Img'" class="order-wrap" :class="rowWrapClass" :style="item._fieldWrapStyle">
|
||||
<view :class="['order-cell', 'flex-box', { 'vertical-layout': mode === 'vertical' }]">
|
||||
<view :class="['order-cell', 'flex-box', { 'vertical-layout': mode === 'vertical' }]" :style="rowCellStyle">
|
||||
<view class="name" :style="item._labelStyle">
|
||||
<text class="required" :style="item._starStyle">{{ item.value.required ? '*' : '' }}</text>
|
||||
<text class="tit">{{ item.value.title }}</text><text v-if="requiredPosition === 'after'" class="required" :style="item._starStyle">{{ item.value.required ? '*' : '' }}</text>
|
||||
<text class="tit" :style="item._titStyle">{{ item.value.title }}</text><text v-if="requiredPosition === 'after'" class="required" :style="item._starStyle">{{ 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)">
|
||||
@@ -142,18 +145,18 @@
|
||||
|
||||
<!-- 日期选择器 -->
|
||||
<view v-if="item.controller == 'Date'" class="order-wrap" :class="rowWrapClass" :style="item._fieldWrapStyle">
|
||||
<view :class="['order-cell', { 'vertical-layout': mode === 'vertical' }]">
|
||||
<view :class="['order-cell', { 'vertical-layout': mode === 'vertical' }]" :style="rowCellStyle">
|
||||
<view class="name" :style="item._labelStyle">
|
||||
<text class="required" :style="item._starStyle">{{ item.value.required ? '*' : '' }}</text>
|
||||
<text class="tit">{{ item.value.title }}</text><text v-if="requiredPosition === 'after'" class="required" :style="item._starStyle">{{ item.value.required ? '*' : '' }}</text>
|
||||
<text class="tit" :style="item._titStyle">{{ item.value.title }}</text><text v-if="requiredPosition === 'after'" class="required" :style="item._starStyle">{{ item.value.required ? '*' : '' }}</text>
|
||||
</view>
|
||||
<view :class="['box', 'box-flex', 'align-right']" :style="item._controlBoxStyle">
|
||||
<picker
|
||||
mode="date"
|
||||
:value="item.val"
|
||||
@change="bindDateChange($event, index)"
|
||||
>
|
||||
<view :class="['uni-input', !item.val ? 'color-tip' : '']">
|
||||
>
|
||||
<view :class="['uni-input', !item.val ? 'color-tip' : '']" :style="item._uniInputStyle + (item.val ? item._valueTextStyle : item._tipTextStyle)">
|
||||
{{ item.val ? item.val : (item.value.placeholder || $lang('components.ns-newform.datePlaceholder')) }}
|
||||
</view>
|
||||
</picker>
|
||||
@@ -164,10 +167,10 @@
|
||||
|
||||
<!-- 日期范围选择器 -->
|
||||
<view v-if="item.controller == 'Datelimit'" class="order-wrap" :class="rowWrapClass" :style="item._fieldWrapStyle">
|
||||
<view :class="['order-cell', 'flex-box', { 'vertical-layout': mode === 'vertical' }]">
|
||||
<view :class="['order-cell', 'flex-box', { 'vertical-layout': mode === 'vertical' }]" :style="rowCellStyle">
|
||||
<view class="name" :style="item._labelStyle">
|
||||
<text class="required" :style="item._starStyle">{{ item.value.required ? '*' : '' }}</text>
|
||||
<text class="tit">{{ item.value.title }}</text><text v-if="requiredPosition === 'after'" class="required" :style="item._starStyle">{{ item.value.required ? '*' : '' }}</text>
|
||||
<text class="tit" :style="item._titStyle">{{ item.value.title }}</text><text v-if="requiredPosition === 'after'" class="required" :style="item._starStyle">{{ item.value.required ? '*' : '' }}</text>
|
||||
</view>
|
||||
<view :class="['box', 'date-boxs', 'align-center']" :style="item._boxAlignStyle">
|
||||
<view class="date-box">
|
||||
@@ -175,9 +178,9 @@
|
||||
mode="date"
|
||||
:value="item.start_date"
|
||||
@change="bindStartDateChange($event, index)"
|
||||
>
|
||||
>
|
||||
<view class="picker-box">
|
||||
<view :class="['uni-input', !item.start_date ? 'color-tip' : '']">
|
||||
<view :class="['uni-input', !item.start_date ? 'color-tip' : '']" :style="item._uniInputStyle + (item.start_date ? item._valueTextStyle : item._tipTextStyle)">
|
||||
{{ item.start_date ? item.start_date : (item.value.placeholder_start || $lang('components.ns-newform.startDate')) }}
|
||||
</view>
|
||||
</view>
|
||||
@@ -189,9 +192,9 @@
|
||||
mode="date"
|
||||
:value="item.end_date"
|
||||
@change="bindEndDateChange($event, index)"
|
||||
>
|
||||
>
|
||||
<view class="picker-box">
|
||||
<view :class="['uni-input', !item.end_date ? 'color-tip' : '']">
|
||||
<view :class="['uni-input', !item.end_date ? 'color-tip' : '']" :style="item._uniInputStyle + (item.end_date ? item._valueTextStyle : item._tipTextStyle)">
|
||||
{{ item.end_date ? item.end_date : (item.value.placeholder_end || $lang('components.ns-newform.endDate')) }}
|
||||
</view>
|
||||
</view>
|
||||
@@ -203,18 +206,18 @@
|
||||
|
||||
<!-- 时间选择器 -->
|
||||
<view v-if="item.controller == 'Time'" class="order-wrap" :class="rowWrapClass" :style="item._fieldWrapStyle">
|
||||
<view :class="['order-cell', { 'vertical-layout': mode === 'vertical' }]">
|
||||
<view :class="['order-cell', { 'vertical-layout': mode === 'vertical' }]" :style="rowCellStyle">
|
||||
<view class="name" :style="item._labelStyle">
|
||||
<text class="required" :style="item._starStyle">{{ item.value.required ? '*' : '' }}</text>
|
||||
<text class="tit">{{ item.value.title }}</text><text v-if="requiredPosition === 'after'" class="required" :style="item._starStyle">{{ item.value.required ? '*' : '' }}</text>
|
||||
<text class="tit" :style="item._titStyle">{{ item.value.title }}</text><text v-if="requiredPosition === 'after'" class="required" :style="item._starStyle">{{ item.value.required ? '*' : '' }}</text>
|
||||
</view>
|
||||
<view :class="['box', 'box-flex', 'align-right']" :style="item._controlBoxStyle">
|
||||
<picker
|
||||
mode="time"
|
||||
:value="item.val"
|
||||
@change="bindTimeChange($event, index)"
|
||||
>
|
||||
<view :class="['uni-input', !item.val ? 'color-tip' : '']">
|
||||
>
|
||||
<view :class="['uni-input', !item.val ? 'color-tip' : '']" :style="item._uniInputStyle + (item.val ? item._valueTextStyle : item._tipTextStyle)">
|
||||
{{ item.val ? item.val : (item.value.placeholder || $lang('components.ns-newform.timePlaceholder')) }}
|
||||
</view>
|
||||
</picker>
|
||||
@@ -225,10 +228,10 @@
|
||||
|
||||
<!-- 时间范围选择器 -->
|
||||
<view v-if="item.controller == 'Timelimit'" class="order-wrap" :class="rowWrapClass" :style="item._fieldWrapStyle">
|
||||
<view :class="['order-cell', 'flex-box', { 'vertical-layout': mode === 'vertical' }]">
|
||||
<view :class="['order-cell', 'flex-box', { 'vertical-layout': mode === 'vertical' }]" :style="rowCellStyle">
|
||||
<view class="name" :style="item._labelStyle">
|
||||
<text class="required" :style="item._starStyle">{{ item.value.required ? '*' : '' }}</text>
|
||||
<text class="tit">{{ item.value.title }}</text><text v-if="requiredPosition === 'after'" class="required" :style="item._starStyle">{{ item.value.required ? '*' : '' }}</text>
|
||||
<text class="tit" :style="item._titStyle">{{ item.value.title }}</text><text v-if="requiredPosition === 'after'" class="required" :style="item._starStyle">{{ item.value.required ? '*' : '' }}</text>
|
||||
</view>
|
||||
<view :class="['box', 'date-boxs', 'align-center']" :style="item._boxAlignStyle">
|
||||
<view class="date-box">
|
||||
@@ -236,9 +239,9 @@
|
||||
mode="time"
|
||||
:value="item.start_time"
|
||||
@change="bindStartTimeChange($event, index)"
|
||||
>
|
||||
>
|
||||
<view class="picker-box">
|
||||
<view :class="['uni-input', !item.start_time ? 'color-tip' : '']">
|
||||
<view :class="['uni-input', !item.start_time ? 'color-tip' : '']" :style="item._uniInputStyle + (item.start_time ? item._valueTextStyle : item._tipTextStyle)">
|
||||
{{ item.start_time ? item.start_time : (item.value.placeholder_start || $lang('components.ns-newform.startTime')) }}
|
||||
</view>
|
||||
</view>
|
||||
@@ -250,9 +253,9 @@
|
||||
mode="time"
|
||||
:value="item.end_time"
|
||||
@change="bindEndTimeChange($event, index)"
|
||||
>
|
||||
>
|
||||
<view class="picker-box">
|
||||
<view :class="['uni-input', !item.end_time ? 'color-tip' : '']">
|
||||
<view :class="['uni-input', !item.end_time ? 'color-tip' : '']" :style="item._uniInputStyle + (item.end_time ? item._valueTextStyle : item._tipTextStyle)">
|
||||
{{ item.end_time ? item.end_time : (item.value.placeholder_end || $lang('components.ns-newform.endTime')) }}
|
||||
</view>
|
||||
</view>
|
||||
@@ -264,10 +267,10 @@
|
||||
|
||||
<!-- 城市选择器 -->
|
||||
<view v-if="item.controller == 'City'" class="order-wrap" :class="rowWrapClass" :style="item._fieldWrapStyle">
|
||||
<view :class="['order-cell', { 'vertical-layout': mode === 'vertical' }]">
|
||||
<view :class="['order-cell', { 'vertical-layout': mode === 'vertical' }]" :style="rowCellStyle">
|
||||
<view class="name" :style="item._labelStyle">
|
||||
<text class="required" :style="item._starStyle">{{ item.value.required ? '*' : '' }}</text>
|
||||
<text class="tit">{{ item.value.title }}</text><text v-if="requiredPosition === 'after'" class="required" :style="item._starStyle">{{ item.value.required ? '*' : '' }}</text>
|
||||
<text class="tit" :style="item._titStyle">{{ item.value.title }}</text><text v-if="requiredPosition === 'after'" class="required" :style="item._starStyle">{{ item.value.required ? '*' : '' }}</text>
|
||||
</view>
|
||||
<view :class="['box', 'box-flex', 'align-right']" :style="item._controlBoxStyle">
|
||||
<pick-regions
|
||||
@@ -275,7 +278,7 @@
|
||||
:selectArr="item.select_arr"
|
||||
@getRegions="handleGetRegions($event, index)"
|
||||
>
|
||||
<view :class="['select-address', !item.val ? 'empty' : '', !item.val ? 'color-tip' : '']">
|
||||
<view :class="['select-address', !item.val ? 'empty' : '', !item.val ? 'color-tip' : '']" :style="item._uniInputStyle + (item.val ? item._valueTextStyle : item._tipTextStyle)">
|
||||
{{ item.val ? item.val : (item.value.placeholder || (item.value.default_type == 1 ? $lang('components.ns-newform.selectProvinceCity') : $lang('components.ns-newform.selectProvinceCityDistrict'))) }}
|
||||
</view>
|
||||
</pick-regions>
|
||||
@@ -300,8 +303,8 @@
|
||||
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>
|
||||
:style="'font-size:' + ((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>
|
||||
@@ -440,8 +443,31 @@ export default {
|
||||
{},
|
||||
...platformKeys.map(k => (terminalBlock[k] || formConfigRoot[k] || {}))
|
||||
);
|
||||
// 合并:defaults <- globalCfg <- platformCfg
|
||||
return Object.assign({}, defaults, globalCfg, platformCfg);
|
||||
// H5 终端配置作为"后台样式基线":商家端编辑器以 H5 预览为准,很多样式只落在
|
||||
// formConfig.terminal.h5(或全局 base)里。若当前平台(如 weapp)没有该键,
|
||||
// 小程序端会一直吃内置默认 → 表现为"后台改了行内边距,H5 变、小程序不变"。
|
||||
// 这里在内置默认之上、全局配置之下插入 h5 基线,保证后台改的样式小程序端同样跟随。
|
||||
let baselineCfg = {};
|
||||
if (this.platform !== 'h5') {
|
||||
const h5Block = terminalBlock['h5'] || formConfigRoot['h5'];
|
||||
if (h5Block && typeof h5Block === 'object') baselineCfg = h5Block;
|
||||
}
|
||||
// 合并:defaults <- h5基线 <- 全局 <- 当前平台
|
||||
const merged = Object.assign({}, defaults, baselineCfg, globalCfg, platformCfg);
|
||||
// 过滤 undefined / null / ''(保留 0 / false):Object.assign 会用 undefined 覆盖默认值,
|
||||
// 导致后续 $util.rpx(undefined) = '' → 内联样式整条失效、静默回退到 SCSS 默认值
|
||||
Object.keys(merged).forEach((k) => {
|
||||
const v = merged[k];
|
||||
if (v === undefined || v === null || v === '') delete merged[k];
|
||||
});
|
||||
// 行内上下边距键名兼容:后台 / 不同版本序列化路径可能下发
|
||||
// row_padding_y / rowPaddingY / row_padding / rowPadding 等,取第一个有效值
|
||||
const rowPadY = this.pickConfigNum(
|
||||
['rowPaddingY', 'row_padding_y', 'rowPadding', 'row_padding', 'rowPaddingVertical', 'row_padding_vertical', 'rowPaddingTop', 'row_padding_top'],
|
||||
terminalBlock, platformKeys, formConfigRoot, baselineCfg
|
||||
);
|
||||
if (rowPadY !== null) merged.rowPaddingY = rowPadY;
|
||||
return merged;
|
||||
},
|
||||
nameWidthStyle() {
|
||||
if (this.mode === 'vertical') {
|
||||
@@ -474,7 +500,11 @@ export default {
|
||||
'--tip-color': cfg.tipColor,
|
||||
'--tip-bg-color': cfg.tipBgColor,
|
||||
'--row-padding-y': this.$util.rpx(cfg.rowPaddingY),
|
||||
'--row-min-height': this.$util.rpx(cfg.rowMinHeight),
|
||||
// 行最小高度:默认 0(不绑定),行高由 row-padding-y + input-height 撑开,后台改样式即时生效;
|
||||
// 仅后台显式配置 row_min_height 时按其值生效,与 rowCellStyle 内联输出保持一致(两端同步)
|
||||
'--row-min-height': (this.explicitConfigNum('rowMinHeight', 'row_min_height') || 0) > 0
|
||||
? this.$util.rpx(cfg.rowMinHeight)
|
||||
: '0rpx',
|
||||
'--label-gap': this.$util.rpx(cfg.labelGap),
|
||||
'--form-bg-color': cfg.formBgColor,
|
||||
'--row-divider-color': cfg.rowDividerColor,
|
||||
@@ -507,10 +537,13 @@ export default {
|
||||
rowWrapClass() {
|
||||
return this.config.showRowDivider === false ? 'order-wrap no-divider' : 'order-wrap';
|
||||
},
|
||||
// 行最小高度
|
||||
// 行最小高度:不再默认绑定。行高改由后台配置的行内边距(row_padding_y)+ 控件高度(input_height)
|
||||
// 自然撑开,后台改样式立即生效;仅当后台**显式**配置了 row_min_height 时才按后台值输出。
|
||||
rowCellStyle() {
|
||||
const h = this.explicitConfigNum('rowMinHeight', 'row_min_height');
|
||||
if (h === null || h <= 0) return '';
|
||||
return this.$util.objToStyle({
|
||||
'min-height': this.$util.rpx(this.config.rowMinHeight)
|
||||
'min-height': this.$util.rpx(h)
|
||||
});
|
||||
},
|
||||
// 标签与输入框间距
|
||||
@@ -522,10 +555,9 @@ export default {
|
||||
'margin-right': this.$util.rpx(this.config.labelGap)
|
||||
};
|
||||
},
|
||||
// 输入框样式(全局,未指定 item 时使用)
|
||||
// 输入框样式(全局,未指定 item 时使用):不绑定最小高度,高度由后台 input_height 决定
|
||||
inputStyle() {
|
||||
return this.$util.objToStyle({
|
||||
'min-height': this.$util.rpx(this.config.inputHeight),
|
||||
'border-radius': this.$util.rpx(this.config.inputRadius)
|
||||
});
|
||||
},
|
||||
@@ -583,6 +615,56 @@ export default {
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 按别名列表取配置数值:优先级 = 当前平台块 > 全局 > h5基线
|
||||
// 返回数字;都不存在返回 null(调用方决定是否回退内置默认值)
|
||||
pickConfigNum(aliasList, terminalBlock, platformKeys, formConfigRoot, baselineCfg) {
|
||||
const blocks = [];
|
||||
(platformKeys || []).forEach((k) => {
|
||||
if (terminalBlock && terminalBlock[k]) blocks.push(terminalBlock[k]);
|
||||
if (formConfigRoot && formConfigRoot[k]) blocks.push(formConfigRoot[k]);
|
||||
});
|
||||
blocks.push(formConfigRoot || {});
|
||||
blocks.push(baselineCfg || {});
|
||||
for (let i = 0; i < blocks.length; i++) {
|
||||
const block = blocks[i];
|
||||
if (!block || typeof block !== 'object') continue;
|
||||
for (let j = 0; j < aliasList.length; j++) {
|
||||
const v = block[aliasList[j]];
|
||||
if (v !== undefined && v !== null && v !== '') {
|
||||
const n = parseInt(v, 10);
|
||||
if (!isNaN(n)) return n;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
},
|
||||
// 读取后台"显式配置"的数值(camel / snake 双键兼容,含平台覆盖块 terminal[platform])。
|
||||
// 与 computed config 的区别:config 会混入组件内置默认值,无法区分"后台配了"和"没配";
|
||||
// 这里只在后台确实下发该字段时才返回数字,未配置返回 null(用于"未配置就不绑定样式"的场景,
|
||||
// 例如行最小高度:未配置时不输出 min-height,行高完全由后台的行内边距 + 控件高度决定)。
|
||||
explicitConfigNum(camelKey, snakeKey) {
|
||||
const root = this.formConfig || {};
|
||||
const terminal = root.terminal || {};
|
||||
const keys = this.platformConfigKeys || [];
|
||||
let raw;
|
||||
// 1) 平台覆盖优先(terminal.h5 / terminal.weapp ... 或扁平 formConfig.mp)
|
||||
for (let i = 0; i < keys.length; i++) {
|
||||
const block = terminal[keys[i]] || root[keys[i]];
|
||||
if (block && typeof block === 'object') {
|
||||
const v = block[camelKey] !== undefined ? block[camelKey] : block[snakeKey];
|
||||
if (v !== undefined && v !== null && v !== '') {
|
||||
raw = v;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
// 2) 全局配置
|
||||
if (raw === undefined || raw === null || raw === '') {
|
||||
raw = root[camelKey] !== undefined ? root[camelKey] : root[snakeKey];
|
||||
}
|
||||
const n = parseInt(raw, 10);
|
||||
return isNaN(n) ? null : n;
|
||||
},
|
||||
// 预计算每项的样式(写到 item._labelStyle / item._boxAlignStyle),
|
||||
// 避免在模板里以方法形式调用:微信小程序 WXML 不支持 :style="getXxx(item)" 语法。
|
||||
// 触发时机:processFormData 末尾 / calculateMaxLabelWidth 末尾 / config 变化 / mode 变化。
|
||||
@@ -598,7 +680,13 @@ export default {
|
||||
return isNaN(n) ? null : n;
|
||||
};
|
||||
// 行容器基础上下内边距(原 rowWrapStyle 语义:全局行间距 rowPaddingY;左右恒 0)
|
||||
const rowPadY = this.$util.rpx(this.config.rowPaddingY);
|
||||
// 兜底 24rpx:config.rowPaddingY 缺失时 $util.rpx 会返回 '',导致整条 padding 简写失效
|
||||
const rowPadYNum = parseInt(this.config.rowPaddingY, 10);
|
||||
const rowPadY = this.$util.rpx(isNaN(rowPadYNum) ? 24 : rowPadYNum);
|
||||
// #ifdef MP-WEIXIN
|
||||
// 诊断用(确认与后台同步后可删除):在微信开发者工具控制台查看后台"行内上下边距"是否送达小程序端
|
||||
console.log('[ns-newform] rowPaddingY =', this.config.rowPaddingY, '→ padding =', rowPadY);
|
||||
// #endif
|
||||
const clampRpx = (n, min, max) => this.$util.rpx(Math.max(min, Math.min(max, n)));
|
||||
// 字段级控件尺寸(宽度 / 高度 / 选项间距):'' / null / undefined = 未设置,沿用全局(宽度默认占满 100%)
|
||||
// clamp 与后端 fieldSizeStyleMixin 一致(width 0..750 / height 0..400 / gap 0..200 rpx)
|
||||
@@ -608,6 +696,35 @@ export default {
|
||||
return isNaN(n) ? null : this.$util.rpx(Math.max(min, Math.min(max, n)));
|
||||
};
|
||||
|
||||
// ---- 全局动态样式值(后台 formConfig 下发):直出 inline,跨端同步 ----
|
||||
// 背景:小程序 WXSS 对 .form-wrap inline style 里的自定义属性(--*)支持不可靠,
|
||||
// 子元素 var(--x, fallback) 会静默回退到 SCSS 默认值,导致「后台改样式 H5 生效 / 小程序不变」。
|
||||
// 对策:与 _starStyle 同思路,把动态值直接算好写到每个消费元素的内联 style 上
|
||||
//(小程序原生元素 inline style 可靠);H5 端 CSS 变量通道保留,两端结果一致。
|
||||
const cfg = this.config;
|
||||
const numOr = (v, fb) => {
|
||||
const n = parseFloat(v);
|
||||
return isNaN(n) ? fb : n;
|
||||
};
|
||||
const gInputHNum = numOr(cfg.inputHeight, 84);
|
||||
const gInputH = this.$util.rpx(gInputHNum);
|
||||
const gInputR = this.$util.rpx(numOr(cfg.inputRadius, 16));
|
||||
const gInputBorder = '1rpx solid ' + (cfg.inputBorderColor || '#e5e7eb');
|
||||
const gInputBg = cfg.inputBgColor || '#ffffff';
|
||||
const gPhColor = cfg.placeholderColor || '#9ca3af';
|
||||
const gPhFs = this.$util.rpx(numOr(cfg.placeholderFontSize, 28));
|
||||
const gFormBg = cfg.formBgColor || '#ffffff';
|
||||
const gDividerColor = cfg.rowDividerColor || '#f0f0f0';
|
||||
const showDivider = cfg.showRowDivider !== false;
|
||||
const labelGapRpx = this.$util.rpx(numOr(cfg.labelGap, 20));
|
||||
const isVertical = this.mode === 'vertical';
|
||||
// input/textarea 是 H5 下编译为 uni-input/uni-textarea 的自定义组件,
|
||||
// 内联 style 只会落在组件根节点(会与内部真实输入框样式叠加),因此元素级内联仅小程序端输出
|
||||
let mpElementInline = false;
|
||||
// #ifdef MP-WEIXIN || MP-ALIPAY || MP-BAIDU || MP-TOUTIAO || MP-QQ || MP-KUAISHOU || MP-LARKDINGTALK || MP-JD || MP-XHS || MP-HARMONY
|
||||
mpElementInline = true;
|
||||
// #endif
|
||||
|
||||
this.formData.forEach((item) => {
|
||||
const itemValue = (item && item.value) || {};
|
||||
|
||||
@@ -633,15 +750,33 @@ export default {
|
||||
if (mRight !== null) fieldMargin['margin-right'] = clampRpx(mRight, -100, 400);
|
||||
const pTop = spNum(itemValue.padding_top), pBottom = spNum(itemValue.padding_bottom),
|
||||
pLeft = spNum(itemValue.padding_left), pRight = spNum(itemValue.padding_right);
|
||||
// 字段级"上下内边距"兼容:部分后台序列化路径下发 padding_y / paddingY(同时作用于上下),
|
||||
// 无 padding_top / padding_bottom 单边值时用它覆盖全局 rowPaddingY
|
||||
const pY = spNum(itemValue.padding_y !== undefined ? itemValue.padding_y : itemValue.paddingY);
|
||||
// 单边取值的 helper:字段级优先 → 全局(行内上下边距 rowPaddingY / 左右恒 0)→ 兜底 0rpx。
|
||||
// 兜底必须给具体值:空字符串会让整条 padding 简写失效,小程序端就"改了没反应"
|
||||
const padOf = (explicit, globalVal, min, max) => {
|
||||
if (explicit !== null) return this.$util.rpx(Math.max(min, Math.min(max, explicit)));
|
||||
return (globalVal === undefined || globalVal === null || globalVal === '') ? '0rpx' : globalVal;
|
||||
};
|
||||
const padT = padOf(pTop !== null ? pTop : pY, rowPadY, 0, 400);
|
||||
const padB = padOf(pBottom !== null ? pBottom : pY, rowPadY, 0, 400);
|
||||
const padL = padOf(pLeft, '0rpx', 0, 400);
|
||||
const padR = padOf(pRight, '0rpx', 0, 400);
|
||||
// 11 种输入控件行容器(.order-wrap,原 rowWrapStyle 承载层):
|
||||
// margin 直出;padding 覆盖对应边(未设置的边跟随全局:上下 rowPaddingY / 左右 0)
|
||||
// margin 直出;padding 用**简写**一次性直出四边(小程序端对 padding 长写支持不稳,
|
||||
// 简写可保证后台配置的上下行内边距一定生效)
|
||||
if (item.controller != 'InfoCard') {
|
||||
const fieldWrapStyle = Object.assign({}, fieldMargin);
|
||||
fieldWrapStyle['padding-top'] = pTop !== null ? clampRpx(pTop, 0, 400) : rowPadY;
|
||||
fieldWrapStyle['padding-bottom'] = pBottom !== null ? clampRpx(pBottom, 0, 400) : rowPadY;
|
||||
fieldWrapStyle['padding-left'] = pLeft !== null ? clampRpx(pLeft, 0, 400) : '0';
|
||||
fieldWrapStyle['padding-right'] = pRight !== null ? clampRpx(pRight, 0, 400) : '0';
|
||||
this.$set(item, '_fieldWrapStyle', fieldWrapStyle);
|
||||
fieldWrapStyle['padding'] = `${padT} ${padR} ${padB} ${padL}`;
|
||||
// 表单底色 / 行分割线颜色直出 inline(替代 var(--form-bg-color) / var(--row-divider-color))
|
||||
fieldWrapStyle['background'] = gFormBg;
|
||||
if (showDivider) {
|
||||
fieldWrapStyle['border-bottom-color'] = gDividerColor;
|
||||
}
|
||||
// ⚠️ 必须输出字符串:小程序端 :style 绑定对象会渲染成 "[object Object]"(样式整体失效),
|
||||
// 这正是"后台改样式 H5 生效、小程序不生效"的根因;objToStyle 转成 kebab-case 字符串后两端通用
|
||||
this.$set(item, '_fieldWrapStyle', this.$util.objToStyle(fieldWrapStyle));
|
||||
}
|
||||
|
||||
const style = Object.assign({}, widthStyle);
|
||||
@@ -655,14 +790,61 @@ export default {
|
||||
}
|
||||
// 合并全局的 nameStyle(margin-right)
|
||||
Object.assign(style, nameMarginStyle);
|
||||
// 不绑定 label 最小高度:行高由后台配置的行内边距 + 控件高度决定(后台可自定义)。
|
||||
// 垂直布局下的标签/控件间距同样由后台 label_gap 驱动,直出 inline
|
||||
if (isVertical) {
|
||||
style['margin-bottom'] = labelGapRpx;
|
||||
}
|
||||
|
||||
// $set 包裹以确保新属性是响应式的(Vue 2 对动态加的属性需要 $set)
|
||||
this.$set(item, '_labelStyle', style);
|
||||
// $set 包裹以确保新属性是响应式的(Vue 2 对动态加的属性需要 $set);输出字符串(同上)
|
||||
this.$set(item, '_labelStyle', this.$util.objToStyle(style));
|
||||
|
||||
// label 标题字号/颜色/字重:item 级覆盖 + 全局,直出 inline(小程序端 var 链不可靠)
|
||||
const itemLabelFs = this.$util.rpx(numOr(itemValue.label_font_size, numOr(cfg.labelFontSize, 28)));
|
||||
const itemLabelColor = itemValue.label_color || cfg.labelColor || '#1f2937';
|
||||
this.$set(item, '_titStyle', this.$util.objToStyle({
|
||||
'font-size': itemLabelFs,
|
||||
'color': itemLabelColor,
|
||||
'font-weight': cfg.labelFontWeight || 600
|
||||
}));
|
||||
|
||||
// 输入框元素级内联(仅小程序端输出,H5 保持 CSS 变量通道,避免 uni-input 根节点样式叠加)
|
||||
if (mpElementInline) {
|
||||
const hNum = cH !== null ? parseFloat(cH) : gInputHNum;
|
||||
this.$set(item, '_inputStyle', this.$util.objToStyle({
|
||||
'font-size': itemLabelFs,
|
||||
'color': itemLabelColor,
|
||||
'height': this.$util.rpx(hNum),
|
||||
'line-height': this.$util.rpx(Math.max(hNum - 2, 0)),
|
||||
'border': gInputBorder,
|
||||
'border-radius': gInputR,
|
||||
'background': gInputBg
|
||||
}));
|
||||
} else {
|
||||
this.$set(item, '_inputStyle', '');
|
||||
}
|
||||
|
||||
// placeholder 颜色/字号:placeholder-style 直出(小程序 placeholder-class 被 scoped 编译后
|
||||
// 无法命中原生 placeholder 节点,placeholder-style 是官方跨端属性)
|
||||
this.$set(item, '_placeholderStyle', 'color: ' + gPhColor + '; font-size: ' + gPhFs + ';');
|
||||
|
||||
// picker 文本区(Select/Date/Time/City 等的 .uni-input / .select-address)与选项文字
|
||||
// 不绑定 min-height:高度由 line-height(后台 inputHeight)+ 行内边距(后台 rowPaddingY)决定
|
||||
this.$set(item, '_uniInputStyle', this.$util.objToStyle({
|
||||
'font-size': itemLabelFs,
|
||||
'line-height': gInputH,
|
||||
'border-radius': gInputR
|
||||
}));
|
||||
this.$set(item, '_valueTextStyle', this.$util.objToStyle({ 'font-size': itemLabelFs, 'color': itemLabelColor }));
|
||||
this.$set(item, '_tipTextStyle', this.$util.objToStyle({ 'font-size': itemLabelFs, 'color': gPhColor }));
|
||||
this.$set(item, '_optionLabelStyle', this.$util.objToStyle({ 'font-size': itemLabelFs, 'color': itemLabelColor }));
|
||||
// 选项选中态图标颜色(字符串,供模板三元拼接)
|
||||
this.$set(item, '_checkedColorStyle', 'color:' + (cfg.checkedColor || '#3b82f6') + ';');
|
||||
|
||||
// item 级别 box 对齐方式(与原 getItemBoxAlignStyle 逻辑一致)
|
||||
// 并入字段级宽度 / 高度(.box 是 flex:1,width 需配合 flex:none 才生效,见 ctrlWidthStyle)
|
||||
const align = itemValue.input_align;
|
||||
this.$set(item, '_boxAlignStyle', Object.assign(align ? { 'text-align': align } : {}, ctrlBoxStyle));
|
||||
this.$set(item, '_boxAlignStyle', this.$util.objToStyle(Object.assign(align ? { 'text-align': align } : {}, ctrlBoxStyle)));
|
||||
|
||||
// 必填星号样式:颜色/字号/间距全部预计算 inline(微信小程序/鸿蒙元服务对
|
||||
// 嵌套 var() fallback 与 .required-after 类 margin 翻转支持不全,动态值不走 CSS 变量链)。
|
||||
@@ -679,7 +861,7 @@ export default {
|
||||
} else {
|
||||
starStyle['margin-right'] = this.$util.rpx(6);
|
||||
}
|
||||
this.$set(item, '_starStyle', starStyle);
|
||||
this.$set(item, '_starStyle', this.$util.objToStyle(starStyle));
|
||||
|
||||
// 选项排列样式:默认 vertical(每项一行),仅当后端显式配置 wrap 时横向换行
|
||||
// 走 inline style(不依赖 dev 编译产物的 SCSS 类,跨端兼容更稳)
|
||||
@@ -699,16 +881,24 @@ export default {
|
||||
}
|
||||
// 字段级选项间距(option_gap):覆盖默认 gap(wrap 12rpx / vertical 20rpx)
|
||||
if (oG !== null) optStyle['gap'] = oG;
|
||||
this.$set(item, '_optionLayoutStyle', optStyle);
|
||||
this.$set(item, '_optionLayoutStyle', this.$util.objToStyle(optStyle));
|
||||
|
||||
// 单行 / 多选行控件(Select、Date、Time、City 的 .box 无 _boxAlignStyle 绑定;
|
||||
// Checkbox / Radio 只需要宽度,高度无意义)单独落 key,供模板直接绑定
|
||||
this.$set(item, '_controlBoxStyle', ctrlBoxStyle);
|
||||
this.$set(item, '_controlWidthStyle', ctrlWidthStyle);
|
||||
this.$set(item, '_controlBoxStyle', this.$util.objToStyle(ctrlBoxStyle));
|
||||
this.$set(item, '_controlWidthStyle', this.$util.objToStyle(ctrlWidthStyle));
|
||||
// textarea:字段级高度直出(宽度走 .box 的 _boxAlignStyle;行高沿用 formConfig),
|
||||
// 同步 min-height 防止被基础规则的 min-height 顶住不缩
|
||||
this.$set(item, '_textareaStyle', Object.assign({}, this.textareaBaseStyle,
|
||||
cH !== null ? { 'height': cH, 'min-height': cH } : {}));
|
||||
// 同步 min-height 防止被基础规则的 min-height 顶住不缩;
|
||||
// 小程序端附加字号/颜色/边框/底色 inline(H5 仍走 CSS 变量,避免 uni-textarea 根节点叠加)
|
||||
this.$set(item, '_textareaStyle', this.$util.objToStyle(Object.assign({},
|
||||
this.textareaBaseStyle,
|
||||
mpElementInline ? {
|
||||
'font-size': itemLabelFs,
|
||||
'color': itemLabelColor,
|
||||
'border': gInputBorder,
|
||||
'background': gInputBg
|
||||
} : {},
|
||||
cH !== null ? { 'height': cH, 'min-height': cH } : {})));
|
||||
|
||||
// 信息卡片:容器底色 + 空白判定 + 分割线样式(与后台 design.js 的
|
||||
// getInfoCardContainerStyle / getInfoCardDividerStyle 对齐;数值单位为 px 直出,
|
||||
@@ -725,11 +915,10 @@ export default {
|
||||
// 字段级间距(InfoCard 行容器即卡片本身,无全局行内边距):margin 直出;
|
||||
// padding 覆盖背景卡默认内边距的对应边(无 bg 时直接生效)
|
||||
Object.keys(fieldMargin).forEach((k) => { wrapStyle[k] = fieldMargin[k]; });
|
||||
if (pTop !== null) wrapStyle['padding-top'] = clampRpx(pTop, 0, 400);
|
||||
if (pBottom !== null) wrapStyle['padding-bottom'] = clampRpx(pBottom, 0, 400);
|
||||
if (pLeft !== null) wrapStyle['padding-left'] = clampRpx(pLeft, 0, 400);
|
||||
if (pRight !== null) wrapStyle['padding-right'] = clampRpx(pRight, 0, 400);
|
||||
this.$set(item, '_infoCardWrapStyle', wrapStyle);
|
||||
if (pTop !== null || pBottom !== null || pLeft !== null || pRight !== null) {
|
||||
wrapStyle['padding'] = `${padOf(pTop, '0rpx', 0, 400)} ${padOf(pRight, '0rpx', 0, 400)} ${padOf(pBottom, '0rpx', 0, 400)} ${padOf(pLeft, '0rpx', 0, 400)}`;
|
||||
}
|
||||
this.$set(item, '_infoCardWrapStyle', this.$util.objToStyle(wrapStyle));
|
||||
// 空白判定:去标签 + + 去空白后为空 → 不渲染富文本(与后台一致,避免空白块)
|
||||
const raw = itemValue.content == null ? '' : String(itemValue.content);
|
||||
const textOnly = raw.replace(/<[^>]+>/g, '').replace(/ /gi, ' ').replace(/\s/g, '');
|
||||
@@ -755,9 +944,9 @@ export default {
|
||||
dStyle['margin-left'] = 'auto';
|
||||
dStyle['margin-right'] = 'auto';
|
||||
}
|
||||
this.$set(item, '_infoCardDividerStyle', dStyle);
|
||||
this.$set(item, '_infoCardDividerStyle', this.$util.objToStyle(dStyle));
|
||||
} else {
|
||||
this.$set(item, '_infoCardDividerStyle', {});
|
||||
this.$set(item, '_infoCardDividerStyle', '');
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -769,6 +958,9 @@ export default {
|
||||
calculateMaxLabelWidth() {
|
||||
if (this.mode === 'vertical' || !this.formData || this.formData.length === 0) {
|
||||
this.maxLabelWidth = 0;
|
||||
// 垂直布局(默认)也要重算每项内联样式:formConfig 变化(后台改样式)后
|
||||
// 小程序端依赖 recomputeItemStyles 把动态值直出 inline,提前 return 会漏刷
|
||||
this.recomputeItemStyles();
|
||||
return;
|
||||
}
|
||||
// 与后端 design.js calculateMaxLabelWidth 算法对齐:
|
||||
@@ -1320,7 +1512,9 @@ view {
|
||||
background: transparent;
|
||||
position: relative;
|
||||
padding: 0;
|
||||
min-height: var(--row-min-height, 60rpx);
|
||||
// 不绑定固定最小高度:行高由后台 row_padding_y + input_height 决定(后台可自定义);
|
||||
// 后台显式配置 row_min_height 时由 rowCellStyle 内联输出
|
||||
min-height: var(--row-min-height, 0);
|
||||
flex-wrap: nowrap;
|
||||
|
||||
// 水平布局(默认)
|
||||
@@ -1386,7 +1580,8 @@ view {
|
||||
white-space: nowrap;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
min-height: var(--input-height, 60rpx);
|
||||
// 不绑定最小高度:标签高度由字号(后台 label_font_size)+ 行内边距决定(两端一致)
|
||||
min-height: 0;
|
||||
line-height: 1.5;
|
||||
|
||||
.tit {
|
||||
@@ -1582,7 +1777,8 @@ view {
|
||||
color: var(--label-color, #1f2937);
|
||||
font-size: var(--label-font-size, 28rpx);
|
||||
transition: all 0.3s ease;
|
||||
min-height: var(--input-height, 84rpx);
|
||||
// 不绑定最小高度:高度由 line-height(后台 input_height,inline 直出)决定
|
||||
min-height: 0;
|
||||
box-sizing: border-box;
|
||||
line-height: var(--input-height, 84rpx);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user