This commit is contained in:
Mrx
2026-06-04 18:04:00 +08:00
parent 0f0cdd67c2
commit b7e0abb898
15 changed files with 258 additions and 13 deletions

1
.env
View File

@@ -15,3 +15,4 @@ VITE_API_BASE_URL=https://www.quannengcha.com/api/v1
# 想用本地接口时注释掉上面那行,取消下面这行注释: # 想用本地接口时注释掉上面那行,取消下面这行注释:
# VITE_API_BASE_URL=http://127.0.0.1:8888/api/v1 # VITE_API_BASE_URL=http://127.0.0.1:8888/api/v1

View File

@@ -1,2 +1,4 @@
# 生产构建:指向 qnc-server-v3 线上网关 # 生产构建:指向 qnc-server-v3 线上网关
VITE_API_BASE_URL=https://www.quannengcha.com/api/v1 VITE_API_BASE_URL=https://www.quannengcha.com/api/v1
# qnc-webview-v3 H5 站点(小程序 web-view / 分享链接)
VITE_H5_ORIGIN=https://www.quannengcha.com

1
components.d.ts vendored
View File

@@ -10,6 +10,7 @@ declare module 'vue' {
AppFooter: typeof import('./src/components/AppFooter.vue')['default'] AppFooter: typeof import('./src/components/AppFooter.vue')['default']
AppLogos: typeof import('./src/components/AppLogos.vue')['default'] AppLogos: typeof import('./src/components/AppLogos.vue')['default']
InputEntry: typeof import('./src/components/InputEntry.vue')['default'] InputEntry: typeof import('./src/components/InputEntry.vue')['default']
ReportShareBar: typeof import('./src/components/report/ReportShareBar.vue')['default']
VehicleBlockFallback: typeof import('./src/components/report/VehicleBlockFallback.vue')['default'] VehicleBlockFallback: typeof import('./src/components/report/VehicleBlockFallback.vue')['default']
VehicleBlockQCXG1H7Y: typeof import('./src/components/report/blocks/VehicleBlockQCXG1H7Y.vue')['default'] VehicleBlockQCXG1H7Y: typeof import('./src/components/report/blocks/VehicleBlockQCXG1H7Y.vue')['default']
VehicleBlockQCXG1U4U: typeof import('./src/components/report/blocks/VehicleBlockQCXG1U4U.vue')['default'] VehicleBlockQCXG1U4U: typeof import('./src/components/report/blocks/VehicleBlockQCXG1U4U.vue')['default']

2
env.d.ts vendored
View File

@@ -5,6 +5,8 @@ interface ImportMetaEnv {
readonly VITE_API_BASE_URL?: string readonly VITE_API_BASE_URL?: string
/** H5 开发代理目标,仅 vite 使用,默认 http://127.0.0.1:8888 */ /** H5 开发代理目标,仅 vite 使用,默认 http://127.0.0.1:8888 */
readonly VITE_API_PROXY_TARGET?: string readonly VITE_API_PROXY_TARGET?: string
/** qnc-webview-v3 站点根地址,如 https://www.quannengcha.com */
readonly VITE_H5_ORIGIN?: string
} }
interface ImportMeta { interface ImportMeta {

View File

@@ -29,6 +29,16 @@ export async function getQueryList(params = {}, requestConfig) {
return res.data return res.data
} }
/** 生成报告分享链接(与 H5 ShareReportButton POST /query/generate_share_link 一致) */
export async function generateQueryShareLink(body, requestConfig = {}) {
const res = await http.post('/query/generate_share_link', body, {
skipLoading: true,
skipBizToast: true,
...requestConfig,
})
return res.data
}
/** 创建查询临时单(与 H5 `InquireForm` 一致POST /api/v1/query/service/:product */ /** 创建查询临时单(与 H5 `InquireForm` 一致POST /api/v1/query/service/:product */
export async function postQueryService(productEn, body, requestConfig) { export async function postQueryService(productEn, body, requestConfig) {
const enc = encodeURIComponent(productEn) const enc = encodeURIComponent(productEn)

View File

@@ -0,0 +1,107 @@
<script setup>
import { ref } from 'vue'
import { fetchReportShareUrl } from '@/utils/reportH5Link'
const props = defineProps({
orderId: { type: String, default: '' },
orderNo: { type: String, default: '' },
})
const loading = ref(false)
async function copyLink() {
if (loading.value)
return
if (!props.orderId && !props.orderNo) {
uni.showToast({ title: '缺少订单信息', icon: 'none' })
return
}
loading.value = true
uni.showLoading({ title: '生成链接…', mask: true })
try {
const url = await fetchReportShareUrl({
orderId: props.orderId,
orderNo: props.orderNo,
})
uni.hideLoading()
await new Promise((resolve, reject) => {
uni.setClipboardData({
data: url,
success: resolve,
fail: reject,
})
})
uni.showToast({ title: '链接已复制,可在浏览器打开', icon: 'none', duration: 2500 })
}
catch (e) {
uni.hideLoading()
uni.showToast({
title: e?.message || '复制失败',
icon: 'none',
duration: 2500,
})
}
finally {
loading.value = false
}
}
</script>
<template>
<view class="share-bar">
<view class="share-bar-inner">
<button
class="share-btn"
:loading="loading"
:disabled="loading"
@tap="copyLink"
>
<view class="share-btn-icon i-carbon-copy" />
<text>{{ loading ? '生成中…' : '复制链接' }}</text>
</button>
</view>
</view>
</template>
<style scoped lang="scss">
.share-bar {
position: fixed;
left: 0;
right: 0;
bottom: 0;
z-index: 100;
background: #fff;
border-top: 1rpx solid #e5e6eb;
box-shadow: 0 -4rpx 24rpx rgba(0, 0, 0, 0.06);
}
.share-bar-inner {
padding: 20rpx 24rpx calc(20rpx + env(safe-area-inset-bottom));
box-sizing: border-box;
}
.share-btn {
display: flex;
align-items: center;
justify-content: center;
gap: 12rpx;
width: 100%;
margin: 0;
height: 88rpx;
line-height: 88rpx;
font-size: 30rpx;
font-weight: 600;
color: #fff;
background: linear-gradient(90deg, #1768ff 0%, #4d94ff 100%);
border-radius: 44rpx;
}
.share-btn::after {
border: none;
}
.share-btn-icon {
font-size: 36rpx;
color: #fff;
}
</style>

View File

@@ -1,7 +1,7 @@
<script setup> <script setup>
import { computed } from 'vue' import { computed } from 'vue'
/** 人车核验简版(与 H5 CQCXGGB2Qverify_code */ /** 人车核验简版(与 H5 CQCXGGB2Qverify_code 1 一致 / 0 不一致 */
const props = defineProps({ const props = defineProps({
blockTitle: { type: String, default: '' }, blockTitle: { type: String, default: '' },
apiId: { type: String, default: '' }, apiId: { type: String, default: '' },
@@ -23,7 +23,7 @@ const isMatch = computed(() => {
const n = Number(code) const n = Number(code)
if (n === 1) if (n === 1)
return true return true
if (n === 2) if (n === 0)
return false return false
return null return null
}) })
@@ -32,7 +32,7 @@ const resultText = computed(() => {
if (isMatch.value === true) if (isMatch.value === true)
return '一致' return '一致'
if (isMatch.value === false) if (isMatch.value === false)
return '不匹配' return '不一致'
return '暂无结果' return '暂无结果'
}) })

17
src/config/h5.js Normal file
View File

@@ -0,0 +1,17 @@
/**
* qnc-webview-v3 H5 站点根地址(不含 /api/v1
* 小程序内 web-view 打开报告页、分享链接拼接用。
*/
export function getH5Origin() {
const fromEnv = import.meta.env.VITE_H5_ORIGIN?.trim()
if (fromEnv)
return fromEnv.replace(/\/$/, '')
const apiBase = import.meta.env.VITE_API_BASE_URL?.trim() || ''
if (/quannengcha\.com/i.test(apiBase))
return apiBase.replace(/\/api\/v1\/?$/i, '').replace(/\/$/, '') || 'https://www.quannengcha.com'
// 本地联调 API 时H5 仍默认走线上web-view 无法打开 localhost
// return 'https://www.quannengcha.com'
return 'http://127.0.0.1:5678'
}

View File

@@ -17,7 +17,7 @@ export const VEHICLE_API_TITLES = {
QCXG3Z3L: '车辆维保详细版查询', QCXG3Z3L: '车辆维保详细版查询',
QCXG1H7Y: '车辆过户简版查询', QCXG1H7Y: '车辆过户简版查询',
QCXG4I1Z: '车辆过户详版查询', QCXG4I1Z: '车辆过户详版查询',
QCXGGB2Q: '车辆二要素核验 V1', QCXGGB2Q: '车辆二要素核验简版',
QCXGP00W: '车辆出险详版查询', QCXGP00W: '车辆出险详版查询',
QCXGYTS2: '人车核验(详版)', QCXGYTS2: '人车核验(详版)',
QCXGGJ3A: '车辆 VIN 码查询号牌简版', QCXGGJ3A: '车辆 VIN 码查询号牌简版',

View File

@@ -150,6 +150,16 @@
"navigationBarTextStyle": "black" "navigationBarTextStyle": "black"
} }
}, },
{
"path": "pages/report/webview",
"type": "page",
"style": {
"navigationBarTitleText": "完整版报告",
"navigationStyle": "default",
"navigationBarBackgroundColor": "#ffffff",
"navigationBarTextStyle": "black"
}
},
{ {
"path": "pages/toolbox/category", "path": "pages/toolbox/category",
"type": "page", "type": "page",

View File

@@ -3,6 +3,7 @@ import { onLoad, onUnload } from '@dcloudio/uni-app'
import { ref } from 'vue' import { ref } from 'vue'
import { getQueryDetailByOrderId, getQueryDetailByOrderNo } from '@/api' import { getQueryDetailByOrderId, getQueryDetailByOrderNo } from '@/api'
import VehicleReportShell from '@/components/report/VehicleReportShell.vue' import VehicleReportShell from '@/components/report/VehicleReportShell.vue'
import ReportShareBar from '@/components/report/ReportShareBar.vue'
import { parseEncryptedQueryReport } from '@/utils/queryReportParse' import { parseEncryptedQueryReport } from '@/utils/queryReportParse'
import { normalizeVehicleQueryData } from '@/utils/vehicleReportNormalize' import { normalizeVehicleQueryData } from '@/utils/vehicleReportNormalize'
@@ -85,7 +86,7 @@ async function load(opts = {}) {
if (parsed.ok) { if (parsed.ok) {
queryParams.value = parsed.queryParams queryParams.value = parsed.queryParams
rows.value = parsed.rows rows.value = parsed.rows
errText.value = rows.value.length ? '' : '暂无报告模块数据' errText.value = rows.value.length ? '' : '渲染失败,可复制底部链接在浏览器查看'
} }
else { else {
rows.value = normalizeVehicleQueryData([]) rows.value = normalizeVehicleQueryData([])
@@ -105,7 +106,7 @@ async function load(opts = {}) {
</script> </script>
<template> <template>
<view class="page-root"> <view class="page-root" :class="{ 'has-share-bar': orderId || orderNo }">
<view v-if="loading && !pending" class="state"> <view v-if="loading && !pending" class="state">
加载中 加载中
</view> </view>
@@ -124,6 +125,11 @@ async function load(opts = {}) {
:rows="rows" :rows="rows"
/> />
</view> </view>
<ReportShareBar
v-if="orderId || orderNo"
:order-id="orderId"
:order-no="orderNo"
/>
</view> </view>
</template> </template>
@@ -134,8 +140,12 @@ async function load(opts = {}) {
box-sizing: border-box; box-sizing: border-box;
} }
.page-root.has-share-bar {
padding-bottom: calc(128rpx + env(safe-area-inset-bottom));
}
.state { .state {
padding: 100rpx 32rpx; padding: 48rpx 32rpx 0;
text-align: center; text-align: center;
font-size: 28rpx; font-size: 28rpx;
color: #86909c; color: #86909c;

View File

@@ -0,0 +1,57 @@
<script setup>
import { onLoad } from '@dcloudio/uni-app'
import { ref } from 'vue'
definePage({
style: {
navigationBarTitleText: '完整版报告',
navigationStyle: 'default',
navigationBarBackgroundColor: '#ffffff',
navigationBarTextStyle: 'black',
},
})
const pageUrl = ref('')
const loadError = ref('')
onLoad((options) => {
const raw = options?.url || ''
if (!raw) {
loadError.value = '缺少报告地址'
return
}
try {
pageUrl.value = decodeURIComponent(raw)
}
catch {
pageUrl.value = raw
}
if (!/^https:\/\//i.test(pageUrl.value)) {
loadError.value = '报告链接无效'
pageUrl.value = ''
}
})
</script>
<template>
<view class="page">
<web-view v-if="pageUrl" :src="pageUrl" />
<view v-else class="err">
<text>{{ loadError || '无法加载报告' }}</text>
</view>
</view>
</template>
<style scoped lang="scss">
.page {
width: 100%;
height: 100vh;
}
.err {
padding: 80rpx 32rpx;
text-align: center;
font-size: 28rpx;
color: #86909c;
}
</style>

View File

@@ -65,17 +65,17 @@ const maskedName = computed(() => {
return name.length > 1 ? name[0] + '*'.repeat(name.length - 1) : '*'; return name.length > 1 ? name[0] + '*'.repeat(name.length - 1) : '*';
}); });
// verify_code: 1 一致,2匹配 // verify_code: 1 一致,0一致
const isMatch = computed(() => { const isMatch = computed(() => {
const code = props.data?.verify_code; const n = Number(props.data?.verify_code);
if (code === 1) return true; if (n === 1) return true;
if (code === 2) return false; if (n === 0) return false;
return null; // 无有效数据时 return null;
}); });
const resultText = computed(() => { const resultText = computed(() => {
if (isMatch.value === true) return '一致'; if (isMatch.value === true) return '一致';
if (isMatch.value === false) return '不匹配'; if (isMatch.value === false) return '不一致';
return '暂无结果'; return '暂无结果';
}); });

27
src/utils/reportH5Link.js Normal file
View File

@@ -0,0 +1,27 @@
import { generateQueryShareLink } from '@/api/query'
import { getH5Origin } from '@/config/h5'
/** 登录用户直接打开 H5 报告(需 H5 端同账号登录) */
export function buildDirectReportUrl({ orderId, orderNo }) {
const base = getH5Origin()
if (orderNo)
return `${base}/report?orderNo=${encodeURIComponent(orderNo)}`
if (orderId)
return `${base}/report?orderId=${encodeURIComponent(String(orderId))}`
return ''
}
/** 与 H5 ShareReportButton 一致POST /query/generate_share_link → /report/share/:id */
export async function fetchReportShareUrl({ orderId, orderNo }) {
const body = orderId
? { order_id: String(orderId) }
: { order_no: String(orderNo) }
const res = await generateQueryShareLink(body)
const linkId = res?.data?.share_link
if (res?.code !== 200 || !linkId)
throw new Error(res?.msg || '生成分享链接失败')
const base = getH5Origin()
return `${base}/report/share/${encodeURIComponent(linkId)}`
}

1
uni-pages.d.ts vendored
View File

@@ -19,6 +19,7 @@ type _LocationUrl =
"/pages/legal/privacy-policy" | "/pages/legal/privacy-policy" |
"/pages/legal/user-agreement" | "/pages/legal/user-agreement" |
"/pages/report/detail" | "/pages/report/detail" |
"/pages/report/webview" |
"/pages/toolbox/category" | "/pages/toolbox/category" |
"/pages/toolbox/index" | "/pages/toolbox/index" |
"/pages/toolbox/query"; "/pages/toolbox/query";