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

840 lines
18 KiB
Vue
Raw Normal View History

2026-03-11 11:15:33 +08:00
<template>
<view class="page">
<view class="content">
<!-- 基础信息卡片 -->
<view class="card pro-card">
<view class="heard">
<view class="">
编号:{{detail.code?detail.code:'~'}}
</view>
<!-- <view class="pro-mark">
<text class="pro-mark-text">专业发布</text>
</view>
<view>
<u-icon @click="openPriceDialog()" name="download" size="25" />
</view> -->
</view>
<view class="title-row">
<text class="name">{{ detail.product_name }}</text>
</view>
<view class="kv">
<view class="kv-item">
<text class="k">车价(¥)</text>
<text class="v">{{ detail.price }}</text>
</view>
<view class="kv-item">
<text class="k">车源地</text>
<text class="v">{{ detail.source_place}}</text>
</view>
<view class="kv-item">
<text class="k">上牌时间</text>
<text class="v">{{ detail.plate_time }}</text>
</view>
<view class="kv-item">
<text class="k">排量</text>
<text class="v">{{ detail.displacement }}</text>
</view>
<view class="kv-item">
<text class="k">里程</text>
<text class="v">{{ detail.mileage }}<text v-if="detail.mileage">Km</text></text>
</view>
<view class="kv-item">
<text class="k">马力</text>
<text class="v">{{ detail.power }}</text>
</view>
<view class="kv-item">
<text class="k">车身颜色</text>
<text class="v">{{ detail.body_color }}</text>
</view>
<view class="kv-item">
<text class="k">创建时间</text>
<text class="v">{{ detail.created_at }}</text>
</view>
</view>
</view>
<!-- 车况介绍 -->
<view class="card">
<view class="section-title">车况介绍</view>
<text class="desc" v-if="detail.condition_desc!=''">{{ detail.condition_desc }}</text>
<view class="noneview" v-else>
<img src="/static/image/none.png" alt="">
</view>
</view>
<!-- 视频区域(专业发布特有) -->
<view class="card video-card" v-if="videoList.length > 0">
<view class="section-title">
<u-icon name="video" size="18" color="#2E6CF6"></u-icon>
<text class="title-text">产品视频</text>
<text class="video-count">({{ videoList.length }}个)</text>
</view>
<view class="video-grid">
<view
class="video-item"
v-for="(video, index) in videoList"
:key="index"
@click="playVideo(video)"
>
<video
class="video-player"
:src="video"
:poster="video + '?vframe/jpg/offset/1'"
controls
show-center-play-btn
object-fit="cover"
></video>
</view>
</view>
</view>
<!-- FAQ 问答区域(专业发布特有) -->
2026-03-12 16:47:27 +08:00
<!-- <view class="card faq-card" v-if="faqList.length > 0">
2026-03-11 11:15:33 +08:00
<view class="section-title">
<u-icon name="question-circle" size="18" color="#FF972F"></u-icon>
<text class="title-text">常见问题 FAQ</text>
<text class="faq-count">({{ faqList.length }}组)</text>
</view>
<view class="faq-list">
<view class="faq-item" v-for="(faq, index) in faqList" :key="index">
<view class="faq-question">
<text class="faq-label">问:</text>
<text class="faq-text">{{ faq.question }}</text>
</view>
<view class="faq-answer">
<text class="faq-label">答:</text>
<text class="faq-text">{{ faq.answer }}</text>
</view>
</view>
</view>
2026-03-12 16:47:27 +08:00
</view> -->
2026-03-11 11:15:33 +08:00
<!-- 产品图 -->
<view class="hero">
<view class="section-title">产品图</view>
<view class="img-grid">
<image
v-for="(src, i) in detail.images_pull"
:key="i"
class="grid-img"
:src="src"
mode="aspectFill"
@click="previewImages(i)"
/>
</view>
</view>
<!-- 附件 -->
<view class="hero">
<view class="section-title">附件</view>
<view v-if="detail.attachment_pull.length>0">
<view v-for="(file, i) in detail.attachment_pull" :key="i" class="fujian" @click="reviewFile(file)">
<p>{{file.name}}</p>
<view>查看</view>
</view>
</view>
<view class="noneview" v-else>
<img src="/static/image/none.png" alt="">
</view>
</view>
</view>
<!-- 价格设置弹框 -->
<uni-popup ref="inputDialog" type="dialog">
<view class="price-dialog">
<view class="pd-title">设置价格</view>
<view class="pd-row">
<view class="pd-label">贸易条款</view>
<view class="pd-price-line">
<uni-data-select
v-model="priceForm.trade_term"
class="tradeTerm"
:localdata="tradeTermOptions"
:clear="false"
/>
</view>
</view>
<view class="pd-row">
<view class="pd-label">价格</view>
<view class="pd-price-line">
<view class="pd-input-suffix-wrap">
<input
class="pd-input pd-input-price pd-input-with-suffix"
v-model="priceForm.price"
type="number"
inputmode="decimal"
placeholder="请输入数字"
@input="onPriceInput"
/>
<view class="pd-currency-suffix">
<uni-data-select
v-model="priceForm.currency"
:localdata="currencyOptions"
:clear="false"
/>
</view>
</view>
</view>
</view>
<!-- 语言单选 -->
<view class="pd-row">
<view class="pd-label">语言</view>
<radio-group class="pd-radio-group" @change="onLangChange">
<label class="pd-radio" v-for="l in langOptions" :key="l.value">
<radio :value="l.value" :checked="priceForm.lang === l.value" />
<text class="pd-radio-text">{{ l.text }}</text>
</label>
</radio-group>
</view>
<!-- 操作按钮 -->
<view class="pd-actions">
<view class="pd-btn pd-cancel" @tap="closePriceDialog">取消</view>
<view class="pd-btn pd-confirm" @tap="confirmPriceDialog">确认</view>
</view>
</view>
</uni-popup>
<!-- 视频播放弹窗 -->
<uni-popup ref="videoPopup" type="center">
<view class="video-popup">
<view class="video-popup-close" @click="closeVideoPopup">
<u-icon name="close" size="24" color="#fff"></u-icon>
</view>
<video
class="popup-video-player"
:src="currentVideoUrl"
controls
autoplay
object-fit="contain"
></video>
</view>
</uni-popup>
</view>
</template>
<script>
import { request, showToast } from '@/api/apiT.js'
export default {
data() {
return {
id: '',
appid: '',
detail: {},
currentImageIndex: 0,
currentVideoUrl: '',
priceForm: {
price: "",
currency: "USD",
lang: "en"
},
currencyOptions: [
{ value: "USD", text: "美元" },
{ value: "CNY", text: "人民币" },
{ value: "RUB", text: "卢布" },
{ value: "AED", text: "迪拉姆" },
{ value: "EUR", text: "欧元" }
],
langOptions: [
{ text: "英语", value: "en" },
{ text: "俄语", value: "ru" },
{ text: "西班牙语", value: "es" },
{ text: "阿拉伯语", value: "ar" }
],
tradeTermOptions: [
{ value: "FOB", text: "FOB" },
{ value: "CIF", text: "CIF" },
{ value: "EXW", text: "EXW" },
{ value: "DDP", text: "DDP" },
{ value: "CIP", text: "CIP" },
{ value: "FCA", text: "FCA" },
{ value: "CFR", text: "CFR" }
]
}
},
computed: {
// 处理图片列表
images() {
const raw = this.detail.images_pull || []
if (Array.isArray(raw)) {
return raw.filter(Boolean)
}
if (typeof raw === 'string') {
return raw.split(',').map(s => (s || '').trim()).filter(Boolean)
}
return []
},
// 处理视频列表
videoList() {
// 优先使用 video_pull(完整URL)
if (Array.isArray(this.detail.video_pull) && this.detail.video_pull.length) {
return this.detail.video_pull.filter(Boolean)
}
// 兼容 intro_video 字段
if (!this.detail.intro_video) return []
if (Array.isArray(this.detail.intro_video)) {
return this.detail.intro_video.filter(Boolean)
}
if (typeof this.detail.intro_video === 'string') {
return this.detail.intro_video.split(',').map(s => (s || '').trim()).filter(Boolean)
}
return []
},
// 处理FAQ列表
faqList() {
if (!this.detail.faqs) return []
if (Array.isArray(this.detail.faqs)) {
return this.detail.faqs.filter(f => f.question && f.answer)
}
return []
}
},
onLoad(options) {
this.id = options?.id || ''
const appid = options?.appid || uni.getStorageSync('fsappid') || ''
if (appid) uni.setStorageSync('fsappid', appid)
// 登录检查
const token = uni.getStorageSync('token')
if (!token) {
const redirect = `/pages/index/detilsPro?id=${this.id}${appid ? `&appid=${encodeURIComponent(appid)}` : ''}`
uni.setStorageSync('post_login_redirect', redirect)
uni.reLaunch({
url: `/pages/login/index?appid=${encodeURIComponent(appid)}&redirect=${encodeURIComponent(redirect)}`
})
return
}
this.getshows()
},
methods: {
// 获取专业发布详情
getshows() {
const that = this
request({
url: 'common/usedcardetail/show',
method: 'GET',
data: { id: that.id }
}).then(res => {
if (res.data.code == 200) {
this.detail = res.data.data
}
})
},
// 预览图片
previewImages(startIndex) {
if (!this.images || this.images.length === 0) return
this.currentImageIndex = startIndex || 0
uni.previewImage({
current: this.images[this.currentImageIndex],
urls: this.images
})
},
// 播放视频
playVideo(videoUrl) {
this.currentVideoUrl = videoUrl
this.$refs.videoPopup.open()
},
// 关闭视频弹窗
closeVideoPopup() {
this.$refs.videoPopup.close()
this.currentVideoUrl = ''
},
// 下载文件
downloadByAnchor(url, filename) {
try {
const a = document.createElement('a')
a.href = url
a.download = filename || ''
a.target = '_blank'
document.body.appendChild(a)
a.click()
document.body.removeChild(a)
} catch (e) {
if (typeof window !== 'undefined') window.open(url, '_blank')
}
},
// 查看附件
reviewFile(file) {
this.downloadByAnchor(file.src_pull, file.name)
},
// 打开价格弹框
openPriceDialog() {
this.priceForm = {
trade_term: '',
price: "",
currency: "USD",
lang: "en"
}
this.$refs.inputDialog.open()
},
// 关闭价格弹框
closePriceDialog() {
this.$refs.inputDialog.close()
},
// 价格输入处理
onPriceInput(e) {
let v = (e.detail && e.detail.value) ? String(e.detail.value) : String(this.priceForm.price || "")
v = v.replace(/[^\d.]/g, "")
const parts = v.split(".")
if (parts.length > 2) {
v = parts[0] + "." + parts.slice(1).join("")
}
v = v.replace(/^0+(\d)/, "$1")
if (v.startsWith(".")) v = "0" + v
this.priceForm.price = v
},
// 语言切换
onLangChange(e) {
this.priceForm.lang = e.detail.value
},
// 确认价格弹框
confirmPriceDialog() {
if (!this.priceForm.trade_term) {
return uni.showToast({ title: "请选择贸易条款", icon: "none" })
}
if (!this.priceForm.price) {
return uni.showToast({ title: "请输入价格", icon: "none" })
}
const payload = {
trade_term: this.priceForm.trade_term,
price: this.priceForm.price,
currency: this.priceForm.currency,
lang: this.priceForm.lang
}
this.closePriceDialog()
this.dialogInputConfirm(payload)
},
// 生成PDF
dialogInputConfirm(val) {
if (!val.price) {
uni.showToast({ title: '请输入价格', icon: 'none' })
return
}
request({
url: 'common/usedcardetail/produce/pdf',
method: 'get',
data: {
id: this.id,
price: val.price,
currency: val.currency,
lang: val.lang,
trade_term: val.trade_term
}
}).then(res => {
if (res.data.code == 200) {
this.downloadpdf(this.id)
} else {
uni.showToast({ title: res.data.msg || '生成失败', icon: 'none' })
}
})
},
// 下载PDF
downloadpdf(item) {
request({
url: 'common/usedcardetail/pdf',
method: 'get',
data: { id: item }
}).then(res => {
if (res.data.code == 200) {
this.downloadByAnchor(res.data.data.pdf_url, res.data.data.file_name)
}
})
}
}
}
</script>
<style scoped lang="scss">
.page {
min-height: 100vh;
background: #f6f7fb;
}
.content {
padding: 18rpx;
box-sizing: border-box;
}
// 专业发布卡片样式
.pro-card {
background: linear-gradient(135deg, #fff 0%, #f8f9ff 100%);
border: 1px solid rgba(46, 108, 246, 0.1);
}
.heard {
padding: 5px 0px;
color: #666;
font-size: 12px;
display: flex;
align-items: center;
justify-content: space-between;
gap: 10rpx;
}
// 专业发布标记
.pro-mark {
padding: 4rpx 12rpx;
border-radius: 999rpx;
background: linear-gradient(135deg, #2E6CF6 0%, #1a5dd9 100%);
box-shadow: 0 4rpx 12rpx rgba(46, 108, 246, 0.3);
.pro-mark-text {
font-size: 20rpx;
color: #fff;
font-weight: 600;
}
}
.card {
margin-top: 16rpx;
background: #fff;
border-radius: 18rpx;
padding: 18rpx 18rpx;
box-shadow: 0 10rpx 30rpx rgba(0, 0, 0, 0.05);
box-sizing: border-box;
}
// 视频卡片样式
.video-card {
border: 1px solid rgba(46, 108, 246, 0.15);
}
// FAQ卡片样式
.faq-card {
border: 1px solid rgba(255, 151, 47, 0.15);
}
.title-row {
display: flex;
align-items: center;
justify-content: space-between;
}
.name {
font-size: 34rpx;
font-weight: 700;
color: #111827;
line-height: 1.2;
}
.kv {
display: flex;
flex-direction: column;
}
.kv-item {
padding: 16rpx 0;
display: flex;
align-items: center;
justify-content: space-between;
border-bottom: 1rpx solid #f1f5f9;
}
.kv-item:last-child {
border-bottom: none;
padding-bottom: 0;
}
.k {
width: 180rpx;
font-size: 26rpx;
color: #374151;
flex-shrink: 0;
}
.v {
font-size: 26rpx;
color: #111827;
text-align: right;
flex: 1;
margin-left: 12rpx;
}
.section-title {
font-size: 28rpx;
font-weight: 700;
color: #111827;
margin-bottom: 10rpx;
display: flex;
align-items: center;
gap: 8rpx;
.title-text {
flex: 1;
}
.video-count,
.faq-count {
font-size: 22rpx;
color: #999;
font-weight: 400;
}
}
.desc {
font-size: 26rpx;
color: #374151;
line-height: 1.7;
white-space: pre-wrap;
}
.hero {
position: relative;
border-radius: 18rpx;
overflow: hidden;
margin-top: 16rpx;
background: #fff;
padding: 18rpx 18rpx;
box-shadow: 0 10rpx 30rpx rgba(0, 0, 0, 0.06);
}
.img-grid {
display: flex;
flex-wrap: wrap;
gap: 10rpx;
padding: 10rpx;
box-sizing: border-box;
}
.grid-img {
width: calc((100% - 20rpx) / 3);
height: 220rpx;
border-radius: 14rpx;
background: #f3f4f6;
}
// 视频网格样式
.video-grid {
display: flex;
flex-wrap: wrap;
gap: 16rpx;
}
.video-item {
width: calc((100% - 16rpx) / 2);
height: 240rpx;
border-radius: 14rpx;
overflow: hidden;
background: #000;
}
.video-player {
width: 100%;
height: 100%;
}
// FAQ列表样式
.faq-list {
display: flex;
flex-direction: column;
gap: 16rpx;
}
.faq-item {
padding: 16rpx;
border-radius: 12rpx;
background: linear-gradient(135deg, rgba(255, 151, 47, 0.05) 0%, rgba(255, 151, 47, 0.02) 100%);
border: 1px solid rgba(255, 151, 47, 0.1);
}
.faq-question,
.faq-answer {
display: flex;
align-items: flex-start;
margin-bottom: 8rpx;
&:last-child {
margin-bottom: 0;
}
}
.faq-label {
font-size: 24rpx;
font-weight: 600;
color: #FF972F;
flex-shrink: 0;
margin-right: 8rpx;
}
.faq-text {
flex: 1;
font-size: 24rpx;
color: #374151;
line-height: 1.6;
}
.fujian {
display: flex;
align-items: center;
justify-content: space-between;
border: 1px solid #ebebeb;
padding: 8px 10px;
border-radius: 4px;
}
.noneview {
text-align: center;
}
// 价格弹框样式
.tradeTerm {
border: 1px solid #eae9e9;
border-radius: 6px;
}
.price-dialog {
width: 640rpx;
background: #fff;
border-radius: 24rpx;
padding: 28rpx;
}
.pd-title {
font-size: 34rpx;
font-weight: 600;
color: #111;
margin-bottom: 20rpx;
text-align: center;
}
.pd-row {
display: flex;
align-items: center;
margin-top: 40rpx;
}
.pd-label {
width: 120rpx;
font-size: 28rpx;
color: #333;
}
.pd-input {
flex: 1;
height: 66rpx;
border: 1px solid #eee;
border-radius: 16rpx;
padding: 0 18rpx;
font-size: 30rpx;
}
.pd-input-suffix-wrap {
position: relative;
flex: 1;
}
.pd-input-with-suffix {
padding-right: 180rpx;
}
.pd-currency-suffix {
position: absolute;
right: 14rpx;
top: 50%;
transform: translateY(-50%);
width: 140rpx;
height: 80rpx;
display: flex;
align-items: center;
justify-content: flex-end;
}
::v-deep .uni-select {
border: none !important;
}
.pd-radio-group {
display: flex;
flex-wrap: wrap;
gap: 16rpx 22rpx;
margin-top: 12rpx;
}
.pd-radio {
display: flex;
align-items: center;
}
.pd-radio-text {
margin-left: 10rpx;
font-size: 26rpx;
color: #333;
}
.pd-actions {
display: flex;
gap: 18rpx;
margin-top: 40rpx;
}
.pd-btn {
flex: 1;
height: 84rpx;
border-radius: 18rpx;
display: flex;
align-items: center;
justify-content: center;
font-size: 30rpx;
}
.pd-cancel {
background: #f5f5f5;
color: #333;
}
.pd-confirm {
background: #3F435B;
color: #fff;
font-weight: 600;
}
.pd-price-line {
flex: 1;
display: flex;
align-items: center;
gap: 16rpx;
}
.pd-input-price {
flex: 1;
min-width: 240rpx;
}
// 视频弹窗样式
.video-popup {
position: relative;
width: 680rpx;
height: 480rpx;
background: #000;
border-radius: 20rpx;
overflow: hidden;
}
.video-popup-close {
position: absolute;
top: 20rpx;
right: 20rpx;
width: 60rpx;
height: 60rpx;
border-radius: 50%;
background: rgba(0, 0, 0, 0.6);
display: flex;
align-items: center;
justify-content: center;
z-index: 10;
}
.popup-video-player {
width: 100%;
height: 100%;
}
</style>