20260711:1、物模型纵向计算
This commit is contained in:
parent
ea8616c649
commit
95d35341ac
@ -8,11 +8,8 @@ TARGET_PORT = 8888 # 请根据实际情况修改
|
||||
|
||||
# 元数组 modbus 指令
|
||||
MODBUS_DATA_HEX = (
|
||||
"7e80DD33D43DFC5B7e",
|
||||
"010320413d0d7541bacb2dbf42019a3f6bdb33c2b19c73c0d39f304100a253c3f9f2187f3a",
|
||||
"020320414056d541b8ad60c02d4acd3fa708cdc2adfe5ac1bcf64c413630c4c3f9a81af11d",
|
||||
"030320413fc48241b919bcc0006ecd3ff874cdc2ae6c0ac18c11e041877c67c3f9b431a049",
|
||||
"040320413f56c341b0362ebf48b333403e3200c2addb5cc0daec4341cf60bdc3f9a40fae0b"
|
||||
"7e80D20C4C6A55D57e",
|
||||
"01030400110018AA3C"
|
||||
)
|
||||
|
||||
# 深部位移计
|
||||
|
||||
@ -133,7 +133,7 @@ public class CRC16Utils {
|
||||
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
String hex = "006000000000";
|
||||
String hex = "01030400110018AA3C";
|
||||
byte[] bytes = ByteUtils.hexToByte(hex);
|
||||
String crc = getCRC(bytes);
|
||||
System.out.println(crc);
|
||||
|
||||
@ -363,7 +363,7 @@ public class TSLCacheImpl implements ITSLCache {
|
||||
// 从数据库查询设备级计算物模型
|
||||
ThingsModel model = new ThingsModel();
|
||||
model.setSerialNumber(serialNumber);
|
||||
model.setIsCalculated(1); // 查询设备级物模型
|
||||
model.setIsCalculatedList(Arrays.asList(1,2)); // 查询设备级物模型
|
||||
List<ThingsModel> models = thingsModelService.selectThingsModelList(model);
|
||||
if (CollectionUtils.isEmpty(models)) {
|
||||
// 清除可能存在的旧缓存
|
||||
|
||||
@ -109,12 +109,14 @@ public class ThingsModel extends BaseEntity {
|
||||
private String formula;
|
||||
|
||||
/**
|
||||
* 物模型来源类型(0-设备上报,1-纯计算,2-其他预留)
|
||||
* 物模型来源类型(0-设备上报,1-纯计算(横向计算),2-纵向计算)
|
||||
* 纯计算物模型:计算公式包含{},依赖其他物模型计算值
|
||||
*/
|
||||
@ApiModelProperty(value = "物模型来源类型", notes = "(0-设备上报,1-纯计算,2-其他预留)")
|
||||
@ApiModelProperty(value = "物模型来源类型", notes = "(0-设备上报,1-纯计算(横向计算),2-纵向计算)")
|
||||
private Integer isCalculated;
|
||||
|
||||
private List<Integer> isCalculatedList;
|
||||
|
||||
/**
|
||||
* 数据定义
|
||||
*/
|
||||
|
||||
@ -81,7 +81,7 @@ public class ThingsModelValueItem {
|
||||
private String formula;
|
||||
|
||||
/**
|
||||
* 物模型来源类型(0-设备上报,1-纯计算,2-其他预留)
|
||||
* 物模型来源类型(0-设备上报,1-纯计算(横向计算),2-纵向计算)
|
||||
*/
|
||||
private Integer isCalculated;
|
||||
|
||||
|
||||
@ -102,9 +102,7 @@ public class DcpPlatformAdapter {
|
||||
|
||||
try {
|
||||
String pushPath = resolvePushPath(mapping, pushInterface);
|
||||
handleNormalPush(
|
||||
platform, mapping, reportSerialNumber, payload, pushPath,
|
||||
interfaceCode, requestSchema);
|
||||
handleNormalPush(platform, mapping, reportSerialNumber, payload, pushPath, interfaceCode, requestSchema);
|
||||
} catch (Exception e) {
|
||||
String serialNumber = StringUtils.hasText(reportSerialNumber)
|
||||
? reportSerialNumber : mapping.getSerialNumber();
|
||||
@ -151,8 +149,7 @@ public class DcpPlatformAdapter {
|
||||
// 无数组字段 → 简单推送,使用子设备 platformDeviceId 和主表 fieldMapping
|
||||
if (!hasArrayField(requestSchema)) {
|
||||
// 如果只有一个设备完全可以走
|
||||
handleSubDeviceSimplePush(platform, mapping, subMapping, reportSerialNumber,
|
||||
payload, pushPath, interfaceCode, requestSchema);
|
||||
handleSubDeviceSimplePush(platform, mapping, subMapping, reportSerialNumber, payload, pushPath, interfaceCode, requestSchema);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -348,12 +345,10 @@ public class DcpPlatformAdapter {
|
||||
|
||||
int replayCount = 0;
|
||||
for (PlatformDeviceSubMapping monitor : subMappings) {
|
||||
if (monitor == null || !monitor.isMonitor()
|
||||
|| !StringUtils.hasText(monitor.getSubSerialNumber())) {
|
||||
if (monitor == null || !monitor.isMonitor() || !StringUtils.hasText(monitor.getSubSerialNumber())) {
|
||||
continue;
|
||||
}
|
||||
JSONObject monitorData = bufferService.getMonitorDataIfFresh(
|
||||
platform.getId(), fieldName, monitor.getSubSerialNumber(), reportIntervalSec);
|
||||
JSONObject monitorData = bufferService.getMonitorDataIfFresh(platform.getId(), fieldName, monitor.getSubSerialNumber(), reportIntervalSec);
|
||||
if (monitorData == null) {
|
||||
continue;
|
||||
}
|
||||
@ -379,8 +374,7 @@ public class DcpPlatformAdapter {
|
||||
JSONArray pushBody = buildArrayPushBody(mapping, bodyFields, fieldName,
|
||||
sensorData, monitor.getPlatformDeviceId());
|
||||
|
||||
PlatformPushLog logEntry = createLogEntry(platform, mapping, interfaceCode,
|
||||
monitor.getSubSerialNumber());
|
||||
PlatformPushLog logEntry = createLogEntry(platform, mapping, interfaceCode, monitor.getSubSerialNumber());
|
||||
doHttpPost(platform, baseUrl, JSON.toJSONString(pushBody), logEntry);
|
||||
pushLogService.asyncSave(logEntry);
|
||||
if (logEntry.getStatus() != null && logEntry.getStatus() == 1) {
|
||||
@ -1117,8 +1111,7 @@ public class DcpPlatformAdapter {
|
||||
}
|
||||
|
||||
if (currentIndex == -1) {
|
||||
log.warn("[DCP] 当前子设备 {} 不在监测点列表中 mappingId={}",
|
||||
reportSerialNumber, mapping.getId());
|
||||
log.warn("[DCP] 当前子设备 {} 不在监测点列表中 mappingId={}", reportSerialNumber, mapping.getId());
|
||||
return;
|
||||
}
|
||||
|
||||
@ -1156,8 +1149,7 @@ public class DcpPlatformAdapter {
|
||||
}
|
||||
|
||||
String sensorId = resolveSubDeviceSensorId(sub);
|
||||
JSONObject sensorDataItem = buildArrayItem(
|
||||
platform, mapping, requestSchema, reportData, fieldName, itemFieldMappings, sensorId);
|
||||
JSONObject sensorDataItem = buildArrayItem(platform, mapping, requestSchema, reportData, fieldName, itemFieldMappings, sensorId);
|
||||
if (sensorDataItem == null || sensorDataItem.isEmpty()) {
|
||||
log.warn("[DCP] 子设备 {} 数据映射为空,跳过其 sensorData", subSn);
|
||||
continue;
|
||||
|
||||
@ -691,9 +691,8 @@ public class DeviceServiceImpl implements IDeviceService {
|
||||
log.warn("=>查询物模型为空[{}],标识符:[{}]", input.getProductId(), identity);
|
||||
continue; // 跳过当前物模型的处理
|
||||
}
|
||||
// 获取设备实时值
|
||||
ValueItem cacheIdentifier = itslValueCache.getCacheIdentifier(input.getProductId(), serialNumber,
|
||||
thingModels.getId());
|
||||
// 获取设备上一次缓存值
|
||||
ValueItem cacheIdentifier = itslValueCache.getCacheIdentifier(input.getProductId(), serialNumber, thingModels.getId());
|
||||
if (Objects.isNull(cacheIdentifier)) {
|
||||
log.warn("=>查询物模型值为空[{}],标识符:[{}]", input.getProductId(), identity);
|
||||
}
|
||||
@ -719,12 +718,17 @@ public class DeviceServiceImpl implements IDeviceService {
|
||||
String refValue = thingsModelValueMap.get(refIdentifier);
|
||||
if (refValue != null && !refValue.isEmpty()) {
|
||||
params.put("{" + refIdentifier + "}", refValue);
|
||||
} else {
|
||||
// 纵向计算物模型
|
||||
if (Integer.valueOf(2).equals(thingModels.getIsCalculated())) {
|
||||
params.put("{" + refIdentifier + "}", cacheIdentifier.getValue());
|
||||
} else {
|
||||
params.put("{" + refIdentifier + "}", "0");
|
||||
log.warn("=>计算公式引用的物模型值不存在[{}],标识符:[{}],使用默认值0", input.getProductId(), refIdentifier);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 执行计算
|
||||
try {
|
||||
|
||||
@ -144,6 +144,12 @@
|
||||
<if test="isCalculated != null ">
|
||||
AND is_calculated = #{isCalculated}
|
||||
</if>
|
||||
<if test="isCalculatedList != null ">
|
||||
AND is_calculated IN
|
||||
<foreach collection="isCalculatedList" item="item" separator="," open="(" close=")">
|
||||
#{item}
|
||||
</foreach>
|
||||
</if>
|
||||
<if test="modelIdList != null ">
|
||||
model_id IN
|
||||
<foreach collection="modelIdList" item="item" separator="," open="(" close=")">
|
||||
|
||||
@ -12,7 +12,7 @@ VUE_APP_BASE_API = /dev-api
|
||||
VUE_APP_SERVER_API_URL = http://localhost:8080
|
||||
|
||||
# MQTT WebSocket 服务器地址
|
||||
VUE_APP_MQTT_SERVER_URL = ws://localhost:8083/mqtt
|
||||
VUE_APP_MQTT_SERVER_URL = ws://47.121.180.229:8083/mqtt
|
||||
|
||||
# 百度地图 API Key
|
||||
VUE_APP_BAI_DU_AK = s9mUXN9a7bKgVu6U6Z3bXhKSxtz97YPI
|
||||
|
||||
@ -21,6 +21,13 @@
|
||||
<el-table v-loading="loading" :data="modelList" size="mini" :border="false">
|
||||
<el-table-column label="物模型名称" align="center" prop="modelName" width="200" />
|
||||
<el-table-column label="标识符" align="center" prop="identifier" />
|
||||
<el-table-column label="计算类型" align="center" width="90">
|
||||
<template slot-scope="scope">
|
||||
<el-tag :type="scope.row.isCalculated === 2 ? 'warning' : 'primary'" size="mini">
|
||||
{{ scope.row.isCalculated === 2 ? '纵向累计' : '横向计算' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="图表展示" align="center" width="80">
|
||||
<template slot-scope="scope">
|
||||
<dict-tag :options="dict.type.iot_yes_no" :value="scope.row.isChart" />
|
||||
@ -80,6 +87,22 @@
|
||||
<el-form-item label="排序" prop="modelOrder">
|
||||
<el-input v-model="form.modelOrder" placeholder="请输入排序" type="number" style="width: 580px" />
|
||||
</el-form-item>
|
||||
<el-form-item label="计算类型" prop="isCalculated">
|
||||
<el-radio-group v-model="form.isCalculated">
|
||||
<el-radio :label="1">
|
||||
横向计算
|
||||
<el-tooltip content="使用同一批上报数据中的物模型值计算" placement="top">
|
||||
<i class="el-icon-question" style="color:#909399;margin-left:5px;cursor:help;"></i>
|
||||
</el-tooltip>
|
||||
</el-radio>
|
||||
<el-radio :label="2">
|
||||
纵向累计
|
||||
<el-tooltip content="使用目标物模型缓存值 + 本次源物模型上报值" placement="top">
|
||||
<i class="el-icon-question" style="color:#909399;margin-left:5px;cursor:help;"></i>
|
||||
</el-tooltip>
|
||||
</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="模型特性" prop="property">
|
||||
<el-checkbox name="isChart" label="图表展示" v-model="form.isChart" :true-label="1" :false-label="0"></el-checkbox>
|
||||
<el-checkbox name="isMonitor" label="实时监测" v-model="form.isMonitor" :true-label="1" :false-label="0"></el-checkbox>
|
||||
@ -130,12 +153,14 @@
|
||||
<span slot="label">{{$t('device.device-edit.148398-83')}}</span>
|
||||
<el-tooltip style="cursor: pointer" effect="light" placement="top">
|
||||
<div slot="content">
|
||||
用于对原始值进行计算后存储和显示<br />
|
||||
横向计算:使用同一批上报数据中的物模型值<br />
|
||||
纵向累计:使用 {last} 表示当前物模型上一次缓存累计值<br />
|
||||
公式中使用产品物模型标识符,并用大括号包裹<br />
|
||||
支持运算符:+ - * / % ^(次方) sqrt(开方)<br />
|
||||
<br />
|
||||
<strong>示例:</strong><br />
|
||||
压力=K*频率平方-常数:1.5*{frequency}^2-100
|
||||
横向计算:1.5*{frequency}^2-100<br />
|
||||
纵向累计:{last}+{rain}
|
||||
</div>
|
||||
<i class="el-icon-question" />
|
||||
</el-tooltip>
|
||||
@ -158,8 +183,24 @@
|
||||
</div>
|
||||
<!-- 物模型变量区域:网关设备按从机展示,便于插值计算 -->
|
||||
<div class="variable-section" v-loading="loadingModels">
|
||||
<span class="section-label">{{ isGatewayDevice ? '从机物模型变量(点击插入,用于跨设备插值如 从机1-从机2):' : '产品通用物模型变量:' }}</span>
|
||||
<template v-if="isGatewayDevice && gatewayVariableGroups.length">
|
||||
<span class="section-label">{{ form.isCalculated === 2 ? '纵向累计变量:' : (isGatewayDevice ? '从机物模型变量(点击插入,用于跨设备插值如 从机1-从机2):' : '产品通用物模型变量:') }}</span>
|
||||
<div v-if="form.isCalculated === 2" class="variable-tags">
|
||||
<el-tag size="small" type="warning" effect="plain" class="var-tag" @click="insertVariable('last')" title="{last}">上次累计值</el-tag>
|
||||
<el-tag
|
||||
v-for="model in productModels"
|
||||
:key="model.identifier"
|
||||
size="small"
|
||||
type="primary"
|
||||
effect="plain"
|
||||
class="var-tag"
|
||||
@click="setVerticalFormula(model.identifier)"
|
||||
:title="`{last}+{${model.identifier}}`"
|
||||
>
|
||||
{{ model.modelName }}
|
||||
</el-tag>
|
||||
<span v-if="productModels.length === 0 && !loadingModels" class="no-data">暂无可用物模型</span>
|
||||
</div>
|
||||
<template v-else-if="isGatewayDevice && gatewayVariableGroups.length">
|
||||
<div v-for="grp in gatewayVariableGroups" :key="grp.slaveId" class="variable-group">
|
||||
<span class="group-label">从机{{ grp.slaveId }}:</span>
|
||||
<el-tag
|
||||
@ -194,7 +235,7 @@
|
||||
</div>
|
||||
<!-- 输入框和清空按钮 -->
|
||||
<div class="formula-input-section">
|
||||
<el-input ref="formulaInput" v-model="form.formula" type="textarea" :rows="2" placeholder="输入或点击上方运算符、物模型变量构建公式" class="formula-input" />
|
||||
<el-input ref="formulaInput" v-model="form.formula" type="textarea" :rows="2" :placeholder="form.isCalculated === 2 ? '请选择源物模型生成累计公式,如:{last}+{rain}' : '输入或点击上方运算符、物模型变量构建公式'" class="formula-input" />
|
||||
<el-button type="danger" plain size="small" title="清空公式" @click="clearFormula" class="clear-btn">清空</el-button>
|
||||
</div>
|
||||
</div>
|
||||
@ -241,6 +282,12 @@
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.form-tip {
|
||||
margin-top: 6px;
|
||||
font-size: 12px;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
.operator-buttons {
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
@ -427,7 +474,6 @@ export default {
|
||||
queryParams: {
|
||||
productId: 0,
|
||||
deviceId: 0,
|
||||
isCalculated: 1,
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
},
|
||||
@ -468,6 +514,7 @@ export default {
|
||||
{ pattern: /^[a-zA-Z][a-zA-Z0-9_]*$/, message: '以字母开头,只含字母、数字、下划线', trigger: 'blur' },
|
||||
],
|
||||
datatype: [{ required: true, message: '请选择数据类型', trigger: 'change' }],
|
||||
isCalculated: [{ required: true, message: '请选择计算类型', trigger: 'change' }],
|
||||
formula: [
|
||||
{ required: true, message: '请输入计算公式', trigger: 'none' },
|
||||
{ validator: this.validateFormula, trigger: 'none' },
|
||||
@ -515,12 +562,25 @@ export default {
|
||||
// 2. 校验变量格式:{标识符} 或 {slaveId:数字.标识符}(跨设备)
|
||||
const variablePattern = /\{([^}]*)\}/g;
|
||||
let match;
|
||||
const variableNames = [];
|
||||
const simpleVar = /^[a-zA-Z][a-zA-Z0-9_]*$/;
|
||||
const crossVar = /^slaveId:\d+\.[a-zA-Z][a-zA-Z0-9_]*$/;
|
||||
while ((match = variablePattern.exec(formula)) !== null) {
|
||||
const varName = match[1];
|
||||
variableNames.push(varName);
|
||||
if (!simpleVar.test(varName) && !crossVar.test(varName)) {
|
||||
callback(new Error(`变量格式错误: {${varName}}。支持:{标识符} 或 {slaveId:从机号.标识符}(如 {slaveId:1.WaterH})`));
|
||||
callback(new Error(`变量格式错误: {${varName}}。支持:{标识符}、{last} 或 {slaveId:从机号.标识符}(如 {slaveId:1.WaterH})`));
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (this.form.isCalculated === 2) {
|
||||
if (!variableNames.includes('last')) {
|
||||
callback(new Error('纵向累计公式必须包含 {last}'));
|
||||
return;
|
||||
}
|
||||
const sourceVariables = variableNames.filter((name) => name !== 'last' && name !== this.form.identifier);
|
||||
if (sourceVariables.length !== 1) {
|
||||
callback(new Error('纵向累计公式必须且只能包含一个源物模型变量,如:{last}+{rain}'));
|
||||
return;
|
||||
}
|
||||
}
|
||||
@ -625,7 +685,15 @@ export default {
|
||||
placeholder: `{slaveId:${g.slaveId}.${m.identifier}}`,
|
||||
})),
|
||||
}));
|
||||
this.productModels = [];
|
||||
return listModel({
|
||||
productId: this.device.productId,
|
||||
deviceId: 0,
|
||||
pageNum: 1,
|
||||
pageSize: 200,
|
||||
});
|
||||
})
|
||||
.then((response) => {
|
||||
this.productModels = (response.rows || []).filter((m) => m.isCalculated !== 1 && m.isCalculated !== 2);
|
||||
})
|
||||
.finally(() => {
|
||||
this.loadingModels = false;
|
||||
@ -643,7 +711,7 @@ export default {
|
||||
pageSize: 200,
|
||||
})
|
||||
.then((response) => {
|
||||
this.productModels = (response.rows || []).filter((m) => m.isCalculated !== 1);
|
||||
this.productModels = (response.rows || []).filter((m) => m.isCalculated !== 1 && m.isCalculated !== 2);
|
||||
})
|
||||
.finally(() => {
|
||||
this.loadingModels = false;
|
||||
@ -731,6 +799,11 @@ export default {
|
||||
: `{${identifierOrPlaceholder}}`;
|
||||
this.insertAtCursor(text);
|
||||
},
|
||||
/** 纵向累计直接生成标准公式 */
|
||||
setVerticalFormula(identifier) {
|
||||
this.form.formula = `{last}+{${identifier}}`;
|
||||
this.focusFormulaInput();
|
||||
},
|
||||
/** 在光标位置插入内容(支持替换选中文本) */
|
||||
insertAtCursor(text) {
|
||||
const input = this.$refs.formulaInput;
|
||||
@ -816,7 +889,6 @@ export default {
|
||||
|
||||
// 格式化specs
|
||||
this.form.specs = this.formatThingsSpecs();
|
||||
this.form.isCalculated = 1;
|
||||
this.form.isReadonly = 1;
|
||||
this.form.type = 1;
|
||||
// 去除公式中的空格
|
||||
|
||||
@ -96,7 +96,9 @@
|
||||
<el-col :span="8">
|
||||
<el-form-item label="批量推送">
|
||||
<el-switch v-model="form.isBatchPush" :active-value="1" :inactive-value="0" active-text="是" inactive-text="否" />
|
||||
<div style="font-size:12px;color:#909399;margin-top:4px">开启后一次请求推送全量子设备数据</div>
|
||||
<el-tooltip content="开启后一次请求推送全量子设备数据" placement="top">
|
||||
<i class="el-icon-question" style="color:#909399;margin-left:5px;cursor:help;"></i>
|
||||
</el-tooltip>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
@ -140,7 +140,7 @@
|
||||
<el-input v-model="form.serialNumber" disabled />
|
||||
</el-form-item>
|
||||
<el-form-item label="设备名称">
|
||||
<el-input v-model="form.deviceName" disabled />
|
||||
<el-input v-model="form.deviceName" />
|
||||
</el-form-item>
|
||||
<el-form-item label="传感器类型" prop="sensorType">
|
||||
<el-select v-model="form.sensorType" placeholder="选择或输入设备类型" filterable allow-create default-first-option style="width:100%">
|
||||
@ -165,13 +165,19 @@
|
||||
<el-col :span="12">
|
||||
<el-form-item label="推送模式" prop="pushMode">
|
||||
<el-radio-group v-model="form.pushMode">
|
||||
<el-radio :label="1">实时推送</el-radio>
|
||||
<el-radio :label="2">定时推送</el-radio>
|
||||
<el-radio :label="1">
|
||||
实时推送
|
||||
<el-tooltip content="设备上报即刻推送" placement="top">
|
||||
<i class="el-icon-question" style="color:#909399;margin-left:5px;cursor:help;"></i>
|
||||
</el-tooltip>
|
||||
</el-radio>
|
||||
<el-radio :label="2">
|
||||
定时推送
|
||||
<el-tooltip content="按间隔推送最新数据" placement="top">
|
||||
<i class="el-icon-question" style="color:#909399;margin-left:5px;cursor:help;"></i>
|
||||
</el-tooltip>
|
||||
</el-radio>
|
||||
</el-radio-group>
|
||||
<div style="font-size:12px;color:#909399;margin-top:4px;line-height:1.5">
|
||||
实时:设备上报即刻推送<br>
|
||||
定时:按间隔推送最新数据
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12" v-if="form.pushMode === 2">
|
||||
@ -589,7 +595,7 @@ export default {
|
||||
this.deviceOptions = [{ deviceId: device.deviceId, deviceName: device.deviceName, serialNumber: device.serialNumber }]
|
||||
this.form.deviceId = device.deviceId
|
||||
this.form.serialNumber = device.serialNumber
|
||||
this.form.deviceName = device.deviceName || device.serialNumber
|
||||
this.form.deviceName = this.form.deviceName || device.deviceName || device.serialNumber
|
||||
this.loadThingsModel(device.deviceId)
|
||||
this.loadSubDeviceOptions(device)
|
||||
}).catch(() => {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user