服务发布
This commit is contained in:
parent
3e39ff1140
commit
573aef3210
|
|
@ -15,7 +15,6 @@
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
|
||||||
<script>
|
<script>
|
||||||
// 引入 spark-md5 计算文件哈希
|
// 引入 spark-md5 计算文件哈希
|
||||||
import SparkMD5 from 'spark-md5'
|
import SparkMD5 from 'spark-md5'
|
||||||
|
|
@ -42,7 +41,7 @@ export default {
|
||||||
maxSize: {
|
maxSize: {
|
||||||
type: Number,
|
type: Number,
|
||||||
default: 500
|
default: 500
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
|
@ -53,7 +52,7 @@ export default {
|
||||||
fileMd5: '',
|
fileMd5: '',
|
||||||
cancelToken: null
|
cancelToken: null
|
||||||
};
|
};
|
||||||
}
|
},
|
||||||
methods: {
|
methods: {
|
||||||
// 选择视频文件
|
// 选择视频文件
|
||||||
async chooseVideo() {
|
async chooseVideo() {
|
||||||
|
|
@ -319,7 +318,7 @@ export default {
|
||||||
chunkIndex,
|
chunkIndex,
|
||||||
fileMd5: this.fileMd5,
|
fileMd5: this.fileMd5,
|
||||||
chunkSize: this.chunkSize,
|
chunkSize: this.chunkSize,
|
||||||
totalChunks: Math.ceil(this.file.size / this.chunkSize))
|
totalChunks: Math.ceil(this.file.size / this.chunkSize)
|
||||||
},
|
},
|
||||||
header: {
|
header: {
|
||||||
'Content-Type': 'multipart/form-data'
|
'Content-Type': 'multipart/form-data'
|
||||||
|
|
@ -411,4 +410,4 @@ button {
|
||||||
color: #666;
|
color: #666;
|
||||||
font-size: 24rpx;
|
font-size: 24rpx;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -50,6 +50,7 @@
|
||||||
v-for="(file, index) in videoList"
|
v-for="(file, index) in videoList"
|
||||||
:key="file || index"
|
:key="file || index"
|
||||||
class="preview-item"
|
class="preview-item"
|
||||||
|
@click="previewVideo(file)"
|
||||||
>
|
>
|
||||||
<!-- <video
|
<!-- <video
|
||||||
:src="file"
|
:src="file"
|
||||||
|
|
@ -57,6 +58,11 @@
|
||||||
@click="handlePreview(index)"
|
@click="handlePreview(index)"
|
||||||
/> -->
|
/> -->
|
||||||
<image class="preview-image" :src="getVideoPoster(file)" mode="aspectFill"></image>
|
<image class="preview-image" :src="getVideoPoster(file)" mode="aspectFill"></image>
|
||||||
|
<view class="video-play-mask">
|
||||||
|
<view class="video-play-circle">
|
||||||
|
<view class="video-play-icon"></view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
<image
|
<image
|
||||||
v-if="deletable"
|
v-if="deletable"
|
||||||
class="delete-icon"
|
class="delete-icon"
|
||||||
|
|
@ -81,6 +87,14 @@
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
<view v-if="showVideoPlayer" class="video-player-mask" @click="closeVideoPlayer">
|
||||||
|
<view class="video-player-box" @click.stop>
|
||||||
|
<video :src="previewVideoUrl" class="video-player" controls autoplay show-fullscreen-btn></video>
|
||||||
|
<view class="video-player-close" @click="closeVideoPlayer">
|
||||||
|
<text class="video-player-close-text">×</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
<!-- 提示文字 -->
|
<!-- 提示文字 -->
|
||||||
<!-- <text v-if="tips" class="tips-text">{{ tips }}</text> -->
|
<!-- <text v-if="tips" class="tips-text">{{ tips }}</text> -->
|
||||||
|
|
||||||
|
|
@ -97,12 +111,12 @@ export default {
|
||||||
props: {
|
props: {
|
||||||
// 已上传文件列表
|
// 已上传文件列表
|
||||||
value: {
|
value: {
|
||||||
type: Array,
|
type: [Array, String, Object],
|
||||||
default: () => []
|
default: () => []
|
||||||
},
|
},
|
||||||
// 单个上传文件
|
// 单个上传文件
|
||||||
fileString: {
|
fileString: {
|
||||||
type: String,
|
type: [String, Object, Array],
|
||||||
default: ''
|
default: ''
|
||||||
},
|
},
|
||||||
// 按钮文字
|
// 按钮文字
|
||||||
|
|
@ -173,10 +187,12 @@ export default {
|
||||||
fileList: [],
|
fileList: [],
|
||||||
oneFile: '',
|
oneFile: '',
|
||||||
videoList: [],
|
videoList: [],
|
||||||
loading: false,
|
loading: false,
|
||||||
dragIndex: -1,
|
dragIndex: -1,
|
||||||
isDragging: false,
|
isDragging: false,
|
||||||
suppressPreview: false
|
suppressPreview: false,
|
||||||
|
showVideoPlayer: false,
|
||||||
|
previewVideoUrl: ''
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -190,19 +206,57 @@ export default {
|
||||||
value: {
|
value: {
|
||||||
immediate: true,
|
immediate: true,
|
||||||
handler(newVal) {
|
handler(newVal) {
|
||||||
this.fileList = [...newVal]
|
if (this.accept === 'video/*') {
|
||||||
|
const videoValue = this.normalizeVideoValue(newVal)
|
||||||
|
this.oneFile = videoValue
|
||||||
|
this.videoList = videoValue ? [videoValue] : []
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.fileList = Array.isArray(newVal) ? [...newVal] : []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
fileString: {
|
fileString: {
|
||||||
immediate: true,
|
immediate: true,
|
||||||
handler(newVal) {
|
handler(newVal) {
|
||||||
this.oneFile = newVal
|
const videoValue = this.normalizeVideoValue(newVal)
|
||||||
this.videoList = newVal ? [newVal] : []
|
this.oneFile = videoValue
|
||||||
|
this.videoList = videoValue ? [videoValue] : []
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
|
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 (const item of value) {
|
||||||
|
const url = this.normalizeVideoValue(item)
|
||||||
|
if (url) return url
|
||||||
|
}
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
if (typeof value === 'object') {
|
||||||
|
const candidates = [value.url, value.path, value.filePath, value.tempFilePath, value.src, value.value]
|
||||||
|
for (const item of candidates) {
|
||||||
|
const url = this.normalizeVideoValue(item)
|
||||||
|
if (url) return url
|
||||||
|
}
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
return String(value)
|
||||||
|
},
|
||||||
|
|
||||||
// 显示权限说明弹窗
|
// 显示权限说明弹窗
|
||||||
showPermissionDialog(title, content) {
|
showPermissionDialog(title, content) {
|
||||||
|
|
@ -319,8 +373,9 @@ export default {
|
||||||
},
|
},
|
||||||
// 上传文件到OSS
|
// 上传文件到OSS
|
||||||
async uploadFile(file) {
|
async uploadFile(file) {
|
||||||
|
const uploadFile = await this.prepareUploadFile(file)
|
||||||
// 检查文件大小
|
// 检查文件大小
|
||||||
if (file.size > this.maxSize * 1024 * 1024) {
|
if (uploadFile.size > this.maxSize * 1024 * 1024) {
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
title: `文件大小不能超过${this.maxSize}MB`,
|
title: `文件大小不能超过${this.maxSize}MB`,
|
||||||
icon: 'none'
|
icon: 'none'
|
||||||
|
|
@ -328,7 +383,7 @@ export default {
|
||||||
return Promise.reject(new Error('文件大小超出限制'))
|
return Promise.reject(new Error('文件大小超出限制'))
|
||||||
}
|
}
|
||||||
let date = `${new Date().getTime()}_${Math.floor(Math.random() * 100000)}`
|
let date = `${new Date().getTime()}_${Math.floor(Math.random() * 100000)}`
|
||||||
const fileExt = this.getFileExt(file)
|
const fileExt = this.getFileExt(uploadFile)
|
||||||
|
|
||||||
let artisan = this.type==1 ? 'yh' : this.type==2 ? 'syr' : this.type==3 ? 'sj' : '';
|
let artisan = this.type==1 ? 'yh' : this.type==2 ? 'syr' : this.type==3 ? 'sj' : '';
|
||||||
let name = `${this.userId}_${artisan}_${date}.${fileExt}`
|
let name = `${this.userId}_${artisan}_${date}.${fileExt}`
|
||||||
|
|
@ -337,7 +392,7 @@ export default {
|
||||||
// 1. 获取OSS上传凭证
|
// 1. 获取OSS上传凭证
|
||||||
const ossConfig = await this.getOssConfig()
|
const ossConfig = await this.getOssConfig()
|
||||||
// 2. 上传文件
|
// 2. 上传文件
|
||||||
const fileUrl = await this.uploadToOss(file, ossConfig, name)
|
const fileUrl = await this.uploadToOss(uploadFile, ossConfig, name)
|
||||||
// 3. 添加到文件列表
|
// 3. 添加到文件列表
|
||||||
if(this.accept == 'image/*') {
|
if(this.accept == 'image/*') {
|
||||||
const newFile = [`${fileUrl}`]
|
const newFile = [`${fileUrl}`]
|
||||||
|
|
@ -379,10 +434,90 @@ export default {
|
||||||
|
|
||||||
getFileExt(file) {
|
getFileExt(file) {
|
||||||
const defaultExt = this.accept === 'video/*' ? 'mp4' : 'jpg'
|
const defaultExt = this.accept === 'video/*' ? 'mp4' : 'jpg'
|
||||||
const source = file && (file.name || file.tempFilePath || file.path || file.url || '')
|
const sources = this.accept === 'video/*'
|
||||||
const cleanPath = String(source).split('?')[0].split('#')[0]
|
? [file && file.tempFilePath, file && file.path, file && file.url, file && file.name]
|
||||||
const ext = cleanPath.includes('.') ? cleanPath.split('.').pop().toLowerCase() : ''
|
: [file && file.path, file && file.tempFilePath, file && file.url, file && file.name]
|
||||||
return /^[a-z0-9]+$/.test(ext) ? ext : defaultExt
|
for (const source of sources) {
|
||||||
|
const cleanPath = String(source || '').split('?')[0].split('#')[0]
|
||||||
|
const ext = cleanPath.includes('.') ? cleanPath.split('.').pop().toLowerCase() : ''
|
||||||
|
if (/^[a-z0-9]+$/.test(ext)) return ext
|
||||||
|
}
|
||||||
|
return defaultExt
|
||||||
|
},
|
||||||
|
|
||||||
|
getFilePath(file) {
|
||||||
|
if (!file) return ''
|
||||||
|
return file.tempFilePath || file.path || file.url || file.name || ''
|
||||||
|
},
|
||||||
|
|
||||||
|
getMimeType(file, name) {
|
||||||
|
const ext = this.getFileExt({ tempFilePath: name, path: name, name })
|
||||||
|
const mimeTypes = {
|
||||||
|
jpg: 'image/jpeg',
|
||||||
|
jpeg: 'image/jpeg',
|
||||||
|
png: 'image/png',
|
||||||
|
gif: 'image/gif',
|
||||||
|
webp: 'image/webp',
|
||||||
|
mp4: 'video/mp4',
|
||||||
|
m4v: 'video/x-m4v',
|
||||||
|
mov: 'video/quicktime',
|
||||||
|
qt: 'video/quicktime',
|
||||||
|
webm: 'video/webm',
|
||||||
|
mpeg: 'video/mpeg',
|
||||||
|
mpg: 'video/mpeg',
|
||||||
|
avi: 'video/x-msvideo',
|
||||||
|
'3gp': 'video/3gpp',
|
||||||
|
mkv: 'video/x-matroska',
|
||||||
|
}
|
||||||
|
return mimeTypes[ext] || (file && file.type) || ''
|
||||||
|
},
|
||||||
|
|
||||||
|
buildOssFormData(ossConfig, objectKey, mimeType) {
|
||||||
|
const formData = {
|
||||||
|
key: objectKey,
|
||||||
|
policy: ossConfig.policy,
|
||||||
|
OSSAccessKeyId: ossConfig.ossAccessKeyId,
|
||||||
|
success_action_status: '200',
|
||||||
|
signature: ossConfig.signature,
|
||||||
|
}
|
||||||
|
if (mimeType) {
|
||||||
|
formData['Content-Type'] = mimeType
|
||||||
|
formData['x-oss-meta-content-type'] = mimeType
|
||||||
|
}
|
||||||
|
return formData
|
||||||
|
},
|
||||||
|
|
||||||
|
async prepareUploadFile(file) {
|
||||||
|
if (this.accept !== 'video/*') return file
|
||||||
|
const filePath = this.getFilePath(file)
|
||||||
|
const fileExt = this.getFileExt(file)
|
||||||
|
if (!filePath || fileExt === 'mp4' || typeof uni.compressVideo !== 'function') {
|
||||||
|
return file
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const res = await uni.compressVideo({
|
||||||
|
src: filePath,
|
||||||
|
quality: 'medium'
|
||||||
|
})
|
||||||
|
const tempFilePath = res.tempFilePath || res.path
|
||||||
|
if (!tempFilePath) return file
|
||||||
|
return {
|
||||||
|
...file,
|
||||||
|
tempFilePath,
|
||||||
|
path: tempFilePath,
|
||||||
|
size: res.size || file.size,
|
||||||
|
name: this.replaceFileExt(file.name, 'mp4'),
|
||||||
|
type: 'video/mp4'
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('视频压缩失败,使用原文件上传:', error)
|
||||||
|
return file
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
replaceFileExt(name, ext) {
|
||||||
|
if (!name) return ''
|
||||||
|
return String(name).replace(/\.[^.]+$/, `.${ext}`)
|
||||||
},
|
},
|
||||||
|
|
||||||
buildFileUrl(ossConfig, name) {
|
buildFileUrl(ossConfig, name) {
|
||||||
|
|
@ -403,6 +538,21 @@ export default {
|
||||||
},
|
},
|
||||||
|
|
||||||
chooseVideoFiles(sourceType, count) {
|
chooseVideoFiles(sourceType, count) {
|
||||||
|
if (count <= 1 && typeof uni.chooseVideo === 'function') {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
uni.chooseVideo({
|
||||||
|
sourceType: [sourceType],
|
||||||
|
compressed: true,
|
||||||
|
maxDuration: 60,
|
||||||
|
success: (res) => resolve([{
|
||||||
|
...res,
|
||||||
|
tempFilePath: res.tempFilePath || res.path,
|
||||||
|
path: res.tempFilePath || res.path,
|
||||||
|
}]),
|
||||||
|
fail: reject,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
if (uni.chooseMedia) {
|
if (uni.chooseMedia) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
uni.chooseMedia({
|
uni.chooseMedia({
|
||||||
|
|
@ -438,14 +588,20 @@ export default {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const objectKey = this.buildObjectKey(ossConfig, name)
|
const objectKey = this.buildObjectKey(ossConfig, name)
|
||||||
const fileUrl = this.buildFileUrl(ossConfig, name)
|
const fileUrl = this.buildFileUrl(ossConfig, name)
|
||||||
// #ifdef H5
|
const mimeType = this.getMimeType(file, name)
|
||||||
const formData = new FormData()
|
let formData;
|
||||||
|
// #ifdef H5
|
||||||
|
formData = new FormData()
|
||||||
formData.append('name',name);
|
formData.append('name',name);
|
||||||
formData.append('policy', ossConfig.policy);
|
formData.append('policy', ossConfig.policy);
|
||||||
formData.append('OSSAccessKeyId', ossConfig.ossAccessKeyId);
|
formData.append('OSSAccessKeyId', ossConfig.ossAccessKeyId);
|
||||||
formData.append('success_action_status', '200');
|
formData.append('success_action_status', '200');
|
||||||
formData.append('signature', ossConfig.signature);
|
formData.append('signature', ossConfig.signature);
|
||||||
formData.append('key', objectKey);
|
formData.append('key', objectKey);
|
||||||
|
if (mimeType) {
|
||||||
|
formData.append('Content-Type', mimeType);
|
||||||
|
formData.append('x-oss-meta-content-type', mimeType);
|
||||||
|
}
|
||||||
// file必须为最后一个表单域,除file以外的其他表单域无顺序要求。
|
// file必须为最后一个表单域,除file以外的其他表单域无顺序要求。
|
||||||
formData.append('file', file.file || file.tempFile || file);
|
formData.append('file', file.file || file.tempFile || file);
|
||||||
fetch(ossConfig.host, { method: 'POST', body: formData},).then((res) => {
|
fetch(ossConfig.host, { method: 'POST', body: formData},).then((res) => {
|
||||||
|
|
@ -459,24 +615,18 @@ export default {
|
||||||
})
|
})
|
||||||
// #endif
|
// #endif
|
||||||
|
|
||||||
// #ifdef APP-PLUS || MP-WEIXIN
|
// #ifdef APP-PLUS || MP-WEIXIN
|
||||||
let formData = {};
|
formData = this.buildOssFormData(ossConfig, objectKey, mimeType);
|
||||||
formData.name = name;
|
formData.name = name;
|
||||||
formData.policy = ossConfig.policy;
|
|
||||||
formData.OSSAccessKeyId = ossConfig.ossAccessKeyId;
|
|
||||||
formData.success_action_status = '200';
|
|
||||||
formData.signature = ossConfig.signature;
|
|
||||||
formData.key = objectKey;
|
|
||||||
formData.file = file;
|
|
||||||
let path;
|
let path;
|
||||||
if(this.accept == 'image/*') {
|
if(this.accept == 'image/*') {
|
||||||
path = file.path
|
path = this.getFilePath(file)
|
||||||
}else if(this.accept == 'video/*') {
|
}else if(this.accept == 'video/*') {
|
||||||
// #ifdef H5
|
// #ifdef H5
|
||||||
path = file.name
|
path = this.getFilePath(file)
|
||||||
// #endif
|
// #endif
|
||||||
// #ifdef APP-PLUS || MP-WEIXIN
|
// #ifdef APP-PLUS || MP-WEIXIN
|
||||||
path = file.tempFilePath
|
path = this.getFilePath(file)
|
||||||
// #endif
|
// #endif
|
||||||
}
|
}
|
||||||
// 使用 uni.uploadFile 上传文件(支持多平台)
|
// 使用 uni.uploadFile 上传文件(支持多平台)
|
||||||
|
|
@ -484,13 +634,7 @@ export default {
|
||||||
url: ossConfig.host,
|
url: ossConfig.host,
|
||||||
filePath: path, // 手机端文件路径
|
filePath: path, // 手机端文件路径
|
||||||
name: 'file',
|
name: 'file',
|
||||||
formData: {
|
formData,
|
||||||
key: objectKey, // 文件名
|
|
||||||
policy: ossConfig.policy, // 后台获取超时时间
|
|
||||||
OSSAccessKeyId: ossConfig.ossAccessKeyId, // 后台获取临时ID
|
|
||||||
success_action_status: 200, // 让服务端返回200,不然,默认会返回204
|
|
||||||
signature: ossConfig.signature // 后台获取签名
|
|
||||||
},
|
|
||||||
success: (res) => {
|
success: (res) => {
|
||||||
const statusCode = Number(res.statusCode)
|
const statusCode = Number(res.statusCode)
|
||||||
if (statusCode >= 200 && statusCode < 300) {
|
if (statusCode >= 200 && statusCode < 300) {
|
||||||
|
|
@ -517,6 +661,17 @@ export default {
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
|
previewVideo(url) {
|
||||||
|
if (!url) return
|
||||||
|
this.previewVideoUrl = url
|
||||||
|
this.showVideoPlayer = true
|
||||||
|
},
|
||||||
|
|
||||||
|
closeVideoPlayer() {
|
||||||
|
this.showVideoPlayer = false
|
||||||
|
this.previewVideoUrl = ''
|
||||||
|
},
|
||||||
|
|
||||||
startDrag(index) {
|
startDrag(index) {
|
||||||
if (!this.sortable || this.accept !== 'image/*' || this.fileList.length < 2) return
|
if (!this.sortable || this.accept !== 'image/*' || this.fileList.length < 2) return
|
||||||
this.dragIndex = index
|
this.dragIndex = index
|
||||||
|
|
@ -662,6 +817,38 @@ export default {
|
||||||
border-radius: 8rpx;
|
border-radius: 8rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.video-play-mask {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
width: 200rpx;
|
||||||
|
height: 200rpx;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
background-color: rgba(0, 0, 0, 0.18);
|
||||||
|
border-radius: 8rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-play-circle {
|
||||||
|
width: 60rpx;
|
||||||
|
height: 60rpx;
|
||||||
|
border-radius: 50%;
|
||||||
|
background-color: rgba(0, 0, 0, 0.55);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-play-icon {
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
border-top: 14rpx solid transparent;
|
||||||
|
border-bottom: 14rpx solid transparent;
|
||||||
|
border-left: 22rpx solid #fff;
|
||||||
|
margin-left: 6rpx;
|
||||||
|
}
|
||||||
|
|
||||||
.delete-icon {
|
.delete-icon {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: -10rpx;
|
top: -10rpx;
|
||||||
|
|
@ -670,4 +857,50 @@ export default {
|
||||||
height: 42rpx;
|
height: 42rpx;
|
||||||
z-index: 5;
|
z-index: 5;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.video-player-mask {
|
||||||
|
position: fixed;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
z-index: 9999;
|
||||||
|
background-color: rgba(0, 0, 0, 0.75);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-player-box {
|
||||||
|
position: relative;
|
||||||
|
width: 690rpx;
|
||||||
|
background-color: #000;
|
||||||
|
border-radius: 8rpx;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-player {
|
||||||
|
width: 690rpx;
|
||||||
|
height: 388rpx;
|
||||||
|
background-color: #000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-player-close {
|
||||||
|
position: absolute;
|
||||||
|
top: 12rpx;
|
||||||
|
right: 12rpx;
|
||||||
|
width: 52rpx;
|
||||||
|
height: 52rpx;
|
||||||
|
border-radius: 50%;
|
||||||
|
background-color: rgba(0, 0, 0, 0.55);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-player-close-text {
|
||||||
|
color: #fff;
|
||||||
|
font-size: 42rpx;
|
||||||
|
line-height: 52rpx;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
<view class="form-checkbox">
|
<view class="form-checkbox">
|
||||||
<view v-for="(item, index) in options" :key="index" class="checkbox-item" @click="onToggle(item)">
|
<view v-for="(item, index) in options" :key="index" class="checkbox-item" @click="onToggle(item)">
|
||||||
<view class="checkbox-dot" :class="{ active: isChecked(item) }"></view>
|
<view class="checkbox-dot" :class="{ active: isChecked(item) }"></view>
|
||||||
<text class="checkbox-label">{{ item }}</text>
|
<text class="checkbox-label">{{ getOptionLabel(item) }}</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
|
|
@ -16,12 +16,37 @@ export default {
|
||||||
maxCount: { type: Number, default: 0 }
|
maxCount: { type: Number, default: 0 }
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
isChecked: function (val) {
|
getOptionValue: function (item) {
|
||||||
return this.value && this.value.indexOf(val) !== -1;
|
if (item && typeof item === 'object') {
|
||||||
|
if (item.value !== undefined) return item.value;
|
||||||
|
if (item.id !== undefined) return item.id;
|
||||||
|
if (item.key !== undefined) return item.key;
|
||||||
|
if (item.code !== undefined) return item.code;
|
||||||
|
return item.label;
|
||||||
|
}
|
||||||
|
return item;
|
||||||
},
|
},
|
||||||
onToggle: function (val) {
|
getOptionLabel: function (item) {
|
||||||
|
if (item && typeof item === 'object') {
|
||||||
|
var label = item.label || item.title || item.name || item.text;
|
||||||
|
if (label !== undefined && label !== null) return label;
|
||||||
|
return String(this.getOptionValue(item));
|
||||||
|
}
|
||||||
|
return item;
|
||||||
|
},
|
||||||
|
findValueIndex: function (list, value) {
|
||||||
|
for (var i = 0; i < list.length; i++) {
|
||||||
|
if (list[i] == value) return i;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
},
|
||||||
|
isChecked: function (item) {
|
||||||
|
return this.findValueIndex(this.value || [], this.getOptionValue(item)) !== -1;
|
||||||
|
},
|
||||||
|
onToggle: function (item) {
|
||||||
|
var val = this.getOptionValue(item);
|
||||||
var newValue = (this.value || []).slice();
|
var newValue = (this.value || []).slice();
|
||||||
var index = newValue.indexOf(val);
|
var index = this.findValueIndex(newValue, val);
|
||||||
if (index === -1) {
|
if (index === -1) {
|
||||||
if (this.maxCount > 0 && newValue.length >= this.maxCount) {
|
if (this.maxCount > 0 && newValue.length >= this.maxCount) {
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<ali-oss-uploader v-model="currentValue" :max-count="maxCount" :max-size="maxSize" accept="image/*" :userId="userId"
|
<ali-oss-uploader v-model="currentValue" :max-count="maxCount" :max-size="maxSize" accept="image/*" :userId="computedUserId"
|
||||||
:type="ossType" width="100%" :sortable="true" @input="onInput" @change="onChange" />
|
:type="computedOssType" width="100%" :sortable="true" @input="onInput" @change="onChange" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|
@ -9,25 +9,29 @@ import AliOssUploader from '../ali-oss-uploader/ali-oss-uploader.vue'
|
||||||
export default {
|
export default {
|
||||||
name: 'FormItemImage',
|
name: 'FormItemImage',
|
||||||
components: { AliOssUploader },
|
components: { AliOssUploader },
|
||||||
props: {
|
props: {
|
||||||
value: { type: Array, default: function () { return []; } },
|
value: { type: Array, default: function () { return []; } },
|
||||||
maxCount: { type: Number, default: 9 },
|
maxCount: { type: Number, default: 9 },
|
||||||
maxSize: { type: Number, default: 5 }
|
maxSize: { type: Number, default: 5 },
|
||||||
},
|
userId: { type: [Number, String], default: null },
|
||||||
|
ossType: { type: [Number, String], default: null }
|
||||||
|
},
|
||||||
data: function () {
|
data: function () {
|
||||||
return {
|
return {
|
||||||
currentValue: []
|
currentValue: []
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
userId: function () {
|
computedUserId: function () {
|
||||||
return this.$store && this.$store.state && this.$store.state.sjInfo
|
if (this.userId) return this.userId;
|
||||||
? this.$store.state.sjInfo.id : null;
|
return this.$store && this.$store.state && this.$store.state.sjInfo
|
||||||
},
|
? this.$store.state.sjInfo.id : null;
|
||||||
ossType: function () {
|
},
|
||||||
var artisanType = getApp().globalData ? getApp().globalData.artisanType : 0;
|
computedOssType: function () {
|
||||||
return (artisanType || 0) + 1;
|
if (this.ossType) return this.ossType;
|
||||||
}
|
var artisanType = getApp().globalData ? getApp().globalData.artisanType : 0;
|
||||||
|
return (artisanType || 0) + 1;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
value: {
|
value: {
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
<template>
|
<template>
|
||||||
<view class="form-radio">
|
<view class="form-radio">
|
||||||
<view v-for="(item, index) in options" :key="index" class="radio-item" @click="onSelect(item)">
|
<view v-for="(item, index) in options" :key="index" class="radio-item" @click="onSelect(item)">
|
||||||
<view class="radio-dot" :class="{ active: value === item }"></view>
|
<view class="radio-dot" :class="{ active: isActive(item) }"></view>
|
||||||
<text class="radio-label">{{ item }}</text>
|
<text class="radio-label">{{ getOptionLabel(item) }}</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
|
|
@ -15,8 +15,29 @@ export default {
|
||||||
options: { type: Array, default: function () { return []; } }
|
options: { type: Array, default: function () { return []; } }
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
onSelect: function (val) {
|
getOptionValue: function (item) {
|
||||||
this.$emit('input', val);
|
if (item && typeof item === 'object') {
|
||||||
|
if (item.value !== undefined) return item.value;
|
||||||
|
if (item.id !== undefined) return item.id;
|
||||||
|
if (item.key !== undefined) return item.key;
|
||||||
|
if (item.code !== undefined) return item.code;
|
||||||
|
return item.label;
|
||||||
|
}
|
||||||
|
return item;
|
||||||
|
},
|
||||||
|
getOptionLabel: function (item) {
|
||||||
|
if (item && typeof item === 'object') {
|
||||||
|
var label = item.label || item.title || item.name || item.text;
|
||||||
|
if (label !== undefined && label !== null) return label;
|
||||||
|
return String(this.getOptionValue(item));
|
||||||
|
}
|
||||||
|
return item;
|
||||||
|
},
|
||||||
|
isActive: function (item) {
|
||||||
|
return this.value == this.getOptionValue(item);
|
||||||
|
},
|
||||||
|
onSelect: function (item) {
|
||||||
|
this.$emit('input', this.getOptionValue(item));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
<template>
|
<template>
|
||||||
<picker :range="options" @change="onChange">
|
<picker :range="displayOptions" @change="onChange">
|
||||||
<view class="form-select">
|
<view class="form-select">
|
||||||
<text :class="['select-text', value ? '' : 'placeholder']">
|
<text :class="['select-text', hasValue ? '' : 'placeholder']">
|
||||||
{{ value || placeholder }}
|
{{ hasValue ? selectedLabel : placeholder }}
|
||||||
</text>
|
</text>
|
||||||
<image src="https://mrrplus.oss-cn-beijing.aliyuncs.com/wxstaic/images/arrow_right.png" class="arrow-right"
|
<image src="https://mrrplus.oss-cn-beijing.aliyuncs.com/wxstaic/images/arrow_right.png" class="arrow-right"
|
||||||
mode="aspectFit" />
|
mode="aspectFit" />
|
||||||
|
|
@ -18,12 +18,48 @@ export default {
|
||||||
options: { type: Array, default: function () { return []; } },
|
options: { type: Array, default: function () { return []; } },
|
||||||
placeholder: { type: String, default: '请选择' }
|
placeholder: { type: String, default: '请选择' }
|
||||||
},
|
},
|
||||||
|
computed: {
|
||||||
|
displayOptions: function () {
|
||||||
|
var self = this;
|
||||||
|
return this.options.map(function (item) {
|
||||||
|
return self.getOptionLabel(item);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
hasValue: function () {
|
||||||
|
return this.value !== undefined && this.value !== null && this.value !== '';
|
||||||
|
},
|
||||||
|
selectedLabel: function () {
|
||||||
|
var self = this;
|
||||||
|
var selected = this.options.find(function (item) {
|
||||||
|
return self.getOptionValue(item) == self.value;
|
||||||
|
});
|
||||||
|
return selected !== undefined ? this.getOptionLabel(selected) : this.value;
|
||||||
|
}
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
getOptionValue: function (item) {
|
||||||
|
if (item && typeof item === 'object') {
|
||||||
|
if (item.value !== undefined) return item.value;
|
||||||
|
if (item.id !== undefined) return item.id;
|
||||||
|
if (item.key !== undefined) return item.key;
|
||||||
|
if (item.code !== undefined) return item.code;
|
||||||
|
return item.label;
|
||||||
|
}
|
||||||
|
return item;
|
||||||
|
},
|
||||||
|
getOptionLabel: function (item) {
|
||||||
|
if (item && typeof item === 'object') {
|
||||||
|
var label = item.label || item.title || item.name || item.text;
|
||||||
|
if (label !== undefined && label !== null) return label;
|
||||||
|
return String(this.getOptionValue(item));
|
||||||
|
}
|
||||||
|
return item;
|
||||||
|
},
|
||||||
onChange: function (e) {
|
onChange: function (e) {
|
||||||
var index = e.detail.value;
|
var index = e.detail.value;
|
||||||
var selected = this.options[index];
|
var selected = this.options[index];
|
||||||
if (selected !== undefined) {
|
if (selected !== undefined) {
|
||||||
this.$emit('input', selected);
|
this.$emit('input', this.getOptionValue(selected));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,16 +20,47 @@
|
||||||
<text class="price-tip-card">{{ group.field.tip }}</text>
|
<text class="price-tip-card">{{ group.field.tip }}</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
<view v-else-if="group.field.type === 'textarea'" class="detail-row">
|
||||||
|
<text :class="['form-label', group.field.is_required === true ? 'required' : '']">{{ group.field.label
|
||||||
|
}}</text>
|
||||||
|
<view class="detail-textarea-wrap">
|
||||||
|
<textarea class="detail-textarea" :value="formData[group.field.key]" :placeholder="group.field.placeholder"
|
||||||
|
placeholder-class="placeholder" maxlength="200" @input="onInput(group.field.key, $event)" />
|
||||||
|
<text class="detail-count">{{ (formData[group.field.key] || "").length }}/200</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view v-else-if="group.field.type === 'select'" class="form-item">
|
||||||
|
<text :class="['form-label', group.field.is_required === true ? 'required' : '']">{{ group.field.label
|
||||||
|
}}</text>
|
||||||
|
<form-item-select :value="formData[group.field.key]" :options="group.field.options || []"
|
||||||
|
:placeholder="group.field.placeholder" @input="onInput(group.field.key, $event)" />
|
||||||
|
</view>
|
||||||
|
<view v-else-if="group.field.type === 'single_select'" class="form-item" style="flex-wrap: wrap;">
|
||||||
|
<text :class="['form-label', group.field.is_required === true ? 'required' : '']">{{ group.field.label
|
||||||
|
}}</text>
|
||||||
|
<view class="select-options">
|
||||||
|
<form-item-radio :value="formData[group.field.key]" :options="group.field.options || []"
|
||||||
|
@input="onInput(group.field.key, $event)" />
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view v-else-if="group.field.type === 'multi_select'" class="form-item" style="flex-wrap: wrap;">
|
||||||
|
<text :class="['form-label', group.field.is_required === true ? 'required' : '']">{{ group.field.label
|
||||||
|
}}</text>
|
||||||
|
<view class="select-options">
|
||||||
|
<form-item-checkbox :value="formData[group.field.key]" :options="group.field.options || []"
|
||||||
|
:maxCount="group.field.maxCount || 0" @input="onInput(group.field.key, $event)" />
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
<view v-else-if="group.field.type === 'image_multi'" class="form-item-up upload-item">
|
<view v-else-if="group.field.type === 'image_multi'" class="form-item-up upload-item">
|
||||||
<view>
|
<view>
|
||||||
<text :class="['form-label', group.field.is_required === true ? 'required' : '']">{{ group.field.label
|
<text :class="['form-label', group.field.is_required === true ? 'required' : '']">{{ group.field.label
|
||||||
}}</text>
|
}}</text>
|
||||||
<text class="upload-tip">(仅可上传{{ group.field.maxCount || 9 }}个图片)</text>
|
<text class="upload-tip">(仅可上传{{ group.field.maxCount || 9 }}个图片)</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="upload-content">
|
<view class="upload-content">
|
||||||
<form-item-image :value="formData[group.field.key]" :maxCount="group.field.maxCount || 9"
|
<form-item-image :value="formData[group.field.key]" :maxCount="group.field.maxCount || 9"
|
||||||
@input="onInput(group.field.key, $event)" />
|
:userId="uploadUserId" @input="onInput(group.field.key, $event)" />
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<view v-else-if="group.field.type === 'video'" class="form-item-up upload-item">
|
<view v-else-if="group.field.type === 'video'" class="form-item-up upload-item">
|
||||||
<view>
|
<view>
|
||||||
|
|
@ -107,10 +138,10 @@
|
||||||
<text :class="['form-label', field.is_required === true ? 'required' : '']">{{ field.label }}</text>
|
<text :class="['form-label', field.is_required === true ? 'required' : '']">{{ field.label }}</text>
|
||||||
<text class="upload-tip">(仅可上传{{ field.maxCount || 9 }}个图片)</text>
|
<text class="upload-tip">(仅可上传{{ field.maxCount || 9 }}个图片)</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="upload-content">
|
<view class="upload-content">
|
||||||
<form-item-image :value="formData[field.key]" :maxCount="field.maxCount || 9"
|
<form-item-image :value="formData[field.key]" :maxCount="field.maxCount || 9"
|
||||||
@input="onInput(field.key, $event)" />
|
:userId="uploadUserId" @input="onInput(field.key, $event)" />
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<view v-else-if="field.type === 'video'" class="form-item-up upload-item no-bg">
|
<view v-else-if="field.type === 'video'" class="form-item-up upload-item no-bg">
|
||||||
<view>
|
<view>
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,6 @@
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
<script src="https://gosspublic.alicdn.com/aliyun-oss-sdk-6.18.0.min.js"></script>
|
|
||||||
<script>
|
<script>
|
||||||
import request from '../../utils/request';
|
import request from '../../utils/request';
|
||||||
export default {
|
export default {
|
||||||
|
|
@ -235,4 +234,4 @@ import request from '../../utils/request';
|
||||||
font-size: 32rpx;
|
font-size: 32rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -200,12 +200,13 @@ import sparkMD5 from 'spark-md5'; // 引入MD5计算库
|
||||||
const loadNext = () => {
|
const loadNext = () => {
|
||||||
const start = currentChunk * chunkSize;
|
const start = currentChunk * chunkSize;
|
||||||
const end = start + chunkSize >= file.size ? file.size : start + chunkSize;
|
const end = start + chunkSize >= file.size ? file.size : start + chunkSize;
|
||||||
// #ifdef H5
|
let reader;
|
||||||
const reader = new FileReader();
|
// #ifdef H5
|
||||||
// #endif
|
reader = new FileReader();
|
||||||
// #ifndef H5
|
// #endif
|
||||||
const reader = uni.getfilesystem;
|
// #ifndef H5
|
||||||
// #endif
|
reader = uni.getfilesystem;
|
||||||
|
// #endif
|
||||||
reader.onload = (e) => {
|
reader.onload = (e) => {
|
||||||
spark.append(e.target.result);
|
spark.append(e.target.result);
|
||||||
currentChunk++;
|
currentChunk++;
|
||||||
|
|
@ -226,12 +227,13 @@ import sparkMD5 from 'spark-md5'; // 引入MD5计算库
|
||||||
try {
|
try {
|
||||||
// 获取ArrayBuffer
|
// 获取ArrayBuffer
|
||||||
const arrayBuffer = await new Promise((resolve) => {
|
const arrayBuffer = await new Promise((resolve) => {
|
||||||
// #ifdef H5
|
let reader;
|
||||||
const reader = new FileReader();
|
// #ifdef H5
|
||||||
// #endif
|
reader = new FileReader();
|
||||||
// #ifndef H5
|
// #endif
|
||||||
const reader = uni.getfilesystem;
|
// #ifndef H5
|
||||||
// #endif
|
reader = uni.getfilesystem;
|
||||||
|
// #endif
|
||||||
reader.onload = () => resolve(reader.result);
|
reader.onload = () => resolve(reader.result);
|
||||||
reader.readAsArrayBuffer(chunk);
|
reader.readAsArrayBuffer(chunk);
|
||||||
});
|
});
|
||||||
|
|
@ -418,4 +420,4 @@ import sparkMD5 from 'spark-md5'; // 引入MD5计算库
|
||||||
font-size: 32rpx;
|
font-size: 32rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -270,14 +270,50 @@ export default {
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
viewEdit(item) {
|
viewEdit(item) {
|
||||||
uni.navigateTo({
|
let url = `/pages/shop/add-service?id=${item.id}&state=${item.state}`;
|
||||||
url: `/pages/shop/add-service?id=${item.id}&state=${item.state}&first_class_id=${item.first_class_id}`,
|
if (item.first_class_id) {
|
||||||
});
|
url += `&first_class_id=${item.first_class_id}`;
|
||||||
|
}
|
||||||
|
if (item.second_class_id) {
|
||||||
|
url += `&second_class_id=${item.second_class_id}`;
|
||||||
|
}
|
||||||
|
uni.navigateTo({ url });
|
||||||
|
|
||||||
},
|
},
|
||||||
addService() {
|
async getCurrentSjId() {
|
||||||
const userId = uni.getStorageSync('userId') || '';
|
const sjInfo = this.$store && this.$store.state && this.$store.state.sjInfo;
|
||||||
request.post("/sj/firstclass", { id: userId }).then((res) => {
|
if (sjInfo && sjInfo.id) {
|
||||||
|
return sjInfo.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
const storageSjId = uni.getStorageSync("sjId");
|
||||||
|
if (storageSjId) {
|
||||||
|
return storageSjId;
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await request.post("/sj/user/getUser");
|
||||||
|
if (res && res.data && res.data.id) {
|
||||||
|
if (this.$store && this.$store.commit) {
|
||||||
|
this.$store.commit("setSjInfo", res.data);
|
||||||
|
}
|
||||||
|
uni.setStorageSync("sjId", res.data.id);
|
||||||
|
return res.data.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
return "";
|
||||||
|
},
|
||||||
|
async addService() {
|
||||||
|
try {
|
||||||
|
const sjId = await this.getCurrentSjId();
|
||||||
|
if (!sjId) {
|
||||||
|
uni.showToast({
|
||||||
|
title: "未获取到商家信息",
|
||||||
|
icon: "none",
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await request.post("/sj/firstclass", { id: sjId });
|
||||||
if (res.data && res.data.length > 0) {
|
if (res.data && res.data.length > 0) {
|
||||||
this.firstClassList = res.data;
|
this.firstClassList = res.data;
|
||||||
this.selectedFirstIndex = 0;
|
this.selectedFirstIndex = 0;
|
||||||
|
|
@ -291,7 +327,13 @@ export default {
|
||||||
icon: "none",
|
icon: "none",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
} catch (error) {
|
||||||
|
console.error("获取商家服务分类失败:", error);
|
||||||
|
uni.showToast({
|
||||||
|
title: "获取分类失败",
|
||||||
|
icon: "none",
|
||||||
|
});
|
||||||
|
}
|
||||||
},
|
},
|
||||||
loadSecondClass(firstId) {
|
loadSecondClass(firstId) {
|
||||||
request.post("/user/secondclass", {
|
request.post("/user/secondclass", {
|
||||||
|
|
@ -979,4 +1021,4 @@ export default {
|
||||||
color: #ffffff;
|
color: #ffffff;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -285,13 +285,14 @@ export default {
|
||||||
sourceType: ['album', 'camera'],
|
sourceType: ['album', 'camera'],
|
||||||
compressed: false
|
compressed: false
|
||||||
})
|
})
|
||||||
// #ifdef H5
|
let selectedFile;
|
||||||
const file = res.tempFile;
|
// #ifdef H5
|
||||||
// #endif
|
selectedFile = res.tempFile;
|
||||||
// #ifdef APP-PLUS || MP-WEIXIN
|
// #endif
|
||||||
const file = res;
|
// #ifdef APP-PLUS || MP-WEIXIN
|
||||||
// #endif
|
selectedFile = res;
|
||||||
await this.uploadFile(file)
|
// #endif
|
||||||
|
await this.uploadFile(selectedFile)
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('上传出错:', error)
|
console.error('上传出错:', error)
|
||||||
|
|
@ -369,8 +370,9 @@ export default {
|
||||||
// 上传到OSS
|
// 上传到OSS
|
||||||
uploadToOss(file, ossConfig, name) {
|
uploadToOss(file, ossConfig, name) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
// #ifdef H5
|
let formData;
|
||||||
const formData = new FormData()
|
// #ifdef H5
|
||||||
|
formData = new FormData()
|
||||||
formData.append('name',name);
|
formData.append('name',name);
|
||||||
formData.append('policy', ossConfig.policy);
|
formData.append('policy', ossConfig.policy);
|
||||||
formData.append('OSSAccessKeyId', ossConfig.ossAccessKeyId);
|
formData.append('OSSAccessKeyId', ossConfig.ossAccessKeyId);
|
||||||
|
|
@ -386,15 +388,15 @@ export default {
|
||||||
})
|
})
|
||||||
// #endif
|
// #endif
|
||||||
|
|
||||||
// #ifdef APP-PLUS || MP-WEIXIN
|
// #ifdef APP-PLUS || MP-WEIXIN
|
||||||
let formData = {};
|
formData = {};
|
||||||
formData.name = name;
|
formData.name = name;
|
||||||
formData.policy = ossConfig.policy;
|
formData.policy = ossConfig.policy;
|
||||||
formData.OSSAccessKeyId = ossConfig.ossAccessKeyId;
|
formData.OSSAccessKeyId = ossConfig.ossAccessKeyId;
|
||||||
formData.success_action_status = '200';
|
formData.success_action_status = '200';
|
||||||
formData.signature = ossConfig.signature;
|
formData.signature = ossConfig.signature;
|
||||||
formData.key = ossConfig.dir + name;
|
formData.key = ossConfig.dir + name;
|
||||||
formData.file = file;
|
formData.file = file;
|
||||||
let path;
|
let path;
|
||||||
if(this.accept == 'image/*') {
|
if(this.accept == 'image/*') {
|
||||||
path = file.path
|
path = file.path
|
||||||
|
|
@ -540,4 +542,4 @@ export default {
|
||||||
font-size: 32rpx;
|
font-size: 32rpx;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -146,9 +146,6 @@
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
|
|
||||||
</view>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|
@ -827,4 +824,4 @@ page {
|
||||||
padding-top: calc(var(--status-bar-height) + env(safe-area-inset-top));
|
padding-top: calc(var(--status-bar-height) + env(safe-area-inset-top));
|
||||||
}
|
}
|
||||||
/* #endif */
|
/* #endif */
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -67,12 +67,10 @@
|
||||||
|
|
||||||
<view class="form-upimg">
|
<view class="form-upimg">
|
||||||
<view class="upload-box" @click="chooseImage('idcard_negative')">
|
<view class="upload-box" @click="chooseImage('idcard_negative')">
|
||||||
</image>
|
|
||||||
<view class="upload-camera img2">
|
<view class="upload-camera img2">
|
||||||
<image v-if="formData.idcard_negative" :src="formData.idcard_negative" mode="aspectFill"
|
<image v-if="formData.idcard_negative" :src="formData.idcard_negative" mode="aspectFill"
|
||||||
class="preview-image">
|
class="preview-image"></image>
|
||||||
<image v-else src="/static/images/camera.png" mode="aspectFit" class="camera-icon">
|
<image v-else src="/static/images/camera.png" mode="aspectFit" class="camera-icon"></image>
|
||||||
</image>
|
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<text class="label required">请上传身份证反面(国徽面)</text>
|
<text class="label required">请上传身份证反面(国徽面)</text>
|
||||||
|
|
@ -126,12 +124,10 @@
|
||||||
|
|
||||||
<view class="form-upimg">
|
<view class="form-upimg">
|
||||||
<view class="upload-box" @click="chooseImage('legal_idcard_negative')">
|
<view class="upload-box" @click="chooseImage('legal_idcard_negative')">
|
||||||
</image>
|
|
||||||
<view class="upload-camera img2">
|
<view class="upload-camera img2">
|
||||||
<image v-if="formData.legal_idcard_negative" :src="formData.legal_idcard_negative"
|
<image v-if="formData.legal_idcard_negative" :src="formData.legal_idcard_negative"
|
||||||
mode="aspectFill" class="preview-image">
|
mode="aspectFill" class="preview-image"></image>
|
||||||
<image v-else src="/static/images/camera.png" mode="aspectFit" class="camera-icon">
|
<image v-else src="/static/images/camera.png" mode="aspectFit" class="camera-icon"></image>
|
||||||
</image>
|
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<text class="label required">请上传法人身份证反面(国徽面)</text>
|
<text class="label required">请上传法人身份证反面(国徽面)</text>
|
||||||
|
|
@ -2002,4 +1998,4 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
/* #endif */
|
/* #endif */
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -45,7 +45,6 @@
|
||||||
<span class="detail-info-user__company__name__line"> | </span>
|
<span class="detail-info-user__company__name__line"> | </span>
|
||||||
<view class="detail-info-user__company__name__text2">
|
<view class="detail-info-user__company__name__text2">
|
||||||
{{ item.for_shop }}
|
{{ item.for_shop }}
|
||||||
</image>
|
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
|
|
@ -811,4 +810,4 @@
|
||||||
padding: 0rpx;
|
padding: 0rpx;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -166,39 +166,61 @@
|
||||||
</view> -->
|
</view> -->
|
||||||
|
|
||||||
<!-- 项目说明 -->
|
<!-- 项目说明 -->
|
||||||
<view class="notice-section">
|
<view class="notice-section" v-if="hasProjectInfo">
|
||||||
<view class="section-title">
|
<view class="section-title">
|
||||||
<text class="section-tit">项目说明</text>
|
<text class="section-tit">项目说明</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="notice-content">
|
<view class="notice-content">
|
||||||
<view class="notice-cont">
|
<view class="notice-cont" v-if="serviceInfo.server_time">
|
||||||
<text class="notice-title">
|
<text class="notice-title">
|
||||||
服务时长:
|
服务时长:
|
||||||
</text>
|
</text>
|
||||||
<text class="notice-text">
|
<text class="notice-text">
|
||||||
{{serviceInfo.server_time}}分钟
|
{{serviceInfo.server_time}}分钟
|
||||||
|
</text>
|
||||||
|
</view>
|
||||||
|
<view class="notice-cont" v-for="(item,i) in visibleDetailList" :key="'detail-' + i">
|
||||||
|
<text class="notice-title">
|
||||||
|
{{item.title}}:
|
||||||
|
</text>
|
||||||
|
<text class="notice-text">
|
||||||
|
{{item.text}}
|
||||||
</text>
|
</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="notice-cont" v-for="(item,i) in serviceInfo.detail">
|
|
||||||
<text class="notice-title">
|
|
||||||
{{item.title}}:
|
|
||||||
</text>
|
|
||||||
<text class="notice-text">
|
|
||||||
{{item.text}}
|
|
||||||
</text>
|
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
<view class="notice-section" v-for="(section, sectionIndex) in dynamicIntroSections"
|
||||||
<!-- 图文详情 -->
|
:key="'dynamic-section-' + sectionIndex">
|
||||||
<imgAndText v-if="serviceInfo.graphic_details" :info="serviceInfo.graphic_details"></imgAndText>
|
<view class="section-title">
|
||||||
|
<text class="section-tit">{{section.title}}</text>
|
||||||
|
</view>
|
||||||
|
<view class="notice-content">
|
||||||
|
<view class="notice-cont dynamic-notice-cont" v-for="(item, itemIndex) in section.fields"
|
||||||
|
:key="'dynamic-field-' + sectionIndex + '-' + itemIndex">
|
||||||
|
<text class="notice-title">{{item.title}}:</text>
|
||||||
|
<view class="dynamic-media-list" v-if="item.images.length">
|
||||||
|
<image class="dynamic-image" v-for="(img, imgIndex) in item.images"
|
||||||
|
:key="'dynamic-img-' + imgIndex" :src="img" mode="aspectFill"
|
||||||
|
@click="previewImage(item.images, imgIndex)"></image>
|
||||||
|
</view>
|
||||||
|
<view class="dynamic-media-list" v-else-if="item.videos.length">
|
||||||
|
<video class="dynamic-video" v-for="(video, videoIndex) in item.videos"
|
||||||
|
:key="'dynamic-video-' + videoIndex" :src="video" controls></video>
|
||||||
|
</view>
|
||||||
|
<text class="notice-text" v-else>{{item.displayValue}}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<!-- 图文详情 -->
|
||||||
|
<imgAndText v-if="serviceInfo.graphic_details" :info="serviceInfo.graphic_details"></imgAndText>
|
||||||
|
|
||||||
<!-- 步骤 -->
|
<!-- 步骤 -->
|
||||||
<view class="notice-section">
|
<view class="notice-section" v-if="visibleProcessList.length">
|
||||||
<view class="section-title">
|
<view class="section-title">
|
||||||
<text class="section-tit">项目步骤</text>
|
<text class="section-tit">项目步骤</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="notice-content">
|
<view class="notice-content">
|
||||||
<view class="notice-cont" v-for="(item,i) in serviceInfo.process">
|
<view class="notice-cont" v-for="(item,i) in visibleProcessList" :key="'process-' + i">
|
||||||
<text class="notice-title">
|
<text class="notice-title">
|
||||||
{{item.title}}:
|
{{item.title}}:
|
||||||
</text>
|
</text>
|
||||||
|
|
@ -207,9 +229,9 @@
|
||||||
</text>
|
</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<!-- 订购须知 -->
|
<!-- 订购须知 -->
|
||||||
<view class="notice-section">
|
<view class="notice-section" v-if="purchaseNotesText">
|
||||||
<view class="section-title">
|
<view class="section-title">
|
||||||
<text class="section-tit">订购须知</text>
|
<text class="section-tit">订购须知</text>
|
||||||
<!-- <view class="section-tit-right">
|
<!-- <view class="section-tit-right">
|
||||||
|
|
@ -218,9 +240,9 @@
|
||||||
</view> -->
|
</view> -->
|
||||||
</view>
|
</view>
|
||||||
<view class="notice-content">
|
<view class="notice-content">
|
||||||
<text class="notice-text">
|
<text class="notice-text">
|
||||||
{{serviceInfo.purchasenotes}}
|
{{purchaseNotesText}}
|
||||||
</text>
|
</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
|
|
@ -324,11 +346,30 @@
|
||||||
}]
|
}]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
userAdrees() {
|
userAdrees() {
|
||||||
return uni.getStorageSync("userAdrees").addressRes || getApp().globalData.addressRes;
|
return uni.getStorageSync("userAdrees").addressRes || getApp().globalData.addressRes;
|
||||||
}
|
},
|
||||||
},
|
visibleDetailList() {
|
||||||
|
return (this.serviceInfo.detail || []).filter((item) => {
|
||||||
|
return item && item.title && item.text;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
visibleProcessList() {
|
||||||
|
return (this.serviceInfo.process || []).filter((item) => {
|
||||||
|
return item && item.text;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
hasProjectInfo() {
|
||||||
|
return !!this.serviceInfo.server_time || this.visibleDetailList.length > 0;
|
||||||
|
},
|
||||||
|
purchaseNotesText() {
|
||||||
|
return this.serviceInfo.purchasenotes || this.serviceInfo.purchase_notes || '';
|
||||||
|
},
|
||||||
|
dynamicIntroSections() {
|
||||||
|
return this.buildDynamicIntroSections(this.serviceInfo.intro);
|
||||||
|
}
|
||||||
|
},
|
||||||
onLoad(option) {
|
onLoad(option) {
|
||||||
//清除选择的信息
|
//清除选择的信息
|
||||||
this.$store.commit("clearServiceState")
|
this.$store.commit("clearServiceState")
|
||||||
|
|
@ -367,8 +408,88 @@
|
||||||
onHide() {
|
onHide() {
|
||||||
this.showVideo = false;
|
this.showVideo = false;
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
async goInvite() {
|
parseMaybeJson(raw) {
|
||||||
|
if (!raw) return [];
|
||||||
|
if (typeof raw !== 'string') return raw;
|
||||||
|
try {
|
||||||
|
return JSON.parse(raw);
|
||||||
|
} catch (e) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
getDynamicPlainValue(value) {
|
||||||
|
if (value === undefined || value === null) return '';
|
||||||
|
if (typeof value !== 'object') return String(value);
|
||||||
|
return value.label || value.title || value.name || value.text || value.value || '';
|
||||||
|
},
|
||||||
|
normalizeDynamicIntroField(item) {
|
||||||
|
if (!item) return null;
|
||||||
|
const type = item.type || '';
|
||||||
|
const value = item.value;
|
||||||
|
const images = [];
|
||||||
|
const videos = [];
|
||||||
|
let displayValue = '';
|
||||||
|
if (value === undefined || value === null || value === '') return null;
|
||||||
|
if (Array.isArray(value) && value.length === 0) return null;
|
||||||
|
|
||||||
|
if (type === 'image_multi') {
|
||||||
|
(value || []).forEach((img) => {
|
||||||
|
const url = typeof img === 'string' ? img : (img && (img.url || img.path));
|
||||||
|
if (url) images.push(url);
|
||||||
|
});
|
||||||
|
} else if (type === 'video') {
|
||||||
|
const list = Array.isArray(value) ? value : [value];
|
||||||
|
list.forEach((video) => {
|
||||||
|
const url = typeof video === 'string' ? video : (video && (video.url || video.path));
|
||||||
|
if (url) videos.push(url);
|
||||||
|
});
|
||||||
|
} else if (type === 'service_step' && Array.isArray(value)) {
|
||||||
|
displayValue = value.map((step, index) => {
|
||||||
|
const text = this.getDynamicPlainValue(step);
|
||||||
|
return text ? `${index + 1}. ${text}` : '';
|
||||||
|
}).filter(Boolean).join(';');
|
||||||
|
} else if (Array.isArray(value)) {
|
||||||
|
displayValue = value.map((val) => this.getDynamicPlainValue(val)).filter(Boolean).join('、');
|
||||||
|
} else if (typeof value === 'object') {
|
||||||
|
displayValue = this.getDynamicPlainValue(value);
|
||||||
|
} else {
|
||||||
|
displayValue = String(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!displayValue && images.length === 0 && videos.length === 0) return null;
|
||||||
|
return {
|
||||||
|
title: item.title || '补充信息',
|
||||||
|
type,
|
||||||
|
displayValue,
|
||||||
|
images,
|
||||||
|
videos,
|
||||||
|
sectionTitle: item.sectionTitle || '',
|
||||||
|
sectionId: item.sectionId || '',
|
||||||
|
};
|
||||||
|
},
|
||||||
|
buildDynamicIntroSections(rawIntro) {
|
||||||
|
const intro = this.parseMaybeJson(rawIntro);
|
||||||
|
if (!Array.isArray(intro)) return [];
|
||||||
|
const groups = [];
|
||||||
|
const groupMap = {};
|
||||||
|
intro.forEach((item) => {
|
||||||
|
const field = this.normalizeDynamicIntroField(item);
|
||||||
|
if (!field) return;
|
||||||
|
const title = field.sectionTitle || (field.type === 'service_step' ? '项目步骤' : '更多说明');
|
||||||
|
const key = field.sectionId || title;
|
||||||
|
if (!groupMap[key]) {
|
||||||
|
groupMap[key] = {
|
||||||
|
title,
|
||||||
|
fields: [],
|
||||||
|
};
|
||||||
|
groups.push(groupMap[key]);
|
||||||
|
}
|
||||||
|
groupMap[key].fields.push(field);
|
||||||
|
});
|
||||||
|
return groups.filter((section) => section.fields.length > 0);
|
||||||
|
},
|
||||||
|
async goInvite() {
|
||||||
let imageUrl =
|
let imageUrl =
|
||||||
'https://mrrplus.oss-cn-beijing.aliyuncs.com/photo/yh/1756713709528-717.png?x-oss-process=image/resize,p_40'
|
'https://mrrplus.oss-cn-beijing.aliyuncs.com/photo/yh/1756713709528-717.png?x-oss-process=image/resize,p_40'
|
||||||
if (this.serviceInfo.photo[0]) {
|
if (this.serviceInfo.photo[0]) {
|
||||||
|
|
@ -1136,13 +1257,33 @@
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.notice-text {
|
.notice-text {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
font-size: 26rpx;
|
font-size: 26rpx;
|
||||||
color: #666;
|
color: #666;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 底部按钮 */
|
.dynamic-notice-cont {
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dynamic-media-list {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dynamic-image,
|
||||||
|
.dynamic-video {
|
||||||
|
width: 160rpx;
|
||||||
|
height: 160rpx;
|
||||||
|
margin-right: 12rpx;
|
||||||
|
margin-bottom: 12rpx;
|
||||||
|
border-radius: 8rpx;
|
||||||
|
background-color: #F6F6F6;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 底部按钮 */
|
||||||
.bottom-bar {
|
.bottom-bar {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
left: 0;
|
left: 0;
|
||||||
|
|
@ -1346,4 +1487,4 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
/* #endif */
|
/* #endif */
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@
|
||||||
ref="dynamicForm" />
|
ref="dynamicForm" />
|
||||||
|
|
||||||
<!-- 服务流程 -->
|
<!-- 服务流程 -->
|
||||||
<view class="form-step">
|
<view class="form-step" v-if="!hasDynamicServiceStepField">
|
||||||
<view class="form-item">
|
<view class="form-item">
|
||||||
<text class="form-label">服务流程</text>
|
<text class="form-label">服务流程</text>
|
||||||
<text @click="addStep" style="color: #FF4767;">添加步骤</text>
|
<text @click="addStep" style="color: #FF4767;">添加步骤</text>
|
||||||
|
|
@ -42,8 +42,9 @@
|
||||||
</view>
|
</view>
|
||||||
<ali-oss-uploader :max-count="1" :max-size="100" :type="artisanType + 1"
|
<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"
|
:user-id="formData.publish_user_id" :width="'654rpx'" :kind="2" :file-string="formData.video"
|
||||||
accept="video/*" button-text="上传视频" @success="uploadVideoSuc" @error="uploadVideoErr"
|
accept="video/*" button-text="上传视频" @input="setVideoValue" @change="setVideoValue"
|
||||||
@delete="uploadVideoDel" @ckeckisShowPer="ckeckisShowPer" @ckecknowQer="ckecknowQer">
|
@success="uploadVideoSuc" @error="uploadVideoErr" @delete="uploadVideoDel"
|
||||||
|
@ckeckisShowPer="ckeckisShowPer" @ckecknowQer="ckecknowQer">
|
||||||
</ali-oss-uploader>
|
</ali-oss-uploader>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
@ -189,37 +190,80 @@ export default {
|
||||||
this.ratio
|
this.ratio
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
hasDynamicVideoField() {
|
hasDynamicVideoField() {
|
||||||
return this.formFields.some((group) => {
|
return this.formFields.some((group) => {
|
||||||
if (group.type === 'single' && group.field) {
|
if (group.type === 'single' && group.field) {
|
||||||
return group.field.type === 'video';
|
return group.field.type === 'video';
|
||||||
}
|
}
|
||||||
if (group.type === 'section' && Array.isArray(group.fields)) {
|
if (group.type === 'section' && Array.isArray(group.fields)) {
|
||||||
return group.fields.some((field) => field.type === 'video');
|
return group.fields.some((field) => field.type === 'video');
|
||||||
}
|
}
|
||||||
return group.type === 'video';
|
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('/');
|
||||||
|
},
|
||||||
},
|
},
|
||||||
serviceClassName() {
|
|
||||||
return [this.firstClassName, this.secondClassName].filter(Boolean).join('/');
|
|
||||||
},
|
|
||||||
},
|
|
||||||
methods: {
|
methods: {
|
||||||
// 字段类型映射:接口类型 -> dynamic-form类型
|
// 字段类型映射:接口类型 -> dynamic-form类型
|
||||||
mapFieldType(fieldType) {
|
mapFieldType(fieldType) {
|
||||||
const typeMap = {
|
const typeMap = {
|
||||||
'text': 'text',
|
'text': 'text',
|
||||||
'number': 'number',
|
'number': 'number',
|
||||||
'image_multi': 'image_multi',
|
'image_multi': 'image_multi',
|
||||||
'video': 'video',
|
'video': 'video',
|
||||||
'richtext_editor': 'richtext_editor',
|
'service_step': 'service_step',
|
||||||
'select': 'select',
|
'richtext_editor': 'richtext_editor',
|
||||||
'single_select': 'single_select',
|
'select': 'select',
|
||||||
'multi_select': 'multi_select',
|
'single_select': 'single_select',
|
||||||
'textarea': 'textarea',
|
'radio': 'single_select',
|
||||||
};
|
'switch': 'single_select',
|
||||||
return typeMap[fieldType] || 'text';
|
'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;
|
||||||
|
},
|
||||||
|
|
||||||
// 将接口config_data转换为dynamic-form需要的分组格式
|
// 将接口config_data转换为dynamic-form需要的分组格式
|
||||||
transformConfigToFields(configData) {
|
transformConfigToFields(configData) {
|
||||||
|
|
@ -239,13 +283,22 @@ export default {
|
||||||
placeholder: preset.placeholder || `请输入${preset.title}`,
|
placeholder: preset.placeholder || `请输入${preset.title}`,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (preset.showHint && preset.hint) {
|
if (preset.options && Array.isArray(preset.options) && preset.options.length > 0) {
|
||||||
field.tip = preset.hint;
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
if (preset.key === 'line_price' || preset.key === 'server_price') {
|
this.applyFieldHint(field, preset);
|
||||||
field.tips = '(元)';
|
|
||||||
field.tip = preset.key === 'line_price' ? '服务的基础定价,未参与任何优惠的价格' : '服务参与单品优惠后的价格';
|
if (preset.key === 'line_price' || preset.key === 'server_price') {
|
||||||
|
field.tips = '(元)';
|
||||||
|
if (!field.tip) {
|
||||||
|
field.tip = preset.key === 'line_price' ? '服务的基础定价,未参与任何优惠的价格' : '服务参与单品优惠后的价格';
|
||||||
|
}
|
||||||
} else if (preset.key === 'server_time') {
|
} else if (preset.key === 'server_time') {
|
||||||
field.placeholder = '请输入服务时长';
|
field.placeholder = '请输入服务时长';
|
||||||
field.tips = '分钟';
|
field.tips = '分钟';
|
||||||
|
|
@ -294,10 +347,17 @@ export default {
|
||||||
field.tips = '分钟';
|
field.tips = '分钟';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sectionField.options && Array.isArray(sectionField.options) &&
|
this.applyFieldHint(field, sectionField);
|
||||||
sectionField.options.length > 0) {
|
|
||||||
field.options = sectionField.options;
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
if (sectionField.maxCount && (sectionField.fieldType === 'multi_select' ||
|
if (sectionField.maxCount && (sectionField.fieldType === 'multi_select' ||
|
||||||
sectionField.fieldType === 'image_multi' || sectionField.fieldType === 'video')) {
|
sectionField.fieldType === 'image_multi' || sectionField.fieldType === 'video')) {
|
||||||
|
|
@ -320,12 +380,15 @@ export default {
|
||||||
|
|
||||||
field._fieldType = sectionField.fieldType;
|
field._fieldType = sectionField.fieldType;
|
||||||
field._hasOriginalKey = !!sectionField.key;
|
field._hasOriginalKey = !!sectionField.key;
|
||||||
field._original = {
|
field._original = {
|
||||||
id: sectionField.id,
|
id: sectionField.id,
|
||||||
sectionId: section.id,
|
sectionId: section.id,
|
||||||
sectionType: section.sectionType,
|
sectionType: section.sectionType,
|
||||||
userDefined: sectionField.userDefined,
|
userDefined: sectionField.userDefined,
|
||||||
};
|
};
|
||||||
|
field._sectionTitle = section.sectionTitle || '';
|
||||||
|
field._sectionId = section.id;
|
||||||
|
field._sectionType = section.sectionType;
|
||||||
|
|
||||||
sectionFields.push(field);
|
sectionFields.push(field);
|
||||||
});
|
});
|
||||||
|
|
@ -444,16 +507,9 @@ export default {
|
||||||
let that = this;
|
let that = this;
|
||||||
let value = e.detail.value;
|
let value = e.detail.value;
|
||||||
|
|
||||||
// 1. 移除非数字和小数点
|
|
||||||
value = value.replace(/[^\d.]/g, '');
|
|
||||||
// 1. 移除非数字和小数点
|
// 1. 移除非数字和小数点
|
||||||
value = value.replace(/[^\d.]/g, '');
|
value = value.replace(/[^\d.]/g, '');
|
||||||
|
|
||||||
// 2. 确保只有一个小数点
|
|
||||||
const parts = value.split('.');
|
|
||||||
if (parts.length > 2) {
|
|
||||||
value = parts[0] + '.' + parts.slice(1).join('');
|
|
||||||
}
|
|
||||||
// 2. 确保只有一个小数点
|
// 2. 确保只有一个小数点
|
||||||
const parts = value.split('.');
|
const parts = value.split('.');
|
||||||
if (parts.length > 2) {
|
if (parts.length > 2) {
|
||||||
|
|
@ -470,29 +526,8 @@ export default {
|
||||||
}
|
}
|
||||||
that.$set(that.formData, 'line_price', value);
|
that.$set(that.formData, 'line_price', value);
|
||||||
},
|
},
|
||||||
// 服务简介部分
|
// 服务简介部分
|
||||||
initDetailForm(detail = []) {
|
initDetailForm(detail = []) {
|
||||||
// 先重置 detailForm 为空
|
|
||||||
this.detailForm = {
|
|
||||||
effect: "",
|
|
||||||
crowd: "",
|
|
||||||
product: "",
|
|
||||||
scope: "",
|
|
||||||
tips: "",
|
|
||||||
};
|
|
||||||
this.detailOtherList = [];
|
|
||||||
// 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);
|
|
||||||
},
|
|
||||||
// 服务简介部分
|
|
||||||
initDetailForm(detail = []) {
|
|
||||||
// 先重置 detailForm 为空
|
// 先重置 detailForm 为空
|
||||||
this.detailForm = {
|
this.detailForm = {
|
||||||
effect: "",
|
effect: "",
|
||||||
|
|
@ -593,9 +628,43 @@ export default {
|
||||||
ckecknowQer(value) {
|
ckecknowQer(value) {
|
||||||
this.nowQer = value;
|
this.nowQer = value;
|
||||||
},
|
},
|
||||||
addVideo(value) {
|
addVideo(value) {
|
||||||
this.formData.video = 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);
|
||||||
|
},
|
||||||
// addImg(arr) {
|
// addImg(arr) {
|
||||||
// this.formData.photo = this.formData.photo.concat(arr);
|
// this.formData.photo = this.formData.photo.concat(arr);
|
||||||
// },
|
// },
|
||||||
|
|
@ -615,25 +684,29 @@ export default {
|
||||||
onFileDelete(file, index) {
|
onFileDelete(file, index) {
|
||||||
console.log("删除文件:", this.formData.photo, index);
|
console.log("删除文件:", this.formData.photo, index);
|
||||||
},
|
},
|
||||||
uploadVideoSuc(file) {
|
uploadVideoSuc(file) {
|
||||||
this.formData.video = file;
|
this.setVideoValue(file);
|
||||||
},
|
},
|
||||||
uploadVideoErr() { },
|
uploadVideoErr() { },
|
||||||
uploadVideoDel() {
|
uploadVideoDel() {
|
||||||
this.formData.video = null;
|
this.formData.video = null;
|
||||||
},
|
},
|
||||||
async getInit() {
|
async getInit() {
|
||||||
await request
|
await request
|
||||||
.post("/sj/servers/detail", {
|
.post("/sj/servers/detail", {
|
||||||
id: this.formData.id
|
id: this.formData.id
|
||||||
})
|
})
|
||||||
.then((res) => {
|
.then(async (res) => {
|
||||||
// 直接合并所有字段,避免遗漏
|
// 直接合并所有字段,避免遗漏
|
||||||
this.formData = {
|
this.formData = {
|
||||||
...this.formData,
|
...this.formData,
|
||||||
...res.data
|
...res.data
|
||||||
};
|
};
|
||||||
// 确保 process 保持为数组(后端可能返回字符串)
|
this.setVideoValue(this.formData.video);
|
||||||
|
if (this.formData.first_class_id && this.formFields.length === 0) {
|
||||||
|
await this.getTemplate();
|
||||||
|
}
|
||||||
|
// 确保 process 保持为数组(后端可能返回字符串)
|
||||||
if (!Array.isArray(this.formData.process)) {
|
if (!Array.isArray(this.formData.process)) {
|
||||||
this.formData.process = [];
|
this.formData.process = [];
|
||||||
}
|
}
|
||||||
|
|
@ -752,16 +825,9 @@ export default {
|
||||||
let that = this;
|
let that = this;
|
||||||
let value = e.detail.value;
|
let value = e.detail.value;
|
||||||
|
|
||||||
// 1. 移除非数字和小数点
|
|
||||||
value = value.replace(/[^\d.]/g, "");
|
|
||||||
// 1. 移除非数字和小数点
|
// 1. 移除非数字和小数点
|
||||||
value = value.replace(/[^\d.]/g, "");
|
value = value.replace(/[^\d.]/g, "");
|
||||||
|
|
||||||
// 2. 确保只有一个小数点
|
|
||||||
const parts = value.split(".");
|
|
||||||
if (parts.length > 2) {
|
|
||||||
value = parts[0] + "." + parts.slice(1).join("");
|
|
||||||
}
|
|
||||||
// 2. 确保只有一个小数点
|
// 2. 确保只有一个小数点
|
||||||
const parts = value.split(".");
|
const parts = value.split(".");
|
||||||
if (parts.length > 2) {
|
if (parts.length > 2) {
|
||||||
|
|
@ -792,7 +858,7 @@ export default {
|
||||||
},
|
},
|
||||||
// 视频上传相关方法
|
// 视频上传相关方法
|
||||||
uploadVideoSuc(file) {
|
uploadVideoSuc(file) {
|
||||||
this.formData.video = file;
|
this.setVideoValue(file);
|
||||||
},
|
},
|
||||||
uploadVideoErr() { },
|
uploadVideoErr() { },
|
||||||
uploadVideoDel() {
|
uploadVideoDel() {
|
||||||
|
|
@ -848,50 +914,7 @@ export default {
|
||||||
return value.length === 0;
|
return value.length === 0;
|
||||||
}
|
}
|
||||||
return !value;
|
return !value;
|
||||||
});
|
|
||||||
// 保存草稿
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
return !value;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (isEmpty) {
|
|
||||||
uni.showToast({
|
|
||||||
title: "请至少填写一项内容",
|
|
||||||
icon: "none",
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (isEmpty) {
|
if (isEmpty) {
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
title: "请至少填写一项内容",
|
title: "请至少填写一项内容",
|
||||||
|
|
@ -942,8 +965,8 @@ export default {
|
||||||
|
|
||||||
// 验证动态表单字段
|
// 验证动态表单字段
|
||||||
validateDynamicForm() {
|
validateDynamicForm() {
|
||||||
// 遍历 formGroups(分组结构)按顺序验证必填项
|
// 遍历 formGroups(分组结构)按顺序验证必填项
|
||||||
for (let i = 0; i < this.formFields.length; i++) {
|
for (let i = 0; i < this.formFields.length; i++) {
|
||||||
const group = this.formFields[i];
|
const group = this.formFields[i];
|
||||||
const fieldsToCheck = [];
|
const fieldsToCheck = [];
|
||||||
if (group.type === 'single' && group.field) {
|
if (group.type === 'single' && group.field) {
|
||||||
|
|
@ -960,17 +983,30 @@ export default {
|
||||||
const isEmpty = value === undefined || value === null || value === '' ||
|
const isEmpty = value === undefined || value === null || value === '' ||
|
||||||
(Array.isArray(value) && value.length === 0);
|
(Array.isArray(value) && value.length === 0);
|
||||||
|
|
||||||
if (isEmpty) {
|
if (isEmpty) {
|
||||||
// 根据字段类型显示不同的提示语
|
// 根据字段类型显示不同的提示语
|
||||||
const prefix = (field.type === 'image_multi' || field.type === 'video') ? '请上传' : '请输入';
|
const prefix = (field.type === 'image_multi' || field.type === 'video') ? '请上传' :
|
||||||
uni.showToast({
|
(field.type === 'select' || field.type === 'single_select' || field.type === 'multi_select') ?
|
||||||
title: `${prefix}${field.label}`,
|
'请选择' : '请输入';
|
||||||
icon: "none",
|
uni.showToast({
|
||||||
|
title: `${prefix}${field.label}`,
|
||||||
|
icon: "none",
|
||||||
});
|
});
|
||||||
return false;
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 服务时长特殊验证
|
||||||
if (field.key === 'server_time') {
|
if (field.key === 'server_time') {
|
||||||
if (value < 10) {
|
if (value < 10) {
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
|
|
@ -1008,8 +1044,9 @@ export default {
|
||||||
}
|
}
|
||||||
|
|
||||||
// 视频字段(独立上传组件)
|
// 视频字段(独立上传组件)
|
||||||
if (this.formData.video) {
|
const videoUrl = this.normalizeVideoValue(this.formData.video);
|
||||||
submitData.video = this.formData.video;
|
if (videoUrl) {
|
||||||
|
submitData.video = videoUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 遍历分组,按顺序处理字段
|
// 遍历分组,按顺序处理字段
|
||||||
|
|
@ -1021,15 +1058,36 @@ export default {
|
||||||
} else if (group.type === 'section' && group.fields) {
|
} else if (group.type === 'section' && group.fields) {
|
||||||
fieldsToProcess = group.fields;
|
fieldsToProcess = group.fields;
|
||||||
}
|
}
|
||||||
for (let j = 0; j < fieldsToProcess.length; j++) {
|
for (let j = 0; j < fieldsToProcess.length; j++) {
|
||||||
const field = fieldsToProcess[j];
|
const field = fieldsToProcess[j];
|
||||||
const value = this.formData[field.key];
|
const value = this.formData[field.key];
|
||||||
if (value === undefined || value === null || value === '') continue;
|
if (value === undefined || value === null || value === '') continue;
|
||||||
if (Array.isArray(value) && value.length === 0) continue;
|
if (Array.isArray(value) && value.length === 0) continue;
|
||||||
|
|
||||||
if (field._hasOriginalKey) {
|
if (field.type === 'service_step' && Array.isArray(value)) {
|
||||||
// 有key的字段直接作为请求参数
|
const processArr = value
|
||||||
// 图片字段确保为纯URL字符串数组
|
.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;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (field._hasOriginalKey) {
|
||||||
|
// 有key的字段直接作为请求参数
|
||||||
|
// 图片字段确保为纯URL字符串数组
|
||||||
if (field.type === 'image_multi' && Array.isArray(value)) {
|
if (field.type === 'image_multi' && Array.isArray(value)) {
|
||||||
submitData[field.key] = value.map(function (item) {
|
submitData[field.key] = value.map(function (item) {
|
||||||
return typeof item === 'string' ? item : (item && item.url ? item.url : item);
|
return typeof item === 'string' ? item : (item && item.url ? item.url : item);
|
||||||
|
|
@ -1039,13 +1097,16 @@ export default {
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// 没有key的字段放入intro数组
|
// 没有key的字段放入intro数组
|
||||||
introArr.push({
|
introArr.push({
|
||||||
id: field._original ? field._original.id : field.key,
|
id: field._original ? field._original.id : field.key,
|
||||||
title: field.label,
|
title: field.label,
|
||||||
type: field._fieldType || field.type,
|
type: field._fieldType || field.type,
|
||||||
value: value,
|
value: value,
|
||||||
});
|
sectionId: field._sectionId || (field._original && field._original.sectionId),
|
||||||
}
|
sectionTitle: field._sectionTitle || '',
|
||||||
|
sectionType: field._sectionType || (field._original && field._original.sectionType),
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1054,9 +1115,9 @@ export default {
|
||||||
}
|
}
|
||||||
|
|
||||||
// 服务步骤:有key时作为独立参数,无key时放入intro数组
|
// 服务步骤:有key时作为独立参数,无key时放入intro数组
|
||||||
if (this.formData.process && this.formData.process.length > 0) {
|
if (!this.hasDynamicServiceStepField && this.formData.process && this.formData.process.length > 0) {
|
||||||
const processArr = this.formData.process
|
const processArr = this.formData.process
|
||||||
.filter(item => item.text && item.text.trim())
|
.filter(item => item.text && item.text.trim())
|
||||||
.map(item => ({ text: item.text.trim() }));
|
.map(item => ({ text: item.text.trim() }));
|
||||||
if (processArr.length > 0) {
|
if (processArr.length > 0) {
|
||||||
if (this.processFieldKey) {
|
if (this.processFieldKey) {
|
||||||
|
|
@ -1068,11 +1129,12 @@ export default {
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
introList = [];
|
introList = [];
|
||||||
}
|
}
|
||||||
introList.push({
|
introList.push({
|
||||||
id: 'service_step',
|
id: 'service_step',
|
||||||
title: '服务流程',
|
title: '服务流程',
|
||||||
value: processArr,
|
type: 'service_step',
|
||||||
});
|
value: processArr,
|
||||||
|
});
|
||||||
submitData.intro = JSON.stringify(introList);
|
submitData.intro = JSON.stringify(introList);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1085,11 +1147,19 @@ export default {
|
||||||
submitForm: debounce(function () {
|
submitForm: debounce(function () {
|
||||||
if (this.isAdd) return;
|
if (this.isAdd) return;
|
||||||
|
|
||||||
// 先同步服务简介数据到formData
|
// 先同步服务简介数据到formData
|
||||||
this.syncDetailForm();
|
this.syncDetailForm();
|
||||||
|
|
||||||
// 验证动态表单字段
|
if (!this.formData.first_class_id) {
|
||||||
if (!this.validateDynamicForm()) {
|
uni.showToast({
|
||||||
|
title: "请选择服务分类",
|
||||||
|
icon: "none",
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证动态表单字段
|
||||||
|
if (!this.validateDynamicForm()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1603,17 +1673,8 @@ export default {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
height: 100rpx;
|
.btn-draft,
|
||||||
padding: 40rpx 0 64rpx;
|
.btn-submit {
|
||||||
box-sizing: border-box;
|
|
||||||
display: flex;
|
|
||||||
flex-flow: row nowrap;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-draft,
|
|
||||||
.btn-submit {
|
|
||||||
width: 334rpx;
|
width: 334rpx;
|
||||||
height: 76rpx;
|
height: 76rpx;
|
||||||
line-height: 76rpx;
|
line-height: 76rpx;
|
||||||
|
|
@ -1942,14 +2003,6 @@ align-items: center;
|
||||||
|
|
||||||
/* 平台特定样式 */
|
/* 平台特定样式 */
|
||||||
/* #ifdef H5 */
|
/* #ifdef H5 */
|
||||||
/* 平台特定样式 */
|
|
||||||
/* #ifdef H5 */
|
|
||||||
|
|
||||||
.form-input,
|
|
||||||
.form-textarea {
|
|
||||||
outline: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-input,
|
.form-input,
|
||||||
.form-textarea {
|
.form-textarea {
|
||||||
outline: none;
|
outline: none;
|
||||||
|
|
@ -1958,18 +2011,7 @@ align-items: center;
|
||||||
.upload-btn {
|
.upload-btn {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.upload-btn {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* #endif */
|
/* #endif */
|
||||||
/* #endif */
|
|
||||||
|
|
||||||
/* #ifdef APP-PLUS */
|
|
||||||
.form-input {
|
|
||||||
height: 90rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* #ifdef APP-PLUS */
|
/* #ifdef APP-PLUS */
|
||||||
.form-input {
|
.form-input {
|
||||||
|
|
@ -1981,20 +2023,7 @@ align-items: center;
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
visibility: visible;
|
visibility: visible;
|
||||||
}
|
}
|
||||||
|
|
||||||
.permission.transform {
|
|
||||||
top: calc(var(--status-bar-height) + 88rpx + 30rpx);
|
|
||||||
opacity: 1;
|
|
||||||
visibility: visible;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* #endif */
|
/* #endif */
|
||||||
/* #endif */
|
|
||||||
|
|
||||||
/* #ifdef MP-WEIXIN */
|
|
||||||
.form-input {
|
|
||||||
height: 80rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* #ifdef MP-WEIXIN */
|
/* #ifdef MP-WEIXIN */
|
||||||
.form-input {
|
.form-input {
|
||||||
|
|
@ -2004,12 +2033,8 @@ align-items: center;
|
||||||
.bottom-buttons {
|
.bottom-buttons {
|
||||||
padding-bottom: env(safe-area-inset-bottom);
|
padding-bottom: env(safe-area-inset-bottom);
|
||||||
}
|
}
|
||||||
|
|
||||||
.bottom-buttons {
|
|
||||||
padding-bottom: env(safe-area-inset-bottom);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* #endif */
|
/* #endif */
|
||||||
|
|
||||||
.tip {
|
.tip {
|
||||||
padding: 20rpx 30rpx;
|
padding: 20rpx 30rpx;
|
||||||
background: #feefec;
|
background: #feefec;
|
||||||
|
|
@ -2017,31 +2042,9 @@ align-items: center;
|
||||||
font-size: 30rpx;
|
font-size: 30rpx;
|
||||||
color: #f31d2f;
|
color: #f31d2f;
|
||||||
line-height: 42rpx;
|
line-height: 42rpx;
|
||||||
text-align: left;
|
text-align: center;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
|
|
||||||
/* #endif */
|
|
||||||
.tip {
|
|
||||||
padding: 20rpx 30rpx;
|
|
||||||
background: #feefec;
|
|
||||||
font-weight: 400;
|
|
||||||
font-size: 30rpx;
|
|
||||||
color: #f31d2f;
|
|
||||||
line-height: 42rpx;
|
|
||||||
text-align: left;
|
|
||||||
font-style: normal;
|
|
||||||
|
|
||||||
text-align: center;
|
|
||||||
text-align: center;
|
|
||||||
|
|
||||||
.tip-icon {
|
|
||||||
width: 34rpx;
|
|
||||||
height: 34rpx;
|
|
||||||
margin-right: 10rpx;
|
|
||||||
transform: translateY(6rpx);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.tip-icon {
|
.tip-icon {
|
||||||
width: 34rpx;
|
width: 34rpx;
|
||||||
height: 34rpx;
|
height: 34rpx;
|
||||||
|
|
|
||||||
|
|
@ -152,20 +152,58 @@
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 项目说明 -->
|
<!-- 项目说明 -->
|
||||||
<view class="notice-section">
|
<view class="notice-section" v-if="hasProjectInfo">
|
||||||
<view class="section-title">
|
<view class="section-title">
|
||||||
<text class="section-tit">项目说明</text>
|
<text class="section-tit">项目说明</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="notice-content">
|
<view class="notice-content">
|
||||||
<view class="notice-cont">
|
<view class="notice-cont" v-if="serviceInfo.server_time">
|
||||||
<text class="notice-title">
|
<text class="notice-title">
|
||||||
服务时长:
|
服务时长:
|
||||||
</text>
|
</text>
|
||||||
<text class="notice-text">
|
<text class="notice-text">
|
||||||
{{serviceInfo.server_time}}分钟
|
{{serviceInfo.server_time}}分钟
|
||||||
|
</text>
|
||||||
|
</view>
|
||||||
|
<view class="notice-cont" v-for="(item,i) in visibleDetailList" :key="'detail-' + i">
|
||||||
|
<text class="notice-title">
|
||||||
|
{{item.title}}:
|
||||||
|
</text>
|
||||||
|
<text class="notice-text">
|
||||||
|
{{item.text}}
|
||||||
</text>
|
</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="notice-cont" v-for="(item,i) in serviceInfo.detail">
|
</view>
|
||||||
|
</view>
|
||||||
|
<view class="notice-section" v-for="(section, sectionIndex) in dynamicIntroSections"
|
||||||
|
:key="'dynamic-section-' + sectionIndex">
|
||||||
|
<view class="section-title">
|
||||||
|
<text class="section-tit">{{section.title}}</text>
|
||||||
|
</view>
|
||||||
|
<view class="notice-content">
|
||||||
|
<view class="notice-cont dynamic-notice-cont" v-for="(item, itemIndex) in section.fields"
|
||||||
|
:key="'dynamic-field-' + sectionIndex + '-' + itemIndex">
|
||||||
|
<text class="notice-title">{{item.title}}:</text>
|
||||||
|
<view class="dynamic-media-list" v-if="item.images.length">
|
||||||
|
<image class="dynamic-image" v-for="(img, imgIndex) in item.images"
|
||||||
|
:key="'dynamic-img-' + imgIndex" :src="img" mode="aspectFill"
|
||||||
|
@click="previewImage(item.images, imgIndex)"></image>
|
||||||
|
</view>
|
||||||
|
<view class="dynamic-media-list" v-else-if="item.videos.length">
|
||||||
|
<video class="dynamic-video" v-for="(video, videoIndex) in item.videos"
|
||||||
|
:key="'dynamic-video-' + videoIndex" :src="video" controls></video>
|
||||||
|
</view>
|
||||||
|
<text class="notice-text" v-else>{{item.displayValue}}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<!-- 步骤 -->
|
||||||
|
<view class="notice-section" v-if="visibleProcessList.length">
|
||||||
|
<view class="section-title">
|
||||||
|
<text class="section-tit">项目步骤</text>
|
||||||
|
</view>
|
||||||
|
<view class="notice-content">
|
||||||
|
<view class="notice-cont" v-for="(item,i) in visibleProcessList" :key="'process-' + i">
|
||||||
<text class="notice-title">
|
<text class="notice-title">
|
||||||
{{item.title}}:
|
{{item.title}}:
|
||||||
</text>
|
</text>
|
||||||
|
|
@ -174,25 +212,9 @@
|
||||||
</text>
|
</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
|
||||||
<!-- 步骤 -->
|
|
||||||
<view class="notice-section">
|
|
||||||
<view class="section-title">
|
|
||||||
<text class="section-tit">项目步骤</text>
|
|
||||||
</view>
|
</view>
|
||||||
<view class="notice-content">
|
<!-- 订购须知 -->
|
||||||
<view class="notice-cont" v-for="(item,i) in serviceInfo.process">
|
<view class="notice-section" v-if="purchaseNotesText">
|
||||||
<text class="notice-title">
|
|
||||||
{{item.title}}:
|
|
||||||
</text>
|
|
||||||
<text class="notice-text">
|
|
||||||
{{item.text}}
|
|
||||||
</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
<!-- 订购须知 -->
|
|
||||||
<view class="notice-section">
|
|
||||||
<view class="section-title">
|
<view class="section-title">
|
||||||
<text class="section-tit">订购须知</text>
|
<text class="section-tit">订购须知</text>
|
||||||
<!-- <view class="section-tit-right">
|
<!-- <view class="section-tit-right">
|
||||||
|
|
@ -201,9 +223,9 @@
|
||||||
</view> -->
|
</view> -->
|
||||||
</view>
|
</view>
|
||||||
<view class="notice-content">
|
<view class="notice-content">
|
||||||
<text class="notice-text">
|
<text class="notice-text">
|
||||||
{{serviceInfo.purchasenotes}}
|
{{purchaseNotesText}}
|
||||||
</text>
|
</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
|
|
@ -283,9 +305,30 @@
|
||||||
tab: '超出期待',
|
tab: '超出期待',
|
||||||
type: '双色法式美甲'
|
type: '双色法式美甲'
|
||||||
}]
|
}]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onLoad(option) {
|
computed: {
|
||||||
|
visibleDetailList() {
|
||||||
|
return (this.serviceInfo.detail || []).filter((item) => {
|
||||||
|
return item && item.title && item.text;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
visibleProcessList() {
|
||||||
|
return (this.serviceInfo.process || []).filter((item) => {
|
||||||
|
return item && item.text;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
hasProjectInfo() {
|
||||||
|
return !!this.serviceInfo.server_time || this.visibleDetailList.length > 0;
|
||||||
|
},
|
||||||
|
purchaseNotesText() {
|
||||||
|
return this.serviceInfo.purchasenotes || this.serviceInfo.purchase_notes || '';
|
||||||
|
},
|
||||||
|
dynamicIntroSections() {
|
||||||
|
return this.buildDynamicIntroSections(this.serviceInfo.intro);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onLoad(option) {
|
||||||
// Android可能需要延迟加载
|
// Android可能需要延迟加载
|
||||||
request.post('/sj/serverdetail', {
|
request.post('/sj/serverdetail', {
|
||||||
id: option.id
|
id: option.id
|
||||||
|
|
@ -315,9 +358,89 @@
|
||||||
onHide() {
|
onHide() {
|
||||||
this.showVideo = false;
|
this.showVideo = false;
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
// 获取手艺人详情
|
parseMaybeJson(raw) {
|
||||||
getSyrDetail(id) {
|
if (!raw) return [];
|
||||||
|
if (typeof raw !== 'string') return raw;
|
||||||
|
try {
|
||||||
|
return JSON.parse(raw);
|
||||||
|
} catch (e) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
getDynamicPlainValue(value) {
|
||||||
|
if (value === undefined || value === null) return '';
|
||||||
|
if (typeof value !== 'object') return String(value);
|
||||||
|
return value.label || value.title || value.name || value.text || value.value || '';
|
||||||
|
},
|
||||||
|
normalizeDynamicIntroField(item) {
|
||||||
|
if (!item) return null;
|
||||||
|
const type = item.type || '';
|
||||||
|
const value = item.value;
|
||||||
|
const images = [];
|
||||||
|
const videos = [];
|
||||||
|
let displayValue = '';
|
||||||
|
if (value === undefined || value === null || value === '') return null;
|
||||||
|
if (Array.isArray(value) && value.length === 0) return null;
|
||||||
|
|
||||||
|
if (type === 'image_multi') {
|
||||||
|
(value || []).forEach((img) => {
|
||||||
|
const url = typeof img === 'string' ? img : (img && (img.url || img.path));
|
||||||
|
if (url) images.push(url);
|
||||||
|
});
|
||||||
|
} else if (type === 'video') {
|
||||||
|
const list = Array.isArray(value) ? value : [value];
|
||||||
|
list.forEach((video) => {
|
||||||
|
const url = typeof video === 'string' ? video : (video && (video.url || video.path));
|
||||||
|
if (url) videos.push(url);
|
||||||
|
});
|
||||||
|
} else if (type === 'service_step' && Array.isArray(value)) {
|
||||||
|
displayValue = value.map((step, index) => {
|
||||||
|
const text = this.getDynamicPlainValue(step);
|
||||||
|
return text ? `${index + 1}. ${text}` : '';
|
||||||
|
}).filter(Boolean).join(';');
|
||||||
|
} else if (Array.isArray(value)) {
|
||||||
|
displayValue = value.map((val) => this.getDynamicPlainValue(val)).filter(Boolean).join('、');
|
||||||
|
} else if (typeof value === 'object') {
|
||||||
|
displayValue = this.getDynamicPlainValue(value);
|
||||||
|
} else {
|
||||||
|
displayValue = String(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!displayValue && images.length === 0 && videos.length === 0) return null;
|
||||||
|
return {
|
||||||
|
title: item.title || '补充信息',
|
||||||
|
type,
|
||||||
|
displayValue,
|
||||||
|
images,
|
||||||
|
videos,
|
||||||
|
sectionTitle: item.sectionTitle || '',
|
||||||
|
sectionId: item.sectionId || '',
|
||||||
|
};
|
||||||
|
},
|
||||||
|
buildDynamicIntroSections(rawIntro) {
|
||||||
|
const intro = this.parseMaybeJson(rawIntro);
|
||||||
|
if (!Array.isArray(intro)) return [];
|
||||||
|
const groups = [];
|
||||||
|
const groupMap = {};
|
||||||
|
intro.forEach((item) => {
|
||||||
|
const field = this.normalizeDynamicIntroField(item);
|
||||||
|
if (!field) return;
|
||||||
|
const title = field.sectionTitle || (field.type === 'service_step' ? '项目步骤' : '更多说明');
|
||||||
|
const key = field.sectionId || title;
|
||||||
|
if (!groupMap[key]) {
|
||||||
|
groupMap[key] = {
|
||||||
|
title,
|
||||||
|
fields: [],
|
||||||
|
};
|
||||||
|
groups.push(groupMap[key]);
|
||||||
|
}
|
||||||
|
groupMap[key].fields.push(field);
|
||||||
|
});
|
||||||
|
return groups.filter((section) => section.fields.length > 0);
|
||||||
|
},
|
||||||
|
// 获取手艺人详情
|
||||||
|
getSyrDetail(id) {
|
||||||
request.post('/sj/syrdetail', {
|
request.post('/sj/syrdetail', {
|
||||||
id
|
id
|
||||||
}).then(res => {
|
}).then(res => {
|
||||||
|
|
@ -808,13 +931,33 @@
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.notice-text {
|
.notice-text {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
font-size: 26rpx;
|
font-size: 26rpx;
|
||||||
color: #666;
|
color: #666;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 底部按钮 */
|
.dynamic-notice-cont {
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dynamic-media-list {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dynamic-image,
|
||||||
|
.dynamic-video {
|
||||||
|
width: 160rpx;
|
||||||
|
height: 160rpx;
|
||||||
|
margin-right: 12rpx;
|
||||||
|
margin-bottom: 12rpx;
|
||||||
|
border-radius: 8rpx;
|
||||||
|
background-color: #F6F6F6;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 底部按钮 */
|
||||||
.bottom-bar {
|
.bottom-bar {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
left: 0;
|
left: 0;
|
||||||
|
|
@ -1012,4 +1155,4 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
/* #endif */
|
/* #endif */
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -228,8 +228,9 @@ const getOssConfig = async (type) => {
|
||||||
const uploadToOss = (file, ossConfig, name, uploadType) => {
|
const uploadToOss = (file, ossConfig, name, uploadType) => {
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
// #ifdef H5
|
let formData;
|
||||||
const formData = new FormData()
|
// #ifdef H5
|
||||||
|
formData = new FormData()
|
||||||
formData.append('name', name);
|
formData.append('name', name);
|
||||||
formData.append('policy', ossConfig.policy);
|
formData.append('policy', ossConfig.policy);
|
||||||
formData.append('OSSAccessKeyId', ossConfig.ossAccessKeyId);
|
formData.append('OSSAccessKeyId', ossConfig.ossAccessKeyId);
|
||||||
|
|
@ -247,15 +248,15 @@ const uploadToOss = (file, ossConfig, name, uploadType) => {
|
||||||
reject(new Error(`上传失败: ${res.statusCode}`))
|
reject(new Error(`上传失败: ${res.statusCode}`))
|
||||||
})
|
})
|
||||||
// #endif
|
// #endif
|
||||||
// #ifdef APP-PLUS || MP-WEIXIN
|
// #ifdef APP-PLUS || MP-WEIXIN
|
||||||
let formData = {};
|
formData = {};
|
||||||
formData.name = name;
|
formData.name = name;
|
||||||
formData.policy = ossConfig.policy;
|
formData.policy = ossConfig.policy;
|
||||||
formData.OSSAccessKeyId = ossConfig.ossAccessKeyId;
|
formData.OSSAccessKeyId = ossConfig.ossAccessKeyId;
|
||||||
formData.success_action_status = '200';
|
formData.success_action_status = '200';
|
||||||
formData.signature = ossConfig.signature;
|
formData.signature = ossConfig.signature;
|
||||||
formData.key = ossConfig.dir + name;
|
formData.key = ossConfig.dir + name;
|
||||||
formData.file = file;
|
formData.file = file;
|
||||||
|
|
||||||
let path;
|
let path;
|
||||||
if (uploadType == 'image') {
|
if (uploadType == 'image') {
|
||||||
|
|
@ -314,4 +315,4 @@ const uploadToOss = (file, ossConfig, name, uploadType) => {
|
||||||
}
|
}
|
||||||
export default {
|
export default {
|
||||||
handleUpload
|
handleUpload
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -148,8 +148,9 @@ const getOssConfig = async (type)=> {
|
||||||
const uploadToOss = (file, ossConfig, name)=> {
|
const uploadToOss = (file, ossConfig, name)=> {
|
||||||
console.log(ossConfig)
|
console.log(ossConfig)
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
// #ifdef H5
|
let formData;
|
||||||
const formData = new FormData()
|
// #ifdef H5
|
||||||
|
formData = new FormData()
|
||||||
formData.append('name',name);
|
formData.append('name',name);
|
||||||
formData.append('policy', ossConfig.policy);
|
formData.append('policy', ossConfig.policy);
|
||||||
formData.append('OSSAccessKeyId', ossConfig.ossAccessKeyId);
|
formData.append('OSSAccessKeyId', ossConfig.ossAccessKeyId);
|
||||||
|
|
@ -165,7 +166,7 @@ const uploadToOss = (file, ossConfig, name)=> {
|
||||||
})
|
})
|
||||||
// #endif
|
// #endif
|
||||||
// #ifdef APP-PLUS || MP-WEIXIN
|
// #ifdef APP-PLUS || MP-WEIXIN
|
||||||
let formData = {};
|
formData = {};
|
||||||
formData.name = name;
|
formData.name = name;
|
||||||
formData.policy = ossConfig.policy;
|
formData.policy = ossConfig.policy;
|
||||||
formData.OSSAccessKeyId = ossConfig.ossAccessKeyId;
|
formData.OSSAccessKeyId = ossConfig.ossAccessKeyId;
|
||||||
|
|
@ -277,4 +278,4 @@ const uploadToOss = (file, ossConfig, name)=> {
|
||||||
}
|
}
|
||||||
export default {
|
export default {
|
||||||
handleUpload
|
handleUpload
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue