连接nfc开发中ing
This commit is contained in:
parent
80d166d8c1
commit
0dc18eb8eb
|
|
@ -110,3 +110,17 @@ export function getDeceasedList(params) {
|
||||||
showLoading: false,
|
showLoading: false,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 提交 NFC 绑定信息
|
||||||
|
* @param {Object} data
|
||||||
|
* @param {string} data.memorialMac - 往生殿设备 MAC
|
||||||
|
* @param {string} data.nfcMac - NFC 卡片 MAC
|
||||||
|
* @param {string} [data.unitId] - 单元 ID
|
||||||
|
* @returns {Promise}
|
||||||
|
*/
|
||||||
|
export function bindNfcCard(data) {
|
||||||
|
return post("/bst/nfc", data, {
|
||||||
|
showLoading: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -257,6 +257,13 @@
|
||||||
"navigationStyle": "custom"
|
"navigationStyle": "custom"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
,
|
||||||
|
{
|
||||||
|
"path": "pages/memorial/nfcPairing",
|
||||||
|
"style": {
|
||||||
|
"navigationStyle": "custom"
|
||||||
|
}
|
||||||
|
}
|
||||||
],
|
],
|
||||||
"subPackages": [
|
"subPackages": [
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,7 @@
|
||||||
<view class="grid-btn" @click="handleResetDuration">时长归零</view>
|
<view class="grid-btn" @click="handleResetDuration">时长归零</view>
|
||||||
<view class="grid-btn" @click="handleIncreaseDuration">增加时长</view>
|
<view class="grid-btn" @click="handleIncreaseDuration">增加时长</view>
|
||||||
<view class="grid-btn" @click="handleSearchMemorial">寻找牌位</view>
|
<view class="grid-btn" @click="handleSearchMemorial">寻找牌位</view>
|
||||||
|
<view class="grid-btn" @click="handleNfcPairing">NFC 配对</view>
|
||||||
<view class="grid-btn add-memorial-btn" @click="handleAddMemorial"
|
<view class="grid-btn add-memorial-btn" @click="handleAddMemorial"
|
||||||
>添加牌位
|
>添加牌位
|
||||||
</view>
|
</view>
|
||||||
|
|
@ -473,6 +474,25 @@ export default {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// 跳转到NFC配对页面
|
||||||
|
handleNfcPairing() {
|
||||||
|
if (!this.ensureUnitSelected()) return;
|
||||||
|
const unitId = this.selectedUnitId;
|
||||||
|
uni.navigateTo({
|
||||||
|
url: `/pages/memorial/nfcPairing?unitId=${unitId}`,
|
||||||
|
success: () => {
|
||||||
|
console.log("跳转到NFC配对页面成功");
|
||||||
|
},
|
||||||
|
fail: (error) => {
|
||||||
|
console.error("跳转失败:", error);
|
||||||
|
uni.showToast({
|
||||||
|
title: "页面跳转失败",
|
||||||
|
icon: "none",
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
464
pages/memorial/nfcPairing.vue
Normal file
464
pages/memorial/nfcPairing.vue
Normal file
|
|
@ -0,0 +1,464 @@
|
||||||
|
<template>
|
||||||
|
<view class="page">
|
||||||
|
<base-background />
|
||||||
|
<custom-navbar title="NFC配对" />
|
||||||
|
|
||||||
|
<view class="content">
|
||||||
|
<view class="status-card">
|
||||||
|
<view class="status-header">
|
||||||
|
<view :class="['status-dot', socketConnected ? 'online' : 'offline']" />
|
||||||
|
<text class="status-title">{{ connectionText }}</text>
|
||||||
|
</view>
|
||||||
|
<text class="status-desc">
|
||||||
|
请保持手机在线,等待刷卡设备将NFC卡号传递到本页面。
|
||||||
|
</text>
|
||||||
|
<view class="card-box" :class="{ ready: !!cardNo }">
|
||||||
|
<text class="card-label">NFC卡号</text>
|
||||||
|
<text class="card-value">{{ cardNo || "等待刷卡..." }}</text>
|
||||||
|
</view>
|
||||||
|
<view v-if="connectionError" class="error-text">{{ connectionError }}</view>
|
||||||
|
<view v-else-if="lastMessage" class="hint-text">{{ lastMessage }}</view>
|
||||||
|
<view class="status-actions">
|
||||||
|
<view class="text-btn" @click="handleRetry">重新连接</view>
|
||||||
|
<view class="text-btn" @click="resetCard" v-if="cardNo">清空卡号</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="form-card">
|
||||||
|
<view class="field">
|
||||||
|
<text class="label">设备 MAC 地址</text>
|
||||||
|
<input
|
||||||
|
v-model.trim="deviceMac"
|
||||||
|
class="input"
|
||||||
|
maxlength="32"
|
||||||
|
placeholder="请输入设备 MAC"
|
||||||
|
placeholder-class="placeholder"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
<view class="field">
|
||||||
|
<text class="label">NFC 卡号</text>
|
||||||
|
<input
|
||||||
|
v-model="cardNo"
|
||||||
|
class="input"
|
||||||
|
disabled
|
||||||
|
placeholder="等待刷卡"
|
||||||
|
placeholder-class="placeholder"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view v-if="unitId" class="field readonly">
|
||||||
|
<text class="label">绑定单元 ID</text>
|
||||||
|
<text class="unit-value">{{ unitId }}</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view
|
||||||
|
:class="['primary-btn', { disabled: !canSubmit || binding }]"
|
||||||
|
@click="handleBind"
|
||||||
|
>
|
||||||
|
{{ binding ? "提交中..." : "提交绑定" }}
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import BaseBackground from "@/components/base-background/base-background.vue";
|
||||||
|
import CustomNavbar from "@/components/custom-navbar/custom-navbar.vue";
|
||||||
|
import { getRequestConfig } from "@/utils/request.js";
|
||||||
|
import { bindNfcCard } from "@/api/memorial/index.js";
|
||||||
|
|
||||||
|
const NFC_WS_PATH = "/ws/nfc/swipeCard";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
BaseBackground,
|
||||||
|
CustomNavbar,
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
unitId: "",
|
||||||
|
socketTask: null,
|
||||||
|
socketConnected: false,
|
||||||
|
deviceMac: "",
|
||||||
|
cardNo: "",
|
||||||
|
binding: false,
|
||||||
|
connectionError: "",
|
||||||
|
lastMessage: "",
|
||||||
|
usingGlobalSocketEvents: false,
|
||||||
|
globalSocketHandlers: null,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
canSubmit() {
|
||||||
|
return !!this.deviceMac && !!this.cardNo;
|
||||||
|
},
|
||||||
|
connectionText() {
|
||||||
|
if (this.connectionError) {
|
||||||
|
return "连接异常";
|
||||||
|
}
|
||||||
|
return this.socketConnected ? "已连接,等待刷卡" : "连接中...";
|
||||||
|
},
|
||||||
|
},
|
||||||
|
onLoad(options = {}) {
|
||||||
|
if (options.unitId) {
|
||||||
|
this.unitId = options.unitId;
|
||||||
|
}
|
||||||
|
if (options.mac) {
|
||||||
|
this.deviceMac = options.mac;
|
||||||
|
}
|
||||||
|
this.initSocket();
|
||||||
|
},
|
||||||
|
onUnload() {
|
||||||
|
this.cleanupSocket();
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
buildSocketUrl() {
|
||||||
|
try {
|
||||||
|
const { baseUrl } = getRequestConfig();
|
||||||
|
if (!baseUrl) return "";
|
||||||
|
const protocol = baseUrl.startsWith("https") ? "wss" : "ws";
|
||||||
|
const host = baseUrl.replace(/^https?:\/\//, "").replace(/\/$/, "");
|
||||||
|
const query = this.unitId ? `?unitId=${this.unitId}` : "";
|
||||||
|
return `${protocol}://${host}${NFC_WS_PATH}${query}`;
|
||||||
|
} catch (error) {
|
||||||
|
console.error("构建WebSocket地址失败", error);
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
},
|
||||||
|
initSocket() {
|
||||||
|
this.connectionError = "";
|
||||||
|
this.lastMessage = "";
|
||||||
|
const url = this.buildSocketUrl();
|
||||||
|
if (!url) {
|
||||||
|
this.connectionError = "缺少WebSocket地址,请检查配置";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.cleanupSocket();
|
||||||
|
console.log("NFC配对页面发起WebSocket连接:", url);
|
||||||
|
this.socketTask = uni.connectSocket({ url });
|
||||||
|
if (!this.socketTask) {
|
||||||
|
this.connectionError = "当前环境不支持WebSocket";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (typeof this.socketTask.onOpen === "function") {
|
||||||
|
this.bindTaskSocketEvents();
|
||||||
|
} else {
|
||||||
|
this.bindGlobalSocketEvents();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
cleanupSocket() {
|
||||||
|
if (this.socketTask && typeof this.socketTask.close === "function") {
|
||||||
|
try {
|
||||||
|
this.socketTask.close();
|
||||||
|
} catch (error) {
|
||||||
|
console.warn("关闭WebSocket失败", error);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
uni.closeSocket && uni.closeSocket({});
|
||||||
|
}
|
||||||
|
this.unbindGlobalSocketEvents();
|
||||||
|
this.socketTask = null;
|
||||||
|
this.socketConnected = false;
|
||||||
|
},
|
||||||
|
bindTaskSocketEvents() {
|
||||||
|
if (!this.socketTask) return;
|
||||||
|
this.socketTask.onOpen(() => {
|
||||||
|
console.log("NFC WebSocket 已连接");
|
||||||
|
this.socketConnected = true;
|
||||||
|
this.connectionError = "";
|
||||||
|
});
|
||||||
|
this.socketTask.onClose((event) => {
|
||||||
|
console.warn("NFC WebSocket 连接关闭", event);
|
||||||
|
this.socketConnected = false;
|
||||||
|
this.socketTask = null;
|
||||||
|
});
|
||||||
|
this.socketTask.onError((error) => {
|
||||||
|
console.error("NFC WebSocket 错误", error);
|
||||||
|
this.connectionError = "连接失败,请重试";
|
||||||
|
this.socketConnected = false;
|
||||||
|
});
|
||||||
|
this.socketTask.onMessage((event) => {
|
||||||
|
this.handleSocketMessage(event);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
bindGlobalSocketEvents() {
|
||||||
|
if (this.usingGlobalSocketEvents) return;
|
||||||
|
this.usingGlobalSocketEvents = true;
|
||||||
|
this.globalSocketHandlers = {
|
||||||
|
open: () => {
|
||||||
|
console.log("NFC WebSocket 已连接 (global handler)");
|
||||||
|
this.socketConnected = true;
|
||||||
|
this.connectionError = "";
|
||||||
|
},
|
||||||
|
close: (event) => {
|
||||||
|
console.warn("NFC WebSocket 连接关闭", event);
|
||||||
|
this.socketConnected = false;
|
||||||
|
},
|
||||||
|
error: (error) => {
|
||||||
|
console.error("NFC WebSocket 错误", error);
|
||||||
|
this.connectionError = "连接失败,请重试";
|
||||||
|
this.socketConnected = false;
|
||||||
|
},
|
||||||
|
message: (event) => {
|
||||||
|
this.handleSocketMessage(event);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
uni.onSocketOpen && uni.onSocketOpen(this.globalSocketHandlers.open);
|
||||||
|
uni.onSocketClose && uni.onSocketClose(this.globalSocketHandlers.close);
|
||||||
|
uni.onSocketError && uni.onSocketError(this.globalSocketHandlers.error);
|
||||||
|
uni.onSocketMessage &&
|
||||||
|
uni.onSocketMessage(this.globalSocketHandlers.message);
|
||||||
|
},
|
||||||
|
unbindGlobalSocketEvents() {
|
||||||
|
if (!this.usingGlobalSocketEvents || !this.globalSocketHandlers) return;
|
||||||
|
const { open, close, error, message } = this.globalSocketHandlers;
|
||||||
|
uni.offSocketOpen && open && uni.offSocketOpen(open);
|
||||||
|
uni.offSocketClose && close && uni.offSocketClose(close);
|
||||||
|
uni.offSocketError && error && uni.offSocketError(error);
|
||||||
|
uni.offSocketMessage && message && uni.offSocketMessage(message);
|
||||||
|
this.usingGlobalSocketEvents = false;
|
||||||
|
this.globalSocketHandlers = null;
|
||||||
|
},
|
||||||
|
handleSocketMessage(event) {
|
||||||
|
let message = event?.data;
|
||||||
|
this.lastMessage = "";
|
||||||
|
try {
|
||||||
|
if (typeof message === "string") {
|
||||||
|
const trimmed = message.trim();
|
||||||
|
if (trimmed.startsWith("{") && trimmed.endsWith("}")) {
|
||||||
|
const parsed = JSON.parse(trimmed);
|
||||||
|
if (parsed.cardNo || parsed.cardNumber || parsed.nfcNo) {
|
||||||
|
this.cardNo = parsed.cardNo || parsed.cardNumber || parsed.nfcNo;
|
||||||
|
this.lastMessage = "已接收卡号";
|
||||||
|
uni.showToast({ title: "收到卡号", icon: "success" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.lastMessage = parsed.msg || trimmed;
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
this.cardNo = trimmed;
|
||||||
|
this.lastMessage = "已接收卡号";
|
||||||
|
uni.showToast({ title: "收到卡号", icon: "success" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else if (typeof message === "object" && message) {
|
||||||
|
const data = message.data || message;
|
||||||
|
if (data.cardNo || data.cardNumber || data.nfcNo) {
|
||||||
|
this.cardNo = data.cardNo || data.cardNumber || data.nfcNo;
|
||||||
|
this.lastMessage = "已接收卡号";
|
||||||
|
uni.showToast({ title: "收到卡号", icon: "success" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.lastMessage = "收到未知消息";
|
||||||
|
} catch (error) {
|
||||||
|
console.error("解析WebSocket消息失败", error, message);
|
||||||
|
this.lastMessage = "消息解析失败";
|
||||||
|
}
|
||||||
|
},
|
||||||
|
resetCard() {
|
||||||
|
this.cardNo = "";
|
||||||
|
this.lastMessage = "";
|
||||||
|
},
|
||||||
|
handleRetry() {
|
||||||
|
this.initSocket();
|
||||||
|
},
|
||||||
|
async handleBind() {
|
||||||
|
if (!this.canSubmit || this.binding) return;
|
||||||
|
this.binding = true;
|
||||||
|
uni.showLoading({ title: "提交中...", mask: true });
|
||||||
|
try {
|
||||||
|
const payload = {
|
||||||
|
memorialMac: this.deviceMac,
|
||||||
|
nfcMac: this.cardNo,
|
||||||
|
};
|
||||||
|
if (this.unitId) {
|
||||||
|
payload.unitId = this.unitId;
|
||||||
|
}
|
||||||
|
const res = await bindNfcCard(payload);
|
||||||
|
if (res && (res.code === 200 || res.status === 200)) {
|
||||||
|
uni.showToast({ title: res.msg || "绑定成功", icon: "success" });
|
||||||
|
setTimeout(() => {
|
||||||
|
uni.navigateBack({ delta: 1 });
|
||||||
|
}, 800);
|
||||||
|
} else {
|
||||||
|
uni.showToast({
|
||||||
|
title: (res && res.msg) || "绑定失败",
|
||||||
|
icon: "none",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("提交绑定失败", error);
|
||||||
|
uni.showToast({ title: "提交失败,请重试", icon: "none" });
|
||||||
|
} finally {
|
||||||
|
this.binding = false;
|
||||||
|
uni.hideLoading();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.page {
|
||||||
|
min-height: 100vh;
|
||||||
|
width: 100%;
|
||||||
|
padding-bottom: 40rpx;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
padding: 0 32rpx 60rpx;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-card,
|
||||||
|
.form-card {
|
||||||
|
background: rgba(255, 255, 255, 0.92);
|
||||||
|
border-radius: 24rpx;
|
||||||
|
padding: 32rpx;
|
||||||
|
margin-top: 32rpx;
|
||||||
|
box-shadow: 0 12rpx 32rpx rgba(0, 0, 0, 0.06);
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 16rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-dot {
|
||||||
|
width: 16rpx;
|
||||||
|
height: 16rpx;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: #f0b400;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-dot.online {
|
||||||
|
background: #3ac569;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-dot.offline {
|
||||||
|
background: #f56c6c;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-title {
|
||||||
|
font-size: 30rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-desc {
|
||||||
|
margin-top: 12rpx;
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #999;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-box {
|
||||||
|
margin-top: 24rpx;
|
||||||
|
border: 2rpx dashed #f0b400;
|
||||||
|
border-radius: 20rpx;
|
||||||
|
padding: 24rpx;
|
||||||
|
background: #fff9eb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-box.ready {
|
||||||
|
border-color: #3ac569;
|
||||||
|
background: #effbf4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-label {
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-value {
|
||||||
|
display: block;
|
||||||
|
margin-top: 16rpx;
|
||||||
|
font-size: 36rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #333;
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-text {
|
||||||
|
margin-top: 16rpx;
|
||||||
|
color: #f56c6c;
|
||||||
|
font-size: 24rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hint-text {
|
||||||
|
margin-top: 16rpx;
|
||||||
|
color: #999;
|
||||||
|
font-size: 24rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-actions {
|
||||||
|
margin-top: 24rpx;
|
||||||
|
display: flex;
|
||||||
|
gap: 32rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-btn {
|
||||||
|
font-size: 26rpx;
|
||||||
|
color: #4a90e2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.field {
|
||||||
|
margin-bottom: 32rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.field.readonly {
|
||||||
|
padding: 24rpx;
|
||||||
|
background: #f9f9f9;
|
||||||
|
border-radius: 16rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.label {
|
||||||
|
font-size: 26rpx;
|
||||||
|
color: #666;
|
||||||
|
margin-bottom: 12rpx;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input {
|
||||||
|
width: 100%;
|
||||||
|
height: 88rpx;
|
||||||
|
border-radius: 16rpx;
|
||||||
|
background: #f8f8f8;
|
||||||
|
padding: 0 24rpx;
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #333;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.placeholder {
|
||||||
|
color: #bbb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.unit-value {
|
||||||
|
font-size: 30rpx;
|
||||||
|
color: #333;
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
|
|
||||||
|
.primary-btn {
|
||||||
|
height: 96rpx;
|
||||||
|
border-radius: 16rpx;
|
||||||
|
background: linear-gradient(135deg, #f0b400, #f08400);
|
||||||
|
color: #fff;
|
||||||
|
font-size: 30rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
margin-top: 12rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.primary-btn.disabled {
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
|
@ -6,8 +6,8 @@ import debounce from "uview-ui/libs/function/debounce";
|
||||||
const ENV_CONFIG = {
|
const ENV_CONFIG = {
|
||||||
release: {
|
release: {
|
||||||
// 正式版
|
// 正式版
|
||||||
// baseUrl: "http://192.168.1.3:4501",
|
baseUrl: "http://192.168.1.4:4501",
|
||||||
baseUrl: "https://tech-ape.top/prod-api",
|
// baseUrl: "https://tech-ape.top/prod-api",
|
||||||
// baseUrl: "https://tech-ape.top/prod-api",
|
// baseUrl: "https://tech-ape.top/prod-api",
|
||||||
appId: 1,
|
appId: 1,
|
||||||
},
|
},
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user