This commit is contained in:
xuli
2025-12-02 11:44:38 +08:00
parent 11990a62da
commit d8d5ac02c9
17 changed files with 233 additions and 1099 deletions

View File

@@ -1,15 +1,12 @@
<template>
<view class="nfc-con">
<view class="nfc-con">
<view class="nfc-bg">
<view class="nfc-title">
<uni-icons type="closeempty" size="20" class="nfc-close" @click="handleClose"></uni-icons>NFC识别
</view>
<block v-if="!nfcResult">
<view class="nfc-pic" @click="readData" v-if="!isReading">
<img :src="'static/images/polling/nfc-logo.png'" />
</view>
<view class="nfc-pic" v-else>
<img class="nfc-pic-animal" :src="'static/images/polling/nfc-logo.png'" />
<block v-if="formData.deviceId==''">
<view class="nfc-pic">
<img class="nfc-pic-animal" :src="'static/images/polling/nfc-logo.png'" />
</view>
<view class="nfc-desc">请将设备靠近NFC识别</view>
</block>
@@ -25,7 +22,7 @@
<view>识别时间</view>
<view>{{parseTime(new Date().getTime(),'{y}年{m}月{d}日 星期{a} {h}:{i}')}}</view>
</view>
<view class="btn-blue" @click="handleConfirm">确定</view>
<view class="btn-blue" @click="handleClose">确定</view>
</block>
<block v-else>
<view class="result-header">
@@ -35,293 +32,200 @@
<view class="result-value">
可能的失败原因手机距离NFC设备较远手机未联网NFC设备故障
</view>
<view class="btn-blue" @click="readData">重新识别</view>
<view class="btn-blue" @click="open">重新识别</view>
</block>
</view>
</view>
</view>
</template>
<script setup>
import { ref,watch } from 'vue'
import { onLoad,onHide} from '@dcloudio/uni-app';
import { ref, onBeforeUnmount,onMounted } from "vue";
import { parseTime } from '@/utils/datetime.js';
// 包路径
const package_NdefRecord = 'android.nfc.NdefRecord';
const package_NdefMessage = 'android.nfc.NdefMessage';
const package_TECH_DISCOVERED = 'android.nfc.action.TECH_DISCOVERED';
const package_Intent = 'android.content.Intent';
const package_Activity = 'android.app.Activity';
const package_PendingIntent = 'android.app.PendingIntent';
const package_IntentFilter = 'android.content.IntentFilter';
const package_NfcAdapter = 'android.nfc.NfcAdapter';
const package_Ndef = 'android.nfc.tech.Ndef';
const package_NdefFormatable = 'android.nfc.tech.NdefFormatable';
const package_Parcelable = 'android.os.Parcelable';
const package_String = 'java.lang.String';
// 定义组件 emits
const emit = defineEmits(['close',"changeNfc"]);
let NfcAdapter;
let NdefRecord;
let NdefMessage;
let Uri;
let readyWriteData = false;
let readyRead = false;
let noNFC = false;
let techListsArray = [
['android.nfc.tech.IsoDep'],
['android.nfc.tech.NfcA'],
['android.nfc.tech.NfcB'],
['android.nfc.tech.NfcF'],
['android.nfc.tech.Nfcf'],
['android.nfc.tech.NfcV'],
['android.net.Uri'],
['android.nfc.tech.NdefFormatable'],
['android.nfc.tech.MifareClassi'],
['android.nfc.tech.MifareUltralight']
];
let text = '';// 要写入的数据
let readResult = '';
const isReading=ref(false)
// 状态管理
const formData = ref({
deviceId: "", //录入的id
});
const readStatus=ref(false);//读取状态 true 成功
const nfcResult=ref('')
// onLoad(() => {
// listenNFCStatus();
// })
// 变量初始化
let NfcAdapter = null;
let MifareClassic = null;
let isListening = false;
// 监听NFC状态
const listenNFCStatus=()=>{
console.log("listenNFCStatus=====begin")
try {
let main = plus.android.runtimeMainActivity();
let Intent = plus.android.importClass('android.content.Intent');
let Activity = plus.android.importClass('android.app.Activity');
let PendingIntent = plus.android.importClass('android.app.PendingIntent');
let IntentFilter = plus.android.importClass('android.content.IntentFilter');
Uri = plus.android.importClass('android.net.Uri');
NfcAdapter = plus.android.importClass('android.nfc.NfcAdapter');
let nfcAdapter = NfcAdapter.getDefaultAdapter(main);
if(nfcAdapter == null){
uni.showToast({
title: '设备不支持NFC',
icon: 'none'
})
noNFC = true;
return;
// 检查NFC状态
function open() {
try {
const main = plus.android.runtimeMainActivity();
NfcAdapter = plus.android.importClass("android.nfc.NfcAdapter");
const nfcAdapter = NfcAdapter.getDefaultAdapter(main);
if (!nfcAdapter) {
uni.showToast({
icon: "error",
title: "该设备不支持NFC",
duration: 5000,
});
return;
}
if (!nfcAdapter.isEnabled()) {
uni.showToast({
icon: "error",
title: "NFC功能未打开",
duration: 5000,
});
return;
}
initNFC();
} catch (e) {
console.error("打开NFC失败:", e);
uni.showToast({
icon: "error",
title: "初始化NFC失败",
});
}
}
// 初始化NFC监听
function initNFC() {
try {
const main = plus.android.runtimeMainActivity();
const Intent = plus.android.importClass("android.content.Intent");
const Activity = plus.android.importClass("android.app.Activity");
const PendingIntent = plus.android.importClass("android.app.PendingIntent");
const IntentFilter = plus.android.importClass(
"android.content.IntentFilter"
);
// 导入需要的NFC类
plus.android.importClass("android.nfc.NdefRecord");
plus.android.importClass("android.nfc.NdefMessage");
MifareClassic = plus.android.importClass("android.nfc.tech.MifareClassic");
NfcAdapter = plus.android.importClass("android.nfc.NfcAdapter");
const nfcAdapter = NfcAdapter.getDefaultAdapter(main);
if (!nfcAdapter || !nfcAdapter.isEnabled()) return;
// 创建Intent过滤器
const ndef = new IntentFilter("android.nfc.action.NDEF_DISCOVERED");
const tag = new IntentFilter("android.nfc.action.TAG_DISCOVERED");
const tech = new IntentFilter("android.nfc.action.TECH_DISCOVERED");
const intentFiltersArray = [ndef, tag, tech];
// 技术列表
const techListsArray = [
["android.nfc.tech.Ndef"],
["android.nfc.tech.IsoDep"],
["android.nfc.tech.NfcA"],
["android.nfc.tech.NfcB"],
["android.nfc.tech.NfcF"],
["android.nfc.tech.Nfcf"],
["android.nfc.tech.Nfef"],
["android.nfc.tech.Ndef"],
["android.nfc.tech.NfcV"],
["android.nfc.tech.NdefFormatable"],
["android.nfc.tech.MifareClassi"],
["android.nfc.tech.MifareUltralight"],
];
// 创建PendingIntent
const _intent = new Intent(main, main.getClass());
_intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
const pendingIntent = PendingIntent.getActivity(main, 0, _intent, 0);
// 监听NFC事件
startNFCListener();
// 启用前台调度
nfcAdapter.enableForegroundDispatch(
main,
pendingIntent,
intentFiltersArray,
techListsArray
);
} catch (e) {
console.error("初始化NFC失败:", e);
}
}
// 开始NFC监听
function startNFCListener() {
if (isListening) return;
plus.globalEvent.addEventListener("newintent", handleNewIntent);
isListening = true;
}
// 停止NFC监听
function stopNFCListener() {
if (isListening) {
plus.globalEvent.removeEventListener("newintent", handleNewIntent);
isListening = false;
}
}
// 处理新的NFC意图
function handleNewIntent() {
readNFCData();
stopNFCListener();
}
// 读取NFC数据
function readNFCData() {
try {
const main = plus.android.runtimeMainActivity();
const _intent = main.getIntent();
// 只处理 NDEF 数据
let rawMsgs = _intent.getParcelableArrayExtra(
"android.nfc.extra.NDEF_MESSAGES"
);
if (rawMsgs && rawMsgs.length > 0) {
try {
const records = rawMsgs[0].getRecords();
const result = records[0].getPayload();
// 取 deviceId
function bytesToString(result) {
return String.fromCharCode(...result);
}
if (!nfcAdapter.isEnabled()) {
uni.showToast({
title: '请在系统设置中先启用NFC功能',
icon: 'none'
});
noNFC = true;
return;
}else{
noNFC = false;
}
let intent = new Intent(main, main.getClass());
intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
let pendingIntent = PendingIntent.getActivity(main, 0, intent, 0);
let ndef = new IntentFilter("android.nfc.action.TECH_DISCOVERED");
ndef.addDataType("*/*");
let intentFiltersArray = [ndef];
plus.globalEvent.addEventListener('newintent',function() {
// console.log('newintent running');
// 监听 NFC
setTimeout(nfcRuning(), 1000);
});
plus.globalEvent.addEventListener('pause',function(e) {
// console.log('pause running');
if (nfcAdapter) {
//关闭前台调度系统
//恢复默认状态
nfcAdapter.disableForegroundDispatch(main);
}
});
plus.globalEvent.addEventListener('resume',function(e) {
// console.log('resume running');
if (nfcAdapter) {
//开启前台调度系统
nfcAdapter.enableForegroundDispatch(main, pendingIntent, intentFiltersArray, techListsArray);
}
});
nfcAdapter.enableForegroundDispatch(main, pendingIntent, intentFiltersArray, techListsArray);
} catch (e) {
console.error(e);
formData.value.deviceId = bytesToString(result).substring(3);
emit("changeNfc", formData.value.deviceId);
readStatus.value=true;
} catch (e) {
console.log("读取NDEF数据错误:", e);
uni.showToast({ title: "读取数据失败,请重新读取", icon: "none" });
}
} else {
uni.showToast({ title: "未识别到NFC标签", icon: "none" });
}
} catch (e) {
console.error("读取NFC数据失败:", e);
uni.showToast({ title: "读取数据失败,请重新读取", icon: "none" });
}
}
// 运行nfc
const nfcRuning=()=>{
NdefRecord = plus.android.importClass("android.nfc.NdefRecord");
NdefMessage = plus.android.importClass("android.nfc.NdefMessage");
let main = plus.android.runtimeMainActivity();
let intent = main.getIntent();
console.log("action type:" + intent.getAction());
if (package_TECH_DISCOVERED == intent.getAction()) {
if (readyWriteData) {
write(intent);
readyWriteData = false;
} else if (readyRead) {
read(intent);
readyRead = false;
}
}
}
// 写入nfc
const write=(intent)=>{
try {
toast('请勿移开标签 正在写入...');
console.log("text=" + text);
let textBytes = plus.android.invoke(text, "getBytes");
// image/jpeg text/plain
let uri = Uri.parse(text)
let message = new NdefMessage([new NdefRecord.createUri(uri)]);
console.log("====message===>", message)
let Ndef = plus.android.importClass('android.nfc.tech.Ndef');
let NdefFormatable = plus.android.importClass('android.nfc.tech.NdefFormatable');
let tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
let ndef = Ndef.get(tag);
if (ndef != null) {
// 待写入的数据长度
let size = message.toByteArray().length;
ndef.connect();
if (!ndef.isWritable()) {
toast('tag不允许写入');
return;
}
if (ndef.getMaxSize() < size) {
toast('文件大小超出容量!');
return;
}
ndef.writeNdefMessage(message);
toast('写入数据成功!');
return;
} else {
let format = NdefFormatable.get(tag);
if (format != null) {
try {
format.connect();
format.format(message);
toast('格式化tag并且写入message');
return;
} catch (e) {
toast('格式化tag失败.');
return;
}
} else {
toast('Tag不支持NDEF');
return;
}
}
} catch (e) {
toast('写入失败');
console.log("error=" + e);
}
}
// 读取nfc
const read=(intent)=>{
toast('请勿移开标签正在读取数据');
// NFC id
let bytesId = intent.getByteArrayExtra(NfcAdapter.EXTRA_ID);
let nfc_id = byteArrayToHexString(bytesId);
console.log('nfc_id:', nfc_id);
let Parcelable = plus.android.importClass("android.os.Parcelable");
let rawmsgs = intent.getParcelableArrayExtra("android.nfc.extra.NDEF_MESSAGES");
//let rawmsgs = intent.getParcelableArrayExtra();
// console.log('rawmsgs:', rawmsgs);
if(rawmsgs != null && rawmsgs.length > 0) {
let records = rawmsgs[0].getRecords();
let result = records[0].getPayload();
let data = plus.android.newObject("java.lang.String", result);
console.log('data=>'+data);
readResult = data;
readStatus.value = true;
nfcResult.value = data;
toast('NFC 数据:' + data);
// plus.runtime.openURL(data, function(res) {
// console.log("NFC 数据----", res);
// });
// return data;
}else{
toast('没有读取到数据');
return ""
}
}
// 字节转换
const byteArrayToHexString=(inarray)=>{ // converts byte arrays to string
let i, j, inn;
let hex = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F"];
let out = "";
for(j = 0; j < inarray.length; ++j) {
inn = inarray[j] & 0xff;
i = (inn >>> 4) & 0x0f;
out += hex[i];
i = inn & 0x0f;
out += hex[i];
}
return out;
}
// 正式调用写入
const writeData= ()=>{
if(noNFC){
toast('请检查设备是否支持并开启 NFC 功能!');
return;
}
// 监听事件,触发条件
readyWriteData = true;
toast('请将NFC标签靠近');
}
// 正式调用读
const readData= ()=>{
if(noNFC){
toast('请检查设备是否支持并开启 NFC 功能!');
return;
}
// 监听事件,触发条件
readyRead = true;
isReading.value = true;
readStatus.value = false;
// toast('请将NFC标签靠近');
}
// 提示
const toast = (content)=>{
uni.showToast({
title: content,
icon: 'none'
})
}
// 回调父组件
const emit = defineEmits(['close','confirm'])
const handleClose = ()=>{
emit('close');
}
const handleConfirm= ()=>{
emit('confirm',nfcResult.value);
}
// 暴露方法给父组件
defineExpose({
listenNFCStatus
onMounted(() => {
open();
});
// 组件卸载前清理
onBeforeUnmount(() => {
stopNFCListener();
});
defineExpose({
open
});
</script>
<style lang="scss" scoped>

View File

@@ -26,7 +26,7 @@ const props = defineProps({
})
const isEnlarged = ref(props.visible);
const mediaUrl = ref(props.url);//http://192.168.236.184:9000/718ys-test/polling/2483ee76b28b4d43b87c13386dad90bb.jpg
const mediaUrl = ref(props.url);
// const mediaType = ref('');//类型
// 显示隐藏
watch(() => props.visible, (newVal, oldVal) => {

View File

@@ -4,7 +4,7 @@ export const AGREEWELCOME_KEY="agreewelcome";
// clientId 默认写2
export const CLIENT_ID="2";
// #区分内外网 //1-内网2-外网
export const NETWORK_ENV=1;
export const NETWORK_ENV=2;
// miniIo 参数对象
export const MINIO_KEY="minioKey"

View File

@@ -3,7 +3,7 @@
"name" : "718友晟",
"appid" : "__UNI__4C459F4",
"description" : "",
"versionName" : "1.0.0",
"versionName" : "1.0.2",
"versionCode" : "100",
"transformPx" : false,
/* 5+App */
@@ -61,7 +61,8 @@
"<uses-permission android:name=\"android.permission.BROADCAST_PACKAGE_REPLACED\" />",
"<uses-permission android:name=\"android.permission.RESTART_PACKAGES\" />",
"<uses-permission android:name=\"android.permission.REQUEST_INSTALL_PACKAGES\" />",
"<uses-permission android:name=\"android.permission.NFC\" />"
"<uses-permission android:name=\"android.permission.NFC\"/>",
"<uses-feature android:name=\"android.hardware.nfc\" android:required=\"true\"/>"
],
"abiFilters" : [ "armeabi-v7a", "arm64-v8a" ],
"minSdkVersion" : 23,

View File

@@ -663,12 +663,6 @@
"navigationBarTitleText": ""
}
},
{
"path": "pages/business/polling/nfcTest/nfcTemplate",
"style": {
"navigationBarTitleText": ""
}
},
{
"path": "pages/business/polling/nfcTest/index",
"style": {

View File

@@ -9,11 +9,11 @@
</template>
<script>
import nfc from '@/utils/ouu-nfc.js'
import nfc from './ouu-nfc.js'
export default {
data() {
return {
inputText: 'https://baidu.com'
inputText: 'testtesttesttesttestfdsf'
}
},
onLoad() {

View File

@@ -1,784 +0,0 @@
<template>
<view class="container" @touchstart="touchStart" @touchend="touchEnd">
<view class="header">
<text class="title">NFC卡片读取器</text>
</view>
<view class="content" :class="{ 'landscape-layout': isLandscape }">
<!-- NFC读取按钮 - 平板适配按钮尺寸不小于48px×48px -->
<button class="nfc-button" @click="startNFCReading" :disabled="isReading">
<text v-if="!isReading">开始NFC读取</text>
<view v-else class="loading-wrapper">
<view class="loading-spinner"></view>
<text>正在读取中...</text>
</view>
</button>
<!-- 读取状态提示 -->
<view class="status-section">
<text class="status-text" :class="statusClass">{{ statusMessage }}</text>
</view>
<!-- 读取结果显示区域 -->
<view class="result-section" v-if="nfcResult">
<view class="result-header">
<text class="result-label">NFC ID:</text>
<button class="copy-button" @click="copyToClipboard">复制</button>
</view>
<text class="result-value">{{ nfcResult }}</text>
</view>
<!-- 操作提示 -->
<view class="instruction">
<text class="instruction-title">操作指南:</text>
<text class="instruction-text">1. 确保设备NFC功能已开启</text>
<text class="instruction-text">2. 将NFC卡片贴近设备感应区域</text>
<text class="instruction-text">3. 听到提示音或感受到震动时表示读取成功</text>
</view>
</view>
<!-- 底部信息 -->
<view class="footer">
<text class="footer-text">支持卡片类型: ISO14443A/B, Mifare, NFC-V, FeliCa</text>
</view>
</view>
</template>
<script>
import nfcUtil from "@/utils/hexiii-nfc.js"
export default {
data() {
return {
isReading: false,
statusMessage: '准备就绪',
statusClass: 'status-ready',
nfcResult: '',
statusHistory: [], // 记录状态历史
isLandscape: false, // 是否横屏
screenWidth: 0, // 屏幕宽度
screenHeight: 0, // 屏幕高度
deviceInfo: null, // 设备信息
touchStartTime: 0, // 触摸开始时间
touchStartX: 0, // 触摸开始X坐标
touchStartY: 0 // 触摸开始Y坐标
}
},
onLoad() {
// 获取设备信息
this._getDeviceInfo();
// 页面加载时检查NFC状态
this._checkNFCStatus();
// 添加屏幕旋转监听
uni.onWindowResize(this._onWindowResize);
},
onShow() {
// 页面显示时重新检查NFC状态
this._checkNFCStatus();
},
onHide() {
// 页面隐藏时暂停NFC监听
if (this.isReading) {
nfcUtil.stopNFCListening();
this.isReading = false;
}
},
onUnload() {
// 页面卸载时清理NFC资源
nfcUtil.cleanupNFC();
// 移除屏幕旋转监听
uni.offWindowResize(this._onWindowResize);
},
methods: {
/**
* 获取设备信息
* @private
*/
_getDeviceInfo() {
uni.getSystemInfo({
success: (res) => {
this.deviceInfo = res;
this.screenWidth = res.screenWidth;
this.screenHeight = res.screenHeight;
// 判断是否为横屏
this.isLandscape = res.windowWidth > res.windowHeight;
console.log('设备信息:', res);
console.log('当前屏幕方向:', this.isLandscape ? '横屏' : '竖屏');
}
});
},
/**
* 监听屏幕旋转
* @private
*/
_onWindowResize(res) {
if (res) {
const isLandscape = res.windowWidth > res.windowHeight;
// 屏幕方向改变时更新状态
if (this.isLandscape !== isLandscape) {
this.isLandscape = isLandscape;
console.log('屏幕旋转为:', isLandscape ? '横屏' : '竖屏');
// 屏幕旋转时重新调整布局
this._adjustLayoutForOrientation();
}
}
},
/**
* 根据屏幕方向调整布局
* @private
*/
_adjustLayoutForOrientation() {
// 可以在这里进行特定的布局调整
// 例如:横屏时调整按钮位置、改变元素大小等
// 目前主要通过CSS媒体查询实现这里预留接口
console.log('调整布局适应屏幕方向');
},
/**
* 开始NFC读取
*/
async startNFCReading() {
// 提供触觉反馈 - 平板用户体验增强
this._provideHapticFeedback();
// 设置读取状态
this.isReading = true;
this.statusMessage = '等待NFC卡片...';
this.statusClass = 'status-waiting';
this._addStatusHistory('开始读取NFC');
try {
// 调用NFC读取功能
this._addStatusHistory('正在监听NFC信号...');
const nfcId = await nfcUtil.listenNFCStatus();
// 确保ID有效
if (nfcId) {
// 成功读取,提供触觉反馈
this._provideHapticFeedback('success');
// 播放成功提示音
this._playBeepSound();
// 显示结果并输出到控制台
this.nfcResult = nfcId;
console.log('NFC读取结果:', nfcId);
// 更新状态
this.statusMessage = '读取成功';
this.statusClass = 'status-success';
this._addStatusHistory(`成功读取ID: ${nfcId}`);
// 显示成功提示框
uni.showModal({
title: '读取成功',
content: `NFC ID: ${nfcId}\n\n是否复制到剪贴板`,
showCancel: true,
cancelText: '取消',
confirmText: '复制',
success: (res) => {
if (res.confirm) {
this.copyToClipboard();
}
}
});
} else {
this.statusMessage = '未读取到NFC数据';
this.statusClass = 'status-error';
this._addStatusHistory('未读取到NFC数据');
}
} catch (error) {
console.error('NFC读取错误:', error);
// 错误情况,提供触觉反馈
this._provideHapticFeedback('error');
// 根据错误类型显示不同的提示
let errorMsg = '读取失败';
if (error.message.includes('不支持NFC')) {
errorMsg = '设备不支持NFC功能';
} else if (error.message.includes('未开启')) {
errorMsg = '请先在系统设置中开启NFC功能';
} else if (error.message.includes('超时')) {
errorMsg = '读取超时,请重试';
} else {
errorMsg = '读取失败: ' + error.message;
}
this.statusMessage = errorMsg;
this.statusClass = 'status-error';
this._addStatusHistory(`错误: ${error.message}`);
// 显示错误提示
uni.showModal({
title: '读取失败',
content: errorMsg,
showCancel: false,
confirmText: '确定'
});
} finally {
// 重置读取状态
this.isReading = false;
// 3秒后重置状态消息
setTimeout(() => {
if (!this.isReading) {
this.statusMessage = '准备就绪';
this.statusClass = 'status-ready';
}
}, 3000);
}
},
/**
* 复制NFC ID到剪贴板
*/
copyToClipboard() {
if (this.nfcResult) {
uni.setClipboardData({
data: this.nfcResult,
success: () => {
uni.showToast({
title: '已复制到剪贴板',
icon: 'success',
duration: 2000
});
}
});
}
},
/**
* 检查NFC状态
* @private
*/
_checkNFCStatus() {
try {
// 尝试初始化NFC适配器检查状态
const main = plus.android.runtimeMainActivity();
const NfcAdapter = plus.android.importClass('android.nfc.NfcAdapter');
const nfcAdapter = NfcAdapter.getDefaultAdapter(main);
if (nfcAdapter == null) {
this.statusMessage = '设备不支持NFC功能';
this.statusClass = 'status-error';
} else if (!nfcAdapter.isEnabled()) {
this.statusMessage = 'NFC功能未开启请在系统设置中开启';
this.statusClass = 'status-warning';
}
} catch (e) {
console.log('NFC状态检查失败可能在非Android环境');
}
},
/**
* 提供触觉反馈
* @private
* @param {string} type - 反馈类型: 'success', 'error'或默认
*/
_provideHapticFeedback(type = 'default') {
try {
// 在Android设备上提供震动反馈
const VIBRATOR_SERVICE = 'vibrator';
const Context = plus.android.importClass('android.content.Context');
const main = plus.android.runtimeMainActivity();
const vibrator = main.getSystemService(VIBRATOR_SERVICE);
// 根据类型设置不同的震动模式
if (vibrator) {
if (type === 'success') {
// 成功震动模式: 短震动
vibrator.vibrate(100);
} else if (type === 'error') {
// 错误震动模式: 长震动
vibrator.vibrate(300);
} else {
// 默认震动模式: 轻微震动
vibrator.vibrate(50);
}
}
} catch (e) {
// 如果设备不支持震动或没有权限,忽略错误
console.log('无法提供震动反馈:', e.message);
}
},
/**
* 播放提示音
* @private
*/
_playBeepSound() {
try {
// 使用系统默认提示音
const AudioManager = plus.android.importClass('android.media.AudioManager');
const ToneGenerator = plus.android.importClass('android.media.ToneGenerator');
const main = plus.android.runtimeMainActivity();
const toneGen = new ToneGenerator(AudioManager.STREAM_NOTIFICATION, 100);
toneGen.startTone(ToneGenerator.TONE_PROP_BEEP);
} catch (e) {
// 如果无法播放提示音,忽略错误
console.log('无法播放提示音:', e.message);
}
},
/**
* 添加状态历史记录
* @private
* @param {string} message - 状态消息
*/
_addStatusHistory(message) {
const timestamp = new Date().toLocaleTimeString();
this.statusHistory.push(`[${timestamp}] ${message}`);
// 只保留最近10条记录
if (this.statusHistory.length > 10) {
this.statusHistory.shift();
}
console.log(`[NFC状态] ${message}`);
},
/**
* 触摸开始事件处理(用于长按等手势识别)
* @private
*/
touchStart(event) {
this.touchStartTime = Date.now();
this.touchStartX = event.touches[0].clientX;
this.touchStartY = event.touches[0].clientY;
},
/**
* 触摸结束事件处理(用于长按等手势识别)
* @private
*/
touchEnd(event) {
// 可以在这里添加手势识别逻辑
// 例如:长按识别、滑动检测等
const touchEndTime = Date.now();
const touchEndX = event.changedTouches[0].clientX;
const touchEndY = event.changedTouches[0].clientY;
// 计算触摸时间和移动距离
const touchDuration = touchEndTime - this.touchStartTime;
const distanceX = Math.abs(touchEndX - this.touchStartX);
const distanceY = Math.abs(touchEndY - this.touchStartY);
// 长按检测超过500ms且移动距离小于10px
if (touchDuration > 500 && distanceX < 10 && distanceY < 10) {
console.log('长按操作');
// 长按可以触发特殊功能,如显示详细信息
}
},
}
}
</script>
<style scoped>
.container {
display: flex;
flex-direction: column;
min-height: 100vh;
padding: 32rpx;
background-color: #f5f5f5;
/* 增加背景纹理,提升平板视觉体验 */
background-image: linear-gradient(45deg, rgba(0, 0, 0, 0.02) 25%, transparent 25%, transparent 75%, rgba(0, 0, 0, 0.02) 75%, rgba(0, 0, 0, 0.02)),
linear-gradient(45deg, rgba(0, 0, 0, 0.02) 25%, transparent 25%, transparent 75%, rgba(0, 0, 0, 0.02) 75%, rgba(0, 0, 0, 0.02));
background-size: 200rpx 200rpx;
background-position: 0 0, 100rpx 100rpx;
}
/* 横屏布局 */
.content.landscape-layout {
/* 横屏时优化内容区布局 */
max-width: 960rpx;
margin: 0 auto;
/* 为横屏模式添加微妙的阴影效果 */
box-shadow: 0 0 20rpx rgba(0, 0, 0, 0.05);
background-color: rgba(255, 255, 255, 0.7);
border-radius: 16rpx;
padding: 40rpx;
backdrop-filter: blur(5rpx);
}
.header {
text-align: center;
margin-bottom: 48rpx;
padding: 20rpx 0;
}
.title {
font-size: 48rpx;
font-weight: bold;
color: #212121;
/* 平板适配:增加文字阴影提升可读性 */
text-shadow: 0 2rpx 4rpx rgba(0, 0, 0, 0.1);
}
.content {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
flex: 1;
padding: 32rpx;
}
/* NFC按钮 - 平板适配按钮高度不小于60px */
.nfc-button {
width: 600rpx;
height: 120rpx;
line-height: 120rpx;
font-size: 36rpx;
background-color: #1976D2;
color: white;
border-radius: 8rpx;
margin-bottom: 48rpx;
display: flex;
align-items: center;
justify-content: center;
/* 平板适配:增加按钮阴影和触摸反馈 */
box-shadow: 0 4rpx 12rpx rgba(25, 118, 210, 0.3);
transition: all 0.3s ease;
position: relative;
overflow: hidden;
}
/* 水波纹效果 - 符合Android设计规范 */
.nfc-button::after {
content: '';
position: absolute;
top: 50%;
left: 50%;
width: 0;
height: 0;
border-radius: 50%;
background-color: rgba(255, 255, 255, 0.3);
transform: translate(-50%, -50%);
transition: width 0.6s, height 0.6s;
}
.nfc-button:active::after {
width: 120%;
height: 120%;
}
.nfc-button:active {
background-color: #1565C0;
transform: translateY(2rpx);
box-shadow: 0 2rpx 6rpx rgba(25, 118, 210, 0.3);
}
.nfc-button[disabled] {
background-color: #90CAF9;
box-shadow: none;
}
/* 加载动画 */
.loading-wrapper {
display: flex;
align-items: center;
justify-content: center;
gap: 16rpx;
}
.loading-spinner {
width: 32rpx;
height: 32rpx;
border: 4rpx solid rgba(255, 255, 255, 0.3);
border-top: 4rpx solid white;
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.status-section {
margin-bottom: 32rpx;
padding: 24rpx 32rpx;
border-radius: 8rpx;
background-color: white;
min-width: 500rpx;
text-align: center;
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.08);
transition: all 0.3s ease;
}
.status-text {
font-size: 32rpx;
font-weight: 500;
transition: color 0.3s ease;
}
.status-ready {
color: #616161;
}
.status-waiting {
color: #1976D2;
animation: pulse 2s infinite;
}
@keyframes pulse {
0% { opacity: 1; }
50% { opacity: 0.7; }
100% { opacity: 1; }
}
.status-success {
color: #4CAF50;
}
.status-error {
color: #F44336;
}
.status-warning {
color: #FF9800;
}
.result-section {
background-color: white;
padding: 32rpx;
border-radius: 8rpx;
margin-bottom: 32rpx;
min-width: 500rpx;
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.08);
transition: all 0.3s ease;
}
.result-section:hover {
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.12);
}
.result-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16rpx;
}
.result-label {
font-size: 28rpx;
color: #757575;
font-weight: 500;
}
.copy-button {
min-width: 120rpx;
height: 60rpx;
line-height: 60rpx;
font-size: 24rpx;
background-color: #E3F2FD;
color: #1976D2;
border-radius: 6rpx;
}
.copy-button:active {
background-color: #BBDEFB;
}
.result-value {
font-size: 36rpx;
font-family: monospace;
color: #212121;
word-break: break-all;
line-height: 1.5;
padding: 16rpx;
background-color: #FAFAFA;
border-radius: 6rpx;
border: 1rpx solid #EEEEEE;
}
.instruction {
margin-top: 32rpx;
background-color: white;
padding: 32rpx;
border-radius: 8rpx;
min-width: 500rpx;
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.08);
}
.instruction-title {
font-size: 32rpx;
font-weight: bold;
color: #212121;
margin-bottom: 16rpx;
display: block;
}
.instruction-text {
font-size: 28rpx;
color: #757575;
line-height: 1.6;
display: block;
margin-bottom: 12rpx;
}
.footer {
margin-top: 40rpx;
padding: 24rpx;
text-align: center;
}
.footer-text {
font-size: 24rpx;
color: #9E9E9E;
}
/* 平板横屏适配 */
@media screen and (orientation: landscape) {
/* 横屏时优化元素排布 */
.container {
padding: 24rpx;
}
.header {
margin-bottom: 32rpx;
}
/* 在横屏模式下,核心操作区放在右侧,便于右手操作 */
.content {
display: flex;
flex-direction: column;
align-items: center;
justify-content: flex-start;
padding-top: 40rpx;
min-height: calc(100vh - 100rpx);
}
/* 横屏时增大按钮点击区域 */
.nfc-button {
width: 500rpx;
height: 130rpx;
line-height: 130rpx;
/* 放在屏幕右侧,便于右手操作 */
align-self: flex-end;
margin-right: 60rpx;
}
/* 状态区域也放在右侧 */
.status-section {
min-width: 600rpx;
align-self: flex-end;
margin-right: 60rpx;
}
/* 结果区域在横屏时更宽,方便查看完整信息 */
.result-section {
min-width: 800rpx;
margin: 32rpx auto;
}
/* 操作指南放在左侧,不干扰核心操作区 */
.instruction {
position: absolute;
left: 40rpx;
top: 180rpx;
min-width: 400rpx;
max-width: 450rpx;
padding: 24rpx;
background-color: rgba(255, 255, 255, 0.95);
}
/* 底部信息在横屏时放在左侧底部 */
.footer {
position: absolute;
left: 40rpx;
bottom: 30rpx;
text-align: left;
}
}
/* 安卓平板特殊适配 */
@media (min-width: 768px) and (min-height: 500px) {
/* 优化触控体验确保所有可点击元素至少有48px x 48px的点击区域 */
.nfc-button {
min-height: 120rpx; /* 约等于48px在rpx中的值 */
min-width: 300rpx;
touch-action: manipulation; /* 防止双击缩放,提高点击响应速度 */
}
.copy-button {
min-height: 80rpx;
min-width: 160rpx;
touch-action: manipulation;
}
/* 平板上增大字体和间距,提升可读性 */
.title {
letter-spacing: 2rpx;
}
.status-text {
letter-spacing: 1rpx;
}
.result-value {
letter-spacing: 2rpx;
font-size: 42rpx; /* 在平板上进一步增大ID显示字体 */
padding: 24rpx;
}
}
/* 大型平板适配10英寸以上 */
@media (min-width: 1024px) {
.container {
padding: 48rpx;
}
.title {
font-size: 64rpx;
}
.nfc-button {
width: 700rpx;
height: 150rpx;
line-height: 150rpx;
font-size: 44rpx;
}
.status-section,
.result-section {
min-width: 900rpx;
padding: 40rpx;
}
/* 大型平板上,优化内容区的最大宽度,避免内容过宽影响阅读 */
.content {
max-width: 70vw;
margin: 0 auto;
}
}
/* 适配不同尺寸的平板 */
@media screen and (min-width: 768px) {
.title {
font-size: 56rpx;
}
.nfc-button {
width: 700rpx;
height: 140rpx;
line-height: 140rpx;
font-size: 40rpx;
}
.status-text {
font-size: 36rpx;
}
.result-value {
font-size: 40rpx;
}
}
</style>

View File

@@ -94,10 +94,20 @@
<view>{{ item.pointName }}</view>
</view>
<!-- #ifdef APP-PLUS -->
<view class="r-right" @click.stop="initNFC(item,index)">
<view class="r-right r-active" v-if="item.resultContent" @click.stop="initNFC(item,index)">
<img :src="'static/images/polling/icon-NFCcode-b.png'" class="img-nfc" />
<view>识别成功<view class="r-font">点击再次识别</view></view>
</view>
<view class="r-right" v-else @click.stop="initNFC(item,index)">
<img :src="'static/images/polling/icon-NFCcode.png'" class="img-nfc" /> 开始识别
</view>
<!-- #endif -->
<!-- #ifndef APP-PLUS -->
<view class="r-right r-active">
<img :src="'static/images/polling/icon-NFCcode-b.png'" class="img-nfc" />
<view>识别成功</view>
</view>
<!-- #endif -->
</view>
</block>
<!-- 扫码 -->
@@ -204,17 +214,21 @@
@close="handlePreviewClose"
></mediaPreview>
<!-- NFC 弹窗 -->
<NFCTemplate v-if="nfcShow" ref="nfcTemplateRef"
@close="nfcClose"
@confirm="nfcConfirm"
></NFCTemplate>
<!-- NFC 数据读取 -->
<NFCTemplate
v-if="nfcShow"
ref="nfcReaderRef"
@changeNfc="handleNfcData"
@close="nfcClose"
/>
</view>
</template>
<script setup>
import { ref,onMounted,onUnmounted,nextTick,computed,reactive,getCurrentInstance } from 'vue'
import { onLoad,onHide} from '@dcloudio/uni-app';
import { MINIO_KEY } from '@/enums/cacheEnums';
import customHeader from '@/components/customHeader.vue';
import MescrollUni from 'mescroll-uni/mescroll-uni.vue';
import multipleSelect from "@/components/multipleSelect.vue";
@@ -222,6 +236,7 @@ import pollingShowModal from "@/components/pollingShowModal.vue";
import customShowModal from "@/components/customShowModal.vue"
import mediaPreview from "@/components/mediaPreview.vue"
import NFCTemplate from "@/components/NFCTemplate.vue"
import { parseTime } from '@/utils/datetime.js';
import { formatTaskStatus } from '@/utils/status.js';
import { taskGroupDetail,submitResult,minioUpload } from '@/api/polling.js'
@@ -370,7 +385,11 @@ const chooseImage = (item) => {
minioUpload(param).then(res=>{
let data = res.data;
// console.log("444图片上传成功=>",data)
imgArr2.value.push(data.fileUrl)
// imgArr2.value.push(data.fileUrl)
imgArr2.value.push({
shortUrl:minioObj.minioThumbUrl +"/"+data.fileName,
url:minioObj.minioUrl +"/"+data.fileName,
})
imgArr.value.push(data.fileName)//传给后台的路径
item.resultContent = imgArr.value.join(",")
})
@@ -432,7 +451,7 @@ let isVisible= ref(false);//放大处理
let mediaUrl= ref('');//放大地址
let videoShow = ref(true);
const showMediaPreview=(item)=>{
console.log("showMediaPreview===")
// console.log("showMediaPreview===")
isVisible.value = true;
videoShow.value = false;
mediaUrl.value = item.url
@@ -443,10 +462,8 @@ const handlePreviewClose=()=>{
}
// nfc 处理
let cardUID = ref('');
let cardValue = ref('');
let nfcShow = ref(false);
const nfcTemplateRef = ref(null);
const nfcReaderRef = ref(null);
// optionObj.pointList
let nfcIndex = ref(0);
const initNFC = async(item,index) => {
@@ -455,19 +472,22 @@ const initNFC = async(item,index) => {
// uni.navigateTo({url:'/pages/business/polling/nfcTest/index'})
// #ifdef APP-PLUS
setTimeout(()=>{
// console.log("nfcTemplateRef==",nfcTemplateRef.value)
if (nfcTemplateRef.value) {
nfcTemplateRef.value.listenNFCStatus();
if(nfcReaderRef.value){
nfcReaderRef.value.open();
}
},500)
},50)
// #endif
}
const nfcClose = async(item) => {
nfcShow.value = false;
}
const nfcConfirm=(result)=>{
console.log("confirm=>",result)
optionObj.pointList[nfcIndex].resultContent = result
const handleNfcData=(data)=>{
console.log("NFC数据:", data);
console.log("NFC数据1111:", optionObj.value.pointList,nfcIndex.value)
optionObj.value.pointList[nfcIndex.value].resultContent = data;
console.log("NFC数据1111:", optionObj.value.pointList[nfcIndex.value])
// nfcShow.value = false;
}
// 扫二维码

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1 +1 @@
{"@platforms":["android","iPhone","iPad"],"id":"__UNI__4C459F4","name":"718友晟","version":{"name":"1.0.0","code":"100"},"description":"","developer":{"name":"","email":"","url":""},"permissions":{"Push":{},"Camera":{},"Barcode":{},"VideoPlayer":{},"UniNView":{"description":"UniNView原生渲染"}},"plus":{"useragent":{"value":"uni-app","concatenate":true},"splashscreen":{"target":"id:1","autoclose":true,"waiting":false,"delay":0},"popGesture":"close","launchwebview":{"id":"1","kernel":"WKWebview"},"compatible":{"ignoreVersion":true},"usingComponents":true,"nvueStyleCompiler":"uni-app","compilerVersion":3,"nativePlugins":{"Tm-TmSafeSaveFileModule":{"__plugin_info__":{"name":"TmSafeSaveFileModule隐私数据保存","description":"TmSafeSaveFileModule隐私数据保存","platforms":"Android","url":"","android_package_name":"","ios_bundle_id":"","isCloud":false,"bought":-1,"pid":"","parameters":{}}}},"statusbar":{"immersed":"supportedDevice","style":"dark","background":"#F8F8F8"},"uniStatistics":{"enable":false},"allowsInlineMediaPlayback":true,"safearea":{"background":"#000000","bottom":{"offset":"auto"}},"uni-app":{"control":"uni-v3","vueVersion":"3","compilerVersion":"4.76","nvueCompiler":"uni-app","renderer":"auto","nvue":{"flex-direction":"column"},"nvueLaunchMode":"normal","webView":{"minUserAgentVersion":"49.0"}},"tabBar":{"position":"bottom","color":"#919191","selectedColor":"#ffffff","borderStyle":"#ffffff","blurEffect":"none","fontSize":"12px","iconWidth":"24px","spacing":"3px","height":"50px","list":[{"pagePath":"pages/home/home","iconPath":"/static/images/tabs/menu-home.png","selectedIconPath":"/static/images/tabs/menu-home-on.png","text":"首页"},{"pagePath":"pages/business/business","iconPath":"/static/images/tabs/menu-business.png","selectedIconPath":"/static/images/tabs/menu-business-on.png","text":"业务中心"},{"pagePath":"pages/notice/notice","iconPath":"/static/images/tabs/menu-info.png","selectedIconPath":"/static/images/tabs/menu-info-on.png","text":"消息"},{"pagePath":"pages/userinfo/userinfo","iconPath":"/static/images/tabs/menu-me.png","selectedIconPath":"/static/images/tabs/menu-me-on.png","text":"我的"}],"backgroundColor":"#000000","selectedIndex":0,"shown":true}},"app-harmony":{"useragent":{"value":"uni-app","concatenate":true},"uniStatistics":{"enable":false},"safearea":{"background":"#000000","bottom":{"offset":"auto"}}},"launch_path":"__uniappview.html"}
{"@platforms":["android","iPhone","iPad"],"id":"__UNI__4C459F4","name":"718友晟","version":{"name":"1.0.2","code":"100"},"description":"","developer":{"name":"","email":"","url":""},"permissions":{"Push":{},"Camera":{},"Barcode":{},"VideoPlayer":{},"UniNView":{"description":"UniNView原生渲染"}},"plus":{"useragent":{"value":"uni-app","concatenate":true},"splashscreen":{"target":"id:1","autoclose":true,"waiting":false,"delay":0},"popGesture":"close","launchwebview":{"id":"1","kernel":"WKWebview"},"compatible":{"ignoreVersion":true},"usingComponents":true,"nvueStyleCompiler":"uni-app","compilerVersion":3,"nativePlugins":{"Tm-TmSafeSaveFileModule":{"__plugin_info__":{"name":"TmSafeSaveFileModule隐私数据保存","description":"TmSafeSaveFileModule隐私数据保存","platforms":"Android","url":"","android_package_name":"","ios_bundle_id":"","isCloud":false,"bought":-1,"pid":"","parameters":{}}}},"statusbar":{"immersed":"supportedDevice","style":"dark","background":"#F8F8F8"},"uniStatistics":{"enable":false},"allowsInlineMediaPlayback":true,"safearea":{"background":"#000000","bottom":{"offset":"auto"}},"uni-app":{"control":"uni-v3","vueVersion":"3","compilerVersion":"4.76","nvueCompiler":"uni-app","renderer":"auto","nvue":{"flex-direction":"column"},"nvueLaunchMode":"normal","webView":{"minUserAgentVersion":"49.0"}},"tabBar":{"position":"bottom","color":"#919191","selectedColor":"#ffffff","borderStyle":"#ffffff","blurEffect":"none","fontSize":"12px","iconWidth":"24px","spacing":"3px","height":"50px","list":[{"pagePath":"pages/home/home","iconPath":"/static/images/tabs/menu-home.png","selectedIconPath":"/static/images/tabs/menu-home-on.png","text":"首页"},{"pagePath":"pages/business/business","iconPath":"/static/images/tabs/menu-business.png","selectedIconPath":"/static/images/tabs/menu-business-on.png","text":"业务中心"},{"pagePath":"pages/notice/notice","iconPath":"/static/images/tabs/menu-info.png","selectedIconPath":"/static/images/tabs/menu-info-on.png","text":"消息"},{"pagePath":"pages/userinfo/userinfo","iconPath":"/static/images/tabs/menu-me.png","selectedIconPath":"/static/images/tabs/menu-me-on.png","text":"我的"}],"backgroundColor":"#000000","selectedIndex":0,"shown":true}},"app-harmony":{"useragent":{"value":"uni-app","concatenate":true},"uniStatistics":{"enable":false},"safearea":{"background":"#000000","bottom":{"offset":"auto"}}},"launch_path":"__uniappview.html"}

View File

@@ -1 +1 @@
.container[data-v-44de363d]{padding:15px;box-sizing:border-box}.container .iframe[data-v-44de363d]{height:100vh;width:100vw;border:none;margin:0}.container .input-box[data-v-44de363d]{border:1px solid #aaa;margin:30px 0;padding:10px;border-radius:100px}.container .btn-box[data-v-44de363d]{display:flex;justify-content:space-between;align-items:center}.container .btn-box .btn-write[data-v-44de363d]{width:100%;height:100px;display:flex;align-items:center;justify-content:center;font-size:20px}.container .btn-box .btn-read[data-v-44de363d]{width:100%;margin-left:20px;height:100px;background-color:#1ee176;color:#fff;display:flex;align-items:center;justify-content:center;font-size:20px}
.container[data-v-9948bfd6]{padding:15px;box-sizing:border-box}.container .iframe[data-v-9948bfd6]{height:100vh;width:100vw;border:none;margin:0}.container .input-box[data-v-9948bfd6]{border:1px solid #aaa;margin:30px 0;padding:10px;border-radius:100px}.container .btn-box[data-v-9948bfd6]{display:flex;justify-content:space-between;align-items:center}.container .btn-box .btn-write[data-v-9948bfd6]{width:100%;height:100px;display:flex;align-items:center;justify-content:center;font-size:20px}.container .btn-box .btn-read[data-v-9948bfd6]{width:100%;margin-left:20px;height:100px;background-color:#1ee176;color:#fff;display:flex;align-items:center;justify-content:center;font-size:20px}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long