Files
anxinyan/user-app/src/pages/report/index.vue
wushumin 9aac78b8da first
2026-05-11 15:28:27 +08:00

559 lines
13 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.
<script setup lang="ts">
import { computed, ref } from "vue";
import { onShow } from "@dcloudio/uni-app";
import { appApi, type ReportListItem } from "../../api/app";
import { getPrivacyMode, maskOrderNo } from "../../utils/privacy";
import { showErrorToast } from "../../utils/feedback";
import { isLoggedIn, redirectToLogin } from "../../utils/auth";
const reports = ref<ReportListItem[]>([]);
const privacyMode = ref(getPrivacyMode());
const reportHeroBackground = ref("");
const defaultReportHeroBackground = "/static/report/report-reference.jpg";
const reportStats = computed(() => ({
total: reports.value.length,
published: reports.value.filter((item) => item.report_id).length,
pending: reports.value.filter((item) => !item.report_id).length,
}));
const reportHeroStyle = computed(() => ({
backgroundImage: `url("${reportHeroBackground.value || defaultReportHeroBackground}")`,
}));
const emptyState = computed(() => ({
title: "暂无鉴定报告",
desc: "正式报告生成后您可以在这里查看结果、下载PDF并继续进入验真。",
}));
function openReport(item: ReportListItem) {
if (!item.report_id || !item.report_no) {
uni.navigateTo({ url: `/pages/order/detail?id=${item.order_id}` });
return;
}
uni.navigateTo({ url: `/pages/report/detail?report_no=${encodeURIComponent(item.report_no)}` });
}
function goHome() {
uni.switchTab({ url: "/pages/home/index" });
}
function goStartAppraisal() {
uni.navigateTo({ url: "/pages/appraisal/service" });
}
function goHelp() {
uni.navigateTo({ url: "/pages/help/index" });
}
async function fetchPageVisuals() {
try {
const data = await appApi.getPageVisuals();
reportHeroBackground.value = data.report_background_image_url || "";
} catch (error) {
console.warn("report page visuals fallback", error);
}
}
onShow(async () => {
privacyMode.value = getPrivacyMode();
void fetchPageVisuals();
if (!isLoggedIn()) {
reports.value = [];
redirectToLogin("/pages/report/index");
return;
}
try {
const data = await appApi.getReports();
reports.value = data.list;
} catch (error) {
reports.value = [];
showErrorToast(error, "报告加载失败");
}
});
</script>
<template>
<view class="report-page">
<view :class="['report-hero', reports.length === 0 ? 'report-hero--empty' : 'report-hero--list']">
<view class="report-hero__bg" :style="reportHeroStyle"></view>
<view class="report-nav">
<view class="report-nav__home" @click="goHome">
<view class="report-nav__home-roof"></view>
<view class="report-nav__home-body"></view>
</view>
<view class="report-nav__title">报告中心</view>
<view class="report-nav__capsule">
<text class="report-nav__dots"></text>
<view class="report-nav__divider"></view>
<view class="report-nav__circle"></view>
</view>
</view>
<view class="report-hero__content">
<view class="report-hero__title">报告中心</view>
<view class="report-hero__desc">正式报告会自动归档到这里支持查看结果下载 PDF 和继续验真</view>
<view class="report-stat-grid">
<view class="report-stat-card">
<view class="report-stat-card__value">{{ reportStats.total }}</view>
<view class="report-stat-card__label">报告总数</view>
</view>
<view class="report-stat-card">
<view class="report-stat-card__value">{{ reportStats.published }}</view>
<view class="report-stat-card__label">已出报告</view>
</view>
<view class="report-stat-card">
<view class="report-stat-card__value">{{ reportStats.pending }}</view>
<view class="report-stat-card__label">待生成</view>
</view>
</view>
</view>
</view>
<view v-if="reports.length === 0" class="report-empty-state">
<view class="report-empty-state__title">{{ emptyState.title }}</view>
<view class="report-empty-state__desc">{{ emptyState.desc }}</view>
<view class="report-empty-state__actions">
<view class="report-empty-state__button report-empty-state__button--primary" @click="goStartAppraisal">发起鉴定</view>
<view class="report-empty-state__button report-empty-state__button--secondary" @click="goHelp">查看帮助</view>
</view>
</view>
<view v-else class="report-list">
<view
v-for="item in reports"
:key="item.report_id || item.order_id"
class="report-card"
@click="openReport(item)"
>
<view class="report-card__top">
<view class="report-card__thumb">
<image v-if="item.product_cover" class="report-card__image" :src="item.product_cover" mode="aspectFill" />
<view v-else class="report-card__placeholder">
<view class="report-card__placeholder-line"></view>
<view class="report-card__placeholder-search"></view>
</view>
</view>
<view class="report-card__content">
<view class="report-card__title">{{ item.product_name }}</view>
<view class="report-card__no">
报告编号{{ item.report_no ? maskOrderNo(item.report_no, privacyMode) : "待生成" }}
</view>
<view class="report-card__institution">出具机构{{ item.institution_name || "待确认" }}</view>
</view>
<text
class="report-card__status"
:class="item.report_id ? 'report-card__status--success' : 'report-card__status--info'"
>
{{ item.report_id ? "已出报告" : "待生成" }}
</text>
</view>
<view class="report-card__footer">
<view class="report-card__result">{{ item.result_text || "等待鉴定结果" }}</view>
<view class="report-card__action">{{ item.report_id ? "查看报告" : "查看订单" }}</view>
</view>
</view>
</view>
</view>
</template>
<style lang="scss" scoped>
.report-page {
width: 100vw;
min-height: 100vh;
overflow-x: hidden;
padding-bottom: calc(48rpx + env(safe-area-inset-bottom));
background: #f2f2f4;
color: #2d2d2f;
font-family: "PingFang SC", "Microsoft YaHei", sans-serif;
}
.report-page,
.report-page view,
.report-page text {
box-sizing: border-box;
}
.report-hero {
position: relative;
width: 100vw;
overflow: hidden;
padding: 88rpx 32rpx 0;
}
.report-hero--empty {
height: 900rpx;
}
.report-hero--list {
height: 900rpx;
}
.report-hero__bg {
position: absolute;
inset: -18rpx;
background-repeat: no-repeat;
background-position: top center;
background-size: 100vw auto;
filter: blur(8rpx) saturate(1.04);
opacity: 0.76;
transform: scale(1.04);
pointer-events: none;
}
.report-hero::after {
content: "";
position: absolute;
inset: 0;
background:
linear-gradient(180deg, rgba(255, 255, 255, 0.03) 0%, rgba(242, 242, 244, 0.18) 45%, rgba(242, 242, 244, 0.78) 76%, #f2f2f4 96%),
linear-gradient(0deg, rgba(255, 255, 255, 0.34), rgba(255, 255, 255, 0.06));
pointer-events: none;
}
.report-nav,
.report-hero__content {
position: relative;
z-index: 1;
}
.report-nav {
display: flex;
align-items: center;
justify-content: space-between;
height: 76rpx;
}
.report-nav__title {
position: absolute;
left: 50%;
top: 50%;
color: #272729;
font-size: 34rpx;
line-height: 1;
font-weight: 700;
transform: translate(-50%, -50%);
white-space: nowrap;
}
.report-nav__home {
position: relative;
width: 56rpx;
height: 56rpx;
}
.report-nav__home-roof {
position: absolute;
left: 10rpx;
top: 7rpx;
width: 36rpx;
height: 36rpx;
border-top: 4rpx solid #2a2a2c;
border-left: 4rpx solid #2a2a2c;
transform: rotate(45deg);
}
.report-nav__home-body {
position: absolute;
left: 12rpx;
bottom: 8rpx;
width: 34rpx;
height: 30rpx;
border: 4rpx solid #2a2a2c;
border-top: 0;
border-radius: 0 0 8rpx 8rpx;
background: linear-gradient(135deg, transparent 0 36%, #f0c000 36% 100%);
}
.report-nav__capsule {
display: flex;
align-items: center;
justify-content: space-around;
width: 174rpx;
height: 64rpx;
padding: 0 20rpx;
border-radius: 34rpx;
background: rgba(255, 255, 255, 0.72);
box-shadow: 0 6rpx 18rpx rgba(0, 0, 0, 0.05);
backdrop-filter: blur(12rpx);
}
.report-nav__dots {
color: #050505;
font-size: 36rpx;
line-height: 1;
font-weight: 800;
}
.report-nav__divider {
width: 1rpx;
height: 34rpx;
background: rgba(20, 20, 20, 0.68);
}
.report-nav__circle {
width: 38rpx;
height: 38rpx;
border: 7rpx solid #070707;
border-radius: 50%;
}
.report-hero__content {
margin-top: 260rpx;
}
.report-hero__title {
color: #262628;
font-size: 70rpx;
line-height: 1.08;
font-weight: 800;
letter-spacing: 0;
}
.report-hero__desc {
width: 650rpx;
max-width: 100%;
margin-top: 28rpx;
color: #666;
font-size: 29rpx;
line-height: 1.7;
}
.report-stat-grid {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 42rpx;
margin: 26rpx 22rpx 0;
}
.report-stat-card {
height: 176rpx;
border-radius: 10rpx;
background: rgba(255, 255, 255, 0.9);
text-align: center;
box-shadow: 0 10rpx 24rpx rgba(0, 0, 0, 0.02);
}
.report-stat-card__value {
margin-top: 35rpx;
color: #2b2b2d;
font-size: 68rpx;
line-height: 1;
font-weight: 500;
}
.report-stat-card__label {
margin-top: 18rpx;
color: #686868;
font-size: 25rpx;
line-height: 1;
}
.report-empty-state {
margin: 240rpx 32rpx 0;
text-align: center;
}
.report-empty-state__title {
color: #202022;
font-size: 40rpx;
line-height: 1.2;
font-weight: 800;
}
.report-empty-state__desc {
width: 560rpx;
max-width: 100%;
margin: 28rpx auto 0;
color: #818181;
font-size: 29rpx;
line-height: 1.55;
}
.report-empty-state__actions {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 38rpx;
margin-top: 50rpx;
}
.report-empty-state__button {
min-width: 0;
height: 68rpx;
border-radius: 36rpx;
font-size: 25rpx;
font-weight: 700;
line-height: 68rpx;
text-align: center;
}
.report-empty-state__button--primary {
background: #edbd00;
color: #fff;
}
.report-empty-state__button--secondary {
background: #fff;
color: #29292b;
}
.report-list {
display: grid;
gap: 22rpx;
margin: -22rpx 32rpx 0;
padding-bottom: 36rpx;
}
.report-card {
padding: 24rpx;
border-radius: 16rpx;
background: #fff;
box-shadow: 0 12rpx 26rpx rgba(0, 0, 0, 0.035);
}
.report-card__top {
display: flex;
align-items: flex-start;
gap: 20rpx;
}
.report-card__thumb {
flex: 0 0 104rpx;
width: 104rpx;
height: 104rpx;
overflow: hidden;
border-radius: 12rpx;
background: #f5f5f5;
}
.report-card__image {
display: block;
width: 104rpx;
height: 104rpx;
}
.report-card__placeholder {
position: relative;
width: 104rpx;
height: 104rpx;
background: #f7f7f7;
}
.report-card__placeholder-line {
position: absolute;
left: 31rpx;
top: 22rpx;
width: 36rpx;
height: 52rpx;
border: 5rpx solid #2e2e30;
border-radius: 4rpx;
}
.report-card__placeholder-search {
position: absolute;
right: 18rpx;
bottom: 18rpx;
width: 34rpx;
height: 34rpx;
border: 5rpx solid #edbd00;
border-radius: 50%;
}
.report-card__placeholder-search::after {
content: "";
position: absolute;
right: -10rpx;
bottom: -6rpx;
width: 18rpx;
height: 5rpx;
border-radius: 4rpx;
background: #edbd00;
transform: rotate(45deg);
}
.report-card__content {
min-width: 0;
flex: 1;
}
.report-card__title {
color: #242426;
font-size: 31rpx;
line-height: 1.25;
font-weight: 800;
}
.report-card__no,
.report-card__institution {
margin-top: 10rpx;
color: #777;
font-size: 23rpx;
line-height: 1.45;
}
.report-card__status {
flex-shrink: 0;
height: 42rpx;
padding: 0 16rpx;
border-radius: 22rpx;
font-size: 22rpx;
line-height: 42rpx;
}
.report-card__status--success {
background: #eaf7ef;
color: #2d7652;
}
.report-card__status--info {
background: #edf4ff;
color: #28669f;
}
.report-card__footer {
display: flex;
align-items: center;
justify-content: space-between;
gap: 18rpx;
margin-top: 24rpx;
}
.report-card__result {
min-width: 0;
color: #777;
font-size: 24rpx;
line-height: 1.45;
}
.report-card__action {
flex-shrink: 0;
min-width: 126rpx;
height: 48rpx;
padding: 0 20rpx;
border-radius: 25rpx;
background: #f1bd00;
color: #fff;
font-size: 23rpx;
font-weight: 700;
line-height: 48rpx;
text-align: center;
}
@media (max-width: 360px) {
.report-stat-grid {
gap: 22rpx;
margin-left: 12rpx;
margin-right: 12rpx;
}
.report-empty-state__actions {
gap: 22rpx;
}
.report-card__top {
gap: 14rpx;
}
}
</style>