daShangDao_scanBook/pages/record/record.vue

678 lines
15 KiB
Vue
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<view 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>
<text class="shop-tag" v-if="item._shop_name">{{ item._shop_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()
},
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()
// 加载完店铺后自动请求数据
this.resetAndFetch()
} 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 {
var targets = []
var shopId = this.getSelectedShopId()
if (shopId) {
targets = [{ id: shopId }]
} else {
// 全部店铺:遍历所有店铺
targets = this.shopList
}
if (targets.length === 0) {
this.recordList = []
this.stats = { successCount: 0, notSentCount: 0, failedCount: 0, total: 0 }
this.hasMore = false
this.isLoading = false
return
}
var mergedProducts = this.page === 1 ? [] : this.recordList.slice()
var mergedStats = { successCount: 0, notSentCount: 0, failedCount: 0, total: 0 }
var allLoaded = true
for (var i = 0; i < targets.length; i++) {
try {
const params = {
page: this.page,
page_size: this.pageSize,
shop_id: targets[i].id
}
const res = await getShopDetail(params)
var products = res.products || []
// 注入店铺名称(全部店铺模式时区分来源)
var shopDisplayName = res.shop_alias_name || targets[i].shop_alias_name || targets[i].shop_name || ''
for (var pi = 0; pi < products.length; pi++) {
if (shopDisplayName) products[pi]._shop_name = shopDisplayName
}
mergedProducts = mergedProducts.concat(products)
mergedStats.successCount += (res.success_count || 0)
mergedStats.notSentCount += (res.not_sent_count || 0)
mergedStats.failedCount += (res.failed_count || 0)
mergedStats.total += (res.total || 0)
// 任何一个店铺还有更多数据则都有更多
if (products.length < this.pageSize) {
// 该店铺已无更多,不改变 allLoaded
} else {
allLoaded = false
}
} catch (e) {
console.error('获取店铺[' + targets[i].shop_alias_name + ']的上书记录失败:', e)
}
}
// 按时间倒序排列
mergedProducts.sort(function(a, b) {
return (b.created_at || 0) - (a.created_at || 0)
})
this.recordList = mergedProducts
this.hasMore = !allLoaded && targets.length > 0
this.stats = mergedStats
} 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;
white-space: nowrap;
flex: 1;
min-width: 0;
}
.shop-tag {
font-size: 20rpx;
color: #409eff;
background: #d9ecff;
padding: 2rpx 10rpx;
border-radius: 6rpx;
margin-left: 8rpx;
flex-shrink: 0;
}
.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>