daShangDao_psiWebApp/backups/goodsPop_index.vue.bak
97694731 0543936df8
Some checks failed
CI / build (20.x) (push) Waiting to run
CI / lint (push) Waiting to run
CI / test (push) Waiting to run
CI / deploy-preview (push) Blocked by required conditions
CI / security (push) Waiting to run
CI / build (18.x) (push) Has been cancelled
change store
2026-06-04 11:18:46 +08:00

370 lines
8.5 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

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

<template>
<el-popover
placement="right-start"
:width="360"
trigger="hover"
:open-delay="500"
:close-delay="100"
:disabled="!isbn"
@show="handleShow"
@hide="handleHide"
>
<template #reference>
<span class="isbn-popover-trigger" :class="{ 'is-loading': loading }">
<slot />
</span>
</template>
<!-- 加载中 -->
<div v-if="loading" class="popover-loading">
<el-icon class="is-loading" :size="24"><Loading /></el-icon>
<span>正在查询书品信息...</span>
</div>
<!-- 查询失败 -->
<div v-else-if="error" class="popover-error">
<el-icon :size="24" color="#e6a23c"><WarningFilled /></el-icon>
<span>{{ error }}</span>
</div>
<!-- 查询成功 → 展示书品信息 -->
<div v-else-if="bookData" class="popover-content">
<div class="popover-header">
<span class="popover-isbn">{{ isbn }}</span>
<span v-if="bookData.isSuit" class="suit-badge">套装书</span>
</div>
<div class="popover-body">
<!-- 左侧:封面图片 -->
<div class="popover-cover">
<img
v-if="bookData.book_pic?.pddPath"
:src="bookData.book_pic.pddPath"
alt="封面"
class="cover-image"
/>
<div v-else class="cover-placeholder">
<el-icon :size="32"><Picture /></el-icon>
</div>
</div>
<!-- 右侧书籍详情 -->
<div class="popover-info">
<div class="info-row">
<span class="info-label">书名</span>
<span class="info-value info-value-name">{{ bookData.bookName || '未知' }}</span>
</div>
<div class="info-row">
<span class="info-label">作者</span>
<span class="info-value">{{ bookData.author || '未知' }}</span>
</div>
<div class="info-row">
<span class="info-label">出版社</span>
<span class="info-value">{{ bookData.publisher || '未知' }}</span>
</div>
<div class="info-row">
<span class="info-label">出版时间</span>
<span class="info-value">{{ bookData.publishDate || '未知' }}</span>
</div>
<div class="info-row">
<span class="info-label">装帧</span>
<span class="info-value">{{ bookData.binding || '未知' }}</span>
</div>
<div class="info-row">
<span class="info-label">定价</span>
<span class="info-value info-value-price">¥{{ formatPrice(bookData.price) }}</span>
</div>
<div class="info-row">
<span class="info-label">页数</span>
<span class="info-value">{{ bookData.pageCount ?? '未知' }}</span>
</div>
<div class="info-row">
<span class="info-label">字数</span>
<span class="info-value">{{ bookData.wordCount ?? '未知' }}</span>
</div>
</div>
</div>
</div>
<!-- ISBN 为空 -->
<div v-else class="popover-empty">
<span>暂无ISBN</span>
</div>
</el-popover>
</template>
<script setup lang="ts">
import { ref, watch } from 'vue'
import { Loading, WarningFilled, Picture } from '@element-plus/icons-vue'
import request from '@/utils/request'
interface BookPic {
localPath?: string
pddPath?: string
}
interface BookInfoResult {
bookName: string
author: string
publisher: string
publishDate: string
binding: string
price: number
pageCount: number
wordCount: number
book_pic?: BookPic
isSuit: boolean
}
const props = defineProps<{
/** ISBN 编号 */
isbn?: string
}>()
const loading = ref(false)
const error = ref<string | null>(null)
const bookData = ref<BookInfoResult | null>(null)
// 缓存已查询过的 ISBN避免重复请求
const cache = new Map<string, BookInfoResult>()
/** 格式化出版时间:处理年月日格式 */
function formatPublishDate(value: string | number | undefined | null): string {
if (value == null || value === '') return ''
const str = String(value)
// 如果已经是 yyyy-mm-dd 格式,直接返回
if (/^\d{4}-\d{2}-\d{2}$/.test(str)) return str
// 如果是 yyyyMMdd 格式8位数字
if (/^\d{8}$/.test(str)) {
return `${str.slice(0, 4)}-${str.slice(4, 6)}-${str.slice(6, 8)}`
}
// 如果是 yyyy-mm 格式
if (/^\d{4}-\d{2}$/.test(str)) return `${str}-01`
// 如果是纯数字时间戳
const num = Number(value)
if (!isNaN(num) && num > 10000) {
const d = new Date(num * 1000)
if (!isNaN(d.getTime())) {
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`
}
}
return str
}
/** 格式化价格:分 → 元 */
function formatPrice(priceInCents: number): string {
if (priceInCents == null) return '0.00'
return (priceInCents / 100).toFixed(2)
}
async function fetchBookInfo(isbn: string) {
// 检查缓存
if (cache.has(isbn)) {
bookData.value = cache.get(isbn)!
return
}
loading.value = true
error.value = null
bookData.value = null
try {
const payload = await request.get('/getBookInfo', {
params: { isbn }
})
const data = payload?.data
if (!data) {
error.value = '数据库中暂无该书数据'
return
}
const result: BookInfoResult = {
bookName: data.book_name || '',
author: data.author || '',
publisher: data.publisher || '',
publishDate: formatPublishDate(data.publication_time),
binding: data.binding_layout || '',
price: typeof data.fix_price === 'number' ? data.fix_price : 0,
pageCount: Number(data.page_count) || 0,
wordCount: Number(data.word_count) || 0,
book_pic: data.book_pic || undefined,
isSuit: data.is_suit === 1
}
// 写入缓存
cache.set(isbn, result)
bookData.value = result
} catch (err) {
console.warn('[goodsPop] 书籍信息查询失败:', err instanceof Error ? err.message : String(err))
error.value = '查询失败,请稍后重试'
} finally {
loading.value = false
}
}
function handleShow() {
if (!props.isbn) return
fetchBookInfo(props.isbn)
}
function handleHide() {
// 不做清理,保留上次查询结果以便下次快速展示
}
</script>
<style scoped>
.isbn-popover-trigger {
cursor: pointer;
border-bottom: 1px dashed #409eff;
transition: all 0.2s;
}
.isbn-popover-trigger:hover {
color: #409eff;
}
.isbn-popover-trigger.is-loading {
opacity: 0.7;
}
/* 加载状态 */
.popover-loading {
display: flex;
align-items: center;
gap: 8px;
padding: 20px;
justify-content: center;
color: #909399;
font-size: 13px;
}
.popover-loading .is-loading {
animation: rotating 1.5s linear infinite;
}
@keyframes rotating {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
/* 错误状态 */
.popover-error {
display: flex;
align-items: center;
gap: 8px;
padding: 20px;
justify-content: center;
color: #e6a23c;
font-size: 13px;
}
/* 空状态 */
.popover-empty {
padding: 20px;
text-align: center;
color: #c0c4cc;
font-size: 13px;
}
/* 内容主体 */
.popover-content {
padding: 4px;
}
.popover-header {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 12px;
padding-bottom: 8px;
border-bottom: 1px solid #ebeef5;
}
.popover-isbn {
font-size: 13px;
font-weight: 600;
color: #303133;
font-family: 'Courier New', monospace;
}
.suit-badge {
display: inline-block;
padding: 1px 8px;
font-size: 11px;
color: #e6a23c;
background: #fdf6ec;
border: 1px solid #f5dab1;
border-radius: 4px;
line-height: 1.6;
}
.popover-body {
display: flex;
gap: 14px;
}
/* 封面 */
.popover-cover {
flex-shrink: 0;
width: 90px;
height: 120px;
border-radius: 4px;
overflow: hidden;
border: 1px solid #ebeef5;
}
.cover-image {
width: 100%;
height: 100%;
object-fit: cover;
}
.cover-placeholder {
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
background: #f5f7fa;
color: #c0c4cc;
}
/* 详情 */
.popover-info {
flex: 1;
min-width: 0;
display: flex;
flex-direction: column;
gap: 4px;
}
.info-row {
display: flex;
font-size: 12px;
line-height: 1.6;
}
.info-label {
flex-shrink: 0;
color: #909399;
width: 56px;
text-align: right;
margin-right: 4px;
}
.info-value {
flex: 1;
color: #303133;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.info-value-name {
font-weight: 600;
color: #409eff;
}
.info-value-price {
color: #f56c6c;
font-weight: 600;
}
</style>