chuangte_bike_newxcx/page_shanghu/guanli/components/NfcAddDialog.vue

477 lines
12 KiB
Vue
Raw Normal View History

2026-03-11 20:16:17 +08:00
<template>
<view>
2026-03-12 13:54:49 +08:00
<u-mask :show="show" :z-index="100" />
2026-03-11 20:16:17 +08:00
<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) {
2026-03-12 13:54:49 +08:00
const uid = this.arrayBufferToHex(res.id).toUpperCase().padStart(16, '0')
2026-03-11 20:16:17 +08:00
console.log('[NFC] 原始响应:', res)
2026-03-12 13:54:49 +08:00
console.log('[NFC] 卡片UID (hex):', uid)
2026-03-11 20:16:17 +08:00
console.log('[NFC] 技术类型 techs:', res.techs)
console.log('[NFC] NDEF messages:', res.messages)
2026-03-12 13:54:49 +08:00
this.form.nfcNo = uid
2026-03-11 20:16:17 +08:00
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 = {
2026-03-12 13:54:49 +08:00
deviceId: this.deviceId,
2026-03-11 20:16:17 +08:00
nfcNo: this.form.nfcNo.trim(),
type: this.form.type,
remark: this.form.remark.trim() || undefined
}
2026-03-12 13:54:49 +08:00
this.$u.post('/bst/deviceNfc', data).then(res => {
2026-03-11 20:16:17 +08:00
uni.hideLoading()
if (res.code === 200) {
2026-03-12 13:54:49 +08:00
uni.showToast({ title: '新增成功', icon: 'success' })
2026-03-11 20:16:17 +08:00
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>