Files
smt-used-car-h5/pages/index/publisheditPro.vue
T
2026-03-11 11:15:33 +08:00

1406 lines
33 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<view class="pages">
<view class="content">
<!-- 产品名称(带绑定功能) -->
<view class="cell">
<view>产品名称 <text class="red">*</text></view>
<view class="right right-bind">
<input class="inp" placeholder="请输入车辆的品牌 系列查找车型" v-model="carinfo.product_name" />
<view class="bind-btn" @tap.stop="onBindByName">绑定</view>
</view>
</view>
<!-- 绑定结果提示 -->
<view v-if="bindState.boundProduct" class="bind-tip">
<text class="bind-tip-text">已绑定:{{ bindState.boundProduct.name || bindState.boundProduct.product_name || bindState.boundProduct.title || '-' }}</text>
<text class="bind-tip-clear" @tap.stop="clearBind">清除</text>
</view>
<view v-else class="bind-tip">
<text>例:比亚迪海狮07DM</text>
</view>
<!-- 基本信息字段 -->
<view class="cell">
<view>车价(¥)</view>
<view class="right">
<input placeholder="请输入车价" v-model="carinfo.price" />
</view>
</view>
<view class="cell">
<view>车源地</view>
<view class="right">
<input placeholder="请输入车源地" v-model="carinfo.source_place" />
</view>
</view>
<view class="cell">
<view>上牌时间</view>
<view class="right">
<uni-datetime-picker
ref="borrowTimePicker"
type="date"
v-model="carinfo.plate_time"
placeholder="请选择时间"
:border="false"
:clear-icon="false"
@change="onBorrowTimeChange"
>
<view class="custom-datetime-input">
<text class="custom-datetime-text" :class="{ 'placeholder': !carinfo.plate_time }">
{{ carinfo.plate_time || '请选择时间' }}
</text>
<uni-icons type="calendar" size="16" color="#999"></uni-icons>
</view>
</uni-datetime-picker>
</view>
</view>
<view class="cell">
<view>排量</view>
<view class="right">
<input placeholder="请输入排量" v-model="carinfo.displacement" />
</view>
</view>
<view class="cell">
<view>马力</view>
<view class="right">
<input placeholder="请输入马力" v-model="carinfo.power" />
</view>
</view>
<view class="cell">
<view>里程(Km)</view>
<view class="right">
<input placeholder="请输入里程" v-model="carinfo.mileage" />
</view>
</view>
<view class="cell">
<view>车身颜色</view>
<view class="right">
<input placeholder="请输入车身颜色" v-model="carinfo.body_color" />
</view>
</view>
<!-- 产品图上传 -->
<view class="section-title">
<text class="title-text">产品图</text>
</view>
<view class="cont">
<view class="media-upload-wrapper">
<!-- #ifdef H5 -->
<view class="h5-picker-grid">
<view class="h5-thumb" v-for="(u, idx) in imagesurl" :key="idx">
<image :src="u" mode="aspectFill" class="h5-thumb-img"></image>
<view class="h5-thumb-del" @tap.stop="delfn({ index: idx })">删除</view>
</view>
<view class="upload-icon-wrapper" @tap="pickImagesH5">
<image src="/static/image/img.png" class="addimg"></image>
</view>
</view>
<!-- #endif -->
<!-- #ifndef H5 -->
<uni-file-picker ref="mediaPicker" :limit="maxMedia" fileMediatype="image" @select="selectMedia" @delete="delfn" >
<view class="upload-icon-wrapper">
<image src="/static/image/img.png" class="addimg"></image>
</view>
</uni-file-picker>
<!-- #endif -->
</view>
</view>
<!-- 车况介绍 -->
<view class="section-title">
<text class="title-text">车况介绍</text>
</view>
<view class="cont">
<view class="">
<textarea auto-height v-model="carinfo.condition_desc" placeholder="请输入车况介绍"/>
</view>
</view>
<!-- ========== 视频上传(专业发布专属功能)========== -->
<view class="section-title">
<text class="title-text">视频介绍</text>
</view>
<!-- 已上传视频列表 -->
<view class="video-list" v-if="videoList.length > 0">
<view class="video-item" v-for="(video, index) in videoList" :key="index">
<video
:src="video.url"
class="video-preview"
:show-center-play-btn="true"
:controls="true"
></video>
<view class="video-delete" @tap="deleteVideo(index)">
<uni-icons type="clear" size="20" color="#fff"></uni-icons>
</view>
</view>
</view>
<!-- 上传视频按钮 -->
<view class="video-upload-btn" @tap="chooseVideo">
<uni-icons type="videocam" size="40" color="#999"></uni-icons>
<text class="upload-text">点击上传视频</text>
</view>
<!-- 附件上传 -->
<view class="section-title">
<text class="title-text">附件</text>
</view>
<view class="cont">
<view class="media-upload-wrapper">
<uni-file-picker ref="mediaPicker1" limit="20" fileMediatype="all" @select="selectMedia1" @delete="delfn1" >
<view class="upload-icon-wrapper">
<image src="/static/image/photo.png" mode="" class="addimg"></image>
</view>
</uni-file-picker>
</view>
</view>
<!-- ========== FAQ 问题答案设置(专业发布专属功能)========== -->
<view class="faq-section">
<view class="section-title">
<text class="title-text">FAQ</text>
</view>
<!-- FAQ 列表 -->
<view class="faq-list">
<view
class="faq-item"
v-for="(faq, index) in faqList"
:key="index"
>
<view class="faq-header">
<text class="faq-index">Q{{ index + 1 }}</text>
<view class="faq-actions">
<text class="faq-delete" @tap="deleteFaq(index)" v-if="faqList.length > 1">删除</text>
</view>
</view>
<view class="faq-input-group">
<view class="faq-label">问题</view>
<input
class="faq-input"
v-model="faq.question"
placeholder="请输入问题,如:车辆是否有事故?"
/>
</view>
<view class="faq-input-group">
<view class="faq-label">答案</view>
<textarea
class="faq-textarea"
v-model="faq.answer"
placeholder="请输入答案"
auto-height
/>
</view>
</view>
</view>
<!-- 添加 FAQ 按钮 -->
<view class="add-faq-btn" @tap="addFaq">
<uni-icons type="plus" size="18" color="#2E6CF6"></uni-icons>
<text class="add-faq-text">添加问题</text>
</view>
</view>
</view>
<!-- 底部提交按钮 -->
<view class="zw">
<button class="btn" @click="updateSubmit">保存修改</button>
</view>
<!-- 绑定产品弹框 -->
<view v-if="bindState.showDialog" class="bind-mask" @tap="closeBindDialog">
<view class="bind-dialog" @tap.stop>
<view class="bind-dialog-head">
<text class="bind-dialog-title">选择要绑定的产品</text>
<text class="bind-dialog-close" @tap="closeBindDialog">×</text>
</view>
<!-- 弹框内搜索 -->
<view class="bind-search" v-if="bindState.list && bindState.list.length">
<input class="bind-search-input" v-model="bindState.keyword" placeholder="搜索产品名称" placeholder-class="bind-search-placeholder" />
<text v-if="bindState.keyword" class="bind-search-clear" @tap.stop="bindState.keyword = ''">清除</text>
</view>
<scroll-view scroll-y class="bind-dialog-body">
<view v-if="bindState.loading" class="bind-loading">加载中…</view>
<view v-else-if="!bindState.list.length" class="bind-empty">未找到匹配的产品</view>
<view v-else-if="!filteredBindList.length" class="bind-empty">未找到匹配结果</view>
<view v-else>
<view
class="bind-item"
:class="{ active: bindState.selectedId === getBindItemId(it, idx) }"
v-for="(it, idx) in filteredBindList"
:key="getBindItemId(it, idx)"
@tap="selectBind(it, idx)"
>
<view class="bind-item-main">
<text class="bind-item-title">{{ it.name || it.product_name || it.title || ('产品 ' + (idx + 1)) }}</text>
<text class="bind-item-sub">{{ formatBindSub(it) }}</text>
</view>
<view class="bind-item-check">
<view class="check-dot" v-if="bindState.selectedId === getBindItemId(it, idx)"></view>
</view>
</view>
</view>
</scroll-view>
<view class="bind-dialog-foot">
<view class="btn btn-ghost" @tap="closeBindDialog">取消</view>
<view class="btn btn-primary" :class="{ disabled: !bindState.selectedId }" @tap="confirmBind">确认绑定</view>
</view>
</view>
</view>
<!-- #ifdef H5 -->
<CustomTabBar class="h5-custom-tabbar" />
<!-- #endif -->
</view>
</template>
<script>
import { request, showToast } from '@/api/apiT.js'
import CustomTabBar from '@/custom-tab-bar/index.vue'
export default {
components: {
CustomTabBar
},
data() {
return {
id: '', // 车辆详情ID
maxMedia: 20,
maxVideos: 3, // 最多上传3个视频
// 绑定产品状态
bindState: {
showDialog: false,
loading: false,
list: [],
selectedId: '',
boundProduct: null,
keyword: ''
},
// 图片相关
imagesurl: [], // 用于显示的图片URL列表(可能是带签名的完整URL)
// 视频相关
videoList: [], // { url: '', name: '' }
// FAQ 列表
faqList: [
{ question: '', answer: '' }
],
// 车辆信息
carinfo: {
id: '',
product_name: '',
vin: '',
price: '',
source_place: '',
plate_place: '',
plate_time: '',
displacement: '',
power: '',
body_color: '',
images: [], // 存储图片文件名(用于提交)
condition_desc: '',
mileage: '',
attachment: [],
// 绑定产品的相关信息
crawl_title: '',
crawl_brand: '',
crawl_series: '',
crawl_year: '',
crawl_msrp: '',
crawl_car_id: '',
crawl_brand_id: '',
crawl_series_id: '',
// 视频文件名列表
videos: [],
// FAQ 数据(提交时转为JSON字符串)
faq_data: []
}
}
},
onLoad(options) {
this.id = options?.id || ''
if (this.id) {
this.getDetail()
} else {
uni.showToast({
title: '缺少车辆ID',
icon: 'none'
})
setTimeout(() => {
uni.navigateBack()
}, 1500)
}
},
computed: {
// 弹框内搜索过滤
filteredBindList() {
const list = Array.isArray(this.bindState.list) ? this.bindState.list : []
const kw = String(this.bindState.keyword || '').trim().toLowerCase()
if (!kw) return list
return list.filter((it, idx) => {
const id = String(it.id || it.product_id || it.code || idx).toLowerCase()
const name = String(it.name || it.product_name || it.title || '').toLowerCase()
const sub = String(this.formatBindSub(it) || '').toLowerCase()
return id.includes(kw) || name.includes(kw) || sub.includes(kw)
})
}
},
methods: {
onBorrowTimeChange(e) {
this.carinfo.plate_time = e
},
// ========== 获取车辆详情(数据回显)==========
getDetail() {
uni.showLoading({ title: '加载中...' })
request({
url: 'common/usedcardetail/show',
method: 'get',
data: { id: this.id }
}).then(res => {
uni.hideLoading()
if (res.data.code !== 200) {
uni.showToast({
title: res.data.message || '获取详情失败',
icon: 'none'
})
return
}
const data = res.data.data || {}
this.initFormData(data)
}).catch(err => {
uni.hideLoading()
console.error('[getDetail error]', err)
uni.showToast({
title: '获取详情失败',
icon: 'none'
})
})
},
// 初始化表单数据
initFormData(data) {
// 基本信息
this.carinfo = {
...this.carinfo,
id: data.id,
product_name: data.product_name || '',
vin: data.vin || '',
price: data.price || '',
source_place: data.source_place || '',
plate_place: data.plate_place || '',
plate_time: data.plate_time || '',
displacement: data.displacement || '',
power: data.power || '',
body_color: data.body_color || '',
mileage: data.mileage || '',
condition_desc: data.condition_desc || '',
// 绑定产品信息
crawl_title: data.crawl_title || '',
crawl_brand: data.crawl_brand || '',
crawl_series: data.crawl_series || '',
crawl_year: data.crawl_year || '',
crawl_msrp: data.crawl_msrp || '',
crawl_car_id: data.crawl_car_id || '',
crawl_brand_id: data.crawl_brand_id || '',
crawl_series_id: data.crawl_series_id || '',
// 图片文件名列表(用于提交)
images: data.images ? String(data.images).split(',').filter(Boolean) : [],
// 视频文件名列表(用于提交)
videos: data.intro_video ? String(data.intro_video).split(',').filter(Boolean) : []
}
// 图片回显:优先使用 images_pull(带签名URL)
if (Array.isArray(data.images_pull) && data.images_pull.length) {
this.imagesurl = data.images_pull
} else if (this.carinfo.images.length) {
// 如果没有 images_pull,使用 images 并拼接基础URL
this.imagesurl = this.carinfo.images.map(img => {
// 如果已经是完整URL,直接使用
if (img.startsWith('http')) return img
// 否则拼接基础URL(根据实际情况调整)
return img
})
}
// 视频回显:优先使用 video_pull(带签名URL)
if (Array.isArray(data.video_pull) && data.video_pull.length) {
this.videoList = data.video_pull.map((url, idx) => ({
url: url,
name: this.carinfo.videos[idx] || `video_${idx + 1}`
}))
} else if (this.carinfo.videos.length) {
this.videoList = this.carinfo.videos.map((name, idx) => ({
url: name.startsWith('http') ? name : name,
name: name
}))
}
// FAQ 回显
if (Array.isArray(data.faqs) && data.faqs.length) {
this.faqList = data.faqs.map(faq => ({
question: faq.question || '',
answer: faq.answer || ''
}))
} else {
this.faqList = [{ question: '', answer: '' }]
}
// 附件回显
if (Array.isArray(data.attachment_pull) && data.attachment_pull.length) {
this.carinfo.attachment = data.attachment_pull.map(att => ({
name: att.name || att.original_name || '',
src: att.src || att.file_name || ''
}))
} else if (data.attachment) {
try {
const parsed = typeof data.attachment === 'string' ? JSON.parse(data.attachment) : data.attachment
this.carinfo.attachment = Array.isArray(parsed) ? parsed : []
} catch (e) {
this.carinfo.attachment = []
}
}
// 已绑定产品提示
if (data.crawl_car_id || data.crawl_title) {
this.bindState.boundProduct = {
car_id: data.crawl_car_id,
title: data.crawl_title,
brand_name: data.crawl_brand,
series_name: data.crawl_series,
year: data.crawl_year,
msrp: data.crawl_msrp,
brand_id: data.crawl_brand_id,
series_id: data.crawl_series_id
}
}
},
// ========== 绑定产品相关方法 ==========
onBindByName() {
const name = String(this.carinfo.product_name || '').trim()
if (!name) {
return uni.showToast({ title: '请先输入产品名称', icon: 'none' })
}
this.bindState.showDialog = true
this.bindState.loading = true
this.bindState.list = []
this.bindState.selectedId = ''
this.bindState.keyword = ''
request({
url: 'common/usedcar/crawl',
method: 'get',
data: { keyword: name }
}).then(res => {
if(res.data.code == 200){
this.bindState.list = res.data.data.model
} else {
uni.showToast({ title: res.data.message, icon: 'none' })
}
}).catch(() => {
uni.showToast({ title: '拉取产品失败,请稍后重试', icon: 'none' })
}).finally(() => {
this.bindState.loading = false
})
},
closeBindDialog() {
this.bindState.showDialog = false
this.bindState.keyword = ''
},
getBindItemId(it, idx) {
return String(it.id || it.product_id || it.requisition_item_id || it.code || idx)
},
selectBind(it, idx) {
this.bindState.selectedId = this.getBindItemId(it, idx)
this.bindState._selected = it
},
formatBindSub(it) {
const parts = []
if (it.sku || it.code) parts.push('编码:' + (it.sku || it.code))
if (it.category_name) parts.push('分类:' + it.category_name)
if (it.price) parts.push('价格:' + it.price)
return parts.join(' · ')
},
confirmBind() {
if (!this.bindState.selectedId || !this.bindState._selected) return
const it = this.bindState._selected
this.bindState.boundProduct = it
this.carinfo.crawl_title = it.title
this.carinfo.crawl_brand = it.brand_name
this.carinfo.crawl_series = it.series_name
this.carinfo.crawl_year = it.year
this.carinfo.crawl_msrp = it.msrp
this.carinfo.crawl_car_id = it.car_id
this.carinfo.crawl_brand_id = it.brand_id
this.carinfo.crawl_series_id = it.series_id
console.log(it)
this.closeBindDialog()
uni.showToast({ title: '绑定成功', icon: 'none' })
},
clearBind() {
this.carinfo.crawl_title = ''
this.carinfo.crawl_brand = ''
this.carinfo.crawl_series = ''
this.carinfo.crawl_year = ''
this.carinfo.crawl_msrp = ''
this.carinfo.crawl_car_id = ''
this.carinfo.crawl_brand_id = ''
this.carinfo.crawl_series_id = ''
this.bindState.boundProduct = null
uni.showToast({ title: '已清除绑定', icon: 'none' })
},
// ========== 图片上传相关方法 ==========
uploadfn(imgUrl, scene) {
uni.showLoading({
title: '上传中'
});
return new Promise((resolve, reject) => {
uni.uploadFile({
url: 'https://usedcar.dev.hnxinyouhe.com/upload?scene=' + scene,
filePath: imgUrl,
name: 'file',
header: {
'Fdesk-Auth-Token': uni.getStorageSync('token'),
'Fdesk-Login-Type': 'feishu',
'Fdesk-System-Code': 'fdesk'
},
success: (uploadFileRes) => {
const res = JSON.parse(uploadFileRes.data || '{}')
uni.hideLoading();
uni.showToast({
title: res.message,
icon: 'none'
})
if(res && res.data && res.data.file_name){
resolve(res.data.file_name)
} else {
reject('上传失败')
}
},
fail: (err) => reject(err)
});
})
},
uploadfndile(imgUrl, scene) {
uni.showLoading({
title: '上传中'
});
return new Promise((resolve, reject) => {
uni.uploadFile({
url: 'https://usedcar.dev.hnxinyouhe.com/upload?scene=' + scene,
filePath: imgUrl,
name: 'file',
header: {
'Fdesk-Auth-Token': uni.getStorageSync('token'),
'Fdesk-Login-Type': 'feishu',
'Fdesk-System-Code': 'fdesk'
},
success: (uploadFileRes) => {
const res = JSON.parse(uploadFileRes.data || '{}')
uni.hideLoading();
uni.showToast({
title: res.message,
icon: 'none'
})
if(res && res.data && res.data.file_name){
resolve(res.data)
} else {
reject(res)
}
},
fail: (err) => reject(err)
});
})
},
pickImagesH5() {
uni.chooseFile({
count: 100,
type: 'image',
success: (res) => {
const paths = (res.tempFilePaths || (res.tempFiles || []).map(it => it.path)).filter(Boolean)
if (!paths.length) return
Promise.all(paths.map(p => this.uploadfndile(p, 'usedcarimg'))).then(urls => {
urls.forEach(url => {
this.imagesurl.push(url.file_url)
this.carinfo.images.push(url.file_name)
})
}).catch((e) => {
uni.showToast({ title: e.message, icon: 'none' })
})
}
})
},
selectMedia(e) {
const allowCount = this.maxMedia - this.carinfo.images.length
const tempFilePaths = e.tempFilePaths || (e.tempFiles || []).map(item => item.path || item.url)
const paths = tempFilePaths.slice(0, allowCount)
if(paths.length === 0){
return
}
Promise.all(paths.map(p => this.uploadfn(p, 'usedcarimg'))).then(urls => {
urls.forEach(url => {
this.carinfo.images.push(url)
this.imagesurl.push(url) // H5环境可能需要调整
})
}).catch(() => {
uni.showToast({
title: '图片上传失败,请重试',
icon: 'none'
})
})
},
delfn(e) {
this.imagesurl.splice(e.index, 1)
this.carinfo.images.splice(e.index, 1)
},
selectMedia1(e) {
const allowCount = this.maxMedia - this.carinfo.attachment.length
const tempFilePaths = e.tempFilePaths || (e.tempFiles || []).map(item => item.path || item.url)
const paths = tempFilePaths.slice(0, allowCount)
if(paths.length === 0){
return
}
Promise.all(paths.map(p => this.uploadfndile(p, 'usedcarfile'))).then(urls => {
urls.forEach(url => {
const obj = {}
obj.name = url.original_name
obj.src = url.file_name
this.carinfo.attachment.push(obj)
})
}).catch(() => {
uni.showToast({
title: '图片上传失败,请重试',
icon: 'none'
})
})
},
delfn1(e) {
this.carinfo.attachment.splice(e.index, 1)
},
// ========== 视频上传相关方法(专业发布专属)==========
chooseVideo() {
// 检查是否已达到最大视频数量
if (this.videoList.length >= this.maxVideos) {
return uni.showToast({
title: `最多只能上传${this.maxVideos}个视频`,
icon: 'none'
})
}
// #ifdef H5
// H5 端使用 chooseFile 选择视频
uni.chooseFile({
count: this.maxVideos - this.videoList.length,
type: 'video',
success: (res) => {
const files = res.tempFiles || []
if (!files.length) return
// 检查文件大小
const maxSize = 100 * 1024 * 1024 // 100MB
for (let file of files) {
if (file.size > maxSize) {
uni.showToast({
title: '视频大小不能超过100MB',
icon: 'none'
})
return
}
}
// 上传视频
files.forEach(file => {
this.uploadVideo(file.path)
})
}
})
// #endif
// #ifndef H5
// 小程序/App 端使用 chooseVideo
uni.chooseVideo({
sourceType: ['camera', 'album'],
maxDuration: 300, // 最大5分钟
success: (res) => {
this.uploadVideo(res.tempFilePath)
}
})
// #endif
},
uploadVideo(filePath) {
uni.showLoading({ title: '视频上传中...' })
this.uploadfndile(filePath, 'usedcarvideo').then(res => {
this.videoList.push({
url: res.file_url,
name: res.file_name
})
this.carinfo.videos.push(res.file_name)
uni.hideLoading()
uni.showToast({
title: '视频上传成功',
icon: 'success'
})
}).catch(err => {
uni.hideLoading()
uni.showToast({
title: '视频上传失败',
icon: 'none'
})
})
},
deleteVideo(index) {
uni.showModal({
title: '提示',
content: '确定删除这个视频吗?',
success: (res) => {
if (res.confirm) {
this.videoList.splice(index, 1)
this.carinfo.videos.splice(index, 1)
}
}
})
},
// ========== FAQ 相关方法(专业发布专属)==========
addFaq() {
this.faqList.push({
question: '',
answer: ''
})
},
deleteFaq(index) {
if (this.faqList.length <= 1) {
return uni.showToast({
title: '至少保留一个问题',
icon: 'none'
})
}
this.faqList.splice(index, 1)
},
validateFaq() {
// 过滤掉空的问题,返回有效的 FAQ 数组
const validFaqs = this.faqList.filter(faq =>
faq.question && faq.question.trim() &&
faq.answer && faq.answer.trim()
)
// 返回有效的 FAQ 数组(可能为空数组)
return validFaqs
},
// ========== 提交更新 ==========
updateSubmit() {
// 验证必填项
if (!this.carinfo.product_name) {
return uni.showToast({ title: '请输入产品名称', icon: 'none' })
}
uni.showLoading({ title: '提交中...' })
// 获取 FAQ 数据(允许为空)
const validFaqs = this.validateFaq()
const payload = {
// 基础字段
id: this.carinfo.id,
product_name: this.carinfo.product_name,
vin: this.carinfo.vin,
price: this.carinfo.price,
source_place: this.carinfo.source_place,
plate_time: this.carinfo.plate_time,
displacement: this.carinfo.displacement,
power: this.carinfo.power,
body_color: this.carinfo.body_color,
mileage: this.carinfo.mileage,
condition_desc: this.carinfo.condition_desc,
// 绑定产品相关字段
crawl_title: this.carinfo.crawl_title,
crawl_brand: this.carinfo.crawl_brand,
crawl_series: this.carinfo.crawl_series,
crawl_year: this.carinfo.crawl_year,
crawl_msrp: this.carinfo.crawl_msrp,
crawl_car_id: this.carinfo.crawl_car_id,
crawl_brand_id: this.carinfo.crawl_brand_id,
crawl_series_id: this.carinfo.crawl_series_id,
// 图片数组 => "url1,url2,url3"
images: (this.carinfo.images || [])
.map(s => String(s || '').trim())
.filter(Boolean)
.join(','),
// 视频数组 => "url1,url2,url3" (后端字段名: intro_video)
intro_video: (this.carinfo.videos || [])
.map(s => String(s || '').trim())
.filter(Boolean)
.join(','),
// 附件数据(转为JSON字符串)
attachment: JSON.stringify(this.carinfo.attachment || []),
// FAQ 数据:直接传数组格式 [{"question": "", "answer": ""}]
faqs: validFaqs,
}
// 移除空值字段
Object.keys(payload).forEach(key => {
if (payload[key] === '' || payload[key] === null || payload[key] === undefined) {
delete payload[key]
}
})
request({
url: 'common/usedcardetail/update',
method: 'post',
data: payload
}).then(res => {
uni.hideLoading()
if(res.data.code == 200){
uni.showToast({
title: res.data.message || '保存成功',
icon: 'success'
})
setTimeout(() => {
uni.navigateBack()
}, 1500)
} else {
uni.showToast({
title: res.data.message || '保存失败,请重试',
icon: 'none'
})
}
}).catch(err => {
uni.hideLoading()
console.error('[update error]', err)
uni.showToast({
title: err.data?.message || '保存失败,请重试',
icon: 'none'
})
})
}
}
}
</script>
<style lang="scss" scoped>
.pages {
max-width: 650px;
}
/* #ifdef H5 */
.pages {
padding-bottom: calc(100rpx + env(safe-area-inset-bottom));
}
/* #endif */
.red {
color: red;
}
.content {
background: #FFFFFF;
border-radius: 10px;
}
.zw {
height: 120px;
}
.cont {
position: relative;
padding: 18px 20px;
align-items: center;
font-weight: 500;
}
.conts {
margin-bottom: 20rpx;
}
.media-upload-wrapper {
display: flex;
flex-wrap: wrap;
gap: 16rpx;
}
.cell {
position: relative;
display: flex;
justify-content: space-between;
padding: 14px 10px;
align-items: center;
font-weight: 500;
}
.cell::after, .cont::after {
content: '';
position: absolute;
width: 200%;
height: 200%;
top: 0;
left: 0;
transform-origin: 0 0;
border-width: 0;
border-bottom-width: 1px;
border-style: solid;
transform: scale(0.5, 0.5);
border-radius: 1px;
box-sizing: border-box;
pointer-events: none;
border-color: #C3C3C3;
}
.right {
text-align: right;
}
.btn {
background-color: #3F435B;
color: white;
border-radius: 50px;
width: 92%;
margin-bottom: 40px;
margin: 15px 4%;
}
.addimg {
width: 215rpx;
height: 215rpx;
}
/* 产品名称 + 绑定按钮 */
.right-bind {
flex-direction: row;
align-items: center;
display: flex;
width: 74%;
}
.right-bind .inp {
flex: 1;
font-size: 28rpx;
}
.bind-btn {
margin-left: 16rpx;
padding: 0 18rpx;
height: 56rpx;
line-height: 56rpx;
border-radius: 12rpx;
background: #2E6CF6;
color: #fff;
font-size: 26rpx;
}
.bind-tip {
padding: 12rpx 18rpx;
background: #F3F6FF;
display: flex;
align-items: center;
justify-content: space-between;
font-size: 24rpx;
color: #999;
}
.bind-tip-text {
color: #2B3A55;
font-size: 24rpx;
}
.bind-tip-clear {
color: #2E6CF6;
font-size: 24rpx;
}
/* 弹框搜索 */
.bind-search {
margin: 5px 24rpx 16rpx;
padding: 12rpx 18rpx;
border-radius: 12rpx;
background: #F3F6FF;
display: flex;
align-items: center;
justify-content: space-between;
}
.bind-search-input {
flex: 1;
font-size: 26rpx;
color: #333;
}
.bind-search-clear {
margin-left: 16rpx;
font-size: 24rpx;
color: #2E6CF6;
}
/* 绑定弹框 */
.bind-mask {
position: fixed;
left: 0; right: 0; top: 0; bottom: 0;
background: rgba(0,0,0,0.45);
z-index: 999;
display: flex;
align-items: center;
justify-content: center;
padding: 40rpx;
}
.bind-dialog {
width: 100%;
max-width: 680rpx;
background: #fff;
border-radius: 20rpx;
overflow: hidden;
}
.bind-dialog-head {
height: 96rpx;
padding: 0 24rpx;
display: flex;
align-items: center;
justify-content: space-between;
border-bottom: 1px solid #f0f0f0;
}
.bind-dialog-title {
font-size: 30rpx;
font-weight: 600;
color: #111;
}
.bind-dialog-close {
font-size: 40rpx;
color: #999;
line-height: 1;
}
.bind-dialog-body {
max-height: 720rpx;
padding: 12rpx 0;
}
.bind-loading, .bind-empty {
padding: 40rpx 24rpx;
color: #888;
text-align: center;
font-size: 26rpx;
}
.bind-item {
padding: 18rpx 24rpx;
display: flex;
align-items: center;
justify-content: space-between;
}
.bind-item.active {
background: #F3F6FF;
}
.bind-item-main {
flex: 1;
padding-right: 16rpx;
}
.bind-item-title {
font-size: 28rpx;
font-weight: 600;
color: #222;
display: block;
}
.bind-item-sub {
margin-top: 6rpx;
font-size: 24rpx;
color: #777;
display: block;
}
.bind-item-check {
width: 44rpx;
height: 44rpx;
border-radius: 50%;
border: 2rpx solid #2E6CF6;
display: flex;
align-items: center;
justify-content: center;
}
.check-dot {
width: 22rpx;
height: 22rpx;
border-radius: 50%;
background: #2E6CF6;
}
.bind-dialog-foot {
padding: 16rpx 24rpx 22rpx;
display: flex;
gap: 16rpx;
border-top: 1px solid #f0f0f0;
}
.bind-dialog-foot .btn{
text-align: center;
padding: 6px 0;
}
.btn-ghost {
background: #F5F6F8;
color: #333;
}
.btn-primary {
background: #2E6CF6;
color: #fff;
}
.btn-primary.disabled {
opacity: .45;
}
/* H5 自定义选择器 */
.h5-picker-grid {
display: flex;
flex-wrap: wrap;
gap: 16rpx;
align-items: flex-start;
}
.h5-thumb {
width: 210rpx;
height: 210rpx;
border-radius: 12rpx;
overflow: hidden;
position: relative;
background: #f6f6f6;
}
.h5-thumb-img {
width: 100%;
height: 100%;
}
.h5-thumb-del {
position: absolute;
right: 8rpx;
top: 8rpx;
background: rgba(0,0,0,0.45);
color: #fff;
font-size: 22rpx;
padding: 6rpx 10rpx;
border-radius: 999rpx;
}
/* ========== 视频上传区域样式 ========== */
.section-title {
padding: 20rpx 24rpx 12rpx;
background: #F8F9FA;
border-bottom: 1px solid #eee;
.title-text {
display: block;
font-size: 30rpx;
font-weight: 600;
color: #333;
margin-bottom: 8rpx;
}
.title-desc {
font-size: 24rpx;
color: #999;
}
}
.video-list {
display: flex;
flex-wrap: wrap;
gap: 20rpx;
padding: 20rpx 24rpx;
}
.video-item {
position: relative;
width: 320rpx;
height: 240rpx;
border-radius: 12rpx;
overflow: hidden;
background: #000;
}
.video-preview {
width: 100%;
height: 100%;
}
.video-delete {
position: absolute;
top: 12rpx;
right: 12rpx;
width: 48rpx;
height: 48rpx;
border-radius: 50%;
background: rgba(0, 0, 0, 0.6);
display: flex;
align-items: center;
justify-content: center;
}
.video-upload-btn {
margin: 20rpx 24rpx;
padding: 40rpx 24rpx;
border: 2rpx dashed #ddd;
border-radius: 12rpx;
background: #fafafa;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 12rpx;
.upload-text {
font-size: 28rpx;
color: #333;
}
.upload-tip {
font-size: 24rpx;
color: #999;
}
}
/* ========== FAQ 区域样式 ========== */
.faq-section {
background: #fff;
margin-top: 20rpx;
padding-bottom: 40rpx;
}
.faq-list {
padding: 0 24rpx;
}
.faq-item {
padding: 24rpx 0;
margin-bottom: 20rpx;
border-radius: 12rpx;
}
.faq-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 20rpx;
}
.faq-index {
display: inline-block;
padding: 8rpx 20rpx;
border-radius: 20rpx;
background: #2E6CF6;
color: #fff;
font-size: 24rpx;
font-weight: 600;
}
.faq-delete {
color: #ff4d4f;
font-size: 26rpx;
}
.faq-input-group {
margin-bottom: 20rpx;
&:last-child {
margin-bottom: 0;
}
}
.faq-label {
font-size: 26rpx;
color: #666;
margin-bottom: 12rpx;
}
.faq-input {
padding: 16rpx 12rpx;
border-radius: 8rpx;
background: #fff;
border: 1px solid #e0e0e0;
font-size: 28rpx;
color: #333;
}
.faq-textarea {
width: auto;
min-height: 120rpx;
padding: 16rpx 12rpx;
border-radius: 8rpx;
background: #fff;
border: 1px solid #e0e0e0;
font-size: 28rpx;
color: #333;
}
.add-faq-btn {
margin: 0 24rpx;
padding: 24rpx;
border-radius: 12rpx;
background: #F3F6FF;
border: 1px dashed #2E6CF6;
display: flex;
align-items: center;
justify-content: center;
gap: 8rpx;
.add-faq-text {
font-size: 28rpx;
color: #2E6CF6;
}
}
.custom-datetime-input {
display: flex;
align-items: center;
gap: 8rpx;
}
.custom-datetime-text.placeholder {
color: #999;
}
</style>