fix bugs
This commit is contained in:
@@ -0,0 +1,31 @@
|
||||
/**
|
||||
* 将 API 返回的 PascalCase 字段名转为前端使用的 camelCase(递归处理嵌套对象/数组)
|
||||
*/
|
||||
function toCamelCaseKey(key) {
|
||||
if (!key || typeof key !== 'string') return key;
|
||||
if (key[0] === key[0].toLowerCase()) return key;
|
||||
return key.charAt(0).toLowerCase() + key.slice(1);
|
||||
}
|
||||
|
||||
export function normalizeApiKeys(value) {
|
||||
if (value === null || value === undefined) return value;
|
||||
if (Array.isArray(value)) {
|
||||
return value.map(item => normalizeApiKeys(item));
|
||||
}
|
||||
if (typeof value !== 'object') return value;
|
||||
|
||||
const result = {};
|
||||
for (const [key, val] of Object.entries(value)) {
|
||||
result[toCamelCaseKey(key)] = normalizeApiKeys(val);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/** 兼容 API 返回 records / Records */
|
||||
export function getApiRecords(data) {
|
||||
if (!data) return [];
|
||||
const records = data.records ?? data.Records;
|
||||
return Array.isArray(records) ? records : [];
|
||||
}
|
||||
|
||||
export default normalizeApiKeys;
|
||||
@@ -0,0 +1,83 @@
|
||||
/**
|
||||
* iOS APNs DeviceToken 封装(原生插件:NetworkUtils-NetworkUtilsModule)
|
||||
* 对应原生方法:getDeviceTokenAsync、refreshDeviceTokenAsync
|
||||
*/
|
||||
// #ifdef APP-PLUS
|
||||
const pushNetworkModule = uni.requireNativePlugin('NetworkUtils-NetworkUtilsModule')
|
||||
// #endif
|
||||
|
||||
/**
|
||||
* 读取当前缓存的 DeviceToken(最近一次 didRegisterForRemoteNotifications 写入本地)
|
||||
* @param {Record<string, unknown>} options 透传给原生,可为 {}
|
||||
*/
|
||||
export function getDeviceToken(options = {}) {
|
||||
return new Promise((resolve, reject) => {
|
||||
// #ifdef APP-PLUS
|
||||
if (!pushNetworkModule || typeof pushNetworkModule.getDeviceTokenAsync !== 'function') {
|
||||
reject(new Error('NetworkUtils 原生插件不可用或未注册 getDeviceTokenAsync'))
|
||||
return
|
||||
}
|
||||
pushNetworkModule.getDeviceTokenAsync(Object.assign({ registerIfEmpty: true }, options || {}), (ret) => {
|
||||
if (ret && (ret.status === 'success' || ret.status === 'empty')) {
|
||||
resolve(ret)
|
||||
} else {
|
||||
reject(ret || new Error('getDeviceTokenAsync 返回异常'))
|
||||
}
|
||||
})
|
||||
// #endif
|
||||
// #ifndef APP-PLUS
|
||||
resolve({
|
||||
status: 'empty',
|
||||
deviceToken: '',
|
||||
hasToken: false,
|
||||
updatedAt: 0,
|
||||
message: '非 App-PLUS 环境'
|
||||
})
|
||||
// #endif
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 触发重新向 APNs 注册(token 更新后由原生写入本地,再调用 getDeviceToken 读取)
|
||||
* @param {{ requestAuthorization?: boolean }} options requestAuthorization 默认 true;已授权后可传 false 避免重复弹权限逻辑
|
||||
*/
|
||||
export function refreshDeviceTokenAsync(options = {}) {
|
||||
return new Promise((resolve, reject) => {
|
||||
// #ifdef APP-PLUS
|
||||
if (!pushNetworkModule || typeof pushNetworkModule.refreshDeviceTokenAsync !== 'function') {
|
||||
reject(new Error('NetworkUtils 原生插件不可用或未注册 refreshDeviceTokenAsync'))
|
||||
return
|
||||
}
|
||||
const opts = {
|
||||
requestAuthorization: options.requestAuthorization !== false
|
||||
}
|
||||
pushNetworkModule.refreshDeviceTokenAsync(opts, (ret) => {
|
||||
if (ret && ret.status === 'requested') {
|
||||
resolve(ret)
|
||||
} else if (ret && (ret.status === 'error' || ret.status === 'denied')) {
|
||||
reject(ret)
|
||||
} else {
|
||||
resolve(ret || {})
|
||||
}
|
||||
})
|
||||
// #endif
|
||||
// #ifndef APP-PLUS
|
||||
resolve({ status: 'skipped', message: '非 App-PLUS 环境' })
|
||||
// #endif
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 先刷新注册再轮询读取(适合首次拿 token 或服务端判定失效后)
|
||||
*/
|
||||
export async function refreshDeviceTokenAndFetch(options = {}) {
|
||||
await refreshDeviceTokenAsync(options)
|
||||
// 授权弹窗关闭 + registerForRemoteNotifications 多为异步,适度拉长首次等待
|
||||
await new Promise((r) => setTimeout(r, 900))
|
||||
let last = await getDeviceToken()
|
||||
for (let i = 0; i < 25 && last.status === 'empty'; i++) {
|
||||
await new Promise((r) => setTimeout(r, 400))
|
||||
last = await getDeviceToken()
|
||||
}
|
||||
return last
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
/**
|
||||
* iOS 远程推送消息封装(原生插件:NetworkUtils-NetworkUtilsModule)
|
||||
* 对应原生方法:onPushNotificationAsync、consumePendingPushNotificationsAsync
|
||||
*/
|
||||
// #ifdef APP-PLUS
|
||||
const pushNetworkModule = uni.requireNativePlugin('NetworkUtils-NetworkUtilsModule')
|
||||
// #endif
|
||||
|
||||
let lastPushNotification = null
|
||||
const processedPushKeys = {}
|
||||
|
||||
function getPushDedupeKey(title, content) {
|
||||
return `${title || ''}|${content || ''}`
|
||||
}
|
||||
|
||||
function markPushProcessed(title, content) {
|
||||
const key = getPushDedupeKey(title, content)
|
||||
processedPushKeys[key] = true
|
||||
return key
|
||||
}
|
||||
|
||||
function isPushProcessed(title, content) {
|
||||
return !!processedPushKeys[getPushDedupeKey(title, content)]
|
||||
}
|
||||
|
||||
function dispatchPushMessage(onMessage, payload, meta = {}) {
|
||||
if (!meta.userTapped && isPushProcessed(payload.title, payload.content)) {
|
||||
console.log('[Push] skip duplicate push:', getPushDedupeKey(payload.title, payload.content))
|
||||
return
|
||||
}
|
||||
markPushProcessed(payload.title, payload.content)
|
||||
lastPushNotification = payload
|
||||
if (typeof onMessage === 'function') {
|
||||
onMessage(payload, meta)
|
||||
}
|
||||
}
|
||||
|
||||
function parseUserTappedFlag(value) {
|
||||
return value === true || value === 1 || value === '1' || value === 'true'
|
||||
}
|
||||
|
||||
function getPushConfirmNo(res = {}) {
|
||||
const confirmNo = res.ConfirmNo || res.confirmNo || res.confNo || res.raw?.ConfirmNo || res.raw?.confirmNo || res.raw?.confNo
|
||||
return confirmNo ? String(confirmNo) : ''
|
||||
}
|
||||
|
||||
/**
|
||||
* 将原生回调数据规范化为 { title, content, body, confirmNo, userTapped, raw }
|
||||
* @param {Record<string, unknown>} res
|
||||
*/
|
||||
export function normalizePushPayload(res = {}) {
|
||||
const title = typeof res.title === 'string' ? res.title : (res.title ? String(res.title) : '')
|
||||
const body = typeof res.body === 'string' ? res.body : (res.body ? String(res.body) : '')
|
||||
const confirmNo = getPushConfirmNo(res)
|
||||
return {
|
||||
title,
|
||||
content: body,
|
||||
body,
|
||||
confirmNo,
|
||||
userTapped: parseUserTappedFlag(res.userTapped),
|
||||
raw: res
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取最近一次收到的推送(内存缓存)
|
||||
*/
|
||||
export function getLastPushNotification() {
|
||||
return lastPushNotification
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取并清空 Native 侧缓存的待处理推送(后台/冷启动场景)
|
||||
* @returns {Promise<Array<{ title: string, content: string, body: string, raw: object }>>}
|
||||
*/
|
||||
export function consumePendingPushNotifications() {
|
||||
return new Promise((resolve) => {
|
||||
// #ifdef APP-PLUS
|
||||
if (!pushNetworkModule) {
|
||||
console.error('[Push] NetworkUtils 原生插件不可用')
|
||||
resolve([])
|
||||
return
|
||||
}
|
||||
if (typeof pushNetworkModule.consumePendingPushNotificationsAsync !== 'function') {
|
||||
console.warn('[Push] 原生暂不支持 consumePendingPushNotificationsAsync,跳过 Native pending 同步')
|
||||
resolve([])
|
||||
return
|
||||
}
|
||||
console.log('[Push] 调用 consumePendingPushNotificationsAsync ...')
|
||||
pushNetworkModule.consumePendingPushNotificationsAsync({}, (ret) => {
|
||||
const items = Array.isArray(ret?.items) ? ret.items : []
|
||||
console.log('[Push] consumePendingPushNotificationsAsync 返回 count=', ret?.count ?? items.length)
|
||||
resolve(items.map((item) => normalizePushPayload(item)))
|
||||
})
|
||||
// #endif
|
||||
// #ifndef APP-PLUS
|
||||
resolve([])
|
||||
// #endif
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 同步处理 Native 缓存的推送
|
||||
* @param {(payload: { title: string, content: string, body: string, raw: object }, meta?: object) => void} onMessage
|
||||
* @param {{ showToast?: boolean }} [options]
|
||||
*/
|
||||
export async function syncPendingPushNotifications(onMessage, options = {}) {
|
||||
console.log('[Push] syncPendingPushNotifications start')
|
||||
const pending = await consumePendingPushNotifications()
|
||||
if (!pending.length) {
|
||||
console.log('[Push] syncPendingPushNotifications: 队列为空')
|
||||
return pending
|
||||
}
|
||||
console.log('[Push] syncPendingPushNotifications count=', pending.length)
|
||||
for (const payload of pending) {
|
||||
dispatchPushMessage(onMessage, payload, {
|
||||
fromPending: true,
|
||||
userTapped: payload.userTapped === true,
|
||||
showToast: options.showToast !== false
|
||||
})
|
||||
}
|
||||
return pending
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册推送消息监听;每次收到远程推送时 callback 会被调用
|
||||
* @param {(payload: { title: string, content: string, body: string, raw: object }, meta?: object) => void} onMessage
|
||||
* @returns {() => void}
|
||||
*/
|
||||
export function listenPushNotification(onMessage) {
|
||||
// #ifdef APP-PLUS
|
||||
if (!pushNetworkModule || typeof pushNetworkModule.onPushNotificationAsync !== 'function') {
|
||||
console.warn('[Push] NetworkUtils 原生插件不可用或未注册 onPushNotificationAsync')
|
||||
return () => {}
|
||||
}
|
||||
pushNetworkModule.onPushNotificationAsync({}, (res) => {
|
||||
const payload = normalizePushPayload(res)
|
||||
console.log('[Push] 收到推送通知:', JSON.stringify(payload))
|
||||
dispatchPushMessage(onMessage, payload, {
|
||||
fromPending: !!res?.fromPending,
|
||||
userTapped: payload.userTapped === true,
|
||||
showToast: true
|
||||
})
|
||||
})
|
||||
return () => {}
|
||||
// #endif
|
||||
// #ifndef APP-PLUS
|
||||
console.warn('[Push] 非 App-PLUS 环境,跳过推送监听')
|
||||
return () => {}
|
||||
// #endif
|
||||
}
|
||||
+9
-4
@@ -121,8 +121,10 @@ module.exports = (vm) => {
|
||||
break;
|
||||
case 400:
|
||||
kickout()
|
||||
break;
|
||||
case 401:
|
||||
if(error.config.url.indexOf('refresh_token')==-1 && error.config.url.indexOf('logout')==-1){
|
||||
if(error.config.url.indexOf('refresh_token')==-1 && error.config.url.indexOf('logout')==-1 && !error.config.custom?.tokenRetried){
|
||||
error.config.custom = {...error.config.custom, tokenRetried: true};
|
||||
return tokenRefresh().then(_ => {
|
||||
error.config.header.Authorization = uni.getStorageSync("token");
|
||||
error.config.header.RefreshToken = uni.getStorageSync("refreshToken")
|
||||
@@ -141,19 +143,22 @@ module.exports = (vm) => {
|
||||
|
||||
}
|
||||
function tokenRefresh(){
|
||||
let oldToken = uni.getStorageSync("token");
|
||||
if (oldToken !== null && oldToken !== '') {
|
||||
const storedRefreshToken = uni.getStorageSync("refreshToken");
|
||||
if (storedRefreshToken) {
|
||||
return refreshToken({})
|
||||
.then((data) => {
|
||||
if (!data?.success || !data.accessToken) throw new Error('Token refresh failed');
|
||||
uni.setStorageSync("token",data.accessToken);
|
||||
uni.setStorageSync("tokenTime",dayjs().unix());
|
||||
return Promise.resolve(true)
|
||||
})
|
||||
.catch(error => {
|
||||
kickout()
|
||||
return Promise.reject(error);
|
||||
})
|
||||
} else {
|
||||
kickout()
|
||||
return Promise.reject(new Error('Missing RefreshToken'));
|
||||
}
|
||||
}
|
||||
function checkToken(){
|
||||
@@ -198,4 +203,4 @@ function checkIP(header){
|
||||
}
|
||||
}*/
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,244 @@
|
||||
function parseDate(value) {
|
||||
const parts = value.split('-').map(Number);
|
||||
return new Date(parts[0], parts[1] - 1, parts[2]);
|
||||
}
|
||||
|
||||
function formatDate(date) {
|
||||
const year = date.getFullYear();
|
||||
const month = `${date.getMonth() + 1}`.padStart(2, '0');
|
||||
const day = `${date.getDate()}`.padStart(2, '0');
|
||||
return `${year}-${month}-${day}`;
|
||||
}
|
||||
|
||||
function addDays(date, count) {
|
||||
const next = new Date(date);
|
||||
next.setDate(next.getDate() + count);
|
||||
return next;
|
||||
}
|
||||
|
||||
function addMonths(date, count) {
|
||||
return new Date(date.getFullYear(), date.getMonth() + count, 1);
|
||||
}
|
||||
|
||||
function startOfWeek(date) {
|
||||
return addDays(date, -date.getDay());
|
||||
}
|
||||
|
||||
function endOfWeek(date) {
|
||||
return addDays(date, 6 - date.getDay());
|
||||
}
|
||||
|
||||
function endOfMonth(date) {
|
||||
return new Date(date.getFullYear(), date.getMonth() + 1, 0);
|
||||
}
|
||||
|
||||
const monthLabels = ['JAN', 'FEB', 'MAR', 'APR', 'MAY', 'JUN', 'JUL', 'AUG', 'SEP', 'OCT', 'NOV', 'DEC'];
|
||||
|
||||
export function buildCalendarMonths({ today, monthCount, weekdayLabels }) {
|
||||
const todayDate = parseDate(today);
|
||||
const months = [];
|
||||
|
||||
for (let monthIndex = 0; monthIndex < monthCount; monthIndex++) {
|
||||
const monthDate = addMonths(todayDate, monthIndex);
|
||||
const monthStart = new Date(monthDate.getFullYear(), monthDate.getMonth(), 1);
|
||||
const start = startOfWeek(monthStart);
|
||||
const end = endOfWeek(endOfMonth(monthStart));
|
||||
const weeks = [];
|
||||
let cursor = start;
|
||||
let weekIndex = 0;
|
||||
|
||||
while (cursor <= end) {
|
||||
const days = weekdayLabels.map((item, dayIndex) => {
|
||||
const date = addDays(cursor, dayIndex);
|
||||
const dateText = formatDate(date);
|
||||
const isLeadingPastDate = monthIndex === 0 && date < todayDate;
|
||||
const isOutsideMonth = date.getMonth() !== monthStart.getMonth() && !isLeadingPastDate;
|
||||
return {
|
||||
date: dateText,
|
||||
dayNo: isOutsideMonth ? '' : `${date.getDate()}`.padStart(2, '0'),
|
||||
isToday: !isOutsideMonth && dateText === today,
|
||||
isPast: date < todayDate,
|
||||
isOutsideMonth,
|
||||
};
|
||||
});
|
||||
weeks.push({
|
||||
weekKey: formatDate(cursor),
|
||||
days,
|
||||
});
|
||||
cursor = addDays(cursor, 7);
|
||||
weekIndex++;
|
||||
}
|
||||
|
||||
months.push({
|
||||
monthKey: formatDate(monthStart).slice(0, 7),
|
||||
monthLabel: monthLabels[monthStart.getMonth()],
|
||||
weeks,
|
||||
});
|
||||
}
|
||||
|
||||
return months;
|
||||
}
|
||||
|
||||
export function buildCalendarDateRange({ today, startDate, endDate, weekdayLabels }) {
|
||||
const todayDate = parseDate(today);
|
||||
const rangeStart = parseDate(startDate);
|
||||
const rangeEnd = parseDate(endDate);
|
||||
const months = [];
|
||||
let monthDate = new Date(rangeStart.getFullYear(), rangeStart.getMonth(), 1);
|
||||
const lastMonthDate = new Date(rangeEnd.getFullYear(), rangeEnd.getMonth(), 1);
|
||||
|
||||
while (monthDate <= lastMonthDate) {
|
||||
const monthStart = new Date(monthDate.getFullYear(), monthDate.getMonth(), 1);
|
||||
const visibleStart = rangeStart > monthStart ? rangeStart : monthStart;
|
||||
const monthEnd = endOfMonth(monthStart);
|
||||
const visibleEnd = rangeEnd < monthEnd ? rangeEnd : monthEnd;
|
||||
const start = startOfWeek(visibleStart);
|
||||
const end = endOfWeek(visibleEnd);
|
||||
const weeks = [];
|
||||
let cursor = start;
|
||||
|
||||
while (cursor <= end) {
|
||||
const days = weekdayLabels.map((item, dayIndex) => {
|
||||
const date = addDays(cursor, dayIndex);
|
||||
const dateText = formatDate(date);
|
||||
const isOutsideMonth = date < rangeStart || date > rangeEnd || date.getMonth() !== monthStart.getMonth();
|
||||
return {
|
||||
date: dateText,
|
||||
dayNo: isOutsideMonth ? '' : `${date.getDate()}`.padStart(2, '0'),
|
||||
isToday: !isOutsideMonth && dateText === today,
|
||||
isPast: date < todayDate,
|
||||
isOutsideMonth,
|
||||
};
|
||||
});
|
||||
weeks.push({
|
||||
weekKey: formatDate(cursor),
|
||||
days,
|
||||
});
|
||||
cursor = addDays(cursor, 7);
|
||||
}
|
||||
|
||||
months.push({
|
||||
monthKey: formatDate(monthStart).slice(0, 7),
|
||||
monthLabel: monthLabels[monthStart.getMonth()],
|
||||
weeks,
|
||||
});
|
||||
monthDate = addMonths(monthDate, 1);
|
||||
}
|
||||
|
||||
return months;
|
||||
}
|
||||
|
||||
export function buildContinuousCalendarWeeks(months) {
|
||||
const weeks = [];
|
||||
|
||||
months.forEach(month => {
|
||||
month.weeks.forEach((week, weekIndex) => {
|
||||
const days = week.days.map(day => ({ ...day }));
|
||||
const startsMonth = days.some(day => day.dayNo === '01');
|
||||
weeks.push({
|
||||
...week,
|
||||
weekId: `${month.monthKey}-${week.weekKey}`,
|
||||
days,
|
||||
monthLabel: weekIndex === 0 || startsMonth ? month.monthLabel : '',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
return weeks;
|
||||
}
|
||||
|
||||
export function getTodayWeekId(months) {
|
||||
for (const month of months) {
|
||||
for (const week of month.weeks) {
|
||||
if (week.days.some(day => day.isToday && !day.isOutsideMonth)) {
|
||||
return `week-${month.monthKey}-${week.weekKey}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
export function getAvailabilityStatusClass(item) {
|
||||
if (item.count > 0) return 'available';
|
||||
return 'zero';
|
||||
}
|
||||
|
||||
export function getCriteriaStatusClass(item) {
|
||||
if (!item.statusText) return '';
|
||||
return item.qualified ? 'qualified' : 'unqualified';
|
||||
}
|
||||
|
||||
function getRecordValue(record, lowerKey, upperKey) {
|
||||
return record[lowerKey] !== undefined ? record[lowerKey] : record[upperKey];
|
||||
}
|
||||
|
||||
function normalizeRecordDate(value) {
|
||||
if (!value) return '';
|
||||
return String(value).slice(0, 10);
|
||||
}
|
||||
|
||||
function formatCriteria(value) {
|
||||
if (value === undefined || value === null || value === '') return '-';
|
||||
const numberValue = Number(value);
|
||||
if (Number.isNaN(numberValue)) return String(value);
|
||||
return numberValue.toLocaleString('en-US');
|
||||
}
|
||||
|
||||
export function mapRoomCalendarQualificationRecords(records = []) {
|
||||
return records.reduce((map, record) => {
|
||||
const date = normalizeRecordDate(getRecordValue(record, 'date', 'Date'));
|
||||
const roomType = getRecordValue(record, 'roomType', 'RoomType');
|
||||
if (!date || !roomType) return map;
|
||||
|
||||
const flag = Number(getRecordValue(record, 'bookingDateQualificationFlag', 'BookingDateQualificationFlag'));
|
||||
map[`${date}_${roomType}`] = {
|
||||
criteria: formatCriteria(getRecordValue(record, 'roomCriteria', 'RoomCriteria')),
|
||||
count: Number(getRecordValue(record, 'roomCount', 'RoomCount') || 0),
|
||||
leader: getRecordValue(record, 'propertyLeader', 'PropertyLeader') || getRecordValue(record, 'leader', 'Leader') || '',
|
||||
qualified: flag === 1,
|
||||
statusText: flag === 1 ? 'Qualified' : 'Unqualified',
|
||||
};
|
||||
return map;
|
||||
}, {});
|
||||
}
|
||||
|
||||
export function getLeaderForRoomType(roomType) {
|
||||
const leaderMap = {
|
||||
GR: 'Samuel H.',
|
||||
X1: 'Hope C.',
|
||||
X2: 'Clara N.',
|
||||
R1: 'Hugo L.',
|
||||
R2: 'Selina T.',
|
||||
MO_MOGR: 'Hugo L.',
|
||||
MO_SUIT: 'Hugo L.',
|
||||
MO: 'Hugo L.',
|
||||
ASC: 'Clara N.',
|
||||
LMG: 'Selina T.',
|
||||
LAR: 'Kevin L.',
|
||||
YOBT: 'Momo C.',
|
||||
HBV: 'Hope C.',
|
||||
YOHO: 'Samuel H.',
|
||||
};
|
||||
return leaderMap[roomType.code] || 'Kevin L.';
|
||||
}
|
||||
|
||||
export function getOtherPropertyRoomTypes(roomTypes) {
|
||||
const displayNameMap = {
|
||||
ASC: 'Ascott',
|
||||
};
|
||||
return roomTypes.reduce((list, roomType) => {
|
||||
if (roomType.code === 'MO') {
|
||||
list.push(
|
||||
{ ...roomType, code: 'MO_MOGR', label: 'MO', displayName: 'MOGR', parentCode: 'MO' },
|
||||
{ ...roomType, code: 'MO_SUIT', label: 'MO', displayName: 'MO Suit', parentCode: 'MO' },
|
||||
);
|
||||
return list;
|
||||
}
|
||||
list.push({
|
||||
...roomType,
|
||||
displayName: roomType.displayName || displayNameMap[roomType.code],
|
||||
parentCode: roomType.parentCode || roomType.code,
|
||||
});
|
||||
return list;
|
||||
}, []);
|
||||
}
|
||||
Reference in New Issue
Block a user