fix: simplify work app return report review

This commit is contained in:
wushumin
2026-05-17 01:04:33 +08:00
parent ba987283dc
commit 7ba3ac1b67

View File

@@ -1,7 +1,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { computed, ref } from "vue"; import { computed, ref } from "vue";
import { onLoad, onShow } from "@dcloudio/uni-app"; import { onLoad, onShow } from "@dcloudio/uni-app";
import { adminApi, type AdminReportDetail } from "../../api/admin"; import { adminApi, type AdminFileAsset, type AdminReportDetail } from "../../api/admin";
import { showErrorToast, showInfoToast } from "../../utils/feedback"; import { showErrorToast, showInfoToast } from "../../utils/feedback";
const loading = ref(false); const loading = ref(false);
@@ -11,37 +11,122 @@ const detail = ref<AdminReportDetail | null>(null);
const reportId = ref(0); const reportId = ref(0);
const returnInternalTagNo = ref(""); const returnInternalTagNo = ref("");
const returnConfirming = ref(false); const returnConfirming = ref(false);
const activeVideo = ref<AdminFileAsset | null>(null);
const isZhongjian = computed(() => detail.value?.report_header.service_provider === "zhongjian"); const isZhongjian = computed(() => detail.value?.report_header.service_provider === "zhongjian");
const isReturnReview = computed(() => Boolean(returnInternalTagNo.value && reportId.value)); const isReturnReview = computed(() => Boolean(returnInternalTagNo.value && reportId.value));
const reportMetaItems = computed(() => {
const current = detail.value;
if (!current) return [];
const items = [
{ label: "发布时间", value: current.report_header.publish_time || "-" },
];
const appraiserName = textValue(current.appraisal_info?.appraiser_name)
|| textValue(current.report_header.report_entry_admin_name);
if (appraiserName) {
items.push({ label: "鉴定师", value: appraiserName });
}
if (isZhongjian.value) {
items.push({ label: "中检报告号", value: current.report_header.zhongjian_report_no || "-" });
items.push({ label: "报告录入人", value: current.report_header.report_entry_admin_name || "-" });
}
return items;
});
const resultMetaItems = computed(() => {
const current = detail.value;
if (!current) return [];
const result = current.result_info || {};
const valuation = current.valuation_info || {};
const items = [
{ label: "结论", value: textValue(result.result_text) || "-" },
{ label: "结论说明", value: textValue(result.result_desc) || "-" },
];
for (const point of normalizedKeyPoints(result.key_points)) {
items.push({
label: point.point_name,
value: [point.point_value, point.point_remark].filter(Boolean).join("") || "-",
});
}
const conditionGrade = textValue(valuation.condition_grade);
const conditionDesc = textValue(valuation.condition_desc);
if (conditionGrade || conditionDesc) {
items.push({ label: "成色评级", value: [conditionGrade, conditionDesc].filter(Boolean).join("") || "-" });
}
const valuationMin = Number(valuation.valuation_min || 0);
const valuationMax = Number(valuation.valuation_max || 0);
const valuationDesc = textValue(valuation.valuation_desc);
if (valuationMin > 0 || valuationMax > 0 || valuationDesc) {
const range = valuationMin > 0 || valuationMax > 0 ? `¥${valuationMin} - ¥${valuationMax}` : "";
items.push({ label: "估值", value: [range, valuationDesc].filter(Boolean).join("") || "-" });
}
return items;
});
const evidenceAttachments = computed(() => detail.value?.evidence_attachments || []);
const zhongjianReportFiles = computed(() => isZhongjian.value ? (detail.value?.zhongjian_report_files || []) : []);
function previewImage(urls: string[], current: string) { function previewImage(urls: string[], current: string) {
if (!urls.length) return; if (!urls.length) return;
uni.previewImage({ urls, current }); uni.previewImage({ urls, current });
} }
function openAsset(item: { file_url: string; file_type?: string; thumbnail_url?: string }) { function textValue(value: unknown) {
if (item.file_type === "image") { return String(value ?? "").trim();
}
function normalizedKeyPoints(value: unknown) {
if (!Array.isArray(value)) return [];
return value
.map((item) => {
const point = item as Record<string, unknown>;
return {
point_code: textValue(point.point_code),
point_name: textValue(point.point_name) || "鉴定项",
point_value: textValue(point.point_value),
point_remark: textValue(point.point_remark),
};
})
.filter((item) => item.point_value || item.point_remark);
}
function isImageAsset(item: AdminFileAsset) {
return item.file_type === "image" || item.mime_type?.startsWith("image/");
}
function isVideoAsset(item: AdminFileAsset) {
return item.file_type === "video" || item.mime_type?.startsWith("video/");
}
function assetTypeLabel(item: AdminFileAsset) {
if (isImageAsset(item)) return "图片";
if (isVideoAsset(item)) return "视频";
if (item.file_type === "pdf" || item.mime_type === "application/pdf") return "PDF";
return "附件";
}
function openAsset(item: AdminFileAsset) {
if (isImageAsset(item)) {
const urls = [ const urls = [
...(detail.value?.evidence_attachments || []).map((asset) => asset.file_url), ...evidenceAttachments.value.filter(isImageAsset).map((asset) => asset.file_url),
...(detail.value?.zhongjian_report_files || []).map((asset) => asset.file_url), ...zhongjianReportFiles.value.filter(isImageAsset).map((asset) => asset.file_url),
]; ];
previewImage(urls, item.file_url); previewImage(urls, item.file_url);
return; return;
} }
if (item.file_type === "video") { if (isVideoAsset(item)) {
const previewMedia = (uni as any).previewMedia; activeVideo.value = item;
if (typeof previewMedia === "function") { return;
previewMedia({
sources: [{ url: item.file_url, type: "video", poster: item.thumbnail_url || "" }],
current: 0,
});
return;
}
} }
if (item.file_type === "pdf") { if (item.file_type === "pdf" || item.mime_type === "application/pdf") {
uni.showLoading({ title: "准备打开" });
uni.downloadFile({ uni.downloadFile({
url: item.file_url, url: item.file_url,
success: (response) => { success: (response) => {
@@ -56,6 +141,7 @@ function openAsset(item: { file_url: string; file_type?: string; thumbnail_url?:
}); });
}, },
fail: () => uni.showToast({ title: "附件打开失败", icon: "none" }), fail: () => uni.showToast({ title: "附件打开失败", icon: "none" }),
complete: () => uni.hideLoading(),
}); });
return; return;
} }
@@ -63,6 +149,10 @@ function openAsset(item: { file_url: string; file_type?: string; thumbnail_url?:
uni.showToast({ title: "当前附件暂不支持打开", icon: "none" }); uni.showToast({ title: "当前附件暂不支持打开", icon: "none" });
} }
function closeVideo() {
activeVideo.value = null;
}
async function fetchDetail() { async function fetchDetail() {
if (!reportId.value) return; if (!reportId.value) return;
loading.value = true; loading.value = true;
@@ -145,7 +235,7 @@ onShow(() => {
<view v-if="isReturnReview" class="card return-review-card"> <view v-if="isReturnReview" class="card return-review-card">
<view class="card-title">回寄前报告核对</view> <view class="card-title">回寄前报告核对</view>
<view class="card-desc">请核对报告编号结论附件和验真信息确认无误后进入回寄信息填写</view> <view class="card-desc">请核对报告编号结论和附件确认无误后进入回寄信息填写</view>
<button class="btn btn--primary main-action" :disabled="returnConfirming" @click="confirmReturnFromReport"> <button class="btn btn--primary main-action" :disabled="returnConfirming" @click="confirmReturnFromReport">
{{ returnConfirming ? "确认中" : "确认寄回" }} {{ returnConfirming ? "确认中" : "确认寄回" }}
</button> </button>
@@ -160,21 +250,9 @@ onShow(() => {
<text class="tag">{{ detail.report_header.service_provider_text }}</text> <text class="tag">{{ detail.report_header.service_provider_text }}</text>
</view> </view>
<view class="meta-grid"> <view class="meta-grid">
<view class="meta-item"> <view v-for="item in reportMetaItems" :key="item.label" class="meta-item">
<view class="meta-label">发布时间</view> <view class="meta-label">{{ item.label }}</view>
<view class="meta-value">{{ detail.report_header.publish_time || "-" }}</view> <view class="meta-value">{{ item.value }}</view>
</view>
<view class="meta-item">
<view class="meta-label">录入人</view>
<view class="meta-value">{{ detail.report_header.report_entry_admin_name || "-" }}</view>
</view>
<view class="meta-item">
<view class="meta-label">中检报告号</view>
<view class="meta-value">{{ detail.report_header.zhongjian_report_no || "-" }}</view>
</view>
<view class="meta-item">
<view class="meta-label">验真次数</view>
<view class="meta-value">{{ detail.verify_info.verify_count }}</view>
</view> </view>
</view> </view>
</view> </view>
@@ -200,79 +278,63 @@ onShow(() => {
<view class="card"> <view class="card">
<view class="card-title">鉴定结果</view> <view class="card-title">鉴定结果</view>
<view class="meta-grid"> <view class="meta-grid">
<view class="meta-item"> <view v-for="(item, index) in resultMetaItems" :key="`${item.label}-${index}`" class="meta-item">
<view class="meta-label">结论</view> <view class="meta-label">{{ item.label }}</view>
<view class="meta-value">{{ detail.result_info.result_text || "-" }}</view> <view class="meta-value">{{ item.value }}</view>
</view>
<view class="meta-item">
<view class="meta-label">结论说明</view>
<view class="meta-value">{{ detail.result_info.result_desc || "-" }}</view>
</view>
<view class="meta-item">
<view class="meta-label">评级</view>
<view class="meta-value">{{ detail.valuation_info.condition_grade || "-" }}</view>
</view>
<view class="meta-item">
<view class="meta-label">估值区间</view>
<view class="meta-value">¥{{ detail.valuation_info.valuation_min || 0 }} - ¥{{ detail.valuation_info.valuation_max || 0 }}</view>
</view> </view>
</view> </view>
</view> </view>
<view class="card"> <view class="card">
<view class="card-title">附件</view> <view class="card-title">附件</view>
<view v-if="detail.evidence_attachments.length" class="list" style="margin-top: 18rpx"> <view v-if="evidenceAttachments.length" class="attachment-grid">
<view <view v-for="item in evidenceAttachments" :key="item.file_url || item.file_id" class="attachment-tile">
v-for="item in detail.evidence_attachments" <view class="attachment-preview" @click="openAsset(item)">
:key="item.file_id" <image v-if="isImageAsset(item)" class="attachment-thumb" :src="item.thumbnail_url || item.file_url" mode="aspectFill" />
class="list-card" <template v-else-if="isVideoAsset(item)">
@click="openAsset(item)" <image v-if="item.thumbnail_url" class="attachment-thumb" :src="item.thumbnail_url" mode="aspectFill" />
> <view v-else class="attachment-video-thumb">
<view class="row"> <text class="attachment-video-label">视频</text>
<view class="list-title">{{ item.name || item.file_id }}</view> </view>
<text class="tag">{{ item.file_type || "附件" }}</text> </template>
<view v-else class="attachment-file-thumb">{{ assetTypeLabel(item) }}</view>
<view v-if="isVideoAsset(item)" class="attachment-play" @click.stop="openAsset(item)"></view>
</view> </view>
<view class="attachment-name">{{ item.name || item.file_id }}</view>
<text class="attachment-type">{{ assetTypeLabel(item) }}</text>
</view> </view>
</view> </view>
<view v-else class="empty" style="padding: 24rpx 0">暂无证据附件</view> <view v-else class="empty" style="padding: 24rpx 0">暂无证据附件</view>
</view> </view>
<view v-if="detail.zhongjian_report_files.length" class="card"> <view v-if="zhongjianReportFiles.length" class="card">
<view class="card-title">中检报告文件</view> <view class="card-title">中检报告文件</view>
<view class="list" style="margin-top: 18rpx"> <view class="attachment-grid">
<view <view v-for="item in zhongjianReportFiles" :key="item.file_url || item.file_id" class="attachment-tile">
v-for="item in detail.zhongjian_report_files" <view class="attachment-preview" @click="openAsset(item)">
:key="item.file_id" <image v-if="isImageAsset(item)" class="attachment-thumb" :src="item.thumbnail_url || item.file_url" mode="aspectFill" />
class="list-card" <template v-else-if="isVideoAsset(item)">
@click="openAsset(item)" <image v-if="item.thumbnail_url" class="attachment-thumb" :src="item.thumbnail_url" mode="aspectFill" />
> <view v-else class="attachment-video-thumb">
<view class="row"> <text class="attachment-video-label">视频</text>
<view class="list-title">{{ item.name || item.file_id }}</view> </view>
<text class="tag">{{ item.file_type || "附件" }}</text> </template>
<view v-else class="attachment-file-thumb">{{ assetTypeLabel(item) }}</view>
<view v-if="isVideoAsset(item)" class="attachment-play" @click.stop="openAsset(item)"></view>
</view> </view>
<view class="attachment-name">{{ item.name || item.file_id }}</view>
<text class="attachment-type">{{ assetTypeLabel(item) }}</text>
</view> </view>
</view> </view>
</view> </view>
<view class="card"> <view v-if="activeVideo" class="video-preview-mask" @click="closeVideo">
<view class="card-title">验真信息</view> <view class="video-preview-panel" @click.stop>
<view class="meta-grid"> <view class="video-preview-head">
<view class="meta-item"> <text class="video-preview-title">{{ activeVideo.name || "附件视频" }}</text>
<view class="meta-label">验真状态</view> <text class="video-preview-close" @click="closeVideo">关闭</text>
<view class="meta-value">{{ isZhongjian ? "中检报告" : detail.verify_info.verify_status || "valid" }}</view>
</view>
<view class="meta-item">
<view class="meta-label">验真页</view>
<view class="meta-value">{{ detail.verify_info.verify_url || "-" }}</view>
</view>
<view class="meta-item">
<view class="meta-label">验真二维码</view>
<view class="meta-value">{{ detail.verify_info.verify_qrcode_url || "-" }}</view>
</view>
<view class="meta-item">
<view class="meta-label">报告页</view>
<view class="meta-value">{{ detail.verify_info.report_page_url || "-" }}</view>
</view> </view>
<video class="video-preview-player" :src="activeVideo.file_url" :poster="activeVideo.thumbnail_url || ''" controls autoplay />
</view> </view>
</view> </view>
</template> </template>
@@ -292,4 +354,148 @@ onShow(() => {
.main-action { .main-action {
margin-top: 18rpx; margin-top: 18rpx;
} }
.attachment-grid {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 14rpx;
margin-top: 18rpx;
}
.attachment-tile {
min-width: 0;
}
.attachment-preview {
position: relative;
width: 100%;
aspect-ratio: 1;
overflow: hidden;
border: 1px solid var(--work-border);
border-radius: var(--work-radius-sm);
background: var(--work-card-muted);
}
.attachment-thumb {
display: block;
width: 100%;
height: 100%;
}
.attachment-video-thumb,
.attachment-file-thumb {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 100%;
}
.attachment-video-thumb {
background: linear-gradient(135deg, #f8fafc 0%, #e8edf3 100%);
color: var(--work-text);
}
.attachment-video-label {
font-size: 24rpx;
font-weight: 900;
}
.attachment-file-thumb {
color: var(--work-text-soft);
font-size: 24rpx;
font-weight: 800;
}
.attachment-play {
position: absolute;
left: 50%;
top: 50%;
width: 54rpx;
height: 54rpx;
margin-left: -27rpx;
margin-top: -27rpx;
border-radius: 50%;
background: rgba(32, 33, 36, 0.72);
color: #ffffff;
font-size: 28rpx;
line-height: 54rpx;
text-align: center;
}
.attachment-name {
min-width: 0;
margin-top: 8rpx;
overflow: hidden;
color: var(--work-text);
font-size: 22rpx;
font-weight: 700;
line-height: 1.35;
text-overflow: ellipsis;
white-space: nowrap;
}
.attachment-type {
display: inline-flex;
align-items: center;
min-height: 34rpx;
margin-top: 6rpx;
padding: 0 10rpx;
border-radius: var(--work-radius-pill);
background: var(--work-info-soft);
color: var(--work-info);
font-size: 20rpx;
font-weight: 700;
line-height: 34rpx;
}
.video-preview-mask {
position: fixed;
z-index: 20;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
padding: 32rpx;
background: rgba(0, 0, 0, 0.58);
}
.video-preview-panel {
width: 100%;
overflow: hidden;
border-radius: var(--work-radius);
background: #ffffff;
}
.video-preview-head {
display: flex;
align-items: center;
justify-content: space-between;
gap: 18rpx;
padding: 22rpx 24rpx;
}
.video-preview-title {
min-width: 0;
overflow: hidden;
color: var(--work-text);
font-size: 28rpx;
font-weight: 800;
text-overflow: ellipsis;
white-space: nowrap;
}
.video-preview-close {
flex: 0 0 auto;
color: var(--work-info);
font-size: 26rpx;
font-weight: 800;
}
.video-preview-player {
display: block;
width: 100%;
height: 58vh;
background: #000000;
}
</style> </style>