476 lines
17 KiB
Vue
476 lines
17 KiB
Vue
<template>
|
||
<div class="review-page">
|
||
<el-card class="section-card">
|
||
<template #header>
|
||
<div class="card-header">
|
||
<span>异常书目审核</span>
|
||
</div>
|
||
</template>
|
||
|
||
<!-- 搜索筛选栏 -->
|
||
<div class="search-bar">
|
||
<div class="search-item">
|
||
<span>ISBN:</span>
|
||
<el-input v-model="searchForm.isbn" placeholder="请输入ISBN" style="width: 180px" clearable />
|
||
</div>
|
||
<div class="search-item">
|
||
<span>状态:</span>
|
||
<el-select v-model="searchForm.status" placeholder="请选择状态" style="width: 140px" clearable>
|
||
<el-option label="待审核" value="0" />
|
||
<el-option label="已通过" value="1" />
|
||
<el-option label="已驳回" value="2" />
|
||
</el-select>
|
||
</div>
|
||
<el-button type="primary" @click="handleSearch">查询</el-button>
|
||
<el-button @click="handleReset">重置</el-button>
|
||
</div>
|
||
|
||
<!-- 数据表格 -->
|
||
<el-table :data="tableData" v-loading="loading" border stripe style="width: 100%"
|
||
@selection-change="handleSelectionChange">
|
||
<el-table-column prop="status" label="状态" width="150" align="center">
|
||
<template #default="{ row }">
|
||
<el-tag :type="statusTagType(row.status)" size="small">
|
||
{{ statusText(row.status) }}
|
||
</el-tag>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column prop="barcode" label="ISBN" width="160" align="center" />
|
||
<el-table-column label="书名" min-width="200" show-overflow-tooltip align="center">
|
||
<template #default="{ row }">
|
||
<el-popover placement="bottom" :width="280" trigger="hover" popper-class="diff-popover">
|
||
<template #reference>
|
||
<span :class="{ 'diff-cell': isChanged(row.old_name, row.new_name) }">{{ row.old_name }}</span>
|
||
</template>
|
||
<div class="diff-content">
|
||
<div class="diff-row old"><span class="diff-label">旧值:</span><span class="diff-val">{{ row.old_name }}</span></div>
|
||
<div class="diff-row new"><span class="diff-label">新值:</span><span class="diff-val">{{ row.new_name }}</span></div>
|
||
</div>
|
||
</el-popover>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="作者" width="150" show-overflow-tooltip align="center">
|
||
<template #default="{ row }">
|
||
<el-popover placement="bottom" :width="280" trigger="hover" popper-class="diff-popover">
|
||
<template #reference>
|
||
<span :class="{ 'diff-cell': isChanged(row.old_author, row.new_author) }">{{ row.old_author }}</span>
|
||
</template>
|
||
<div class="diff-content">
|
||
<div class="diff-row old"><span class="diff-label">旧值:</span><span class="diff-val">{{ row.old_author }}</span></div>
|
||
<div class="diff-row new"><span class="diff-label">新值:</span><span class="diff-val">{{ row.new_author }}</span></div>
|
||
</div>
|
||
</el-popover>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="出版社" width="150" show-overflow-tooltip align="center">
|
||
<template #default="{ row }">
|
||
<el-popover placement="bottom" :width="280" trigger="hover" popper-class="diff-popover">
|
||
<template #reference>
|
||
<span :class="{ 'diff-cell': isChanged(row.old_publisher, row.new_publisher) }">{{ row.old_publisher }}</span>
|
||
</template>
|
||
<div class="diff-content">
|
||
<div class="diff-row old"><span class="diff-label">旧值:</span><span class="diff-val">{{ row.old_publisher }}</span></div>
|
||
<div class="diff-row new"><span class="diff-label">新值:</span><span class="diff-val">{{ row.new_publisher }}</span></div>
|
||
</div>
|
||
</el-popover>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="套装书" width="150" show-overflow-tooltip align="center">
|
||
<template #default="{ row }">
|
||
<el-popover placement="bottom" :width="280" trigger="hover" popper-class="diff-popover">
|
||
<template #reference>
|
||
<el-tag type="info" size="small" :class="{ 'diff-cell': isChanged(row.old_is_suit, row.new_is_suit) }">
|
||
{{ row.old_is_suit === 0 ? '非套装书' : '套装书' }}
|
||
</el-tag>
|
||
</template>
|
||
<div class="diff-content">
|
||
<div class="diff-row old"><span class="diff-label">旧值:</span><span class="diff-val">{{ row.old_is_suit === 0 ? '非套装书' : '套装书' }}</span></div>
|
||
<div class="diff-row new"><span class="diff-label">新值:</span><span class="diff-val">{{ row.new_is_suit === 0 ? '非套装书' : '套装书' }}</span></div>
|
||
</div>
|
||
</el-popover>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="定价" width="150" show-overflow-tooltip align="center">
|
||
<template #default="{ row }">
|
||
<el-popover placement="bottom" :width="280" trigger="hover" popper-class="diff-popover">
|
||
<template #reference>
|
||
<span :class="{ 'diff-cell': isChanged(row.old_price, row.new_price) }">{{ row.old_price }}</span>
|
||
</template>
|
||
<div class="diff-content">
|
||
<div class="diff-row old"><span class="diff-label">旧值:</span><span class="diff-val">{{ row.old_price }}</span></div>
|
||
<div class="diff-row new"><span class="diff-label">新值:</span><span class="diff-val">{{ row.new_price }}</span></div>
|
||
</div>
|
||
</el-popover>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="装帧" width="150" show-overflow-tooltip align="center">
|
||
<template #default="{ row }">
|
||
<el-popover placement="bottom" :width="280" trigger="hover" popper-class="diff-popover">
|
||
<template #reference>
|
||
<span :class="{ 'diff-cell': isChanged(row.old_binding_layout, row.new_binding_layout) }">{{ row.old_binding_layout }}</span>
|
||
</template>
|
||
<div class="diff-content">
|
||
<div class="diff-row old"><span class="diff-label">旧值:</span><span class="diff-val">{{ row.old_binding_layout }}</span></div>
|
||
<div class="diff-row new"><span class="diff-label">新值:</span><span class="diff-val">{{ row.new_binding_layout }}</span></div>
|
||
</div>
|
||
</el-popover>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="页数" width="150" show-overflow-tooltip align="center">
|
||
<template #default="{ row }">
|
||
<el-popover placement="bottom" :width="280" trigger="hover" popper-class="diff-popover">
|
||
<template #reference>
|
||
<span :class="{ 'diff-cell': isChanged(row.old_page_count, row.new_page_count) }">{{ row.old_page_count }}</span>
|
||
</template>
|
||
<div class="diff-content">
|
||
<div class="diff-row old"><span class="diff-label">旧值:</span><span class="diff-val">{{ row.old_page_count }}</span></div>
|
||
<div class="diff-row new"><span class="diff-label">新值:</span><span class="diff-val">{{ row.new_page_count }}</span></div>
|
||
</div>
|
||
</el-popover>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="字数" width="150" show-overflow-tooltip align="center">
|
||
<template #default="{ row }">
|
||
<el-popover placement="bottom" :width="280" trigger="hover" popper-class="diff-popover">
|
||
<template #reference>
|
||
<span :class="{ 'diff-cell': isChanged(row.old_word_count, row.new_word_count) }">{{ row.old_word_count }}</span>
|
||
</template>
|
||
<div class="diff-content">
|
||
<div class="diff-row old"><span class="diff-label">旧值:</span><span class="diff-val">{{ row.old_word_count }}</span></div>
|
||
<div class="diff-row new"><span class="diff-label">新值:</span><span class="diff-val">{{ row.new_word_count }}</span></div>
|
||
</div>
|
||
</el-popover>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="出版时间" width="150" show-overflow-tooltip align="center">
|
||
<template #default="{ row }">
|
||
<el-popover placement="bottom" :width="280" trigger="hover" popper-class="diff-popover">
|
||
<template #reference>
|
||
<span :class="{ 'diff-cell': isPubTimeChanged(row.old_publication_time, row.new_publication_time) }">{{ formatPubTime(row.old_publication_time) }}</span>
|
||
</template>
|
||
<div class="diff-content">
|
||
<div class="diff-row old"><span class="diff-label">旧值:</span><span class="diff-val">{{ formatPubTime(row.old_publication_time) }}</span></div>
|
||
<div class="diff-row new"><span class="diff-label">新值:</span><span class="diff-val">{{ formatPubTime(row.new_publication_time) }}</span></div>
|
||
</div>
|
||
</el-popover>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="提交时间" width="180" align="center">
|
||
<template #default="{ row }">
|
||
{{ formatDateTime(row.created_at) }}
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="操作" width="180" fixed="right" align="center">
|
||
<template #default="{ row }">
|
||
<el-button type="primary" @click="handleApprove(row)">
|
||
查看
|
||
</el-button>
|
||
<el-button type="danger" @click="handleReject(row)">
|
||
删除
|
||
</el-button>
|
||
</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
|
||
<!-- 分页 -->
|
||
<div class="pagination-wrapper">
|
||
<el-pagination v-model:current-page="pagination.current" v-model:page-size="pagination.pageSize"
|
||
:page-sizes="[10, 20, 50, 100]" :total="pagination.total" layout="total, sizes, prev, pager, next, jumper"
|
||
@size-change="handlePageChange" @current-change="handlePageChange" />
|
||
</div>
|
||
</el-card>
|
||
|
||
<!-- 查看详情弹窗 -->
|
||
<el-dialog v-model="detailVisible" title="调剂记录详情" width="60%" :close-on-click-modal="false">
|
||
<template v-if="detailData">
|
||
<el-descriptions :column="2" border>
|
||
<el-descriptions-item label="ISBN">{{ detailData.isbn }}</el-descriptions-item>
|
||
<el-descriptions-item label="书名">{{ detailData.bookName }}</el-descriptions-item>
|
||
<el-descriptions-item label="作者">{{ detailData.author }}</el-descriptions-item>
|
||
<el-descriptions-item label="出版社">{{ detailData.publisher }}</el-descriptions-item>
|
||
<el-descriptions-item label="异常类型">{{ detailData.abnormalType }}</el-descriptions-item>
|
||
<el-descriptions-item label="状态">
|
||
<el-tag :type="statusTagType(detailData.status)" size="small">
|
||
{{ statusText(detailData.status) }}
|
||
</el-tag>
|
||
</el-descriptions-item>
|
||
<el-descriptions-item label="异常说明" :span="2">
|
||
{{ detailData.abnormalDesc || '无' }}
|
||
</el-descriptions-item>
|
||
<el-descriptions-item label="提交时间">{{ detailData.createdAt }}</el-descriptions-item>
|
||
<el-descriptions-item v-if="detailData.reviewTime" label="审核时间">{{ detailData.reviewTime
|
||
}}</el-descriptions-item>
|
||
</el-descriptions>
|
||
</template>
|
||
</el-dialog>
|
||
|
||
<!-- 审核弹窗 - 使用 SubmIllegalBook 组件 -->
|
||
<SubmIllegalBook
|
||
v-model:visible="reviewDialogVisible"
|
||
:review-data="reviewRecord"
|
||
:goods="null"
|
||
@review="handleReviewResult"
|
||
/>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
import { ref, reactive, onMounted } from 'vue'
|
||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||
import request from '@/utils/request'
|
||
import SubmIllegalBook from '@/components/dialog/submIllegalBook/SubmIllegalBook.vue'
|
||
|
||
// 搜索表单
|
||
const searchForm = reactive({
|
||
isbn: '',
|
||
status: ''
|
||
})
|
||
|
||
// 表格数据
|
||
const tableData = ref<any[]>([])
|
||
const loading = ref(false)
|
||
const selectedRows = ref<any[]>([])
|
||
|
||
// 分页
|
||
const pagination = reactive({
|
||
current: 1,
|
||
pageSize: 20,
|
||
total: 0
|
||
})
|
||
|
||
// 详情弹窗
|
||
const detailVisible = ref(false)
|
||
const detailData = ref<any>(null)
|
||
|
||
// 审核弹窗
|
||
const reviewDialogVisible = ref(false)
|
||
const reviewRecord = ref<any>(null)
|
||
|
||
// 状态标签样式
|
||
function statusTagType(status: string): string {
|
||
const map: Record<string, string> = {
|
||
0: 'warning',
|
||
1: 'success',
|
||
2: 'danger'
|
||
}
|
||
return map[status] || 'info'
|
||
}
|
||
|
||
// 格式化出版时间为 yyyy-mm(输入为10位时间戳,秒级)
|
||
function formatPubTime(time: any): string {
|
||
if (time === null || time === undefined || time === '') return ''
|
||
const ts = Number(time)
|
||
if (isNaN(ts) || ts <= 0) return ''
|
||
const d = new Date(ts * 1000)
|
||
const y = d.getFullYear()
|
||
const m = String(d.getMonth() + 1).padStart(2, '0')
|
||
return `${y}-${m}`
|
||
}
|
||
|
||
// 格式化为完整时间 yyyy-mm-dd HH:mm:ss(输入为10位时间戳,秒级)
|
||
function formatDateTime(time: any): string {
|
||
if (time === null || time === undefined || time === '') return ''
|
||
const ts = Number(time)
|
||
if (isNaN(ts) || ts <= 0) return ''
|
||
const d = new Date(ts * 1000)
|
||
const y = d.getFullYear()
|
||
const M = String(d.getMonth() + 1).padStart(2, '0')
|
||
const day = String(d.getDate()).padStart(2, '0')
|
||
const h = String(d.getHours()).padStart(2, '0')
|
||
const m = String(d.getMinutes()).padStart(2, '0')
|
||
const s = String(d.getSeconds()).padStart(2, '0')
|
||
return `${y}-${M}-${day} ${h}:${m}:${s}`
|
||
}
|
||
|
||
// 判断新旧数据是否不同
|
||
function isChanged(oldVal: any, newVal: any): boolean {
|
||
// 如果新旧值都是空值,视为相同
|
||
if ((oldVal === null || oldVal === undefined || oldVal === '') &&
|
||
(newVal === null || newVal === undefined || newVal === '')) {
|
||
return false
|
||
}
|
||
return String(oldVal) !== String(newVal)
|
||
}
|
||
|
||
// 判断出版时间是否不同(处理时间戳空值情况)
|
||
function isPubTimeChanged(oldTs: any, newTs: any): boolean {
|
||
return isChanged(formatPubTime(oldTs), formatPubTime(newTs))
|
||
}
|
||
|
||
// 状态文本
|
||
function statusText(status: string): string {
|
||
const map: Record<string, string> = {
|
||
0: '待审核',
|
||
1: '已通过',
|
||
2: '已驳回'
|
||
}
|
||
return map[status] || status
|
||
}
|
||
|
||
// 查询
|
||
function handleSearch() {
|
||
pagination.current = 1
|
||
fetchData()
|
||
}
|
||
|
||
// 重置
|
||
function handleReset() {
|
||
searchForm.isbn = ''
|
||
searchForm.status = ''
|
||
handleSearch()
|
||
}
|
||
|
||
// 多选
|
||
function handleSelectionChange(rows: any[]) {
|
||
selectedRows.value = rows
|
||
}
|
||
|
||
// 分页变化
|
||
function handlePageChange() {
|
||
fetchData()
|
||
}
|
||
|
||
// 获取数据
|
||
async function fetchData() {
|
||
loading.value = true
|
||
try {
|
||
const res = await request.get('/product_log/list', {
|
||
params: {
|
||
barcode: searchForm.isbn || undefined,
|
||
status: searchForm.status || undefined,
|
||
page: pagination.current,
|
||
page_size: pagination.pageSize
|
||
}
|
||
})
|
||
const data = res?.data
|
||
if (data) {
|
||
tableData.value = Array.isArray(data.list) ? data.list : Array.isArray(data) ? data : []
|
||
pagination.total = data.total || 0
|
||
} else {
|
||
tableData.value = []
|
||
pagination.total = 0
|
||
}
|
||
} catch (err) {
|
||
ElMessage.error('获取列表失败')
|
||
} finally {
|
||
loading.value = false
|
||
}
|
||
}
|
||
|
||
// 审核 - 打开审核弹窗
|
||
function handleApprove(row: any) {
|
||
reviewRecord.value = row
|
||
reviewDialogVisible.value = true
|
||
}
|
||
|
||
// 删除
|
||
async function handleReject(row: any) {
|
||
try {
|
||
await ElMessageBox.confirm('确认删除该条记录?', '提示', {
|
||
type: 'warning'
|
||
})
|
||
const formData = new FormData()
|
||
formData.append('id', String(row.id))
|
||
await request.post('/product_log/delete', formData, {
|
||
headers: { 'Content-Type': 'multipart/form-data' }
|
||
})
|
||
ElMessage.success('已删除')
|
||
fetchData()
|
||
} catch {
|
||
// 取消
|
||
}
|
||
}
|
||
|
||
// 审核结果处理 - 弹窗关闭后刷新列表
|
||
function handleReviewResult(action: 'approve' | 'reject', record: any) {
|
||
fetchData()
|
||
}
|
||
|
||
// 查看详情
|
||
function handleViewDetail(row: any) {
|
||
detailData.value = row
|
||
detailVisible.value = true
|
||
}
|
||
|
||
onMounted(() => {
|
||
fetchData()
|
||
})
|
||
</script>
|
||
|
||
<style scoped>
|
||
.review-page {
|
||
padding: 0;
|
||
}
|
||
|
||
.card-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
}
|
||
|
||
.section-card {
|
||
margin-bottom: 16px;
|
||
}
|
||
|
||
.search-bar {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 16px;
|
||
margin-bottom: 16px;
|
||
}
|
||
|
||
.search-item {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
}
|
||
|
||
.search-item span {
|
||
font-size: 14px;
|
||
color: #606266;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.pagination-wrapper {
|
||
display: flex;
|
||
justify-content: flex-end;
|
||
margin-top: 16px;
|
||
}
|
||
</style>
|
||
|
||
<style>
|
||
/* 新旧数据对比 - 单元格变红 */
|
||
.diff-cell {
|
||
color: #f56c6c !important;
|
||
font-weight: bold;
|
||
}
|
||
|
||
/* Popover 内容样式(全局,因 popover 渲染在 body 下) */
|
||
.diff-popover {
|
||
padding: 8px 12px !important;
|
||
}
|
||
.diff-content {
|
||
font-size: 13px;
|
||
line-height: 1.6;
|
||
}
|
||
.diff-row {
|
||
display: flex;
|
||
align-items: flex-start;
|
||
gap: 4px;
|
||
padding: 2px 0;
|
||
}
|
||
.diff-label {
|
||
white-space: nowrap;
|
||
color: #909399;
|
||
flex-shrink: 0;
|
||
}
|
||
.diff-val {
|
||
color: #303133;
|
||
word-break: break-all;
|
||
}
|
||
.diff-row.new .diff-val {
|
||
color: #e6a23c;
|
||
font-weight: bold;
|
||
}
|
||
</style>
|