c4ca8a87fa
- sign页面与签约宝精简版对齐:查看合同入口、AI积分区块(合同审查/我的报告/积分充值)
- 新增pages/ai/{points,review,reports}三个页面(与签约宝精简版一致)
- pages.json注册AI页面路由
361 lines
9.8 KiB
Vue
361 lines
9.8 KiB
Vue
<template>
|
||
<view class="page-wrap">
|
||
<uititle title="我的报告"></uititle>
|
||
|
||
<!-- 查询按钮 -->
|
||
<view class="action-bar">
|
||
<u-button type="primary" size="small" shape="circle" @click="queryReports">
|
||
🔍 查询报告
|
||
</u-button>
|
||
</view>
|
||
|
||
<!-- 空状态 -->
|
||
<view v-if="reportList.length === 0 && !loading" class="empty-box">
|
||
<view class="empty-icon">📋</view>
|
||
<view class="empty-text">暂无审查报告</view>
|
||
<view class="empty-hint">点击上方「查询报告」从服务器加载</view>
|
||
</view>
|
||
|
||
<!-- 加载中 -->
|
||
<view v-if="loading" class="loading-box">
|
||
<u-loading-icon size="48"></u-loading-icon>
|
||
<text class="loading-text">加载中...</text>
|
||
</view>
|
||
|
||
<!-- 报告列表 -->
|
||
<view v-for="(r, i) in reportList" :key="i" class="report-item">
|
||
<view class="report-header">
|
||
<view class="report-name">📄 {{ r.fileName }}</view>
|
||
<view :class="['status-tag', 'status-' + r.status]">{{ statusMap[r.status] || r.status }}</view>
|
||
</view>
|
||
<view class="report-meta">{{ r.time }} · 消耗 {{ r.amount }} 积分</view>
|
||
<view class="report-actions">
|
||
<view v-if="r.htmlUrl" class="action-btn" @click="openUrl(r.htmlUrl)">📄 报告下载</view>
|
||
<view v-if="r.docxUrl" class="action-btn" @click="openUrl(r.docxUrl)">📝 下载 Word</view>
|
||
<view v-if="r.fileUrl" class="action-btn" @click="openUrl(r.fileUrl)">📎 查看上传文件</view>
|
||
<!-- 报告均可下载查看,注释在线查看功能
|
||
<view v-if="r.status === 'completed' && r.result" class="action-btn" @click="viewSummary(r)">📋 在线查看报告</view>
|
||
-->
|
||
<view v-if="r.status === 'uploaded' && !r._submitting" class="action-btn action-btn-primary" @click="reReview(r)">🔁 重新审查</view>
|
||
<view v-if="r._submitting" class="action-btn action-btn-disabled">提交中...</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 摘要弹窗 -->
|
||
<u-popup :show="showSummary" mode="bottom" @close="showSummary = false" round="24">
|
||
<view class="summary-popup">
|
||
<view class="summary-title">📋 审查摘要</view>
|
||
<scroll-view scroll-y class="summary-body">
|
||
<rich-text :nodes="summaryContent"></rich-text>
|
||
</scroll-view>
|
||
<view class="summary-actions">
|
||
<u-button v-if="summaryHtmlUrl" type="primary" size="small" @click="openUrl(summaryHtmlUrl)">查看完整报告</u-button>
|
||
<u-button size="small" @click="showSummary = false">关闭</u-button>
|
||
</view>
|
||
</view>
|
||
</u-popup>
|
||
</view>
|
||
</template>
|
||
|
||
<script>
|
||
import uititle from '@/components/common/title'
|
||
import dayjs from 'dayjs'
|
||
|
||
export default {
|
||
components: { uititle },
|
||
data() {
|
||
return {
|
||
reportList: [],
|
||
loading: false,
|
||
showSummary: false,
|
||
summaryContent: '',
|
||
summaryHtmlUrl: '',
|
||
statusMap: {
|
||
uploaded: '待审查',
|
||
reviewing: '排队中',
|
||
processing: '审查中',
|
||
completed: '已完成',
|
||
failed: '失败'
|
||
}
|
||
}
|
||
},
|
||
onShow() {
|
||
this.queryReports()
|
||
},
|
||
methods: {
|
||
// 获取当前登录用户uid(vuex优先,storage兜底)
|
||
getUid() {
|
||
let uid = (typeof vk.getVuex === 'function' && vk.getVuex('$user.userInfo._id')) || ''
|
||
if (!uid) {
|
||
const cache = vk.getStorageSync('uni_id_user_info') || {}
|
||
uid = cache._id || cache.uid || ''
|
||
}
|
||
return uid
|
||
},
|
||
// 未登录引导
|
||
checkLogin() {
|
||
if (this.getUid()) return true
|
||
uni.showModal({
|
||
title: '提示',
|
||
content: '请先登录后查看报告',
|
||
confirmText: '去登录',
|
||
success: (res) => {
|
||
if (res.confirm) uni.navigateTo({ url: '/pages/login/login' })
|
||
}
|
||
})
|
||
return false
|
||
},
|
||
async queryReports() {
|
||
if (!this.checkLogin()) return
|
||
this.loading = true
|
||
try {
|
||
const res = await vk.callFunction({
|
||
url: 'client/ai/pub/getMyReports',
|
||
data: { pageIndex: 1, pageSize: 50, uid: this.getUid() }
|
||
})
|
||
if (res.code === 0 && res.data && res.data.rows) {
|
||
this.reportList = res.data.rows.map(r => ({
|
||
fileName: r.file_name,
|
||
fileSize: r.file_size,
|
||
fileId: r.file_id || '',
|
||
fileUrl: r.file_url || '',
|
||
htmlUrl: r.html_url || '',
|
||
docxUrl: r.docx_url || '',
|
||
time: dayjs(r._add_time && r._add_time['$date'] ? r._add_time['$date'] : r._add_time).format('YYYY-MM-DD HH:mm'),
|
||
amount: r.points_used || 0,
|
||
result: r.result,
|
||
status: r.status,
|
||
recordId: r._id
|
||
}))
|
||
}
|
||
} catch (e) {
|
||
uni.showToast({ title: '加载失败', icon: 'none' })
|
||
}
|
||
this.loading = false
|
||
},
|
||
viewSummary(r) {
|
||
this.summaryContent = this.formatContent(r.result)
|
||
this.summaryHtmlUrl = r.htmlUrl
|
||
this.showSummary = true
|
||
},
|
||
// 待审查记录 → 重新发起审查
|
||
async reReview(r) {
|
||
if (!this.checkLogin()) return
|
||
if (!r.fileId) {
|
||
uni.showToast({ title: '该记录缺少文件信息', icon: 'none' })
|
||
return
|
||
}
|
||
const confirmed = await new Promise((resolve) => {
|
||
uni.showModal({
|
||
title: '重新审查',
|
||
content: `确认对「${r.fileName}」发起审查?审查完成后将按文件大小扣除相应积分。`,
|
||
confirmText: '确认审查',
|
||
confirmColor: '#a92924',
|
||
success: (res) => resolve(!!res.confirm),
|
||
fail: () => resolve(false)
|
||
})
|
||
})
|
||
if (!confirmed) return
|
||
|
||
uni.showLoading({ title: '提交中...', mask: true })
|
||
try {
|
||
const balanceRes = await vk.callFunction({ url: 'client/points/kh/getBalance' })
|
||
if (balanceRes.code !== 0 || !balanceRes.data || balanceRes.data.balance < 200) {
|
||
uni.hideLoading()
|
||
uni.showToast({ title: '积分不足,请先充值', icon: 'none' })
|
||
return
|
||
}
|
||
this.$set(r, '_submitting', true)
|
||
const res = await vk.callFunction({
|
||
url: 'client/ai/pub/review',
|
||
data: {
|
||
record_id: r.recordId,
|
||
file_id: r.fileId,
|
||
file_name: r.fileName,
|
||
file_size: r.fileSize || 0,
|
||
uid: this.getUid()
|
||
}
|
||
})
|
||
uni.hideLoading()
|
||
this.$set(r, '_submitting', false)
|
||
if (res.code === 0) {
|
||
r.status = (res.data && res.data.status) || 'reviewing'
|
||
uni.showToast({ title: '已提交审查', icon: 'success' })
|
||
setTimeout(() => this.queryReports(), 1500)
|
||
} else {
|
||
uni.showToast({ title: res.msg || '提交失败', icon: 'none' })
|
||
}
|
||
} catch (e) {
|
||
uni.hideLoading()
|
||
this.$set(r, '_submitting', false)
|
||
uni.showToast({ title: '提交异常', icon: 'none' })
|
||
}
|
||
},
|
||
formatContent(text) {
|
||
if (!text) return '<p>暂无审查结果</p>'
|
||
let html = text
|
||
html = html.replace(/^###\s+(.+)$/gm, '<h4 style="font-size:15px;margin:12px 0 6px;">$1</h4>')
|
||
html = html.replace(/^##\s+(.+)$/gm, '<h3 style="font-size:16px;margin:16px 0 8px;">$1</h3>')
|
||
html = html.replace(/\*\*(.*?)\*\*/g, '<strong style="color:#a92924;">$1</strong>')
|
||
html = html.replace(/^[\-\•]\s+(.+)$/gm, '<div style="padding-left:12px;">• $1</div>')
|
||
html = html.replace(/^(\d+)\.\s+(.+)$/gm, '<div style="padding-left:12px;">$1. $2</div>')
|
||
html = html.replace(/\n/g, '<br>')
|
||
return html
|
||
},
|
||
openUrl(url) {
|
||
if (url) {
|
||
// #ifdef H5
|
||
window.open(url, '_blank')
|
||
// #endif
|
||
// #ifndef H5
|
||
uni.navigateTo({ url: '/pages/ai/webview?url=' + encodeURIComponent(url) })
|
||
// #endif
|
||
}
|
||
}
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style lang="scss" scoped>
|
||
.page-wrap {
|
||
min-height: 100vh;
|
||
background: #f5f5f5;
|
||
}
|
||
|
||
.action-bar {
|
||
padding: 20rpx 30rpx;
|
||
display: flex;
|
||
justify-content: flex-end;
|
||
}
|
||
|
||
.empty-box {
|
||
text-align: center;
|
||
padding: 120rpx 30rpx;
|
||
}
|
||
|
||
.empty-icon {
|
||
font-size: 80rpx;
|
||
}
|
||
|
||
.empty-text {
|
||
font-size: 30rpx;
|
||
font-weight: 600;
|
||
margin-top: 16rpx;
|
||
}
|
||
|
||
.empty-hint {
|
||
font-size: 24rpx;
|
||
color: #999;
|
||
margin-top: 8rpx;
|
||
}
|
||
|
||
.loading-box {
|
||
text-align: center;
|
||
padding: 60rpx 0;
|
||
}
|
||
|
||
.loading-text {
|
||
display: block;
|
||
font-size: 28rpx;
|
||
margin-top: 16rpx;
|
||
}
|
||
|
||
.report-item {
|
||
background: #fff;
|
||
margin: 16rpx 30rpx;
|
||
padding: 24rpx;
|
||
border-radius: 12rpx;
|
||
}
|
||
|
||
.report-header {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 16rpx;
|
||
}
|
||
|
||
.report-name {
|
||
flex: 1;
|
||
font-size: 28rpx;
|
||
font-weight: 600;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.status-tag {
|
||
font-size: 22rpx;
|
||
padding: 4rpx 16rpx;
|
||
border-radius: 8rpx;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.status-uploaded { background: #f0f0f0; color: #999; }
|
||
.status-reviewing { background: #fdf6ec; color: #e6a23c; }
|
||
.status-processing { background: #fdf6ec; color: #e6a23c; }
|
||
.status-completed { background: #f0f9eb; color: #67c23a; }
|
||
.status-failed { background: #fef0f0; color: #f56c6c; }
|
||
|
||
.report-meta {
|
||
font-size: 22rpx;
|
||
color: #999;
|
||
margin-top: 8rpx;
|
||
}
|
||
|
||
.report-actions {
|
||
display: flex;
|
||
gap: 16rpx;
|
||
margin-top: 16rpx;
|
||
flex-wrap: wrap;
|
||
}
|
||
|
||
.action-btn {
|
||
font-size: 24rpx;
|
||
color: #a92924;
|
||
padding: 8rpx 20rpx;
|
||
border: 1rpx solid #a92924;
|
||
border-radius: 8rpx;
|
||
}
|
||
|
||
.action-btn:active {
|
||
background: #a92924;
|
||
color: #fff;
|
||
}
|
||
|
||
.action-btn-primary {
|
||
background: #a92924;
|
||
color: #fff;
|
||
}
|
||
|
||
.action-btn-primary:active {
|
||
background: #8c1f1b;
|
||
}
|
||
|
||
.action-btn-disabled {
|
||
color: #bbb;
|
||
border-color: #ddd;
|
||
}
|
||
|
||
.summary-popup {
|
||
padding: 30rpx;
|
||
max-height: 70vh;
|
||
}
|
||
|
||
.summary-title {
|
||
font-size: 32rpx;
|
||
font-weight: 600;
|
||
margin-bottom: 20rpx;
|
||
}
|
||
|
||
.summary-body {
|
||
max-height: 50vh;
|
||
font-size: 26rpx;
|
||
line-height: 1.8;
|
||
}
|
||
|
||
.summary-actions {
|
||
display: flex;
|
||
gap: 16rpx;
|
||
margin-top: 24rpx;
|
||
justify-content: center;
|
||
}
|
||
</style> |