mrr.sj.front/pages/shop/add-service.vue

2216 lines
52 KiB
Vue
Raw Normal View History

2026-03-24 11:45:13 +08:00
<template>
2026-04-03 15:49:27 +08:00
<view class="add-service">
<view class="fixed-header">
<view class="tip" v-if="formData.back_text && formData.state == 4">
<image
src="https://mrrplus.oss-cn-beijing.aliyuncs.com/photo/system/390d9d12-3c30-4639-9ac3-7b604b606b6d.png"
class="tip-icon"></image>
<text>{{ formData.back_text }}</text>
</view>
</view>
<!-- 表单区域 -->
<view class="form-container">
2026-06-11 20:48:30 +08:00
<!-- 服务分类仅显示不可编辑 -->
2026-06-12 13:59:52 +08:00
<view class="form-item service-class-item" v-if="serviceClassName">
<text class="form-label">服务分类</text>
<text class="form-value service-class-value">{{ serviceClassName }}</text>
2026-06-11 20:48:30 +08:00
</view>
2026-06-09 17:49:15 +08:00
<!-- 动态表单区域 -->
2026-06-11 20:48:30 +08:00
<dynamic-form :fields="formFields" v-model="formData" :upload-user-id="formData.publish_user_id"
ref="dynamicForm" />
<!-- 服务流程 -->
2026-06-23 09:55:51 +08:00
<view class="form-step" v-if="!hasDynamicServiceStepField">
2026-06-11 20:48:30 +08:00
<view class="form-item">
<text class="form-label">服务流程</text>
<text @click="addStep" style="color: #FF4767;">添加步骤</text>
</view>
<view class="step" v-for="(item, i) in formData.process" :key="i">
<text class="form-label">{{ i + 1 }}</text>
<input type="text" class="step-inp" v-model="formData.process[i].text" placeholder="请输入步骤内容" />
<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="video-upload" v-if="!hasDynamicVideoField">
<view class="flex-c">
<text class="form-label required">上传视频</text>
<text class="upload-tip">(仅可上传1个视频)</text>
</view>
<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"
2026-06-23 09:55:51 +08:00
accept="video/*" button-text="上传视频" @input="setVideoValue" @change="setVideoValue"
@success="uploadVideoSuc" @error="uploadVideoErr" @delete="uploadVideoDel"
@ckeckisShowPer="ckeckisShowPer" @ckecknowQer="ckecknowQer">
2026-06-11 20:48:30 +08:00
</ali-oss-uploader>
</view>
2026-04-03 15:49:27 +08:00
</view>
<!-- 底部按钮 -->
<view class="bottom-buttons">
<button class="btn-draft" @click="saveDraft" v-if="!state || state == 0">
存草稿
</button>
<button class="btn-submit" @click="submitForm">提交</button>
</view>
<!-- 获取权限提示匡内容 -->
<view class="permission" :class="{ transform: isShowPer && nowQer === 'xc' }">
<view class="per-tit">美融融plus 对储存空间/照片权限申请说明</view>
<view class="per-cont">便于您使用该功能上传您的照片/图片/视频以及用于更换头像发布商品等场景中读取相册和文件内容</view>
</view>
<view class="permission" :class="{ transform: isShowPer && nowQer === 'xj' }">
<view class="per-tit">美融融plus 对相机拍摄权限申请说明</view>
<view class="per-cont">便于您使用该功能上传您的照片/图片/视频以及用于更换头像发布商品等场景中所需内容</view>
</view>
</view>
2026-03-24 11:45:13 +08:00
</template>
<script>
2026-06-09 17:49:15 +08:00
import request from "../../utils/request";
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
} from "@/utils/debounce.js";
import AliOssUploader from "../../components/ali-oss-uploader/ali-oss-uploader.vue";
import permissionUtils from "../../utils/per";
import locationService from "../../utils/locationService";
import _public from "../../utils/public";
export default {
components: {
CircleProgress,
AliOssUploader,
DynamicForm,
},
data() {
return {
formFields: [], // 动态表单字段配置
2026-06-11 20:48:30 +08:00
processFieldKey: "", // 服务步骤在config_data中的key用作提交参数名
firstClassName: "",
secondClassName: "",
2026-06-09 17:49:15 +08:00
showReminderPopup: false,
artisanType: getApp().globalData.artisanType,
nowQer: "xc",
progress: 0,
isShow: false,
state: null,
isShowPer: false,
formData: {
2026-04-03 15:49:27 +08:00
publish_user_id: null,
2026-06-09 17:49:15 +08:00
server_kind: getApp().globalData.artisanType,
title: "",
server_price: "",
line_price: '',
server_time: "",
first_class_id: "",
second_class_id: "",
detail: [],
process: [],
video: "",
photo: [],
graphic_details: "",
sj_see: null, // 1代表商家端不可见 2代表商家端可见
// 新增的五个独立字段
efficacy: "",
crowd: "",
product: "",
scope: "",
prompt: "",
},
ratio: null,
// 服务简介部分
detailForm: {
effect: "",
crowd: "",
product: "",
scope: "",
tips: "",
},
detailOtherList: [],
imagesList: [], // 临时图片地址
addDetailList: [],
showProgress: false,
publish_user_id: null,
isAdd: false,
isDraft: false,
2026-06-11 20:48:30 +08:00
postid: 0
2026-06-09 17:49:15 +08:00
};
},
2026-06-11 20:48:30 +08:00
onShow() { },
async onLoad(options) {
2026-06-10 16:23:47 +08:00
// 监听图文详情编辑回调在onLoad注册确保navigateBack时能收到事件
uni.$on("updateGraphicDetails", (data) => {
// 替换整个formData对象引用确保dynamic-form的watch.value能触发
this.formData = Object.assign({}, this.formData, {
graphic_details: JSON.stringify(data)
});
});
2026-06-09 17:49:15 +08:00
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;
}
2026-06-11 20:48:30 +08:00
// 获取模板先加载模板再获取详情确保formFields已就绪
await this.getTemplate();
2026-06-09 17:49:15 +08:00
if (options.id) {
this.formData.id = options.id;
2026-06-11 20:48:30 +08:00
await this.getInit();
2026-06-09 17:49:15 +08:00
}
2026-06-11 20:48:30 +08:00
// 获取分类名称用于顶部显示
await this.loadClassNames();
2026-06-09 17:49:15 +08:00
// 判断是否显示上门提醒弹窗
const notShow = uni.getStorageSync('addServiceReminderNotShow');
2026-06-11 20:48:30 +08:00
await this.getUserInfo();
2026-06-09 17:49:15 +08:00
},
2026-06-10 16:23:47 +08:00
onUnload() {
uni.$off("updateGraphicDetails");
},
2026-06-09 17:49:15 +08:00
computed: {
showShopPrice() {
return (
this.artisanType == 1 &&
this.formData.sj_see == 2 &&
this.formData.server_price &&
this.formData.first_class_id
);
},
shopPrice() {
return _public.roundUpToTwoDecimals(
this.formData.server_price,
this.ratio
);
2026-04-03 15:49:27 +08:00
},
2026-06-23 09:55:51 +08:00
hasDynamicVideoField() {
return this.formFields.some((group) => {
2026-06-11 20:48:30 +08:00
if (group.type === 'single' && group.field) {
return group.field.type === 'video';
}
if (group.type === 'section' && Array.isArray(group.fields)) {
return group.fields.some((field) => field.type === 'video');
}
2026-06-23 09:55:51 +08:00
return group.type === 'video';
});
},
hasDynamicServiceStepField() {
return this.formFields.some((group) => {
if (group.type === 'single' && group.field) {
return group.field.type === 'service_step';
}
if (group.type === 'section' && Array.isArray(group.fields)) {
return group.fields.some((field) => field.type === 'service_step');
}
return group.type === 'service_step';
});
},
serviceClassName() {
return [this.firstClassName, this.secondClassName].filter(Boolean).join('/');
},
2026-06-12 13:59:52 +08:00
},
2026-06-09 17:49:15 +08:00
methods: {
2026-06-10 16:23:47 +08:00
// 字段类型映射:接口类型 -> dynamic-form类型
mapFieldType(fieldType) {
const typeMap = {
'text': 'text',
2026-06-23 09:55:51 +08:00
'number': 'number',
'image_multi': 'image_multi',
'video': 'video',
'service_step': 'service_step',
'richtext_editor': 'richtext_editor',
'select': 'select',
'single_select': 'single_select',
'radio': 'single_select',
'switch': 'single_select',
'boolean': 'single_select',
'multi_select': 'multi_select',
'textarea': 'textarea',
};
return typeMap[fieldType] || 'text';
},
normalizeFieldOptions(options) {
if (!Array.isArray(options)) return [];
return options.map((item) => {
if (item && typeof item === 'object') {
const value = item.value !== undefined ? item.value :
(item.id !== undefined ? item.id :
(item.key !== undefined ? item.key :
(item.code !== undefined ? item.code : item.label)));
const label = item.label || item.title || item.name || item.text || String(value);
return {
...item,
label,
value,
};
}
return {
label: String(item),
value: item,
};
});
},
applyFieldHint(field, rawConfig) {
if (!rawConfig || !rawConfig.hint) return;
if (rawConfig.showHint === false || rawConfig.showHint === 0 || rawConfig.showHint === '0') return;
field.tip = rawConfig.hint;
},
2026-06-10 16:23:47 +08:00
// 将接口config_data转换为dynamic-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}`,
};
2026-06-23 09:55:51 +08:00
if (preset.options && Array.isArray(preset.options) && preset.options.length > 0) {
field.options = this.normalizeFieldOptions(preset.options);
}
if (preset.default_value !== undefined) {
field.default_value = preset.default_value;
} else if (preset.defaultValue !== undefined) {
field.default_value = preset.defaultValue;
}
2026-06-10 16:23:47 +08:00
2026-06-23 09:55:51 +08:00
this.applyFieldHint(field, preset);
if (preset.key === 'line_price' || preset.key === 'server_price') {
field.tips = '(元)';
if (!field.tip) {
field.tip = preset.key === 'line_price' ? '服务的基础定价,未参与任何优惠的价格' : '服务参与单品优惠后的价格';
}
2026-06-10 16:23:47 +08:00
} 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;
2026-06-11 20:48:30 +08:00
field.maxSize = preset.maxFileSize;
2026-06-10 16:23:47 +08:00
}
2026-06-11 20:48:30 +08:00
field._fieldType = preset.fieldType;
2026-06-10 16:23:47 +08:00
field._hasOriginalKey = !!preset.key;
2026-06-11 20:48:30 +08:00
groups.push({
type: 'single',
field
});
2026-06-10 16:23:47 +08:00
});
}
// 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) => {
2026-06-11 20:48:30 +08:00
const fieldKey = sectionField.key ||
`custom_${sectionField.id || customFieldIndex++}`;
2026-06-10 16:23:47 +08:00
const field = {
key: fieldKey,
type: this.mapFieldType(sectionField.fieldType),
label: sectionField.title,
is_required: sectionField.required,
2026-06-11 20:48:30 +08:00
placeholder: sectionField.placeholder ||
`请输入${sectionField.title}`,
2026-06-10 16:23:47 +08:00
};
if (sectionField.key === 'server_time') {
field.placeholder = '请输入服务时长';
field.tips = '分钟';
}
2026-06-23 09:55:51 +08:00
this.applyFieldHint(field, sectionField);
if (sectionField.options && Array.isArray(sectionField.options) &&
sectionField.options.length > 0) {
field.options = this.normalizeFieldOptions(sectionField.options);
}
if (sectionField.default_value !== undefined) {
field.default_value = sectionField.default_value;
} else if (sectionField.defaultValue !== undefined) {
field.default_value = sectionField.defaultValue;
}
2026-06-10 16:23:47 +08:00
2026-06-11 20:48:30 +08:00
if (sectionField.maxCount && (sectionField.fieldType === 'multi_select' ||
sectionField.fieldType === 'image_multi' || sectionField.fieldType === 'video')) {
2026-06-10 16:23:47 +08:00
field.maxCount = sectionField.maxCount;
}
2026-06-11 20:48:30 +08:00
if (sectionField.maxLength && (sectionField.fieldType === 'text' ||
sectionField.fieldType === 'textarea')) {
2026-06-10 16:23:47 +08:00
field.maxLength = sectionField.maxLength;
}
if (sectionField.maxLength && sectionField.fieldType === 'number') {
field.maxLength = sectionField.maxLength;
}
if (sectionField.maxFileSize) {
field.maxFileSize = sectionField.maxFileSize;
2026-06-11 20:48:30 +08:00
field.maxSize = sectionField.maxFileSize;
2026-06-10 16:23:47 +08:00
}
2026-06-11 20:48:30 +08:00
field._fieldType = sectionField.fieldType;
2026-06-10 16:23:47 +08:00
field._hasOriginalKey = !!sectionField.key;
2026-06-23 09:55:51 +08:00
field._original = {
id: sectionField.id,
sectionId: section.id,
sectionType: section.sectionType,
userDefined: sectionField.userDefined,
};
field._sectionTitle = section.sectionTitle || '';
field._sectionId = section.id;
field._sectionType = section.sectionType;
2026-06-10 16:23:47 +08:00
sectionFields.push(field);
});
}
groups.push({
type: 'section',
sectionTitle: section.sectionTitle || '',
fields: sectionFields,
});
});
}
return groups;
},
2026-06-09 17:49:15 +08:00
// 获取服务模板
async getTemplate() {
try {
const res = await request.post("/sj/servers/getTemplate", {
2026-06-11 20:48:30 +08:00
first_class: this.formData.first_class_id,
2026-06-09 17:49:15 +08:00
});
2026-06-10 16:23:47 +08:00
console.log("模板接口返回数据:", res);
if (res.data && res.data.config_data) {
// 使用接口返回的config_data转换为表单字段
// config_data 可能是字符串,需要解析
2026-06-11 20:48:30 +08:00
const configData = typeof res.data.config_data === 'string' ?
JSON.parse(res.data.config_data) :
res.data.config_data;
2026-06-10 16:23:47 +08:00
console.log("解析后的config_data:", configData);
2026-06-11 20:48:30 +08:00
// 查找service_step类型的字段记录其key作为提交参数名
this.processFieldKey = "";
const allConfigFields = [
...(configData.presetFields || []),
...(configData.sections || []).reduce((acc, s) => acc.concat(s.fields || []), [])
];
const stepField = allConfigFields.find(f => f.fieldType === 'service_step');
if (stepField && stepField.key) {
this.processFieldKey = stepField.key;
}
2026-06-10 16:23:47 +08:00
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) {
// 兼容旧格式
2026-06-09 17:49:15 +08:00
this.formFields = res.data.fields;
}
} catch (e) {
console.error("获取模板失败", e);
}
// 初始化表单数据
const initialData = {};
2026-06-11 20:48:30 +08:00
const allFields = this.getDynamicFields();
allFields.forEach((field) => {
2026-06-09 17:49:15 +08:00
if (field.default_value !== undefined) {
initialData[field.key] = field.default_value;
2026-06-11 20:48:30 +08:00
} else if (
field.type === 'multi_select' ||
field.type === 'image_multi' ||
field.type === 'service_step' ||
(field.type === 'video' && field.maxCount > 1)
) {
2026-06-09 17:49:15 +08:00
initialData[field.key] = [];
} else if (!this.formData[field.key] && this.formData[field.key] !== 0) {
initialData[field.key] = '';
}
2026-04-03 15:49:27 +08:00
});
2026-06-11 20:48:30 +08:00
this.formData = {
...this.formData,
...initialData
};
},
getDynamicFields() {
const fields = [];
this.formFields.forEach((group) => {
if (group.type === 'single' && group.field) {
fields.push(group.field);
} else if (group.type === 'section' && Array.isArray(group.fields)) {
fields.push(...group.fields);
} else if (group.key) {
fields.push(group);
}
});
return fields;
},
// 根据分类ID获取分类名称
async loadClassNames() {
if (!this.formData.first_class_id) return;
try {
const firstRes = await request.post("/sj/firstclass", {});
if (firstRes.data && firstRes.data.length > 0) {
const matched = firstRes.data.find(item => item.id == this.formData.first_class_id);
if (matched) {
this.firstClassName = matched.title;
}
}
} catch (e) {
console.error("获取一级分类失败", e);
}
if (!this.formData.second_class_id) return;
try {
const secondRes = await request.post("/user/secondclass", {
id: this.formData.first_class_id,
});
if (secondRes.data && secondRes.data.length > 0) {
const matched = secondRes.data.find(item => item.id == this.formData.second_class_id);
if (matched) {
this.secondClassName = matched.title;
}
}
} catch (e) {
console.error("获取二级分类失败", e);
}
2026-04-03 15:49:27 +08:00
},
2026-06-09 17:49:15 +08:00
inpPrice2(e) {
let that = this;
let value = e.detail.value;
// 1. 移除非数字和小数点
value = value.replace(/[^\d.]/g, '');
// 2. 确保只有一个小数点
const parts = value.split('.');
if (parts.length > 2) {
value = parts[0] + '.' + parts.slice(1).join('');
}
// 3. 限制小数点后最多一位
if (parts.length > 1) {
value = parts[0] + '.' + parts[1].slice(0, 2);
}
// 4. 如果以小数点开头前面补0
if (value.startsWith('.')) {
value = '0' + value;
}
that.$set(that.formData, 'line_price', value);
},
2026-06-23 09:55:51 +08:00
// 服务简介部分
initDetailForm(detail = []) {
2026-06-09 17:49:15 +08:00
// 先重置 detailForm 为空
this.detailForm = {
effect: "",
crowd: "",
product: "",
scope: "",
tips: "",
};
this.detailOtherList = [];
// 优先从后端返回的新字段赋值
if (this.formData.efficacy) this.detailForm.effect = this.formData.efficacy;
if (this.formData.crowd) this.detailForm.crowd = this.formData.crowd;
if (this.formData.product) this.detailForm.product = this.formData.product;
if (this.formData.scope) this.detailForm.scope = this.formData.scope;
if (this.formData.prompt) this.detailForm.tips = this.formData.prompt;
2026-06-09 17:49:15 +08:00
// 优先从后端返回的新字段赋值
if (this.formData.efficacy) this.detailForm.effect = this.formData.efficacy;
if (this.formData.crowd) this.detailForm.crowd = this.formData.crowd;
if (this.formData.product) this.detailForm.product = this.formData.product;
if (this.formData.scope) this.detailForm.scope = this.formData.scope;
if (this.formData.prompt) this.detailForm.tips = this.formData.prompt;
// 如果新字段都为空,则尝试从旧的 detail 数组中提取(兼容旧数据)
if (!this.formData.efficacy && !this.formData.crowd && !this.formData.product) {
(detail || []).forEach((item) => {
if (item.title === "服务功效") {
this.detailForm.effect = item.text || "";
} else if (item.title === "适用人群") {
this.detailForm.crowd = item.text || "";
} else if (item.title === "产品清单") {
this.detailForm.product = item.text || "";
} else if (item.title === "适用范围" || item.title === "使用范围") {
this.detailForm.scope = item.text || "";
} else if (item.title === "温馨提示") {
this.detailForm.tips = item.text || "";
} else {
this.detailOtherList.push({
title: item.title || "",
text: item.text || "",
photo: item.photo || "",
});
}
});
}
},
syncDetailForm() {
2026-06-10 16:23:47 +08:00
// 双向同步如果formData有值来自dynamic-form同步到detailForm否则从detailForm同步到formData
if (this.formData.efficacy) {
this.detailForm.effect = this.formData.efficacy;
} else {
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();
}
if (this.formData.product) {
this.detailForm.product = this.formData.product;
} else {
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();
}
if (this.formData.prompt) {
this.detailForm.tips = this.formData.prompt;
} else {
this.formData.prompt = (this.detailForm.tips || "").trim();
}
2026-06-09 17:49:15 +08:00
},
checkDetailForm() {
return true;
2026-04-03 15:49:27 +08:00
},
2026-06-09 17:49:15 +08:00
closeReminderPopup(notShowAgain) {
console.log('关闭弹窗,不再提示:', notShowAgain);
if (notShowAgain) {
uni.setStorageSync('addServiceReminderNotShow', true);
}
this.showReminderPopup = false;
2026-04-03 15:49:27 +08:00
},
2026-06-09 17:49:15 +08:00
goImgAndText() {
uni.navigateTo({
url: `/pages/shop/add-img-text?list=${this.formData.graphic_details}`,
});
},
ckeckisShowPer(value) {
console.log("ckeckisShowPer==========", value);
this.isShowPer = value;
},
ckecknowQer(value) {
this.nowQer = value;
},
2026-06-23 09:55:51 +08:00
addVideo(value) {
this.setVideoValue(value);
},
normalizeVideoValue(value) {
if (!value) return "";
if (typeof value === "string") {
const str = value.trim();
if (!str) return "";
if ((str.startsWith("{") && str.endsWith("}")) || (str.startsWith("[") && str.endsWith("]"))) {
try {
return this.normalizeVideoValue(JSON.parse(str));
} catch (error) {
return str;
}
}
return str;
}
if (Array.isArray(value)) {
for (let i = 0; i < value.length; i++) {
const url = this.normalizeVideoValue(value[i]);
if (url) return url;
}
return "";
}
if (typeof value === "object") {
const candidates = [value.url, value.path, value.filePath, value.tempFilePath, value.src, value.value];
for (let i = 0; i < candidates.length; i++) {
const url = this.normalizeVideoValue(candidates[i]);
if (url) return url;
}
return "";
}
return String(value);
},
setVideoValue(value) {
this.formData.video = this.normalizeVideoValue(value);
},
2026-06-09 17:49:15 +08:00
// addImg(arr) {
// this.formData.photo = this.formData.photo.concat(arr);
// },
// deleteImage(index) {
// this.formData.photo.splice(index, 1)
// },
onUploadSuccess(file) {
console.log("添加的图片", this.formData.photo);
},
onUploadError(error) {
console.error("上传失败:", error);
uni.showToast({
title: "上传失败",
icon: "none",
});
},
onFileDelete(file, index) {
console.log("删除文件:", this.formData.photo, index);
},
2026-06-23 09:55:51 +08:00
uploadVideoSuc(file) {
this.setVideoValue(file);
},
2026-06-09 17:49:15 +08:00
uploadVideoErr() { },
uploadVideoDel() {
this.formData.video = null;
},
async getInit() {
2026-06-23 09:55:51 +08:00
await request
.post("/sj/servers/detail", {
id: this.formData.id
})
.then(async (res) => {
// 直接合并所有字段,避免遗漏
this.formData = {
...this.formData,
...res.data
};
this.setVideoValue(this.formData.video);
if (this.formData.first_class_id && this.formFields.length === 0) {
await this.getTemplate();
}
// 确保 process 保持为数组(后端可能返回字符串)
2026-06-11 20:48:30 +08:00
if (!Array.isArray(this.formData.process)) {
this.formData.process = [];
}
2026-06-09 17:49:15 +08:00
// 确保 detail 数组被保留(可能用于兼容)
this.formData.detail = res.data.detail || [];
2026-06-11 20:48:30 +08:00
// 确保photo为纯字符串数组去除对象索引
if (this.formData.photo && !Array.isArray(this.formData.photo)) {
this.formData.photo = Object.values(this.formData.photo);
}
if (Array.isArray(this.formData.photo)) {
this.formData.photo = this.formData.photo.map(function (item) {
return typeof item === 'string' ? item : (item && item.url ? item.url :
String(item));
});
}
// 解析intro字段的JSON数据用于回显自定义字段
if (res.data.intro) {
try {
const introArr = typeof res.data.intro === 'string' ?
JSON.parse(res.data.intro) :
res.data.intro;
if (Array.isArray(introArr)) {
// 遍历所有表单字段,构建 id -> field 的映射
const idToFieldMap = {};
this.formFields.forEach(function (group) {
const fields = group.field ? [group.field] : (group.fields || []);
fields.forEach(function (f) {
if (f._original && f._original.id) {
idToFieldMap[f._original.id] = f;
}
});
});
// 用intro中的id去匹配表单字段将值回填到formData
introArr.forEach(function (item) {
const matchedField = idToFieldMap[item.id];
if (matchedField && item.value !== undefined) {
this.$set(this.formData, matchedField.key, item.value);
}
}.bind(this));
}
} catch (e) {
console.error('解析intro字段失败:', e);
}
}
// 解析服务步骤数据回显
if (this.processFieldKey && res.data[this.processFieldKey]) {
try {
const processRaw = res.data[this.processFieldKey];
const processArr = typeof processRaw === 'string' ?
JSON.parse(processRaw) : processRaw;
if (Array.isArray(processArr)) {
this.formData.process = processArr.map(item => ({
title: item.title || '',
text: item.text || '',
photo: item.photo || '',
}));
}
} catch (e) {
console.error('解析服务步骤数据失败:', e);
}
} else if (res.data.intro) {
// 无独立key时从intro数组中查找service_step
try {
const introArr = typeof res.data.intro === 'string' ?
JSON.parse(res.data.intro) : res.data.intro;
if (Array.isArray(introArr)) {
const stepItem = introArr.find(item => item.id === 'service_step');
if (stepItem && Array.isArray(stepItem.value)) {
this.formData.process = stepItem.value.map(item => ({
title: item.title || '',
text: item.text || '',
photo: item.photo || '',
}));
}
}
} catch (e) {
console.error('从intro解析服务步骤失败:', e);
}
}
2026-06-09 17:49:15 +08:00
// 调用初始化方法,它会优先使用新字段
this.initDetailForm(res.data.detail || []);
});
},
getUserInfo() {
2026-06-11 20:48:30 +08:00
return request
.post("/sj/user/getuser", {
type: 2,
2026-06-09 17:49:15 +08:00
})
.then((result) => {
2026-06-11 20:48:30 +08:00
console.log('getUserInfo返回:', result);
const userId = result.data.id || result.data.user_id || result.data.userId;
console.log('12121212');
if (userId) {
this.formData.publish_user_id = userId;
this.publish_user_id = userId;
uni.setStorageSync('userId', userId);
} else {
// 接口没有返回id从storage获取
this.formData.publish_user_id = uni.getStorageSync('userId') || '';
}
console.log('publish_user_id:', this.formData.publish_user_id);
this.postid = result.data.id
})
.catch((err) => {
console.error('getUserInfo失败:', err);
// 接口失败从storage获取
this.formData.publish_user_id = uni.getStorageSync('userId') || '';
2026-06-09 17:49:15 +08:00
});
},
// 是否在商家端售卖
changeSale(value) {
this.formData.sj_see = value;
},
// 输入价格
inpPrice(e) {
let that = this;
let value = e.detail.value;
// 1. 移除非数字和小数点
value = value.replace(/[^\d.]/g, "");
// 2. 确保只有一个小数点
const parts = value.split(".");
if (parts.length > 2) {
value = parts[0] + "." + parts.slice(1).join("");
}
2026-04-03 15:49:27 +08:00
2026-06-09 17:49:15 +08:00
// 3. 限制小数点后最多一位
if (parts.length > 1) {
value = parts[0] + "." + parts[1].slice(0, 2);
}
// 4. 如果以小数点开头前面补0
if (value.startsWith(".")) {
value = "0" + value;
}
that.$set(that.formData, "server_price", value);
},
deleteStep(i) {
this.formData.process.splice(i, 1);
},
addStep() {
let i = this.formData.process.length + 1;
let arr = {
title: "第" + i + "步",
text: "",
photo: "",
};
this.formData.process.push(arr);
},
2026-06-11 20:48:30 +08:00
// 视频上传相关方法
uploadVideoSuc(file) {
2026-06-23 09:55:51 +08:00
this.setVideoValue(file);
2026-06-11 20:48:30 +08:00
},
uploadVideoErr() { },
uploadVideoDel() {
this.formData.video = '';
},
ckeckisShowPer(val) {
this.isShowPer = val;
},
ckecknowQer(val) {
this.nowQer = val;
},
2026-06-09 17:49:15 +08:00
// 删除视频
deleteVideo() {
this.formData.video = "";
},
2026-06-09 17:49:15 +08:00
// 删除视频
deleteVideo() {
this.formData.video = "";
},
// 保存草稿
saveDraft: debounce(function () {
if (this.isDraft) return;
this.syncDetailForm();
const {
title,
server_price,
line_price,
server_time,
first_class_id,
second_class_id,
detail,
process,
video,
photo,
} = this.formData;
let item = {
title,
server_price,
line_price,
server_time,
first_class_id,
second_class_id,
detail,
process,
video,
photo,
};
// 检查是否所有字段都为空
const isEmpty = Object.values(item).every((value) => {
if (Array.isArray(value)) {
return value.length === 0;
2026-04-03 15:49:27 +08:00
}
2026-06-09 17:49:15 +08:00
return !value;
});
if (isEmpty) {
2026-04-03 15:49:27 +08:00
uni.showToast({
title: "请至少填写一项内容",
2026-04-03 15:49:27 +08:00
icon: "none",
});
return;
2026-06-10 16:23:47 +08:00
}
2026-04-03 15:49:27 +08:00
// 实现保存草稿逻辑
this.formData.state = 0;
const submitData = this.buildSubmitData();
submitData.state = 0;
let url = null;
if (!this.formData.id) {
url = "/user/serveradd";
} else {
url = "/user/serverchange";
}
request.post(url, submitData).then((res) => {
if (res.state == 1) {
this.isDraft = true;
const pages = getCurrentPages();
const prevPage = pages[pages.length - 2]; // 上一个页面
uni.showToast({
title: res.msg,
icon: "success",
duration: 1000,
});
setTimeout(() => {
uni.navigateBack({
delta: 1,
success: () => {
prevPage.$vm.currentTab = 3;
prevPage.$vm.state = 0;
prevPage.$vm.getServicesList(); // 直接调用上个页面的刷新方法
},
});
}, 500);
} else {
2026-04-03 15:49:27 +08:00
uni.showToast({
title: res.msg,
2026-04-03 15:49:27 +08:00
icon: "none",
});
this.isDraft = false;
2026-04-03 15:49:27 +08:00
}
});
}, 500),
// 验证动态表单字段
validateDynamicForm() {
2026-06-23 09:55:51 +08:00
// 遍历 formGroups分组结构按顺序验证必填项
for (let i = 0; i < this.formFields.length; i++) {
const group = this.formFields[i];
const fieldsToCheck = [];
if (group.type === 'single' && group.field) {
fieldsToCheck.push(group.field);
} else if (group.type === 'section' && group.fields) {
fieldsToCheck.push(...group.fields);
2026-06-10 16:23:47 +08:00
}
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);
2026-06-23 09:55:51 +08:00
if (isEmpty) {
// 根据字段类型显示不同的提示语
const prefix = (field.type === 'image_multi' || field.type === 'video') ? '请上传' :
(field.type === 'select' || field.type === 'single_select' || field.type === 'multi_select') ?
'请选择' : '请输入';
uni.showToast({
title: `${prefix}${field.label}`,
icon: "none",
2026-06-10 16:23:47 +08:00
});
2026-06-23 09:55:51 +08:00
return false;
}
if (field.type === 'service_step' && Array.isArray(value)) {
const hasEmptyStep = value.some((item) => !item || !item.text || !item.text.trim());
if (hasEmptyStep) {
uni.showToast({
title: "请完善服务流程内容",
icon: "none",
});
return false;
}
}
2026-04-03 15:49:27 +08:00
2026-06-23 09:55:51 +08:00
// 服务时长特殊验证
if (field.key === 'server_time') {
if (value < 10) {
uni.showToast({
title: "服务时长最短为10分钟",
icon: "none",
});
return false;
}
if (value > 240) {
uni.showToast({
title: "服务时长最长为240分钟",
icon: "none",
});
return false;
}
2026-06-10 16:23:47 +08:00
}
}
}
return true;
},
// 构建提交数据有key的字段直接作为参数没有key的字段放入intro数组
buildSubmitData() {
const submitData = {};
const introArr = [];
console.log(this.formData, '=-=-=-=-=-=');
// 添加固定字段
if (this.formData.publish_user_id) {
if (this.formData.id) {
submitData.id = this.formData.id;
}
submitData.publish_user_id = this.formData.publish_user_id || uni.getStorageSync('userId') || '';
submitData.first_class_id = this.formData.first_class_id || '';
submitData.second_class_id = this.formData.second_class_id || '';
submitData.server_kind = this.formData.server_kind || getApp().globalData.artisanType;
2026-06-11 20:48:30 +08:00
}
// 视频字段(独立上传组件)
2026-06-23 09:55:51 +08:00
const videoUrl = this.normalizeVideoValue(this.formData.video);
if (videoUrl) {
submitData.video = videoUrl;
2026-06-10 16:23:47 +08:00
}
2026-06-04 15:00:22 +08:00
// 遍历分组,按顺序处理字段
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;
}
2026-06-23 09:55:51 +08:00
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.type === 'service_step' && Array.isArray(value)) {
const processArr = value
.filter(item => item && item.text && item.text.trim())
.map(item => ({ text: item.text.trim() }));
if (processArr.length === 0) continue;
if (field._hasOriginalKey) {
submitData[field.key] = JSON.stringify(processArr);
} else {
introArr.push({
id: field._original ? field._original.id : field.key,
title: field.label,
type: field._fieldType || field.type,
value: processArr,
sectionId: field._sectionId || (field._original && field._original.sectionId),
sectionTitle: field._sectionTitle || '',
sectionType: field._sectionType || (field._original && field._original.sectionType),
});
}
continue;
}
2026-06-23 09:55:51 +08:00
if (field._hasOriginalKey) {
// 有key的字段直接作为请求参数
// 图片字段确保为纯URL字符串数组
if (field.type === 'image_multi' && Array.isArray(value)) {
submitData[field.key] = value.map(function (item) {
return typeof item === 'string' ? item : (item && item.url ? item.url : item);
});
} else {
submitData[field.key] = value;
}
2026-06-11 20:48:30 +08:00
} else {
// 没有key的字段放入intro数组
2026-06-23 09:55:51 +08:00
introArr.push({
id: field._original ? field._original.id : field.key,
title: field.label,
type: field._fieldType || field.type,
value: value,
sectionId: field._sectionId || (field._original && field._original.sectionId),
sectionTitle: field._sectionTitle || '',
sectionType: field._sectionType || (field._original && field._original.sectionType),
});
}
2026-04-03 15:49:27 +08:00
}
2026-06-09 17:49:15 +08:00
}
2026-04-03 15:49:27 +08:00
if (introArr.length > 0) {
submitData.intro = JSON.stringify(introArr);
}
2026-06-10 16:23:47 +08:00
// 服务步骤有key时作为独立参数无key时放入intro数组
2026-06-23 09:55:51 +08:00
if (!this.hasDynamicServiceStepField && this.formData.process && this.formData.process.length > 0) {
const processArr = this.formData.process
.filter(item => item.text && item.text.trim())
.map(item => ({ text: item.text.trim() }));
if (processArr.length > 0) {
if (this.processFieldKey) {
submitData[this.processFieldKey] = JSON.stringify(processArr);
} else {
let introList = [];
try {
introList = submitData.intro ? JSON.parse(submitData.intro) : [];
} catch (e) {
introList = [];
}
2026-06-23 09:55:51 +08:00
introList.push({
id: 'service_step',
title: '服务流程',
type: 'service_step',
value: processArr,
});
submitData.intro = JSON.stringify(introList);
2026-06-11 20:48:30 +08:00
}
}
}
return submitData;
},
2026-06-10 16:23:47 +08:00
// 提交表单
submitForm: debounce(function () {
if (this.isAdd) return;
2026-06-10 16:23:47 +08:00
2026-06-23 09:55:51 +08:00
// 先同步服务简介数据到formData
this.syncDetailForm();
2026-06-10 16:23:47 +08:00
2026-06-23 09:55:51 +08:00
if (!this.formData.first_class_id) {
uni.showToast({
title: "请选择服务分类",
icon: "none",
});
return;
}
// 验证动态表单字段
if (!this.validateDynamicForm()) {
return;
}
2026-06-10 16:23:47 +08:00
if (this.artisanType == 2) {
delete this.formData.sj_see;
}
// 提交表单逻辑
delete this.formData.state;
let url = null;
if (!this.formData.id) {
url = "/user/serveradd";
2026-06-09 17:49:15 +08:00
} else {
url = "/user/serverupdate";
2026-04-03 15:49:27 +08:00
}
console.log(this.formData, '-0-0-0-0-0-');
const submitData = this.buildSubmitData();
submitData.publish_user_id = this.formData.publish_user_id;
submitData.second_class_id = this.formData.second_class_id;
submitData.photo = Object.values(this.formData.photo || {});
console.log(submitData, 'submitData');
submitData.publish_user_id = this.postid
submitData.server_kind = 2
submitData.first_class_id = this.formData.first_class_id
request.post(url, submitData).then((res) => {
if (res.state == 1) {
const pages = getCurrentPages();
const prevPage = pages[pages.length - 2]; // 上一个页面
uni.showToast({
title: res.msg,
icon: "success",
duration: 600,
mask: true,
});
this.isAdd = true;
setTimeout(() => {
uni.navigateBack({
delta: 1,
success: () => {
prevPage.$vm.currentTab = 1;
prevPage.$vm.state = 1;
prevPage.$vm.getServicesList(); // 直接调用上个页面的刷新方法
},
});
}, 500);
2026-04-03 15:49:27 +08:00
} else {
uni.showToast({
title: res.msg,
icon: "none",
});
this.isAdd = false;
2026-04-03 15:49:27 +08:00
}
});
}, 500),
2026-06-09 17:49:15 +08:00
},
};
2026-03-24 11:45:13 +08:00
</script>
<style lang="less">
2026-06-09 17:49:15 +08:00
.add-service {
padding: 24rpx 24rpx 0;
box-sizing: border-box;
}
.add-service {
padding: 24rpx 24rpx 0;
box-sizing: border-box;
}
2026-06-09 17:49:15 +08:00
.form-container {
margin-top: 20rpx;
}
2026-06-04 15:00:22 +08:00
.form-container {
margin-top: 20rpx;
}
.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;
}
2026-06-09 17:49:15 +08:00
.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;
}
.price-tip {
width: 100%;
margin-top: 40rpx;
.price-tip {
width: 100%;
margin-top: 40rpx;
.price-tip-card {
padding: 8rpx 12rpx;
background: #FDF0F0;
border-radius: 8rpx;
font-weight: 400;
font-size: 24rpx;
color: #EE4853;
line-height: 33rpx;
}
}
2026-06-09 17:49:15 +08:00
.price-tip-card {
padding: 8rpx 12rpx;
background: #FDF0F0;
border-radius: 8rpx;
2026-04-03 15:49:27 +08:00
font-weight: 400;
font-size: 24rpx;
2026-06-09 17:49:15 +08:00
color: #EE4853;
line-height: 33rpx;
}
}
.shopsale-box {
flex-flow: column;
justify-content: space-between;
align-items: space-between;
}
.shopsale-box {
flex-flow: column;
justify-content: space-between;
align-items: space-between;
}
.shopsale-change {
display: flex;
flex-flow: row nowrap;
justify-content: space-between;
align-items: center;
margin-top: 24rpx;
}
2026-06-09 17:49:15 +08:00
.shopsale-change {
display: flex;
flex-flow: row nowrap;
justify-content: space-between;
align-items: center;
margin-top: 24rpx;
}
.shopsale-option {
display: flex;
flex-flow: row nowrap;
justify-content: center;
align-items: center;
}
.shopsale-option {
display: flex;
flex-flow: row nowrap;
justify-content: center;
align-items: center;
}
2026-06-09 17:49:15 +08:00
.shopsale-option:last-child {
margin-left: 220rpx;
}
2026-06-04 15:00:22 +08:00
.shopsale-option:last-child {
margin-left: 220rpx;
}
.option-text {
padding-left: 16rpx;
}
2026-06-09 17:49:15 +08:00
.option-text {
padding-left: 16rpx;
}
.form-item-up {
background-color: #fff;
padding: 24rpx;
display: flex;
flex-flow: column nowrap;
justify-content: space-between;
align-items: flex-start;
box-sizing: border-box;
border-radius: 10rpx;
margin-bottom: 24rpx;
}
2026-06-04 15:00:22 +08:00
.form-item-up {
background-color: #fff;
padding: 24rpx;
display: flex;
flex-flow: column nowrap;
justify-content: space-between;
align-items: flex-start;
box-sizing: border-box;
border-radius: 10rpx;
margin-bottom: 24rpx;
}
.form-label {
font-size: 28rpx;
color: #333333;
}
2026-06-09 17:49:15 +08:00
.form-label {
font-size: 28rpx;
color: #333333;
}
.form-input {
flex: 1;
height: 40rpx;
font-size: 28rpx;
color: #333333;
align-items: center;
text-align: center;
}
2026-06-04 15:00:22 +08:00
.form-input {
flex: 1;
height: 40rpx;
font-size: 28rpx;
color: #333333;
align-items: center;
text-align: center;
}
.form-right {
display: flex;
justify-content: space-between;
align-items: center;
}
2026-06-09 17:49:15 +08:00
.form-right {
display: flex;
justify-content: space-between;
align-items: center;
}
.form-value {
font-size: 28rpx;
color: #333333;
margin-right: 16rpx;
}
2026-06-12 13:59:52 +08:00
.service-class-item {
justify-content: flex-start;
align-items: flex-start;
}
.service-class-value {
flex: 1;
margin-right: 0;
line-height: 40rpx;
word-break: break-all;
}
2026-06-09 17:49:15 +08:00
.placeholder {
font-weight: 400;
font-size: 28rpx;
color: #999999;
}
2026-06-04 15:00:22 +08:00
.placeholder {
font-weight: 400;
font-size: 28rpx;
color: #999999;
}
.textarea-item {
padding-bottom: 30rpx;
}
2026-06-09 17:49:15 +08:00
.textarea-item {
padding-bottom: 30rpx;
}
.form-textarea {
flex: 1;
height: 198rpx;
font-size: 28rpx;
color: #333333;
line-height: 1.5;
margin-left: 32rpx;
}
2026-06-04 15:00:22 +08:00
.form-textarea {
flex: 1;
height: 198rpx;
font-size: 28rpx;
color: #333333;
line-height: 1.5;
margin-left: 32rpx;
}
.upload-item {
padding-bottom: 30rpx;
}
2026-06-09 17:49:15 +08:00
.upload-item {
padding-bottom: 30rpx;
}
.upload-tip {
font-size: 24rpx;
color: #999999;
margin-left: 10rpx;
}
2026-06-04 15:00:22 +08:00
.upload-tip {
font-size: 24rpx;
color: #999999;
margin-left: 10rpx;
}
.upload-content {
margin-top: 20rpx;
position: relative;
}
2026-06-09 17:49:15 +08:00
.upload-content {
margin-top: 20rpx;
position: relative;
}
.progress-box {
width: 204rpx;
height: 204rpx;
background-color: rgba(0, 0, 0, 0.5);
position: absolute;
top: 0;
left: 0;
right: 0;
z-index: 99;
display: flex;
flex-flow: column nowrap;
justify-content: center;
align-items: center;
}
2026-06-04 15:00:22 +08:00
.progress-box {
width: 204rpx;
height: 204rpx;
background-color: rgba(0, 0, 0, 0.5);
position: absolute;
top: 0;
left: 0;
right: 0;
z-index: 99;
display: flex;
flex-flow: column nowrap;
justify-content: center;
align-items: center;
}
.progress-text {
font-size: 42rpx;
color: #ffffff;
font-weight: 500;
}
.progress-text {
font-size: 42rpx;
color: #ffffff;
font-weight: 500;
}
2026-06-09 17:49:15 +08:00
.progress-lable {
font-size: 28rpx;
color: #ffffff;
}
2026-06-04 15:00:22 +08:00
.progress-lable {
font-size: 28rpx;
color: #ffffff;
}
.progress-box progress {
/* width: 204rpx; */
position: absolute;
left: 0;
right: 0;
bottom: 0;
}
2026-06-09 17:49:15 +08:00
.progress-box progress {
/* width: 204rpx; */
position: absolute;
left: 0;
right: 0;
bottom: 0;
}
.video-preview,
.image-preview,
.upload-btn {
width: 200rpx;
height: 200rpx;
margin-right: 10rpx;
margin-bottom: 20rpx;
position: relative;
}
2026-06-04 15:00:22 +08:00
.video-preview,
.image-preview,
.upload-btn {
width: 200rpx;
height: 200rpx;
margin-right: 10rpx;
margin-bottom: 20rpx;
position: relative;
}
.preview-video,
.preview-image {
width: 200rpx;
height: 200rpx;
border-radius: 8rpx;
}
2026-06-09 17:49:15 +08:00
.preview-video,
.preview-image {
width: 200rpx;
height: 200rpx;
border-radius: 8rpx;
}
.upload-btn {
border: 2rpx dashed #dddddd;
border-radius: 8rpx;
display: flex;
justify-content: center;
align-items: center;
}
.upload-btn {
border: 2rpx dashed #dddddd;
border-radius: 8rpx;
display: flex;
justify-content: center;
align-items: center;
}
.plus-icon {
font-size: 60rpx;
color: #999999;
}
2026-06-09 17:49:15 +08:00
.plus-icon {
font-size: 60rpx;
color: #999999;
}
.delete-btn {
position: absolute;
top: -20rpx;
right: -20rpx;
width: 40rpx;
height: 40rpx;
background-color: rgba(0, 0, 0, 0.5);
border-radius: 50%;
color: #ffffff;
display: flex;
justify-content: center;
align-items: center;
font-size: 32rpx;
}
.delete-btn {
position: absolute;
top: -20rpx;
right: -20rpx;
width: 40rpx;
height: 40rpx;
background-color: rgba(0, 0, 0, 0.5);
border-radius: 50%;
color: #ffffff;
display: flex;
justify-content: center;
align-items: center;
font-size: 32rpx;
}
.bottom-buttons {
/* position: fixed;
2026-06-09 17:49:15 +08:00
.bottom-buttons {
/* position: fixed;
2026-03-24 11:45:13 +08:00
left: 0;
right: 0;
bottom: 0; */
2026-06-09 17:49:15 +08:00
height: 100rpx;
padding: 40rpx 0 64rpx;
box-sizing: border-box;
display: flex;
flex-flow: row nowrap;
justify-content: space-between;
align-items: center;
}
2026-06-23 09:55:51 +08:00
.btn-draft,
.btn-submit {
width: 334rpx;
height: 76rpx;
line-height: 76rpx;
text-align: center;
font-size: 28rpx;
border-radius: 40rpx;
margin: 0 24rpx;
}
2026-06-09 17:49:15 +08:00
.btn-draft,
.btn-submit {
width: 334rpx;
height: 76rpx;
line-height: 76rpx;
text-align: center;
font-size: 28rpx;
border-radius: 40rpx;
margin: 0 24rpx;
}
.btn-draft {
background-color: #ffffff;
color: #e8101e;
border: 2rpx solid #e8101e;
box-sizing: border-box;
}
.btn-submit {
background-color: #e8101e;
color: #ffffff;
width: 705rpx;
height: 76rpx;
}
.form-step {
background-color: #fff;
margin-bottom: 24rpx;
}
.form-step {
background-color: #fff;
margin-bottom: 24rpx;
}
2026-06-09 17:49:15 +08:00
.form-step .form-item {
margin-bottom: 0;
}
2026-06-04 15:00:22 +08:00
.form-step .form-item {
margin-bottom: 0;
}
.step {
display: flex;
flex-flow: row nowrap;
justify-content: space-between;
align-items: center;
padding: 0 24rpx 24rpx;
}
2026-06-09 17:49:15 +08:00
.step {
display: flex;
flex-flow: row nowrap;
justify-content: space-between;
align-items: center;
padding: 0 24rpx 24rpx;
}
.step-inp {
flex: 1;
font-size: 28rpx;
color: #333333;
margin-left: 38rpx;
padding: 0 24rpx;
background-color: rgba(230, 230, 230, 0.5);
}
2026-06-04 15:00:22 +08:00
.step-inp {
flex: 1;
font-size: 28rpx;
color: #333333;
margin-left: 38rpx;
padding: 0 24rpx;
background-color: rgba(230, 230, 230, 0.5);
}
2026-06-09 17:49:15 +08:00
.step-textarea {
font-size: 24rpx;
flex: 1;
height: 70rpx;
border-radius: 6rpx;
margin-left: 38rpx;
padding: 24rpx;
background-color: rgba(230, 230, 230, 0.5);
}
.step-textarea {
font-size: 24rpx;
flex: 1;
height: 70rpx;
border-radius: 6rpx;
margin-left: 38rpx;
padding: 24rpx;
background-color: rgba(230, 230, 230, 0.5);
}
/* 选择器触发按钮样式 */
.picker-trigger {
display: flex;
align-items: center;
justify-content: space-between;
padding: 24rpx 32rpx;
background-color: #ffffff;
border-radius: 12rpx;
margin-bottom: 24rpx;
}
2026-06-09 17:49:15 +08:00
/* 选择器触发按钮样式 */
.picker-trigger {
display: flex;
align-items: center;
justify-content: space-between;
padding: 24rpx 32rpx;
background-color: #ffffff;
border-radius: 12rpx;
margin-bottom: 24rpx;
}
.picker-label {
font-size: 28rpx;
color: #333333;
}
.picker-label {
font-size: 28rpx;
color: #333333;
}
.picker-value {
font-size: 28rpx;
color: #666666;
}
2026-06-09 17:49:15 +08:00
.picker-value {
font-size: 28rpx;
color: #666666;
}
.picker-arrow {
width: 32rpx;
height: 32rpx;
margin-left: 16rpx;
}
.picker-arrow {
width: 32rpx;
height: 32rpx;
margin-left: 16rpx;
}
2026-06-09 17:49:15 +08:00
.delete-icon {
width: 32rpx;
height: 32rpx;
margin-left: 24rpx;
}
2026-06-04 15:00:22 +08:00
.delete-icon {
width: 32rpx;
height: 32rpx;
margin-left: 24rpx;
}
.form-detail {
position: relative;
}
2026-06-09 17:49:15 +08:00
.form-detail {
position: relative;
}
.required::before {
content: "*";
color: #ff0000;
margin-right: 4rpx;
}
2026-06-04 15:00:22 +08:00
.required::before {
content: "*";
color: #ff0000;
margin-right: 4rpx;
}
2026-06-09 17:49:15 +08:00
.step-inp,
.form-input {
height: 60rpx;
}
.step-inp,
.form-input {
height: 60rpx;
}
// 服务简介相关
.detail-card {
background: #ffffff;
border-radius: 20rpx;
padding: 0 24rpx 8rpx;
margin-bottom: 24rpx;
box-sizing: border-box;
overflow: hidden;
}
2026-06-09 17:49:15 +08:00
// 服务简介相关
.detail-card {
background: #ffffff;
border-radius: 20rpx;
padding: 0 24rpx 8rpx;
margin-bottom: 24rpx;
box-sizing: border-box;
overflow: hidden;
}
.detail-title {
padding: 24rpx 0 25rpx;
}
.detail-title {
padding: 24rpx 0 25rpx;
}
2026-06-09 17:49:15 +08:00
.detail-title .form-label {
font-size: 30rpx;
font-weight: 500;
color: #333;
line-height: 40rpx;
}
2026-06-04 15:00:22 +08:00
.detail-title .form-label {
font-size: 30rpx;
font-weight: 500;
color: #333;
line-height: 40rpx;
}
.detail-row {
padding: 0 0 36rpx;
}
2026-06-09 17:49:15 +08:00
.detail-row {
padding: 0 0 36rpx;
}
.detail-row .form-label {
display: block;
margin-bottom: 20rpx;
line-height: 40rpx;
}
2026-06-04 15:00:22 +08:00
.detail-row .form-label {
display: block;
margin-bottom: 20rpx;
line-height: 40rpx;
}
.detail-textarea-wrap {
position: relative;
background: #f7f7f7;
border-radius: 16rpx;
padding: 24rpx 24rpx 52rpx;
box-sizing: border-box;
}
2026-06-09 17:49:15 +08:00
.detail-textarea-wrap {
position: relative;
background: #f7f7f7;
border-radius: 16rpx;
padding: 24rpx 24rpx 52rpx;
box-sizing: border-box;
}
.detail-textarea {
width: 100%;
height: 108rpx;
min-height: 108rpx;
margin: 0;
padding: 0;
background: transparent;
border: 0;
font-size: 28rpx;
line-height: 40rpx;
color: #333333;
box-sizing: border-box;
}
2026-06-04 15:00:22 +08:00
.detail-textarea {
width: 100%;
height: 108rpx;
min-height: 108rpx;
margin: 0;
padding: 0;
background: transparent;
border: 0;
font-size: 28rpx;
line-height: 40rpx;
color: #333333;
box-sizing: border-box;
}
.detail-count {
position: absolute;
right: 24rpx;
bottom: 16rpx;
font-size: 22rpx;
color: #999999;
line-height: 32rpx;
}
2026-06-09 17:49:15 +08:00
.detail-count {
position: absolute;
right: 24rpx;
bottom: 16rpx;
font-size: 22rpx;
color: #999999;
line-height: 32rpx;
}
/* 平台特定样式 */
/* #ifdef H5 */
.form-input,
.form-textarea {
outline: none;
}
2026-06-09 17:49:15 +08:00
.upload-btn {
cursor: pointer;
}
2026-06-09 17:49:15 +08:00
/* #endif */
/* #ifdef APP-PLUS */
.form-input {
height: 90rpx;
}
.permission.transform {
top: calc(var(--status-bar-height) + 88rpx + 30rpx);
opacity: 1;
visibility: visible;
}
/* #endif */
/* #ifdef MP-WEIXIN */
.form-input {
height: 80rpx;
}
2026-06-09 17:49:15 +08:00
.bottom-buttons {
padding-bottom: env(safe-area-inset-bottom);
}
/* #endif */
2026-06-23 09:55:51 +08:00
2026-06-09 17:49:15 +08:00
.tip {
padding: 20rpx 30rpx;
background: #feefec;
font-weight: 400;
font-size: 30rpx;
color: #f31d2f;
line-height: 42rpx;
2026-06-23 09:55:51 +08:00
text-align: center;
2026-06-09 17:49:15 +08:00
font-style: normal;
.tip-icon {
width: 34rpx;
height: 34rpx;
margin-right: 10rpx;
transform: translateY(6rpx);
}
}
/* 淡入淡出动画 */
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.3s;
}
/* 淡入淡出动画 */
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.3s;
}
2026-06-09 17:49:15 +08:00
.fade-enter,
.fade-leave-to {
opacity: 0;
}
2026-06-04 15:00:22 +08:00
.fade-enter,
.fade-leave-to {
opacity: 0;
}
.popup-mask2 {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.5);
// z-index: 1000;
display: flex;
align-items: center;
justify-content: center;
}
2026-06-09 17:49:15 +08:00
.popup-mask2 {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.5);
// z-index: 1000;
display: flex;
align-items: center;
justify-content: center;
}
.popup-content2 {
width: 600rpx;
background-color: #ffffff;
border-radius: 20rpx;
overflow: hidden;
padding: 40rpx 30rpx;
box-sizing: border-box;
}
2026-06-04 15:00:22 +08:00
.popup-content2 {
width: 600rpx;
background-color: #ffffff;
border-radius: 20rpx;
overflow: hidden;
padding: 40rpx 30rpx;
box-sizing: border-box;
}
.popup-title {
font-size: 36rpx;
font-weight: 600;
color: #333333;
text-align: center;
margin-bottom: 20rpx;
}
2026-06-09 17:49:15 +08:00
.popup-title {
font-size: 36rpx;
font-weight: 600;
color: #333333;
text-align: center;
margin-bottom: 20rpx;
}
.popup-message {
font-size: 30rpx;
color: #666666;
text-align: center;
margin-top: 40rpx;
margin-bottom: 30rpx;
line-height: 1.5;
}
2026-06-04 15:00:22 +08:00
.popup-message {
font-size: 30rpx;
color: #666666;
text-align: center;
margin-top: 40rpx;
margin-bottom: 30rpx;
line-height: 1.5;
}
2026-06-09 17:49:15 +08:00
.popup-buttons {
display: flex;
flex-flow: row nowrap;
justify-content: space-between;
padding: 20rpx 0 0 0;
gap: 60rpx;
}
.popup-buttons {
display: flex;
flex-flow: row nowrap;
justify-content: space-between;
padding: 20rpx 0 0 0;
gap: 60rpx;
}
.popup-btn {
flex: 1;
height: 80rpx;
line-height: 80rpx;
text-align: center;
font-size: 26rpx;
border-radius: 40rpx;
}
2026-06-09 17:49:15 +08:00
.popup-btn {
flex: 1;
height: 80rpx;
line-height: 80rpx;
text-align: center;
font-size: 26rpx;
border-radius: 40rpx;
}
.popup-btn-know {
background-color: #e8101e;
color: #ffffff;
}
.popup-btn-notshow {
background-color: #ffffff;
color: #e8101e;
border: 1rpx #e8101e solid;
}
2026-06-11 20:48:30 +08:00
.video-upload {
background-color: #fff;
padding: 24rpx;
margin-bottom: 24rpx;
}
.flex-c {
display: flex;
align-items: center;
}
.flex {
display: flex;
}
</style>