预定义组件备份
This commit is contained in:
parent
d4a6a63b65
commit
a7f9544bc5
|
|
@ -0,0 +1,97 @@
|
|||
<template>
|
||||
<view class="form-checkbox">
|
||||
<view v-for="(item, index) in options" :key="index" class="checkbox-item" @click="toggle(item)">
|
||||
<view :class="['checkbox-dot', { checked: isSelected(item.value) }]">
|
||||
<text v-if="isSelected(item.value)" class="check-icon">✓</text>
|
||||
</view>
|
||||
<text class="checkbox-label">{{ item.label }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'FormItemCheckbox',
|
||||
props: {
|
||||
value: {
|
||||
type: Array,
|
||||
default: function () {
|
||||
return [];
|
||||
}
|
||||
},
|
||||
options: {
|
||||
type: Array,
|
||||
default: function () {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
},
|
||||
data: function () {
|
||||
return {
|
||||
selectedValues: []
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
value: {
|
||||
handler: function (val) {
|
||||
this.selectedValues = Array.isArray(val) ? val.slice() : [];
|
||||
},
|
||||
immediate: true
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
isSelected: function (val) {
|
||||
return this.selectedValues.indexOf(val) > -1;
|
||||
},
|
||||
toggle: function (item) {
|
||||
var index = this.selectedValues.indexOf(item.value);
|
||||
if (index > -1) {
|
||||
this.selectedValues.splice(index, 1);
|
||||
} else {
|
||||
this.selectedValues.push(item.value);
|
||||
}
|
||||
this.$emit('input', this.selectedValues.slice());
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.form-checkbox {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 24rpx;
|
||||
}
|
||||
|
||||
.checkbox-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 16rpx 0;
|
||||
}
|
||||
|
||||
.checkbox-dot {
|
||||
width: 36rpx;
|
||||
height: 36rpx;
|
||||
border-radius: 6rpx;
|
||||
border: 2rpx solid #dddddd;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-right: 12rpx;
|
||||
}
|
||||
|
||||
.checkbox-dot.checked {
|
||||
border-color: #e8101e;
|
||||
background-color: #e8101e;
|
||||
}
|
||||
|
||||
.check-icon {
|
||||
color: #ffffff;
|
||||
font-size: 24rpx;
|
||||
}
|
||||
|
||||
.checkbox-label {
|
||||
font-size: 28rpx;
|
||||
color: #333333;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,112 @@
|
|||
<template>
|
||||
<view class="form-image">
|
||||
<view class="image-list">
|
||||
<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">×</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>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "FormItemImage",
|
||||
props: {
|
||||
value: { type: Array, default: function() { return []; } },
|
||||
maxCount: { type: Number, default: 9 }
|
||||
},
|
||||
data: function() {
|
||||
return {
|
||||
imageList: []
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
value: {
|
||||
handler: function(val) {
|
||||
this.imageList = Array.isArray(val) ? val.slice() : [];
|
||||
},
|
||||
immediate: true
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
chooseImage: function() {
|
||||
var remaining = this.maxCount - this.imageList.length;
|
||||
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) {
|
||||
this.imageList.splice(index, 1);
|
||||
this.$emit("input", this.imageList);
|
||||
},
|
||||
previewImage: function(index) {
|
||||
uni.previewImage({
|
||||
urls: this.imageList,
|
||||
current: index
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.form-image {
|
||||
width: 100%;
|
||||
}
|
||||
.image-list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 16rpx;
|
||||
}
|
||||
.image-item {
|
||||
width: 200rpx;
|
||||
height: 200rpx;
|
||||
border-radius: 8rpx;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
.preview-image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
.delete-btn {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
width: 40rpx;
|
||||
height: 40rpx;
|
||||
background-color: rgba(0, 0, 0, 0.6);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.delete-icon {
|
||||
color: #ffffff;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
.add-btn {
|
||||
background-color: #f5f5f5;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: 2rpx dashed #cccccc;
|
||||
}
|
||||
.add-icon {
|
||||
font-size: 60rpx;
|
||||
color: #999999;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
<template>
|
||||
<input type="number" class="form-input" :value="value" :placeholder="placeholder" placeholder-class="placeholder"
|
||||
@input="$emit('input', $event.detail.value)" />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'FormItemNumber',
|
||||
props: {
|
||||
value: {
|
||||
type: [String, Number],
|
||||
default: ''
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: '请输入数字'
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.form-input {
|
||||
flex: 1;
|
||||
height: 80rpx;
|
||||
font-size: 28rpx;
|
||||
color: #333333;
|
||||
text-align: center;
|
||||
background-color: #f5f5f5;
|
||||
border-radius: 8rpx;
|
||||
padding: 0 24rpx;
|
||||
}
|
||||
|
||||
.placeholder {
|
||||
font-weight: 400;
|
||||
font-size: 28rpx;
|
||||
color: #999999;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
<template>
|
||||
<view class="form-radio">
|
||||
<view v-for="(item, index) in options" :key="index" class="radio-item" @click="onSelect(item.value)">
|
||||
<view class="radio-dot" :class="{ active: value == item.value }"></view>
|
||||
<text class="radio-label">{{ item.label }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "FormItemRadio",
|
||||
props: {
|
||||
value: {
|
||||
type: [String, Number],
|
||||
default: ""
|
||||
},
|
||||
options: {
|
||||
type: Array,
|
||||
default: function() {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onSelect: function(val) {
|
||||
this.$emit("input", val);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.form-radio {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.radio-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-right: 40rpx;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
.radio-dot {
|
||||
width: 36rpx;
|
||||
height: 36rpx;
|
||||
border-radius: 50%;
|
||||
border: 2rpx solid #cccccc;
|
||||
margin-right: 12rpx;
|
||||
position: relative;
|
||||
}
|
||||
.radio-dot.active {
|
||||
border-color: #e8101e;
|
||||
background-color: #e8101e;
|
||||
}
|
||||
.radio-dot.active::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: 16rpx;
|
||||
height: 16rpx;
|
||||
border-radius: 50%;
|
||||
background-color: #ffffff;
|
||||
}
|
||||
.radio-label {
|
||||
font-size: 28rpx;
|
||||
color: #333333;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,83 @@
|
|||
<template>
|
||||
<view class="form-richtext">
|
||||
<view class="richtext-toolbar">
|
||||
<text class="toolbar-btn" @click="insertTag('b')">加粗</text>
|
||||
<text class="toolbar-btn" @click="insertTag('i')">斜体</text>
|
||||
<text class="toolbar-btn" @click="insertImage">图片</text>
|
||||
</view>
|
||||
<textarea class="richtext-editor" :value="value" placeholder="请输入图文内容" placeholder-class="placeholder"
|
||||
@input="$emit('input', $event.detail.value)" />
|
||||
<view v-if="value" class="richtext-preview">
|
||||
<rich-text :nodes="value" />
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "FormItemRichtext",
|
||||
props: {
|
||||
value: { type: String, default: "" },
|
||||
},
|
||||
methods: {
|
||||
insertTag(tag) {
|
||||
const text = `<${tag}></${tag}>`;
|
||||
this.$emit("input", (this.value || "") + text);
|
||||
},
|
||||
insertImage() {
|
||||
uni.chooseImage({
|
||||
count: 1,
|
||||
success: (res) => {
|
||||
const imgTag = `<img src="${res.tempFilePaths[0]}" />`;
|
||||
this.$emit("input", (this.value || "") + imgTag);
|
||||
},
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.form-richtext {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.richtext-toolbar {
|
||||
display: flex;
|
||||
gap: 16rpx;
|
||||
padding: 16rpx;
|
||||
background-color: #f0f0f0;
|
||||
border-radius: 8rpx 8rpx 0 0;
|
||||
}
|
||||
|
||||
.toolbar-btn {
|
||||
padding: 8rpx 16rpx;
|
||||
background-color: #ffffff;
|
||||
border-radius: 4rpx;
|
||||
font-size: 24rpx;
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
.richtext-editor {
|
||||
width: 100%;
|
||||
height: 300rpx;
|
||||
background-color: #ffffff;
|
||||
border: 2rpx solid #e0e0e0;
|
||||
border-top: none;
|
||||
border-radius: 0 0 8rpx 8rpx;
|
||||
padding: 20rpx;
|
||||
font-size: 28rpx;
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
.placeholder {
|
||||
color: #999999;
|
||||
}
|
||||
|
||||
.richtext-preview {
|
||||
margin-top: 16rpx;
|
||||
padding: 16rpx;
|
||||
background-color: #f9f9f9;
|
||||
border-radius: 8rpx;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,79 @@
|
|||
<template>
|
||||
<picker :range="optionLabels" @change="onChange">
|
||||
<view class="form-select">
|
||||
<text :class="['select-text', selectedLabel ? '' : 'placeholder']">
|
||||
{{ selectedLabel || placeholder }}
|
||||
</text>
|
||||
<image src="https://mrrplus.oss-cn-beijing.aliyuncs.com/wxstaic/images/arrow_right.png" class="arrow-right" mode="aspectFit" />
|
||||
</view>
|
||||
</picker>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "FormItemSelect",
|
||||
props: {
|
||||
value: {
|
||||
type: [String, Number],
|
||||
default: ""
|
||||
},
|
||||
options: {
|
||||
type: Array,
|
||||
default: function() {
|
||||
return [];
|
||||
}
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: "请选择"
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
optionLabels: function() {
|
||||
return this.options.map(function(item) {
|
||||
return item.label;
|
||||
});
|
||||
},
|
||||
selectedLabel: function() {
|
||||
var self = this;
|
||||
var found = this.options.find(function(item) {
|
||||
return item.value == self.value;
|
||||
});
|
||||
return found ? found.label : "";
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onChange: function(e) {
|
||||
var index = e.detail.value;
|
||||
var selected = this.options[index];
|
||||
if (selected) {
|
||||
this.$emit("input", selected.value);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.form-select {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
height: 80rpx;
|
||||
background-color: #f5f5f5;
|
||||
border-radius: 8rpx;
|
||||
padding: 0 24rpx;
|
||||
}
|
||||
.select-text {
|
||||
font-size: 28rpx;
|
||||
color: #333333;
|
||||
}
|
||||
.placeholder {
|
||||
color: #999999;
|
||||
}
|
||||
.arrow-right {
|
||||
width: 24rpx;
|
||||
height: 24rpx;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
<template>
|
||||
<input type="text" class="form-input" :value="value" :placeholder="placeholder" placeholder-class="placeholder"
|
||||
@input="$emit('input', $event.detail.value)" />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'FormItemText',
|
||||
props: {
|
||||
value: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: '请输入'
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.form-input {
|
||||
flex: 1;
|
||||
height: 80rpx;
|
||||
font-size: 28rpx;
|
||||
color: #333333;
|
||||
text-align: center;
|
||||
background-color: #f5f5f5;
|
||||
border-radius: 8rpx;
|
||||
padding: 0 24rpx;
|
||||
}
|
||||
|
||||
.placeholder {
|
||||
font-weight: 400;
|
||||
font-size: 28rpx;
|
||||
color: #999999;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
<template>
|
||||
<view class="textarea-wrap">
|
||||
<textarea class="form-textarea" :value="value" :placeholder="placeholder" placeholder-class="placeholder"
|
||||
maxlength="200" @input="$emit('input', $event.detail.value)" />
|
||||
<text class="char-count">{{ (value || '').length }}/200</text>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'FormItemTextarea',
|
||||
props: {
|
||||
value: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: '请输入'
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.textarea-wrap {
|
||||
width: 100%;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.form-textarea {
|
||||
width: 100%;
|
||||
height: 150rpx;
|
||||
font-size: 28rpx;
|
||||
color: #333333;
|
||||
line-height: 1.5;
|
||||
background-color: #f5f5f5;
|
||||
border-radius: 8rpx;
|
||||
padding: 20rpx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.placeholder {
|
||||
font-weight: 400;
|
||||
font-size: 28rpx;
|
||||
color: #999999;
|
||||
}
|
||||
|
||||
.char-count {
|
||||
position: absolute;
|
||||
right: 16rpx;
|
||||
bottom: 16rpx;
|
||||
font-size: 24rpx;
|
||||
color: #999999;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,104 @@
|
|||
<template>
|
||||
<view class="form-video">
|
||||
<view v-if="videoUrl" class="video-preview">
|
||||
<video :src="videoUrl" class="video-player" controls />
|
||||
<view class="delete-btn" @click="deleteVideo">
|
||||
<text class="delete-icon">×</text>
|
||||
</view>
|
||||
</view>
|
||||
<view v-else class="video-add" @click="chooseVideo">
|
||||
<text class="add-icon">+</text>
|
||||
<text class="add-text">选择视频</text>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "FormItemVideo",
|
||||
props: {
|
||||
value: { type: String, default: "" }
|
||||
},
|
||||
data: function() {
|
||||
return {
|
||||
videoUrl: ""
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
value: {
|
||||
handler: function(val) {
|
||||
this.videoUrl = val || "";
|
||||
},
|
||||
immediate: true
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
chooseVideo: function() {
|
||||
var self = this;
|
||||
uni.chooseVideo({
|
||||
sourceType: ["album", "camera"],
|
||||
maxDuration: 60,
|
||||
success: function(res) {
|
||||
self.videoUrl = res.tempFilePath;
|
||||
self.$emit("input", self.videoUrl);
|
||||
}
|
||||
});
|
||||
},
|
||||
deleteVideo: function() {
|
||||
this.videoUrl = "";
|
||||
this.$emit("input", "");
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.form-video {
|
||||
width: 100%;
|
||||
}
|
||||
.video-preview {
|
||||
width: 100%;
|
||||
position: relative;
|
||||
}
|
||||
.video-player {
|
||||
width: 100%;
|
||||
height: 400rpx;
|
||||
border-radius: 8rpx;
|
||||
}
|
||||
.delete-btn {
|
||||
position: absolute;
|
||||
top: 16rpx;
|
||||
right: 16rpx;
|
||||
width: 48rpx;
|
||||
height: 48rpx;
|
||||
background-color: rgba(0, 0, 0, 0.6);
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.delete-icon {
|
||||
color: #ffffff;
|
||||
font-size: 32rpx;
|
||||
}
|
||||
.video-add {
|
||||
width: 300rpx;
|
||||
height: 300rpx;
|
||||
background-color: #f5f5f5;
|
||||
border: 2rpx dashed #cccccc;
|
||||
border-radius: 8rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.add-icon {
|
||||
font-size: 60rpx;
|
||||
color: #999999;
|
||||
}
|
||||
.add-text {
|
||||
font-size: 24rpx;
|
||||
color: #999999;
|
||||
margin-top: 8rpx;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,197 @@
|
|||
<template>
|
||||
<view class="dynamic-form">
|
||||
<view v-for="(field, index) in fields" :key="field.id || index" class="form-item"
|
||||
:class="{ 'form-item-up': isUpType(field.type) }">
|
||||
<view class="form-header">
|
||||
<text class="form-label" :class="{ required: field.is_required == 1 }">{{ field.label }}</text>
|
||||
<text v-if="field.tips" class="form-tips">{{ field.tips }}</text>
|
||||
</view>
|
||||
<view class="form-control">
|
||||
<!-- 单行文本 -->
|
||||
<form-item-text v-if="field.type === 'text'" :value="formData[field.key]"
|
||||
:placeholder="field.placeholder" @input="onInput(field.key, $event)" />
|
||||
<!-- 多行文本 -->
|
||||
<form-item-textarea v-else-if="field.type === 'textarea'" :value="formData[field.key]"
|
||||
:placeholder="field.placeholder" @input="onInput(field.key, $event)" />
|
||||
<!-- 数字输入 -->
|
||||
<form-item-number v-else-if="field.type === 'number'" :value="formData[field.key]"
|
||||
:placeholder="field.placeholder" @input="onInput(field.key, $event)" />
|
||||
<!-- 多图上传 -->
|
||||
<form-item-image v-else-if="field.type === 'image_multi'" :value="formData[field.key]"
|
||||
:maxCount="field.maxCount || 9" @input="onInput(field.key, $event)" />
|
||||
<!-- 视频上传 -->
|
||||
<form-item-video v-else-if="field.type === 'video'" :value="formData[field.key]"
|
||||
@input="onInput(field.key, $event)" />
|
||||
<!-- 下拉选择 -->
|
||||
<form-item-select v-else-if="field.type === 'select'" :value="formData[field.key]"
|
||||
:options="field.options" :placeholder="field.placeholder" @input="onInput(field.key, $event)" />
|
||||
<!-- 单选 -->
|
||||
<form-item-radio v-else-if="field.type === 'single_select'" :value="formData[field.key]"
|
||||
:options="field.options" @input="onInput(field.key, $event)" />
|
||||
<!-- 多选 -->
|
||||
<form-item-checkbox v-else-if="field.type === 'multi_select'" :value="formData[field.key]"
|
||||
:options="field.options" @input="onInput(field.key, $event)" />
|
||||
<!-- 图文编辑器 -->
|
||||
<form-item-richtext v-else-if="field.type === 'richtext_editor'" :value="formData[field.key]"
|
||||
@input="onInput(field.key, $event)" />
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import FormItemText from './FormItemText.vue';
|
||||
import FormItemTextarea from './FormItemTextarea.vue';
|
||||
import FormItemNumber from './FormItemNumber.vue';
|
||||
import FormItemImage from './FormItemImage.vue';
|
||||
import FormItemVideo from './FormItemVideo.vue';
|
||||
import FormItemSelect from './FormItemSelect.vue';
|
||||
import FormItemRadio from './FormItemRadio.vue';
|
||||
import FormItemCheckbox from './FormItemCheckbox.vue';
|
||||
import FormItemRichtext from './FormItemRichtext.vue';
|
||||
|
||||
export default {
|
||||
name: 'DynamicForm',
|
||||
components: {
|
||||
FormItemText,
|
||||
FormItemTextarea,
|
||||
FormItemNumber,
|
||||
FormItemImage,
|
||||
FormItemVideo,
|
||||
FormItemSelect,
|
||||
FormItemRadio,
|
||||
FormItemCheckbox,
|
||||
FormItemRichtext
|
||||
},
|
||||
props: {
|
||||
fields: {
|
||||
type: Array,
|
||||
default: function () {
|
||||
return [];
|
||||
}
|
||||
},
|
||||
value: {
|
||||
type: Object,
|
||||
default: function () {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
},
|
||||
data: function () {
|
||||
return {
|
||||
formData: {}
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
value: {
|
||||
handler: function (val) {
|
||||
this.formData = Object.assign({}, val);
|
||||
},
|
||||
immediate: true,
|
||||
deep: true
|
||||
},
|
||||
fields: {
|
||||
handler: function (val) {
|
||||
var self = this;
|
||||
if (val && val.length > 0) {
|
||||
val.forEach(function (field) {
|
||||
if (self.formData[field.key] === undefined) {
|
||||
self.$set(self.formData, field.key, self.getDefaultValue(field.type));
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
immediate: true
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
isUpType: function (type) {
|
||||
return type === 'image_multi' || type === 'video';
|
||||
},
|
||||
getDefaultValue: function (type) {
|
||||
if (type === 'multi_select') {
|
||||
return [];
|
||||
}
|
||||
if (type === 'image_multi') {
|
||||
return [];
|
||||
}
|
||||
return '';
|
||||
},
|
||||
onInput: function (key, val) {
|
||||
this.$set(this.formData, key, val);
|
||||
this.$emit('input', Object.assign({}, this.formData));
|
||||
},
|
||||
validate: function () {
|
||||
for (var i = 0; i < this.fields.length; i++) {
|
||||
var field = this.fields[i];
|
||||
if (field.is_required == 1) {
|
||||
var val = this.formData[field.key];
|
||||
if (val === undefined || val === null || val === '' || (Array.isArray(val) && val.length === 0)) {
|
||||
uni.showToast({
|
||||
title: '请填写' + field.label,
|
||||
icon: 'none'
|
||||
});
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
},
|
||||
getData: function () {
|
||||
return Object.assign({}, this.formData);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.dynamic-form {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.form-item {
|
||||
background-color: #fff;
|
||||
padding: 24rpx;
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
box-sizing: border-box;
|
||||
border-radius: 10rpx;
|
||||
margin-bottom: 24rpx;
|
||||
}
|
||||
|
||||
.form-item-up {
|
||||
flex-flow: column nowrap;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.form-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.form-label {
|
||||
font-size: 28rpx;
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
.form-label.required::before {
|
||||
content: '*';
|
||||
color: #e8101e;
|
||||
margin-right: 4rpx;
|
||||
}
|
||||
|
||||
.form-tips {
|
||||
font-size: 24rpx;
|
||||
color: #999999;
|
||||
margin-left: 10rpx;
|
||||
}
|
||||
|
||||
.form-control {
|
||||
width: 100%;
|
||||
flex: 1;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -100,6 +100,48 @@
|
|||
|
||||
</view>
|
||||
<!-- <view class="add-btn"></view> -->
|
||||
|
||||
<!-- 服务分类选择弹窗 -->
|
||||
<view class="category-popup-mask" v-if="showCategoryPopup" @click="closeCategoryPopup">
|
||||
<view class="category-popup" @click.stop>
|
||||
<view class="category-popup-header">
|
||||
<text class="category-popup-title">选择服务分类</text>
|
||||
<view class="category-popup-close" @click="closeCategoryPopup">
|
||||
<text class="category-popup-close-icon">×</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="category-popup-body">
|
||||
<!-- 左侧一级分类 -->
|
||||
<scroll-view scroll-y class="category-left">
|
||||
<view v-for="(item, index) in firstClassList" :key="item.id"
|
||||
:class="['category-left-item', { active: selectedFirstIndex === index }]"
|
||||
@click="selectFirstClass(index, item)">
|
||||
<text class="category-left-text">{{ item.title }}</text>
|
||||
</view>
|
||||
</scroll-view>
|
||||
<!-- 右侧二级分类 -->
|
||||
<scroll-view scroll-y class="category-right">
|
||||
<view v-if="secondClassList.length === 0" class="category-empty">
|
||||
<text class="category-empty-text">暂无子分类</text>
|
||||
</view>
|
||||
<view v-for="item in secondClassList" :key="item.id" class="category-right-item"
|
||||
@click="selectSecondClass(item)">
|
||||
<text class="category-right-text">{{ item.title }}</text>
|
||||
<view :class="['category-dot', { selected: selectedSecondId === item.id }]"></view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
<!-- 底部按钮 -->
|
||||
<view class="category-popup-footer">
|
||||
<view class="category-popup-btn category-popup-btn-cancel" @click="closeCategoryPopup">
|
||||
<text>取消</text>
|
||||
</view>
|
||||
<view class="category-popup-btn category-popup-btn-confirm" @click="confirmCategory">
|
||||
<text>确认</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
|
|
@ -137,6 +179,12 @@
|
|||
startX: 0,
|
||||
moveX: 0,
|
||||
currentIndex: -1,
|
||||
// 分类选择相关
|
||||
showCategoryPopup: false,
|
||||
firstClassList: [],
|
||||
secondClassList: [],
|
||||
selectedFirstIndex: -1,
|
||||
selectedSecondId: null,
|
||||
};
|
||||
},
|
||||
// computed: {
|
||||
|
|
@ -248,9 +296,72 @@
|
|||
|
||||
},
|
||||
addService() {
|
||||
uni.navigateTo({
|
||||
url: `/pages/shop/add-service`,
|
||||
// 获取一级分类并显示弹窗
|
||||
request.post("/sj/firstclass", {}).then((res) => {
|
||||
if (res.data && res.data.length > 0) {
|
||||
this.firstClassList = res.data;
|
||||
this.selectedFirstIndex = 0;
|
||||
this.selectedSecondId = null;
|
||||
this.secondClassList = [];
|
||||
this.showCategoryPopup = true;
|
||||
// 自动加载第一个一级分类的二级分类
|
||||
this.loadSecondClass(res.data[0].id);
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: "暂无分类数据",
|
||||
icon: "none",
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
// 加载二级分类
|
||||
loadSecondClass(firstId) {
|
||||
request.post("/user/secondclass", {
|
||||
id: firstId,
|
||||
}).then((res) => {
|
||||
this.secondClassList = res.data || [];
|
||||
});
|
||||
},
|
||||
// 选择一级分类
|
||||
selectFirstClass(index, item) {
|
||||
if (this.selectedFirstIndex === index) return;
|
||||
this.selectedFirstIndex = index;
|
||||
this.selectedSecondId = null;
|
||||
this.loadSecondClass(item.id);
|
||||
},
|
||||
// 选择二级分类(仅记录选中状态)
|
||||
selectSecondClass(item) {
|
||||
this.selectedSecondId = item.id;
|
||||
},
|
||||
// 确认选择分类
|
||||
confirmCategory() {
|
||||
if (this.selectedFirstIndex < 0) {
|
||||
uni.showToast({
|
||||
title: "请选择服务分类",
|
||||
icon: "none",
|
||||
});
|
||||
return;
|
||||
}
|
||||
// 如果有二级分类但未选择
|
||||
if (this.secondClassList.length > 0 && !this.selectedSecondId) {
|
||||
uni.showToast({
|
||||
title: "请选择服务子类",
|
||||
icon: "none",
|
||||
});
|
||||
return;
|
||||
}
|
||||
const firstItem = this.firstClassList[this.selectedFirstIndex];
|
||||
const url = `/pages/shop/add-service?first_class_id=${firstItem.id}`;
|
||||
uni.navigateTo({ url });
|
||||
this.closeCategoryPopup();
|
||||
},
|
||||
// 关闭分类弹窗
|
||||
closeCategoryPopup() {
|
||||
this.showCategoryPopup = false;
|
||||
this.firstClassList = [];
|
||||
this.secondClassList = [];
|
||||
this.selectedFirstIndex = -1;
|
||||
this.selectedSecondId = null;
|
||||
},
|
||||
deleteService(item) {
|
||||
uni.showModal({
|
||||
|
|
@ -747,4 +858,198 @@
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
/* 分类选择弹窗样式 */
|
||||
.category-popup-mask {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
z-index: 2000;
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.category-popup {
|
||||
width: 100%;
|
||||
height: 70vh;
|
||||
background-color: #ffffff;
|
||||
border-radius: 32rpx 32rpx 0 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.category-popup-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 32rpx;
|
||||
position: relative;
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
}
|
||||
|
||||
.category-popup-title {
|
||||
font-size: 34rpx;
|
||||
font-weight: 600;
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
.category-popup-close {
|
||||
position: absolute;
|
||||
right: 32rpx;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
width: 48rpx;
|
||||
height: 48rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.category-popup-close-icon {
|
||||
font-size: 48rpx;
|
||||
color: #999999;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.category-popup-body {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.category-left {
|
||||
width: 220rpx;
|
||||
background-color: #f5f5f5;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.category-left-item {
|
||||
padding: 30rpx 24rpx;
|
||||
text-align: center;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.category-left-item.active {
|
||||
background-color: #ffffff;
|
||||
}
|
||||
|
||||
.category-left-item.active::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
width: 6rpx;
|
||||
height: 40rpx;
|
||||
background-color: #E8101E;
|
||||
border-radius: 0 3rpx 3rpx 0;
|
||||
}
|
||||
|
||||
.category-left-text {
|
||||
font-size: 28rpx;
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
.category-left-item.active .category-left-text {
|
||||
color: #E8101E;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.category-right {
|
||||
flex: 1;
|
||||
background-color: #ffffff;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.category-right-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 30rpx 32rpx;
|
||||
border-bottom: 1rpx solid #f5f5f5;
|
||||
}
|
||||
|
||||
.category-right-text {
|
||||
font-size: 28rpx;
|
||||
color: #333333;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.category-dot {
|
||||
width: 36rpx;
|
||||
height: 36rpx;
|
||||
border-radius: 50%;
|
||||
border: 2rpx solid #dddddd;
|
||||
margin-left: 16rpx;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.category-dot.selected {
|
||||
border-color: #E8101E;
|
||||
background-color: #E8101E;
|
||||
}
|
||||
|
||||
.category-dot.selected::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: 16rpx;
|
||||
height: 16rpx;
|
||||
border-radius: 50%;
|
||||
background-color: #ffffff;
|
||||
}
|
||||
|
||||
.category-empty {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 100rpx 0;
|
||||
}
|
||||
|
||||
.category-empty-text {
|
||||
font-size: 28rpx;
|
||||
color: #999999;
|
||||
}
|
||||
|
||||
.category-popup-footer {
|
||||
display: flex;
|
||||
padding: 24rpx 32rpx;
|
||||
padding-bottom: calc(24rpx + env(safe-area-inset-bottom));
|
||||
border-top: 1rpx solid #f0f0f0;
|
||||
background-color: #ffffff;
|
||||
}
|
||||
|
||||
.category-popup-btn {
|
||||
flex: 1;
|
||||
height: 88rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 44rpx;
|
||||
font-size: 30rpx;
|
||||
}
|
||||
|
||||
.category-popup-btn-cancel {
|
||||
background-color: #f5f5f5;
|
||||
margin-right: 24rpx;
|
||||
}
|
||||
|
||||
.category-popup-btn-cancel text {
|
||||
color: #666666;
|
||||
}
|
||||
|
||||
.category-popup-btn-confirm {
|
||||
background-color: #E8101E;
|
||||
}
|
||||
|
||||
.category-popup-btn-confirm text {
|
||||
color: #ffffff;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -11,241 +11,8 @@
|
|||
|
||||
<!-- 表单区域 -->
|
||||
<view class="form-container">
|
||||
<!-- 服务名称 -->
|
||||
<view class="form-item">
|
||||
<text class="form-label required">服务名称</text>
|
||||
<input type="text" v-model="formData.title" placeholder="请输入服务名称" placeholder-class="placeholder"
|
||||
class="form-input" />
|
||||
</view>
|
||||
|
||||
<!-- 服务价格 -->
|
||||
<view class="form-item" style="flex-wrap: wrap;">
|
||||
<text class="form-label required">服务定价</text>
|
||||
<input type="digit" v-model.number="formData.line_price" placeholder="请输入定价,最多两位小数"
|
||||
placeholder-class="placeholder" class="form-input" @input="inpPrice2" @blur="inpPrice2"
|
||||
@confirm="inpPrice2" step="0.01" />
|
||||
<view class="price-tip">
|
||||
<text class="price-tip-card">服务的基础定价,未参与任何优惠的价格</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 服务价格 -->
|
||||
<view class="form-item" style="flex-wrap: wrap;">
|
||||
<text class="form-label required">服务售价</text>
|
||||
<input type="digit" v-model.number="formData.server_price" placeholder="请输入售价,最多两位小数"
|
||||
placeholder-class="placeholder" class="form-input" @input="inpPrice" @blur="inpPrice"
|
||||
@confirm="inpPrice" step="0.01" />
|
||||
<view class="price-tip">
|
||||
<text class="price-tip-card">服务参与单品优惠后的价格</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 服务分类 -->
|
||||
<view class="form-item">
|
||||
<text class="form-label required">服务分类</text>
|
||||
<view class="form-right" @click="openServiceTypePicker">
|
||||
<text :class="[
|
||||
'form-value',
|
||||
formData.first_class_id ? '' : 'placeholder',
|
||||
]">
|
||||
{{ serviceType || "选择分类" }}
|
||||
</text>
|
||||
<image src="https://mrrplus.oss-cn-beijing.aliyuncs.com/wxstaic/images/arrow_right.png"
|
||||
class="arrow-right" mode="aspectFit"></image>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 服务子类 -->
|
||||
<view class="form-item" v-if="showCategory">
|
||||
<text class="form-label required">服务子类</text>
|
||||
<view class="form-right" @click="openCategoryPicker">
|
||||
<text :class="[
|
||||
'form-value',
|
||||
formData.second_class_id ? '' : 'placeholder',
|
||||
]">
|
||||
{{ category || "选择子类" }}
|
||||
</text>
|
||||
<image src="https://mrrplus.oss-cn-beijing.aliyuncs.com/wxstaic/images/arrow_right.png"
|
||||
class="arrow-right" mode="aspectFit"></image>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 是否允许该服务在门店售卖 -->
|
||||
<view class="form-item shopsale-box" v-if="artisanType == 1">
|
||||
<view class="form-label required">
|
||||
是否允许该服务在门店端售卖,以供门店下单购买。
|
||||
{{
|
||||
showShopPrice
|
||||
? "门店下单购买后,您需到上门至门店进行服务,门店端展示价格为"
|
||||
: ""
|
||||
}}
|
||||
<text style="color: #e8101e" v-if="showShopPrice">{{
|
||||
shopPrice
|
||||
}}</text>
|
||||
{{ showShopPrice ? "元。" : "" }}
|
||||
</view>
|
||||
<view class="shopsale-change">
|
||||
<view class="shopsale-option" @click="changeSale(2)">
|
||||
<agree-radio :isAgree="formData.sj_see == 2"></agree-radio>
|
||||
<text class="option-text">允许</text>
|
||||
</view>
|
||||
<view class="shopsale-option" @click="changeSale(1)">
|
||||
<agree-radio :isAgree="formData.sj_see == 1"></agree-radio>
|
||||
<text class="option-text">不允许</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 服务时间 -->
|
||||
<view class="form-item">
|
||||
<text class="form-label required">服务时长</text>
|
||||
<input type="number" v-model="formData.server_time" placeholder="请输入服务时间"
|
||||
placeholder-class="placeholder" class="form-input" />
|
||||
<text class="pr-240">分钟</text>
|
||||
</view>
|
||||
|
||||
<!-- 服务简介 -->
|
||||
<!-- <view class="form-item textarea-item">
|
||||
<text class="form-label">服务简介</text>
|
||||
<textarea
|
||||
v-model="formData.detail"
|
||||
placeholder="详细描述商品的购买详情、使用情况及出售原因能够更快的出售商品哦~"
|
||||
placeholder-class="placeholder"
|
||||
class="form-textarea"
|
||||
maxlength="-1"
|
||||
/>
|
||||
</view> -->
|
||||
|
||||
<view class="form-step">
|
||||
<view class="form-item">
|
||||
<text class="form-label">图文详情</text>
|
||||
<view class="form-detail">
|
||||
<text @click="goImgAndText">{{
|
||||
formData.graphic_details ? "已设置" : "去设置"
|
||||
}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 服务简介 -->
|
||||
<view class="detail-card">
|
||||
<view class="detail-title">
|
||||
<text class="form-label">服务简介</text>
|
||||
</view>
|
||||
|
||||
<view class="detail-row">
|
||||
<text class="form-label required">服务功效</text>
|
||||
<view class="detail-textarea-wrap">
|
||||
<textarea class="detail-textarea" v-model="detailForm.effect" placeholder="请输入服务功效"
|
||||
placeholder-class="placeholder" maxlength="200"></textarea>
|
||||
<text class="detail-count">{{(detailForm.effect || '').length}}/200</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="detail-row">
|
||||
<text class="form-label required">适用人群</text>
|
||||
<view class="detail-textarea-wrap">
|
||||
<textarea class="detail-textarea" v-model="detailForm.crowd"
|
||||
placeholder="请输入不适用人群,例如:未成年人/孕妇不适用" placeholder-class="placeholder"
|
||||
maxlength="200"></textarea>
|
||||
<text class="detail-count">{{(detailForm.crowd || '').length}}/200</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="detail-row">
|
||||
<text class="form-label">产品清单</text>
|
||||
<view class="detail-textarea-wrap">
|
||||
<textarea class="detail-textarea" v-model="detailForm.product" placeholder="请输入产品清单"
|
||||
placeholder-class="placeholder" maxlength="200"></textarea>
|
||||
<text class="detail-count">{{(detailForm.product || '').length}}/200</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="detail-row">
|
||||
<text class="form-label">适用范围</text>
|
||||
<view class="detail-textarea-wrap">
|
||||
<textarea class="detail-textarea" v-model="detailForm.scope" placeholder="请输入使用时间/适用人数"
|
||||
placeholder-class="placeholder" maxlength="200"></textarea>
|
||||
<text class="detail-count">{{(detailForm.scope || '').length}}/200</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="detail-row">
|
||||
<text class="form-label">温馨提示</text>
|
||||
<view class="detail-textarea-wrap">
|
||||
<textarea class="detail-textarea" v-model="detailForm.tips" placeholder="请输入其他注意事项"
|
||||
placeholder-class="placeholder" maxlength="200"></textarea>
|
||||
<text class="detail-count">{{(detailForm.tips || '').length}}/200</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="form-step">
|
||||
<view class="form-item">
|
||||
<text class="form-label required">服务流程</text>
|
||||
<text @click="addStep">添加</text>
|
||||
</view>
|
||||
<view class="step" v-for="(item, i) in formData.process">
|
||||
<text class="form-label">第{{ i + 1 }}步</text>
|
||||
<input type="text" class="step-inp" v-model="formData.process[i].text" />
|
||||
<image src="https://mrrplus.oss-cn-beijing.aliyuncs.com/wxstaic/images/delete_icon.png"
|
||||
class="delete-icon" mode="aspectFit" @click="deleteStep(i)"></image>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 购买须知 -->
|
||||
<!-- <view class="form-item textarea-item">
|
||||
<text class="form-label">购买须知</text>
|
||||
<textarea
|
||||
v-model="formData.purchase_notes"
|
||||
placeholder="详细描述商品的购买详情、使用情况及出售原因能够更快的出售商品哦~"
|
||||
placeholder-class="placeholder"
|
||||
class="form-textarea"
|
||||
maxlength="-1"
|
||||
/>
|
||||
</view> -->
|
||||
<!-- <circle-progress
|
||||
:progress="progress"
|
||||
:size="300"
|
||||
:strokeWidth="15"
|
||||
color="rgba(252, 67, 124, 1)"
|
||||
backgroundColor="rgba(245, 245, 245, 1)"
|
||||
/> -->
|
||||
|
||||
<!-- 上传短视频 -->
|
||||
<view class="form-item-up upload-item">
|
||||
<view>
|
||||
<text class="form-label">上传短视频</text>
|
||||
<text class="upload-tip">(仅可上传1个视频)</text>
|
||||
</view>
|
||||
<!-- <upload-video
|
||||
@addVideo="addVideo"
|
||||
@deleteVideo="deleteVideo"
|
||||
:video="formData.video"></upload-video> -->
|
||||
<ali-oss-uploader :max-count="1" :max-size="100" :type="artisanType + 1"
|
||||
:user-id="formData.publish_user_id" :width="'654rpx'" :kind="2" :file-string="formData.video"
|
||||
accept="video/*" button-text="上传视频" @success="uploadVideoSuc" @error="uploadVideoErr"
|
||||
@delete="uploadVideoDel" @ckeckisShowPer="ckeckisShowPer" @ckecknowQer="ckecknowQer">
|
||||
</ali-oss-uploader>
|
||||
</view>
|
||||
|
||||
<!-- 服务图片 -->
|
||||
<view class="form-item-up upload-item">
|
||||
<view>
|
||||
<text class="form-label required">服务图片</text>
|
||||
<text class="upload-tip">(仅可上传9个图片)</text>
|
||||
</view>
|
||||
<!-- <upload-img
|
||||
:photo="formData.photo"
|
||||
:maxNum="9"
|
||||
:width="'654rpx'"
|
||||
@addImg="addImg"
|
||||
@deleteImage="deleteImage"></upload-img> -->
|
||||
<ali-oss-uploader v-model="formData.photo" :max-count="9" :max-size="5" :width="'654rpx'"
|
||||
:type="artisanType + 1" :user-id="formData.publish_user_id" button-text="上传图片"
|
||||
tips="最多上传3张图片,每张不超过5MB" @success="onUploadSuccess" @error="onUploadError" @delete="onFileDelete"
|
||||
@ckeckisShowPer="ckeckisShowPer" @ckecknowQer="ckecknowQer">
|
||||
</ali-oss-uploader>
|
||||
</view>
|
||||
<!-- 动态表单区域 -->
|
||||
<dynamic-form :fields="formFields" v-model="formData" ref="dynamicForm" />
|
||||
</view>
|
||||
|
||||
<!-- 底部按钮 -->
|
||||
|
|
@ -256,16 +23,6 @@
|
|||
<button class="btn-submit" @click="submitForm">提交</button>
|
||||
</view>
|
||||
|
||||
<!-- 服务类型选择器 -->
|
||||
<popup-picker :show="showServiceTypePicker" title="选择服务类型" :columns="[serviceTypes]"
|
||||
:value="[selectedServiceTypeIndex]" @close="showServiceTypePicker = false"
|
||||
@confirm="handleServiceTypeConfirm" />
|
||||
|
||||
<!-- 服务分类选择器 -->
|
||||
<PopupPickerMy :show="showCategoryPicker" title="选择服务分类" :columns="[categoryAll]" :value="selectedCategoryIndex"
|
||||
@close="showCategoryPicker = false" @confirm="handleCategoryConfirm" />
|
||||
<view class="mask" v-if="isShow" @click="isShow = false"></view>
|
||||
|
||||
<!-- 获取权限提示匡内容 -->
|
||||
<view class="permission" :class="{ transform: isShowPer && nowQer === 'xc' }">
|
||||
<view class="per-tit">美融融plus 对储存空间/照片权限申请说明</view>
|
||||
|
|
@ -275,29 +32,13 @@
|
|||
<view class="per-tit">美融融plus 对相机拍摄权限申请说明</view>
|
||||
<view class="per-cont">便于您使用该功能上传您的照片/图片/视频以及用于更换头像、发布商品等场景中所需内容。</view>
|
||||
</view>
|
||||
|
||||
<!-- 新增上门提醒弹窗-->
|
||||
<transition name="fade">
|
||||
<view class="popup-mask2" v-if="showReminderPopup" @click="closeReminderPopup(false)">
|
||||
<view class="popup-content2" @click.stop>
|
||||
<view class="popup-title">提示</view>
|
||||
<view class="popup-message">此服务是需要上门服务的哦~</view>
|
||||
<view class="popup-buttons">
|
||||
<view class="popup-btn popup-btn-notshow" @click="closeReminderPopup(true)">不再提示</view>
|
||||
<view class="popup-btn popup-btn-know" @click="closeReminderPopup(false)">我已知晓</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</transition>
|
||||
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import request from "../../utils/request";
|
||||
import PopupPicker from "@/components/popup-picker/popup-picker.vue";
|
||||
import PopupPickerMy from "@/components/popup-picker/PopupPickerMy.vue";
|
||||
import CircleProgress from "@/components/circle-progress/circle-progress.vue";
|
||||
import DynamicForm from "@/components/dynamic-form/dynamic-form.vue";
|
||||
import sparkMD5 from "spark-md5"; // 引入MD5计算库
|
||||
import {
|
||||
debounce
|
||||
|
|
@ -309,13 +50,13 @@
|
|||
|
||||
export default {
|
||||
components: {
|
||||
PopupPicker,
|
||||
CircleProgress,
|
||||
AliOssUploader,
|
||||
PopupPickerMy,
|
||||
DynamicForm,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
formFields: [], // 动态表单字段配置
|
||||
showReminderPopup: false,
|
||||
artisanType: getApp().globalData.artisanType,
|
||||
nowQer: "xc",
|
||||
|
|
@ -356,17 +97,8 @@
|
|||
},
|
||||
detailOtherList: [],
|
||||
|
||||
serviceTypes: [], // 服务分类
|
||||
categoryAll: [],
|
||||
showServiceTypePicker: false,
|
||||
showCategoryPicker: false,
|
||||
selectedServiceTypeIndex: 0,
|
||||
selectedCategoryIndex: [0],
|
||||
serviceType: "",
|
||||
category: "",
|
||||
imagesList: [], // 临时图片地址
|
||||
addDetailList: [],
|
||||
showCategory: false,
|
||||
showProgress: false,
|
||||
publish_user_id: null,
|
||||
isAdd: false,
|
||||
|
|
@ -381,19 +113,21 @@
|
|||
},
|
||||
onLoad(options) {
|
||||
this.state = options.state || "";
|
||||
// 接收分类参数
|
||||
if (options.first_class_id) {
|
||||
this.formData.first_class_id = options.first_class_id;
|
||||
}
|
||||
if (options.second_class_id) {
|
||||
this.formData.second_class_id = options.second_class_id;
|
||||
}
|
||||
if (options.id) {
|
||||
this.formData.id = options.id;
|
||||
this.getInit();
|
||||
} else {
|
||||
request.post("/sj/firstclass", {}).then((res) => {
|
||||
this.serviceTypes = this.serviceTypes.concat(res.data);
|
||||
});
|
||||
}
|
||||
// 获取模板
|
||||
this.getTemplate();
|
||||
// 判断是否显示上门提醒弹窗
|
||||
const notShow = uni.getStorageSync('addServiceReminderNotShow');
|
||||
// if (!notShow) {
|
||||
// this.showReminderPopup = true;
|
||||
// }
|
||||
this.getUserInfo();
|
||||
},
|
||||
computed: {
|
||||
|
|
@ -413,6 +147,53 @@
|
|||
},
|
||||
},
|
||||
methods: {
|
||||
// 获取服务模板
|
||||
async getTemplate() {
|
||||
// 模拟数据
|
||||
const mockFields = [
|
||||
{ key: 'title', type: 'text', label: '服务名称', is_required: 1, placeholder: '请输入服务名称' },
|
||||
{ key: 'service_price', type: 'number', label: '服务定价', is_required: 1, placeholder: '请输入定价,最多两位小数', tips: '(元)' },
|
||||
{ key: 'sale_price', type: 'number', label: '服务售价', is_required: 1, placeholder: '请输入售价,最多两位小数', tips: '(元)' },
|
||||
{ key: 'service_time', type: 'number', label: '服务时长', is_required: 1, placeholder: '请输入服务时间', tips: '(分钟)' },
|
||||
{ key: 'effect', type: 'textarea', label: '服务功效', is_required: 1, placeholder: '请输入服务功效' },
|
||||
{ key: 'crowd', type: 'textarea', label: '适用人群', is_required: 1, placeholder: '请输入不适用人群,例如:未成年人/孕妇不适用' },
|
||||
{ key: 'product', type: 'textarea', label: '产品清单', is_required: 0, placeholder: '请输入产品清单' },
|
||||
{ key: 'scope', type: 'textarea', label: '适用范围', is_required: 0, placeholder: '请输入使用时间/适用人数' },
|
||||
{ key: 'tips_text', type: 'textarea', label: '温馨提示', is_required: 0, placeholder: '请输入其他注意事项' },
|
||||
{ key: 'images', type: 'image_multi', label: '服务图片', is_required: 1, maxCount: 9 },
|
||||
{ key: 'video', type: 'video', label: '上传短视频', is_required: 0 },
|
||||
{ key: 'category', type: 'select', label: '服务分类', is_required: 1, placeholder: '请选择服务分类', options: [{ label: '美发', value: '1' }, { label: '美甲', value: '2' }, { label: '美容', value: '3' }] },
|
||||
{ key: 'service_type', type: 'single_select', label: '服务类型', is_required: 1, options: [{ label: '到店服务', value: '1' }, { label: '上门服务', value: '2' }] },
|
||||
{ key: 'features', type: 'multi_select', label: '服务特色', is_required: 0, options: [{ label: '一对一服务', value: '1' }, { label: '免费咨询', value: '2' }, { label: '售后保障', value: '3' }] },
|
||||
];
|
||||
try {
|
||||
const res = await request.post("/sj/servers/getTemplate", {
|
||||
first_class_id: this.formData.first_class_id,
|
||||
});
|
||||
if (res.data && res.data.fields && res.data.fields.length > 0) {
|
||||
this.formFields = res.data.fields;
|
||||
} else {
|
||||
// 接口返回空数据时使用模拟数据
|
||||
this.formFields = mockFields;
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("获取模板失败", e);
|
||||
// 接口失败时使用模拟数据
|
||||
this.formFields = mockFields;
|
||||
}
|
||||
// 初始化表单数据
|
||||
const initialData = {};
|
||||
this.formFields.forEach((field) => {
|
||||
if (field.default_value !== undefined) {
|
||||
initialData[field.key] = field.default_value;
|
||||
} else if (field.type === 'multi_select' || field.type === 'image_multi') {
|
||||
initialData[field.key] = [];
|
||||
} else if (!this.formData[field.key] && this.formData[field.key] !== 0) {
|
||||
initialData[field.key] = '';
|
||||
}
|
||||
});
|
||||
this.formData = { ...this.formData, ...initialData };
|
||||
},
|
||||
inpPrice2(e) {
|
||||
let that = this;
|
||||
let value = e.detail.value;
|
||||
|
|
@ -563,9 +344,6 @@
|
|||
this.formData.video = null;
|
||||
},
|
||||
async getInit() {
|
||||
const firstclass = await request.post("/sj/firstclass");
|
||||
this.serviceTypes = this.serviceTypes.concat(firstclass.data);
|
||||
|
||||
await request
|
||||
.post("/sj/servers/detail", {
|
||||
id: this.formData.id
|
||||
|
|
@ -580,32 +358,6 @@
|
|||
this.formData.detail = res.data.detail || [];
|
||||
// 调用初始化方法,它会优先使用新字段
|
||||
this.initDetailForm(res.data.detail || []);
|
||||
// 处理分类显示
|
||||
firstclass.data.filter((item, i) => {
|
||||
if (item.id == this.formData.first_class_id) {
|
||||
this.selectedServiceTypeIndex = i;
|
||||
this.serviceType = item.title;
|
||||
this.ratio = item.ratio;
|
||||
}
|
||||
});
|
||||
});
|
||||
request
|
||||
.post("/user/secondclass", {
|
||||
id: this.formData.first_class_id,
|
||||
})
|
||||
.then((result) => {
|
||||
if (result.data.length == 0 || !result.data) {
|
||||
this.showCategory = false;
|
||||
} else {
|
||||
this.categoryAll = result.data;
|
||||
this.categoryAll.filter((e, i) => {
|
||||
if (e.id == this.formData.second_class_id) {
|
||||
this.selectedCategoryIndex = [i];
|
||||
this.category = e.title;
|
||||
}
|
||||
});
|
||||
this.showCategory = true;
|
||||
}
|
||||
});
|
||||
},
|
||||
getUserInfo() {
|
||||
|
|
@ -774,20 +526,6 @@
|
|||
return
|
||||
}
|
||||
|
||||
if (!this.formData.first_class_id) {
|
||||
uni.showToast({
|
||||
title: "请选择服务分类",
|
||||
icon: "none",
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (this.showCategory && !this.formData.second_class_id) {
|
||||
uni.showToast({
|
||||
title: "请选择服务子类",
|
||||
icon: "none",
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (!this.formData.server_time) {
|
||||
uni.showToast({
|
||||
title: "请输入服务时长",
|
||||
|
|
@ -908,59 +646,6 @@
|
|||
}
|
||||
});
|
||||
}, 500),
|
||||
|
||||
// 打开服务分类
|
||||
openServiceTypePicker() {
|
||||
console.log("open");
|
||||
// 实现分类选择逻辑
|
||||
this.showServiceTypePicker = true;
|
||||
},
|
||||
|
||||
// 打开服务子类选择器
|
||||
openCategoryPicker() {
|
||||
// 实现子类选择逻辑
|
||||
if (!this.formData.first_class_id) {
|
||||
uni.showToast({
|
||||
title: "请先选择服务分类",
|
||||
icon: "none",
|
||||
});
|
||||
} else {
|
||||
this.showCategoryPicker = true;
|
||||
}
|
||||
},
|
||||
|
||||
// 处理服务类型选择
|
||||
handleServiceTypeConfirm(e) {
|
||||
console.log("e", e);
|
||||
const selectedType = e.items[0];
|
||||
if (this.formData.first_class_id == selectedType.id) return;
|
||||
this.formData.first_class_id = selectedType.id;
|
||||
this.serviceType = selectedType.title;
|
||||
this.ratio = selectedType.ratio;
|
||||
let id = selectedType.id;
|
||||
request
|
||||
.post("/user/secondclass", {
|
||||
id,
|
||||
})
|
||||
.then((res) => {
|
||||
if (res.data.length == 0 || !res.data) {
|
||||
this.showCategory = false;
|
||||
this.selectedCategoryIndex = [0];
|
||||
} else {
|
||||
this.categoryAll = res.data;
|
||||
this.showCategory = true;
|
||||
this.selectedCategoryIndex = [0];
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
// 处理服务分类选择
|
||||
handleCategoryConfirm(e) {
|
||||
console.log(e, "selectedCategory", this.categoryAll);
|
||||
const selectedCategory = e.item;
|
||||
this.formData.second_class_id = selectedCategory.id;
|
||||
this.category = selectedCategory.title;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -120,14 +120,14 @@ const request = async (options, isUpdate) => {
|
|||
|
||||
if (process.env.NODE_ENV === "development") {
|
||||
baseURL = url; // 发布到生产环境时,此处代码会被摇树移除掉。
|
||||
// baseURL = "http://116.63.163.121:93"; // 发布到生产环境时,此处代码会被摇树移除掉。
|
||||
//baseURL = 'http://60.247.146.5:96'; // 发布到生产环境时,此处代码会被摇树移除掉。
|
||||
baseURL = "https://app.mrrweb.com.cn";
|
||||
baseURL = "http://116.63.163.121:93"; // 发布到生产环境时,此处代码会被摇树移除掉。
|
||||
//baseURL = 'http://116.63.163.121:96'; // 发布到生产环境时,此处代码会被摇树移除掉。
|
||||
// baseURL = "https://app.mrrweb.com.cn";
|
||||
} else {
|
||||
baseURL = url; // 发布到生产环境时,此处代码会被摇树移除掉。
|
||||
// baseURL = "http://116.63.163.121:93"; // 发布到生产环境时,此处代码会被摇树移除掉。
|
||||
//baseURL = 'http://60.247.146.5:96'; // 发布到生产环境时,此处代码会被摇树移除掉。
|
||||
baseURL = "https://app.mrrweb.com.cn";
|
||||
baseURL = "http://116.63.163.121:93"; // 发布到生产环境时,此处代码会被摇树移除掉。
|
||||
//baseURL = 'http://116.63.163.121:96'; // 发布到生产环境时,此处代码会被摇树移除掉。
|
||||
// baseURL = "https://app.mrrweb.com.cn";
|
||||
|
||||
console.log("生产环境");
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue