|
|
@@ -1,10 +1,12 @@
|
|
|
<script setup>
|
|
|
-import { ref, onMounted, computed, inject } from 'vue'
|
|
|
-import { useRouter } from 'vue-router'
|
|
|
+import { ref, onMounted, computed, inject, watch } from 'vue'
|
|
|
+import { useRouter, useRoute } from 'vue-router'
|
|
|
import { fetchMyQrCodesApi, fetchQrInfoApi, resetPasswordApi, bindQrCodeApi } from '@/services/api'
|
|
|
import { useToast } from 'primevue/usetoast'
|
|
|
+import { saveMaintenanceCode, getMaintenanceCode } from '@/utils/maintenanceCodeCache'
|
|
|
|
|
|
const router = useRouter()
|
|
|
+const route = useRoute()
|
|
|
const toast = useToast()
|
|
|
|
|
|
// 注入父组件提供的注册方法
|
|
|
@@ -41,6 +43,13 @@ const bindQrCode = ref('')
|
|
|
const bindMaintenanceCode = ref('')
|
|
|
const bindLoading = ref(false)
|
|
|
|
|
|
+// 首次扫描后的绑定弹窗(从 URL 参数触发)
|
|
|
+const showFirstScanBindDialog = ref(false)
|
|
|
+const firstScanQrCode = ref('')
|
|
|
+const firstScanMaintenanceCode = ref('')
|
|
|
+const firstScanBindLoading = ref(false)
|
|
|
+const showMaintenanceCodeHintDialog = ref(false)
|
|
|
+
|
|
|
const qrTypeLabel = computed(() => {
|
|
|
if (!qrLookupData.value?.qrType) return '-'
|
|
|
return qrLookupData.value.qrType === 'person' ? 'Person' : qrLookupData.value.qrType === 'pet' ? 'Pet/Item' : qrLookupData.value.qrType
|
|
|
@@ -216,10 +225,12 @@ const openScanPage = async () => {
|
|
|
}
|
|
|
|
|
|
// 绑定二维码相关
|
|
|
+const hasCachedBindMaintenanceCode = ref(false)
|
|
|
const openBindDialog = () => {
|
|
|
showBindDialog.value = true
|
|
|
bindQrCode.value = ''
|
|
|
bindMaintenanceCode.value = ''
|
|
|
+ hasCachedBindMaintenanceCode.value = false
|
|
|
}
|
|
|
|
|
|
const closeBindDialog = () => {
|
|
|
@@ -234,7 +245,15 @@ const submitBind = async () => {
|
|
|
toast.add({ severity: 'warn', summary: 'Notice', detail: 'Please enter QR code number', life: 2000 })
|
|
|
return
|
|
|
}
|
|
|
- const maintenanceCode = bindMaintenanceCode.value.trim()
|
|
|
+ // 如果有缓存的维护码,直接使用;否则使用用户输入的
|
|
|
+ let maintenanceCode = bindMaintenanceCode.value.trim()
|
|
|
+ if (!maintenanceCode && hasCachedBindMaintenanceCode.value) {
|
|
|
+ // 从缓存中获取
|
|
|
+ const cachedCode = getMaintenanceCode(code)
|
|
|
+ if (cachedCode) {
|
|
|
+ maintenanceCode = cachedCode
|
|
|
+ }
|
|
|
+ }
|
|
|
if (!maintenanceCode) {
|
|
|
toast.add({ severity: 'warn', summary: 'Notice', detail: 'Please enter maintenance code', life: 2000 })
|
|
|
return
|
|
|
@@ -242,6 +261,8 @@ const submitBind = async () => {
|
|
|
bindLoading.value = true
|
|
|
try {
|
|
|
await bindQrCodeApi({ qrCode: code, maintenanceCode })
|
|
|
+ // 保存维护码到缓存
|
|
|
+ saveMaintenanceCode(code, maintenanceCode)
|
|
|
toast.add({
|
|
|
severity: 'success',
|
|
|
summary: 'Success',
|
|
|
@@ -262,6 +283,83 @@ const submitBind = async () => {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+// 首次扫描后的绑定相关
|
|
|
+const hasCachedMaintenanceCode = ref(false)
|
|
|
+const openFirstScanBindDialog = (qrCode) => {
|
|
|
+ firstScanQrCode.value = qrCode
|
|
|
+ // 尝试从缓存中获取维护码
|
|
|
+ const cachedCode = getMaintenanceCode(qrCode)
|
|
|
+ hasCachedMaintenanceCode.value = !!cachedCode
|
|
|
+ firstScanMaintenanceCode.value = cachedCode || ''
|
|
|
+ showFirstScanBindDialog.value = true
|
|
|
+}
|
|
|
+
|
|
|
+const closeFirstScanBindDialog = () => {
|
|
|
+ showFirstScanBindDialog.value = false
|
|
|
+ firstScanQrCode.value = ''
|
|
|
+ firstScanMaintenanceCode.value = ''
|
|
|
+ hasCachedMaintenanceCode.value = false
|
|
|
+ // 清除 URL 中的 qrCode 参数
|
|
|
+ router.replace({ name: 'qrmanagerHome' })
|
|
|
+}
|
|
|
+
|
|
|
+const submitFirstScanBind = async () => {
|
|
|
+ const code = firstScanQrCode.value.trim()
|
|
|
+ if (!code) {
|
|
|
+ toast.add({ severity: 'warn', summary: 'Notice', detail: 'QR code number is required', life: 2000 })
|
|
|
+ return
|
|
|
+ }
|
|
|
+ // 如果有缓存的维护码,直接使用;否则使用用户输入的
|
|
|
+ let maintenanceCode = firstScanMaintenanceCode.value.trim()
|
|
|
+ if (!maintenanceCode && hasCachedMaintenanceCode.value) {
|
|
|
+ // 从缓存中获取
|
|
|
+ const cachedCode = getMaintenanceCode(code)
|
|
|
+ if (cachedCode) {
|
|
|
+ maintenanceCode = cachedCode
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (!maintenanceCode) {
|
|
|
+ toast.add({ severity: 'warn', summary: 'Notice', detail: 'Please enter maintenance code', life: 2000 })
|
|
|
+ return
|
|
|
+ }
|
|
|
+ firstScanBindLoading.value = true
|
|
|
+ try {
|
|
|
+ await bindQrCodeApi({ qrCode: code, maintenanceCode })
|
|
|
+ // 保存维护码到缓存
|
|
|
+ saveMaintenanceCode(code, maintenanceCode)
|
|
|
+ toast.add({
|
|
|
+ severity: 'success',
|
|
|
+ summary: 'Success',
|
|
|
+ detail: 'Binding successful',
|
|
|
+ life: 2500
|
|
|
+ })
|
|
|
+ closeFirstScanBindDialog()
|
|
|
+ await fetchMyQrCodes(pagination.value.page)
|
|
|
+ } catch (e) {
|
|
|
+ toast.add({
|
|
|
+ severity: 'error',
|
|
|
+ summary: 'Binding Failed',
|
|
|
+ detail: e?.message || e?.error || 'Binding failed, please check if the maintenance code is correct',
|
|
|
+ life: 3000
|
|
|
+ })
|
|
|
+ } finally {
|
|
|
+ firstScanBindLoading.value = false
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 监听绑定二维码输入,自动填充维护码
|
|
|
+watch(bindQrCode, (newQrCode) => {
|
|
|
+ if (newQrCode && showBindDialog.value) {
|
|
|
+ const cachedCode = getMaintenanceCode(newQrCode.trim())
|
|
|
+ hasCachedBindMaintenanceCode.value = !!cachedCode
|
|
|
+ if (cachedCode) {
|
|
|
+ bindMaintenanceCode.value = cachedCode
|
|
|
+ } else {
|
|
|
+ bindMaintenanceCode.value = ''
|
|
|
+ }
|
|
|
+ }
|
|
|
+})
|
|
|
+
|
|
|
onMounted(() => {
|
|
|
fetchMyQrCodes()
|
|
|
|
|
|
@@ -269,6 +367,12 @@ onMounted(() => {
|
|
|
if (registerOpenPasswordDialog) {
|
|
|
registerOpenPasswordDialog(openPasswordDialog)
|
|
|
}
|
|
|
+
|
|
|
+ // 检查 URL 中是否有 qrCode 参数,如果有则显示首次扫描绑定对话框
|
|
|
+ const qrCodeFromQuery = route.query.qrCode?.toString().trim()
|
|
|
+ if (qrCodeFromQuery) {
|
|
|
+ openFirstScanBindDialog(qrCodeFromQuery)
|
|
|
+ }
|
|
|
})
|
|
|
</script>
|
|
|
|
|
|
@@ -555,7 +659,7 @@ onMounted(() => {
|
|
|
required
|
|
|
/>
|
|
|
</div>
|
|
|
- <div>
|
|
|
+ <div v-if="!hasCachedBindMaintenanceCode">
|
|
|
<label class="mb-1 block text-sm font-medium text-slate-700">Maintenance Code <span class="text-red-500">*</span></label>
|
|
|
<input
|
|
|
v-model="bindMaintenanceCode"
|
|
|
@@ -566,6 +670,14 @@ onMounted(() => {
|
|
|
required
|
|
|
/>
|
|
|
</div>
|
|
|
+ <div v-else class="rounded-xl border border-emerald-200 bg-emerald-50 p-3">
|
|
|
+ <div class="flex items-start gap-2">
|
|
|
+ <i class="pi pi-check-circle text-sm text-emerald-600" />
|
|
|
+ <p class="text-xs text-emerald-700">
|
|
|
+ Maintenance code has been entered. You can click the button to proceed.
|
|
|
+ </p>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
|
|
|
<div class="flex gap-3 pt-2">
|
|
|
<button
|
|
|
@@ -587,6 +699,104 @@ onMounted(() => {
|
|
|
</form>
|
|
|
</div>
|
|
|
</div>
|
|
|
+
|
|
|
+ <!-- 首次扫描后的绑定对话框 -->
|
|
|
+ <div
|
|
|
+ v-if="showFirstScanBindDialog"
|
|
|
+ class="fixed inset-0 z-50 flex items-center justify-center bg-black/50 backdrop-blur-sm"
|
|
|
+ @click.self="closeFirstScanBindDialog"
|
|
|
+ >
|
|
|
+ <div class="w-full max-w-md rounded-2xl border border-slate-200 bg-white p-6 shadow-xl">
|
|
|
+ <div class="mb-4">
|
|
|
+ <div class="text-xl font-semibold text-slate-900">Bind QR Code</div>
|
|
|
+ <div class="mt-1 text-sm text-slate-500">Do you want to bind the QR code you just scanned?</div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="mb-4 rounded-xl border border-slate-200 bg-slate-50 p-4">
|
|
|
+ <div class="text-xs font-medium text-slate-500 mb-1">QR Code Number</div>
|
|
|
+ <div class="text-base font-mono font-semibold text-slate-900">{{ firstScanQrCode }}</div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <form class="space-y-4" @submit.prevent="submitFirstScanBind">
|
|
|
+ <div v-if="!hasCachedMaintenanceCode">
|
|
|
+ <label class="mb-1 block text-sm font-medium text-slate-700">Maintenance Code <span class="text-red-500">*</span></label>
|
|
|
+ <input
|
|
|
+ v-model="firstScanMaintenanceCode"
|
|
|
+ type="text"
|
|
|
+ maxlength="8"
|
|
|
+ class="w-full rounded-xl border border-slate-200 px-3 py-2 outline-none focus:border-slate-900"
|
|
|
+ placeholder="e.g., AB12CD34"
|
|
|
+ required
|
|
|
+ autofocus
|
|
|
+ />
|
|
|
+ <button
|
|
|
+ type="button"
|
|
|
+ @click="showMaintenanceCodeHintDialog = true"
|
|
|
+ class="mt-2 text-sm text-cyan-600 hover:text-cyan-700 hover:underline"
|
|
|
+ >
|
|
|
+ Don't know the maintenance code?
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ <div v-else class="rounded-xl border border-emerald-200 bg-emerald-50 p-3">
|
|
|
+ <div class="flex items-start gap-2">
|
|
|
+ <i class="pi pi-check-circle text-sm text-emerald-600" />
|
|
|
+ <p class="text-xs text-emerald-700">
|
|
|
+ Maintenance code has been entered. You can click the button to proceed.
|
|
|
+ </p>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="flex gap-3 pt-2">
|
|
|
+ <button
|
|
|
+ type="button"
|
|
|
+ class="flex-1 rounded-xl border border-slate-200 px-4 py-2 text-sm font-semibold text-slate-700 hover:bg-slate-50"
|
|
|
+ @click="closeFirstScanBindDialog"
|
|
|
+ :disabled="firstScanBindLoading"
|
|
|
+ >
|
|
|
+ Skip
|
|
|
+ </button>
|
|
|
+ <button
|
|
|
+ type="submit"
|
|
|
+ class="flex-1 rounded-xl bg-slate-900 px-4 py-2 text-sm font-semibold text-white hover:bg-slate-800 disabled:opacity-60"
|
|
|
+ :disabled="firstScanBindLoading"
|
|
|
+ >
|
|
|
+ {{ firstScanBindLoading ? 'Binding...' : 'Confirm Bind' }}
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ </form>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 维护码提示对话框 -->
|
|
|
+ <div
|
|
|
+ v-if="showMaintenanceCodeHintDialog"
|
|
|
+ class="fixed inset-0 z-[60] flex items-center justify-center bg-black/50 backdrop-blur-sm"
|
|
|
+ @click.self="showMaintenanceCodeHintDialog = false"
|
|
|
+ >
|
|
|
+ <div class="w-full max-w-2xl rounded-2xl border border-slate-200 bg-white p-6 shadow-xl">
|
|
|
+ <div class="mb-4 flex items-center gap-3">
|
|
|
+ <div class="flex h-10 w-10 items-center justify-center rounded-full bg-cyan-100">
|
|
|
+ <i class="pi pi-info-circle text-lg text-cyan-600" />
|
|
|
+ </div>
|
|
|
+ <h3 class="text-lg font-semibold text-slate-900">Find Maintenance Code</h3>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="py-4">
|
|
|
+ <img src="/img/Maintenance_code.jpg" alt="Maintenance code location hint"
|
|
|
+ class="w-full rounded-xl border border-slate-200" />
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="flex justify-end">
|
|
|
+ <button
|
|
|
+ type="button"
|
|
|
+ class="rounded-xl bg-cyan-600 px-6 py-2.5 text-sm font-semibold text-white transition hover:bg-cyan-700"
|
|
|
+ @click="showMaintenanceCodeHintDialog = false"
|
|
|
+ >
|
|
|
+ Got it
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
</div>
|
|
|
</template>
|
|
|
|