Files
used-car-h5/pages/index/publish.vue
T
2026-01-13 15:10:33 +08:00

743 lines
18 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>
<!-- <uni-nav-bar leftText=" " :fixed="true" :border="false" color="#fff" backgroundColor="#3F435B" :statusBar="true" title="二手车录入"></uni-nav-bar> -->
<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="请输入车架号/VIN码" v-model="carinfo.vin" />
</view>
</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">
<input placeholder="请输入上牌地" v-model="carinfo.plate_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>里程</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="cont">
<view class="conts">产品图</view>
<view class="media-upload-wrapper">
<uni-file-picker ref="mediaPicker" limit="20" fileMediatype="image" @select="selectMedia" @delete="delfn" >
<view class="upload-icon-wrapper">
<image src="/static/image/img.png" mode="" class="addimg"></image>
</view>
</uni-file-picker>
</view>
</view>
<view class="cont">
<view class="conts">车况介绍</view>
<view class="">
<textarea auto-height v-model="carinfo.condition_desc" placeholder="请输入车况介绍"/>
</view>
</view>
<view class="cont">
<view class="conts">附件</view>
<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>
</view>
<view class="zw">
<button class="btn" @click="draftadd">提交</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>
</view>
</template>
<script>
import { request,showToast } from '@/api/apiT.js'
export default {
data() {
return {
maxMedia:20,
bindState:{
showDialog:false,
loading:false,
list:[],
selectedId:'',
boundProduct:null,
keyword:''
},
carinfo:{
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:''
}
}
},
onLoad() {
},
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
},
// 绑定:根据输入的“产品名称”拉取候选产品
onBindByName() {
const name = String(this.carinfo.product_name || '').trim()
if (!name) {
return uni.showToast({ title: '请先输入产品名称', icon: 'none' })
}
// 打开弹框 + loading
this.bindState.showDialog = true
this.bindState.loading = true
this.bindState.list = []
this.bindState.selectedId = ''
this.bindState.keyword = ''
/**
* TODO:把这里的接口地址换成你真实的“按名称搜索产品”接口
* - 传参:name(产品名称)
* - 返回:数组(可能多个匹配)
*/
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
// 写入“已绑定”状态(你后续提交表单时可直接带上 bound_product_id)
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
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.bindState.boundProduct = null
uni.showToast({ title: '已清除绑定', icon: 'none' })
},
uploadfn(imgUrl,scene){
uni.showLoading({
title: '上传中'
});
return new Promise((resolve, reject) => {
uni.uploadFile({
url: 'https://userdcar-server.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://userdcar-server.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('上传失败')
}
},
fail: (err) => reject(err)
});
})
},
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)
})
}).catch(() => {
uni.showToast({
title:'图片上传失败,请重试',
icon:'none'
})
})
},
delfn(e){
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)
},
draftadd(){
const payload = {
...this.carinfo,
// images 数组 => "url1,url2,url3"
images: (this.carinfo.images || [])
.map(s => String(s || '').trim())
.filter(Boolean)
.join(',')
}
request({
url:'common/usedcar/store',
method:'post',
data:payload
}).then(res => {
uni.showToast({
title: res.data.message,
icon: 'none'
});
setTimeout(() => {
uni.reLaunch({
url: '/pages/index/index',// 替换为你要跳 转的页面路径
});
}, 1000);
})
}
}
}
</script>
<style lang="scss">
.uniui-right{
position: absolute;
top: 2px;
}
.red{
color: red;
}
.content{
// margin: 20px 12px;
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;
/* border-bottom: 1px solid #C3C3C3; */
align-items: center;
font-weight: 500;
}
.cont::after,.cell::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;
}
.cell1{
display: flex;
justify-content:space-between;
padding: 18px 20px;
align-items: center;
font-weight: 500;
}
.right{
text-align: right;
}
.select{
display: flex;
position: relative;
margin-right: 10px;
}
.foot{
position: fixed;
bottom: 0px;
width: 100%;
height: 120px;
background: #FFFFFF;
z-index: 2;
}
.btn{
background-color: #3F435B;
color: white;
border-radius: 50px;
width: 92%;
margin-bottom: 40px;
margin: 15px 4%;
}
.actives{
background-color: #dfdfdf;
}
.text{
display: flex;
align-items: center;
justify-content: space-between;
line-height: 30px;
padding: 10px 15px 0px;
.qd{
color: #3471FF;
}
}
.popup-content {
padding: 20px;
background-color: #fff;
}
.option-list {
max-height: 400rpx;
margin-top: 20rpx;
}
.option-item {
padding: 20rpx;
border-bottom: 1rpx solid #eee;
}
.option-item:last-child {
border-bottom: none;
}
.no-data {
text-align: center;
padding: 20rpx;
color: #999;
}
.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{
// margin: 0 24rpx 16rpx;
padding: 12rpx 18rpx;
// border-radius: 12rpx;
background: #F3F6FF;
display: flex;
align-items: center;
justify-content: space-between;
}
.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-placeholder{
color: #9AA6BF;
}
.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;
}
.btn{
flex: 1;
height: 76rpx;
border-radius: 16rpx;
display: flex;
align-items: center;
justify-content: center;
font-size: 28rpx;
}
.btn-ghost{
background: #F5F6F8;
color: #333;
}
.btn-primary{
background: #2E6CF6;
color: #fff;
}
.btn-primary.disabled{
opacity: .45;
}
</style>