This commit is contained in:
丁杰 2026-06-10 16:23:47 +08:00
parent 6aa66ce311
commit 6b659a6878
5 changed files with 528 additions and 398 deletions

View File

@ -1,122 +1,49 @@
<template> <template>
<view class="form-image"> <ali-oss-uploader v-model="currentValue" :max-count="maxCount" :max-size="maxSize" accept="image/*" :userId="userId"
<view class="image-list"> :type="ossType" width="100%" @input="onInput" @change="onChange" />
<view v-for="(item, index) in imageList" :key="index" class="image-item">
<image :src="item" mode="aspectFill" class="preview-image" @click="previewImage(index)" />
<view class="delete-btn" @click.stop="deleteImage(index)">
<text class="delete-icon">&times;</text>
</view>
</view>
<view v-if="imageList.length < maxCount" class="image-item add-btn" @click="chooseImage">
<text class="add-icon">+</text>
</view>
</view>
</view>
</template> </template>
<script> <script>
import AliOssUploader from '../ali-oss-uploader/ali-oss-uploader.vue'
export default { export default {
name: 'FormItemImage', name: 'FormItemImage',
components: { AliOssUploader },
props: { props: {
value: { type: Array, default: function () { return []; } }, value: { type: Array, default: function () { return []; } },
maxCount: { type: Number, default: 9 } maxCount: { type: Number, default: 9 },
maxSize: { type: Number, default: 5 }
}, },
data: function () { data: function () {
return { return {
imageList: [] currentValue: []
}; };
}, },
computed: {
userId: function () {
return this.$store && this.$store.state && this.$store.state.sjInfo
? this.$store.state.sjInfo.id : null;
},
ossType: function () {
var artisanType = getApp().globalData ? getApp().globalData.artisanType : 0;
return (artisanType || 0) + 1;
}
},
watch: { watch: {
value: { value: {
handler: function (val) { handler: function (val) {
this.imageList = Array.isArray(val) ? val.slice() : []; this.currentValue = Array.isArray(val) ? val.slice() : [];
}, },
immediate: true immediate: true
} }
}, },
methods: { methods: {
chooseImage: function () { onInput: function (list) {
var remaining = this.maxCount - this.imageList.length; this.$emit('input', list);
if (remaining <= 0) return;
var self = this;
uni.chooseImage({
count: remaining,
sizeType: ['compressed'],
sourceType: ['album', 'camera'],
success: function (res) {
self.imageList = self.imageList.concat(res.tempFilePaths);
self.$emit('input', self.imageList);
}
});
}, },
deleteImage: function (index) { onChange: function (list) {
this.imageList.splice(index, 1); this.$emit('change', list);
this.$emit('input', this.imageList);
},
previewImage: function (index) {
uni.previewImage({
urls: this.imageList,
current: index
});
} }
} }
}; };
</script> </script>
<style scoped>
.form-image {
width: 100%;
}
.image-list {
display: flex;
flex-wrap: wrap;
}
.image-item {
width: 200rpx;
height: 200rpx;
margin-right: 10rpx;
margin-bottom: 20rpx;
border-radius: 8rpx;
overflow: hidden;
position: relative;
}
.preview-image {
width: 200rpx;
height: 200rpx;
border-radius: 8rpx;
}
.delete-btn {
position: absolute;
top: -20rpx;
right: -20rpx;
width: 40rpx;
height: 40rpx;
background-color: rgba(0, 0, 0, 0.5);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
}
.delete-icon {
color: #ffffff;
font-size: 32rpx;
}
.add-btn {
background-color: #ffffff;
display: flex;
align-items: center;
justify-content: center;
border: 2rpx dashed #dddddd;
}
.add-icon {
font-size: 60rpx;
color: #999999;
}
</style>

View File

