提交
This commit is contained in:
@@ -0,0 +1,406 @@
|
||||
<template>
|
||||
|
||||
<view class="car-cards" >
|
||||
<view class="heard">
|
||||
<view class="" @click="handleCardTap">
|
||||
编号:{{item.code?item.code:'~'}}
|
||||
</view>
|
||||
<view v-if="ismy">
|
||||
<u-icon @click="detilsfn(item)" name="edit-pen-fill" size="25" />
|
||||
</view>
|
||||
</view>
|
||||
<view class="car-card" @click="handleCardTap">
|
||||
<image class="thumb" :src="item.images_pull[0]" mode="aspectFill" />
|
||||
<view class="info">
|
||||
<view class="row row1">
|
||||
<text class="name u-line-1">{{ item.product_name }}</text>
|
||||
</view>
|
||||
|
||||
<view class="row row2">
|
||||
<view class="kv">
|
||||
<text class="k">车源地</text>
|
||||
<text class="v u-line-1">{{ item.source_place }}</text>
|
||||
</view>
|
||||
<view class="kv">
|
||||
<text class="k">车身颜色</text>
|
||||
<text class="v u-line-1">{{ item.body_color }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="row row3">
|
||||
<view class="kv">
|
||||
<text class="k">上牌时间</text>
|
||||
<text class="v">{{ formatPlateTime(item.plate_time) }}</text>
|
||||
</view>
|
||||
<view class="kv">
|
||||
<text class="k">里程</text>
|
||||
<text class="v">{{ item.mileage }} <text v-if="item.mileage">Km</text> </text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<!-- 操作区:保存 + 分享(方案A:卡片右下角悬浮) -->
|
||||
<view class="card-actions">
|
||||
<view class="actions-lift" @click="handleCardTap">
|
||||
<text class="">创建时间:</text>
|
||||
<text class="">{{ item.created_at }}</text>
|
||||
</view>
|
||||
<view class="right-btn">
|
||||
<view class="action-btn" @tap.stop="handleToggleSave">
|
||||
<u-icon name="download" size="18" />
|
||||
</view>
|
||||
<view class="action-btn" @tap.stop="handleShare">
|
||||
<u-icon name="share" size="18" />
|
||||
</view>
|
||||
<view class="action-btn" @tap.stop="handleSet(item)" v-if="item.status==1">
|
||||
<u-icon name="setting" size="18" />
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { request } from '@/api/apiT.js'
|
||||
|
||||
export default {
|
||||
name: 'CarCard',
|
||||
props: {
|
||||
item: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
ismy:{
|
||||
default: false,
|
||||
type: Boolean
|
||||
},
|
||||
index: {
|
||||
type: Number,
|
||||
default: -1
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
detilsfn(item){
|
||||
const id = item.id
|
||||
uni.navigateTo({
|
||||
url: `/pages/index/publishedit?id=${id}`
|
||||
})
|
||||
},
|
||||
formatPlateTime(dateStr) {
|
||||
return String(dateStr || '').slice(0, 7) // "2026-01"
|
||||
},
|
||||
pdfchanges() {
|
||||
this.$emit('pdfchange')
|
||||
},
|
||||
handleSet(item){
|
||||
const that = this
|
||||
uni.showModal({
|
||||
title: '提示',
|
||||
content: '确认标记已售出?',
|
||||
success: res => {
|
||||
if (res.confirm) {
|
||||
request({
|
||||
url:'common/usedcar/update/status',
|
||||
method:'post',
|
||||
data: {id:item.id,status:2}
|
||||
}).then(res => {
|
||||
uni.showToast({
|
||||
title: res.data.message,
|
||||
icon: 'none'
|
||||
});
|
||||
setTimeout(() => {
|
||||
that.$emit('restchange')
|
||||
}, 1000);
|
||||
|
||||
})
|
||||
} else if (res.cancel) {
|
||||
console.log('用户点击取消');
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
handleCardTap() {
|
||||
// 直接处理跳转,避免事件传递问题
|
||||
if (this.item && this.item.id) {
|
||||
const id = this.item.id
|
||||
uni.navigateTo({
|
||||
url: `/pages/index/detils?id=${id}`
|
||||
})
|
||||
} else {
|
||||
console.error('CarCard: item or item.id is missing', this.item)
|
||||
}
|
||||
},
|
||||
// H5 下载:用 a 标签触发
|
||||
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')
|
||||
}
|
||||
},
|
||||
downloadpdf(item) {
|
||||
request({
|
||||
url: 'common/usedcar/pdf',
|
||||
method: 'get',
|
||||
data: { id: item.id }
|
||||
}).then(res => {
|
||||
if (res.data.code == 200) {
|
||||
this.downloadByAnchor(res.data.data.pdf_url, res.data.data.file_name)
|
||||
}
|
||||
})
|
||||
},
|
||||
handleToggleSave() {
|
||||
const item = this.item
|
||||
// if (item.pdf_src && item.pdf_src != '') {
|
||||
// this.downloadpdf(item)
|
||||
// } else {
|
||||
// 让父组件统一弹出输入框(避免列表里每个卡片都渲染一个 popup)
|
||||
this.$emit('request-price', { item, index: this.index })
|
||||
// }
|
||||
},
|
||||
buildShareLink(id) {
|
||||
// 统一生成"详情页路径"
|
||||
const path = `pages/index/detils?id=${id}&appid=${uni.getStorageSync('fsappid')}`
|
||||
// #ifdef H5
|
||||
try {
|
||||
const origin = window.location.origin
|
||||
// uni-app H5 默认 hash 路由
|
||||
return `${origin}/${path}`
|
||||
} catch (e) {
|
||||
return path
|
||||
}
|
||||
// #endif
|
||||
return path
|
||||
},
|
||||
isFeishuEnv() {
|
||||
// 飞书/ Lark 客户端内置浏览器 UA 通常包含 Feishu / Lark
|
||||
try {
|
||||
const ua = (navigator.userAgent || '').toLowerCase()
|
||||
return ua.includes('feishu') || ua.includes('lark')
|
||||
} catch (e) {
|
||||
return false
|
||||
}
|
||||
},
|
||||
feishuShare(payload) {
|
||||
// 在飞书内优先使用 tt.share
|
||||
try {
|
||||
if (!this.isFeishuEnv()) return false
|
||||
if (!window || !window.tt || !window.h5sdk) return false
|
||||
// ready 后再调用
|
||||
window.h5sdk.ready(() => {
|
||||
// 优先使用 shareWebContent(文档推荐的 Web App 分享能力),不支持则降级到 share
|
||||
if (typeof window.tt.shareWebContent === 'function') {
|
||||
window.tt.shareWebContent({
|
||||
title: payload.title,
|
||||
content: payload.desc,
|
||||
url: payload.link,
|
||||
imageUrl: payload.imageUrl,
|
||||
success: () => {
|
||||
uni.showToast({ title: '已唤起飞书分享', icon: 'none' })
|
||||
},
|
||||
fail: (err) => {
|
||||
console.error('feishu shareWebContent fail:', err)
|
||||
uni.showToast({ title: '分享失败,可复制链接', icon: 'none' })
|
||||
}
|
||||
})
|
||||
} else if (typeof window.tt.share === 'function') {
|
||||
window.tt.share({
|
||||
title: payload.title,
|
||||
desc: payload.desc,
|
||||
link: payload.link,
|
||||
imageUrl: payload.imageUrl,
|
||||
success: () => {
|
||||
uni.showToast({ title: '已唤起飞书分享', icon: 'none' })
|
||||
},
|
||||
fail: (err) => {
|
||||
console.error('feishu share fail:', err)
|
||||
uni.showToast({ title: '分享失败,可复制链接', icon: 'none' })
|
||||
}
|
||||
})
|
||||
} else {
|
||||
throw new Error('Feishu share api not available')
|
||||
}
|
||||
})
|
||||
return true
|
||||
} catch (e) {
|
||||
return false
|
||||
}
|
||||
},
|
||||
handleShare() {
|
||||
const item = this.item
|
||||
const title = item.product_name || '车辆详情'
|
||||
const link = this.buildShareLink(item.id)
|
||||
const imageUrl = (item.images_pull && item.images_pull[0]) ? item.images_pull[0] : ''
|
||||
// #ifdef MP-WEIXIN
|
||||
// 小程序:打开系统分享面板(需要页面 onShareAppMessage 支持)
|
||||
uni.showShareMenu({ withShareTicket: true })
|
||||
uni.showToast({ title: '请使用右上角分享', icon: 'none' })
|
||||
// #endif
|
||||
|
||||
// #ifdef H5
|
||||
// 1) 飞书内:优先走飞书分享
|
||||
const ok = this.feishuShare({
|
||||
title,
|
||||
desc: title,
|
||||
link,
|
||||
imageUrl
|
||||
})
|
||||
if (ok) return
|
||||
|
||||
// H5:优先走 Web Share API
|
||||
if (typeof navigator !== 'undefined' && navigator.share) {
|
||||
navigator.share({ title, text: title, url: link }).catch(() => {})
|
||||
return
|
||||
}
|
||||
// #endif
|
||||
|
||||
// 兜底:弹出操作并复制链接
|
||||
uni.showActionSheet({
|
||||
itemList: ['复制链接', '复制标题'],
|
||||
success: (res) => {
|
||||
if (res.tapIndex === 0) {
|
||||
uni.setClipboardData({ data: link })
|
||||
} else {
|
||||
uni.setClipboardData({ data: title })
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.heard{
|
||||
padding: 5px 10px;
|
||||
color: #666;
|
||||
font-size: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.car-cards {
|
||||
padding-bottom: 6rpx; // 预留右下角按钮空间,避免遮挡信息
|
||||
border-radius: 18rpx;
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
box-shadow: 0 10rpx 30rpx rgba(0, 0, 0, 0.06);
|
||||
margin-bottom: 18rpx;
|
||||
cursor: pointer;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
}
|
||||
.car-card {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
padding: 18rpx;
|
||||
}
|
||||
|
||||
/* 方案A:卡片右下角悬浮操作区 */
|
||||
.card-actions {
|
||||
padding: 18rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 14rpx;
|
||||
border-top: 1px solid #e1e1e1;
|
||||
}
|
||||
.actions-lift {
|
||||
font-size: 12px;
|
||||
}
|
||||
.right-btn {
|
||||
display: flex;
|
||||
gap: 14rpx;
|
||||
}
|
||||
|
||||
.action-btn {
|
||||
width: 50rpx;
|
||||
height: 50rpx;
|
||||
border-radius: 999rpx;
|
||||
background: rgba(255, 255, 255, 0.92);
|
||||
box-shadow: 0 8rpx 18rpx rgba(0, 0, 0, 0.12);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.action-btn:active {
|
||||
transform: scale(0.96);
|
||||
}
|
||||
|
||||
.thumb {
|
||||
width: 190rpx;
|
||||
height: 140rpx;
|
||||
border-radius: 14rpx;
|
||||
background: #eef2f7;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.info {
|
||||
flex: 1;
|
||||
margin-left: 18rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.row1 {
|
||||
justify-content: space-between;
|
||||
margin-bottom: 10rpx;
|
||||
}
|
||||
|
||||
.name {
|
||||
font-size: 30rpx;
|
||||
font-weight: 700;
|
||||
color: #111827;
|
||||
max-width: 420rpx;
|
||||
}
|
||||
|
||||
.tag {
|
||||
font-size: 22rpx;
|
||||
padding: 6rpx 12rpx;
|
||||
border-radius: 999rpx;
|
||||
background: rgba(255, 151, 47, 0.14);
|
||||
color: #b45309;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.row2,
|
||||
.row3 {
|
||||
justify-content: space-between;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.kv {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
}
|
||||
|
||||
.k {
|
||||
font-size: 22rpx;
|
||||
color: #6b7280;
|
||||
margin-right: 10rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.v {
|
||||
font-size: 24rpx;
|
||||
color: #374151;
|
||||
font-weight: 600;
|
||||
min-width: 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -0,0 +1,445 @@
|
||||
<template>
|
||||
<view class="car-card-pro">
|
||||
<view class="heard">
|
||||
<view class="" @click="handleCardTap">
|
||||
编号:{{item.code?item.code:'~'}}
|
||||
</view>
|
||||
<view v-if="ismy">
|
||||
<u-icon @click="editfn(item)" name="edit-pen-fill" size="25" />
|
||||
</view>
|
||||
</view>
|
||||
<view class="car-card" @click="handleCardTap">
|
||||
<image class="thumb" :src="item.images_pull[0]" mode="aspectFill" />
|
||||
<view class="info">
|
||||
<view class="row row1">
|
||||
<text class="name u-line-1">{{ item.product_name }}</text>
|
||||
</view>
|
||||
|
||||
<view class="row row2">
|
||||
<view class="kv">
|
||||
<text class="k">车源地</text>
|
||||
<text class="v u-line-1">{{ item.source_place }}</text>
|
||||
</view>
|
||||
<view class="kv">
|
||||
<text class="k">车身颜色</text>
|
||||
<text class="v u-line-1">{{ item.body_color }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="row row3">
|
||||
<view class="kv">
|
||||
<text class="k">上牌时间</text>
|
||||
<text class="v">{{ formatPlateTime(item.plate_time) }}</text>
|
||||
</view>
|
||||
<view class="kv">
|
||||
<text class="k">里程</text>
|
||||
<text class="v">{{ item.mileage }} <text v-if="item.mileage">Km</text> </text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<!-- 操作区 -->
|
||||
<view class="card-actions">
|
||||
<view class="actions-lift" @click="handleCardTap">
|
||||
<text class="">创建时间:</text>
|
||||
<text class="">{{ item.created_at }}</text>
|
||||
</view>
|
||||
<!-- <view class="right-btn">
|
||||
<view class="action-btn" @tap.stop="handleToggleSave">
|
||||
<u-icon name="download" size="18" />
|
||||
</view>
|
||||
<view class="action-btn" @tap.stop="handleShare">
|
||||
<u-icon name="share" size="18" />
|
||||
</view>
|
||||
<view class="action-btn" @tap.stop="handleSet(item)" v-if="item.status==1">
|
||||
<u-icon name="setting" size="18" />
|
||||
</view>
|
||||
</view> -->
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { request } from '@/api/apiT.js'
|
||||
|
||||
export default {
|
||||
name: 'CarCardPro',
|
||||
props: {
|
||||
item: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
ismy: {
|
||||
default: false,
|
||||
type: Boolean
|
||||
},
|
||||
index: {
|
||||
type: Number,
|
||||
default: -1
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// 计算视频数量
|
||||
videoCount() {
|
||||
if (!this.item.intro_video) return 0
|
||||
if (Array.isArray(this.item.intro_video)) {
|
||||
return this.item.intro_video.filter(Boolean).length
|
||||
}
|
||||
if (typeof this.item.intro_video === 'string') {
|
||||
return this.item.intro_video.split(',').filter(s => s.trim()).length
|
||||
}
|
||||
return 0
|
||||
},
|
||||
// 计算FAQ数量
|
||||
faqCount() {
|
||||
if (!this.item.faqs) return 0
|
||||
if (Array.isArray(this.item.faqs)) {
|
||||
return this.item.faqs.filter(f => f.question && f.answer).length
|
||||
}
|
||||
return 0
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 跳转到编辑页面(供应商发布专用编辑页)
|
||||
editfn(item) {
|
||||
const id = item.id
|
||||
uni.navigateTo({
|
||||
url: `/pages/index/publisheditPro?id=${id}`
|
||||
})
|
||||
},
|
||||
formatPlateTime(dateStr) {
|
||||
return String(dateStr || '').slice(0, 7)
|
||||
},
|
||||
pdfchanges() {
|
||||
this.$emit('pdfchange')
|
||||
},
|
||||
// 标记已售出
|
||||
handleSet(item) {
|
||||
const that = this
|
||||
uni.showModal({
|
||||
title: '提示',
|
||||
content: '确认标记已售出?',
|
||||
success: res => {
|
||||
if (res.confirm) {
|
||||
request({
|
||||
url: 'common/usedcardetail/update/status',
|
||||
method: 'post',
|
||||
data: { id: item.id, status: 2 }
|
||||
}).then(res => {
|
||||
uni.showToast({
|
||||
title: res.data.message,
|
||||
icon: 'none'
|
||||
})
|
||||
setTimeout(() => {
|
||||
that.$emit('restchange')
|
||||
}, 1000)
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
// 跳转到专业详情页
|
||||
handleCardTap() {
|
||||
if (this.item && this.item.id) {
|
||||
const id = this.item.id
|
||||
uni.navigateTo({
|
||||
url: `/pages/index/detilsPro?id=${id}`
|
||||
})
|
||||
} else {
|
||||
console.error('CarCardPro: item or item.id is missing', this.item)
|
||||
}
|
||||
},
|
||||
// H5 下载
|
||||
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')
|
||||
}
|
||||
},
|
||||
// 下载PDF
|
||||
downloadpdf(item) {
|
||||
request({
|
||||
url: 'common/usedcardetail/pdf',
|
||||
method: 'get',
|
||||
data: { id: item.id }
|
||||
}).then(res => {
|
||||
if (res.data.code == 200) {
|
||||
this.downloadByAnchor(res.data.data.pdf_url, res.data.data.file_name)
|
||||
}
|
||||
})
|
||||
},
|
||||
// 请求生成PDF
|
||||
handleToggleSave() {
|
||||
const item = this.item
|
||||
this.$emit('request-price', { item, index: this.index })
|
||||
},
|
||||
// 生成分享链接
|
||||
buildShareLink(id) {
|
||||
const path = `pages/index/detilsPro?id=${id}&appid=${uni.getStorageSync('fsappid')}`
|
||||
// #ifdef H5
|
||||
try {
|
||||
const origin = window.location.origin
|
||||
return `${origin}/${path}`
|
||||
} catch (e) {
|
||||
return path
|
||||
}
|
||||
// #endif
|
||||
return path
|
||||
},
|
||||
// 判断是否在飞书环境
|
||||
isFeishuEnv() {
|
||||
try {
|
||||
const ua = (navigator.userAgent || '').toLowerCase()
|
||||
return ua.includes('feishu') || ua.includes('lark')
|
||||
} catch (e) {
|
||||
return false
|
||||
}
|
||||
},
|
||||
// 飞书分享
|
||||
feishuShare(payload) {
|
||||
try {
|
||||
if (!this.isFeishuEnv()) return false
|
||||
if (!window || !window.tt || !window.h5sdk) return false
|
||||
window.h5sdk.ready(() => {
|
||||
if (typeof window.tt.shareWebContent === 'function') {
|
||||
window.tt.shareWebContent({
|
||||
title: payload.title,
|
||||
content: payload.desc,
|
||||
url: payload.link,
|
||||
imageUrl: payload.imageUrl,
|
||||
success: () => {
|
||||
uni.showToast({ title: '已唤起飞书分享', icon: 'none' })
|
||||
},
|
||||
fail: (err) => {
|
||||
console.error('feishu shareWebContent fail:', err)
|
||||
uni.showToast({ title: '分享失败,可复制链接', icon: 'none' })
|
||||
}
|
||||
})
|
||||
} else if (typeof window.tt.share === 'function') {
|
||||
window.tt.share({
|
||||
title: payload.title,
|
||||
desc: payload.desc,
|
||||
link: payload.link,
|
||||
imageUrl: payload.imageUrl,
|
||||
success: () => {
|
||||
uni.showToast({ title: '已唤起飞书分享', icon: 'none' })
|
||||
},
|
||||
fail: (err) => {
|
||||
console.error('feishu share fail:', err)
|
||||
uni.showToast({ title: '分享失败,可复制链接', icon: 'none' })
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
return true
|
||||
} catch (e) {
|
||||
return false
|
||||
}
|
||||
},
|
||||
// 分享处理
|
||||
handleShare() {
|
||||
const item = this.item
|
||||
const title = item.product_name || '车辆详情'
|
||||
const link = this.buildShareLink(item.id)
|
||||
const imageUrl = (item.images_pull && item.images_pull[0]) ? item.images_pull[0] : ''
|
||||
|
||||
// #ifdef MP-WEIXIN
|
||||
uni.showShareMenu({ withShareTicket: true })
|
||||
uni.showToast({ title: '请使用右上角分享', icon: 'none' })
|
||||
// #endif
|
||||
|
||||
// #ifdef H5
|
||||
const ok = this.feishuShare({
|
||||
title,
|
||||
desc: title,
|
||||
link,
|
||||
imageUrl
|
||||
})
|
||||
if (ok) return
|
||||
|
||||
if (typeof navigator !== 'undefined' && navigator.share) {
|
||||
navigator.share({ title, text: title, url: link }).catch(() => {})
|
||||
return
|
||||
}
|
||||
// #endif
|
||||
|
||||
uni.showActionSheet({
|
||||
itemList: ['复制链接', '复制标题'],
|
||||
success: (res) => {
|
||||
if (res.tapIndex === 0) {
|
||||
uni.setClipboardData({ data: link })
|
||||
} else {
|
||||
uni.setClipboardData({ data: title })
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.heard {
|
||||
padding: 5px 10px;
|
||||
color: #666;
|
||||
font-size: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.car-card-pro {
|
||||
padding-bottom: 6rpx;
|
||||
border-radius: 18rpx;
|
||||
background: linear-gradient(135deg, #fff 0%, #f8f9ff 100%);
|
||||
// box-shadow: 0 10rpx 30rpx rgba(46, 108, 246, 0.12);
|
||||
margin-bottom: 18rpx;
|
||||
cursor: pointer;
|
||||
border: 1px solid rgba(46, 108, 246, 0.1);
|
||||
}
|
||||
|
||||
.car-card {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
padding: 18rpx;
|
||||
}
|
||||
|
||||
.card-actions {
|
||||
padding: 18rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 14rpx;
|
||||
border-top: 1px solid #e1e1e1;
|
||||
}
|
||||
|
||||
.actions-lift {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.right-btn {
|
||||
display: flex;
|
||||
gap: 14rpx;
|
||||
}
|
||||
|
||||
.action-btn {
|
||||
width: 50rpx;
|
||||
height: 50rpx;
|
||||
border-radius: 999rpx;
|
||||
background: rgba(255, 255, 255, 0.92);
|
||||
box-shadow: 0 8rpx 18rpx rgba(0, 0, 0, 0.12);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.action-btn:active {
|
||||
transform: scale(0.96);
|
||||
}
|
||||
|
||||
.thumb {
|
||||
width: 190rpx;
|
||||
height: 140rpx;
|
||||
border-radius: 14rpx;
|
||||
background: #eef2f7;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.info {
|
||||
flex: 1;
|
||||
margin-left: 18rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.row1 {
|
||||
justify-content: space-between;
|
||||
margin-bottom: 10rpx;
|
||||
}
|
||||
|
||||
.name {
|
||||
font-size: 30rpx;
|
||||
font-weight: 700;
|
||||
color: #111827;
|
||||
max-width: 280rpx;
|
||||
}
|
||||
|
||||
.row2,
|
||||
.row3 {
|
||||
justify-content: space-between;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.row3 {
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
// 特性标签行
|
||||
.row4 {
|
||||
gap: 12rpx;
|
||||
margin-top: 6rpx;
|
||||
}
|
||||
|
||||
.feature-tag {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6rpx;
|
||||
padding: 6rpx 12rpx;
|
||||
border-radius: 12rpx;
|
||||
background: rgba(46, 108, 246, 0.08);
|
||||
|
||||
.tag-text {
|
||||
font-size: 20rpx;
|
||||
color: #2E6CF6;
|
||||
}
|
||||
|
||||
&:nth-child(2) {
|
||||
background: rgba(255, 151, 47, 0.08);
|
||||
|
||||
.tag-text {
|
||||
color: #FF972F;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.tag-text-empty {
|
||||
font-size: 20rpx;
|
||||
color: #999;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.kv {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
}
|
||||
|
||||
.k {
|
||||
font-size: 22rpx;
|
||||
color: #6b7280;
|
||||
margin-right: 10rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.v {
|
||||
font-size: 24rpx;
|
||||
color: #374151;
|
||||
font-weight: 600;
|
||||
min-width: 0;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,143 @@
|
||||
<template>
|
||||
<view class="custom-tabbar" :style="{ paddingBottom: safeAreaInsets.bottom + 'px' }">
|
||||
<view
|
||||
v-for="(item, index) in tabList"
|
||||
:key="index"
|
||||
class="tab-item"
|
||||
:class="{ active: current === index }"
|
||||
@tap="switchTab(item, index)"
|
||||
>
|
||||
<image
|
||||
class="tab-icon"
|
||||
:src="current === index ? item.selectedIconPath : item.iconPath"
|
||||
mode="aspectFit"
|
||||
/>
|
||||
<text class="tab-text">{{ item.text }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getVisibleTabBar, getCurrentTabIndex, getUserRole } from '@/common/tabbar.js'
|
||||
|
||||
export default {
|
||||
name: 'CustomTabBar',
|
||||
data() {
|
||||
return {
|
||||
current: 0,
|
||||
safeAreaInsets: {
|
||||
bottom: 0
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
tabList() {
|
||||
// 根据权限获取可见的 tabbar 列表
|
||||
const userRole = getUserRole()
|
||||
return getVisibleTabBar(userRole)
|
||||
}
|
||||
},
|
||||
onLoad() {
|
||||
this.updateCurrent()
|
||||
this.initSafeArea()
|
||||
},
|
||||
onShow() {
|
||||
// 页面显示时更新当前 tab 索引
|
||||
this.updateCurrent()
|
||||
},
|
||||
watch: {
|
||||
// 监听 tabList 变化,当角色改变导致 tabbar 列表变化时,更新当前索引
|
||||
tabList: {
|
||||
handler() {
|
||||
this.updateCurrent()
|
||||
},
|
||||
immediate: true
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
initSafeArea() {
|
||||
// 获取安全区域信息(适配 iPhone X 等)
|
||||
// #ifdef APP-PLUS || H5
|
||||
try {
|
||||
const systemInfo = uni.getSystemInfoSync()
|
||||
this.safeAreaInsets = systemInfo.safeAreaInsets || { bottom: 0 }
|
||||
} catch (e) {
|
||||
this.safeAreaInsets = { bottom: 0 }
|
||||
}
|
||||
// #endif
|
||||
},
|
||||
updateCurrent() {
|
||||
const pages = getCurrentPages()
|
||||
if (pages.length === 0) return
|
||||
|
||||
const currentPage = pages[pages.length - 1]
|
||||
const currentPath = currentPage.route || currentPage.$route?.path || ''
|
||||
const index = getCurrentTabIndex(currentPath)
|
||||
// 如果当前页面不在可见的 tabbar 列表中,保持当前索引或设为 0
|
||||
this.current = index >= 0 ? index : 0
|
||||
},
|
||||
switchTab(item, index) {
|
||||
if (this.current === index) {
|
||||
return
|
||||
}
|
||||
uni.switchTab({
|
||||
url: '/' + item.pagePath,
|
||||
fail: (err) => {
|
||||
// 如果 switchTab 失败,可能是该页面不在 tabBar 中,使用 navigateTo
|
||||
uni.navigateTo({
|
||||
url: '/' + item.pagePath,
|
||||
fail: () => {
|
||||
console.error('导航失败:', err)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.custom-tabbar {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 100rpx;
|
||||
background: #fff;
|
||||
border-top: 1px solid #e5e5e5;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-around;
|
||||
z-index: 999;
|
||||
box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.tab-item {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.tab-item.active {
|
||||
.tab-text {
|
||||
color: #3F435B;
|
||||
}
|
||||
}
|
||||
|
||||
.tab-icon {
|
||||
width: 44rpx;
|
||||
height: 44rpx;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.tab-text {
|
||||
font-size: 24rpx;
|
||||
color: #7A7E83;
|
||||
transition: color 0.2s;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,315 @@
|
||||
<template>
|
||||
<view class="main">
|
||||
<!-- <view class="input" :style="disabled?'background-color:#f5f7fa':''"> -->
|
||||
<view class="input">
|
||||
<!-- <input @click="showModal" v-model="_value" :style="disabled?'color:#c0c4cc':''" :placeholder="placeholder" disabled/> -->
|
||||
<input @click="showModal" v-model="_value" :placeholder="placeholder" disabled/>
|
||||
<text v-if="clearable&&!disabled" @click="empty" class="selectIcon iconcross"></text>
|
||||
</view>
|
||||
<view class="select-modal" :class="isShowModal?'show':''" @tap="hideModal">
|
||||
<view class="select-dialog" @tap.stop="" :style="{backgroundColor:bgColor}">
|
||||
<view class="select-bar bg-white">
|
||||
<view class="action text-blue" @tap="cancelClick">{{cancelText}}</view>
|
||||
<view class="action text-green" @tap="confirmClick">{{confirmText}}</view>
|
||||
</view>
|
||||
<view class="select-content">
|
||||
<view class="select-item" v-for="(item,index) in list" :key="index"
|
||||
:style="valueIndexOf(item)?'color:'+selectColor+';background-color:'+selectBgColor+';':'color:'+color+';'"
|
||||
@click="select(item)">
|
||||
<view class="title">{{getLabelKeyValue(item)}}</view>
|
||||
<text class="selectIcon icongou" v-if="valueIndexOf(item)"></text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
isShowModal:false
|
||||
};
|
||||
},
|
||||
props: {
|
||||
value:{
|
||||
type:[Number,String,Array,Object],
|
||||
default:null
|
||||
},
|
||||
placeholder:{ // 占位符
|
||||
default: "",
|
||||
type: String
|
||||
},
|
||||
multiple:{ // 是否多选
|
||||
default: false,
|
||||
type: Boolean
|
||||
},
|
||||
list: {
|
||||
default: () => [],
|
||||
type: Array
|
||||
},
|
||||
valueKey:{ // 指定list中valueKey的值作为下拉框绑定内容
|
||||
default: 'value',
|
||||
type: String
|
||||
},
|
||||
labelKey:{ // 指定list中labelKey的值作为下拉框显示内容
|
||||
default: 'label',
|
||||
type: String
|
||||
},
|
||||
disabled: {
|
||||
default: false,
|
||||
type: Boolean
|
||||
},
|
||||
clearable:{
|
||||
default: false,
|
||||
type: Boolean
|
||||
},
|
||||
cancelText:{
|
||||
default: "取消",
|
||||
type: String
|
||||
},
|
||||
confirmText:{
|
||||
default: "确定",
|
||||
type: String
|
||||
},
|
||||
color:{
|
||||
default: "#000000",
|
||||
type: String
|
||||
},
|
||||
selectColor:{
|
||||
default: "#0081ff",
|
||||
type: String
|
||||
},
|
||||
bgColor:{
|
||||
default: "#F1F1F1",
|
||||
type: String
|
||||
},
|
||||
selectBgColor:{
|
||||
default: "#FFFFFF",
|
||||
type: String
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
_value: {
|
||||
get() {
|
||||
return this.get_value(this.value);
|
||||
},
|
||||
set(val) {
|
||||
this.$emit('change', val);
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
},
|
||||
methods: {
|
||||
get_value(val){ // 将数组值转换为以,隔开的字符串
|
||||
if(val || val===0){
|
||||
if(Array.isArray(val)){
|
||||
let chooseAttr = []
|
||||
val.forEach(item=>{
|
||||
let choose = this.list.find(temp => {
|
||||
let val_val = this.getValueKeyValue(temp)
|
||||
return item == val_val
|
||||
})
|
||||
chooseAttr.push(choose)
|
||||
})
|
||||
let values = chooseAttr.map(temp => this.getLabelKeyValue(temp)).join(',')
|
||||
return values
|
||||
} else {
|
||||
let choose = this.list.find(temp => {
|
||||
let val_val = this.getValueKeyValue(temp)
|
||||
return val == val_val
|
||||
})
|
||||
return this.getLabelKeyValue(choose)
|
||||
}
|
||||
} else {
|
||||
return ""
|
||||
}
|
||||
},
|
||||
select(item){ // 点击选项
|
||||
let val = this.getValueKeyValue(item);
|
||||
if(this.multiple){
|
||||
let _value = this.value;
|
||||
let index = _value.indexOf(val);
|
||||
if(index!=-1){
|
||||
_value.splice(index,1)
|
||||
this.$emit('change', _value)
|
||||
} else {
|
||||
_value.push(val)
|
||||
this.$emit('change', _value)
|
||||
}
|
||||
} else {
|
||||
this.$emit('change', val)
|
||||
this.hideModal()
|
||||
}
|
||||
},
|
||||
valueIndexOf(item){
|
||||
let val = this.getValueKeyValue(item);
|
||||
if(Array.isArray(this.value)){
|
||||
return this.value.indexOf(val)!=-1
|
||||
} else {
|
||||
return this.value == val
|
||||
}
|
||||
},
|
||||
getLabelKeyValue(item){ // 获取label
|
||||
return item[this.labelKey]
|
||||
},
|
||||
getValueKeyValue(item){ // 获取value
|
||||
return item[this.valueKey]
|
||||
},
|
||||
empty(){ // 清空
|
||||
if(this.multiple){
|
||||
this.$emit('change', [])
|
||||
} else {
|
||||
this.$emit('change', '')
|
||||
}
|
||||
},
|
||||
cancelClick(){ // 点击取消
|
||||
this.$emit('cancel', this._value)
|
||||
this.hideModal()
|
||||
},
|
||||
confirmClick(){ // 点击确定
|
||||
this.$emit('confirm', this._value)
|
||||
this.hideModal()
|
||||
},
|
||||
showModal(){ // 显示model
|
||||
if(!this.disabled){
|
||||
this.isShowModal = true
|
||||
}
|
||||
},
|
||||
hideModal(){ // 隐藏model
|
||||
this.isShowModal = false
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
@font-face {font-family: "selectIcon";
|
||||
src: url('//at.alicdn.com/t/font_1833441_ycfzdhg2u3.eot?t=1590375117208'); /* IE9 */
|
||||
src: url('//at.alicdn.com/t/font_1833441_ycfzdhg2u3.eot?t=1590375117208#iefix') format('embedded-opentype'), /* IE6-IE8 */
|
||||
url('data:application/x-font-woff2;charset=utf-8;base64,d09GMgABAAAAAAMEAAsAAAAABvQAAAK4AAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHEIGVgCDBgqBRIFCATYCJAMMCwgABCAFhQUHNRsfBsg+QCa3uoO0oAJTMwhxVu965keqWBy1hkbwtfzWb2Z279/shRhJisKF6FApKLI7oyBbpAaHo3w24k+ca9EUJbDmjaeznUdZ/FOUlkWdJ33rizZY/Pw6J5Xw0qKYxHTMesePHVT6EFpaC4zV70sKi2bYgNPc1w0WHnDVC/e/UnNTgyP+4Jq6BBpIHoisgypLaIAFEtU0wgeaIG8Yu4nAIZwnUK1QgFfOT6nUUoBpgXjj2lqplTMpiuXtCW3N2iK+aPTS2/Qdnzny8d+5IEiaDMy99exklra//FrKnX48pChmgrq5QcYRQCEe17ruqgqLAKv8WntwqwhpLms/nB5yW/iHRxJEC0QOgT3NnfgF01NBKvOuIzNoZdh5gJuAeGrsozE8vOJ7u5D832oz55039W5G+S52K0H+zNf1TJz07k26kqoQybRfwVFV4rjDS/K8EXUyuF1cXnT3weKS9Rvdm/xe7h8oA1hLwOR18R+Y4n4zwpr4z5SU089Vc+cpfWL+mn5APmT3Z39jeOs/GbWjK+DnmsuL/u6ehMX4j4yedSVkAUUuPh3TY022MtKZUEOtPqCb8Bkvnr5XT6imU0gGrEJW7aAL/gw0OhegVV2F6pC7uTOppirKIA4MFQhTrpCM+AbZlDu64L/QmAkQWlMhQXU75D07O9Gtl0PUYjTBLyAzOLNQYtypIEEjvsXtBLQTooV2nrQrGEau2gKmZlR4L8gwnGtBJbUn1diCOOQUnEkTkRAOeci9KHOQxvFro+tx3ZcGAaeljstCSBNDJuArgIyBYyy6OdZxAhHIELu1IC9AtgShCVtLltEKrSff1XoHJo3RC33hM63o3j6pSNkmqmIWEAtxFHB2OwoRBAfyeqE3r2ogHeF42dBhs7gvf7CukH5MmlUGOCpHihxFfs6TehDyKCqVAA==') format('woff2'),
|
||||
url('//at.alicdn.com/t/font_1833441_ycfzdhg2u3.woff?t=1590375117208') format('woff'),
|
||||
url('//at.alicdn.com/t/font_1833441_ycfzdhg2u3.ttf?t=1590375117208') format('truetype'), /* chrome, firefox, opera, Safari, Android, iOS 4.2+ */
|
||||
url('//at.alicdn.com/t/font_1833441_ycfzdhg2u3.svg?t=1590375117208#selectIcon') format('svg'); /* iOS 4.1- */
|
||||
}
|
||||
|
||||
.selectIcon {
|
||||
font-family: "selectIcon" !important;
|
||||
font-size: 16px;
|
||||
font-style: normal;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
.icongou:before {
|
||||
content: "\e61c";
|
||||
}
|
||||
|
||||
.iconcross:before {
|
||||
content: "\e61a";
|
||||
}
|
||||
|
||||
</style>
|
||||
<style lang="scss" scoped>
|
||||
.main{
|
||||
font-size: 28rpx;
|
||||
text-align: right;
|
||||
}
|
||||
.bg-white{
|
||||
background-color: #FFFFFF;
|
||||
}
|
||||
.text-blue{
|
||||
color:#929292;
|
||||
}
|
||||
.text-green{
|
||||
color:#3c9cff;
|
||||
}
|
||||
.input {
|
||||
display: flex;
|
||||
align-items:center;
|
||||
font-size: 28rpx;
|
||||
// height: 60rpx;
|
||||
// padding: 10rpx 20rpx;
|
||||
// border-radius: 10rpx;
|
||||
// border-style: solid;
|
||||
// border-width: 1rpx;
|
||||
// border-color: rgba(0, 0, 0, 0.1);
|
||||
input{
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
.select-modal {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
z-index: 9999;
|
||||
opacity: 0;
|
||||
outline: 0;
|
||||
text-align: center;
|
||||
-ms-transform: scale(1.185);
|
||||
transform: scale(1.185);
|
||||
backface-visibility: hidden;
|
||||
perspective: 2000rpx;
|
||||
background: rgba(0, 0, 0, 0.6);
|
||||
transition: all 0.3s ease-in-out 0s;
|
||||
pointer-events: none;
|
||||
margin-bottom: -1000rpx;
|
||||
&::before {
|
||||
content: "\200B";
|
||||
display: inline-block;
|
||||
height: 100%;
|
||||
vertical-align: bottom;
|
||||
}
|
||||
.select-dialog {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
background-color: #f8f8f8;
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
border-radius: 0;
|
||||
// border-radius: 10px 10px 0px 0px;
|
||||
.select-content{
|
||||
// background-color: #F1F1F1;
|
||||
max-height: 440rpx;
|
||||
overflow:auto;
|
||||
.select-item{
|
||||
padding:30rpx 20rpx;
|
||||
display: flex;
|
||||
.title{
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.select-modal.show {
|
||||
opacity: 1;
|
||||
transition-duration: 0.3s;
|
||||
-ms-transform: scale(1);
|
||||
transform: scale(1);
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
pointer-events: auto;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.select-bar {
|
||||
padding: 0 20rpx;
|
||||
display: flex;
|
||||
position: relative;
|
||||
align-items: center;
|
||||
min-height: 80rpx;
|
||||
justify-content: space-between;
|
||||
.action {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
justify-content: center;
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user