feat: route material tag scans to reports
This commit is contained in:
@@ -15,7 +15,7 @@ class MaterialTagRedirectController
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$url = (new MaterialTagService())->buildMaterialTagDetailUrl($token);
|
$url = (new MaterialTagService())->buildMaterialTagScanEntryUrl($token);
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
return response($e->getMessage(), 500);
|
return response($e->getMessage(), 500);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -922,11 +922,61 @@ class MaterialTagService
|
|||||||
return $this->buildMaterialTagH5Url($token, $baseUrl);
|
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
|
private function buildMaterialTagH5Url(string $token, string $baseUrl): string
|
||||||
{
|
{
|
||||||
return rtrim($baseUrl, '/') . '/#/pages/material-tag/detail?token=' . rawurlencode($token);
|
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
|
private function generateUniqueBatchNo(): string
|
||||||
{
|
{
|
||||||
for ($i = 0; $i < 20; $i++) {
|
for ($i = 0; $i < 20; $i++) {
|
||||||
|
|||||||
@@ -412,7 +412,7 @@ export interface VerifyData {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface MaterialTagData {
|
export interface MaterialTagData {
|
||||||
tag_status: "unbound" | "pending_report" | "published" | "not_found";
|
tag_status: "unbound" | "pending_report" | "published" | "invalid" | "not_found";
|
||||||
status_text: string;
|
status_text: string;
|
||||||
message: string;
|
message: string;
|
||||||
qr_token: string;
|
qr_token: string;
|
||||||
|
|||||||
@@ -29,7 +29,17 @@ const statusTitle = computed(() => {
|
|||||||
|
|
||||||
function goReport() {
|
function goReport() {
|
||||||
if (!reportNo.value) return;
|
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) {
|
async function fetchDetail(currentToken: string) {
|
||||||
@@ -37,6 +47,7 @@ async function fetchDetail(currentToken: string) {
|
|||||||
loadError.value = "";
|
loadError.value = "";
|
||||||
try {
|
try {
|
||||||
detail.value = await appApi.getMaterialTag(currentToken);
|
detail.value = await appApi.getMaterialTag(currentToken);
|
||||||
|
if (redirectToReportIfReady()) return;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn("material tag load failed", error);
|
console.warn("material tag load failed", error);
|
||||||
loadError.value = resolveErrorMessage(error, "吊牌信息加载失败,请稍后重试。");
|
loadError.value = resolveErrorMessage(error, "吊牌信息加载失败,请稍后重试。");
|
||||||
|
|||||||
@@ -98,6 +98,18 @@ const zhongjianImageFiles = computed(() => zhongjianReportFiles.value.filter((it
|
|||||||
const zhongjianOtherFiles = computed(() => zhongjianReportFiles.value.filter((item) => item.file_type !== "image"));
|
const zhongjianOtherFiles = computed(() => zhongjianReportFiles.value.filter((item) => item.file_type !== "image"));
|
||||||
const reportNo = computed(() => detail.value.report_header.report_no || "");
|
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 = "") {
|
function appendProductItem(items: ProductDisplayItem[], label: unknown, value: unknown, remark: unknown = "") {
|
||||||
const labelText = textValue(label);
|
const labelText = textValue(label);
|
||||||
const valueText = textValue(value);
|
const valueText = textValue(value);
|
||||||
@@ -302,14 +314,27 @@ watch(traceInfoVisible, (visible) => {
|
|||||||
|
|
||||||
onLoad(async (options) => {
|
onLoad(async (options) => {
|
||||||
const id = Number(options?.id || 0);
|
const id = Number(options?.id || 0);
|
||||||
const currentReportNo = String(options?.report_no || "");
|
let currentReportNo = String(options?.report_no || "");
|
||||||
if (!id && !currentReportNo) {
|
const materialTagToken = String(options?.material_tag_token || "").trim();
|
||||||
|
let materialTagResolvedFromToken = false;
|
||||||
|
if (!id && !currentReportNo && !materialTagToken) {
|
||||||
loadError.value = "缺少报告编号,无法查看详情。";
|
loadError.value = "缺少报告编号,无法查看详情。";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
try {
|
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({
|
detail.value = await appApi.getReportDetail({
|
||||||
id: id || undefined,
|
id: id || undefined,
|
||||||
report_no: currentReportNo || undefined,
|
report_no: currentReportNo || undefined,
|
||||||
@@ -385,10 +410,7 @@ onLoad(async (options) => {
|
|||||||
<view class="report-result__value">{{ resultItem.value || "-" }}</view>
|
<view class="report-result__value">{{ resultItem.value || "-" }}</view>
|
||||||
<view v-if="resultItem.remark" class="report-result__desc">{{ resultItem.remark }}</view>
|
<view v-if="resultItem.remark" class="report-result__desc">{{ resultItem.remark }}</view>
|
||||||
</view>
|
</view>
|
||||||
<view class="report-seal">
|
<image class="report-seal" src="/static/report/report-auth-badge.png" mode="aspectFit" />
|
||||||
<text class="report-seal__brand">安心验</text>
|
|
||||||
<text class="report-seal__main">鉴定</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view class="product-spec">
|
<view class="product-spec">
|
||||||
@@ -540,7 +562,7 @@ onLoad(async (options) => {
|
|||||||
left: 28rpx;
|
left: 28rpx;
|
||||||
right: 28rpx;
|
right: 28rpx;
|
||||||
height: 560rpx;
|
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;
|
opacity: 1;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
@@ -724,56 +746,10 @@ onLoad(async (options) => {
|
|||||||
position: absolute;
|
position: absolute;
|
||||||
right: 2rpx;
|
right: 2rpx;
|
||||||
bottom: 22rpx;
|
bottom: 22rpx;
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
width: 106rpx;
|
width: 106rpx;
|
||||||
height: 106rpx;
|
height: 106rpx;
|
||||||
border: 4rpx solid rgba(40, 151, 73, 0.82);
|
display: block;
|
||||||
border-radius: 999rpx;
|
pointer-events: none;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.product-spec {
|
.product-spec {
|
||||||
|
|||||||
BIN
user-app/src/static/report/report-auth-badge.png
Normal file
BIN
user-app/src/static/report/report-auth-badge.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 27 KiB |
BIN
user-app/src/static/report/report-bg-watermark.png
Normal file
BIN
user-app/src/static/report/report-bg-watermark.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 684 KiB |
@@ -329,10 +329,7 @@ onShow(() => {
|
|||||||
<view class="report-result__value">{{ resultItem.value }}</view>
|
<view class="report-result__value">{{ resultItem.value }}</view>
|
||||||
<view v-if="resultItem.remark" class="report-result__desc">{{ resultItem.remark }}</view>
|
<view v-if="resultItem.remark" class="report-result__desc">{{ resultItem.remark }}</view>
|
||||||
</view>
|
</view>
|
||||||
<view class="report-seal">
|
<image class="report-seal" src="/static/report/report-auth-badge.png" mode="aspectFit" />
|
||||||
<text class="report-seal__brand">安心验</text>
|
|
||||||
<text class="report-seal__main">鉴定</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view class="product-spec">
|
<view class="product-spec">
|
||||||
@@ -508,7 +505,7 @@ onShow(() => {
|
|||||||
left: 28rpx;
|
left: 28rpx;
|
||||||
right: 28rpx;
|
right: 28rpx;
|
||||||
height: 560rpx;
|
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;
|
opacity: 1;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
@@ -688,56 +685,10 @@ onShow(() => {
|
|||||||
position: absolute;
|
position: absolute;
|
||||||
right: 2rpx;
|
right: 2rpx;
|
||||||
bottom: 22rpx;
|
bottom: 22rpx;
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
width: 106rpx;
|
width: 106rpx;
|
||||||
height: 106rpx;
|
height: 106rpx;
|
||||||
border: 4rpx solid rgba(40, 151, 73, 0.82);
|
display: block;
|
||||||
border-radius: 999rpx;
|
pointer-events: none;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.product-spec {
|
.product-spec {
|
||||||
|
|||||||
BIN
work-app/src/static/report/report-auth-badge.png
Normal file
BIN
work-app/src/static/report/report-auth-badge.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 27 KiB |
BIN
work-app/src/static/report/report-bg-watermark.png
Normal file
BIN
work-app/src/static/report/report-bg-watermark.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 684 KiB |
Reference in New Issue
Block a user