提交demo
This commit is contained in:
@@ -0,0 +1,332 @@
|
||||
<template>
|
||||
<view class="page">
|
||||
<!-- 自定义导航(pages.json 里已设置 navigationStyle: custom) -->
|
||||
<!-- <uni-nav-bar leftText=" " @clickLeft="goBack" leftIcon="back" :fixed="true" :border="false" color="#fff" backgroundColor="#3F435B" :statusBar="true" title="车辆详情"></uni-nav-bar> -->
|
||||
<view class="content">
|
||||
<!-- 基础信息卡片 -->
|
||||
<view class="card">
|
||||
<view class="title-row">
|
||||
<text class="name">{{ detail.product_name }}</text>
|
||||
</view>
|
||||
<view class="kv">
|
||||
<view class="kv-item" @click="copyIfHas('车辆识别码', detail.vin)">
|
||||
<text class="k">车辆识别码</text>
|
||||
<text class="v ellipsis">{{ detail.vin }}</text>
|
||||
<text class="copy" v-if="detail.vin">复制</text>
|
||||
</view>
|
||||
|
||||
<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_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>
|
||||
</view>
|
||||
|
||||
<view class="kv-item">
|
||||
<text class="k">车身颜色</text>
|
||||
<text class="v">{{ detail.color }}</text>
|
||||
</view>
|
||||
|
||||
<view class="kv-item">
|
||||
<text class="k">创建时间</text>
|
||||
<text class="v">{{ detail.created_at }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<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="card">
|
||||
<view class="section-title">车况介绍</view>
|
||||
<text class="desc">{{ detail.condition_desc }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { request, showToast } from '@/api/apiT.js'
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
id: '',
|
||||
detail: {},
|
||||
currentImageIndex: 0
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
images() {
|
||||
// 兼容: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 []
|
||||
}
|
||||
},
|
||||
onLoad(options) {
|
||||
// 1) 支持通过 id 打开:/pages/index/detils?id=123
|
||||
this.id = options?.id || ''
|
||||
this.getshows()
|
||||
},
|
||||
methods: {
|
||||
//获取详情
|
||||
getshows(){
|
||||
|
||||
let that = this;
|
||||
request({
|
||||
url:'common/usedcar/show',
|
||||
method:'GET',
|
||||
data:{
|
||||
id:that.id
|
||||
}
|
||||
}).then(res => {
|
||||
if (res.data.code == 200) {
|
||||
this.detail = res.data.data
|
||||
}
|
||||
})
|
||||
},
|
||||
goBack() {
|
||||
uni.navigateBack({
|
||||
delta: 1,
|
||||
fail: () => {
|
||||
// 某些环境下没有历史栈,兜底回首页
|
||||
uni.reLaunch({ url: '/pages/index/index' })
|
||||
}
|
||||
})
|
||||
},
|
||||
previewImages(startIndex) {
|
||||
if (!this.images || this.images.length === 0) return
|
||||
this.currentImageIndex = startIndex || 0
|
||||
uni.previewImage({
|
||||
current: this.images[this.currentImageIndex],
|
||||
urls: this.images
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.page {
|
||||
min-height: 100vh;
|
||||
background: #f6f7fb;
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: 18rpx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
|
||||
.grid-img-single {
|
||||
width: 100%;
|
||||
height: 420rpx;
|
||||
border-radius: 14rpx;
|
||||
}
|
||||
|
||||
.hero-badges {
|
||||
position: absolute;
|
||||
left: 14rpx;
|
||||
bottom: 14rpx;
|
||||
display: flex;
|
||||
gap: 10rpx;
|
||||
}
|
||||
|
||||
.badge {
|
||||
padding: 10rpx 14rpx;
|
||||
border-radius: 999rpx;
|
||||
font-size: 24rpx;
|
||||
color: #fff;
|
||||
background: rgba(255, 151, 47, 0.95);
|
||||
}
|
||||
|
||||
.badge-ghost {
|
||||
background: rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
|
||||
.title-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.name {
|
||||
font-size: 34rpx;
|
||||
font-weight: 700;
|
||||
color: #111827;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.sub-row {
|
||||
margin-top: 10rpx;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 12rpx;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.sub {
|
||||
font-size: 24rpx;
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
|
||||
.ellipsis {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.copy {
|
||||
margin-left: 12rpx;
|
||||
font-size: 22rpx;
|
||||
color: #ff972f;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 28rpx;
|
||||
font-weight: 700;
|
||||
color: #111827;
|
||||
margin-bottom: 10rpx;
|
||||
}
|
||||
|
||||
.desc {
|
||||
font-size: 26rpx;
|
||||
color: #374151;
|
||||
line-height: 1.7;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
.footer {
|
||||
padding: 28rpx 0 40rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
gap: 14rpx;
|
||||
}
|
||||
|
||||
.footer-text {
|
||||
font-size: 24rpx;
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
.retry {
|
||||
padding: 14rpx 26rpx;
|
||||
border-radius: 999rpx;
|
||||
background: rgba(255, 151, 47, 0.12);
|
||||
color: #ff972f;
|
||||
font-size: 24rpx;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,249 @@
|
||||
<template>
|
||||
<view class="page">
|
||||
|
||||
|
||||
<!-- 列表 -->
|
||||
<z-paging ref="paging" v-model="list" @query="queryList" empty-view-text="暂无数据!">
|
||||
<!-- <uni-nav-bar leftText=" " :fixed="true" :border="false" color="#fff" backgroundColor="#3F435B" :statusBar="true" title="首页"></uni-nav-bar> -->
|
||||
<!-- 顶部:标题 + 搜索(不想要搜索可直接删除这一块) -->
|
||||
<view class="page-head">
|
||||
<view class="head-search">
|
||||
<u-search
|
||||
v-model="keyword"
|
||||
placeholder="请输入关键词"
|
||||
:show-action="false"
|
||||
shape="round"
|
||||
@change="onKeywordChange"
|
||||
@search="onSearch"
|
||||
@clear="onSearch"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
<view class="list-wrap">
|
||||
<view
|
||||
v-for="(item, idx) in list"
|
||||
:key="item.id "
|
||||
class="car-card"
|
||||
@tap="goDetail(item)"
|
||||
>
|
||||
<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">{{ ym(item.plate_time) }}</text>
|
||||
</view>
|
||||
<view class="kv">
|
||||
<text class="k">里程</text>
|
||||
<text class="v">{{ item.mileage }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</z-paging>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { request } from '@/api/apiT.js'
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
keyword: '',
|
||||
page: 1,
|
||||
pageSize: 10,
|
||||
loading: false,
|
||||
finished: false,
|
||||
list: []
|
||||
}
|
||||
},
|
||||
onLoad() {
|
||||
|
||||
},
|
||||
methods: {
|
||||
ym(dateStr) {
|
||||
return String(dateStr || '').slice(0, 7) // "2026-01"
|
||||
},
|
||||
onKeywordChange(val) {
|
||||
// val 是输入值(uView 通常会传)
|
||||
this.keyword = val
|
||||
|
||||
// 防抖:300ms 内持续输入只触发一次
|
||||
if (this._kwTimer) clearTimeout(this._kwTimer)
|
||||
this._kwTimer = setTimeout(() => {
|
||||
this.onSearch()
|
||||
}, 300)
|
||||
},
|
||||
onSearch() {
|
||||
this.queryList(this.page,this.pageSize)
|
||||
},
|
||||
queryList(pageNo, pageSize, from) {
|
||||
this.loading = true;
|
||||
var that = this;
|
||||
const params = {
|
||||
current:pageNo,
|
||||
status:1,
|
||||
size:10
|
||||
};
|
||||
params.keyword = this.keyword;
|
||||
request({
|
||||
url:'common/usedcar/list',
|
||||
method:'get',
|
||||
data: params
|
||||
}).then(res => {
|
||||
if(res.data.code ==200){
|
||||
var datas = res.data.data.records;
|
||||
this.$refs.paging.complete(datas);
|
||||
}else{
|
||||
}
|
||||
uni.stopPullDownRefresh();
|
||||
//
|
||||
})
|
||||
},
|
||||
goDetail(item) {
|
||||
// 你后续详情页如果要接 id,这里按后端字段改一下即可
|
||||
const id = item.id
|
||||
uni.navigateTo({
|
||||
url: `/pages/index/detils?id=${id}`
|
||||
})
|
||||
},
|
||||
formatMileage(v) {
|
||||
if (v === '-' || v === undefined || v === null || String(v).trim() === '') return '-'
|
||||
const n = Number(v)
|
||||
if (isNaN(n)) return String(v)
|
||||
// < 10000:直接显示;>= 10000:转万
|
||||
if (n >= 10000) {
|
||||
return `${(n / 10000).toFixed(1)}万 km`
|
||||
}
|
||||
return `${Math.round(n)} km`
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.page {
|
||||
min-height: 100vh;
|
||||
background: #f4f6f8;
|
||||
}
|
||||
|
||||
.page-head {
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
bottom: 50px;
|
||||
z-index: 99;
|
||||
// padding: 28rpx 24rpx 18rpx;
|
||||
// background: linear-gradient(180deg, rgba(255, 151, 47, 0.22) 0%, rgba(255, 151, 47, 0.06) 55%, #f4f6f8 100%);
|
||||
}
|
||||
|
||||
.head-search {
|
||||
background: rgba(255, 255, 255, 1);
|
||||
border-radius: 18rpx;
|
||||
padding: 20rpx 10px;
|
||||
box-shadow: 0 10rpx 28rpx rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
.list-wrap {
|
||||
padding: 18rpx 24rpx 40rpx;
|
||||
}
|
||||
|
||||
.car-card {
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
padding: 18rpx;
|
||||
border-radius: 18rpx;
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
box-shadow: 0 10rpx 30rpx rgba(0, 0, 0, 0.06);
|
||||
margin-bottom: 18rpx;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
|
||||
.loadmore {
|
||||
padding: 18rpx 0 6rpx;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,346 @@
|
||||
<template>
|
||||
<view class="page">
|
||||
<uni-nav-bar leftText=" " :fixed="true" :border="false" color="#fff" backgroundColor="#3F435B" :statusBar="true" title="我的"></uni-nav-bar>
|
||||
<!-- 顶部:标题 + 搜索(不想要搜索可直接删除这一块) -->
|
||||
<view class="page-head">
|
||||
<view class="head-search">
|
||||
<u-search
|
||||
v-model="keyword"
|
||||
placeholder="请输入关键词"
|
||||
:show-action="false"
|
||||
shape="round"
|
||||
@search="onSearch"
|
||||
@clear="onSearch"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 列表 -->
|
||||
<view class="list-wrap">
|
||||
<uni-swipe-action-item
|
||||
:threshold="0"
|
||||
:right-options="options1"
|
||||
@click="bindClick"
|
||||
>
|
||||
<view
|
||||
v-for="(item, idx) in list"
|
||||
:key="item.id || item.car_id || item.product_id || idx"
|
||||
class="car-card"
|
||||
@tap="goDetail(item)"
|
||||
>
|
||||
<image class="thumb" src="/static/image/car.png" mode="aspectFill" />
|
||||
<view class="info">
|
||||
<view class="row row1">
|
||||
<text class="name u-line-1">{{ item.name }}</text>
|
||||
</view>
|
||||
|
||||
<view class="row row2">
|
||||
<view class="kv">
|
||||
<text class="k">车源地</text>
|
||||
<text class="v u-line-1">{{ item.source }}</text>
|
||||
</view>
|
||||
<view class="kv">
|
||||
<text class="k">车身颜色</text>
|
||||
<text class="v u-line-1">{{ item.color }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="row row3">
|
||||
<view class="kv">
|
||||
<text class="k">上牌时间</text>
|
||||
<text class="v">{{ item.plate_time }}</text>
|
||||
</view>
|
||||
<view class="kv">
|
||||
<text class="k">里程</text>
|
||||
<text class="v">{{ item.mileage }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</uni-swipe-action-item>
|
||||
|
||||
|
||||
<!-- 空状态 -->
|
||||
<u-empty v-if="!loading && list.length === 0" text="暂无数据" mode="data" />
|
||||
|
||||
<!-- 底部加载状态 -->
|
||||
<view v-if="list.length > 0" class="loadmore">
|
||||
<u-loadmore :status="loadStatus" />
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { request } from '@/api/apiT.js'
|
||||
|
||||
/**
|
||||
* 首页列表接口(你如果后端接口不是这个路径,改这里即可)
|
||||
* 兼容分页:page/per_page(或 page/page_size)
|
||||
*/
|
||||
const LIST_API = 'car/list'
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
options1: [{
|
||||
text: '编辑',
|
||||
style: {
|
||||
backgroundColor: '#007aff'
|
||||
}
|
||||
},
|
||||
{
|
||||
text: '删除',
|
||||
style: {
|
||||
backgroundColor: '#F56C6C'
|
||||
}
|
||||
}
|
||||
],
|
||||
keyword: '',
|
||||
page: 1,
|
||||
pageSize: 10,
|
||||
loading: false,
|
||||
finished: false,
|
||||
list: [
|
||||
{
|
||||
name:'奥迪Q3 2013款 35 TFSI 舒适型',
|
||||
source:'北京',
|
||||
color:'黑色',
|
||||
plate_time:'2013-07',
|
||||
mileage:'11万公里'
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
loadStatus() {
|
||||
if (this.loading && this.page === 1) return 'loading'
|
||||
if (this.loading) return 'loading'
|
||||
if (this.finished) return 'nomore'
|
||||
return 'loadmore'
|
||||
}
|
||||
},
|
||||
onLoad() {
|
||||
this.fetchList(true)
|
||||
},
|
||||
onPullDownRefresh() {
|
||||
this.fetchList(true).finally(() => {
|
||||
uni.stopPullDownRefresh()
|
||||
})
|
||||
},
|
||||
onReachBottom() {
|
||||
if (!this.loading && !this.finished) {
|
||||
this.fetchList(false)
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onSearch() {
|
||||
this.fetchList(true)
|
||||
},
|
||||
async fetchList(reset = false) {
|
||||
if (this.loading) return
|
||||
this.loading = true
|
||||
try {
|
||||
if (reset) {
|
||||
this.page = 1
|
||||
this.finished = false
|
||||
}
|
||||
|
||||
const res = await request({
|
||||
url: LIST_API,
|
||||
method: 'GET',
|
||||
data: {
|
||||
page: this.page,
|
||||
per_page: this.pageSize,
|
||||
keyword: this.keyword || ''
|
||||
}
|
||||
})
|
||||
|
||||
const payload = (res && res.data) ? res.data : {}
|
||||
const data = payload.data || payload || {}
|
||||
|
||||
// 兼容不同后端结构:list / data / items / rows / records
|
||||
let rows =
|
||||
data.list ||
|
||||
data.data ||
|
||||
data.items ||
|
||||
data.rows ||
|
||||
data.records ||
|
||||
[]
|
||||
|
||||
if (!Array.isArray(rows)) {
|
||||
rows = []
|
||||
}
|
||||
|
||||
if (reset) this.list = []
|
||||
this.list = this.list.concat(rows)
|
||||
|
||||
// 兼容 total / last_page / has_more 等字段
|
||||
const hasMore =
|
||||
(data.has_more === true) ||
|
||||
(data.next_page_url ? true : false) ||
|
||||
(false)
|
||||
|
||||
if (rows.length < this.pageSize && !hasMore) {
|
||||
this.finished = true
|
||||
} else {
|
||||
this.page += 1
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('[home fetchList] error:', e)
|
||||
uni.showToast({ title: e?.message || '加载失败', icon: 'none' })
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
},
|
||||
|
||||
goDetail(item) {
|
||||
// 你后续详情页如果要接 id,这里按后端字段改一下即可
|
||||
const id = item.id || item.car_id || item.product_id || ''
|
||||
uni.navigateTo({
|
||||
url: `/pages/index/detils?id=${encodeURIComponent(id)}`
|
||||
})
|
||||
},
|
||||
formatPlate(v) {
|
||||
if (!v || v === '-') return '-'
|
||||
// 允许:时间戳 / yyyy-mm-dd / yyyy/mm/dd / yyyyMMdd
|
||||
let d
|
||||
if (typeof v === 'number' || /^\d+$/.test(String(v))) {
|
||||
const n = Number(v)
|
||||
// 秒/毫秒都兼容
|
||||
d = new Date(n < 1e12 ? n * 1000 : n)
|
||||
} else {
|
||||
const s = String(v)
|
||||
const s2 = s.replace(/\//g, '-').trim()
|
||||
if (/^\d{8}$/.test(s2)) {
|
||||
const y = s2.slice(0, 4)
|
||||
const m = s2.slice(4, 6)
|
||||
const dd = s2.slice(6, 8)
|
||||
d = new Date(`${y}-${m}-${dd}T00:00:00`)
|
||||
} else {
|
||||
d = new Date(s2)
|
||||
}
|
||||
}
|
||||
if (isNaN(d.getTime())) return String(v)
|
||||
const y = d.getFullYear()
|
||||
const m = String(d.getMonth() + 1).padStart(2, '0')
|
||||
return `${y}-${m}`
|
||||
},
|
||||
formatMileage(v) {
|
||||
if (v === '-' || v === undefined || v === null || String(v).trim() === '') return '-'
|
||||
const n = Number(v)
|
||||
if (isNaN(n)) return String(v)
|
||||
// < 10000:直接显示;>= 10000:转万
|
||||
if (n >= 10000) {
|
||||
return `${(n / 10000).toFixed(1)}万 km`
|
||||
}
|
||||
return `${Math.round(n)} km`
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.page {
|
||||
min-height: 100vh;
|
||||
background: #f4f6f8;
|
||||
}
|
||||
|
||||
.page-head {
|
||||
// position: fixed;
|
||||
// width: 100%;
|
||||
}
|
||||
.head-search {
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
// border-radius: 18rpx;
|
||||
padding: 6rpx;
|
||||
box-shadow: 0 10rpx 28rpx rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
.list-wrap {
|
||||
padding: 18rpx 24rpx 40rpx;
|
||||
}
|
||||
|
||||
.car-card {
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
padding: 18rpx;
|
||||
border-radius: 18rpx;
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
box-shadow: 0 10rpx 30rpx rgba(0, 0, 0, 0.06);
|
||||
// margin-bottom: 18rpx;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
|
||||
.loadmore {
|
||||
padding: 18rpx 0 6rpx;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,291 @@
|
||||
<template>
|
||||
<view class="page">
|
||||
|
||||
|
||||
<!-- 列表 -->
|
||||
<z-paging ref="paging" v-model="list" @query="queryList" empty-view-text="暂无数据!">
|
||||
<!-- <uni-nav-bar leftText=" " :fixed="true" :border="false" color="#fff" backgroundColor="#3F435B" :statusBar="true" title="我的"></uni-nav-bar> -->
|
||||
<!-- 顶部:标题 + 搜索(不想要搜索可直接删除这一块) -->
|
||||
<view class="page-head">
|
||||
<view class="head-search">
|
||||
<u-search
|
||||
v-model="keyword"
|
||||
placeholder="请输入关键词"
|
||||
:show-action="false"
|
||||
shape="round"
|
||||
@search="onSearch"
|
||||
@clear="onSearch"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
<view class="list-wrap">
|
||||
<uni-swipe-action-item
|
||||
v-for="(item, idx) in list"
|
||||
:key="item.id "
|
||||
:right-options="options1"
|
||||
@click="swipeClick($event, item)"
|
||||
>
|
||||
<view class="car-card" @click="goDetail(item)">
|
||||
<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">{{ ym(item.plate_time) }}</text>
|
||||
</view>
|
||||
<view class="kv">
|
||||
<text class="k">里程</text>
|
||||
<text class="v">{{ item.mileage }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</uni-swipe-action-item>
|
||||
</view>
|
||||
</z-paging>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { request } from '@/api/apiT.js'
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
options1: [{
|
||||
text: '编辑',
|
||||
style: {
|
||||
backgroundColor: '#007aff'
|
||||
}
|
||||
},
|
||||
{
|
||||
text: '删除',
|
||||
style: {
|
||||
backgroundColor: '#F56C6C'
|
||||
}
|
||||
}
|
||||
],
|
||||
keyword: '',
|
||||
page: 1,
|
||||
pageSize: 10,
|
||||
loading: false,
|
||||
finished: false,
|
||||
list: []
|
||||
}
|
||||
},
|
||||
onLoad() {
|
||||
|
||||
},
|
||||
methods: {
|
||||
ym(dateStr) {
|
||||
return String(dateStr || '').slice(0, 7) // "2026-01"
|
||||
},
|
||||
onSearch() {
|
||||
this.queryList(this.page,this.pageSize)
|
||||
},
|
||||
queryList(pageNo, pageSize, from) {
|
||||
this.loading = true;
|
||||
var that = this;
|
||||
const params = {
|
||||
current:pageNo,
|
||||
status:1,
|
||||
size:10
|
||||
};
|
||||
params.scene = 'self';
|
||||
params.keyword = this.keyword;
|
||||
request({
|
||||
url:'common/usedcar/list',
|
||||
method:'get',
|
||||
data: params
|
||||
}).then(res => {
|
||||
if(res.data.code ==200){
|
||||
var datas = res.data.data.records;
|
||||
this.$refs.paging.complete(datas);
|
||||
}else{
|
||||
}
|
||||
uni.stopPullDownRefresh();
|
||||
//
|
||||
})
|
||||
},
|
||||
goDetail(item) {
|
||||
// 你后续详情页如果要接 id,这里按后端字段改一下即可
|
||||
const id = item.id || item.car_id || item.product_id || ''
|
||||
uni.navigateTo({
|
||||
url: `/pages/index/detils?id=${id}`
|
||||
})
|
||||
},
|
||||
formatMileage(v) {
|
||||
if (v === '-' || v === undefined || v === null || String(v).trim() === '') return '-'
|
||||
const n = Number(v)
|
||||
if (isNaN(n)) return String(v)
|
||||
// < 10000:直接显示;>= 10000:转万
|
||||
if (n >= 10000) {
|
||||
return `${(n / 10000).toFixed(1)}万 km`
|
||||
}
|
||||
return `${Math.round(n)} km`
|
||||
},
|
||||
swipeClick(e,item){
|
||||
let {
|
||||
content
|
||||
} = e;
|
||||
if (content.text === '删除') {
|
||||
uni.showModal({
|
||||
title: '提示',
|
||||
content: '是否删除',
|
||||
success: res => {
|
||||
if (res.confirm) {
|
||||
request({
|
||||
url:'common/usedcar/destroy',
|
||||
method:'post',
|
||||
data: {id:item.id}
|
||||
}).then(res => {
|
||||
uni.showToast({
|
||||
title: res.data.message,
|
||||
icon: 'none'
|
||||
});
|
||||
this.queryList()
|
||||
})
|
||||
} else if (res.cancel) {
|
||||
console.log('用户点击取消');
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
const id = item.id
|
||||
uni.navigateTo({
|
||||
url: `/pages/index/publishedit?id=${id}`
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.page {
|
||||
min-height: 100vh;
|
||||
background: #f4f6f8;
|
||||
}
|
||||
|
||||
.page-head {
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
bottom: 50px;
|
||||
z-index: 99;
|
||||
// padding: 28rpx 24rpx 18rpx;
|
||||
// background: linear-gradient(180deg, rgba(255, 151, 47, 0.22) 0%, rgba(255, 151, 47, 0.06) 55%, #f4f6f8 100%);
|
||||
}
|
||||
|
||||
.head-search {
|
||||
background: rgba(255, 255, 255, 1);
|
||||
border-radius: 18rpx;
|
||||
padding: 20rpx 10px;
|
||||
box-shadow: 0 10rpx 28rpx rgba(0, 0, 0, 0.06);
|
||||
|
||||
}
|
||||
|
||||
.list-wrap {
|
||||
padding: 18rpx 24rpx 40rpx;
|
||||
}
|
||||
|
||||
.car-card {
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
padding: 18rpx;
|
||||
border-radius: 18rpx;
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
box-shadow: 0 10rpx 30rpx rgba(0, 0, 0, 0.06);
|
||||
// margin-bottom: 18rpx;
|
||||
}
|
||||
.uni-swipe{
|
||||
margin-bottom: 18rpx;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
|
||||
.loadmore {
|
||||
padding: 18rpx 0 6rpx;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,321 @@
|
||||
<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">
|
||||
<input placeholder="请输入产品名称" v-model="carinfo.product_name" />
|
||||
</view>
|
||||
</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.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>
|
||||
<view class="zw">
|
||||
<button class="btn" @click="draftadd">提交</button>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
<script>
|
||||
import { request,showToast } from '@/api/apiT.js'
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
maxMedia:20,
|
||||
carinfo:{
|
||||
product_name:'',
|
||||
vin:'',
|
||||
price:'',
|
||||
source_place:'',
|
||||
plate_place:'',
|
||||
plate_time:'',
|
||||
displacement:'',
|
||||
body_color:'',
|
||||
images:[],
|
||||
condition_desc:'',
|
||||
mileage:''
|
||||
}
|
||||
}
|
||||
},
|
||||
onLoad() {
|
||||
},
|
||||
methods: {
|
||||
onBorrowTimeChange(e) {
|
||||
this.carinfo.plate_time = e
|
||||
},
|
||||
uploadfn(imgUrl){
|
||||
return new Promise((resolve, reject) => {
|
||||
uni.uploadFile({
|
||||
url: 'https://usedcar.dev.hnxinyouhe.com/upload/image?scene=usedcarimg',
|
||||
filePath: imgUrl,
|
||||
name: 'image',
|
||||
header:{'Fdesk-Auth-Token': uni.getStorageSync('token'),'Fdesk-Login-Type': 'feishu','Fdesk-System-Code':'fdesk'},
|
||||
success: (uploadFileRes) => {
|
||||
const res = JSON.parse(uploadFileRes.data || '{}')
|
||||
if(res && res.data && res.data.href){
|
||||
resolve(res.data.href)
|
||||
}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))).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)
|
||||
},
|
||||
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: 18px 20px;
|
||||
/* 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;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,422 @@
|
||||
<template>
|
||||
<view>
|
||||
<!-- <uni-nav-bar leftText=" " @clickLeft="leftClick" leftIcon="back" :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">
|
||||
<input placeholder="请输入产品名称" v-model="carinfo.product_name" />
|
||||
</view>
|
||||
</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.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"
|
||||
v-model="fileList"
|
||||
:limit="maxMedia"
|
||||
file-mediatype="image"
|
||||
mode="grid"
|
||||
:auto-upload="false"
|
||||
@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>
|
||||
<view class="zw">
|
||||
<button class="btn" @click="draftadd">提交</button>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
<script>
|
||||
import { request,showToast } from '@/api/apiT.js'
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
maxMedia:20,
|
||||
// uni-file-picker 回显/展示用(url/name/extname)
|
||||
fileList: [],
|
||||
id:'',
|
||||
_uploading:false,
|
||||
carinfo:{
|
||||
id:'',
|
||||
product_name:'',
|
||||
vin:'',
|
||||
price:'',
|
||||
source_place:'',
|
||||
plate_place:'',
|
||||
plate_time:'',
|
||||
displacement:'',
|
||||
body_color:'',
|
||||
// 提交给后端用:相对路径数组(最终 join(','))
|
||||
images:[],
|
||||
condition_desc:'',
|
||||
mileage:''
|
||||
}
|
||||
}
|
||||
},
|
||||
onLoad(options) {
|
||||
this.id = options?.id || ''
|
||||
this.getshows()
|
||||
},
|
||||
methods: {
|
||||
leftClick() {
|
||||
uni.navigateBack()
|
||||
},
|
||||
getshows(){
|
||||
let that = this;
|
||||
request({
|
||||
url:'common/usedcar/show',
|
||||
method:'GET',
|
||||
data:{
|
||||
id:that.id
|
||||
}
|
||||
}).then(res => {
|
||||
if (res.data.code == 200) {
|
||||
const d = res.data.data || {}
|
||||
// 1) images:后端返回的是逗号字符串,这里转成数组(用于编辑提交)
|
||||
const keys = String(d.images || '')
|
||||
.split(',')
|
||||
.map(s => String(s || '').trim())
|
||||
.filter(Boolean)
|
||||
|
||||
// 2) 表单字段
|
||||
this.carinfo = {
|
||||
...this.carinfo,
|
||||
...d,
|
||||
images: keys
|
||||
}
|
||||
|
||||
// 3) 图片回显:优先用 images_pull(签名URL用于显示)
|
||||
const pulls = Array.isArray(d.images_pull) && d.images_pull.length
|
||||
? d.images_pull
|
||||
: keys
|
||||
this.fileList = pulls.map((u, idx) => {
|
||||
const raw = String(u || '')
|
||||
const filename = raw.split('?')[0].split('/').pop() || `img_${idx + 1}`
|
||||
const ext = filename.includes('.') ? filename.split('.').pop() : ''
|
||||
return { url: raw, name: filename, extname: ext }
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
onBorrowTimeChange(e) {
|
||||
this.carinfo.plate_time = e
|
||||
},
|
||||
uploadfn(imgUrl){
|
||||
return new Promise((resolve, reject) => {
|
||||
uni.uploadFile({
|
||||
url: 'https://usedcar.dev.hnxinyouhe.com/upload/image?scene=usedcarimg',
|
||||
filePath: imgUrl,
|
||||
name: 'image',
|
||||
header:{'Fdesk-Auth-Token': uni.getStorageSync('token'),'Fdesk-Login-Type': 'feishu','Fdesk-System-Code':'fdesk'},
|
||||
success: (uploadFileRes) => {
|
||||
const res = JSON.parse(uploadFileRes.data || '{}')
|
||||
if(res && res.data && res.data.href){
|
||||
resolve(res.data.href)
|
||||
}else{
|
||||
reject('上传失败')
|
||||
}
|
||||
},
|
||||
fail: (err) => reject(err)
|
||||
});
|
||||
})
|
||||
},
|
||||
selectMedia(e){
|
||||
const allowCount = this.maxMedia - (this.carinfo.images?.length || 0)
|
||||
if(allowCount <= 0){
|
||||
uni.showToast({
|
||||
title:`最多只能上传${this.maxMedia}张图片`,
|
||||
icon:'none'
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
const tempFiles = e.tempFiles || []
|
||||
const tempFilePaths = (tempFiles.length
|
||||
? tempFiles.map(f => f.path || f.url).filter(Boolean)
|
||||
: (e.tempFilePaths || [])
|
||||
)
|
||||
const paths = tempFilePaths.slice(0, allowCount)
|
||||
if(paths.length === 0) return
|
||||
if(this._uploading) return
|
||||
this._uploading = true
|
||||
|
||||
// 先占位,保证 delete 的 index 与 images 数组对得上
|
||||
const startAt = this.carinfo.images.length
|
||||
for(let i=0;i<paths.length;i++){
|
||||
this.carinfo.images.push('__PENDING__')
|
||||
}
|
||||
|
||||
Promise.all(paths.map(p => this.uploadfn(p)))
|
||||
.then(keys => {
|
||||
keys.forEach((key, i) => {
|
||||
// 替换占位
|
||||
this.$set(this.carinfo.images, startAt + i, String(key))
|
||||
})
|
||||
})
|
||||
.catch(() => {
|
||||
// 失败:移除占位,同时把 fileList 对应项删掉(避免页面“假回显”)
|
||||
this.carinfo.images.splice(startAt, paths.length)
|
||||
if(Array.isArray(this.fileList) && this.fileList.length >= startAt + paths.length){
|
||||
this.fileList.splice(startAt, paths.length)
|
||||
}
|
||||
uni.showToast({
|
||||
title:'图片上传失败,请重试',
|
||||
icon:'none'
|
||||
})
|
||||
})
|
||||
.finally(() => {
|
||||
this._uploading = false
|
||||
})
|
||||
},
|
||||
delfn(e){
|
||||
// fileList 会由 uni-file-picker 自己维护;这里同步删除提交用的 images
|
||||
this.carinfo.images.splice(e.index, 1)
|
||||
},
|
||||
draftadd(){
|
||||
const payload = {
|
||||
...this.carinfo,
|
||||
// images 数组 => "url1,url2,url3"
|
||||
images: (this.carinfo.images || [])
|
||||
.map(s => String(s || '').trim())
|
||||
.filter(s => !!s && s !== '__PENDING__')
|
||||
.join(',')
|
||||
}
|
||||
request({
|
||||
url:'common/usedcar/update',
|
||||
method:'post',
|
||||
data:{
|
||||
id:payload.id,
|
||||
product_name:payload.product_name,
|
||||
vin:payload.vin,
|
||||
price:payload.price,
|
||||
source_place:payload.source_place,
|
||||
plate_place:payload.plate_place,
|
||||
plate_time:payload.plate_time,
|
||||
displacement:payload.displacement,
|
||||
body_color:payload.body_color,
|
||||
images:payload.images,
|
||||
condition_desc:payload.condition_desc,
|
||||
mileage:payload.mileage
|
||||
}
|
||||
}).then(res => {
|
||||
uni.showToast({
|
||||
title: res.data.message,
|
||||
icon: 'none'
|
||||
});
|
||||
setTimeout(() => {
|
||||
uni.reLaunch({
|
||||
url: '/pages/index/mine',// 替换为你要跳转的页面路径
|
||||
});
|
||||
}, 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: 18px 20px;
|
||||
/* 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;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,226 @@
|
||||
<template>
|
||||
<view class="auth-page">
|
||||
<view class="auth-card">
|
||||
<view v-if="status === 'loading'">
|
||||
<view class="spinner"></view>
|
||||
<view class="text-wrap">
|
||||
<text class="title">{{ title }}</text>
|
||||
<text class="desc">{{ desc }}<text class="dots">{{ dots }}</text></text>
|
||||
</view>
|
||||
<view class="progress">
|
||||
<view class="bar"></view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view v-else class="error-wrap">
|
||||
<uni-icons type="clear" size="44" color="#ff4d4f"></uni-icons>
|
||||
<text class="title">登录失败</text>
|
||||
<text class="desc">{{ errorMsg }}</text>
|
||||
<button class="retry-btn" @click="retry">重试</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { request, showToast } from '@/api/apiT.js'
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
status: 'loading',
|
||||
title: '正在完成登录',
|
||||
desc: '处理中',
|
||||
dots: '',
|
||||
errorMsg: '请稍后重试',
|
||||
_dotTimer: null,
|
||||
minStayMs: 600,
|
||||
_startedAt: 0,
|
||||
_code: ''
|
||||
}
|
||||
},
|
||||
onLoad(options) {
|
||||
this._startedAt = Date.now()
|
||||
this.startDots()
|
||||
// #ifdef H5
|
||||
this._code = options.code || ''
|
||||
// #endif
|
||||
|
||||
if (!this._code) {
|
||||
this.fail('缺少 code 参数')
|
||||
return
|
||||
}
|
||||
this.logins(this._code)
|
||||
},
|
||||
onUnload() {
|
||||
this.stopDots()
|
||||
},
|
||||
methods: {
|
||||
startDots() {
|
||||
this.stopDots()
|
||||
let n = 0
|
||||
this._dotTimer = setInterval(() => {
|
||||
n = (n + 1) % 4
|
||||
this.dots = '.'.repeat(n)
|
||||
}, 350)
|
||||
},
|
||||
stopDots() {
|
||||
if (this._dotTimer) {
|
||||
clearInterval(this._dotTimer)
|
||||
this._dotTimer = null
|
||||
}
|
||||
},
|
||||
fail(msg) {
|
||||
this.status = 'error'
|
||||
this.errorMsg = msg || '请稍后重试'
|
||||
uni.showToast({ title: msg || '登录失败', icon: 'none' })
|
||||
},
|
||||
async logins(code) {
|
||||
try {
|
||||
this.title = '正在交换登录凭证'
|
||||
this.desc = '请稍候'
|
||||
|
||||
const res = await request({
|
||||
url: 'common/auth/login',
|
||||
method: 'POST',
|
||||
data: { feishu_code: code,app_id:uni.getStorageSync('fsappid')}
|
||||
})
|
||||
|
||||
const token = res?.data?.data?.union_id
|
||||
if (!token) {
|
||||
throw new Error(res?.data?.msg || '未获取到 access_token')
|
||||
}
|
||||
|
||||
uni.setStorageSync('token', token)
|
||||
|
||||
this.title = '登录成功'
|
||||
this.desc = '正在进入首页'
|
||||
|
||||
this.goHomeSmooth()
|
||||
} catch (err) {
|
||||
console.error('[feishu callback] error:', err)
|
||||
this.fail(err?.message || '登录失败')
|
||||
}
|
||||
},
|
||||
goHomeSmooth() {
|
||||
const elapsed = Date.now() - this._startedAt
|
||||
const wait = Math.max(0, this.minStayMs - elapsed)
|
||||
setTimeout(() => {
|
||||
uni.reLaunch({ url: '/pages/index/index' })
|
||||
}, wait)
|
||||
},
|
||||
retry() {
|
||||
this.status = 'loading'
|
||||
this.title = '正在完成登录'
|
||||
this.desc = '处理中'
|
||||
this.startDots()
|
||||
if (this._code) this.logins(this._code)
|
||||
else this.fail('缺少 code 参数')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.auth-page {
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 40rpx;
|
||||
box-sizing: border-box;
|
||||
background: linear-gradient(180deg, #FF972F 0%, #F4F6F8 38%, #F4F6F8 100%);
|
||||
}
|
||||
|
||||
.auth-card {
|
||||
width: 100%;
|
||||
max-width: 640rpx;
|
||||
background: rgba(255, 255, 255, 0.92);
|
||||
border-radius: 20rpx;
|
||||
padding: 44rpx 36rpx 32rpx;
|
||||
box-shadow: 0 10rpx 40rpx rgba(0, 0, 0, 0.08);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
|
||||
.spinner {
|
||||
width: 88rpx;
|
||||
height: 88rpx;
|
||||
border-radius: 50%;
|
||||
border: 8rpx solid rgba(255,151,47, 0.18);
|
||||
border-top-color: #FF972F;
|
||||
animation: spin 0.9s linear infinite;
|
||||
margin-bottom: 24rpx;
|
||||
}
|
||||
|
||||
.text-wrap {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.title {
|
||||
display: block;
|
||||
font-size: 34rpx;
|
||||
color: #1f2937;
|
||||
font-weight: 600;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.desc {
|
||||
display: block;
|
||||
font-size: 26rpx;
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
.dots {
|
||||
display: inline-block;
|
||||
width: 40rpx;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.progress {
|
||||
width: 100%;
|
||||
height: 10rpx;
|
||||
border-radius: 999rpx;
|
||||
background: rgba(255,151,47, 0.10);
|
||||
overflow: hidden;
|
||||
margin-top: 26rpx;
|
||||
}
|
||||
|
||||
.progress .bar {
|
||||
height: 100%;
|
||||
width: 40%;
|
||||
border-radius: 999rpx;
|
||||
background: rgba(255,151,47, 0.55);
|
||||
animation: move 1.1s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.error-wrap {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.retry-btn {
|
||||
margin-top: 24rpx;
|
||||
width: 240rpx;
|
||||
height: 72rpx;
|
||||
line-height: 72rpx;
|
||||
border-radius: 999rpx;
|
||||
background: #FF972F;
|
||||
color: #fff;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
@keyframes move {
|
||||
0% { transform: translateX(-60%); }
|
||||
50% { transform: translateX(140%); }
|
||||
100% { transform: translateX(-60%); }
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,207 @@
|
||||
<template>
|
||||
<view class="auth-page">
|
||||
<view class="auth-card">
|
||||
<!-- 这里如果你有站点 logo,可替换为自己的路径;没有也不影响 -->
|
||||
<!-- <image class="logo" src="/static/logo.png" mode="heightFix"></image> -->
|
||||
|
||||
<view class="spinner"></view>
|
||||
|
||||
<view class="text-wrap">
|
||||
<text class="title">{{ title }}</text>
|
||||
<text class="desc">{{ desc }}<text class="dots">{{ dots }}</text></text>
|
||||
</view>
|
||||
|
||||
<view class="progress">
|
||||
<view class="bar"></view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { request, showToast } from '@/api/apiT.js'
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
fsappid: '',
|
||||
title: '正在登录',
|
||||
desc: '加载中',
|
||||
dots: '',
|
||||
_dotTimer: null,
|
||||
// 最短停留时间,避免“闪一下就跳”的割裂感
|
||||
minStayMs: 600,
|
||||
_startedAt: 0
|
||||
}
|
||||
},
|
||||
onLoad(options) {
|
||||
this.fsappid = options.appid || '';
|
||||
uni.setStorageSync('fsappid', this.fsappid);
|
||||
this._startedAt = Date.now()
|
||||
this.startDots()
|
||||
this.silentAuth()
|
||||
},
|
||||
onUnload() {
|
||||
this.stopDots()
|
||||
},
|
||||
methods: {
|
||||
startDots() {
|
||||
this.stopDots()
|
||||
let n = 0
|
||||
this._dotTimer = setInterval(() => {
|
||||
n = (n + 1) % 4
|
||||
this.dots = '.'.repeat(n)
|
||||
}, 350)
|
||||
},
|
||||
stopDots() {
|
||||
if (this._dotTimer) {
|
||||
clearInterval(this._dotTimer)
|
||||
this._dotTimer = null
|
||||
}
|
||||
},
|
||||
async silentAuth() {
|
||||
try {
|
||||
// 1) 如果本地已有 token,直接进首页(丝滑)
|
||||
const token = uni.getStorageSync('token')
|
||||
if (token) {
|
||||
this.title = '已登录'
|
||||
this.desc = '正在进入首页'
|
||||
return this.goHomeSmooth()
|
||||
}
|
||||
|
||||
// 2) 获取飞书授权跳转地址(静默授权/免登)
|
||||
this.title = '正在跳转飞书授权'
|
||||
this.desc = '准备中'
|
||||
const res = await request({
|
||||
url: 'common/auth/feishu/scope',
|
||||
method: 'get',
|
||||
data:{app_id:this.fsappid}
|
||||
})
|
||||
const auth_url = res?.data?.data?.auth_url
|
||||
if (!auth_url) {
|
||||
throw new Error('未获取到授权地址')
|
||||
}
|
||||
|
||||
// 3) 给一点点过渡时间,让动画先跑起来再跳转
|
||||
await this.sleep(180)
|
||||
|
||||
// 仅 H5 才能用 window.location
|
||||
// #ifdef H5
|
||||
window.location.replace(auth_url)
|
||||
// #endif
|
||||
|
||||
// #ifndef H5
|
||||
showToast && showToast('仅 H5 环境支持飞书静默授权')
|
||||
// #endif
|
||||
} catch (err) {
|
||||
console.error('[feishu silentAuth] error:', err)
|
||||
uni.showToast({ title: err?.message || '登录失败', icon: 'none' })
|
||||
}
|
||||
},
|
||||
goHomeSmooth() {
|
||||
const elapsed = Date.now() - this._startedAt
|
||||
const wait = Math.max(0, this.minStayMs - elapsed)
|
||||
setTimeout(() => {
|
||||
uni.reLaunch({
|
||||
url: '/pages/index/index'
|
||||
})
|
||||
}, wait)
|
||||
},
|
||||
sleep(ms) {
|
||||
return new Promise(resolve => setTimeout(resolve, ms))
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.auth-page {
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 40rpx;
|
||||
box-sizing: border-box;
|
||||
/* 更“高级”的登录背景 */
|
||||
background: linear-gradient(180deg, #FF972F 0%, #F4F6F8 38%, #F4F6F8 100%);
|
||||
}
|
||||
|
||||
.auth-card {
|
||||
width: 100%;
|
||||
max-width: 640rpx;
|
||||
background: rgba(255, 255, 255, 0.92);
|
||||
border-radius: 20rpx;
|
||||
padding: 44rpx 36rpx 32rpx;
|
||||
box-shadow: 0 10rpx 40rpx rgba(0, 0, 0, 0.08);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
|
||||
.logo {
|
||||
height: 72rpx;
|
||||
margin-bottom: 26rpx;
|
||||
}
|
||||
|
||||
.spinner {
|
||||
width: 88rpx;
|
||||
height: 88rpx;
|
||||
border-radius: 50%;
|
||||
border: 8rpx solid rgba(255,151,47, 0.18);
|
||||
border-top-color: #FF972F;
|
||||
animation: spin 0.9s linear infinite;
|
||||
margin-bottom: 24rpx;
|
||||
}
|
||||
|
||||
.text-wrap {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.title {
|
||||
display: block;
|
||||
font-size: 34rpx;
|
||||
color: #1f2937;
|
||||
font-weight: 600;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.desc {
|
||||
display: block;
|
||||
font-size: 26rpx;
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
.dots {
|
||||
display: inline-block;
|
||||
width: 40rpx; /* 固定宽度避免文字抖动 */
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.progress {
|
||||
width: 100%;
|
||||
height: 10rpx;
|
||||
border-radius: 999rpx;
|
||||
background: rgba(255, 151, 47, 0.10);
|
||||
overflow: hidden;
|
||||
margin-top: 26rpx;
|
||||
}
|
||||
|
||||
.progress .bar {
|
||||
height: 100%;
|
||||
width: 40%;
|
||||
border-radius: 999rpx;
|
||||
background: rgba(255, 151, 47, 0.55);
|
||||
animation: move 1.1s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
@keyframes move {
|
||||
0% { transform: translateX(-60%); }
|
||||
50% { transform: translateX(140%); }
|
||||
100% { transform: translateX(-60%); }
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user