diff --git a/server-api/app/controller/app/MaterialTagRedirectController.php b/server-api/app/controller/app/MaterialTagRedirectController.php
index bd8f0d5..518953b 100644
--- a/server-api/app/controller/app/MaterialTagRedirectController.php
+++ b/server-api/app/controller/app/MaterialTagRedirectController.php
@@ -15,7 +15,7 @@ class MaterialTagRedirectController
}
try {
- $url = (new MaterialTagService())->buildMaterialTagDetailUrl($token);
+ $url = (new MaterialTagService())->buildMaterialTagScanEntryUrl($token);
} catch (\Throwable $e) {
return response($e->getMessage(), 500);
}
diff --git a/server-api/app/support/MaterialTagService.php b/server-api/app/support/MaterialTagService.php
index 9cb1c70..5b79d1a 100644
--- a/server-api/app/support/MaterialTagService.php
+++ b/server-api/app/support/MaterialTagService.php
@@ -922,11 +922,61 @@ class MaterialTagService
return $this->buildMaterialTagH5Url($token, $baseUrl);
}
+ public function buildMaterialTagScanEntryUrl(string $token): string
+ {
+ $baseUrl = $this->normalizeH5BaseUrl($this->getSystemConfigValue('h5', 'page_base_url'));
+ if ($baseUrl === '') {
+ throw new \RuntimeException('H5 页面根地址未配置');
+ }
+
+ $reportNo = $this->findPublishedReportNoByToken($token);
+ if ($reportNo !== '') {
+ return $this->buildReportDetailH5Url($reportNo, $baseUrl, $token);
+ }
+
+ return $this->buildMaterialTagH5Url($token, $baseUrl);
+ }
+
private function buildMaterialTagH5Url(string $token, string $baseUrl): string
{
return rtrim($baseUrl, '/') . '/#/pages/material-tag/detail?token=' . rawurlencode($token);
}
+ private function buildReportDetailH5Url(string $reportNo, string $baseUrl, string $token = ''): string
+ {
+ $params = ['report_no' => $reportNo];
+ if ($token !== '') {
+ $params['material_tag_token'] = $token;
+ }
+
+ return rtrim($baseUrl, '/') . '/#/pages/report/detail?' . http_build_query($params, '', '&', PHP_QUERY_RFC3986);
+ }
+
+ private function findPublishedReportNoByToken(string $token): string
+ {
+ $tag = Db::name('material_tag_codes')->where('qr_token', $token)->find();
+ if (
+ !$tag
+ || (int)($tag['report_id'] ?? 0) <= 0
+ || ($tag['bind_status'] ?? '') !== 'bound'
+ || ($tag['status'] ?? 'active') === 'invalid'
+ ) {
+ return '';
+ }
+
+ $batch = Db::name('material_batches')->where('id', (int)$tag['batch_id'])->find();
+ if ($batch && ($batch['status'] ?? 'active') === 'invalid') {
+ return '';
+ }
+
+ $report = Db::name('reports')
+ ->where('id', (int)$tag['report_id'])
+ ->where('report_status', 'published')
+ ->find();
+
+ return $report ? (string)$report['report_no'] : '';
+ }
+
private function generateUniqueBatchNo(): string
{
for ($i = 0; $i < 20; $i++) {
diff --git a/user-app/src/api/app.ts b/user-app/src/api/app.ts
index 77d772b..a52c388 100644
--- a/user-app/src/api/app.ts
+++ b/user-app/src/api/app.ts
@@ -412,7 +412,7 @@ export interface VerifyData {
}
export interface MaterialTagData {
- tag_status: "unbound" | "pending_report" | "published" | "not_found";
+ tag_status: "unbound" | "pending_report" | "published" | "invalid" | "not_found";
status_text: string;
message: string;
qr_token: string;
diff --git a/user-app/src/pages/material-tag/detail.vue b/user-app/src/pages/material-tag/detail.vue
index a0b9d2a..677d033 100644
--- a/user-app/src/pages/material-tag/detail.vue
+++ b/user-app/src/pages/material-tag/detail.vue
@@ -29,7 +29,17 @@ const statusTitle = computed(() => {
function goReport() {
if (!reportNo.value) return;
- uni.navigateTo({ url: `/pages/report/detail?report_no=${encodeURIComponent(reportNo.value)}` });
+ uni.navigateTo({ url: buildReportDetailUrl(reportNo.value) });
+}
+
+function buildReportDetailUrl(currentReportNo: string) {
+ return `/pages/report/detail?report_no=${encodeURIComponent(currentReportNo)}`;
+}
+
+function redirectToReportIfReady() {
+ if (!isPublished.value || !reportNo.value) return false;
+ uni.redirectTo({ url: buildReportDetailUrl(reportNo.value) });
+ return true;
}
async function fetchDetail(currentToken: string) {
@@ -37,6 +47,7 @@ async function fetchDetail(currentToken: string) {
loadError.value = "";
try {
detail.value = await appApi.getMaterialTag(currentToken);
+ if (redirectToReportIfReady()) return;
} catch (error) {
console.warn("material tag load failed", error);
loadError.value = resolveErrorMessage(error, "吊牌信息加载失败,请稍后重试。");
diff --git a/user-app/src/pages/report/detail.vue b/user-app/src/pages/report/detail.vue
index 6b83844..c83edc4 100644
--- a/user-app/src/pages/report/detail.vue
+++ b/user-app/src/pages/report/detail.vue
@@ -98,6 +98,18 @@ const zhongjianImageFiles = computed(() => zhongjianReportFiles.value.filter((it
const zhongjianOtherFiles = computed(() => zhongjianReportFiles.value.filter((item) => item.file_type !== "image"));
const reportNo = computed(() => detail.value.report_header.report_no || "");
+async function recordMaterialTagScan(currentToken: string, expectedReportNo: string) {
+ try {
+ const materialTag = await appApi.getMaterialTag(currentToken);
+ const linkedReportNo = materialTag.report_summary?.report_no || "";
+ if (linkedReportNo && expectedReportNo && linkedReportNo !== expectedReportNo) {
+ console.warn("material tag report mismatch", { expectedReportNo, linkedReportNo });
+ }
+ } catch (error) {
+ console.warn("material tag scan log failed", error);
+ }
+}
+
function appendProductItem(items: ProductDisplayItem[], label: unknown, value: unknown, remark: unknown = "") {
const labelText = textValue(label);
const valueText = textValue(value);
@@ -302,14 +314,27 @@ watch(traceInfoVisible, (visible) => {
onLoad(async (options) => {
const id = Number(options?.id || 0);
- const currentReportNo = String(options?.report_no || "");
- if (!id && !currentReportNo) {
+ let currentReportNo = String(options?.report_no || "");
+ const materialTagToken = String(options?.material_tag_token || "").trim();
+ let materialTagResolvedFromToken = false;
+ if (!id && !currentReportNo && !materialTagToken) {
loadError.value = "缺少报告编号,无法查看详情。";
return;
}
loading.value = true;
try {
+ if (!id && !currentReportNo && materialTagToken) {
+ const materialTag = await appApi.getMaterialTag(materialTagToken);
+ materialTagResolvedFromToken = true;
+ currentReportNo = materialTag.report_summary?.report_no || "";
+ if (materialTag.tag_status !== "published" || !currentReportNo) {
+ throw new Error(materialTag.message || "该吊牌暂未关联已发布报告。");
+ }
+ }
+ if (currentReportNo && materialTagToken && !materialTagResolvedFromToken) {
+ void recordMaterialTagScan(materialTagToken, currentReportNo);
+ }
detail.value = await appApi.getReportDetail({
id: id || undefined,
report_no: currentReportNo || undefined,
@@ -385,10 +410,7 @@ onLoad(async (options) => {
{{ resultItem.value || "-" }}
{{ resultItem.remark }}
-
- 安心验
- 鉴定
-
+
@@ -540,7 +562,7 @@ onLoad(async (options) => {
left: 28rpx;
right: 28rpx;
height: 560rpx;
- background: url("../../static/report/report-watermark.svg") center / 100% 100% no-repeat;
+ background: url("../../static/report/report-bg-watermark.png") center / 100% auto no-repeat;
opacity: 1;
pointer-events: none;
}
@@ -724,56 +746,10 @@ onLoad(async (options) => {
position: absolute;
right: 2rpx;
bottom: 22rpx;
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-content: center;
width: 106rpx;
height: 106rpx;
- border: 4rpx solid rgba(40, 151, 73, 0.82);
- border-radius: 999rpx;
- background: rgba(255, 255, 255, 0.42);
- color: #239245;
- box-shadow: inset 0 0 0 4rpx rgba(40, 151, 73, 0.1);
- transform: rotate(-9deg);
-}
-
-.report-seal::before {
- position: absolute;
- inset: 10rpx;
- border: 2rpx solid rgba(40, 151, 73, 0.58);
- border-radius: inherit;
- content: "";
-}
-
-.report-seal::after {
- position: absolute;
- left: 50%;
- bottom: 12rpx;
- width: 34rpx;
- height: 5rpx;
- border-radius: 999rpx;
- background: currentColor;
- content: "";
- opacity: 0.7;
- transform: translateX(-50%);
-}
-
-.report-seal__brand {
- position: relative;
- z-index: 1;
- font-size: 18rpx;
- font-weight: 900;
- line-height: 1;
-}
-
-.report-seal__main {
- position: relative;
- z-index: 1;
- margin-top: 9rpx;
- font-size: 26rpx;
- font-weight: 900;
- line-height: 1;
+ display: block;
+ pointer-events: none;
}
.product-spec {
diff --git a/user-app/src/static/report/report-auth-badge.png b/user-app/src/static/report/report-auth-badge.png
new file mode 100644
index 0000000..b7170dd
Binary files /dev/null and b/user-app/src/static/report/report-auth-badge.png differ
diff --git a/user-app/src/static/report/report-bg-watermark.png b/user-app/src/static/report/report-bg-watermark.png
new file mode 100644
index 0000000..6ef1ee4
Binary files /dev/null and b/user-app/src/static/report/report-bg-watermark.png differ
diff --git a/work-app/src/pages/report/detail.vue b/work-app/src/pages/report/detail.vue
index e7744c2..969a16f 100644
--- a/work-app/src/pages/report/detail.vue
+++ b/work-app/src/pages/report/detail.vue
@@ -329,10 +329,7 @@ onShow(() => {
{{ resultItem.value }}
{{ resultItem.remark }}
-
- 安心验
- 鉴定
-
+
@@ -508,7 +505,7 @@ onShow(() => {
left: 28rpx;
right: 28rpx;
height: 560rpx;
- background: url("../../static/report/report-watermark.svg") center / 100% 100% no-repeat;
+ background: url("../../static/report/report-bg-watermark.png") center / 100% auto no-repeat;
opacity: 1;
pointer-events: none;
}
@@ -688,56 +685,10 @@ onShow(() => {
position: absolute;
right: 2rpx;
bottom: 22rpx;
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-content: center;
width: 106rpx;
height: 106rpx;
- border: 4rpx solid rgba(40, 151, 73, 0.82);
- border-radius: 999rpx;
- background: rgba(255, 255, 255, 0.42);
- color: #239245;
- box-shadow: inset 0 0 0 4rpx rgba(40, 151, 73, 0.1);
- transform: rotate(-9deg);
-}
-
-.report-seal::before {
- position: absolute;
- inset: 10rpx;
- border: 2rpx solid rgba(40, 151, 73, 0.58);
- border-radius: inherit;
- content: "";
-}
-
-.report-seal::after {
- position: absolute;
- left: 50%;
- bottom: 12rpx;
- width: 34rpx;
- height: 5rpx;
- border-radius: 999rpx;
- background: currentColor;
- content: "";
- opacity: 0.7;
- transform: translateX(-50%);
-}
-
-.report-seal__brand {
- position: relative;
- z-index: 1;
- font-size: 18rpx;
- font-weight: 900;
- line-height: 1;
-}
-
-.report-seal__main {
- position: relative;
- z-index: 1;
- margin-top: 9rpx;
- font-size: 26rpx;
- font-weight: 900;
- line-height: 1;
+ display: block;
+ pointer-events: none;
}
.product-spec {
diff --git a/work-app/src/static/report/report-auth-badge.png b/work-app/src/static/report/report-auth-badge.png
new file mode 100644
index 0000000..b7170dd
Binary files /dev/null and b/work-app/src/static/report/report-auth-badge.png differ
diff --git a/work-app/src/static/report/report-bg-watermark.png b/work-app/src/static/report/report-bg-watermark.png
new file mode 100644
index 0000000..6ef1ee4
Binary files /dev/null and b/work-app/src/static/report/report-bg-watermark.png differ