1237 lines
47 KiB
Vue
1237 lines
47 KiB
Vue
<script setup lang="ts">
|
||
import { computed, onMounted, ref, watch } from "vue";
|
||
import { useRoute } from "vue-router";
|
||
import { ElMessage, ElMessageBox } from "element-plus";
|
||
import QRCode from "qrcode";
|
||
import {
|
||
adminApi,
|
||
type AdminManualInspectionPayload,
|
||
type AdminReportDetail,
|
||
type AdminReportListItem,
|
||
} from "../../api/admin";
|
||
import OrderStatusTag from "../../components/OrderStatusTag.vue";
|
||
|
||
function createInspectionPayload(): AdminManualInspectionPayload {
|
||
return {
|
||
report_header: {
|
||
report_no: "",
|
||
report_title: "安心验检查单",
|
||
report_status: "pending_publish",
|
||
service_provider: "anxinyan",
|
||
institution_name: "安心验",
|
||
publish_time: "",
|
||
},
|
||
product_info: {
|
||
product_name: "",
|
||
category_name: "",
|
||
brand_name: "",
|
||
color: "",
|
||
size_spec: "",
|
||
serial_no: "",
|
||
},
|
||
result_info: {
|
||
result_status: "authentic",
|
||
result_text: "正品",
|
||
result_desc: "",
|
||
},
|
||
appraisal_info: {
|
||
appraiser_name: "",
|
||
reviewer_name: "",
|
||
appraisal_time: "",
|
||
},
|
||
valuation_info: {
|
||
condition_grade: "",
|
||
condition_desc: "",
|
||
valuation_min: "",
|
||
valuation_max: "",
|
||
valuation_desc: "",
|
||
},
|
||
risk_notice_text: "",
|
||
};
|
||
}
|
||
|
||
const loading = ref(false);
|
||
const detailLoading = ref(false);
|
||
const drawerVisible = ref(false);
|
||
const inspectionDrawerVisible = ref(false);
|
||
const inspectionSubmitting = ref(false);
|
||
const publishingId = ref<number | null>(null);
|
||
const rejectingId = ref<number | null>(null);
|
||
const traceVisibilitySavingId = ref<number | null>(null);
|
||
const detailQrDataUrl = ref("");
|
||
|
||
const keyword = ref("");
|
||
const serviceProvider = ref("");
|
||
const reportStatus = ref("");
|
||
|
||
const reports = ref<AdminReportListItem[]>([]);
|
||
const detail = ref<AdminReportDetail | null>(null);
|
||
const inspectionForm = ref<AdminManualInspectionPayload>(createInspectionPayload());
|
||
const route = useRoute();
|
||
|
||
const canPublishCurrentReport = computed(() => detail.value?.report_header.report_status === "pending_publish");
|
||
const canRejectCurrentReport = computed(
|
||
() => detail.value?.report_header.report_type !== "inspection" && detail.value?.report_header.report_status === "pending_publish",
|
||
);
|
||
const canEditCurrentInspection = computed(
|
||
() => detail.value?.report_header.report_type === "inspection" && detail.value?.report_header.report_status !== "published",
|
||
);
|
||
const inspectionDrawerTitle = computed(() => (inspectionForm.value.id ? "编辑补录检查单" : "补录检查单"));
|
||
|
||
const providerOptions = [
|
||
{ label: "全部服务", value: "" },
|
||
{ label: "实物鉴定", value: "anxinyan" },
|
||
{ label: "中检鉴定", value: "zhongjian" },
|
||
];
|
||
|
||
const statusOptions = [
|
||
{ label: "全部状态", value: "" },
|
||
{ label: "已发布", value: "published" },
|
||
{ label: "待发布", value: "pending_publish" },
|
||
{ label: "已驳回", value: "rejected" },
|
||
{ label: "草稿中", value: "draft" },
|
||
{ label: "已更新", value: "updated" },
|
||
{ label: "已作废", value: "invalid" },
|
||
];
|
||
|
||
const inspectionStatusOptions = [
|
||
{ label: "草稿保存", value: "draft" },
|
||
{ label: "待发布", value: "pending_publish" },
|
||
{ label: "直接发布", value: "published" },
|
||
];
|
||
|
||
const resultOptions = [
|
||
{ label: "正品", value: "authentic", text: "正品" },
|
||
{ label: "存疑", value: "uncertain", text: "存疑" },
|
||
{ label: "非正品", value: "not_authentic", text: "非正品" },
|
||
];
|
||
|
||
function applyProviderPreset(force = false) {
|
||
const provider = inspectionForm.value.report_header.service_provider;
|
||
const title = provider === "zhongjian" ? "中检检查单" : "安心验检查单";
|
||
const institution = provider === "zhongjian" ? "中检合作机构" : "安心验";
|
||
|
||
if (force || !inspectionForm.value.report_header.report_title) {
|
||
inspectionForm.value.report_header.report_title = title;
|
||
}
|
||
if (force || !inspectionForm.value.report_header.institution_name) {
|
||
inspectionForm.value.report_header.institution_name = institution;
|
||
}
|
||
}
|
||
|
||
function syncResultText() {
|
||
const matched = resultOptions.find((item) => item.value === inspectionForm.value.result_info.result_status);
|
||
if (matched && !inspectionForm.value.result_info.result_text) {
|
||
inspectionForm.value.result_info.result_text = matched.text;
|
||
}
|
||
}
|
||
|
||
function previewEvidence(url: string) {
|
||
if (!url) return;
|
||
window.open(url, "_blank", "noopener,noreferrer");
|
||
}
|
||
|
||
function evidenceTypeLabel(fileType?: string) {
|
||
return fileType === "image" ? "图片" : fileType === "video" ? "视频" : fileType === "pdf" ? "PDF" : "附件";
|
||
}
|
||
|
||
const imageEvidenceList = computed(() =>
|
||
(detail.value?.evidence_attachments || []).filter((item) => item.file_type === "image"),
|
||
);
|
||
|
||
const fileEvidenceList = computed(() =>
|
||
(detail.value?.evidence_attachments || []).filter((item) => item.file_type !== "image"),
|
||
);
|
||
const zhongjianReportImageList = computed(() =>
|
||
(detail.value?.zhongjian_report_files || []).filter((item) => item.file_type === "image"),
|
||
);
|
||
const zhongjianReportFileList = computed(() =>
|
||
(detail.value?.zhongjian_report_files || []).filter((item) => item.file_type !== "image"),
|
||
);
|
||
|
||
function openInspectionCreate() {
|
||
inspectionForm.value = createInspectionPayload();
|
||
applyProviderPreset(true);
|
||
syncResultText();
|
||
inspectionDrawerVisible.value = true;
|
||
}
|
||
|
||
function openInspectionEditFromDetail() {
|
||
if (!detail.value) return;
|
||
|
||
inspectionForm.value = {
|
||
id: detail.value.report_header.id,
|
||
report_header: {
|
||
report_no: detail.value.report_header.report_no,
|
||
report_title: detail.value.report_header.report_title,
|
||
report_status: detail.value.report_header.report_status,
|
||
service_provider: detail.value.report_header.service_provider,
|
||
institution_name: detail.value.report_header.institution_name,
|
||
publish_time: detail.value.report_header.publish_time || "",
|
||
},
|
||
product_info: {
|
||
product_name: detail.value.product_info.product_name || "",
|
||
category_name: detail.value.product_info.category_name || "",
|
||
brand_name: detail.value.product_info.brand_name || "",
|
||
color: detail.value.product_info.color || "",
|
||
size_spec: detail.value.product_info.size_spec || "",
|
||
serial_no: detail.value.product_info.serial_no || "",
|
||
},
|
||
result_info: {
|
||
result_status: detail.value.result_info.result_status || "authentic",
|
||
result_text: detail.value.result_info.result_text || "",
|
||
result_desc: detail.value.result_info.result_desc || "",
|
||
},
|
||
appraisal_info: {
|
||
appraiser_name: detail.value.appraisal_info.appraiser_name || "",
|
||
reviewer_name: detail.value.appraisal_info.reviewer_name || "",
|
||
appraisal_time: detail.value.appraisal_info.appraisal_time || "",
|
||
},
|
||
valuation_info: {
|
||
condition_grade: detail.value.valuation_info.condition_grade || "",
|
||
condition_desc: detail.value.valuation_info.condition_desc || "",
|
||
valuation_min: detail.value.valuation_info.valuation_min ?? "",
|
||
valuation_max: detail.value.valuation_info.valuation_max ?? "",
|
||
valuation_desc: detail.value.valuation_info.valuation_desc || "",
|
||
},
|
||
risk_notice_text: detail.value.risk_notice_text || "",
|
||
};
|
||
inspectionDrawerVisible.value = true;
|
||
}
|
||
|
||
async function syncQrCode(url: string) {
|
||
if (!/^https?:\/\//i.test(url)) {
|
||
detailQrDataUrl.value = "";
|
||
return;
|
||
}
|
||
|
||
try {
|
||
detailQrDataUrl.value = await QRCode.toDataURL(url, {
|
||
width: 220,
|
||
margin: 1,
|
||
});
|
||
} catch (error) {
|
||
console.error(error);
|
||
detailQrDataUrl.value = "";
|
||
}
|
||
}
|
||
|
||
async function fetchReports() {
|
||
loading.value = true;
|
||
try {
|
||
const response = await adminApi.getReports({
|
||
keyword: keyword.value,
|
||
service_provider: serviceProvider.value,
|
||
status: reportStatus.value,
|
||
});
|
||
if (response.code !== 0) {
|
||
ElMessage.error(response.message || "报告列表加载失败");
|
||
return;
|
||
}
|
||
reports.value = response.data.list;
|
||
} catch (error) {
|
||
console.error(error);
|
||
ElMessage.error("报告列表加载失败");
|
||
} finally {
|
||
loading.value = false;
|
||
}
|
||
}
|
||
|
||
async function loadDetail(id: number) {
|
||
detailLoading.value = true;
|
||
detailQrDataUrl.value = "";
|
||
try {
|
||
const response = await adminApi.getReportDetail(id);
|
||
if (response.code !== 0) {
|
||
ElMessage.error(response.message || "报告详情加载失败");
|
||
return;
|
||
}
|
||
detail.value = response.data;
|
||
await syncQrCode(response.data.verify_info.verify_qrcode_url || response.data.verify_info.report_page_url || "");
|
||
} catch (error) {
|
||
console.error(error);
|
||
ElMessage.error("报告详情加载失败");
|
||
} finally {
|
||
detailLoading.value = false;
|
||
}
|
||
}
|
||
|
||
async function openDetail(row: AdminReportListItem) {
|
||
drawerVisible.value = true;
|
||
await loadDetail(row.id);
|
||
}
|
||
|
||
function parseReportId(value: unknown) {
|
||
const raw = Array.isArray(value) ? value[0] : value;
|
||
const id = Number(raw || 0);
|
||
return Number.isInteger(id) && id > 0 ? id : 0;
|
||
}
|
||
|
||
async function openDetailFromRouteQuery() {
|
||
const reportId = parseReportId(route.query.report_id);
|
||
if (!reportId) {
|
||
return;
|
||
}
|
||
if (drawerVisible.value && detail.value?.report_header.id === reportId) {
|
||
return;
|
||
}
|
||
drawerVisible.value = true;
|
||
await loadDetail(reportId);
|
||
}
|
||
|
||
type PublishReportTarget = Pick<AdminReportListItem, "id" | "report_status" | "report_type" | "material_tag_bound"> | {
|
||
id: number;
|
||
report_status: string;
|
||
report_type: string;
|
||
material_tag_bound: boolean;
|
||
};
|
||
type RejectReportTarget = Pick<AdminReportListItem, "id" | "report_status" | "report_type"> | {
|
||
id: number;
|
||
report_status: string;
|
||
report_type: string;
|
||
};
|
||
type ReportTraceVisibilityTarget = Pick<AdminReportListItem, "id" | "trace_info_visible">;
|
||
|
||
async function promptReportMaterialTagInput() {
|
||
try {
|
||
const result = await ElMessageBox.prompt("是否已鉴定完成并确定发布报告?", "绑定验真吊牌并发布报告", {
|
||
type: "warning",
|
||
inputPlaceholder: "请扫描验真吊牌二维码",
|
||
inputPattern: /\S+/,
|
||
inputErrorMessage: "请扫描验真吊牌二维码",
|
||
confirmButtonText: "是的,去绑定验真吊牌",
|
||
cancelButtonText: "取消",
|
||
closeOnClickModal: false,
|
||
});
|
||
return String(result.value || "").trim();
|
||
} catch {
|
||
return "";
|
||
}
|
||
}
|
||
|
||
async function publishReport(row: PublishReportTarget) {
|
||
if (row.report_status !== "pending_publish") {
|
||
ElMessage.warning("仅待发布报告可以执行发布");
|
||
return;
|
||
}
|
||
|
||
const needMaterialTag = row.report_type !== "inspection" && !row.material_tag_bound;
|
||
let qrInput = "";
|
||
if (needMaterialTag) {
|
||
qrInput = await promptReportMaterialTagInput();
|
||
if (!qrInput) {
|
||
return;
|
||
}
|
||
} else {
|
||
try {
|
||
await ElMessageBox.confirm("发布后用户端将可查看正式报告并进行验真,是否继续?", "发布报告", {
|
||
type: "warning",
|
||
confirmButtonText: "确认发布",
|
||
cancelButtonText: "取消",
|
||
});
|
||
} catch {
|
||
return;
|
||
}
|
||
}
|
||
|
||
publishingId.value = row.id;
|
||
try {
|
||
const response = await adminApi.publishReport(row.id, qrInput);
|
||
if (response.code !== 0) {
|
||
ElMessage.error(response.message || "报告发布失败");
|
||
return;
|
||
}
|
||
|
||
ElMessage.success(response.message || "报告已发布");
|
||
await fetchReports();
|
||
|
||
if (drawerVisible.value && detail.value?.report_header.id === row.id) {
|
||
await loadDetail(row.id);
|
||
}
|
||
} catch (error) {
|
||
console.error(error);
|
||
ElMessage.error("报告发布失败");
|
||
} finally {
|
||
publishingId.value = null;
|
||
}
|
||
}
|
||
|
||
async function rejectReport(row: RejectReportTarget) {
|
||
if (row.report_status !== "pending_publish") {
|
||
ElMessage.warning("仅待发布报告可以驳回");
|
||
return;
|
||
}
|
||
if (row.report_type === "inspection") {
|
||
ElMessage.warning("补录检查单不支持驳回复鉴");
|
||
return;
|
||
}
|
||
|
||
let reason = "";
|
||
try {
|
||
const result = await ElMessageBox.prompt("请填写驳回原因,鉴定师将据此重新鉴定。", "驳回报告", {
|
||
type: "warning",
|
||
inputType: "textarea",
|
||
inputPlaceholder: "例如:结论说明不完整、图片附件不清晰",
|
||
inputPattern: /\S+/,
|
||
inputErrorMessage: "请填写驳回原因",
|
||
confirmButtonText: "确认驳回",
|
||
cancelButtonText: "取消",
|
||
closeOnClickModal: false,
|
||
});
|
||
reason = String(result.value || "").trim();
|
||
} catch {
|
||
return;
|
||
}
|
||
|
||
rejectingId.value = row.id;
|
||
try {
|
||
const response = await adminApi.rejectReport(row.id, reason);
|
||
if (response.code !== 0) {
|
||
ElMessage.error(response.message || "报告驳回失败");
|
||
return;
|
||
}
|
||
|
||
ElMessage.success(response.message || "报告已驳回");
|
||
await fetchReports();
|
||
if (drawerVisible.value && detail.value?.report_header.id === row.id) {
|
||
await loadDetail(row.id);
|
||
}
|
||
} catch (error: any) {
|
||
console.error(error);
|
||
ElMessage.error(error?.message || "报告驳回失败");
|
||
} finally {
|
||
rejectingId.value = null;
|
||
}
|
||
}
|
||
|
||
function logSummary(data: Record<string, any>) {
|
||
const status = data.report_status || "-";
|
||
const version = data.report_version ? `v${data.report_version}` : "";
|
||
const publishTime = data.publish_time ? ` · ${data.publish_time}` : "";
|
||
const rejectReason = data.reject_reason || data.invalid_reason ? ` · ${data.reject_reason || data.invalid_reason}` : "";
|
||
return [status, version].filter(Boolean).join(" / ") + publishTime + rejectReason;
|
||
}
|
||
|
||
function switchValueToBoolean(value: unknown) {
|
||
if (typeof value === "boolean") return value;
|
||
if (typeof value === "number") return value === 1;
|
||
return ["1", "true", "yes", "on"].includes(String(value).trim().toLowerCase());
|
||
}
|
||
|
||
async function updateReportTraceVisibility(row: ReportTraceVisibilityTarget, value: unknown) {
|
||
const visible = switchValueToBoolean(value);
|
||
traceVisibilitySavingId.value = row.id;
|
||
try {
|
||
const response = await adminApi.updateReportTraceVisibility(row.id, visible);
|
||
if (response.code !== 0) {
|
||
ElMessage.error(response.message || "追溯信息开关保存失败");
|
||
return;
|
||
}
|
||
|
||
const appliedVisible = Boolean(response.data.trace_info_visible);
|
||
row.trace_info_visible = appliedVisible;
|
||
|
||
const listItem = reports.value.find((item) => item.id === row.id);
|
||
if (listItem) {
|
||
listItem.trace_info_visible = appliedVisible;
|
||
}
|
||
if (detail.value?.report_header.id === row.id) {
|
||
detail.value.report_header.trace_info_visible = appliedVisible;
|
||
}
|
||
|
||
ElMessage.success(response.message || (appliedVisible ? "追溯信息已设为显示" : "追溯信息已隐藏"));
|
||
} catch (error) {
|
||
console.error(error);
|
||
ElMessage.error("追溯信息开关保存失败");
|
||
} finally {
|
||
traceVisibilitySavingId.value = null;
|
||
}
|
||
}
|
||
|
||
function handleTraceVisibilityChange(row: ReportTraceVisibilityTarget, value: unknown) {
|
||
void updateReportTraceVisibility(row, value);
|
||
}
|
||
|
||
function validateInspectionForm() {
|
||
const { report_header, product_info, result_info } = inspectionForm.value;
|
||
if (!report_header.report_title.trim()) {
|
||
ElMessage.warning("请填写检查单标题");
|
||
return false;
|
||
}
|
||
if (!report_header.institution_name.trim()) {
|
||
ElMessage.warning("请填写出具机构");
|
||
return false;
|
||
}
|
||
if (!product_info.product_name.trim()) {
|
||
ElMessage.warning("请填写商品名称");
|
||
return false;
|
||
}
|
||
if (!result_info.result_text.trim()) {
|
||
ElMessage.warning("请填写鉴定结论");
|
||
return false;
|
||
}
|
||
return true;
|
||
}
|
||
|
||
async function saveInspection() {
|
||
if (!validateInspectionForm()) {
|
||
return;
|
||
}
|
||
|
||
inspectionSubmitting.value = true;
|
||
try {
|
||
const response = await adminApi.saveInspectionReport(inspectionForm.value);
|
||
if (response.code !== 0) {
|
||
ElMessage.error(response.message || "检查单保存失败");
|
||
return;
|
||
}
|
||
|
||
ElMessage.success(response.message || "检查单已保存");
|
||
inspectionDrawerVisible.value = false;
|
||
await fetchReports();
|
||
drawerVisible.value = true;
|
||
await loadDetail(response.data.id);
|
||
} catch (error) {
|
||
console.error(error);
|
||
ElMessage.error("检查单保存失败");
|
||
} finally {
|
||
inspectionSubmitting.value = false;
|
||
}
|
||
}
|
||
|
||
async function copyText(value: string, label: string) {
|
||
if (!value) {
|
||
ElMessage.warning(`${label}为空`);
|
||
return;
|
||
}
|
||
|
||
try {
|
||
if (navigator.clipboard?.writeText) {
|
||
await navigator.clipboard.writeText(value);
|
||
} else {
|
||
const input = document.createElement("textarea");
|
||
input.value = value;
|
||
document.body.appendChild(input);
|
||
input.select();
|
||
document.execCommand("copy");
|
||
document.body.removeChild(input);
|
||
}
|
||
ElMessage.success(`${label}已复制`);
|
||
} catch (error) {
|
||
console.error(error);
|
||
ElMessage.error(`${label}复制失败`);
|
||
}
|
||
}
|
||
|
||
onMounted(() => {
|
||
applyProviderPreset(true);
|
||
syncResultText();
|
||
fetchReports();
|
||
openDetailFromRouteQuery();
|
||
});
|
||
|
||
watch(
|
||
() => route.query.report_id,
|
||
() => {
|
||
openDetailFromRouteQuery();
|
||
},
|
||
);
|
||
</script>
|
||
|
||
<template>
|
||
<el-card class="panel-card" shadow="never">
|
||
<div class="filters-row" style="justify-content: space-between;">
|
||
<div class="filters-row">
|
||
<el-input v-model="keyword" placeholder="搜索报告编号 / 鉴定单号 / 订单号 / 商品名称" clearable style="width: 340px" />
|
||
<el-select v-model="serviceProvider" placeholder="服务类型" style="width: 160px">
|
||
<el-option v-for="item in providerOptions" :key="item.value" :label="item.label" :value="item.value" />
|
||
</el-select>
|
||
<el-select v-model="reportStatus" placeholder="报告状态" style="width: 160px">
|
||
<el-option v-for="item in statusOptions" :key="item.value" :label="item.label" :value="item.value" />
|
||
</el-select>
|
||
<el-button type="primary" @click="fetchReports">查询</el-button>
|
||
</div>
|
||
<el-button type="primary" plain @click="openInspectionCreate">补录检查单</el-button>
|
||
</div>
|
||
</el-card>
|
||
|
||
<el-card class="panel-card orders-table" shadow="never">
|
||
<el-table v-loading="loading" :data="reports" stripe>
|
||
<el-table-column prop="report_no" label="报告编号" min-width="180" />
|
||
<el-table-column prop="appraisal_no" label="鉴定单号" min-width="180" />
|
||
<el-table-column prop="report_type_text" label="类型" min-width="120" />
|
||
<el-table-column prop="report_title" label="报告标题" min-width="180" />
|
||
<el-table-column prop="product_name" label="商品名称" min-width="220" />
|
||
<el-table-column prop="service_provider_text" label="服务类型" min-width="120" />
|
||
<el-table-column label="报告状态" min-width="120">
|
||
<template #default="{ row }">
|
||
<OrderStatusTag :status="row.report_status_text" />
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="验真吊牌" min-width="120">
|
||
<template #default="{ row }">
|
||
<OrderStatusTag v-if="row.report_type !== 'inspection'" :status="row.material_tag_bound ? '已绑定' : '未绑定'" />
|
||
<span v-else class="detail-label">不适用</span>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="追溯信息" min-width="130">
|
||
<template #default="{ row }">
|
||
<el-switch
|
||
:model-value="row.trace_info_visible"
|
||
:loading="traceVisibilitySavingId === row.id"
|
||
inline-prompt
|
||
active-text="显示"
|
||
inactive-text="隐藏"
|
||
@change="handleTraceVisibilityChange(row, $event)"
|
||
/>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column prop="institution_name" label="出具机构" min-width="160" />
|
||
<el-table-column prop="publish_time" label="发布时间" min-width="170" />
|
||
<el-table-column label="操作" fixed="right" width="280">
|
||
<template #default="{ row }">
|
||
<el-button link type="primary" @click="openDetail(row)">查看详情</el-button>
|
||
<el-button
|
||
v-if="row.report_type === 'inspection' && row.report_status !== 'published'"
|
||
link
|
||
type="success"
|
||
@click="openDetail(row).then(() => openInspectionEditFromDetail())"
|
||
>
|
||
编辑检查单
|
||
</el-button>
|
||
<el-button
|
||
v-if="row.report_type !== 'inspection' && row.report_status === 'pending_publish'"
|
||
link
|
||
type="warning"
|
||
:loading="publishingId === row.id"
|
||
@click="publishReport(row)"
|
||
>
|
||
发布报告
|
||
</el-button>
|
||
<el-button
|
||
v-if="row.report_type !== 'inspection' && row.report_status === 'pending_publish'"
|
||
link
|
||
type="danger"
|
||
:loading="rejectingId === row.id"
|
||
@click="rejectReport(row)"
|
||
>
|
||
驳回报告
|
||
</el-button>
|
||
</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
</el-card>
|
||
|
||
<el-drawer v-model="drawerVisible" size="62%" title="报告详情">
|
||
<div v-loading="detailLoading" v-if="detail" class="detail-grid">
|
||
<div style="grid-column: 1 / -1; display: flex; justify-content: flex-end; gap: 12px; margin-bottom: 8px">
|
||
<el-button v-if="canEditCurrentInspection" type="success" plain @click="openInspectionEditFromDetail">
|
||
编辑检查单
|
||
</el-button>
|
||
<el-button
|
||
v-if="canPublishCurrentReport"
|
||
type="primary"
|
||
:loading="publishingId === detail.report_header.id"
|
||
@click="publishReport({
|
||
id: detail.report_header.id,
|
||
report_status: detail.report_header.report_status,
|
||
report_type: detail.report_header.report_type,
|
||
material_tag_bound: Boolean(detail.material_tag),
|
||
})"
|
||
>
|
||
发布报告
|
||
</el-button>
|
||
<el-button
|
||
v-if="canRejectCurrentReport"
|
||
type="danger"
|
||
plain
|
||
:loading="rejectingId === detail.report_header.id"
|
||
@click="rejectReport({
|
||
id: detail.report_header.id,
|
||
report_status: detail.report_header.report_status,
|
||
report_type: detail.report_header.report_type,
|
||
})"
|
||
>
|
||
驳回报告
|
||
</el-button>
|
||
</div>
|
||
|
||
<div class="detail-card">
|
||
<div class="detail-card__title">报告概览</div>
|
||
<div class="detail-card__desc">
|
||
<div class="detail-label">报告编号</div>
|
||
<div class="detail-value">{{ detail.report_header.report_no }}</div>
|
||
</div>
|
||
<div class="detail-card__desc">
|
||
<div class="detail-label">报告类型</div>
|
||
<div class="detail-value">{{ detail.report_header.report_type_text }}</div>
|
||
</div>
|
||
<div class="detail-card__desc">
|
||
<div class="detail-label">报告标题</div>
|
||
<div class="detail-value">{{ detail.report_header.report_title }}</div>
|
||
</div>
|
||
<div class="detail-card__desc">
|
||
<div class="detail-label">报告状态</div>
|
||
<div class="detail-value">
|
||
<OrderStatusTag :status="detail.report_header.report_status_text" />
|
||
</div>
|
||
</div>
|
||
<div v-if="detail.report_header.reject_reason" class="detail-card__desc">
|
||
<div class="detail-label">驳回原因</div>
|
||
<div class="detail-value">{{ detail.report_header.reject_reason }}</div>
|
||
</div>
|
||
<div v-if="detail.report_header.rejected_at" class="detail-card__desc">
|
||
<div class="detail-label">驳回信息</div>
|
||
<div class="detail-value">{{ detail.report_header.rejected_by_name || "-" }} / {{ detail.report_header.rejected_at }}</div>
|
||
</div>
|
||
<div class="detail-card__desc">
|
||
<div class="detail-label">出具机构</div>
|
||
<div class="detail-value">{{ detail.report_header.institution_name }}</div>
|
||
</div>
|
||
<div class="detail-card__desc">
|
||
<div class="detail-label">追溯信息</div>
|
||
<div class="detail-value report-visibility-control">
|
||
<el-switch
|
||
:model-value="detail.report_header.trace_info_visible"
|
||
:loading="traceVisibilitySavingId === detail.report_header.id"
|
||
inline-prompt
|
||
active-text="显示"
|
||
inactive-text="隐藏"
|
||
@change="handleTraceVisibilityChange(detail.report_header, $event)"
|
||
/>
|
||
<span>{{ detail.report_header.trace_info_visible ? "用户端显示追溯信息 tab" : "用户端隐藏追溯信息 tab" }}</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="detail-card">
|
||
<div class="detail-card__title">验真吊牌</div>
|
||
<template v-if="detail.report_header.report_type === 'inspection'">
|
||
<div class="detail-card__desc">
|
||
<div class="detail-value">补录检查单不需要绑定验真吊牌</div>
|
||
</div>
|
||
</template>
|
||
<template v-else-if="detail.material_tag">
|
||
<div class="detail-card__desc">
|
||
<div class="detail-label">二维码链接</div>
|
||
<div class="detail-value" style="word-break: break-all;">{{ detail.material_tag.qr_url }}</div>
|
||
</div>
|
||
<div class="detail-card__desc">
|
||
<div class="detail-label">验真编码</div>
|
||
<div class="detail-value">{{ detail.material_tag.verify_code }}</div>
|
||
</div>
|
||
<div class="detail-card__desc">
|
||
<div class="detail-label">绑定时间</div>
|
||
<div class="detail-value">{{ detail.material_tag.bound_at || "-" }}</div>
|
||
</div>
|
||
</template>
|
||
<div v-else class="detail-card__desc">
|
||
<div class="detail-value">未绑定,发布前需要扫描验真吊牌。</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="detail-card detail-card--wide">
|
||
<div class="detail-card__title">审核记录</div>
|
||
<div v-if="detail.audit_logs.length" class="report-log-list">
|
||
<div v-for="log in detail.audit_logs" :key="`audit-${log.id}`" class="report-log-item">
|
||
<div class="report-log-main">
|
||
<span>{{ log.action_text }}</span>
|
||
<span>{{ log.operator_name || "-" }}</span>
|
||
<span>{{ log.created_at }}</span>
|
||
</div>
|
||
<div v-if="log.remark" class="report-log-remark">{{ log.remark }}</div>
|
||
<div class="report-log-snapshot">{{ logSummary(log.after_data) }}</div>
|
||
</div>
|
||
</div>
|
||
<div v-else class="detail-card__desc">
|
||
<div class="detail-value">暂无审核记录</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="detail-card detail-card--wide">
|
||
<div class="detail-card__title">修改记录</div>
|
||
<div v-if="detail.change_logs.length" class="report-log-list">
|
||
<div v-for="log in detail.change_logs" :key="`change-${log.id}`" class="report-log-item">
|
||
<div class="report-log-main">
|
||
<span>{{ log.action_text }}</span>
|
||
<span>{{ log.operator_name || "-" }}</span>
|
||
<span>{{ log.created_at }}</span>
|
||
</div>
|
||
<div v-if="log.remark" class="report-log-remark">{{ log.remark }}</div>
|
||
<div class="report-log-snapshot">
|
||
{{ logSummary(log.before_data) }} -> {{ logSummary(log.after_data) }}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div v-else class="detail-card__desc">
|
||
<div class="detail-value">暂无修改记录</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="detail-card">
|
||
<div class="detail-card__title">商品信息</div>
|
||
<div class="detail-card__desc">
|
||
<div class="detail-label">商品名称</div>
|
||
<div class="detail-value">{{ detail.product_info.product_name || "-" }}</div>
|
||
</div>
|
||
<div class="detail-card__desc">
|
||
<div class="detail-label">品类 / 品牌</div>
|
||
<div class="detail-value">{{ detail.product_info.category_name || "-" }} / {{ detail.product_info.brand_name || "-" }}</div>
|
||
</div>
|
||
<div class="detail-card__desc">
|
||
<div class="detail-label">颜色 / 规格</div>
|
||
<div class="detail-value">{{ detail.product_info.color || "-" }} / {{ detail.product_info.size_spec || "-" }}</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="detail-card">
|
||
<div class="detail-card__title">鉴定结果</div>
|
||
<div class="detail-card__desc">
|
||
<div class="detail-label">结论</div>
|
||
<div class="detail-value">{{ detail.result_info.result_text || "-" }}</div>
|
||
</div>
|
||
<div class="detail-card__desc">
|
||
<div class="detail-label">说明</div>
|
||
<div class="detail-value">{{ detail.result_info.result_desc || "-" }}</div>
|
||
</div>
|
||
<template v-if="detail.result_info.key_points?.length">
|
||
<div v-for="(item, index) in detail.result_info.key_points" :key="`${item.point_code || item.point_name}-${index}`" class="detail-card__desc">
|
||
<div class="detail-label">{{ item.point_name || "鉴定项" }}</div>
|
||
<div class="detail-value">{{ item.point_value || "-" }}</div>
|
||
</div>
|
||
</template>
|
||
<div v-if="detail.result_info.external_remark" class="detail-card__desc">
|
||
<div class="detail-label">对外备注</div>
|
||
<div class="detail-value">{{ detail.result_info.external_remark }}</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="detail-card">
|
||
<div class="detail-card__title">鉴定信息</div>
|
||
<div class="detail-card__desc">
|
||
<div class="detail-label">服务类型</div>
|
||
<div class="detail-value">{{ detail.report_header.service_provider_text }}</div>
|
||
</div>
|
||
<div class="detail-card__desc">
|
||
<div class="detail-label">鉴定师</div>
|
||
<div class="detail-value">{{ detail.appraisal_info.appraiser_name || "-" }}</div>
|
||
</div>
|
||
<div class="detail-card__desc">
|
||
<div class="detail-label">鉴定时间</div>
|
||
<div class="detail-value">{{ detail.appraisal_info.appraisal_time || "-" }}</div>
|
||
</div>
|
||
<template v-if="detail.report_header.service_provider === 'zhongjian'">
|
||
<div class="detail-card__desc">
|
||
<div class="detail-label">中检报告编号</div>
|
||
<div class="detail-value">{{ detail.report_header.zhongjian_report_no || "-" }}</div>
|
||
</div>
|
||
<div class="detail-card__desc">
|
||
<div class="detail-label">报告录入人</div>
|
||
<div class="detail-value">{{ detail.report_header.report_entry_admin_name || "-" }}</div>
|
||
</div>
|
||
<div class="detail-card__desc">
|
||
<div class="detail-label">录入时间</div>
|
||
<div class="detail-value">{{ detail.report_header.report_entered_at || "-" }}</div>
|
||
</div>
|
||
</template>
|
||
</div>
|
||
|
||
<div class="detail-card">
|
||
<div class="detail-card__title">评级与估值</div>
|
||
<div class="detail-card__desc">
|
||
<div class="detail-label">成色评级</div>
|
||
<div class="detail-value">{{ detail.valuation_info.condition_grade || "-" }}</div>
|
||
</div>
|
||
<div class="detail-card__desc">
|
||
<div class="detail-label">成色说明</div>
|
||
<div class="detail-value">{{ detail.valuation_info.condition_desc || "-" }}</div>
|
||
</div>
|
||
<div class="detail-card__desc">
|
||
<div class="detail-label">估值区间</div>
|
||
<div class="detail-value">¥{{ detail.valuation_info.valuation_min || 0 }} - ¥{{ detail.valuation_info.valuation_max || 0 }}</div>
|
||
</div>
|
||
<div class="detail-card__desc">
|
||
<div class="detail-label">估值说明</div>
|
||
<div class="detail-value">{{ detail.valuation_info.valuation_desc || "-" }}</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="detail-card" style="grid-column: 1 / -1">
|
||
<div class="detail-card__title">证据附件</div>
|
||
<div v-if="detail.evidence_attachments.length" class="report-evidence-stack">
|
||
<div v-if="imageEvidenceList.length" class="report-evidence-section">
|
||
<div class="report-evidence-section__title">图片证据</div>
|
||
<div class="report-evidence-gallery">
|
||
<div
|
||
v-for="attachment in imageEvidenceList"
|
||
:key="attachment.file_id"
|
||
class="report-evidence-gallery__item"
|
||
@click="previewEvidence(attachment.file_url)"
|
||
>
|
||
<img :src="attachment.thumbnail_url || attachment.file_url" :alt="attachment.name || '证据图片'" />
|
||
<div class="report-evidence-gallery__caption">{{ attachment.name || "未命名图片" }}</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div v-if="fileEvidenceList.length" class="report-evidence-section">
|
||
<div class="report-evidence-section__title">视频 / 文档证据</div>
|
||
<div class="report-evidence-list">
|
||
<div v-for="attachment in fileEvidenceList" :key="attachment.file_id" class="report-evidence-card">
|
||
<div class="report-evidence-card__preview" @click="previewEvidence(attachment.file_url)">
|
||
<div class="report-evidence-card__filetype">{{ evidenceTypeLabel(attachment.file_type) }}</div>
|
||
</div>
|
||
<div class="report-evidence-card__body">
|
||
<div class="detail-value" style="margin-top: 0; word-break: break-word;">{{ attachment.name || attachment.file_url }}</div>
|
||
<div class="detail-label" style="margin-top: 6px;">{{ evidenceTypeLabel(attachment.file_type) }}</div>
|
||
<el-button size="small" style="margin-top: 10px" @click="previewEvidence(attachment.file_url)">查看附件</el-button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div v-else class="detail-card__desc">
|
||
<div class="detail-value">当前报告未附带证据附件</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div v-if="detail.report_header.service_provider === 'zhongjian'" class="detail-card" style="grid-column: 1 / -1">
|
||
<div class="detail-card__title">中检报告文件</div>
|
||
<div v-if="detail.zhongjian_report_files.length" class="report-evidence-stack">
|
||
<div v-if="zhongjianReportImageList.length" class="report-evidence-section">
|
||
<div class="report-evidence-section__title">报告图片</div>
|
||
<div class="report-evidence-gallery">
|
||
<div
|
||
v-for="attachment in zhongjianReportImageList"
|
||
:key="attachment.file_id"
|
||
class="report-evidence-gallery__item"
|
||
@click="previewEvidence(attachment.file_url)"
|
||
>
|
||
<img :src="attachment.thumbnail_url || attachment.file_url" :alt="attachment.name || '中检报告图片'" />
|
||
<div class="report-evidence-gallery__caption">{{ attachment.name || "未命名图片" }}</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div v-if="zhongjianReportFileList.length" class="report-evidence-section">
|
||
<div class="report-evidence-section__title">报告文档</div>
|
||
<div class="report-evidence-list">
|
||
<div v-for="attachment in zhongjianReportFileList" :key="attachment.file_id" class="report-evidence-card">
|
||
<div class="report-evidence-card__preview" @click="previewEvidence(attachment.file_url)">
|
||
<div class="report-evidence-card__filetype">{{ evidenceTypeLabel(attachment.file_type) }}</div>
|
||
</div>
|
||
<div class="report-evidence-card__body">
|
||
<div class="detail-value" style="margin-top: 0; word-break: break-word;">{{ attachment.name || attachment.file_url }}</div>
|
||
<div class="detail-label" style="margin-top: 6px;">{{ evidenceTypeLabel(attachment.file_type) }}</div>
|
||
<el-button size="small" style="margin-top: 10px" @click="previewEvidence(attachment.file_url)">查看文件</el-button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div v-else class="detail-card__desc">
|
||
<div class="detail-value">当前报告未上传中检报告文件</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="detail-card" style="grid-column: 1 / -1">
|
||
<div class="detail-card__title">扫码与公开链接</div>
|
||
<div style="display: grid; grid-template-columns: 220px 1fr; gap: 24px; align-items: start;">
|
||
<div
|
||
style="width: 220px; height: 220px; border-radius: 16px; border: 1px dashed var(--admin-border); display: flex; align-items: center; justify-content: center; overflow: hidden; background: #fff;"
|
||
>
|
||
<el-image v-if="detailQrDataUrl" :src="detailQrDataUrl" fit="contain" style="width: 200px; height: 200px" />
|
||
<div v-else style="padding: 16px; text-align: center; color: var(--admin-text-subtle); line-height: 1.7;">
|
||
请先在系统配置中填写 H5 页面根地址,再生成可扫码的公开链接。
|
||
</div>
|
||
</div>
|
||
<div style="display: grid; gap: 14px;">
|
||
<div class="detail-card__desc" style="margin: 0;">
|
||
<div class="detail-label">扫码打开报告页</div>
|
||
<div class="detail-value" style="word-break: break-all;">{{ detail.verify_info.verify_qrcode_url || "-" }}</div>
|
||
<el-button size="small" style="margin-top: 8px" @click="copyText(detail.verify_info.verify_qrcode_url, '报告链接')">复制报告链接</el-button>
|
||
</div>
|
||
<div class="detail-card__desc" style="margin: 0;">
|
||
<div class="detail-label">H5 验真页</div>
|
||
<div class="detail-value" style="word-break: break-all;">{{ detail.verify_info.verify_url || "-" }}</div>
|
||
<el-button size="small" style="margin-top: 8px" @click="copyText(detail.verify_info.verify_url, '验真链接')">复制验真链接</el-button>
|
||
</div>
|
||
<div class="detail-card__desc" style="margin: 0;">
|
||
<div class="detail-label">验真状态 / 次数</div>
|
||
<div class="detail-value">{{ detail.verify_info.verify_status }} / {{ detail.verify_info.verify_count }}</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="detail-card" style="grid-column: 1 / -1">
|
||
<div class="detail-card__title">风险说明</div>
|
||
<div class="detail-card__desc">
|
||
<div class="detail-value">{{ detail.risk_notice_text || "-" }}</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</el-drawer>
|
||
|
||
<el-drawer v-model="inspectionDrawerVisible" size="56%" :title="inspectionDrawerTitle">
|
||
<div style="display: grid; gap: 24px;">
|
||
<el-card shadow="never">
|
||
<template #header>基础信息</template>
|
||
<el-row :gutter="16">
|
||
<el-col :span="12">
|
||
<el-form-item label="检查单编号">
|
||
<el-input v-model="inspectionForm.report_header.report_no" placeholder="可留空,系统自动生成" />
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="12">
|
||
<el-form-item label="检查单标题">
|
||
<el-input v-model="inspectionForm.report_header.report_title" placeholder="请输入检查单标题" />
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="12">
|
||
<el-form-item label="服务类型">
|
||
<el-select v-model="inspectionForm.report_header.service_provider" style="width: 100%" @change="applyProviderPreset()">
|
||
<el-option label="实物鉴定" value="anxinyan" />
|
||
<el-option label="中检鉴定" value="zhongjian" />
|
||
</el-select>
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="12">
|
||
<el-form-item label="保存状态">
|
||
<el-select v-model="inspectionForm.report_header.report_status" style="width: 100%">
|
||
<el-option v-for="item in inspectionStatusOptions" :key="item.value" :label="item.label" :value="item.value" />
|
||
</el-select>
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="12">
|
||
<el-form-item label="出具机构">
|
||
<el-input v-model="inspectionForm.report_header.institution_name" placeholder="请输入出具机构" />
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="12">
|
||
<el-form-item label="发布时间">
|
||
<el-date-picker
|
||
v-model="inspectionForm.report_header.publish_time"
|
||
type="datetime"
|
||
value-format="YYYY-MM-DD HH:mm:ss"
|
||
placeholder="直接发布时可指定发布时间"
|
||
style="width: 100%"
|
||
/>
|
||
</el-form-item>
|
||
</el-col>
|
||
</el-row>
|
||
</el-card>
|
||
|
||
<el-card shadow="never">
|
||
<template #header>商品信息</template>
|
||
<el-row :gutter="16">
|
||
<el-col :span="12"><el-form-item label="商品名称"><el-input v-model="inspectionForm.product_info.product_name" /></el-form-item></el-col>
|
||
<el-col :span="12"><el-form-item label="品类"><el-input v-model="inspectionForm.product_info.category_name" /></el-form-item></el-col>
|
||
<el-col :span="12"><el-form-item label="品牌"><el-input v-model="inspectionForm.product_info.brand_name" /></el-form-item></el-col>
|
||
<el-col :span="12"><el-form-item label="颜色"><el-input v-model="inspectionForm.product_info.color" /></el-form-item></el-col>
|
||
<el-col :span="12"><el-form-item label="规格 / 尺寸"><el-input v-model="inspectionForm.product_info.size_spec" /></el-form-item></el-col>
|
||
<el-col :span="24"><el-form-item label="序列号 / 编码"><el-input v-model="inspectionForm.product_info.serial_no" /></el-form-item></el-col>
|
||
</el-row>
|
||
</el-card>
|
||
|
||
<el-card shadow="never">
|
||
<template #header>鉴定结果</template>
|
||
<el-row :gutter="16">
|
||
<el-col :span="12">
|
||
<el-form-item label="结果类型">
|
||
<el-select v-model="inspectionForm.result_info.result_status" style="width: 100%" @change="syncResultText">
|
||
<el-option v-for="item in resultOptions" :key="item.value" :label="item.label" :value="item.value" />
|
||
</el-select>
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="12">
|
||
<el-form-item label="结果文案">
|
||
<el-input v-model="inspectionForm.result_info.result_text" placeholder="例如:正品 / 存疑 / 非正品" />
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="24">
|
||
<el-form-item label="结果说明">
|
||
<el-input v-model="inspectionForm.result_info.result_desc" type="textarea" :rows="4" placeholder="请输入检查结论说明" />
|
||
</el-form-item>
|
||
</el-col>
|
||
</el-row>
|
||
</el-card>
|
||
|
||
<el-card shadow="never">
|
||
<template #header>鉴定与估值信息</template>
|
||
<el-row :gutter="16">
|
||
<el-col :span="12"><el-form-item label="鉴定师"><el-input v-model="inspectionForm.appraisal_info.appraiser_name" /></el-form-item></el-col>
|
||
<el-col :span="12">
|
||
<el-form-item label="鉴定时间">
|
||
<el-date-picker
|
||
v-model="inspectionForm.appraisal_info.appraisal_time"
|
||
type="datetime"
|
||
value-format="YYYY-MM-DD HH:mm:ss"
|
||
style="width: 100%"
|
||
/>
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="12"><el-form-item label="成色评级"><el-input v-model="inspectionForm.valuation_info.condition_grade" placeholder="例如 A / B+" /></el-form-item></el-col>
|
||
<el-col :span="12"><el-form-item label="最低估值"><el-input v-model="inspectionForm.valuation_info.valuation_min" type="number" /></el-form-item></el-col>
|
||
<el-col :span="12"><el-form-item label="最高估值"><el-input v-model="inspectionForm.valuation_info.valuation_max" type="number" /></el-form-item></el-col>
|
||
<el-col :span="24"><el-form-item label="成色说明"><el-input v-model="inspectionForm.valuation_info.condition_desc" type="textarea" :rows="3" /></el-form-item></el-col>
|
||
<el-col :span="24"><el-form-item label="估值说明"><el-input v-model="inspectionForm.valuation_info.valuation_desc" type="textarea" :rows="3" /></el-form-item></el-col>
|
||
</el-row>
|
||
</el-card>
|
||
|
||
<el-card shadow="never">
|
||
<template #header>风险说明</template>
|
||
<el-form-item label="页面说明文案">
|
||
<el-input v-model="inspectionForm.risk_notice_text" type="textarea" :rows="4" placeholder="请输入风险提示与适用说明" />
|
||
</el-form-item>
|
||
</el-card>
|
||
|
||
<div style="display: flex; justify-content: flex-end; gap: 12px;">
|
||
<el-button @click="inspectionDrawerVisible = false">取消</el-button>
|
||
<el-button type="primary" :loading="inspectionSubmitting" @click="saveInspection">保存检查单</el-button>
|
||
</div>
|
||
</div>
|
||
</el-drawer>
|
||
</template>
|
||
|
||
<style scoped>
|
||
.report-evidence-stack {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 20px;
|
||
margin-top: 14px;
|
||
}
|
||
|
||
.report-evidence-section__title {
|
||
color: var(--admin-text-main);
|
||
font-size: 14px;
|
||
font-weight: 700;
|
||
}
|
||
|
||
.report-evidence-gallery {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
|
||
gap: 14px;
|
||
margin-top: 12px;
|
||
}
|
||
|
||
.report-evidence-gallery__item {
|
||
border-radius: 16px;
|
||
overflow: hidden;
|
||
border: 1px solid #efe8d9;
|
||
background: #fcfaf5;
|
||
cursor: pointer;
|
||
}
|
||
|
||
.report-evidence-gallery__item img {
|
||
width: 100%;
|
||
height: 180px;
|
||
object-fit: cover;
|
||
display: block;
|
||
}
|
||
|
||
.report-evidence-gallery__caption {
|
||
padding: 10px 12px;
|
||
color: var(--admin-text-main);
|
||
font-size: 13px;
|
||
font-weight: 600;
|
||
line-height: 1.5;
|
||
word-break: break-word;
|
||
}
|
||
|
||
.report-evidence-list {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
|
||
gap: 14px;
|
||
margin-top: 12px;
|
||
}
|
||
|
||
.report-evidence-card {
|
||
display: grid;
|
||
grid-template-columns: 96px minmax(0, 1fr);
|
||
gap: 14px;
|
||
padding: 14px;
|
||
border-radius: 16px;
|
||
background: #fcfaf5;
|
||
border: 1px solid #efe8d9;
|
||
}
|
||
|
||
.report-evidence-card__preview {
|
||
width: 96px;
|
||
height: 96px;
|
||
border-radius: 14px;
|
||
border: 1px solid #efe8d9;
|
||
background: #ffffff;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
overflow: hidden;
|
||
cursor: pointer;
|
||
}
|
||
|
||
.report-evidence-card__preview img {
|
||
width: 100%;
|
||
height: 100%;
|
||
object-fit: cover;
|
||
display: block;
|
||
}
|
||
|
||
.report-evidence-card__filetype {
|
||
color: var(--admin-progress);
|
||
font-size: 13px;
|
||
font-weight: 700;
|
||
}
|
||
|
||
.report-evidence-card__body {
|
||
min-width: 0;
|
||
}
|
||
|
||
.report-visibility-control {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 10px;
|
||
flex-wrap: wrap;
|
||
}
|
||
|
||
.report-visibility-control span {
|
||
color: var(--admin-text-subtle);
|
||
font-size: 13px;
|
||
font-weight: 500;
|
||
}
|
||
|
||
.detail-card--wide {
|
||
grid-column: 1 / -1;
|
||
}
|
||
|
||
.report-log-list {
|
||
display: grid;
|
||
gap: 10px;
|
||
}
|
||
|
||
.report-log-item {
|
||
display: grid;
|
||
gap: 6px;
|
||
padding: 12px;
|
||
border: 1px solid var(--admin-border);
|
||
border-radius: 8px;
|
||
background: #fffdfa;
|
||
}
|
||
|
||
.report-log-main {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 12px;
|
||
color: var(--admin-text-main);
|
||
font-size: 13px;
|
||
font-weight: 700;
|
||
}
|
||
|
||
.report-log-remark,
|
||
.report-log-snapshot {
|
||
color: var(--admin-text-subtle);
|
||
font-size: 13px;
|
||
line-height: 1.5;
|
||
word-break: break-word;
|
||
}
|
||
</style>
|