Files
smt-used-car-h5/pages/index/publisheditPro.vue
T

1402 lines
33 KiB
Vue
Raw Normal View History

2026-03-11 11:15:33 +08:00
<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 问题答案设置(专业发布专属功能)========== -->
2026-03-12 16:47:27 +08:00
<!-- <view class="faq-section">
2026-03-11 11:15:33 +08:00
<view class="section-title">
<text class="title-text">FAQ</text>
</view>
<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>
<view class="add-faq-btn" @tap="addFaq">
<uni-icons type="plus" size="18" color="#2E6CF6"></uni-icons>
<text class="add-faq-text">添加问题</text>
</view>
2026-03-12 16:47:27 +08:00
</view> -->
2026-03-11 11:15:33 +08:00
</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({
2026-03-12 16:47:27 +08:00
url: 'https://userdcar-server.hnxinyouhe.com/upload?scene=' + scene,
2026-03-11 11:15:33 +08:00
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({
2026-03-12 16:47:27 +08:00
url: 'https://userdcar-server.hnxinyouhe.com/upload?scene=' + scene,
2026-03-11 11:15:33 +08:00
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>