feat: align work app report detail style

This commit is contained in:
wushumin
2026-05-22 21:35:30 +08:00
parent 78098851f9
commit 21360a6a2c
2 changed files with 663 additions and 248 deletions

View File

@@ -2,8 +2,8 @@
"name": "安心验作业端", "name": "安心验作业端",
"appid": "__UNI__E0C8390", "appid": "__UNI__E0C8390",
"description": "安心验仓管与鉴定作业 Android App", "description": "安心验仓管与鉴定作业 Android App",
"versionName": "1.0.1", "versionName": "1.0.2",
"versionCode": "102", "versionCode": "103",
"transformPx": false, "transformPx": false,
"app-plus": { "app-plus": {
"usingComponents": true, "usingComponents": true,

View File

@@ -4,6 +4,8 @@ import { onLoad, onShow } from "@dcloudio/uni-app";
import { adminApi, type AdminFileAsset, 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";
type ReportTab = "product" | "attachments";
const loading = ref(false); const loading = ref(false);
const pageReady = ref(false); const pageReady = ref(false);
const loadError = ref(""); const loadError = ref("");
@@ -11,74 +13,83 @@ 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 activeTab = ref<ReportTab>("product");
const activeVideo = ref<AdminFileAsset | null>(null); 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 || "-",
});
}
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("") || "-" });
}
const externalRemark = textValue(result.external_remark);
if (externalRemark) {
items.push({ label: "备注", value: externalRemark });
}
return items;
});
const evidenceAttachments = computed(() => detail.value?.evidence_attachments || []); const evidenceAttachments = computed(() => detail.value?.evidence_attachments || []);
const zhongjianReportFiles = computed(() => isZhongjian.value ? (detail.value?.zhongjian_report_files || []) : []); const zhongjianReportFiles = computed(() => isZhongjian.value ? (detail.value?.zhongjian_report_files || []) : []);
const imageEvidenceList = computed(() => evidenceAttachments.value.filter(isImageAsset));
const fileEvidenceList = computed(() => evidenceAttachments.value.filter((item) => !isImageAsset(item)));
const zhongjianImageFiles = computed(() => zhongjianReportFiles.value.filter(isImageAsset));
const zhongjianOtherFiles = computed(() => zhongjianReportFiles.value.filter((item) => !isImageAsset(item)));
const reportImages = computed(() => {
if (imageEvidenceList.value.length) return imageEvidenceList.value;
return zhongjianImageFiles.value;
});
const hasAttachments = computed(() => evidenceAttachments.value.length > 0 || zhongjianReportFiles.value.length > 0);
const reportNo = computed(() => textValue(detail.value?.report_header.report_no) || "-");
const productName = computed(() => textValue(detail.value?.product_info?.product_name) || "-");
const institutionName = computed(() => textValue(detail.value?.report_header.institution_name) || "-");
const publishTime = computed(() => textValue(detail.value?.report_header.publish_time) || "-");
const resultItem = computed(() => {
const result = detail.value?.result_info || {};
return {
label: "检测结论",
value: textValue(result.result_text) || "-",
remark: textValue(result.result_desc),
};
});
const productSpecItems = computed(() => {
const current = detail.value;
if (!current) return [];
function previewImage(urls: string[], current: string) { const product = current.product_info || {};
if (!urls.length) return; const result = current.result_info || {};
uni.previewImage({ urls, current }); const valuation = current.valuation_info || {};
const items: Array<{ label: string; value: string; remark?: string }> = [];
appendSpecItem(items, "品类", product.category_name);
appendSpecItem(items, "品牌", product.brand_name);
appendSpecItem(items, "颜色", product.color);
appendSpecItem(items, "规格/尺寸", product.size_spec);
appendSpecItem(items, "序列号/编码", product.serial_no);
for (const point of normalizedKeyPoints(result.key_points)) {
appendSpecItem(items, point.point_name, point.point_value, point.point_remark);
}
appendSpecItem(items, "服务类型", current.report_header.service_provider_text);
if (isZhongjian.value) {
appendSpecItem(items, "中检报告号", current.report_header.zhongjian_report_no);
appendSpecItem(items, "报告录入人", current.report_header.report_entry_admin_name);
}
const appraiserName = textValue(current.appraisal_info?.appraiser_name)
|| textValue(current.report_header.report_entry_admin_name);
appendSpecItem(items, "鉴定师", appraiserName);
appendSpecItem(items, "成色评级", valuation.condition_grade, valuation.condition_desc);
appendSpecItem(items, "估值区间", formatValuationRange(valuation.valuation_min, valuation.valuation_max), valuation.valuation_desc);
appendSpecItem(items, "备注", result.external_remark);
return items;
});
function appendSpecItem(
items: Array<{ label: string; value: string; remark?: string }>,
label: string,
value: unknown,
remark: unknown = "",
) {
const valueText = textValue(value);
const remarkText = textValue(remark);
if (!valueText && !remarkText) return;
items.push({
label,
value: valueText || "-",
remark: remarkText,
});
} }
function textValue(value: unknown) { function textValue(value: unknown) {
@@ -97,7 +108,25 @@ function normalizedKeyPoints(value: unknown) {
point_remark: textValue(point.point_remark), point_remark: textValue(point.point_remark),
}; };
}) })
.filter((item) => item.point_value); .filter((item) => item.point_value || item.point_remark);
}
function formatValuationRange(min: unknown, max: unknown) {
const minValue = Number(min || 0);
const maxValue = Number(max || 0);
if (minValue <= 0 && maxValue <= 0) return "";
if (minValue > 0 && maxValue > 0) return `¥${formatMoney(minValue)} - ¥${formatMoney(maxValue)}`;
if (minValue > 0) return `¥${formatMoney(minValue)}`;
return `¥${formatMoney(maxValue)}`;
}
function formatMoney(value: number) {
return String(Number(value.toFixed(2))).replace(/\.0+$/, "");
}
function previewImage(urls: string[], current: string) {
if (!urls.length) return;
uni.previewImage({ urls, current });
} }
function isImageAsset(item: AdminFileAsset) { function isImageAsset(item: AdminFileAsset) {
@@ -115,13 +144,13 @@ function assetTypeLabel(item: AdminFileAsset) {
return "附件"; return "附件";
} }
function openAsset(item: AdminFileAsset) { function assetDisplayName(item: AdminFileAsset, index: number) {
return item.name || `${assetTypeLabel(item)} ${index + 1}`;
}
function openAsset(item: AdminFileAsset, files: AdminFileAsset[]) {
if (isImageAsset(item)) { if (isImageAsset(item)) {
const urls = [ previewImage(files.filter(isImageAsset).map((asset) => asset.file_url), item.file_url);
...evidenceAttachments.value.filter(isImageAsset).map((asset) => asset.file_url),
...zhongjianReportFiles.value.filter(isImageAsset).map((asset) => asset.file_url),
];
previewImage(urls, item.file_url);
return; return;
} }
@@ -227,112 +256,152 @@ onShow(() => {
</script> </script>
<template> <template>
<view class="page"> <view :class="['page', 'report-page', isReturnReview ? 'report-page--with-action' : '']">
<view v-if="!pageReady && loading" class="empty">正在加载报告详情</view> <view v-if="!pageReady && loading" class="notice-card">
<view v-else-if="!pageReady && loadError" class="empty">{{ loadError }}</view> <view class="notice-card__title">正在加载报告详情</view>
<view class="notice-card__desc">请稍候正在同步报告正文与附件</view>
</view>
<view v-else-if="!pageReady && loadError" class="notice-card">
<view class="notice-card__title">报告详情加载失败</view>
<view class="notice-card__desc">{{ loadError }}</view>
</view>
<template v-else-if="detail"> <template v-else-if="detail">
<view class="hero"> <view v-if="isReturnReview" class="review-strip">
<view class="eyebrow">报告详情</view> <view>
<view class="title">{{ detail.report_header.report_title }}</view> <view class="review-strip__title">回寄前报告核对</view>
<view class="subtitle">{{ detail.report_header.report_no }}</view> <view class="review-strip__desc">核对报告编号结论和附件后进入回寄登记</view>
</view>
<text class="review-strip__tag">待确认</text>
</view> </view>
<view v-if="isReturnReview" class="card return-review-card"> <view class="report-shell">
<view class="card-title">回寄前报告核对</view> <view class="report-cover">
<view class="card-desc">请核对报告编号结论和附件确认无误后进入回寄信息填写</view> <swiper v-if="reportImages.length" class="report-cover__swiper" indicator-dots circular>
<button class="btn btn--primary main-action" :disabled="returnConfirming" @click="confirmReturnFromReport"> <swiper-item v-for="item in reportImages" :key="item.file_url || item.file_id">
<image
class="report-cover__image"
:src="item.thumbnail_url || item.file_url"
mode="aspectFill"
@click="openAsset(item, reportImages)"
/>
</swiper-item>
</swiper>
<view v-else class="report-cover__empty">暂无鉴定图片</view>
</view>
<view class="report-meta">
<view class="report-meta__row">
<text class="report-meta__label">产品名称</text>
<text class="report-meta__value">{{ productName }}</text>
</view>
<view class="report-meta__row">
<text class="report-meta__label">检测机构</text>
<text class="report-meta__value">{{ institutionName }}</text>
</view>
<view class="report-meta__row">
<text class="report-meta__label">报告编号</text>
<text class="report-meta__value">{{ reportNo }}</text>
</view>
<view class="report-meta__row report-meta__row--date">
<text class="report-meta__label">出具日期</text>
<text class="report-meta__tag">{{ detail.report_header.report_status_text }}</text>
<text class="report-meta__value">{{ publishTime }}</text>
</view>
</view>
<view class="report-tabs">
<view :class="['report-tab', activeTab === 'product' ? 'report-tab--active' : '']" @click="activeTab = 'product'">产品信息</view>
<view :class="['report-tab', activeTab === 'attachments' ? 'report-tab--active' : '']" @click="activeTab = 'attachments'">核验附件</view>
</view>
<view v-if="activeTab === 'product'" class="report-panel">
<view class="report-watermark" aria-hidden="true"></view>
<view class="report-result">
<view class="report-result__content">
<view class="report-result__label">{{ resultItem.label }}</view>
<view class="report-result__value">{{ resultItem.value }}</view>
<view v-if="resultItem.remark" class="report-result__desc">{{ resultItem.remark }}</view>
</view>
<view class="report-seal">
<text class="report-seal__brand">ANXINYAN</text>
<text class="report-seal__main">可信</text>
</view>
</view>
<view class="product-spec">
<view v-for="(item, index) in productSpecItems" :key="`${item.label}-${index}`" class="product-spec__row">
<view class="product-spec__label">{{ item.label }}</view>
<view class="product-spec__line"></view>
<view class="product-spec__value">{{ item.value || "-" }}</view>
<view v-if="item.remark" class="product-spec__remark">{{ item.remark }}</view>
</view>
</view>
</view>
<view v-else class="report-panel report-panel--attachments">
<view v-if="!hasAttachments" class="attachment-empty">暂无核验附件</view>
<view v-if="evidenceAttachments.length" class="inline-section">
<view class="inline-section__title">鉴定证据</view>
<view v-if="imageEvidenceList.length" class="asset-grid">
<view
v-for="item in imageEvidenceList"
:key="item.file_url || item.file_id"
class="asset-tile"
@click="openAsset(item, imageEvidenceList)"
>
<image class="asset-tile__image" :src="item.thumbnail_url || item.file_url" mode="aspectFill" />
</view>
</view>
<view v-if="fileEvidenceList.length" class="asset-list">
<view
v-for="(item, index) in fileEvidenceList"
:key="item.file_url || item.file_id"
class="asset-list__item"
@click="openAsset(item, fileEvidenceList)"
>
<text>{{ assetDisplayName(item, index) }}</text>
<text class="asset-list__type">{{ assetTypeLabel(item) }}</text>
</view>
</view>
</view>
<view v-if="isZhongjian || zhongjianReportFiles.length" class="inline-section">
<view class="inline-section__title">中检报告文件</view>
<view v-if="zhongjianImageFiles.length" class="asset-grid">
<view
v-for="item in zhongjianImageFiles"
:key="item.file_url || item.file_id"
class="asset-tile"
@click="openAsset(item, zhongjianImageFiles)"
>
<image class="asset-tile__image" :src="item.thumbnail_url || item.file_url" mode="aspectFill" />
</view>
</view>
<view v-if="zhongjianOtherFiles.length" class="asset-list">
<view
v-for="(item, index) in zhongjianOtherFiles"
:key="item.file_url || item.file_id"
class="asset-list__item"
@click="openAsset(item, zhongjianOtherFiles)"
>
<text>{{ assetDisplayName(item, index) }}</text>
<text class="asset-list__type">{{ assetTypeLabel(item) }}</text>
</view>
</view>
<view v-if="isZhongjian && !zhongjianReportFiles.length" class="attachment-empty attachment-empty--compact">暂无中检报告文件</view>
</view>
</view>
</view>
<view v-if="isReturnReview" class="report-actions">
<button class="report-actions__button" :disabled="returnConfirming" @click="confirmReturnFromReport">
{{ returnConfirming ? "确认中" : "确认寄回" }} {{ returnConfirming ? "确认中" : "确认寄回" }}
</button> </button>
</view> </view>
<view class="card">
<view class="row">
<view>
<view class="card-title">{{ detail.report_header.report_status_text }}</view>
<view class="card-desc">{{ detail.report_header.institution_name }}</view>
</view>
<text class="tag">{{ detail.report_header.service_provider_text }}</text>
</view>
<view class="meta-grid">
<view v-for="item in reportMetaItems" :key="item.label" class="meta-item">
<view class="meta-label">{{ item.label }}</view>
<view class="meta-value">{{ item.value }}</view>
</view>
</view>
</view>
<view class="card">
<view class="card-title">商品信息</view>
<view class="meta-grid">
<view class="meta-item">
<view class="meta-label">商品名称</view>
<view class="meta-value">{{ detail.product_info.product_name || "-" }}</view>
</view>
<view class="meta-item">
<view class="meta-label">品类 / 品牌</view>
<view class="meta-value">{{ detail.product_info.category_name || "-" }} / {{ detail.product_info.brand_name || "-" }}</view>
</view>
<view class="meta-item">
<view class="meta-label">颜色 / 规格</view>
<view class="meta-value">{{ detail.product_info.color || "-" }} / {{ detail.product_info.size_spec || "-" }}</view>
</view>
</view>
</view>
<view class="card">
<view class="card-title">鉴定结果</view>
<view class="meta-grid">
<view v-for="(item, index) in resultMetaItems" :key="`${item.label}-${index}`" class="meta-item">
<view class="meta-label">{{ item.label }}</view>
<view class="meta-value">{{ item.value }}</view>
</view>
</view>
</view>
<view class="card">
<view class="card-title">附件</view>
<view v-if="evidenceAttachments.length" class="attachment-grid">
<view v-for="item in evidenceAttachments" :key="item.file_url || item.file_id" class="attachment-tile">
<view class="attachment-preview" @click="openAsset(item)">
<image v-if="isImageAsset(item)" class="attachment-thumb" :src="item.thumbnail_url || item.file_url" mode="aspectFill" />
<template v-else-if="isVideoAsset(item)">
<image v-if="item.thumbnail_url" class="attachment-thumb" :src="item.thumbnail_url" mode="aspectFill" />
<view v-else class="attachment-video-thumb">
<text class="attachment-video-label">视频</text>
</view>
</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 class="attachment-name">{{ item.name || item.file_id }}</view>
<text class="attachment-type">{{ assetTypeLabel(item) }}</text>
</view>
</view>
<view v-else class="empty" style="padding: 24rpx 0">暂无证据附件</view>
</view>
<view v-if="zhongjianReportFiles.length" class="card">
<view class="card-title">中检报告文件</view>
<view class="attachment-grid">
<view v-for="item in zhongjianReportFiles" :key="item.file_url || item.file_id" class="attachment-tile">
<view class="attachment-preview" @click="openAsset(item)">
<image v-if="isImageAsset(item)" class="attachment-thumb" :src="item.thumbnail_url || item.file_url" mode="aspectFill" />
<template v-else-if="isVideoAsset(item)">
<image v-if="item.thumbnail_url" class="attachment-thumb" :src="item.thumbnail_url" mode="aspectFill" />
<view v-else class="attachment-video-thumb">
<text class="attachment-video-label">视频</text>
</view>
</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 class="attachment-name">{{ item.name || item.file_id }}</view>
<text class="attachment-type">{{ assetTypeLabel(item) }}</text>
</view>
</view>
</view>
<view v-if="activeVideo" class="video-preview-mask" @click="closeVideo"> <view v-if="activeVideo" class="video-preview-mask" @click="closeVideo">
<view class="video-preview-panel" @click.stop> <view class="video-preview-panel" @click.stop>
<view class="video-preview-head"> <view class="video-preview-head">
@@ -347,111 +416,457 @@ onShow(() => {
</template> </template>
<style scoped lang="scss"> <style scoped lang="scss">
.list { .report-page {
display: grid; width: 100vw;
gap: 14rpx; max-width: 100vw;
padding: 28rpx 32rpx 48rpx;
overflow-x: hidden;
background: #f1f3f6;
color: #3c3f45;
} }
.return-review-card { .report-page--with-action {
border-color: var(--work-accent); padding-bottom: 150rpx;
} }
.main-action { .notice-card {
margin-top: 18rpx; padding: 42rpx 34rpx;
border-radius: 22rpx;
background: #ffffff;
box-shadow: 0 18rpx 48rpx rgba(31, 36, 48, 0.08);
} }
.attachment-grid { .notice-card__title {
display: grid; color: #2f3238;
grid-template-columns: repeat(3, minmax(0, 1fr)); font-size: 32rpx;
gap: 14rpx; font-weight: 800;
margin-top: 18rpx; line-height: 1.35;
} }
.attachment-tile { .notice-card__desc {
margin-top: 12rpx;
color: #7e838b;
font-size: 26rpx;
line-height: 1.55;
}
.review-strip {
display: flex;
align-items: center;
justify-content: space-between;
gap: 22rpx;
margin-bottom: 22rpx;
padding: 22rpx 24rpx;
border: 1px solid rgba(223, 183, 51, 0.34);
border-radius: 18rpx;
background: #fff9e6;
}
.review-strip__title {
color: #3d3f44;
font-size: 28rpx;
font-weight: 800;
line-height: 1.35;
}
.review-strip__desc {
margin-top: 6rpx;
color: #7b6c3e;
font-size: 23rpx;
line-height: 1.45;
}
.review-strip__tag {
flex: 0 0 auto;
min-height: 38rpx;
padding: 0 14rpx;
border-radius: 999rpx;
background: #ffffff;
color: #9f8433;
font-size: 22rpx;
font-weight: 800;
line-height: 38rpx;
}
.report-shell {
position: relative;
overflow: hidden;
border-radius: 28rpx;
background: #ffffff;
box-shadow: 0 18rpx 48rpx rgba(31, 36, 48, 0.08);
}
.report-cover {
height: 356rpx;
margin: 28rpx 28rpx 0;
overflow: hidden;
border-radius: 10rpx;
background: #e8eaee;
}
.report-cover__swiper,
.report-cover__image,
.report-cover__empty {
width: 100%;
height: 100%;
}
.report-cover__image {
display: block;
}
.report-cover__empty {
display: flex;
align-items: center;
justify-content: center;
color: #8c919b;
font-size: 24rpx;
}
.report-meta {
padding: 34rpx 28rpx 12rpx;
}
.report-meta__row {
display: flex;
align-items: flex-start;
gap: 18rpx;
min-width: 0;
min-height: 48rpx;
}
.report-meta__row + .report-meta__row {
margin-top: 20rpx;
}
.report-meta__label {
flex: 0 0 138rpx;
display: block;
color: #7d828a;
font-size: 28rpx;
line-height: 1.4;
}
.report-meta__value {
flex: 1;
display: block;
min-width: 0;
color: #44474d;
font-size: 28rpx;
font-weight: 700;
line-height: 1.35;
text-align: right;
white-space: normal;
word-break: break-all;
overflow-wrap: anywhere;
}
.report-meta__row--date .report-meta__value {
flex: 0 1 auto;
margin-left: auto;
word-break: normal;
}
.report-meta__tag {
flex: 0 0 auto;
display: block;
min-height: 38rpx;
padding: 0 16rpx;
border: 1px solid rgba(221, 179, 47, 0.74);
border-radius: 6rpx;
background: #fff9e6;
color: #9f8433;
font-size: 22rpx;
font-weight: 700;
line-height: 36rpx;
}
.report-tabs {
display: flex;
align-items: center;
justify-content: center;
gap: 104rpx;
padding: 26rpx 40rpx 34rpx;
}
.report-tab {
position: relative;
color: #8e9298;
font-size: 32rpx;
font-weight: 700;
line-height: 1.6;
}
.report-tab--active {
color: var(--work-accent);
}
.report-tab--active::after {
position: absolute;
left: 50%;
bottom: -10rpx;
width: 44rpx;
height: 6rpx;
border-radius: 999rpx;
background: var(--work-accent);
content: "";
transform: translateX(-50%);
}
.report-panel {
position: relative;
min-height: 440rpx;
padding: 18rpx 28rpx 48rpx;
overflow: hidden;
}
.report-watermark {
position: absolute;
top: -6rpx;
left: 50%;
width: 520rpx;
height: 430rpx;
border-radius: 50%;
opacity: 0.46;
transform: translateX(-50%);
background:
repeating-radial-gradient(ellipse at center, rgba(230, 195, 79, 0.2) 0, rgba(230, 195, 79, 0.2) 2rpx, transparent 3rpx, transparent 17rpx),
repeating-conic-gradient(from 0deg, rgba(230, 195, 79, 0.12) 0deg 8deg, transparent 8deg 16deg);
pointer-events: none;
}
.report-result {
position: relative;
z-index: 1;
display: flex;
align-items: flex-start;
gap: 24rpx;
padding: 10rpx 0 30rpx;
border-bottom: 1px solid #e5e5e5;
}
.report-result__content {
flex: 1;
min-width: 0; min-width: 0;
} }
.attachment-preview { .report-result__label {
position: relative; color: #3d3f44;
width: 100%; font-size: 28rpx;
aspect-ratio: 1; font-weight: 800;
overflow: hidden; line-height: 1.45;
border: 1px solid var(--work-border);
border-radius: var(--work-radius-sm);
background: var(--work-card-muted);
} }
.attachment-thumb { .report-result__value {
margin-top: 14rpx;
color: #e04135;
font-size: 38rpx;
font-weight: 900;
line-height: 1.22;
letter-spacing: 0;
}
.report-result__desc {
margin-top: 10rpx;
color: #6f747c;
font-size: 24rpx;
line-height: 1.55;
}
.report-seal {
flex: 0 0 auto;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
width: 104rpx;
height: 104rpx;
margin-top: 2rpx;
border: 4rpx solid rgba(56, 164, 73, 0.8);
border-radius: 999rpx;
color: #39a54b;
transform: rotate(-10deg);
}
.report-seal__brand {
font-size: 16rpx;
font-weight: 800;
line-height: 1;
}
.report-seal__main {
margin-top: 8rpx;
font-size: 28rpx;
font-weight: 900;
line-height: 1;
}
.product-spec {
position: relative;
z-index: 1;
padding-top: 20rpx;
}
.product-spec__row {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 10rpx;
min-height: 54rpx;
}
.product-spec__label {
flex: 0 0 auto;
display: block;
color: #858991;
font-size: 24rpx;
line-height: 1.4;
}
.product-spec__line {
flex: 1;
min-width: 32rpx;
height: 1px;
border-bottom: 1px dotted #b9bdc4;
}
.product-spec__value {
flex: 0 1 auto;
display: block;
max-width: 58%;
color: #5b5f67;
font-size: 24rpx;
font-weight: 700;
line-height: 1.4;
text-align: right;
word-break: break-all;
overflow-wrap: anywhere;
}
.product-spec__remark {
flex-basis: 100%;
display: block;
margin: -2rpx 0 12rpx 0;
color: #8b9098;
font-size: 22rpx;
line-height: 1.45;
}
.report-panel--attachments {
padding-top: 2rpx;
}
.inline-section {
position: relative;
z-index: 1;
padding-top: 24rpx;
border-top: 1px solid #ececec;
}
.inline-section + .inline-section {
margin-top: 30rpx;
}
.inline-section:first-child {
padding-top: 0;
border-top: 0;
}
.inline-section__title {
color: #3f4248;
font-size: 28rpx;
font-weight: 800;
}
.attachment-empty {
padding: 58rpx 0;
color: #8b9098;
font-size: 26rpx;
text-align: center;
}
.attachment-empty--compact {
padding: 24rpx 0 0;
text-align: left;
}
.asset-grid {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 12rpx;
margin-top: 18rpx;
}
.asset-tile {
position: relative;
aspect-ratio: 1;
overflow: hidden;
border: 1px solid rgba(127, 119, 94, 0.16);
border-radius: 8rpx;
background: #f5f3ee;
}
.asset-tile__image {
display: block; display: block;
width: 100%; width: 100%;
height: 100%; height: 100%;
} }
.attachment-video-thumb, .asset-list {
.attachment-file-thumb { display: grid;
gap: 12rpx;
margin-top: 18rpx;
}
.asset-list__item {
display: flex; display: flex;
align-items: center; justify-content: space-between;
justify-content: center; gap: 16rpx;
width: 100%; padding: 18rpx;
height: 100%; border-radius: 8rpx;
} background: #faf8f1;
color: #3f4248;
.attachment-video-thumb { font-size: 26rpx;
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; font-weight: 700;
line-height: 1.35; }
.asset-list__item text:first-child {
min-width: 0;
overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: nowrap; white-space: nowrap;
} }
.attachment-type { .asset-list__type {
display: inline-flex; flex: 0 0 auto;
color: var(--work-accent);
}
.report-actions {
position: fixed;
z-index: 18;
left: 0;
right: 0;
bottom: 0;
width: 100vw;
box-sizing: border-box;
padding: 22rpx 32rpx calc(22rpx + env(safe-area-inset-bottom));
background: rgba(241, 243, 246, 0.96);
box-shadow: 0 -10rpx 34rpx rgba(31, 36, 48, 0.05);
}
.report-actions__button {
display: flex;
align-items: center; align-items: center;
min-height: 34rpx; justify-content: center;
margin-top: 6rpx; width: 100%;
padding: 0 10rpx; min-height: 82rpx;
border-radius: var(--work-radius-pill); border-radius: 999rpx;
background: var(--work-info-soft); background: #dfb733;
color: var(--work-info); color: #ffffff;
font-size: 20rpx; font-size: 28rpx;
font-weight: 700; font-weight: 800;
line-height: 34rpx; line-height: 82rpx;
} }
.video-preview-mask { .video-preview-mask {