258 lines
6.3 KiB
Vue
258 lines
6.3 KiB
Vue
<template>
|
|
<view class="verify-page">
|
|
|
|
<image class="bg-image" src="/static/square/squearbj.png" mode="aspectFill"></image>
|
|
|
|
<view class="nav-bar">
|
|
<view class="nav-back" @tap="goBack">←</view>
|
|
<text class="nav-title">周边验真</text>
|
|
</view>
|
|
|
|
<view v-if="loading" class="loading">
|
|
<text>加载中…</text>
|
|
</view>
|
|
|
|
<view v-else-if="error" class="error">
|
|
<text>{{ error }}</text>
|
|
<button @tap="loadData">重试</button>
|
|
</view>
|
|
|
|
<view v-else-if="data" class="content">
|
|
<view class="header">
|
|
<image class="thumb" :src="data.image || '/static/nft/collection.png'" mode="aspectFit" />
|
|
<view class="badge"><text>✓ 已通过验真</text></view>
|
|
</view>
|
|
|
|
<view class="info-card">
|
|
<view v-for="row in infoRows" :key="row.label" class="info-row">
|
|
<text class="info-label">{{ row.label }}</text>
|
|
<text class="info-value" :class="{ 'info-hash': row.hash }">{{ row.value || '—' }}</text>
|
|
</view>
|
|
</view>
|
|
|
|
<view class="tip-row">
|
|
<text class="tip-icon">💡</text>
|
|
<text class="tip-text">加入前请对比实物,确认一致后再添加</text>
|
|
</view>
|
|
|
|
<button class="mint-btn" :loading="submitting" :disabled="submitting" @tap="handleAddToCollection">
|
|
加入我的藏品
|
|
</button>
|
|
</view>
|
|
</view>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { ref, computed } from 'vue'
|
|
import { onLoad } from '@dcloudio/uni-app'
|
|
import { getPeripheralByHashApi, mintToMyCollectionByHashApi } from '@/utils/api.js'
|
|
|
|
// stage 1 强制新版:扫码 URL 形如 /verify/{encrypted_code}?sign=xxx
|
|
// encrypted_code(32 hex)+ sign(32 hex)由 QR 码自带,不再支持老版明文 code
|
|
const HEX32_RE = /^[0-9a-f]{32}$/i
|
|
const encryptedCode = ref('')
|
|
const sign = ref('')
|
|
const data = ref(null)
|
|
const loading = ref(true)
|
|
const error = ref('')
|
|
const submitting = ref(false)
|
|
|
|
onLoad((options) => {
|
|
// 强校验:必须同时有 encrypted_code + sign
|
|
if (HEX32_RE.test(options.encryptedCode || '') && options.sign) {
|
|
encryptedCode.value = String(options.encryptedCode)
|
|
sign.value = String(options.sign)
|
|
loadData()
|
|
return
|
|
}
|
|
// 老入口(明文 code / asset_id)不再支持
|
|
error.value = '请使用最新版本扫描周边二维码'
|
|
loading.value = false
|
|
})
|
|
|
|
const infoRows = computed(() => {
|
|
if (!data.value) return []
|
|
const d = data.value
|
|
console.log('infoRows data:', d)
|
|
return [
|
|
{ label: '周边编号', value: d.code || '—' },
|
|
{ label: '品牌', value: d.brand },
|
|
{ label: '公司', value: d.company },
|
|
{ label: '链上哈希', value: d.hash, hash: true },
|
|
{ label: '验证次数', value: `${d.verify_count} 次` },
|
|
{ label: '验证人', value: d.verifier },
|
|
{ label: '首次验证', value: formatDate(d.verified_at) },
|
|
// 周边素材类型(后端 omitempty,空时 d.material_type 为 undefined)
|
|
// { label: '素材类型', value: (d.material_type && d.material_type.trim()) || 'new' }
|
|
]
|
|
})
|
|
|
|
function goBack() {
|
|
uni.navigateBack({ delta: 1, fail: () => uni.switchTab({ url: '/pages/square/square' }) })
|
|
}
|
|
|
|
async function loadData() {
|
|
loading.value = true
|
|
error.value = ''
|
|
try {
|
|
// stage 1:按加密 code 验真(详见 design §5.1.3)
|
|
const res = await getPeripheralByHashApi(encryptedCode.value, sign.value)
|
|
// stage 1 新增业务码 50013(sign 验证失败)—— 引导用户重新扫码
|
|
if (res?.code === 50013) {
|
|
throw new Error('验签失败,二维码可能已损坏或被篡改,请重新扫码')
|
|
}
|
|
if (!res || !res.data) throw new Error(res?.message || '此物品暂无验真信息')
|
|
data.value = res.data
|
|
} catch (e) {
|
|
error.value = e.message || '加载失败'
|
|
} finally {
|
|
loading.value = false
|
|
}
|
|
}
|
|
|
|
async function handleAddToCollection() {
|
|
if (submitting.value) return
|
|
submitting.value = true
|
|
try {
|
|
const res = await mintToMyCollectionByHashApi(encryptedCode.value, sign.value)
|
|
uni.showToast({ title: '已加入我的藏品', icon: 'success' })
|
|
setTimeout(() => {
|
|
uni.navigateTo({
|
|
url: `/pages/asset-detail/asset-detail?asset_id=${res.data?.asset_id}`
|
|
})
|
|
}, 800)
|
|
} catch (e) {
|
|
uni.showToast({ title: e.message || '添加失败', icon: 'none' })
|
|
} finally {
|
|
submitting.value = false
|
|
}
|
|
}
|
|
|
|
function formatDate(ts) {
|
|
if (!ts) return ''
|
|
const d = new Date(ts * 1000)
|
|
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`
|
|
}
|
|
</script>
|
|
|
|
<style scoped>
|
|
.verify-page {
|
|
/* background: #0a0a0a; */
|
|
min-height: 100vh;
|
|
padding: 24rpx;
|
|
|
|
}
|
|
|
|
/* 背景图片 */
|
|
.bg-image {
|
|
position: fixed;
|
|
top: 0;
|
|
left: 0;
|
|
width: 100%;
|
|
height: 120%;
|
|
z-index: 0;
|
|
}
|
|
|
|
.nav-bar {
|
|
display: flex;
|
|
align-items: center;
|
|
height: 88rpx;
|
|
padding: 0 24rpx;
|
|
color: #fff;
|
|
position: relative;
|
|
top: 96rpx;
|
|
z-index: 1;
|
|
margin-bottom: 24rpx;
|
|
}
|
|
|
|
.nav-back {
|
|
font-size: 40rpx;
|
|
padding-right: 24rpx;
|
|
position: relative;
|
|
z-index: 1;
|
|
}
|
|
|
|
.nav-title {
|
|
flex: 1;
|
|
text-align: center;
|
|
font-size: 32rpx;
|
|
font-weight: 600;
|
|
}
|
|
|
|
.loading,
|
|
.error {
|
|
color: #aaa;
|
|
text-align: center;
|
|
padding: 80rpx 0;
|
|
position: relative;
|
|
z-index: 1;
|
|
}
|
|
|
|
.content {
|
|
position: relative;
|
|
z-index: 1;
|
|
}
|
|
|
|
.header {
|
|
/* display: flex;
|
|
align-items: center;
|
|
justify-content: center; */
|
|
margin-top: 64rpx;
|
|
}
|
|
|
|
.info-card {
|
|
/* background: #1a1a1a; */
|
|
border-radius: 16rpx;
|
|
/* padding: 24rpx; */
|
|
margin-top: 24rpx;
|
|
}
|
|
|
|
.info-row {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
padding: 16rpx 0;
|
|
/* border-bottom: 1rpx solid #2a2a2a; */
|
|
}
|
|
|
|
.info-row:last-child {
|
|
border-bottom: none;
|
|
}
|
|
|
|
.info-label {
|
|
color: rgba(255, 255, 255, 0.7);
|
|
font-size: 26rpx;
|
|
}
|
|
|
|
.info-value {
|
|
color: #fff;
|
|
text-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.7);
|
|
font-size: 28rpx;
|
|
width: 60%;
|
|
word-break: break-all;
|
|
text-align: left;
|
|
}
|
|
|
|
.info-value.info-hash {
|
|
font-family: monospace;
|
|
font-size: 22rpx;
|
|
color: #3ddc84;
|
|
}
|
|
|
|
.tip-row {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8rpx;
|
|
padding: 24rpx 8rpx;
|
|
color: #888;
|
|
font-size: 26rpx;
|
|
}
|
|
|
|
.mint-btn {
|
|
background: linear-gradient(135deg, rgba(255, 222, 8, 0.49) 0%, rgba(252, 100, 102, 1) 61.1%, rgba(244, 88, 104, 1) 100%);
|
|
box-shadow: inset 0 0.03125rem 0 rgba(255, 255, 255, 0.45), 2px 2px 4px 0px #f2151578;
|
|
color: #fff;
|
|
border-radius: 100rpx;
|
|
margin-top: 32rpx;
|
|
font-weight: 600;
|
|
}
|
|
</style> |