chuangte_bike_newxcx/page_shanghu/guanli/components/NfcAddDialog.vue
2026-03-12 13:54:49 +08:00

477 lines
12 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<view>
<u-mask :show="show" :z-index="100" />
<view class="nfc-add-dialog" v-if="show">
<!-- 标题 -->
<view class="dialog-title">添加NFC卡</view>
<!-- 表单 -->
<view class="dialog-body">
<!-- NFC编号 -->
<view class="form-item">
<text class="form-label">NFC编号 <text class="required">*</text></text>
<view class="input-row">
<input
class="form-input"
v-model="form.nfcNo"
placeholder="请输入或扫描NFC编号"
placeholder-style="color:#C7CDD3"
:disabled="nfcReading"
/>
<view
class="scan-btn"
:class="{ 'scan-btn-reading': nfcReading }"
@click="toggleNfcRead"
>
<!-- NFC 扫描图标 -->
<view class="scan-icon-wrap">
<view class="scan-circle" :class="{ 'scan-pulse': nfcReading }"></view>
<view class="scan-wave wave1" v-if="nfcReading"></view>
<view class="scan-wave wave2" v-if="nfcReading"></view>
</view>
<text class="scan-text">{{ nfcReading ? '取消' : '扫卡' }}</text>
</view>
</view>
<!-- NFC 读取状态提示 -->
<view class="nfc-status" v-if="nfcReading">
<text class="nfc-status-text">请将NFC卡靠近手机背面...</text>
</view>
<view class="nfc-status nfc-status-success" v-if="nfcReadSuccess">
<text class="nfc-status-text">✓ 读取成功</text>
</view>
</view>
<!-- 卡片类型 -->
<view class="form-item">
<text class="form-label">卡片类型 <text class="required">*</text></text>
<view class="type-group">
<view
class="type-btn"
:class="form.type === '1' ? 'type-btn-active' : ''"
@click="form.type = '1'"
>主卡</view>
<view
class="type-btn"
:class="form.type === '2' ? 'type-btn-active' : ''"
@click="form.type = '2'"
>副卡</view>
</view>
</view>
<!-- 备注 -->
<view class="form-item">
<text class="form-label">备注</text>
<input
class="form-input"
v-model="form.remark"
placeholder="请输入备注(选填)"
placeholder-style="color:#C7CDD3"
/>
</view>
</view>
<!-- 底部按钮 -->
<view class="dialog-footer">
<view class="dialog-cancel" @click="handleClose">取消</view>
<view class="dialog-confirm" @click="handleSubmit">确定</view>
</view>
</view>
</view>
</template>
<script>
export default {
name: 'NfcAddDialog',
props: {
show: {
type: Boolean,
default: false
},
deviceId: {
type: [String, Number],
required: true
}
},
data() {
return {
form: {
nfcNo: '',
type: '1',
remark: ''
},
nfcReading: false,
nfcReadSuccess: false,
nfcAdapter: null
}
},
watch: {
show(val) {
if (val) {
this.resetForm()
} else {
this.stopNfcRead()
}
}
},
methods: {
resetForm() {
this.form = { nfcNo: '', type: '1', remark: '' }
this.nfcReading = false
this.nfcReadSuccess = false
},
handleClose() {
this.stopNfcRead()
this.$emit('close')
},
// 切换NFC读取状态
toggleNfcRead() {
if (this.nfcReading) {
this.stopNfcRead()
} else {
this.startNfcRead()
}
},
// 开始NFC读取
startNfcRead() {
// #ifdef MP-WEIXIN
if (!wx.getNFCAdapter) {
uni.showToast({ title: '当前微信版本不支持NFC', icon: 'none' })
return
}
const adapter = wx.getNFCAdapter()
this.nfcAdapter = adapter
this.nfcReadSuccess = false
adapter.startDiscovery({
success: () => {
this.nfcReading = true
// 监听NFC卡片
adapter.onDiscovered(this.onNfcDiscovered)
},
fail: (err) => {
console.error('NFC startDiscovery fail:', err)
if (err.errCode === 41001) {
uni.showToast({ title: '请在手机设置中开启NFC', icon: 'none' })
} else if (err.errCode === 41002) {
uni.showToast({ title: '设备不支持NFC功能', icon: 'none' })
} else {
uni.showToast({ title: '启动NFC失败请检查NFC是否开启', icon: 'none' })
}
}
})
// #endif
// #ifndef MP-WEIXIN
uni.showToast({ title: '该功能仅支持微信小程序', icon: 'none' })
// #endif
},
// NFC卡片发现回调
onNfcDiscovered(res) {
const uid = this.arrayBufferToHex(res.id).toUpperCase().padStart(16, '0')
console.log('[NFC] 原始响应:', res)
console.log('[NFC] 卡片UID (hex):', uid)
console.log('[NFC] 技术类型 techs:', res.techs)
console.log('[NFC] NDEF messages:', res.messages)
this.form.nfcNo = uid
this.nfcReadSuccess = true
this.stopNfcRead()
uni.showToast({ title: '读取成功', icon: 'success', duration: 1500 })
},
// 停止NFC读取
stopNfcRead() {
// #ifdef MP-WEIXIN
if (this.nfcAdapter) {
this.nfcAdapter.offDiscovered(this.onNfcDiscovered)
this.nfcAdapter.stopDiscovery({
complete: () => {
this.nfcReading = false
this.nfcAdapter = null
}
})
} else {
this.nfcReading = false
}
// #endif
// #ifndef MP-WEIXIN
this.nfcReading = false
// #endif
},
// ArrayBuffer 转十六进制字符串
arrayBufferToHex(buffer) {
if (!buffer) return ''
const bytes = new Uint8Array(buffer)
return Array.from(bytes)
.map(b => b.toString(16).padStart(2, '0'))
.join('')
},
// 提交表单
handleSubmit() {
if (!this.form.nfcNo.trim()) {
uni.showToast({ title: 'NFC编号不能为空', icon: 'none' })
return
}
if (!this.form.type) {
uni.showToast({ title: '请选择卡片类型', icon: 'none' })
return
}
uni.showLoading({ title: '绑定中...', mask: true })
const data = {
deviceId: this.deviceId,
nfcNo: this.form.nfcNo.trim(),
type: this.form.type,
remark: this.form.remark.trim() || undefined
}
this.$u.post('/bst/deviceNfc', data).then(res => {
uni.hideLoading()
if (res.code === 200) {
uni.showToast({ title: '新增成功', icon: 'success' })
this.$emit('success')
this.handleClose()
} else {
uni.showToast({ title: res.msg, icon: 'none' })
}
}).catch(() => {
uni.hideLoading()
uni.showToast({ title: '操作失败', icon: 'none' })
})
}
}
}
</script>
<style lang="scss" scoped>
.nfc-add-dialog {
position: fixed;
left: 60rpx;
top: 50%;
transform: translateY(-50%);
width: 630rpx;
background: #FFFFFF;
border-radius: 24rpx;
z-index: 110;
overflow: hidden;
.dialog-title {
font-size: 34rpx;
font-weight: 700;
color: #3D3D3D;
text-align: center;
padding: 44rpx 40rpx 30rpx;
border-bottom: 1rpx solid #F0F0F0;
}
.dialog-body {
padding: 30rpx 40rpx 10rpx;
.form-item {
margin-bottom: 30rpx;
.form-label {
font-size: 28rpx;
color: #3D3D3D;
margin-bottom: 16rpx;
display: block;
.required {
color: #FF4D4F;
margin-left: 4rpx;
}
}
.form-input {
width: 100%;
height: 72rpx;
border: 2rpx solid #E5E5E5;
border-radius: 10rpx;
padding: 0 20rpx;
font-size: 28rpx;
color: #3D3D3D;
box-sizing: border-box;
background: #FAFAFA;
}
/* NFC编号 输入行input + 扫卡按钮) */
.input-row {
display: flex;
align-items: center;
gap: 16rpx;
.form-input {
flex: 1;
width: auto;
}
.scan-btn {
flex-shrink: 0;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
width: 110rpx;
height: 72rpx;
background: #EBF3FF;
border-radius: 10rpx;
border: 2rpx solid #4C97E7;
&.scan-btn-reading {
background: #FFF3E0;
border-color: #FF9800;
}
.scan-icon-wrap {
position: relative;
width: 36rpx;
height: 28rpx;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 4rpx;
.scan-circle {
width: 18rpx;
height: 18rpx;
border-radius: 50%;
background: #4C97E7;
position: relative;
z-index: 1;
&.scan-pulse {
background: #FF9800;
animation: pulse 1.2s ease-in-out infinite;
}
}
.scan-wave {
position: absolute;
border-radius: 50%;
border: 2rpx solid #FF9800;
animation: wave-expand 1.2s ease-out infinite;
opacity: 0;
}
.wave1 {
width: 28rpx;
height: 28rpx;
animation-delay: 0s;
}
.wave2 {
width: 40rpx;
height: 40rpx;
animation-delay: 0.4s;
}
}
.scan-text {
font-size: 20rpx;
color: #4C97E7;
line-height: 1;
font-weight: 500;
&.scan-btn-reading & {
color: #FF9800;
}
}
}
.scan-btn-reading .scan-text {
color: #FF9800;
}
}
/* NFC 状态提示 */
.nfc-status {
margin-top: 12rpx;
padding: 10rpx 16rpx;
border-radius: 8rpx;
background: #FFF8E1;
.nfc-status-text {
font-size: 22rpx;
color: #FF9800;
}
}
.nfc-status-success {
background: #F0FFF4;
.nfc-status-text {
color: #28C445;
}
}
/* 卡片类型选择 */
.type-group {
display: flex;
gap: 20rpx;
.type-btn {
flex: 1;
height: 72rpx;
display: flex;
align-items: center;
justify-content: center;
border-radius: 10rpx;
font-size: 28rpx;
color: #666666;
background: #F5F5F5;
border: 2rpx solid transparent;
box-sizing: border-box;
}
.type-btn-active {
background: #EBF3FF;
color: #4C97E7;
border-color: #4C97E7;
font-weight: 500;
}
}
}
}
.dialog-footer {
display: flex;
border-top: 1rpx solid #F0F0F0;
margin-top: 10rpx;
.dialog-cancel,
.dialog-confirm {
flex: 1;
height: 90rpx;
display: flex;
align-items: center;
justify-content: center;
font-size: 32rpx;
font-weight: 500;
}
.dialog-cancel {
color: #666666;
border-right: 1rpx solid #F0F0F0;
}
.dialog-confirm {
color: #4C97E7;
}
}
}
@keyframes pulse {
0%, 100% { transform: scale(1); opacity: 1; }
50% { transform: scale(1.3); opacity: 0.7; }
}
@keyframes wave-expand {
0% { transform: scale(0.5); opacity: 0.8; }
100% { transform: scale(1.8); opacity: 0; }
}
</style>