@ -1,50 +1,59 @@
<template> <template>
<view class="form-video"> <view>
<view v-if="videoUrl" class="video-preview"> <ali-oss-uploader v-if="!currentValue" :max-count="1" :max-size="maxSize" accept="video/*" :userId="userId"
<video :src="videoUrl" class="preview-video" controls /> :type="ossType" width="100%" @input="onInput" @change="onChange" />
<view v-else class="video-wrapper">
<video :src="currentValue" class="preview-video" controls />
<view class="delete-btn" @click="deleteVideo"> <view class="delete-btn" @click="deleteVideo">
<text class="delete-icon">&times;</text> <text class="delete-icon">&times;</text>
</view> </view>
</view> </view>
<view v-else class="upload-btn" @click="chooseVideo">
<text class="plus-icon">+</text>
</view>
</view> </view>
</template> </template>
<script> <script>
import AliOssUploader from '../ali-oss-uploader/ali-oss-uploader.vue'
export default { export default {
name: 'FormItemVideo', name: 'FormItemVideo',
components: { AliOssUploader },
props: { props: {
value: { type: String, default: '' } value: { type: String, default: '' },
maxSize: { type: Number, default: 100 }
}, },
data: function () { data: function () {
return { return {
videoUrl: '' currentValue: ''
}; };
}, },
computed: {
userId: function () {
return this.$store && this.$store.state && this.$store.state.sjInfo
? this.$store.state.sjInfo.id : null;
},
ossType: function () {
var artisanType = getApp().globalData ? getApp().globalData.artisanType : 0;
return (artisanType || 0) + 1;
}
},
watch: { watch: {
value: { value: {
handler: function (val) { handler: function (val) {
this.videoUrl = val || ''; this.currentValue = val || '';
}, },
immediate: true immediate: true
} }
}, },
methods: { methods: {
chooseVideo: function () { onInput: function (val) {
var self = this; this.currentValue = val;
uni.chooseVideo({ this.$emit('input', val);
sourceType: ['album', 'camera'], },
maxDuration: 60, onChange: function (val) {
success: function (res) { this.$emit('change', val);
self.videoUrl = res.tempFilePath;
self.$emit('input', self.videoUrl);
}
});
}, },
deleteVideo: function () { deleteVideo: function () {
this.videoUrl = ''; this.currentValue = '';
this.$emit('input', ''); this.$emit('input', '');
} }
} }
@ -52,14 +61,10 @@ export default {
</script> </script>
<style scoped> <style scoped>
.form-video { .video-wrapper {
width: 100%; position: relative;
}
.video-preview {
width: 200rpx; width: 200rpx;
height: 200rpx; height: 200rpx;
position: relative;
} }
.preview-video { .preview-video {
@ -70,35 +75,22 @@ export default {
.delete-btn { .delete-btn {
position: absolute; position: absolute;
top: -20rpx; top: -10rpx;
right: -20rpx; right: -10rpx;
width: 40rpx; width: 40rpx;
height: 40rpx; height: 40rpx;
background-color: rgba(0, 0, 0, 0.5); background-color: #ff4d4f;
color: white;
border-radius: 50%; border-radius: 50%;
display: flex; display: flex;
align-items: center;
justify-content: center; justify-content: center;
align-items: center;
font-size: 32rpx;
z-index: 1;
} }
.delete-icon { .delete-icon {
color: #ffffff; color: #ffffff;
font-size: 32rpx; font-size: 32rpx;
} }
.upload-btn {
width: 200rpx;
height: 200rpx;
background-color: #ffffff;
border: 2rpx dashed #dddddd;
border-radius: 8rpx;
display: flex;
justify-content: center;
align-items: center;
}
.plus-icon {
font-size: 60rpx;
color: #999999;
}
</style> </style>

View File

@ -1,27 +1,66 @@
<template> <template>
<view class="dynamic-form"> <view class="dynamic-form">
<view v-for="(field, index) in fields" :key="field.key" class="form-section"> <view v-for="(group, gi) in fields" :key="gi" class="form-group">
<!-- 单行文本 --> <!-- single -->
<view v-if="field.type === 'text'" class="form-item"> <block v-if="group.type === 'single'">
<text :class="['form-label', field.is_required === true ? 'required' : '']">{{ field.label }}</text> <view v-if="group.field.type === 'text'" class="form-item">
<input type="text" :value="formData[field.key]" :placeholder="field.placeholder" placeholder-class="placeholder" <text :class="['form-label', group.field.is_required === true ? 'required' : '']">{{ group.field.label }}</text>
class="form-input" @input="onInput(field.key, $event)" /> <input type="text" :value="formData[group.field.key]" :placeholder="group.field.placeholder"
placeholder-class="placeholder" class="form-input" @input="onInput(group.field.key, $event)" />
</view> </view>
<view v-else-if="group.field.type === 'number'" class="form-item" :style="group.field.tip ? 'flex-wrap: wrap;' : ''">
<!-- 多行文本 --> <text :class="['form-label', group.field.is_required === true ? 'required' : '']">{{ group.field.label }}</text>
<view v-else-if="field.type === 'textarea'" class="detail-card"> <input type="digit" :value="formData[group.field.key]" :placeholder="group.field.placeholder"
<view class="detail-row"> placeholder-class="placeholder" class="form-input" @input="onInput(group.field.key, $event)" />
<text v-if="group.field.tips && !group.field.tip" class="form-tips">{{ group.field.tips }}</text>
<view v-if="group.field.tip" class="price-tip">
<text class="price-tip-card">{{ group.field.tip }}</text>
</view>
</view>
<view v-else-if="group.field.type === 'image_multi'" class="form-item-up upload-item">
<view>
<text :class="['form-label', group.field.is_required === true ? 'required' : '']">{{ group.field.label }}</text>
<text class="upload-tip">(仅可上传{{ group.field.maxCount || 9 }}个图片)</text>
</view>
<view class="upload-content">
<form-item-image :value="formData[group.field.key]" :maxCount="group.field.maxCount || 9"
@input="onInput(group.field.key, $event)" />
</view>
</view>
<view v-else-if="group.field.type === 'video'" class="form-item-up upload-item">
<view>
<text :class="['form-label', group.field.is_required === true ? 'required' : '']">{{ group.field.label }}</text>
<text class="upload-tip">(仅可上传1个视频)</text>
</view>
<view class="upload-content">
<form-item-video :value="formData[group.field.key]" @input="onInput(group.field.key, $event)" />
</view>
</view>
<view v-else-if="group.field.type === 'richtext_editor'" class="form-item">
<text :class="['form-label', group.field.is_required === true ? 'required' : '']">{{ group.field.label }}</text>
<form-item-richtext :value="formData[group.field.key]" @input="onInput(group.field.key, $event)" />
</view>
</block>
<!-- section -->
<view v-if="group.type === 'section'" class="section-card">
<view v-if="group.sectionTitle" class="section-title-bar">
<text class="section-title">{{ group.sectionTitle }}</text>
</view>
<view v-for="(field, fi) in group.fields" :key="field.key || fi" class="section-field">
<view v-if="field.type === 'text'" class="form-item no-bg">
<text :class="['form-label', field.is_required === true ? 'required' : '']">{{ field.label }}</text>
<input type="text" :value="formData[field.key]" :placeholder="field.placeholder"
placeholder-class="placeholder" class="form-input" @input="onInput(field.key, $event)" />
</view>
<view v-else-if="field.type === 'textarea'" class="detail-row">
<text :class="['form-label', field.is_required === true ? 'required' : '']">{{ field.label }}</text> <text :class="['form-label', field.is_required === true ? 'required' : '']">{{ field.label }}</text>
<view class="detail-textarea-wrap"> <view class="detail-textarea-wrap">
<textarea class="detail-textarea" :value="formData[field.key]" :placeholder="field.placeholder" <textarea class="detail-textarea" :value="formData[field.key]" :placeholder="field.placeholder"
placeholder-class="placeholder" maxlength="200" @input="onInput(field.key, $event)" /> placeholder-class="placeholder" maxlength="200" @input="onInput(field.key, $event)" />
<text class="detail-count">{{ (formData[field.key] || '').length }}/200</text> <text class="detail-count">{{ (formData[field.key] || "").length }}/200</text>
</view> </view>
</view> </view>
</view> <view v-else-if="field.type === 'number'" class="form-item no-bg" :style="field.tip ? 'flex-wrap: wrap;' : ''">
<!-- 数字输入 -->
<view v-else-if="field.type === 'number'" class="form-item" :style="field.tip ? 'flex-wrap: wrap;' : ''">
<text :class="['form-label', field.is_required === true ? 'required' : '']">{{ field.label }}</text> <text :class="['form-label', field.is_required === true ? 'required' : '']">{{ field.label }}</text>
<input type="digit" :value="formData[field.key]" :placeholder="field.placeholder" <input type="digit" :value="formData[field.key]" :placeholder="field.placeholder"
placeholder-class="placeholder" class="form-input" @input="onInput(field.key, $event)" /> placeholder-class="placeholder" class="form-input" @input="onInput(field.key, $event)" />
@ -30,9 +69,26 @@
<text class="price-tip-card">{{ field.tip }}</text> <text class="price-tip-card">{{ field.tip }}</text>
</view> </view>
</view> </view>
<view v-else-if="field.type === 'select'" class="form-item no-bg">
<!-- 多图上传 --> <text :class="['form-label', field.is_required === true ? 'required' : '']">{{ field.label }}</text>
<view v-else-if="field.type === 'image_multi'" class="form-item-up upload-item"> <form-item-select :value="formData[field.key]" :options="field.options || []" :placeholder="field.placeholder"
@input="onInput(field.key, $event)" />
</view>
<view v-else-if="field.type === 'single_select'" class="form-item no-bg" style="flex-wrap: wrap;">
<text :class="['form-label', field.is_required === true ? 'required' : '']">{{ field.label }}</text>
<view class="select-options">
<form-item-radio :value="formData[field.key]" :options="field.options || []"
@input="onInput(field.key, $event)" />
</view>
</view>
<view v-else-if="field.type === 'multi_select'" class="form-item no-bg" style="flex-wrap: wrap;">
<text :class="['form-label', field.is_required === true ? 'required' : '']">{{ field.label }}</text>
<view class="select-options">
<form-item-checkbox :value="formData[field.key]" :options="field.options || []"
:maxCount="field.maxCount || 0" @input="onInput(field.key, $event)" />
</view>
</view>
<view v-else-if="field.type === 'image_multi'" class="form-item-up upload-item no-bg">
<view> <view>
<text :class="['form-label', field.is_required === true ? 'required' : '']">{{ field.label }}</text> <text :class="['form-label', field.is_required === true ? 'required' : '']">{{ field.label }}</text>
<text class="upload-tip">(仅可上传{{ field.maxCount || 9 }}个图片)</text> <text class="upload-tip">(仅可上传{{ field.maxCount || 9 }}个图片)</text>
@ -42,9 +98,7 @@
@input="onInput(field.key, $event)" /> @input="onInput(field.key, $event)" />
</view> </view>
</view> </view>
<view v-else-if="field.type === 'video'" class="form-item-up upload-item no-bg">
<!-- 视频上传 -->
<view v-else-if="field.type === 'video'" class="form-item-up upload-item">
<view> <view>
<text :class="['form-label', field.is_required === true ? 'required' : '']">{{ field.label }}</text> <text :class="['form-label', field.is_required === true ? 'required' : '']">{{ field.label }}</text>
<text class="upload-tip">(仅可上传1个视频)</text> <text class="upload-tip">(仅可上传1个视频)</text>
@ -53,39 +107,14 @@
<form-item-video :value="formData[field.key]" @input="onInput(field.key, $event)" /> <form-item-video :value="formData[field.key]" @input="onInput(field.key, $event)" />
</view> </view>
</view> </view>
<view v-else-if="field.type === 'richtext_editor'" class="form-item no-bg">
<!-- 下拉选择 -->
<view v-else-if="field.type === 'select'" class="form-item">
<text :class="['form-label', field.is_required === true ? 'required' : '']">{{ field.label }}</text>
<form-item-select :value="formData[field.key]" :options="field.options || []" :placeholder="field.placeholder"
@input="onInput(field.key, $event)" />
</view>
<!-- 单选 -->
<view v-else-if="field.type === 'single_select'" class="form-item" style="flex-wrap: wrap;">
<text :class="['form-label', field.is_required === true ? 'required' : '']">{{ field.label }}</text>
<view class="select-options">
<form-item-radio :value="formData[field.key]" :options="field.options || []"
@input="onInput(field.key, $event)" />
</view>
</view>
<!-- 多选 -->
<view v-else-if="field.type === 'multi_select'" class="form-item" style="flex-wrap: wrap;">
<text :class="['form-label', field.is_required === true ? 'required' : '']">{{ field.label }}</text>
<view class="select-options">
<form-item-checkbox :value="formData[field.key]" :options="field.options || []"
:maxCount="field.maxCount || 0" @input="onInput(field.key, $event)" />
</view>
</view>
<!-- 图文编辑器 -->
<view v-else-if="field.type === 'richtext_editor'" class="form-item">
<text :class="['form-label', field.is_required === true ? 'required' : '']">{{ field.label }}</text> <text :class="['form-label', field.is_required === true ? 'required' : '']">{{ field.label }}</text>
<form-item-richtext :value="formData[field.key]" @input="onInput(field.key, $event)" /> <form-item-richtext :value="formData[field.key]" @input="onInput(field.key, $event)" />
</view> </view>
</view> </view>
</view> </view>
</view>
</view>
</template> </template>
<script> <script>
@ -138,15 +167,13 @@ export default {
handler: function () { handler: function () {
var self = this; var self = this;
var data = {}; var data = {};
this.fields.forEach(function (field) { this.fields.forEach(function (group) {
if (self.formData[field.key] !== undefined) { if (group.type === 'single' && group.field) {
data[field.key] = self.formData[field.key]; self._initFieldData(data, group.field);
} else if (field.default_value !== undefined) { } else if (group.type === 'section' && group.fields) {
data[field.key] = field.default_value; group.fields.forEach(function (field) {
} else if (field.type === 'multi_select' || field.type === 'image_multi') { self._initFieldData(data, field);
data[field.key] = []; });
} else {
data[field.key] = '';
} }
}); });
this.formData = Object.assign({}, this.formData, data); this.formData = Object.assign({}, this.formData, data);
@ -155,6 +182,17 @@ export default {
} }
}, },
methods: { methods: {
_initFieldData: function (data, field) {
if (this.formData[field.key] !== undefined) {
data[field.key] = this.formData[field.key];
} else if (field.default_value !== undefined) {
data[field.key] = field.default_value;
} else if (field.type === 'multi_select' || field.type === 'image_multi') {
data[field.key] = [];
} else {
data[field.key] = '';
}
},
onInput: function (key, event) { onInput: function (key, event) {
var value = event; var value = event;
if (event && event.target && event.target.value !== undefined) { if (event && event.target && event.target.value !== undefined) {
@ -169,7 +207,15 @@ export default {
validate: function () { validate: function () {
var self = this; var self = this;
for (var i = 0; i < this.fields.length; i++) { for (var i = 0; i < this.fields.length; i++) {
var field = this.fields[i]; var group = this.fields[i];
var fieldsToCheck = [];
if (group.type === 'single' && group.field) {
fieldsToCheck.push(group.field);
} else if (group.type === 'section' && group.fields) {
fieldsToCheck = group.fields;
}
for (var j = 0; j < fieldsToCheck.length; j++) {
var field = fieldsToCheck[j];
if (field.is_required === true) { if (field.is_required === true) {
var value = self.formData[field.key]; var value = self.formData[field.key];
if (!value || (Array.isArray(value) && value.length === 0)) { if (!value || (Array.isArray(value) && value.length === 0)) {
@ -181,6 +227,7 @@ export default {
} }
} }
} }
}
return true; return true;
} }
} }
@ -192,7 +239,7 @@ export default {
width: 100%; width: 100%;
} }
.form-section { .form-group {
width: 100%; width: 100%;
} }
@ -221,8 +268,8 @@ export default {
.form-tips { .form-tips {
font-size: 24rpx; font-size: 24rpx;
color: #999999; color: #333333;
margin-left: 10rpx; margin-left: 0;
} }
.price-tip { .price-tip {
@ -272,17 +319,55 @@ export default {
margin-top: 16rpx; margin-top: 16rpx;
} }
.detail-card { .section-card {
background: #ffffff; background: #ffffff;
border-radius: 20rpx; border-radius: 20rpx;
padding: 0 24rpx 8rpx; padding: 0 24rpx 24rpx;
margin-bottom: 24rpx; margin-bottom: 24rpx;
box-sizing: border-box; box-sizing: border-box;
overflow: hidden; overflow: hidden;
} }
/* section内字段间距20rpx通过section-field包装器控制 */
.section-field + .section-field {
padding-top: 12rpx;
}
/* section内字段去掉独立白色背景和底部间距 */
.section-card .form-item,
.section-card .form-item-up {
background-color: transparent;
padding-left: 0;
padding-right: 0;
margin-bottom: 0;
border-radius: 0;
}
.section-card .detail-row {
padding-top: 0;
padding-bottom: 0;
}
.section-title-bar {
padding: 0;
margin: 0;
}
.section-title {
padding: 24rpx 0 20rpx;
font-size: 30rpx;
font-weight: 500;
color: #333;
line-height: 40rpx;
display: block;
}
.detail-row { .detail-row {
padding: 24rpx 0 36rpx; padding: 20rpx 0 0;
}
.detail-row:first-child {
padding-top: 0;
} }
.detail-row .form-label { .detail-row .form-label {

View File

@ -106,12 +106,15 @@ export default {
}; };
}, },
onShow() { onShow() {
uni.$on("updateGraphicDetails", (data) => {
this.$set(this.formData, "graphic_details", JSON.stringify(data));
//
});
}, },
onLoad(options) { onLoad(options) {
// onLoadnavigateBack
uni.$on("updateGraphicDetails", (data) => {
// formDatadynamic-formwatch.value
this.formData = Object.assign({}, this.formData, {
graphic_details: JSON.stringify(data)
});
});
this.state = options.state || ""; this.state = options.state || "";
// //
if (options.first_class_id) { if (options.first_class_id) {
@ -130,6 +133,9 @@ export default {
const notShow = uni.getStorageSync('addServiceReminderNotShow'); const notShow = uni.getStorageSync('addServiceReminderNotShow');
this.getUserInfo(); this.getUserInfo();
}, },
onUnload() {
uni.$off("updateGraphicDetails");
},
computed: { computed: {
showShopPrice() { showShopPrice() {
return ( return (
@ -147,39 +153,155 @@ export default {
}, },
}, },
methods: { methods: {
// -> dynamic-form
mapFieldType(fieldType) {
const typeMap = {
'text': 'text',
'number': 'number',
'image_multi': 'image_multi',
'video': 'video',
'richtext_editor': 'richtext_editor',
'select': 'select',
'single_select': 'single_select',
'multi_select': 'multi_select',
'textarea': 'textarea',
};
return typeMap[fieldType] || 'text';
},
// config_datadynamic-form
transformConfigToFields(configData) {
const groups = [];
let customFieldIndex = 0;
// 1. presetFields-
if (configData.presetFields && Array.isArray(configData.presetFields)) {
configData.presetFields.forEach((preset) => {
if (!preset.enabled) return;
const field = {
key: preset.key,
type: this.mapFieldType(preset.fieldType),
label: preset.title,
is_required: preset.required,
placeholder: preset.placeholder || `请输入${preset.title}`,
};
if (preset.showHint && preset.hint) {
field.tip = preset.hint;
}
if (preset.key === 'line_price' || preset.key === 'server_price') {
field.tips = '(元)';
field.tip = preset.key === 'line_price' ? '服务的基础定价,未参与任何优惠的价格' : '服务参与单品优惠后的价格';
} else if (preset.key === 'server_time') {
field.placeholder = '请输入服务时长';
field.tips = '分钟';
}
if (preset.maxCount) {
field.maxCount = preset.maxCount;
}
if (preset.maxLength && preset.fieldType === 'text') {
field.maxLength = preset.maxLength;
}
if (preset.maxFileSize) {
field.maxFileSize = preset.maxFileSize;
}
field._hasOriginalKey = !!preset.key;
groups.push({ type: 'single', field });
});
}
// 2. sections- section
if (configData.sections && Array.isArray(configData.sections)) {
configData.sections.forEach((section) => {
const sectionFields = [];
if (section.fields && Array.isArray(section.fields)) {
section.fields.forEach((sectionField) => {
const fieldKey = sectionField.key || `custom_${sectionField.id || customFieldIndex++}`;
const field = {
key: fieldKey,
type: this.mapFieldType(sectionField.fieldType),
label: sectionField.title,
is_required: sectionField.required,
placeholder: sectionField.placeholder || `请输入${sectionField.title}`,
};
if (sectionField.key === 'server_time') {
field.placeholder = '请输入服务时长';
field.tips = '分钟';
}
if (sectionField.options && Array.isArray(sectionField.options) && sectionField.options.length > 0) {
field.options = sectionField.options;
}
if (sectionField.maxCount && (sectionField.fieldType === 'multi_select' || sectionField.fieldType === 'image_multi')) {
field.maxCount = sectionField.maxCount;
}
if (sectionField.maxLength && (sectionField.fieldType === 'text' || sectionField.fieldType === 'textarea')) {
field.maxLength = sectionField.maxLength;
}
if (sectionField.maxLength && sectionField.fieldType === 'number') {
field.maxLength = sectionField.maxLength;
}
if (sectionField.maxFileSize) {
field.maxFileSize = sectionField.maxFileSize;
}
field._hasOriginalKey = !!sectionField.key;
field._original = {
id: sectionField.id,
sectionId: section.id,
sectionType: section.sectionType,
userDefined: sectionField.userDefined,
};
sectionFields.push(field);
});
}
groups.push({
type: 'section',
sectionTitle: section.sectionTitle || '',
fields: sectionFields,
});
});
}
return groups;
},
// //
async getTemplate() { async getTemplate() {
//
const mockFields = [
{ key: 'title', type: 'text', label: '服务名称', is_required: true, placeholder: '请输入服务名称' },
{ key: 'service_price', type: 'number', label: '服务定价', is_required: true, placeholder: '请输入定价,最多两位小数', tips: '(元)', tip: '服务的基础定价,未参与任何优惠的价格' },
{ key: 'sale_price', type: 'number', label: '服务售价', is_required: true, placeholder: '请输入售价,最多两位小数', tips: '(元)', tip: '服务参与单品优惠后的价格' },
{ key: 'service_time', type: 'number', label: '服务时长', is_required: true, placeholder: '请输入服务时间', tips: '(分钟)' },
{ key: 'effect', type: 'textarea', label: '服务功效', is_required: true, placeholder: '请输入服务功效' },
{ key: 'crowd', type: 'textarea', label: '适用人群', is_required: true, placeholder: '请输入不适用人群,例如:未成年人/孕妇不适用' },
{ key: 'product', type: 'textarea', label: '产品清单', is_required: false, placeholder: '请输入产品清单' },
{ key: 'scope', type: 'textarea', label: '适用范围', is_required: false, placeholder: '请输入使用时间/适用人数' },
{ key: 'tips_text', type: 'textarea', label: '温馨提示', is_required: false, placeholder: '请输入其他注意事项' },
{ key: 'images', type: 'image_multi', label: '服务图片', is_required: true, maxCount: 9 },
{ key: 'video', type: 'video', label: '上传短视频', is_required: false },
{ key: 'service_type', type: 'single_select', label: '服务类型', is_required: true, options: ['到店服务', '上门服务'] },
{ key: 'features', type: 'multi_select', label: '服务特色', is_required: false, options: ['一对一服务', '免费咨询', '售后保障'], maxCount: 2 },
{ key: 'graphic_details', type: 'richtext_editor', label: '图文详情', is_required: false },
];
try { try {
const res = await request.post("/sj/servers/getTemplate", { const res = await request.post("/sj/servers/getTemplate", {
first_class_id: this.formData.first_class_id, first_class_id: this.formData.first_class_id,
}); });
if (res.data && res.data.fields && res.data.fields.length > 0) { console.log("模板接口返回数据:", res);
if (res.data && res.data.config_data) {
// 使config_data
// config_data
const configData = typeof res.data.config_data === 'string'
? JSON.parse(res.data.config_data)
: res.data.config_data;
console.log("解析后的config_data:", configData);
console.log("presetFields类型:", typeof configData.presetFields, Array.isArray(configData.presetFields));
const fields = this.transformConfigToFields(configData);
console.log("转换后的fields数组:", fields, "长度:", fields.length);
this.formFields = fields;
} else if (res.data && res.data.fields && res.data.fields.length > 0) {
//
this.formFields = res.data.fields; this.formFields = res.data.fields;
} else {
// 使
this.formFields = mockFields;
} }
} catch (e) { } catch (e) {
console.error("获取模板失败", e); console.error("获取模板失败", e);
// 使
this.formFields = mockFields;
} }
// //
const initialData = {}; const initialData = {};
@ -261,36 +383,35 @@ export default {
}, },
syncDetailForm() { syncDetailForm() {
// detailForm formData // formDatadynamic-formdetailFormdetailFormformData
if (this.formData.efficacy) {
this.detailForm.effect = this.formData.efficacy;
} else {
this.formData.efficacy = (this.detailForm.effect || "").trim(); this.formData.efficacy = (this.detailForm.effect || "").trim();
}
if (this.formData.crowd) {
this.detailForm.crowd = this.formData.crowd;
} else {
this.formData.crowd = (this.detailForm.crowd || "").trim(); this.formData.crowd = (this.detailForm.crowd || "").trim();
}
if (this.formData.product) {
this.detailForm.product = this.formData.product;
} else {
this.formData.product = (this.detailForm.product || "").trim(); this.formData.product = (this.detailForm.product || "").trim();
}
if (this.formData.scope) {
this.detailForm.scope = this.formData.scope;
} else {
this.formData.scope = (this.detailForm.scope || "").trim(); this.formData.scope = (this.detailForm.scope || "").trim();
}
if (this.formData.prompt) {
this.detailForm.tips = this.formData.prompt;
} else {
this.formData.prompt = (this.detailForm.tips || "").trim(); this.formData.prompt = (this.detailForm.tips || "").trim();
}
}, },
checkDetailForm() { checkDetailForm() {
if (!this.detailForm.effect || !this.detailForm.effect.trim()) {
uni.showToast({
title: "请输入服务功效",
icon: "none",
});
return false;
}
if (!this.detailForm.crowd || !this.detailForm.crowd.trim()) {
uni.showToast({
title: "请输入适用人群",
icon: "none",
});
return false;
}
// if (!this.detailForm.product || !this.detailForm.product.trim()) {
// uni.showToast({
// title: "",
// icon: "none",
// });
// return false;
// }
return true; return true;
}, },
@ -498,103 +619,116 @@ export default {
}); });
}, 500), }, 500),
// //
submitForm: debounce(function () { validateDynamicForm() {
if (this.isAdd) return; // formGroups
this.syncDetailForm(); for (let i = 0; i < this.formFields.length; i++) {
// const group = this.formFields[i];
const fieldsToCheck = [];
if (!this.formData.title) { if (group.type === 'single' && group.field) {
uni.showToast({ fieldsToCheck.push(group.field);
title: "请输入服务名称", } else if (group.type === 'section' && group.fields) {
icon: "none", fieldsToCheck.push(...group.fields);
});
return;
}
if (!this.formData.line_price) {
uni.showToast({
title: '请输入服务定价',
icon: 'none'
})
return
}
if (!this.formData.server_price) {
uni.showToast({
title: '请输入服务售价',
icon: 'none'
})
return
} }
if (!this.formData.server_time) { for (let j = 0; j < fieldsToCheck.length; j++) {
const field = fieldsToCheck[j];
if (!field.is_required) continue;
const value = this.formData[field.key];
const isEmpty = value === undefined || value === null || value === '' ||
(Array.isArray(value) && value.length === 0);
if (isEmpty) {
//
const prefix = (field.type === 'image_multi' || field.type === 'video') ? '请上传' : '请输入';
uni.showToast({ uni.showToast({
title: "请输入服务时长", title: `${prefix}${field.label}`,
icon: "none", icon: "none",
}); });
return; return false;
} }
if (this.formData.server_time < 10) {
//
if (field.key === 'server_time') {
if (value < 10) {
uni.showToast({ uni.showToast({
title: "服务时长最短为10分钟", title: "服务时长最短为10分钟",
icon: "none", icon: "none",
}); });
return; return false;
} }
if (this.formData.server_time > 240) { if (value > 240) {
uni.showToast({ uni.showToast({
title: "服务时长最长为240分钟", title: "服务时长最长为240分钟",
icon: "none", icon: "none",
}); });
return; return false;
} }
if (!this.formData.sj_see && this.artisanType == 1) { }
uni.showToast({ }
title: "请选择是否允许在门店端售卖", }
icon: "none", return true;
},
// keykeyintro
buildSubmitData() {
const submitData = {};
const introArr = [];
//
if (this.formData.id) submitData.id = this.formData.id;
if (this.formData.first_class_id) submitData.first_class_id = this.formData.first_class_id;
if (this.formData.second_class_id) submitData.second_class_id = this.formData.second_class_id;
submitData.server_kind = this.formData.server_kind || getApp().globalData.artisanType;
//
for (let i = 0; i < this.formFields.length; i++) {
const group = this.formFields[i];
let fieldsToProcess = [];
if (group.type === 'single' && group.field) {
fieldsToProcess.push(group.field);
} else if (group.type === 'section' && group.fields) {
fieldsToProcess = group.fields;
}
for (let j = 0; j < fieldsToProcess.length; j++) {
const field = fieldsToProcess[j];
const value = this.formData[field.key];
if (value === undefined || value === null || value === '') continue;
if (Array.isArray(value) && value.length === 0) continue;
if (field._hasOriginalKey) {
// key
submitData[field.key] = value;
} else {
// keyintro
introArr.push({
id: field._original ? field._original.id : field.key,
title: field.label,
value: value,
}); });
return; }
}
} }
// if (introArr.length > 0) {
if (!this.checkDetailForm()) { submitData.intro = JSON.stringify(introArr);
return;
} }
return submitData;
},
//
submitForm: debounce(function () {
if (this.isAdd) return;
// formData
this.syncDetailForm(); this.syncDetailForm();
// //
if (this.formData.process && this.formData.process.length > 0) { if (!this.validateDynamicForm()) {
for (let item of this.formData.process) {
if (!item.text) {
uni.showToast({
title: "请完善服务流程内容",
icon: "none",
});
return;
}
}
} else {
uni.showToast({
title: "请添加服务流程内容",
icon: "none",
});
return; return;
} }
//
// if (!this.formData.video) {
// uni.showToast({
// title: '',
// icon: 'none'
// })
// return
// }
if (!this.formData.photo || this.formData.photo.length === 0) {
uni.showToast({
title: "请上传服务图片",
icon: "none",
});
return;
}
if (this.artisanType == 2) { if (this.artisanType == 2) {
delete this.formData.sj_see; delete this.formData.sj_see;
} }
@ -607,16 +741,8 @@ export default {
url = "/user/serverupdate"; url = "/user/serverupdate";
} }
console.log(this.formData, '-0-0-0-0-0-'); console.log(this.formData, '-0-0-0-0-0-');
delete this.formData.shop_business_time const submitData = this.buildSubmitData();
delete this.formData.shop_head_photo request.post(url, submitData).then((res) => {
delete this.formData.shop_address
delete this.formData.shop_dependency
delete this.formData.shop_class
delete this.formData.team_buy
request.post(url, this.formData).then((res) => {
if (res.state == 1) { if (res.state == 1) {
const pages = getCurrentPages(); const pages = getCurrentPages();
const prevPage = pages[pages.length - 2]; // const prevPage = pages[pages.length - 2]; //

View File

@ -120,13 +120,13 @@ const request = async (options, isUpdate) => {
if (process.env.NODE_ENV === "development") { if (process.env.NODE_ENV === "development") {
baseURL = url; // 发布到生产环境时,此处代码会被摇树移除掉。 baseURL = url; // 发布到生产环境时,此处代码会被摇树移除掉。
baseURL = "http://116.63.163.121:93"; // 发布到生产环境时,此处代码会被摇树移除掉。 // baseURL = "http://116.63.163.121:93"; // 发布到生产环境时,此处代码会被摇树移除掉。
//baseURL = 'http://116.63.163.121:96'; // 发布到生产环境时,此处代码会被摇树移除掉。 baseURL = "http://116.63.163.121:96"; // 发布到生产环境时,此处代码会被摇树移除掉。
// baseURL = "https://app.mrrweb.com.cn"; // baseURL = "https://app.mrrweb.com.cn";
} else { } else {
baseURL = url; // 发布到生产环境时,此处代码会被摇树移除掉。 baseURL = url; // 发布到生产环境时,此处代码会被摇树移除掉。
baseURL = "http://116.63.163.121:93"; // 发布到生产环境时,此处代码会被摇树移除掉。 // baseURL = "http://116.63.163.121:93"; // 发布到生产环境时,此处代码会被摇树移除掉。
//baseURL = 'http://116.63.163.121:96'; // 发布到生产环境时,此处代码会被摇树移除掉。 baseURL = "http://116.63.163.121:96"; // 发布到生产环境时,此处代码会被摇树移除掉。
// baseURL = "https://app.mrrweb.com.cn"; // baseURL = "https://app.mrrweb.com.cn";
console.log("生产环境"); console.log("生产环境");