631 lines
13 KiB
Vue
631 lines
13 KiB
Vue
<template>
|
||
<view class="page-container">
|
||
<!-- 筛选区域 -->
|
||
<view class="filter-section">
|
||
<view class="filter-card">
|
||
<view class="filter-row">
|
||
<view class="filter-item">
|
||
<text class="filter-label">平台</text>
|
||
<picker class="filter-picker" @change="handlePlatformChange" :value="platformIndex" :range="platformList">
|
||
<view class="picker-value">
|
||
<text class="picker-text">{{ platformList[platformIndex] }}</text>
|
||
<text class="picker-arrow">▼</text>
|
||
</view>
|
||
</picker>
|
||
</view>
|
||
<view class="filter-item">
|
||
<text class="filter-label">店铺</text>
|
||
<picker class="filter-picker" @change="handleShopChange" :value="shopIndex" :range="shopNames">
|
||
<view class="picker-value">
|
||
<text class="picker-text">{{ shopNames[shopIndex] }}</text>
|
||
<text class="picker-arrow">▼</text>
|
||
</view>
|
||
</picker>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 统计信息 -->
|
||
<view class="stats-section">
|
||
<view class="stats-card">
|
||
<view class="stats-item">
|
||
<text class="stats-num success">{{ stats.successCount }}</text>
|
||
<text class="stats-label">已发送</text>
|
||
</view>
|
||
<view class="stats-divider"></view>
|
||
<view class="stats-item">
|
||
<text class="stats-num pending">{{ stats.notSentCount }}</text>
|
||
<text class="stats-label">未发送</text>
|
||
</view>
|
||
<view class="stats-divider"></view>
|
||
<view class="stats-item">
|
||
<text class="stats-num failed">{{ stats.failedCount }}</text>
|
||
<text class="stats-label">失败</text>
|
||
</view>
|
||
<view class="stats-divider"></view>
|
||
<view class="stats-item">
|
||
<text class="stats-num total">{{ stats.total }}</text>
|
||
<text class="stats-label">总计</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 记录列表 -->
|
||
<scroll-view class="record-scroll" scroll-y :refresher-enabled="true" :refresher-triggered="isRefreshing" @refresherrefresh="onRefresh" @scrolltolower="loadMore">
|
||
<view class="record-list">
|
||
<view class="record-item" v-for="(item, index) in recordList" :key="index">
|
||
<view class="record-main">
|
||
<!-- 图片 -->
|
||
<view class="record-image" @click="previewImage(item.live_image)">
|
||
<image class="book-image" :src="getFirstImage(item.live_image)" mode="aspectFill"></image>
|
||
</view>
|
||
<!-- 基本信息 -->
|
||
<view class="record-info">
|
||
<view class="info-row">
|
||
<text class="book-name">{{ item.name }}</text>
|
||
</view>
|
||
<view class="info-row">
|
||
<text class="info-label">ISBN:</text>
|
||
<text class="info-value">{{ item.barcode || '-' }}</text>
|
||
</view>
|
||
<view class="info-row">
|
||
<text class="info-label">波次:</text>
|
||
<text class="info-value">{{ item.wave_no || '-' }}</text>
|
||
</view>
|
||
<view class="info-row">
|
||
<text class="info-label">货位:</text>
|
||
<text class="info-value">{{ item.warehouse_name }}{{ item.location_code ? ' - ' + item.location_code : '' }}</text>
|
||
</view>
|
||
<view class="info-row">
|
||
<text class="info-label">时间:</text>
|
||
<text class="info-value">{{ formatTime(item.created_at) }}</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
<!-- 价格和库存 -->
|
||
<view class="record-detail">
|
||
<view class="detail-row">
|
||
<view class="detail-item">
|
||
<text class="detail-label">定价</text>
|
||
<text class="detail-value">¥{{ formatPrice(item.price) }}</text>
|
||
</view>
|
||
<view class="detail-item">
|
||
<text class="detail-label">售价</text>
|
||
<text class="detail-value price">¥{{ formatPrice(item.sale_price) }}</text>
|
||
</view>
|
||
<view class="detail-item">
|
||
<text class="detail-label">库存</text>
|
||
<text class="detail-value">{{ item.quantity }}本</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
<!-- 状态 -->
|
||
<view class="publish-status">
|
||
<view class="status-row">
|
||
<text class="status-tag" :class="getStatusClass(item.status_in_shop)">{{ item.msg || getStatusText(item.status_in_shop) }}</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 加载更多 -->
|
||
<view class="load-more" v-if="loadingMore">
|
||
<view class="loading-spinner"></view>
|
||
<text class="load-more-text">加载中...</text>
|
||
</view>
|
||
<view class="load-more" v-if="!hasMore && recordList.length > 0">
|
||
<text class="no-more-text">— 已全部加载 —</text>
|
||
</view>
|
||
|
||
<!-- 空状态 -->
|
||
<view class="empty-state" v-if="recordList.length === 0 && !isLoading">
|
||
<text class="empty-icon">📭</text>
|
||
<text class="empty-text">暂无上书记录</text>
|
||
</view>
|
||
</scroll-view>
|
||
</view>
|
||
</template>
|
||
|
||
<script>
|
||
import { getShopList, getShopDetail } from '@/utils/api.js'
|
||
|
||
export default {
|
||
data() {
|
||
return {
|
||
// 平台筛选
|
||
platformIndex: 0,
|
||
platformList: ['全部平台', '拼多多', '孔夫子', '闲鱼'],
|
||
platformTypes: [0, 1, 2, 5],
|
||
|
||
// 店铺筛选
|
||
shopIndex: 0,
|
||
shopList: [],
|
||
shopNames: ['全部店铺'],
|
||
|
||
// 分页
|
||
page: 1,
|
||
pageSize: 10,
|
||
hasMore: true,
|
||
isLoading: false,
|
||
loadingMore: false,
|
||
isRefreshing: false,
|
||
|
||
// 数据
|
||
recordList: [],
|
||
stats: { successCount: 0, notSentCount: 0, failedCount: 0, total: 0 }
|
||
}
|
||
},
|
||
|
||
onLoad() {
|
||
uni.setNavigationBarTitle({ title: '上书记录' })
|
||
this.loadShopList()
|
||
this.fetchRecords()
|
||
},
|
||
|
||
methods: {
|
||
// 加载店铺列表
|
||
async loadShopList() {
|
||
try {
|
||
const platformType = this.platformTypes[this.platformIndex]
|
||
const params = { pageNum: 1, pageSize: 100 }
|
||
if (platformType > 0) params.shop_type = String(platformType)
|
||
const res = await getShopList(params)
|
||
this.shopList = res.list || []
|
||
this.updateShopNames()
|
||
} catch (e) {
|
||
console.error('加载店铺列表失败:', e)
|
||
}
|
||
},
|
||
|
||
// 更新店铺名下拉列表
|
||
updateShopNames() {
|
||
const names = ['全部店铺']
|
||
this.shopList.forEach(function(s) {
|
||
names.push(s.shop_alias_name || s.shop_name || '未命名')
|
||
})
|
||
this.shopNames = names
|
||
if (this.shopIndex >= names.length) {
|
||
this.shopIndex = 0
|
||
}
|
||
},
|
||
|
||
// 平台切换
|
||
handlePlatformChange(e) {
|
||
this.platformIndex = e.detail.value
|
||
this.shopIndex = 0
|
||
this.loadShopList()
|
||
this.resetAndFetch()
|
||
},
|
||
|
||
// 店铺切换
|
||
handleShopChange(e) {
|
||
this.shopIndex = e.detail.value
|
||
this.resetAndFetch()
|
||
},
|
||
|
||
// 重置分页并加载
|
||
resetAndFetch() {
|
||
this.page = 1
|
||
this.hasMore = true
|
||
this.recordList = []
|
||
this.stats = { successCount: 0, notSentCount: 0, failedCount: 0, total: 0 }
|
||
this.fetchRecords()
|
||
},
|
||
|
||
// 获取当前选中的店铺ID
|
||
getSelectedShopId() {
|
||
if (this.shopIndex <= 0) return ''
|
||
const shop = this.shopList[this.shopIndex - 1]
|
||
return shop ? shop.id : ''
|
||
},
|
||
|
||
// 获取上书记录
|
||
async fetchRecords() {
|
||
if (this.isLoading || this.loadingMore) return
|
||
this.isLoading = true
|
||
try {
|
||
const shopId = this.getSelectedShopId()
|
||
if (!shopId) {
|
||
this.isLoading = false
|
||
this.recordList = []
|
||
this.hasMore = false
|
||
return
|
||
}
|
||
const params = {
|
||
page: this.page,
|
||
page_size: this.pageSize,
|
||
shop_id: shopId
|
||
}
|
||
const res = await getShopDetail(params)
|
||
const products = res.products || []
|
||
if (this.page === 1) {
|
||
this.recordList = products
|
||
} else {
|
||
this.recordList = this.recordList.concat(products)
|
||
}
|
||
this.hasMore = products.length >= this.pageSize
|
||
this.stats = {
|
||
successCount: res.success_count || 0,
|
||
notSentCount: res.not_sent_count || 0,
|
||
failedCount: res.failed_count || 0,
|
||
total: res.total || 0
|
||
}
|
||
} catch (e) {
|
||
console.error('获取上书记录失败:', e)
|
||
} finally {
|
||
this.isLoading = false
|
||
this.loadingMore = false
|
||
this.isRefreshing = false
|
||
}
|
||
},
|
||
|
||
// 下拉刷新
|
||
onRefresh() {
|
||
this.isRefreshing = true
|
||
this.page = 1
|
||
this.hasMore = true
|
||
this.fetchRecords()
|
||
},
|
||
|
||
// 上拉加载更多
|
||
loadMore() {
|
||
if (this.loadingMore || !this.hasMore) return
|
||
this.loadingMore = true
|
||
this.page++
|
||
this.fetchRecords()
|
||
},
|
||
|
||
// 获取第一张图片
|
||
getFirstImage(images) {
|
||
if (!images) return ''
|
||
if (typeof images === 'string') return images
|
||
return images[0] || ''
|
||
},
|
||
|
||
// 格式化价格(分→元)
|
||
formatPrice(price) {
|
||
if (!price && price !== 0) return '0.00'
|
||
return (parseFloat(price) / 100).toFixed(2)
|
||
},
|
||
|
||
// 格式化时间(时间戳→日期)
|
||
formatTime(timestamp) {
|
||
if (!timestamp) return '-'
|
||
const date = new Date(parseInt(timestamp) * 1000)
|
||
const y = date.getFullYear()
|
||
const m = String(date.getMonth() + 1).padStart(2, '0')
|
||
const d = String(date.getDate()).padStart(2, '0')
|
||
const h = String(date.getHours()).padStart(2, '0')
|
||
const mi = String(date.getMinutes()).padStart(2, '0')
|
||
return y + '-' + m + '-' + d + ' ' + h + ':' + mi
|
||
},
|
||
|
||
// 获取状态CSS类
|
||
getStatusClass(status) {
|
||
if (status === 1) return 'status-success'
|
||
if (status === 0) return 'status-pending'
|
||
return 'status-failed'
|
||
},
|
||
|
||
// 获取状态文字
|
||
getStatusText(status) {
|
||
if (status === 1) return '已发布到店铺'
|
||
if (status === 0) return '未发送到店铺'
|
||
return '发布失败'
|
||
},
|
||
|
||
// 预览图片
|
||
previewImage(images) {
|
||
if (!images) return
|
||
const urls = typeof images === 'string' ? [images] : images
|
||
if (urls.length === 0) return
|
||
uni.previewImage({
|
||
urls: urls,
|
||
current: urls[0]
|
||
})
|
||
}
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style>
|
||
.page-container {
|
||
min-height: 100vh;
|
||
background: #f5f6fa;
|
||
padding: 24rpx 28rpx;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
/* 筛选区域 */
|
||
.filter-section {
|
||
margin-bottom: 24rpx;
|
||
}
|
||
|
||
.filter-card {
|
||
background: #ffffff;
|
||
border-radius: 16rpx;
|
||
padding: 20rpx 24rpx;
|
||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.06);
|
||
}
|
||
|
||
.filter-row {
|
||
display: flex;
|
||
gap: 20rpx;
|
||
margin-bottom: 16rpx;
|
||
}
|
||
|
||
.filter-row:last-child {
|
||
margin-bottom: 0;
|
||
}
|
||
|
||
.filter-item {
|
||
flex: 1;
|
||
display: flex;
|
||
align-items: center;
|
||
}
|
||
|
||
.filter-label {
|
||
font-size: 26rpx;
|
||
color: #4e5969;
|
||
margin-right: 12rpx;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.filter-picker {
|
||
flex: 1;
|
||
}
|
||
|
||
.picker-value {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
background: #f5f7fa;
|
||
padding: 12rpx 16rpx;
|
||
border-radius: 8rpx;
|
||
}
|
||
|
||
.picker-text {
|
||
font-size: 26rpx;
|
||
color: #1d2129;
|
||
}
|
||
|
||
.picker-arrow {
|
||
font-size: 20rpx;
|
||
color: #86909c;
|
||
}
|
||
|
||
/* 统计信息 */
|
||
.stats-section {
|
||
margin-bottom: 24rpx;
|
||
}
|
||
|
||
.stats-card {
|
||
background: #ffffff;
|
||
border-radius: 16rpx;
|
||
padding: 20rpx 24rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-around;
|
||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.06);
|
||
}
|
||
|
||
.stats-item {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
gap: 6rpx;
|
||
flex: 1;
|
||
}
|
||
|
||
.stats-num {
|
||
font-size: 36rpx;
|
||
font-weight: 700;
|
||
}
|
||
|
||
.stats-num.success { color: #67c23a; }
|
||
.stats-num.pending { color: #e6a23c; }
|
||
.stats-num.failed { color: #f56c6c; }
|
||
.stats-num.total { color: #409eff; }
|
||
|
||
.stats-label {
|
||
font-size: 22rpx;
|
||
color: #909399;
|
||
}
|
||
|
||
.stats-divider {
|
||
width: 1px;
|
||
height: 48rpx;
|
||
background: #ebeef5;
|
||
}
|
||
|
||
/* 滚动列表 */
|
||
.record-scroll {
|
||
height: calc(100vh - 300rpx);
|
||
}
|
||
|
||
.record-list {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 20rpx;
|
||
}
|
||
|
||
.record-item {
|
||
background: #ffffff;
|
||
border-radius: 16rpx;
|
||
padding: 24rpx;
|
||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.06);
|
||
}
|
||
|
||
.record-main {
|
||
display: flex;
|
||
gap: 20rpx;
|
||
margin-bottom: 20rpx;
|
||
}
|
||
|
||
/* 图片区域 */
|
||
.record-image {
|
||
width: 180rpx;
|
||
height: 180rpx;
|
||
border-radius: 12rpx;
|
||
overflow: hidden;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.book-image {
|
||
width: 100%;
|
||
height: 100%;
|
||
}
|
||
|
||
/* 基本信息 */
|
||
.record-info {
|
||
flex: 1;
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 8rpx;
|
||
}
|
||
|
||
.info-row {
|
||
display: flex;
|
||
align-items: center;
|
||
}
|
||
|
||
.book-name {
|
||
font-size: 30rpx;
|
||
color: #1d2129;
|
||
font-weight: 600;
|
||
lines: 1;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.info-label {
|
||
font-size: 24rpx;
|
||
color: #86909c;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.info-value {
|
||
font-size: 24rpx;
|
||
color: #4e5969;
|
||
}
|
||
|
||
/* 价格和库存 */
|
||
.record-detail {
|
||
background: #f5f6fa;
|
||
border-radius: 12rpx;
|
||
padding: 16rpx 20rpx;
|
||
margin-bottom: 16rpx;
|
||
}
|
||
|
||
.detail-row {
|
||
display: flex;
|
||
justify-content: space-around;
|
||
}
|
||
|
||
.detail-item {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
gap: 6rpx;
|
||
}
|
||
|
||
.detail-label {
|
||
font-size: 22rpx;
|
||
color: #86909c;
|
||
}
|
||
|
||
.detail-value {
|
||
font-size: 28rpx;
|
||
color: #1d2129;
|
||
font-weight: 600;
|
||
}
|
||
|
||
.detail-value.price {
|
||
color: #f56c6c;
|
||
}
|
||
|
||
/* 状态 */
|
||
.publish-status {
|
||
border-top: 1rpx solid #ebeef5;
|
||
padding-top: 16rpx;
|
||
}
|
||
|
||
.status-row {
|
||
display: flex;
|
||
justify-content: center;
|
||
}
|
||
|
||
.status-tag {
|
||
font-size: 22rpx;
|
||
padding: 6rpx 20rpx;
|
||
border-radius: 8rpx;
|
||
}
|
||
|
||
.status-success {
|
||
background: #e1f3d8;
|
||
color: #67c23a;
|
||
}
|
||
|
||
.status-pending {
|
||
background: #faecd8;
|
||
color: #e6a23c;
|
||
}
|
||
|
||
.status-failed {
|
||
background: #fde2e2;
|
||
color: #f56c6c;
|
||
}
|
||
|
||
/* 加载更多 */
|
||
.load-more {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
padding: 30rpx 0;
|
||
gap: 12rpx;
|
||
}
|
||
|
||
.load-more-text {
|
||
font-size: 24rpx;
|
||
color: #909399;
|
||
}
|
||
|
||
.no-more-text {
|
||
font-size: 24rpx;
|
||
color: #c0c4cc;
|
||
}
|
||
|
||
.loading-spinner {
|
||
width: 32rpx;
|
||
height: 32rpx;
|
||
border: 3rpx solid #e4e7ed;
|
||
border-top-color: #409eff;
|
||
border-radius: 50%;
|
||
animation: spin 0.8s linear infinite;
|
||
}
|
||
|
||
@keyframes spin {
|
||
0% { transform: rotate(0deg); }
|
||
100% { transform: rotate(360deg); }
|
||
}
|
||
|
||
/* 空状态 */
|
||
.empty-state {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
padding: 120rpx 0;
|
||
}
|
||
|
||
.empty-icon {
|
||
font-size: 80rpx;
|
||
margin-bottom: 20rpx;
|
||
}
|
||
|
||
.empty-text {
|
||
font-size: 28rpx;
|
||
color: #86909c;
|
||
}
|
||
</style>
|