merge limo to daylight

This commit is contained in:
Mauve
2026-09-22 16:37:00 +08:00
parent 0c92868836
commit a7359d443b
73 changed files with 11614 additions and 1040 deletions
+389 -35
View File
@@ -1,6 +1,6 @@
<script> <script>
import config from './common/config.js' import config from './common/config.js'
import {writeDebugLog,writePublicDebugLog,getSettingByKey,getSettings,verifyToken,logout} from './api/commonApi.js' import {writeDebugLog,getSettings,verifyToken,logout,writePublicDebugLog} from './api/commonApi.js'
import { getCfgSettingByKey } from './api/configApi.js' import { getCfgSettingByKey } from './api/configApi.js'
import store from './store/index.js' import store from './store/index.js'
import FixedCoordinateConverter from './common/fixedCoordinateConverter.js' import FixedCoordinateConverter from './common/fixedCoordinateConverter.js'
@@ -9,7 +9,7 @@
import {refreshToken} from './api/commonApi.js' import {refreshToken} from './api/commonApi.js'
import dayjs from './uni_modules/uview-ui/libs/util/dayjs.js'; import dayjs from './uni_modules/uview-ui/libs/util/dayjs.js';
import { decryptCfgSettingValue } from './common/cfgSettingCrypto.js'; import { decryptCfgSettingValue } from './common/cfgSettingCrypto.js';
import {saveDeviceToken} from './api/notification.js'; import { saveDeviceToken, saveNotification } from './api/notificationApi.js';
// #ifdef APP-PLUS // #ifdef APP-PLUS
var networkModule = uni.requireNativePlugin("NetworkUtils-NetworkUtilsModule") var networkModule = uni.requireNativePlugin("NetworkUtils-NetworkUtilsModule")
// #endif // #endif
@@ -17,7 +17,9 @@
data() { data() {
return { return {
timer: null, // 定时器引用 timer: null, // 定时器引用
backgroundTaskId: 0 // iOS 后台任务ID backgroundTaskId: 0, // iOS 后台任务ID
lastPushNotificationTitle: '',
lastPushNotificationContent: ''
} }
}, },
onLaunch: function() { onLaunch: function() {
@@ -28,22 +30,18 @@
type: 'wgs84', type: 'wgs84',
success: (res) => {} success: (res) => {}
}); });
/*plus.globalEvent.addEventListener('newintent', (e)=>{ // 延迟触发推送注册:避免 plus/原生桥尚未就绪导致 refresh 未真正调到原生(Xcode 看不到 refreshDeviceTokenAsync ENTER)
var args= plus.runtime.arguments; setTimeout(() => {
console.log(args) this.refreshPushDeviceTokenAndSync({ requestAuthorization: true }).catch((err) => {
uni.showToast({ console.warn('[APNs] refreshDeviceTokenAsync failed', err)
title: 'APP LAUNCH-newintent' })
}); }, 1200)
if(args){
var code = this.getSchemeParam('code',args) this.setupPushNotificationListener();
uni.showToast({ setTimeout(() => {
title: code this.syncPendingPushNotificationsFromNative();
}); }, 2000);
uni.redirectTo({
url:'/pages/login/login?code='+encodeURIComponent(code)
})
}
});*/
// #endif // #endif
}, },
onShow: function() { onShow: function() {
@@ -80,12 +78,248 @@
// #endif // #endif
}, },
beforeDestroy() { beforeDestroy() {
// 组件销毁时清理资源
// #ifdef APP-PLUS // #ifdef APP-PLUS
this.stopTimer(); this.stopTimer();
// #endif // #endif
}, },
methods:{ methods: {
parsePushMessageReceivedPayload(rawPayload) {
const root =
rawPayload &&
rawPayload.detail != null &&
typeof rawPayload.detail === 'object'
? rawPayload.detail
: rawPayload;
if (!root || typeof root !== 'object') {
return { notificationTitle: '', notificationContent: '' };
}
let merged = Object.assign({}, root);
let extra = merged.data ?? merged.payload ?? merged.message ?? merged.content;
if (typeof extra === 'string') {
const text = extra.trim();
if (text.startsWith('{') || text.startsWith('[')) {
try {
extra = JSON.parse(text);
} catch (parseError) {
extra = null;
}
} else if (text) {
merged._dataStringFallback = text;
}
}
if (extra != null && typeof extra === 'object' && !Array.isArray(extra)) {
merged = Object.assign({}, merged, extra);
}
const aps = merged.aps;
let alertBody = '';
let alertTitle = '';
if (aps && typeof aps.alert === 'object' && aps.alert !== null) {
alertTitle = String(aps.alert.title || '').trim();
alertBody = String(
aps.alert.body ?? aps.alert.subtitle ?? ''
).trim();
} else if (aps && typeof aps.alert === 'string') {
alertBody = String(aps.alert).trim();
}
const notificationTitle =
String(
alertTitle ||
merged.title ||
merged.Title ||
merged.notificationTitle ||
merged.notification_title ||
''
).trim();
const notificationContent = String(
alertBody ||
merged.body ||
merged.content ||
merged.Content ||
merged.message ||
merged.Message ||
merged.text ||
merged.description ||
merged._dataStringFallback ||
''
).trim();
return { notificationTitle, notificationContent };
},
getCachedUserId() {
const userData = uni.getStorageSync('userData');
if (
userData != null &&
userData !== '' &&
userData != undefined &&
userData.userId != null &&
userData.userId !== ''
) {
return userData.userId;
}
return '';
},
isUserLoggedIn() {
const user = uni.getStorageSync('userData');
const token = uni.getStorageSync('token');
const clientId = uni.getStorageSync('clientId');
const timeOut = uni.getStorageSync('timeOut');
const now = Math.floor(new Date().getTime() / 1000);
let loginTag = false;
if (user) {
loginTag = user.loginTag === true;
}
if (
token == undefined ||
token == null ||
token === '' ||
clientId == undefined ||
clientId == null ||
clientId === ''
) {
loginTag = false;
}
if (timeOut != null && timeOut !== '' && now >= timeOut) {
loginTag = false;
}
return loginTag === true;
},
getPushNotificationPageUrl() {
return '/limo/notification/notification';
},
getPushConfirmNo(payload = {}) {
const confirmNo = payload.confirmNo || payload.raw?.ConfirmNo || payload.raw?.confirmNo || payload.raw?.confNo;
return confirmNo ? String(confirmNo) : '';
},
getPushTargetUrl(payload = {}) {
const confirmNo = this.getPushConfirmNo(payload);
if (confirmNo) {
return `/limo/bookingDetail/bookingDetail?confNo=${encodeURIComponent(confirmNo)}&from=push`;
}
return this.getPushNotificationPageUrl();
},
setPendingPushNavigation(url) {
if (!url) {
return;
}
uni.setStorageSync('pendingPushNavigation', url);
console.log('[Push] pending navigation saved:', url);
},
tryConsumePendingPushNavigation() {
const pendingUrl = uni.getStorageSync('pendingPushNavigation');
if (!pendingUrl) {
return false;
}
if (!this.isUserLoggedIn()) {
return false;
}
uni.removeStorageSync('pendingPushNavigation');
console.log('[Push] consume pending navigation:', pendingUrl);
this.navTo(pendingUrl, 0, false);
return true;
},
shouldOpenNotificationPageFromPush(payload = {}, meta = {}) {
return meta.userTapped === true || payload.userTapped === true;
},
navigateToNotificationPageFromPush(payload = {}) {
const targetUrl = this.getPushTargetUrl(payload);
console.log('[Push] navigateToNotificationPageFromPush targetUrl=', targetUrl, 'confirmNo=', this.getPushConfirmNo(payload));
if (!this.isUserLoggedIn()) {
this.setPendingPushNavigation(targetUrl);
uni.redirectTo({
url: '/pages/login/login'
});
return;
}
this.navTo(targetUrl, 0, false);
},
queuePushForSaveLater(title, content) {
const queue = uni.getStorageSync('pendingPushSaveQueue') || []
queue.push({ title, content, at: Date.now() })
uni.setStorageSync('pendingPushSaveQueue', queue)
console.warn('[Push] userId 未就绪,已暂存待 saveNotification,条数=', queue.length)
},
async flushPendingPushSaveQueue() {
const userId = this.getCachedUserId()
if (!userId) {
return
}
const queue = uni.getStorageSync('pendingPushSaveQueue') || []
if (!queue.length) {
return
}
uni.removeStorageSync('pendingPushSaveQueue')
console.log('[Push] flushPendingPushSaveQueue count=', queue.length)
for (const item of queue) {
try {
await saveNotification({
title: item.title,
content: item.content,
userId
})
console.log('[Push] saveNotification success (flushed)', item.title)
} catch (saveError) {
console.error('[Push] saveNotification failed (flushed)', saveError)
}
}
},
async persistReceivedPushNotification(notificationTitle, notificationContent) {
const userId = this.getCachedUserId();
if (!userId) {
this.queuePushForSaveLater(notificationTitle, notificationContent)
return;
}
try {
await saveNotification({
title: notificationTitle,
content: notificationContent,
userId
});
console.log('[Push] saveNotification success', {
title: notificationTitle,
content: notificationContent,
userId
});
} catch (saveError) {
console.error('[Push] saveNotification failed', saveError);
}
},
setupGlobalEventListeners() {
if (this._globalPushHookRegistered) {
return;
}
this._globalPushHookRegistered = true;
uni.$on('onPushMessageReceived', (rawPayload) => {
try {
const { notificationTitle, notificationContent } =
this.parsePushMessageReceivedPayload(rawPayload);
this.lastPushNotificationTitle = notificationTitle;
this.lastPushNotificationContent = notificationContent;
console.log(
'[Push] onPushMessageReceived',
notificationTitle || '(no title)',
notificationContent || '(no content)',
rawPayload
);
this.persistReceivedPushNotification(
notificationTitle,
notificationContent
);
const toastText =
notificationTitle ||
notificationContent ||
'Received push message';
uni.showToast({
title:
toastText.length > 36
? `${toastText.slice(0, 33)}…`
: toastText,
icon: 'none',
duration: 2800
});
} catch (handlerError) {
console.error('[Push] handler error', handlerError, rawPayload);
}
});
},
startTimer() { startTimer() {
// 先清除可能存在的旧定时器 // 先清除可能存在的旧定时器
this.stopTimer(); this.stopTimer();
@@ -135,6 +369,10 @@
} }
this.macauString = res.data; this.macauString = res.data;
uni.setStorageSync("macauString",this.macauString.keyValues); uni.setStorageSync("macauString",this.macauString.keyValues);
// uni.showToast({
// title:'Macau String = '+this.macauString.keyValues,
// icon: "none"
// })
}catch(e){ }catch(e){
console.log(e); console.log(e);
}finally{ }finally{
@@ -168,13 +406,14 @@
plus.screen.lockOrientation("portrait"); plus.screen.lockOrientation("portrait");
// #endif // #endif
await this.initCfg(); // 获取配置参数 await this.initCfg(); // 获取配置参数
this.setupGlobalEventListeners();
}, },
// 初始化方法 // 初始化方法
async initCfg(){ async initCfg(){
// 配置站点信息 // 配置站点信息
const cfg = new config(); const cfg = new config();
// 判断外网还是内网 // 判断外网还是内网
// 現在內外網改成由後端判斷 // 現在內外網改成由後端判斷
// 如果是外网 // 如果是外网
const userData = uni.getStorageSync('userData'); const userData = uni.getStorageSync('userData');
@@ -371,10 +610,6 @@
}, },
//根据Permission显示Tabbar //根据Permission显示Tabbar
showTabbarByPermission(userData){ showTabbarByPermission(userData){
uni.setTabBarItem({
index: 3,
visible: !!userData && userData.canViewCalendar === true
});
if(userData!==null && userData!==undefined){ if(userData!==null && userData!==undefined){
console.log('Setting Tabbar'); console.log('Setting Tabbar');
if(userData.canViewDashboard){ if(userData.canViewDashboard){
@@ -395,6 +630,12 @@
visible:true visible:true
}); });
} }
if(userData.canViewCalendar){
uni.setTabBarItem({
index:3,
visible:true
});
}
} }
}, },
// 返回 // 返回
@@ -421,6 +662,110 @@
}); });
}, },
/** 从原生读取当前缓存的 APNs DeviceToken,并写入本地 Storage(供业务上报) */
syncPushDeviceTokenFromNative() {
getDeviceToken({})
.then(async (ret) => {
console.log('[APNs] getDeviceToken', ret.status, ret.updatedAt);
const deviceToken = ret.deviceToken ? String(ret.deviceToken).trim() : '';
let useLanguage = 0;
//获取AppSetting,后端可以判断发中文还是发英文 英文:0 中文:1 默认为0
if(uni.getLocale()!==null && uni.getLocale()!==undefined){
useLanguage = uni.getLocale()=='en'? 0 : 1;
}
if (deviceToken) {
const userId = this.getCachedUserId();
console.log('[APNs] saveDeviceToken params', {
userId,
deviceToken,
rawUserData: uni.getStorageSync('userData'),
rawRet: ret,
useLanguage
});
this.writeLog(
userId,
'syncPushDeviceTokenFromNative',
'',
'[APP][App.vue][getDevice Token][Success]' + deviceToken,
'',
'',
1,
false
);
uni.setStorageSync('apnsDeviceToken', deviceToken);
uni.setStorageSync('apnsDeviceTokenUpdatedAt', ret.updatedAt || 0);
try {
const res = await saveDeviceToken({ userId, deviceToken, useLanguage});
console.log('[APNs] saveDeviceToken success', res);
} catch (saveErr) {
console.error('[APNs] saveDeviceToken failed', saveErr);
}
}
})
.catch((e) => {
console.warn('[APNs] getDeviceToken fail', e);
uni.showToast({
title: 'error = ' + e,
icon: 'none'
});
const userId = this.getCachedUserId();
this.writeLog(
userId,
'syncPushDeviceTokenFromNative',
'',
'[APP][App.vue][getDevice Token][Fail] Error Message :' + e,
'',
'',
1,
false
);
});
},
/**
* 触发 refreshDeviceTokenAsync(重新向 APNs 注册),随后轮询 getDeviceToken。
* @param {{ requestAuthorization?: boolean }} opts 默认可不传;已授权场景可传 { requestAuthorization: false }
*/
refreshPushDeviceTokenNative(opts) {
return refreshDeviceTokenAsync(opts || { requestAuthorization: true })
},
/** 刷新并尽量拿到新 token(适合服务端返回推送失效时调用) */
refreshPushDeviceTokenAndSync(opts) {
return refreshDeviceTokenAndFetch(opts || { requestAuthorization: true }).then((ret) => {
if (ret.deviceToken) {
uni.setStorageSync('apnsDeviceToken', ret.deviceToken)
uni.setStorageSync('apnsDeviceTokenUpdatedAt', ret.updatedAt || 0)
}
return ret
})
},
setupPushNotificationListener() {
listenPushNotification((payload, meta) => {
this.handlePushNotificationMessage(payload, meta)
})
},
async handlePushNotificationMessage(payload = {}, meta = {}) {
const { title, content } = payload
this.lastPushNotificationTitle = title
this.lastPushNotificationContent = content
await this.persistReceivedPushNotification(title, content)
console.log('[Push] handlePushNotificationMessage userTapped=', payload.userTapped, meta?.userTapped, 'confirmNo=', this.getPushConfirmNo(payload))
if (this.shouldOpenNotificationPageFromPush(payload, meta)) {
this.navigateToNotificationPageFromPush(payload)
}
},
async syncPendingPushNotificationsFromNative() {
console.log('[Push] syncPendingPushNotificationsFromNative')
await syncPendingPushNotifications((payload, meta) => {
this.handlePushNotificationMessage(payload, {
...meta,
showToast: meta?.fromPending ? false : meta?.showToast
})
}, { showToast: false })
await this.flushPendingPushSaveQueue()
},
checkLocationAndVPN(type){ checkLocationAndVPN(type){
let objVPNData = this.checkVPNOpen(); let objVPNData = this.checkVPNOpen();
let isOpenVPN = false; let isOpenVPN = false;
@@ -472,10 +817,9 @@
icon: "none" icon: "none"
})*/ })*/
this.writeLog(userId,"getLocationVPN","","[APP][App.vue][checkLocationAndVPN][Success]"+result.isInMacau+ ", city:"+result.city+ ", address:"+result.fullAddress+ ", IP address:"+ipAddress,"","",1,false); this.writeLog(userId,"getLocationVPN","","[APP][App.vue][checkLocationAndVPN][Success]"+result.isInMacau+ ", city:"+result.city+ ", address:"+result.fullAddress+ ", IP address:"+ipAddress,"","",1,false);
/*uni.showToast({
title:'是否在澳门:'+isInMacau+',是否使用VPN:'+isOpenVPN,
icon: "none"
})*/
if(!isInMacau){ if(!isInMacau){
let macauStringList = uni.getStorageSync("macauString").split(";"); let macauStringList = uni.getStorageSync("macauString").split(";");
for(let i=0;i<macauStringList.length;i++){ for(let i=0;i<macauStringList.length;i++){
@@ -488,8 +832,12 @@
} }
} }
} }
//isInMacau = true;
//模擬數據 模拟数据
// isInMacau = true;
// isOpenVPN = false; // isOpenVPN = false;
//模擬數據 模拟数据
if((isInMacau===null||isInMacau===undefined) && isOpenVPN!==null){ //调用地址出错 if((isInMacau===null||isInMacau===undefined) && isOpenVPN!==null){ //调用地址出错
msgType = "1"; msgType = "1";
} }
@@ -572,6 +920,8 @@
return; return;
} }
} }
const token = uni.getStorageSync("token");
let obj = {}; let obj = {};
obj.loginId = userId; obj.loginId = userId;
@@ -585,8 +935,11 @@
//console.log(obj); //console.log(obj);
try{ try{
const token = uni.getStorageSync('token'); if(token != undefined && token != null && token !=""){
const response = token ? await writeDebugLog(obj) : await writePublicDebugLog(obj); const response = await writeDebugLog(obj);
}else{
const response = await writePublicDebugLog(obj);
}
} }
catch(e){ catch(e){
console.error(e.errMsg); console.error(e.errMsg);
@@ -616,7 +969,6 @@
index:2, index:2,
visible:false visible:false
}); });
uni.setTabBarItem({ index: 3, visible: false });
if(siteData.mockSSO==false&&!siteData.internalTag){ if(siteData.mockSSO==false&&!siteData.internalTag){
//this.logoutWithADFSApp() //this.logoutWithADFSApp()
uni.redirectTo({ uni.redirectTo({
@@ -737,6 +1089,8 @@
} }
}, },
kickout() { kickout() {
this.logout();
uni.removeStorageSync("userData"); uni.removeStorageSync("userData");
uni.removeStorageSync("token"); uni.removeStorageSync("token");
uni.removeStorageSync("idToken"); uni.removeStorageSync("idToken");
+1
View File
@@ -2,6 +2,7 @@ export const getBookingsMock = (params, config = {}) => uni.$u.http.post('/app/b
export const saveBooking = (params, config = {}) => uni.$u.http.post('/app/booking/create', params, config) export const saveBooking = (params, config = {}) => uni.$u.http.post('/app/booking/create', params, config)
export const getBookings = (params, config = {}) => uni.$u.http.post('/app/bookings/search_optimize', params, config) export const getBookings = (params, config = {}) => uni.$u.http.post('/app/bookings/search_optimize', params, config)
export const getBookingByConf = (params, config = {}) => uni.$u.http.post('/app/booking/get_booking_by_conf', params, config) export const getBookingByConf = (params, config = {}) => uni.$u.http.post('/app/booking/get_booking_by_conf', params, config)
export const getLimoBookings = (params, config = {}) => uni.$u.http.post('/limo/search_limo', params, config)
export const getBookingById = (params, config = {}) => uni.$u.http.post('/app/booking/get_booking_by_id', params, config) export const getBookingById = (params, config = {}) => uni.$u.http.post('/app/booking/get_booking_by_id', params, config)
export const editBooking = (params, config = {}) => uni.$u.http.post('/app/booking/edit', params, config) export const editBooking = (params, config = {}) => uni.$u.http.post('/app/booking/edit', params, config)
export const getBookingsByGroupId = (params, config = {}) => uni.$u.http.post('/app/booking/get_bookings_by_groupId', params, config) export const getBookingsByGroupId = (params, config = {}) => uni.$u.http.post('/app/booking/get_bookings_by_groupId', params, config)
-1
View File
@@ -1,6 +1,5 @@
export const writeDebugLog = (params, config = {}) => uni.$u.http.post('/system/write_log', params, config) export const writeDebugLog = (params, config = {}) => uni.$u.http.post('/system/write_log', params, config)
export const writePublicDebugLog = (params, config = {}) => uni.$u.http.post('/system/write_public_log', params, config) export const writePublicDebugLog = (params, config = {}) => uni.$u.http.post('/system/write_public_log', params, config)
export const getSettingByKey = (params, config = {}) => uni.$u.http.post('/system/sys_setting/get', params, config)
export const getSettings = (params, config = {}) => uni.$u.http.post('/system/sys_setting/get_public_login_settings', params, config) export const getSettings = (params, config = {}) => uni.$u.http.post('/system/sys_setting/get_public_login_settings', params, config)
export const verifyToken = (params, config = {}) => uni.$u.http.post('/app/me', params, config) export const verifyToken = (params, config = {}) => uni.$u.http.post('/app/me', params, config)
export const refreshToken = (params, config = {}) => uni.$u.http.post('/app/refresh_token?isRenewRefreshToken=false', params, config) export const refreshToken = (params, config = {}) => uni.$u.http.post('/app/refresh_token?isRenewRefreshToken=false', params, config)
+1 -1
View File
@@ -10,4 +10,4 @@ export const getAvailableRoom = (params, config = {}) => uni.$u.http.post('/app/
export const getExtendBooking = (params, config = {}) => uni.$u.http.post('/app/config/is_extend_booking', params, config) export const getExtendBooking = (params, config = {}) => uni.$u.http.post('/app/config/is_extend_booking', params, config)
export const getWaitlistBooking = (params, config = {}) => uni.$u.http.post('/app/config/get_waitlist_booking', params, config) export const getWaitlistBooking = (params, config = {}) => uni.$u.http.post('/app/config/get_waitlist_booking', params, config)
export const getCfgSettingByKey = (params, config = {}) => uni.$u.http.post('/web/setting/get_public_setting_by_key', params, config) export const getCfgSettingByKey = (params, config = {}) => uni.$u.http.post('/web/setting/get_public_setting_by_key', params, config)
export const getCalendarSetting = (params, config = {}) => uni.$u.http.post('/app/setting/get_calendar_setting', params, config) export const getCalendarSetting = (params, config = {}) => uni.$u.http.post('/app/setting/get_calendar_setting', params, config)
+22
View File
@@ -0,0 +1,22 @@
export const getLimoTripPriceList = (params, config = {}) => uni.$u.http.post('/limo/getTripPriceList', params, config)
export const getLimoLocation = (params, config = {}) =>
uni.$u.http.get('/limo/getLocation', { params, ...config })
export const getLimoCarType= (params, config = {}) =>
uni.$u.http.get('/limo/getCarType', { params, ...config })
export const getLimoGreetType = (params, config = {}) =>
uni.$u.http.get('/limo/getGreetType', { params, ...config })
export const getQualification = (params, config = {}) =>
uni.$u.http.get('/limo/checkQualification', { params, ...config })
export const getLimoOverrider = (params, config = {}) =>
uni.$u.http.get('/limo/getOverrider', { params, ...config })
export const getLimoApprover = (params, config = {}) =>
uni.$u.http.get('/limo/playerLimoBooker', { params, ...config })
export const getLimoFlightDetails = (params, config = {}) =>
uni.$u.http.get('/limo/getFlightDetails', { params, ...config })
export const getLimoTodayBooking = (params, config = {}) => uni.$u.http.post('/limo/getTodayBooking', params, config)
export const createLimoBooking = (params, config = {}) => uni.$u.http.post('/limo/createBooking', params, config)
export const prepareBooking = (params, config = {}) => uni.$u.http.post('/limo/prepareBooking', params, config)
export const editLimoBooking = (params, config = {}) => uni.$u.http.post('/limo/updateBooking', params, config)
export const cancelLimoBooking = (params, config = {}) => uni.$u.http.post('/limo/cancelBooking', params, config)
export const getLimoBookingById = (params, config = {}) => uni.$u.http.post('/limo/get_limo_booking_by_id', params, config)
export const getLimoBookingByConf = (params, config = {}) => uni.$u.http.post('/limo/get_limo_booking_by_conf', params, config)
+9
View File
@@ -0,0 +1,9 @@
export const saveNotification = (params, config = {}) => uni.$u.http.post('/notification/saveNotification', params, config)
export const getNotificationByUserId = (params, config = {}) =>
uni.$u.http.get('/notification/get_notification_by_user_id', { params, ...config })
export const changeNotification = (params, config = {}) => uni.$u.http.post('/notification/change_status', params, config)
export const readAllNotification = (params, config = {}) => uni.$u.http.post('/notification/read_all', params, config)
export const countNotification = (params, config = {}) =>
uni.$u.http.get('/notification/count_notification_by_user_id', { params, ...config })
export const saveDeviceToken = (params, config = {}) => uni.$u.http.post('/deviceToken/save_device_token', params, config)
export const updateDeviceToken = (params, config = {}) => uni.$u.http.post('/deviceToken/update_device_token', params, config)
@@ -1883,3 +1883,4 @@
border-radius: 22px; border-radius: 22px;
} }
</style> </style>
+1 -1
View File
@@ -502,7 +502,7 @@
this.getBooking(this.opt); this.getBooking(this.opt);
}else{ }else{
this.iconPath = '../../static/images/ico_err.png'; this.iconPath = '../../static/images/ico_err.png';
this.popMsgStr = this.$t("bookingDetail.changeMiscChargeFail"); this.popMsgStr = this.$t("bookingDetail.changeMisChargeFail");
this.showMsgBox = true; this.showMsgBox = true;
this.showETAMsg = false; this.showETAMsg = false;
+462 -120
View File
@@ -18,88 +18,138 @@
<view style="border:1rpx solid #ccc;border-radius: 14px;padding:4px 12px;" class="hz-flex-row hz-flex-vertical-mid"> <view style="border:1rpx solid #ccc;border-radius: 14px;padding:4px 12px;" class="hz-flex-row hz-flex-vertical-mid">
<image @click="navTo('/pages/findPlayer/findPlayer',1,false)" src="../../static/images/ico_find_player.png" class="hz-nav-btn"></image> <image @click="navTo('/pages/findPlayer/findPlayer',1,false)" src="../../static/images/ico_find_player.png" class="hz-nav-btn"></image>
<view style="height:20px;border-left:1px solid #ccc;margin-left:8px;margin-right:8px;"></view> <view style="height:20px;border-left:1px solid #ccc;margin-left:8px;margin-right:8px;"></view>
<image v-if="userData && userData.canViewCalendar === true" @click="navToPlayerRoomCalendar" src="../../static/images/calendar-line.png" class="hz-nav-btn"></image> <image @click="navToPlayerRoomCalendar" src="../../static/images/calendar-line.png" class="hz-nav-btn"></image>
<image v-else @click="getDataByPage" src="../../static/images/ico_list.png" class="hz-nav-btn"></image>
</view> </view>
</view> </view>
</u-navbar> </u-navbar>
<view class="hz-spacing hz-bg-white" style="margin-top:8px;">
<view class="hz-card-profile hz-mr-b-30"> <!-- booking record page -->
<view class="hz-spacing hz-bg-white mt-[20]">
<view class="hz-card-profile mb-[40]">
<view class="hz-flex-row"> <view class="hz-flex-row">
<view style="width:60%"> <view class="hz-w-60">
<view class="hz-font-color-light hz-font-sm">{{$t("common.playerId")}}</view> <view class="hz-font-color-light hz-font-sm">{{$t("common.playerId")}}</view>
<view class="hz-font-bolder hz-font-lg" >{{ playerData.playerId }}</view> <view class="hz-font-bolder hz-font-lg" >{{ playerData.playerId }}</view>
</view> </view>
<view style="width:40%"> <view class="hz-w-40">
<view class="hz-font-color-light hz-font-sm">{{$t("common.tier")}}</view> <view class="hz-font-color-light hz-font-sm">{{$t("common.tier")}}</view>
<view class="hz-font-bolder hz-font-lg" >{{ playerData.tierName }}</view> <view class="hz-font-bolder hz-font-lg" >{{ playerData.tierName }}</view>
</view> </view>
</view> </view>
<view class="hz-flex-row hz-mr-t-30"> <view class="hz-flex-row hz-mr-t-30">
<view style="width:100%"> <view class="hz-w-100">
<view class="hz-font-color-light hz-font-sm">{{$t("common.name")}}</view> <view class="hz-font-color-light hz-font-sm">{{$t("common.name")}}</view>
<view class="hz-font-bolder hz-font-lg" >{{playerData.salutation + " " + playerData.name }}</view> <view class="hz-font-bolder hz-font-lg" >{{playerData.salutation + " " + playerData.name }}</view>
</view> </view>
</view> </view>
</view> </view>
<view v-if="!showEmpty">
<!-- 'hz-card-extend' disable extend --> <view>
<view @click="navToDetail(booking)" v-for="(booking,index) in bookingList" :key="index" <view v-for="(record,index) in displayRecordList" :key="`${record.recordType}-${record.id || record.confirmationNo || index}`">
:class="getRecordColor(booking)" class="hz-pd-30 hz-font-bold hz-font-sm" style="margin-top:20rpx;align-items: center;"> <view @click="openRecordDetail(record)" :class="getRecordCardClass(record)" class="hz-card-booking hz-record-card hz-pd-30 hz-font-bold hz-font-sm">
<view class="hz-flex-row"> <view class="hz-flex-row hz-booking-type-row">
<view> <view class="hz-booking-type-wrap">
<view class="hz-font-md">{{$t("common.confirmationNo")}}: {{ getConfirmationNo(booking)}}</view> <image :src="getBookingTypeIcon(record)" class="hz-booking-type-icon"></image>
<view class="hz-font-md">{{ getBookingTypeLabel(record) }}</view>
</view>
<view class="hz-font-md hz-text-right" :class="getRecordStatusClass(record)">{{getRecordStatusText(record)}}</view>
</view> </view>
<view style="margin-left:auto"> <view v-if="isHotelRecord(record)">
<view class="hz-font-md" :class="getStatusTextColor(booking)">{{getStatusText(booking)}}</view> <view class="hz-flex-row hz-row-gap">
<view class="hz-w-50">
<view class="hz-font-md">{{$t("common.confirmationNo")}}: {{ getConfirmationNo(record)}}</view>
</view>
<view class="hz-w-50">
<view class="hz-font-md hz-text-right">{{ record.propertyName }}</view>
</view>
</view>
<view class="hz-flex-row hz-row-gap">
<view class="hz-w-55">
<view class="hz-font-md">{{$t("common.checkInDate")}}: {{ record.checkinDate }}</view>
</view>
</view>
<view class="hz-flex-row hz-row-gap">
<view class="hz-w-55">
<view class="hz-font-md">{{$t("common.checkOutDate")}}: {{ record.checkoutDate }}</view>
</view>
<view class="hz-flex-row hz-sharer-wrap" v-if="canShowSharer(record)">
<view v-if="hasSharer(record)" class="hz-btn-sharer hz-font-min hz-sharer-tag">{{$t("bookingRecord.sharer").toUpperCase()}}</view>
</view>
</view>
<!-- <view class="hz-flex-row hz-row-gap">
<view class="hz-w-55 hz-font-md" v-show="record.eta!==null && record.eta!=='00:00:00'">{{$t("common.etaTime")}}: {{formatEtaToHHmm(record.eta)}}</view>
<view v-if="record.miscCharge != null" class="hz-flex-row hz-sharer-wrap">
<view>
<view class="hz-font-md">{{$t("common.misCharge")}}: {{ formatWithThousands(record.miscCharge) }}</view>
</view>
</view>
</view> -->
<view class="hz-flex-row" style="margin-top:6px;">
<view v-show="record.eta!==null && record.eta!=='00:00:00'" class="hz-font-md">{{$t("common.etaTime")}}: {{formatEtaToHHmm(record.eta)}}</view>
<span v-show="record.eta!==null && record.eta!=='00:00:00' && record.miscCharge!==null" class="hz-font-md" style="padding:0 6px;">|</span>
<view v-show="record.miscCharge!==null" class="hz-font-md">{{$t("common.misCharge")}}: {{formatWithThousands(record.miscCharge)}}</view>
</view>
</view> </view>
</view> <!-- 不然就是 limo 的 booking -->
<view class="hz-flex-row" style="margin-top:6px;"> <view v-else>
<view style="width:55%;"> <view class="hz-flex-row hz-row-gap">
<view class="hz-font-md">{{$t("common.checkInDate")}}: {{ booking.checkinDate }}</view> <view class="hz-w-100">
<view class="hz-font-md">Conf # : {{ getConfirmationNo(record) }}</view>
</view>
</view>
<view class="hz-flex-row hz-row-gap">
<view class="hz-w-100">
<view class="hz-font-md">{{$t("limoBookingRecord.pickupDatetime")}}: {{ record.pickupDatetime }}</view>
</view>
</view>
<view class="hz-flex-row hz-row-gap">
<view class="hz-w-100">
<view class="hz-font-md">{{$t("limoBookingRecord.pickup")}}: {{ record.pickup }}</view>
</view>
</view>
<view v-if="record.extraDropoff && record.extraDropoff.length" class="hz-flex-row hz-row-gap">
<view class="hz-w-100">
<view class="hz-font-md">{{$t("limoBookingRecord.extraDropoff")}}:</view>
<view class="hz-font-md" v-for="(dropoffPoint,index) in record.extraDropoff" :key="index">
#{{index + 1}}:{{dropoffPoint}}
</view>
</view>
</view>
<view class="hz-flex-row hz-row-gap hz-flex-vertical-mid">
<view class="hz-w-100 hz-limo-dropoff-wrap">
<view class="hz-font-md">
{{$t("limoBookingRecord.dropoff")}}: {{record.dropoff}}
</view>
</view>
</view>
</view> </view>
<view style="width:50%;">
<view style="text-align: right;" class="hz-font-md">{{ booking.propertyName }}</view>
</view>
</view>
<view class="hz-flex-row" style="margin-top:6px;">
<view style="width:55%;">
<view class="hz-font-md">{{$t("common.checkOutDate")}}: {{ booking.checkoutDate }}</view>
</view>
<!-- enable sharer and extend 2024-09-27 -->
<view class="hz-flex-row" style="width:50%;text-align: right;justify-content: flex-end;" v-if="userData.canNewBooking&&booking.propertyCategory!='OTHER'">
<view v-if="(booking.sharer!==null && booking.sharer!=='[]')||booking.isShare" class="hz-btn-sharer hz-font-min" style="width:40%;height:14px;line-height:14px;margin-right: 3px;">{{$t("bookingRecord.sharer").toUpperCase()}}</view>
<!-- <view @click="navToDetail(booking)" v-if="((booking.lastOperaStatus==='DUE_OUT' || booking.lastOperaStatus==='CHECKED_IN') && booking.confirmationNo!=null && (booking.approveStatus!='W'&&booking.approveStatus!='PN'))" class="hz-btn-extend hz-font-min" style="width:40%;height:14px;line-height:14px;">{{$t("bookingRecord.extend").toUpperCase()}}</view> -->
<!-- <view @click="navTo('/booking/bookingGroup/bookingGroup?groupId='+booking.groupId,0,false)" v-if="booking.groupId!==undefined && booking.groupId!==null && booking.groupId!==''" class="hz-btn-group" style="margin-left:8px;">{{$t("bookingRecord.group")}}</view> -->
</view>
</view>
<view class="hz-flex-row" style="margin-top:6px;">
<view v-show="booking.eta!==null && booking.eta!=='00:00:00'" class="hz-font-md">{{$t("common.etaTime")}}: {{formatEtaToHHmm(booking.eta)}}</view>
<span v-show="booking.eta!==null && booking.eta!=='00:00:00' && booking.miscCharge!==null" class="hz-font-md" style="padding:0 6px;">|</span>
<view v-show="booking.miscCharge!==null" class="hz-font-md">{{$t("common.misCharge")}}: {{formatWithThousands(booking.miscCharge)}}</view>
</view> </view>
</view> </view>
<!-- <u-loadmore :status="showLoadMore" :loadmoreText="loadMoreStr" :nomoreText="nomoreStr" :fontSize="14" color="#ccc" :line="true" /> -->
</view> </view>
<u-empty
v-if="showEmpty" <!-- <u-empty
v-if="isDisplayEmpty"
mode="search" mode="search"
:text="dataNotFoundStr" :text="dataNotFoundStr"
> >
</u-empty> </u-empty> -->
</view> </view>
<u-tabbar <u-tabbar
:fixed="true" :fixed="true"
:placeholder="true" :placeholder="true"
:safeAreaInsetBottom="true" :safeAreaInsetBottom="true"
customStyle="z-index:100;height:58px;" customStyle="z-index:100;height:58px;"
v-if="userData.canNewBooking"
> >
<view class="book hz-flex-row hz-spacing"> <view v-if="userData.canNewBooking" class="book hz-flex-row hz-spacing hz-book-btn-wrap" :class="{'hz-book-btn-wrap--single': hasSingleBookingPermission}">
<view class="hz-btn hz-font-color-white hz-font-lg hz-font-bold hz-text-center" @click="navToNewBooking()"> <view class="hz-btn hz-font-color-white hz-font-sm hz-font-bold hz-text-center" @click="navToNewBooking()">
{{$t("bookingRecord.newsBooking")}} {{$t("bookingRecord.newsBooking")}}
</view> </view>
</view> </view>
<view v-if="userData.canViewLimo" class="book hz-flex-row hz-spacing hz-book-btn-wrap" :class="{'hz-book-btn-wrap--single': hasSingleBookingPermission}">
<view class="hz-btn hz-font-color-white hz-font-sm hz-font-bold hz-text-center" @click="navToNewLimo()">
{{$t("bookingRecord.newLimo")}}
</view>
</view>
</u-tabbar> </u-tabbar>
<u-modal :content="msgStr" :show="showMsg" :confirmText="yesText" :showCancelButton="false" closeOnClickOverlay @confirm="showMsg=false" confirmColor="#C39A4B" @close="showMsg=false"></u-modal> <u-modal :content="msgStr" :show="showMsg" :confirmText="yesText" :showCancelButton="false" closeOnClickOverlay @confirm="showMsg=false" confirmColor="#C39A4B" @close="showMsg=false"></u-modal>
</view> </view>
@@ -109,7 +159,7 @@
<script> <script>
import {playerLookup} from '../../api/mgmApi.js' import {playerLookup} from '../../api/mgmApi.js'
import {mapOperaStatus,mapApproveStatus} from '../../common/mapping.js' import {mapOperaStatus,mapApproveStatus} from '../../common/mapping.js'
import {getBookingsMock,getBookings} from '../../api/bookingApi.js'; import {getBookings, getLimoBookings} from '../../api/bookingApi.js';
import dayjs from '../../uni_modules/uview-ui/libs/util/dayjs.js'; import dayjs from '../../uni_modules/uview-ui/libs/util/dayjs.js';
export default { export default {
data() { data() {
@@ -121,15 +171,14 @@
dataNotFoundStr:this.$t("common.dataNotFound"), dataNotFoundStr:this.$t("common.dataNotFound"),
// 界面变量 // 界面变量
from:'', from:'',
showEmpty:false,
title:'', title:'',
footFuncHei:100,
currentIndex:0,
showMsg:false, showMsg:false,
msgStr:this.$t("bookingRecord.operaProfile"), msgStr:this.$t("bookingRecord.operaProfile"),
yesText:this.$t("common.ok"), yesText:this.$t("common.ok"),
currentTask:null, currentTask:null,
getDataByPageRequestId:0, getDataByPageRequestId:0,
limoBookingList:[
]
} }
}, },
onLoad(opt) { onLoad(opt) {
@@ -145,6 +194,8 @@
// 检查是否登录 // 检查是否登录
this.isLogin(); this.isLogin();
// 拿localstorage中的playerData // 拿localstorage中的playerData
//修改MiscCharge后 返回页面自动刷新
// this.refreshHotelBooking();
this.getDataByPage(); this.getDataByPage();
}, },
@@ -153,49 +204,109 @@
this.pullDownRefresh(); this.pullDownRefresh();
}, },
methods: { methods: {
navToPlayerRoomCalendar(){ updateHotelBookingList(res){
const userData = uni.getStorageSync("userData"); if(Array.isArray(res) && res.length > 0){
if (!userData || userData.canViewCalendar !== true) return; this.bookingList = res;
this.navTo('/booking/playerRoomCalendar/playerRoomCalendar',0,false); }else{
this.bookingList = [];
}
}, },
updateLimoBookingList(res) {
if(this.userData.canViewLimo==false) return;
if (Array.isArray(res) && res.length > 0) {
let temp = res.map(item => {
const pickUpDatetime = item.pickUpDatetime || item.pickUpDateTime || item.pickupDatetime || '';
return {
id: item.id,
recordType: 'limo',
confirmationNo: item.confirmationNo,
status: item.bookingStatus,
pickUpDatetime,
pickupDatetime: this.formatDate(pickUpDatetime, 'YYYY-MM-DD HH:mm'),
pickup: item.pickUpPoint,
dropoff: item.dropOffPoint,
extraDropoff:this.getExtraDropOff(item.extraDropOffPoint)
// extraDropoff:item.extraDropOffPoint.split(";")
}
});
this.limoBookingList = temp;
} else {
this.limoBookingList = [];
}
},
openBookingPage(path){
uni.showLoading({
title: this.$t("common.loading")
});
this.navTo(path,0,false);
uni.hideLoading();
},
// 數據方法 // 數據方法
async getDataByPage(){ async getDataByPage(){
if(this.userData.canViewBooking==false) return; if(this.userData.canViewBooking==false) return;
if(this.currentTask){ const previousTask = this.currentTask;
const requestId = ++this.getDataByPageRequestId;
if(previousTask){
try{ try{
this.currentTask.abort(); previousTask.abort();
}catch(err){ }catch(err){
} }
} }
const requestId = ++this.getDataByPageRequestId;
// 一次页面加载会同时发起 Hotel 和 Limo 请求,统一保存并取消它们。
const requestTasks = [];
const requestController = {
abort: () => {
requestTasks.forEach(task => {
if(task && typeof task.abort === 'function'){
try{
task.abort();
}catch(err){
}
}
});
}
};
const requestConfig = {
getTask: task => {
if(requestId === this.getDataByPageRequestId){
requestTasks.push(task);
}else if(task && typeof task.abort === 'function'){
task.abort();
}
}
};
this.currentTask = requestController;
let params = {}; let params = {};
uni.showLoading({ uni.showLoading({
title: this.$t("common.loading") title: this.$t("common.loading")
}); });
try{ try{
if(this.playerData.nameIds.length==0){ if(this.playerData.nameIds.length==0){
await this.getProfileByPlayerId(requestId) await this.getProfileByPlayerId(requestId, requestConfig)
}else{ }else{
params.member = this.playerData params.member = this.playerData;
params.userId = this.userData.userId; params.userId = this.userData.userId;
const res = await getBookings(params, { params.profileId = this.playerData.profileId;
getTask: (task, options) => { const limoPromise = getLimoBookings(params, requestConfig).catch((e) => {
this.currentTask = task; return null;
}
}); });
const hotelPromise = getBookings(params, requestConfig).catch((e) => {
return null;
});
const [resLimo, resHotel] = await Promise.all([limoPromise, hotelPromise]);
if(requestId !== this.getDataByPageRequestId) return; if(requestId !== this.getDataByPageRequestId) return;
this.currentTask = null; if (resLimo) {
this.bookingList = []; this.updateLimoBookingList(resLimo);
if(res.length>0){ }
if(res!==undefined && res!==null){ if (resHotel) {
this.bookingList = res; this.updateHotelBookingList(resHotel);
}
}else{
} }
} }
}catch(e){ }catch(e){
if(e.errMsg!==undefined && e.errMsg.includes("abort")){ if(requestId !== this.getDataByPageRequestId || (e && e.errMsg!==undefined && e.errMsg.includes("abort"))){
return; return;
} }
// console.log(e); // console.log(e);
@@ -204,11 +315,38 @@
await this.writeLog(this.userData.userId,"save_booking","","[APP][bookingDetail.vue][getDataByPage][Catch Exception]"+e.errMsg,"[RequestData]" +JSON.stringify(params),"",1,false); await this.writeLog(this.userData.userId,"save_booking","","[APP][bookingDetail.vue][getDataByPage][Catch Exception]"+e.errMsg,"[RequestData]" +JSON.stringify(params),"",1,false);
}finally{ }finally{
if(requestId === this.getDataByPageRequestId){ if(requestId === this.getDataByPageRequestId){
this.currentTask = null; if(this.currentTask === requestController){
this.currentTask = null;
}
uni.hideLoading(); uni.hideLoading();
} }
} }
}, },
async refreshHotelBooking(){
if(this.userData.canViewBooking==false) return;
let params = {};
uni.showLoading({
title: this.$t("common.loading")
});
try{
if(this.playerData.nameIds.length==0){
await this.getProfileByPlayerId()
}else{
params.member = this.playerData;
params.userId = this.userData.userId;
params.profileId = this.playerData.profileId;
const resHotel = await getBookings(params);
this.updateHotelBookingList(resHotel);
}
}catch(e){
console.log(e);
// write log
uni.$u.toast(e.errMsg,5000);
await this.writeLog(this.userData.userId,"save_booking","","[APP][bookingDetail.vue][getDataByPage][Catch Exception]"+e.errMsg,"[RequestData]" +JSON.stringify(params),"",1,false);
}finally{
uni.hideLoading();
}
},
pullDownRefresh(){ pullDownRefresh(){
try{ try{
this.getDataByPage('refresh'); this.getDataByPage('refresh');
@@ -217,6 +355,60 @@
} }
}, },
// 界面方法 // 界面方法
isHotelRecord(record){
return record.recordType === 'hotel';
},
getRecordCardClass(record){
return this.getRecordColor(record);
},
openRecordDetail(record){
if(this.isHotelRecord(record)){
this.navToDetail(record);
}else{
this.navToLimoDetail(record);
}
},
getBookingTypeIcon(record){
if(this.isHotelRecord(record)){
return '../../static/images/ico_hotel_active.png';
}
return '../../static/icon/tabbar/taxi-fill.png';
},
getBookingTypeLabel(record){
let bookingType = this.$t("limoBookingRecord.bookingType");
if(this.isHotelRecord(record)){
return bookingType+': Hotel';
}
return bookingType+': Limo';
},
getRecordStatusText(record){
if(this.isHotelRecord(record)){
return this.getStatusText(record);
}else{
return record.status.replace("_"," ");
}
},
getRecordStatusClass(record){
return this.getStatusTextColor(record);
},
canShowSharer(record){
return this.userData && this.userData.canNewBooking && record.propertyCategory!='OTHER';
},
hasSharer(record){
return (record.sharer!==null && record.sharer!=='[]')||record.isShare;
},
hasEta(record){
return record.eta!==undefined && record.eta!==null && record.eta!=='';
},
getDropOffPointLines(dropOffPoint){
if(dropOffPoint === null || dropOffPoint === undefined || dropOffPoint === '') return [''];
return String(dropOffPoint).split(/[;;]/).map(item => item.trim()).filter(item => item !== '');
},
getExtraDropOffPoint(dropOffPoint){
if(dropOffPoint === null || dropOffPoint === undefined || dropOffPoint === '') return [''];
return String(dropOffPoint).split(/[;;]/).map(item => item.trim()).filter(item => item !== '');
},
back(){ back(){
if(this.from==='booking'){ if(this.from==='booking'){
this.navTo("/pages/findPlayer/findPlayer",1,false); this.navTo("/pages/findPlayer/findPlayer",1,false);
@@ -227,6 +419,12 @@
navToDetail(booking){ navToDetail(booking){
this.navTo('/booking/bookingDetail/bookingDetail?id='+booking.id+"&conf="+booking.confirmationNo+"&from=list",0,false) this.navTo('/booking/bookingDetail/bookingDetail?id='+booking.id+"&conf="+booking.confirmationNo+"&from=list",0,false)
}, },
navToLimoDetail(limoBooking){
this.navTo('/limo/bookingDetail/bookingDetail?id=' + limoBooking.id +'&conf='+limoBooking.confirmationNo+"&from=list",0,false)
},
navToPlayerRoomCalendar(){
this.navTo('/booking/playerRoomCalendar/playerRoomCalendar',0,false)
},
// ETA 统一显示 HH:mm(兼容 HH:mm:ss / HH:mm) // ETA 统一显示 HH:mm(兼容 HH:mm:ss / HH:mm)
formatEtaToHHmm(eta) { formatEtaToHHmm(eta) {
if (eta === null || eta === undefined || eta === '') return ''; if (eta === null || eta === undefined || eta === '') return '';
@@ -259,81 +457,170 @@
this.title = this.$t("bookingRecord.title"); this.title = this.$t("bookingRecord.title");
}, },
navToNewBooking(){ navToNewBooking(){
this.getDataByPage(); this.openBookingPage('/booking/newBooking/newBooking');
uni.showLoading({
title: this.$t("common.loading")
});
// remove no opera restrict
// if(this.playerData.nameIds.length==0){
// this.showMsg = true;
// }else{
// this.navTo('/booking/newBooking/newBooking',0,false)
// }
this.navTo('/booking/newBooking/newBooking',0,false)
uni.hideLoading();
}, },
async getProfileByPlayerId(requestId){ navToNewLimo(){
this.openBookingPage('/limo/newLimo/newLimo?from=bookingRecord');
},
async getProfileByPlayerId(requestId = this.getDataByPageRequestId, requestConfig = {}){
try{ try{
let params = {playerId:this.playerData.playerId,userId:this.userData.userId}; const params = {playerId:this.playerData.playerId,userId:this.userData.userId};
const res = await playerLookup(params, { const res = await playerLookup(params, requestConfig);
getTask: (task, options) => {
this.currentTask = task;
}
});
if(requestId !== this.getDataByPageRequestId) return; if(requestId !== this.getDataByPageRequestId) return;
if(res.success == true){ if(res.success == true){
var player = res.data; const player = res.data;
if(player !== null){ if(player !== null){
if(player.codedFlag==1){ if(player.codedFlag==1){
uni.setStorageSync("playerData",player); uni.setStorageSync("playerData",player);
this.playerData = uni.getStorageSync("playerData"); this.playerData = uni.getStorageSync("playerData");
let params = {}; const bookingParams = {
params.member = player member: player,
params.userId = this.userData.userId; userId: this.userData.userId
const res = await getBookings(params, { };
getTask: (task, options) => { const [resHotel, resLimo] = await Promise.all([
this.currentTask = task; getBookings(bookingParams, requestConfig),
} getLimoBookings(bookingParams, requestConfig)
}); ]);
if(requestId !== this.getDataByPageRequestId) return; if(requestId !== this.getDataByPageRequestId) return;
this.currentTask = null; this.updateHotelBookingList(resHotel);
this.bookingList = []; this.updateLimoBookingList(resLimo);
if(res.length>0){
if(res!==undefined && res!==null){
this.bookingList = res;
}
}else{
}
} }
} }
} }
}catch(e){ }catch(e){
if(e.errMsg!==undefined && e.errMsg.includes("abort")){ if(requestId !== this.getDataByPageRequestId || (e && e.errMsg!==undefined && e.errMsg.includes("abort"))){
return; return;
} }
console.error(e); console.error(e);
var request = e.config!=undefined?JSON.stringify(e.config):""; var request = e.config!=undefined?JSON.stringify(e.config):"";
var response = e.data!=undefined?JSON.stringify(e.data):""; var response = e.data!=undefined?JSON.stringify(e.data):"";
await this.writeLog(this.userData.userId,"find_player","","[APP][findPlayer.vue][getProfileByPlayerId][Catch Exception]"+e.errMsg,request,response,1,false); await this.writeLog(this.userData.userId,"find_player","","[APP][findPlayer.vue][getProfileByPlayerId][Catch Exception]"+e.errMsg,request,response,1,false);
}finally{
} }
}, },
formatDate(date, format = 'YYYY-MM-DD HH:mm:ss') {
const d = new Date(date);
if (Number.isNaN(d.getTime())) {
return '';
}
const map = {
YYYY: d.getFullYear(),
MM: String(d.getMonth() + 1).padStart(2, '0'),
DD: String(d.getDate()).padStart(2, '0'),
HH: String(d.getHours()).padStart(2, '0'),
mm: String(d.getMinutes()).padStart(2, '0'),
ss: String(d.getSeconds()).padStart(2, '0')
};
return format.replace(/YYYY|MM|DD|HH|mm|ss/g, matched => map[matched]);
},
getExtraDropOff(extraDropOffPoint) {
let returnExtraDropOffPoint = null;
if(extraDropOffPoint!=undefined && extraDropOffPoint!=null && extraDropOffPoint!=''){
returnExtraDropOffPoint = extraDropOffPoint.split(";");
}
console.log(returnExtraDropOffPoint);
return returnExtraDropOffPoint;
},
/** 用于酒店 / limo 混合列表排序:越大表示时间越晚 */
getRecordSortTimeMs(record) {
if (!record) {
return Number.POSITIVE_INFINITY;
}
if (record.recordType === 'limo') {
const raw = record.pickUpDatetime || record.pickUpDateTime || record.pickupDatetime;
if (raw != null && raw !== '') {
const n = Number(raw);
if (!Number.isNaN(n)) {
return n;
}
const parsedRaw = dayjs(String(raw).trim());
if (parsedRaw.isValid()) {
return parsedRaw.valueOf();
}
}
if (record.pickupDatetime) {
const s = String(record.pickupDatetime).trim();
const m = s.match(/^(\d{1,2})\/(\d{1,2})\/(\d{2,4})\s+(\d{1,2}):(\d{2})$/);
if (m) {
const year = m[3].length === 2 ? 2000 + parseInt(m[3], 10) : parseInt(m[3], 10);
const d = new Date(
year,
parseInt(m[2], 10) - 1,
parseInt(m[1], 10),
parseInt(m[4], 10),
parseInt(m[5], 10)
);
if (!Number.isNaN(d.getTime())) {
return d.getTime();
}
}
}
return Number.POSITIVE_INFINITY;
}
const checkInDate = record.checkinDate || record.checkInDate || record.CheckInDate;
if (checkInDate != null && String(checkInDate).trim() !== '') {
const parsed = dayjs(String(checkInDate).trim());
if (parsed.isValid()) {
return parsed.valueOf();
}
}
return Number.POSITIVE_INFINITY;
},
getRecordSortDateMs(record) {
const timeMs = this.getRecordSortTimeMs(record);
if (!Number.isFinite(timeMs) || Number.isNaN(timeMs)) {
return Number.POSITIVE_INFINITY;
}
const d = new Date(timeMs);
d.setHours(0, 0, 0, 0);
return d.getTime();
},
getRecordTypeSortIndex(record) {
return record && record.recordType === 'limo' ? 1 : 0;
},
compareRecordForDisplay(a, b) {
const dateA = this.getRecordSortDateMs(a);
const dateB = this.getRecordSortDateMs(b);
if (dateA !== dateB) {
return dateA - dateB;
}
const typeDiff = this.getRecordTypeSortIndex(a) - this.getRecordTypeSortIndex(b);
if (typeDiff !== 0) {
return typeDiff;
}
if (a && b && a.recordType === 'limo' && b.recordType === 'limo') {
const timeA = this.getRecordSortTimeMs(a);
const timeB = this.getRecordSortTimeMs(b);
if (timeA !== timeB) {
return timeA - timeB;
}
}
return 0;
}
}, },
computed:{ computed:{
hasSingleBookingPermission(){
if (!this.userData) return false;
return Boolean(this.userData.canNewBooking) !== Boolean(this.userData.canViewLimo);
},
displayRecordList(){
const hotelRecords = this.bookingList.map(item => ({...item, recordType: 'hotel'}));
const limoRecords = this.limoBookingList.map(item => ({...item, recordType: 'limo'}));
console.log(limoRecords);
return hotelRecords.concat(limoRecords).sort((a, b) => this.compareRecordForDisplay(a, b));
},
isDisplayEmpty(){
return this.displayRecordList.length === 0;
},
getConfirmationNo(){ getConfirmationNo(){
return function (booking) { return function (booking) {
if(booking!==undefined && booking!==null){ if(booking!==undefined && booking!==null){
if(booking.confirmationNo!==undefined && booking.confirmationNo!==null && if(booking.confirmationNo!==undefined && booking.confirmationNo!==null &&
booking.confirmationNo!=='null' && booking.confirmationNo!=='' && booking.approveStatus!='W'){ booking.confirmationNo!=='null' && booking.confirmationNo!=='' && booking.approveStatus!='W' && booking.approveStatus!='AP'){
return booking.confirmationNo; return booking.confirmationNo;
} }
else
{
return 'N/A';
}
}else{
return 'N/A';
} }
return 'N/A';
} }
}, },
dateFormat() { dateFormat() {
@@ -439,7 +726,41 @@
if(booking.approveStatus!=null){ if(booking.approveStatus!=null){
approveStatus = booking.approveStatus; approveStatus = booking.approveStatus;
} }
// Opera和Approve Status都不为空的情况 if(booking.recordType === 'limo'){
const noOpera = operaStatus==null || operaStatus==undefined || operaStatus==='' || operaStatus==='null';
const noApprove = approveStatus==null || approveStatus==undefined || approveStatus==='' || approveStatus==='null';
if(noOpera && noApprove && booking.status!=null && booking.status!==undefined && String(booking.status).trim()!==''){
const s = String(booking.status).toUpperCase().replace(/\s+/g,' ');
if(s.includes('CANCEL')){
return 'hz-status-font-cancel';
}
if(s.includes('WAITLIST') || s.includes('WAIT LIST')){
return 'hz-status-font-waitlist';
}
if(s.includes('NOT YET')){
return 'hz-status-font-waitlist';
}
if(s.includes('PENDING')){
return 'hz-status-font-pending';
}
if(s.includes('CONFIRM')){
return 'hz-status-font-duein';
}
if(s.includes('DRIVER_DEPARTED')){
return 'hz-status-font-duein';
}
if(s.includes('DRIVER_ASSIGNED')){
return 'hz-status-font-duein';
}
if(s.includes('DRIVER_ARRIVED')){
return 'hz-status-font-duein';
}
if(s.includes('GUEST_PICKED')){
return 'hz-status-font-duein';
}
return '';
}
}
if((operaStatus!=null && operaStatus!=undefined) || (approveStatus!=null && approveStatus!=undefined)) if((operaStatus!=null && operaStatus!=undefined) || (approveStatus!=null && approveStatus!=undefined))
{ {
if(approveStatus.toUpperCase()==='PN'||approveStatus.toUpperCase()==='F'){ if(approveStatus.toUpperCase()==='PN'||approveStatus.toUpperCase()==='F'){
@@ -553,12 +874,31 @@
}, },
getRecordColor(){ getRecordColor(){
return function (booking) { return function (booking) {
if(booking==null){
return 'hz-card-booking';
}
if(booking.recordType === 'limo'){
const noApprove = booking.approveStatus==null || booking.approveStatus===undefined || booking.approveStatus==='' || booking.approveStatus==='null';
const noOpera = booking.lastOperaStatus==null || booking.lastOperaStatus===undefined || booking.lastOperaStatus==='' || booking.lastOperaStatus==='null';
if(noApprove && noOpera && booking.status!=null && booking.status!==undefined && String(booking.status).trim()!==''){
const s = String(booking.status).toUpperCase().replace(/\s+/g,' ');
if(s.includes('NOT YET') || s.includes('CONFIRM')){
return 'hz-card-booking';
}
if(s.includes('WAITLIST') || s.includes('WAIT LIST') || s.includes('PENDING')){
return 'hz-card-waitlist';
}
return 'hz-card-booking';
}
}
if(booking.approveStatus==='W'|| booking.approveStatus==='WA'){ if(booking.approveStatus==='W'|| booking.approveStatus==='WA'){
return 'hz-card-waitlist' return 'hz-card-waitlist'
}else if(booking.approveStatus==='AP'){ }else if(booking.approveStatus==='AP'){
return 'hz-card-approval' return 'hz-card-approval'
}else if((booking.lastOperaStatus==='DUE_OUT' || booking.lastOperaStatus==='CHECKED_IN') && booking.confirmationNo!=null && (booking.sharer==null || booking.sharer==='[]' || booking.sharer==='')){ }else if((booking.lastOperaStatus==='DUE_OUT' || booking.lastOperaStatus==='CHECKED_IN') && booking.confirmationNo!=null && (booking.sharer==null || booking.sharer==='[]' || booking.sharer==='')){
return 'hz-card-booking' return 'hz-card-booking'
}else if(booking.approveStatus==='AP'){
return 'hz-card-approval'
}else{ }else{
return 'hz-card-booking' return 'hz-card-booking'
} }
@@ -581,5 +921,7 @@
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.hz-book-btn-wrap--single {
width: 95%;
}
</style> </style>
+12 -12
View File
@@ -458,9 +458,8 @@
this.userData = uni.getStorageSync("userData"); this.userData = uni.getStorageSync("userData");
// this.modelBooking.bookingTypeId = this.userData.bookingTypeId; // this.modelBooking.bookingTypeId = this.userData.bookingTypeId;
this.modelBooking.propertyId = this.userData.propertyId; this.modelBooking.propertyId = this.userData.propertyId;
this.modelBooking.teamId = this.userData.teamStructureId; this.modelBooking.teamId = this.userData.teamStructureId;
this.modelBooking.sectionName = this.userData.section;
this.modelBooking.sectionId = this.userData.sectionId; this.modelBooking.sectionId = this.userData.sectionId;
this.modelBooking.createdBy = this.userData.userId; this.modelBooking.createdBy = this.userData.userId;
this.modelBooking.createdByName = this.userData.userName; this.modelBooking.createdByName = this.userData.userName;
@@ -701,7 +700,7 @@
} }
} }
} }
if (this.maxRange == 0) this.getSetting(); if (this.maxRange == 0) this.getSetting();
} }
} else { } else {
@@ -776,8 +775,9 @@
this.currentTask = task; this.currentTask = task;
} }
}); });
this.currentTask = null; this.currentTask = null;
if (res.success) { if (res.success) {
item.name = res.data.englishName; item.name = res.data.englishName;
@@ -807,10 +807,10 @@
let item = this.modelBooking.sharerList[index]; let item = this.modelBooking.sharerList[index];
let book = uni.getStorageSync("bookingData") let book = uni.getStorageSync("bookingData")
let oldItem = book.sharerList[index]; let oldItem = book.sharerList[index];
// 姓名被手动修改后,清除不再匹配的玩家 ID。 //清空plaerId
if (item.name != oldItem.name && item.playerId.length > 4) { // if (item.name != oldItem.name && item.playerId.length == 8) {
item.playerId = ""; // item.playerId = "";
} // }
}, },
async buildCalendar() { async buildCalendar() {
uni.showLoading({ uni.showLoading({
@@ -949,9 +949,9 @@
'available': 0, 'available': 0,
}); });
} }
} }
var isWaitlist = false; var isWaitlist = false;
var waitlistCount = 0; var waitlistCount = 0;
var criteriaCount = e.length - 1; var criteriaCount = e.length - 1;
@@ -984,7 +984,7 @@
let checkDate = JSON.parse(JSON.stringify(e)); let checkDate = JSON.parse(JSON.stringify(e));
checkDate.pop(); checkDate.pop();
if (!this.checkDate(checkDate)) return false; if (!this.checkDate(checkDate)) return false;
//rebuild special code //rebuild special code
let specialCode = this.modelBooking.specialCode; let specialCode = this.modelBooking.specialCode;
let specialName = this.modelBooking.specialName; let specialName = this.modelBooking.specialName;
@@ -1525,7 +1525,7 @@
} else { } else {
this.showMsgNoExtend = true; this.showMsgNoExtend = true;
} }
}, },
checkConsecutiveNight(checkInDate, night) { checkConsecutiveNight(checkInDate, night) {
@@ -18,19 +18,19 @@
<!-- <view class="player-summary"> --> <!-- <view class="player-summary"> -->
<view class="hz-card-profile mb-[40]" style="margin:9px 9px 6px;"> <view class="hz-card-profile mb-[40]" style="margin:9px 9px 6px;">
<view class="hz-flex-row"> <view class="hz-flex-row">
<view style="width:60%"> <view class="hz-w-60">
<view class="hz-font-color-light hz-font-sm">{{$t("common.playerId")}}</view> <view class="hz-font-color-light hz-font-sm">{{$t("common.playerId")}}</view>
<view class="hz-font-bolder hz-font-lg" >{{ safePlayerData.playerId }}</view> <view class="hz-font-bolder hz-font-lg" >{{ playerData.playerId }}</view>
</view> </view>
<view style="width:40%"> <view class="hz-w-40">
<view class="hz-font-color-light hz-font-sm">{{$t("common.tier")}}</view> <view class="hz-font-color-light hz-font-sm">{{$t("common.tier")}}</view>
<view class="hz-font-bolder hz-font-lg" >{{ safePlayerData.tierName }}</view> <view class="hz-font-bolder hz-font-lg" >{{ playerData.tierName }}</view>
</view> </view>
</view> </view>
<view class="hz-flex-row hz-mr-t-30"> <view class="hz-flex-row hz-mr-t-30">
<view style="width:100%"> <view class="hz-w-100">
<view class="hz-font-color-light hz-font-sm">{{$t("common.name")}}</view> <view class="hz-font-color-light hz-font-sm">{{$t("common.name")}}</view>
<view class="hz-font-bolder hz-font-lg" >{{ playerName }}</view> <view class="hz-font-bolder hz-font-lg" >{{playerData.salutation + " " + playerData.name }}</view>
</view> </view>
</view> </view>
</view> </view>
@@ -197,13 +197,11 @@
propertyTabItemStyle() { propertyTabItemStyle() {
return { return {
height: '70rpx', height: '70rpx',
padding: '0 24rpx', padding: '0',
flexShrink: 0,
}; };
}, },
propertyTabActiveStyle() { propertyTabActiveStyle() {
return { return {
whiteSpace: 'nowrap',
color: '#C39A4B', color: '#C39A4B',
fontFamily: 'Inter-Bold', fontFamily: 'Inter-Bold',
fontSize: '30rpx', fontSize: '30rpx',
@@ -212,7 +210,6 @@
}, },
propertyTabInactiveStyle() { propertyTabInactiveStyle() {
return { return {
whiteSpace: 'nowrap',
color: '#111111', color: '#111111',
fontFamily: 'Inter-Bold', fontFamily: 'Inter-Bold',
fontSize: '30rpx', fontSize: '30rpx',
@@ -241,7 +238,6 @@
this.navBack(); this.navBack();
}, },
async loadPropertiesRoomTypes() { async loadPropertiesRoomTypes() {
if (!this.canLoadPlayerCalendar()) return;
try { try {
this.calendarReady = false; this.calendarReady = false;
uni.showLoading({ uni.showLoading({
@@ -249,19 +245,14 @@
}); });
await this.loadCalendarSetting(); await this.loadCalendarSetting();
const res = await getPropertiesRoomTypes({}); const res = await getPropertiesRoomTypes({});
if (this.isNoPermissionResponse(res)) { this.propertyList = res.propertyList;
this.showCalendarNoPermission();
return;
}
this.propertyList = Array.isArray(res && res.propertyList) ? res.propertyList : [];
if (!this.propertyList.length) throw new Error('No calendar properties configured');
this.selectedPropertyCode = this.getDefaultPropertyCode(); this.selectedPropertyCode = this.getDefaultPropertyCode();
this.selectDefaultOptions(); this.selectDefaultOptions();
this.buildCalendar(); this.buildCalendar();
await this.loadQualificationData(); await this.loadQualificationData();
this.calendarRenderKey += 1; this.calendarRenderKey += 1;
this.$nextTick(() => { this.$nextTick(() => {
this.calendarReady = !this.calendarNoPermission; this.calendarReady = true;
}); });
} catch (e) { } catch (e) {
uni.$u.toast(e.message || e.errMsg || 'Load property room types failed'); uni.$u.toast(e.message || e.errMsg || 'Load property room types failed');
@@ -464,12 +455,10 @@
})); }));
}, },
refreshCalendar() { refreshCalendar() {
if (!this.canLoadPlayerCalendar()) return;
this.ensureSelectedOptionsForRefresh(true); this.ensureSelectedOptionsForRefresh(true);
this.loadQualificationData(); this.loadQualificationData();
}, },
async loadQualificationData() { async loadQualificationData() {
if (!this.canLoadPlayerCalendar()) return;
const records = []; const records = [];
const dateRange = this.getCalendarDateRange(); const dateRange = this.getCalendarDateRange();
const requestId = this.startCalendarRequest(); const requestId = this.startCalendarRequest();
@@ -523,20 +512,7 @@
}); });
}, },
isNoPermissionResponse(res) { isNoPermissionResponse(res) {
return res && res.success === false && (res.errorCode || res.code) === 'NO_PERMISSION'; return res && res.success === false && res.errorCode === 'NO_PERMISSION';
},
canLoadPlayerCalendar() {
this.userData = uni.getStorageSync("userData");
if (!this.userData || this.userData.canViewCalendar !== true) {
this.showCalendarNoPermission();
return false;
}
if (!this.safePlayerData.playerId) {
this.calendarReady = false;
uni.$u.toast(this.$t("common.dataNotFound"));
return false;
}
return true;
}, },
showCalendarNoPermission() { showCalendarNoPermission() {
this.qualificationMap = {}; this.qualificationMap = {};
+108 -168
View File
@@ -139,10 +139,10 @@
<!-- <view class="hz-font-bolder hz-font-lg" >{{ handleShowWhenScreenshot(bookingData.eta) }}</view> --> <!-- <view class="hz-font-bolder hz-font-lg" >{{ handleShowWhenScreenshot(bookingData.eta) }}</view> -->
</view> </view>
</view> </view>
<view v-if="bookingData.miscCharge!=null" class="hz-flex-row"> <view v-if="bookingData.mischarge!=null" class="hz-flex-row">
<view style="width:100%"> <view style="width:100%">
<view class="hz-font-color-black hz-font-sm">{{ $t("common.misCharge") }}</view> <view class="hz-font-color-black hz-font-sm">{{ $t("common.misCharge") }}</view>
<view class="hz-font-bolder hz-font-lg">{{ bookingData.miscCharge || "-" }}</view> <view class="hz-font-bolder hz-font-lg">{{ bookingData.mischarge || "-" }}</view>
</view> </view>
</view> </view>
</view> </view>
@@ -254,18 +254,19 @@
<!-- Remark --> <!-- Remark -->
<view style="margin-bottom: 16px;margin-top: 16px;"> <view style="margin-bottom: 16px;margin-top: 16px;">
<view style="text-align: left;">{{$t("bookingConfirm.approvalRemark")}}</view> <view style="text-align: left;">{{$t("bookingConfirm.approvalRemark")}}</view>
<u-checkbox-group placement="row" <u-checkbox-group placement="row" @change="changeOtherRemark"
:value="unqualifiedDateCheckboxStates[currentUnqualifiedIndex] || []" v-model="unqualifiedDateCheckboxStates[currentUnqualifiedIndex]"
@change="changeOtherRemark"
style="flex-direction: unset;display: block;"> style="flex-direction: unset;display: block;">
<view style="width:50%;display: inline-flex;"> <view style="width:50%;display: inline-flex;">
<u-checkbox activeColor="#C39A4B" labelColor="#333333" shape="circle" <u-checkbox activeColor="#C39A4B" labelColor="#333333" shape="circle"
customStyle="height:30px;width:100%;" label="Extend" name="Extend"></u-checkbox> customStyle="height:30px;width:100%;" label="Extend" name="Extend"
:value="unqualifiedDateCheckboxStates[currentUnqualifiedIndex] && unqualifiedDateCheckboxStates[currentUnqualifiedIndex].includes('Extend')"></u-checkbox>
</view> </view>
<view style="width:50%;display: inline-flex;position: relative;left:10%;"> <view style="width:50%;display: inline-flex;position: relative;left:10%;">
<u-checkbox activeColor="#C39A4B" labelColor="#333333" shape="circle" <u-checkbox activeColor="#C39A4B" labelColor="#333333" shape="circle"
customStyle="height:30px;width:100%;" label="Remark" name="Remark"></u-checkbox> customStyle="height:30px;width:100%;" label="Remark" name="Remark"
:value="unqualifiedDateCheckboxStates[currentUnqualifiedIndex] && unqualifiedDateCheckboxStates[currentUnqualifiedIndex].includes('Remark')"></u-checkbox>
</view> </view>
</u-checkbox-group> </u-checkbox-group>
<view style="width:100%;margin-top:16px;" v-show="showRemark"> <view style="width:100%;margin-top:16px;" v-show="showRemark">
@@ -328,32 +329,41 @@
<rich-text v-html="msgStr" style="line-height:26px;"></rich-text> <rich-text v-html="msgStr" style="line-height:26px;"></rich-text>
</view> </view>
<view style="margin-bottom: 10px;text-align: left;"> {{$t("bookingConfirm.approvalRemark")}}</view> <view style="margin-bottom: 10px;text-align: left;"> {{$t("bookingConfirm.approvalRemark")}}</view>
<!-- uView:u-checkbox 必须在 u-checkbox-group 内;房型互斥用 group + 受控 :value --> <u-radio-group placement="row" @change="changeRoomRemark" v-if="showRadio"
<u-checkbox-group placement="row"
:value="waitlistRoomGroupValue"
@change="onWaitlistRoomRemarkGroupChange"
style="flex-direction: unset;display: block;"> style="flex-direction: unset;display: block;">
<view style="width:50%;display: inline-flex;">
<u-radio activeColor="#C39A4B" labelColor="#333333" shape="circle"
customStyle="height:30px;width:100%;" label="2nd Room" name="2nd Room"></u-radio>
</view>
<view style="width:50%;display: inline-flex;position: relative;left:10%;">
<u-radio activeColor="#C39A4B" labelColor="#333333" shape="circle"
customStyle="height:30px;width:100%;" label="3rd Room" name="3rd Room"></u-radio>
</view>
</u-radio-group>
<u-checkbox-group placement="row" v-else style="flex-direction: unset;display: block;">
<view style="width:50%;display: inline-flex;"> <view style="width:50%;display: inline-flex;">
<u-checkbox activeColor="#C39A4B" labelColor="#333333" shape="circle" <u-checkbox activeColor="#C39A4B" labelColor="#333333" shape="circle"
customStyle="height:30px;width:100%;" label="2nd Room" name="2nd Room"></u-checkbox> customStyle="height:30px;width:100%;" label="2nd Room" name="2nd Room"></u-checkbox>
</view> </view>
<view style="width:50%;display: inline-flex;position: relative;left:10%;"> <view style="width:50%;display: inline-flex;position: relative;left:10%;">
<u-checkbox activeColor="#C39A4B" labelColor="#333333" shape="circle" <u-checkbox activeColor="#C39A4B" labelColor="#333333" shape="circle"
customStyle="height:30px;width:100%;" label="3rd Room" name="3rd Room"></u-checkbox> customStyle="height:30px;width:100%;" label="3rd Room" name="3rd Room"></u-checkbox>
</view> </view>
</u-checkbox-group> </u-checkbox-group>
<u-checkbox-group placement="row" <u-checkbox-group placement="row" @change="changeOtherRemark"
:value="waitlistOtherRemarkCheckList"
@change="changeWaitlistOtherRemark"
style="flex-direction: unset;display: block;"> style="flex-direction: unset;display: block;">
<view style="width:50%;display: inline-flex;"> <view style="width:50%;display: inline-flex;">
<u-checkbox activeColor="#C39A4B" labelColor="#333333" shape="circle" <u-checkbox activeColor="#C39A4B" labelColor="#333333" shape="circle"
customStyle="height:30px;width:100%;" label="Extend" name="Extend"></u-checkbox> customStyle="height:30px;width:100%;" label="Extend" name="Extend"
:value="unqualifiedDateCheckboxStates[currentUnqualifiedIndex] && unqualifiedDateCheckboxStates[currentUnqualifiedIndex].includes('Extend')"></u-checkbox>
</view> </view>
<view style="width:50%;display: inline-flex;position: relative;left:10%;"> <view style="width:50%;display: inline-flex;position: relative;left:10%;">
<u-checkbox activeColor="#C39A4B" labelColor="#333333" shape="circle" <u-checkbox activeColor="#C39A4B" labelColor="#333333" shape="circle"
customStyle="height:30px;width:100%;" label="Remark" name="Remark"></u-checkbox> customStyle="height:30px;width:100%;" label="Remark" name="Remark"
:value="unqualifiedDateCheckboxStates[currentUnqualifiedIndex] && unqualifiedDateCheckboxStates[currentUnqualifiedIndex].includes('Remark')"></u-checkbox>
</view> </view>
</u-checkbox-group> </u-checkbox-group>
<view style="width:100%;margin-top:16px;" v-show="showRemark"> <view style="width:100%;margin-top:16px;" v-show="showRemark">
@@ -427,8 +437,7 @@
selectedApprover:'', selectedApprover:'',
showRemark: false, showRemark: false,
showWaitlistModal:false, showWaitlistModal:false,
// Waitlist 弹窗 Extend/Remark(必须与 u-checkbox-group 的 :value 绑定,勿用子项 :value) showRadio:false,
waitlistOtherRemarkCheckList: [],
unqualifiedDateList: [], unqualifiedDateList: [],
currentUnqualifiedIndex: 0, currentUnqualifiedIndex: 0,
unqualifiedDateRemarks: [], unqualifiedDateRemarks: [],
@@ -454,17 +463,6 @@
// 數據方法 // 數據方法
getBooking(){ getBooking(){
this.bookingData = uni.getStorageSync("bookingData"); this.bookingData = uni.getStorageSync("bookingData");
// 确保 roomRemark 是响应式字段,否则 iOS 上可能出现 u-checkbox 状态未被正确回刷导致“可多选”
if (this.bookingData !== null && this.bookingData !== undefined && typeof this.bookingData === 'object') {
const hasRoomRemark = Object.prototype.hasOwnProperty.call(this.bookingData, 'roomRemark');
if (!hasRoomRemark || this.bookingData.roomRemark === null || this.bookingData.roomRemark === undefined || this.bookingData.roomRemark === 'null') {
(this.$set ? this.$set(this.bookingData, 'roomRemark', '') : this.bookingData.roomRemark = '');
}
const hasOtherRemark = Object.prototype.hasOwnProperty.call(this.bookingData, 'otherRemark');
if (!hasOtherRemark || this.bookingData.otherRemark === null || this.bookingData.otherRemark === undefined || this.bookingData.otherRemark === 'null') {
(this.$set ? this.$set(this.bookingData, 'otherRemark', '') : this.bookingData.otherRemark = '');
}
}
if(this.bookingData!==null && this.bookingData!==undefined && this.bookingData!==''){ if(this.bookingData!==null && this.bookingData!==undefined && this.bookingData!==''){
this.oldDateList = this.bookingData.dateList; this.oldDateList = this.bookingData.dateList;
this.checkList = []; this.checkList = [];
@@ -500,7 +498,11 @@
this.submitBooking(); this.submitBooking();
} }
}, },
buildUnqualifiedDateLists() { submitApproval() {
// 显示审批模态框
this.getApprovalReason();
this.showApprovalModal = true;
this.showModalBtn = true;
this.currentUnqualifiedIndex = 0; this.currentUnqualifiedIndex = 0;
this.unqualifiedDateList = []; this.unqualifiedDateList = [];
this.unqualifiedDateRemarks = []; this.unqualifiedDateRemarks = [];
@@ -508,27 +510,33 @@
this.unqualifiedDateApprovers = []; this.unqualifiedDateApprovers = [];
this.unqualifiedDateOtherRemarks = []; this.unqualifiedDateOtherRemarks = [];
this.unqualifiedDateCheckboxStates = []; this.unqualifiedDateCheckboxStates = [];
if (this.checkList != null && this.checkList.length > 0) { if(this.checkList!=null && this.checkList.length>0){
if (this.bookingData.bookType == 1) { if(this.bookingData.bookType==1){
for (let i = 0; i < this.bookingData.extendDate.length; i++) { // 把extendDate中的状态都设置成不够分
for (let j = 0; j < this.checkList.length; j++) { for(let i=0;i<this.bookingData.extendDate.length;i++){
if (i == this.checkList[j] && this.bookingData.extendDate[i].status == 1) { for(let j=0;j<this.checkList.length;j++){
if(i == this.checkList[j] && this.bookingData.extendDate[i].status==1){
console.log(this.bookingData.extendDate[i]);
this.unqualifiedDateList.push(this.bookingData.extendDate[i]); this.unqualifiedDateList.push(this.bookingData.extendDate[i]);
this.unqualifiedDateRemarks.push(this.bookingData.extendDate[i].remark || null); this.unqualifiedDateRemarks.push(this.bookingData.extendDate[i].remark || null);
this.unqualifiedDateReasons.push(''); this.unqualifiedDateReasons.push('');
// 多选 Approver:为每个 index 初始化为数组
this.unqualifiedDateApprovers.push([]); this.unqualifiedDateApprovers.push([]);
this.unqualifiedDateOtherRemarks.push(''); this.unqualifiedDateOtherRemarks.push('');
this.unqualifiedDateCheckboxStates.push([]); this.unqualifiedDateCheckboxStates.push([]);
} }
} }
} }
} else { }else{
for (let i = 0; i < this.bookingData.dateList.length; i++) { // 把dateList中的状态都设置成不够分
for (let j = 0; j < this.checkList.length; j++) { for(let i=0;i<this.bookingData.dateList.length;i++){
if (i == this.checkList[j] && this.bookingData.dateList[i].status == 1) { for(let j=0;j<this.checkList.length;j++){
if(i == this.checkList[j] && this.bookingData.dateList[i].status==1){
// console.log(this.bookingData.dateList[i]);
this.unqualifiedDateList.push(this.bookingData.dateList[i]); this.unqualifiedDateList.push(this.bookingData.dateList[i]);
this.unqualifiedDateRemarks.push(this.bookingData.dateList[i].remark || ''); this.unqualifiedDateRemarks.push(this.bookingData.dateList[i].remark || '');
this.unqualifiedDateReasons.push(''); this.unqualifiedDateReasons.push('');
// 多选 Approver:为每个 index 初始化为数组
this.unqualifiedDateApprovers.push([]); this.unqualifiedDateApprovers.push([]);
this.unqualifiedDateOtherRemarks.push(''); this.unqualifiedDateOtherRemarks.push('');
this.unqualifiedDateCheckboxStates.push([]); this.unqualifiedDateCheckboxStates.push([]);
@@ -536,16 +544,10 @@
} }
} }
} }
}else{
return false;
} }
}, },
submitApproval() {
// 与 Waitlist 共用:必须先填充 unqualifiedDateList,否则 getWaitlistRemark 无法匹配 Extend
this.buildUnqualifiedDateLists();
// 显示审批模态框
this.getApprovalReason();
this.showApprovalModal = true;
this.showModalBtn = true;
},
onApprovalChange(value){ onApprovalChange(value){
this.selectedApprover = value; this.selectedApprover = value;
// 多选:value 为数组,需要拼接 approverName // 多选:value 为数组,需要拼接 approverName
@@ -1101,7 +1103,7 @@
if (this.bookingData.extendDate[i].status == 1) { if (this.bookingData.extendDate[i].status == 1) {
// remarkText只保存remark输入的内容,不加Extend前缀 // remarkText只保存remark输入的内容,不加Extend前缀
this.bookingData.extendDate[i].remarkText = this.unqualifiedDateRemarks[unqualifiedIndex] || ''; this.bookingData.extendData[i].remarkText = this.unqualifiedDateRemarks[unqualifiedIndex] || '';
// 添加approvalReason字段 // 添加approvalReason字段
this.bookingData.extendDate[i].approvalReason = this.unqualifiedDateReasons[unqualifiedIndex] || null; this.bookingData.extendDate[i].approvalReason = this.unqualifiedDateReasons[unqualifiedIndex] || null;
@@ -1432,64 +1434,40 @@
}, },
getWaitlistRemark(type,index){ getWaitlistRemark(type,index){
let item = (type==2?this.bookingData.extendDate[index]:this.bookingData.dateList[index]); let item = (type==2?this.bookingData.extendDate[index]:this.bookingData.dateList[index]);
let unqualifiedIndex = this.unqualifiedDateList.findIndex(uItem => uItem === item); let remarkArr = [];
if (unqualifiedIndex === -1 && item.date && item.date.length) { let roomRemark = this.bookingData.roomRemark;
unqualifiedIndex = this.unqualifiedDateList.findIndex((uItem) => { let otherRemark = this.bookingData.otherRemark;
const ud = uItem.date; if(roomRemark!=''&&roomRemark!=undefined) remarkArr.push(roomRemark);
const id = item.date; if(otherRemark!=''&&otherRemark!=undefined) remarkArr.push(otherRemark);
return ud && id && ud.length && id.length &&
ud[0] === id[0] && ud[ud.length - 1] === id[id.length - 1];
});
}
// 仅一条 Waitlist/Unqualified 时,引用不一致也会导致 findIndex 失败,直接对齐下标 0
if (unqualifiedIndex === -1 && this.unqualifiedDateList.length === 1) {
unqualifiedIndex = 0;
}
const checkboxState = unqualifiedIndex !== -1
? (this.unqualifiedDateCheckboxStates[unqualifiedIndex] || [])
: [];
const otherSlot = unqualifiedIndex !== -1
? (this.unqualifiedDateOtherRemarks[unqualifiedIndex] || '')
: '';
const wlTags = this.waitlistOtherRemarkCheckList || [];
const globalOther = (this.bookingData && this.bookingData.otherRemark) ? String(this.bookingData.otherRemark) : '';
const parseTags = (s) => String(s).split(';').map((x) => x.trim()).filter(Boolean);
const hasExtend = checkboxState.includes('Extend') ||
parseTags(otherSlot).includes('Extend') ||
wlTags.includes('Extend') ||
parseTags(globalOther).includes('Extend');
const roomRemark = this.bookingData.roomRemark; // 找到对应的unqualifiedDate索引
const idxRemark = unqualifiedIndex !== -1 ? this.unqualifiedDateRemarks[unqualifiedIndex] : ''; const unqualifiedIndex = this.unqualifiedDateList.findIndex(uItem => uItem === item);
const manualRemark = (idxRemark != null && idxRemark !== '') ? idxRemark : this.freeRemark; let extendStr = '';
const remarkChecked = unqualifiedIndex !== -1 && if (unqualifiedIndex !== -1) {
(this.unqualifiedDateCheckboxStates[unqualifiedIndex] || []).includes('Remark'); const checkboxState = this.unqualifiedDateCheckboxStates[unqualifiedIndex] || [];
if (checkboxState.includes('Extend')) {
if (unqualifiedIndex !== -1 && this.unqualifiedDateOtherRemarks[unqualifiedIndex] != null) { extendStr = 'Extend';
this.bookingData.otherRemark = this.unqualifiedDateOtherRemarks[unqualifiedIndex]; }
} }
// 对应 BookingDate 的 remark:勾选 Extend 时放在字符串首位
const dateParts = [];
if (hasExtend) {
dateParts.push('Extend');
}
if (roomRemark != null && roomRemark !== '' && roomRemark !== undefined) {
dateParts.push(roomRemark);
}
if (item.status == 1 && if (item.status == 1 &&
item.remarkText != null && item.remarkText !== '' && item.remarkText !== undefined) { item.remarkText != '' &&
dateParts.push(item.remarkText); item.remarkText != undefined) {
} if (extendStr) {
if ((remarkChecked || this.showRemark) && manualRemark !== '' && manualRemark != null && item.status == 1) { remarkArr.push(extendStr + '; ' + item.remarkText);
if (manualRemark !== item.remarkText) dateParts.push(manualRemark); } else {
remarkArr.push(item.remarkText);
}
} else if (item.status == 1 && extendStr) {
remarkArr.push(extendStr);
} }
if(this.showRemark&&this.freeRemark!=''&&item.status==1) remarkArr.push(this.freeRemark);
const dateRemarkJoin = dateParts.join('; '); if(remarkArr.length>0) {
if (dateRemarkJoin) { this.bookingData.remark = remarkArr.join('; ');
this.bookingData.remark = dateRemarkJoin; item.remark = remarkArr.join('; ');
item.remark = dateRemarkJoin; }else{
} else {
this.bookingData.remark = ""; this.bookingData.remark = "";
item.remark = ""; item.remark = "";
} }
@@ -1585,10 +1563,6 @@
}, },
showWaitlistPop() { showWaitlistPop() {
// 关键:直接点「放入 Waitlist」不会经过 submitApproval,必须在此处填充 unqualifiedDateList,
// 否则 getWaitlistRemark 里 findIndex 恒为 -1,Extend 永远不会写入 remark
this.buildUnqualifiedDateLists();
this.waitlistOtherRemarkCheckList = (this.unqualifiedDateCheckboxStates[0] || []).slice();
this.showWaitlistModal = true; this.showWaitlistModal = true;
this.showMsgBox = false; this.showMsgBox = false;
}, },
@@ -1632,27 +1606,17 @@
} }
}); });
}, },
onWaitlistRoomRemarkGroupChange(e) { changeRoomRemark(e,item) {
const raw = Array.isArray(e) ? e : (e && Array.isArray(e.detail) ? e.detail : []); if (e) {
const names = raw.map((x) => (x != null ? String(x) : '')).filter(Boolean); if(e==item.roomRemark){
if (!this.bookingData) { item.roomRemark=""
return; item.showRadio = true;
setTimeout(()=>{item.showRadio = false;this.$forceUpdate()},1)
}else{
item.roomRemark=e
}
} }
if (!Object.prototype.hasOwnProperty.call(this.bookingData, 'roomRemark')) { this.$forceUpdate()
this.$set(this.bookingData, 'roomRemark', '');
}
if (names.length === 0) {
this.bookingData.roomRemark = '';
return;
}
if (names.length === 1) {
this.bookingData.roomRemark = names[0];
return;
}
// 双选时视为互斥:保留本次切换目标(与原先不同的那一项)
const prev = this.bookingData.roomRemark;
const next = names.find((n) => n !== prev) || names[names.length - 1];
this.bookingData.roomRemark = next;
}, },
// changeOtherRemark(e,item) { // changeOtherRemark(e,item) {
// if (e) { // if (e) {
@@ -1671,41 +1635,27 @@
// this.$forceUpdate() // this.$forceUpdate()
// } // }
// }, // },
changeWaitlistOtherRemark(e) {
const raw = Array.isArray(e) ? e : (e && Array.isArray(e.detail) ? e.detail : []);
const names = raw.map((x) => (x != null ? String(x) : '')).filter(Boolean);
this.waitlistOtherRemarkCheckList = names.slice();
const extras = names.filter((n) => n !== 'Remark');
const str = extras.join('; ');
this.showRemark = names.includes('Remark');
const n = this.unqualifiedDateCheckboxStates.length;
for (let i = 0; i < n; i++) {
this.$set(this.unqualifiedDateCheckboxStates, i, names.slice());
this.$set(this.unqualifiedDateOtherRemarks, i, str);
}
if (this.bookingData) {
this.bookingData.otherRemark = str;
}
},
changeOtherRemark(e) { changeOtherRemark(e) {
const raw = Array.isArray(e) ? e : (e && Array.isArray(e.detail) ? e.detail : []); if (e) {
const idx = this.currentUnqualifiedIndex; let str = '';
if (!raw || raw.length === 0) { let show = false;
this.showRemark = false; for (let i = 0; i < e.length; i++) {
this.$set(this.unqualifiedDateOtherRemarks, idx, '');
this.$set(this.unqualifiedDateCheckboxStates, idx, []); if (e[i] == 'Remark') {
if (this.bookingData) { show = true;
this.bookingData.otherRemark = ''; } else {
str = e[i];
}
} }
return; this.showRemark = show;
} // 存储到当前索引的otherRemark
const names = raw.map((x) => (x != null ? String(x) : '')).filter(Boolean); this.unqualifiedDateOtherRemarks[this.currentUnqualifiedIndex] = str;
const extras = names.filter((n) => n !== 'Remark'); // 存储checkbox状态
this.showRemark = names.includes('Remark'); this.unqualifiedDateCheckboxStates[this.currentUnqualifiedIndex] = e;
this.$set(this.unqualifiedDateCheckboxStates, idx, names.slice()); } else {
this.$set(this.unqualifiedDateOtherRemarks, idx, extras.join('; ')); this.showRemark = false;
if (this.bookingData) { this.unqualifiedDateOtherRemarks[this.currentUnqualifiedIndex] = '';
this.bookingData.otherRemark = extras.join('; '); this.unqualifiedDateCheckboxStates[this.currentUnqualifiedIndex] = [];
} }
}, },
@@ -1721,16 +1671,6 @@
}, },
}, },
computed:{ computed:{
waitlistRoomGroupValue() {
if (!this.bookingData || !this.bookingData.roomRemark) {
return [];
}
const r = this.bookingData.roomRemark;
if (r === '2nd Room' || r === '3rd Room') {
return [r];
}
return [];
},
getStatusColor(){ getStatusColor(){
return function (booking) { return function (booking) {
let operaStatus = null; let operaStatus = null;
@@ -2051,4 +1991,4 @@
color: #C39A4B; color: #C39A4B;
margin-bottom: 15px; margin-bottom: 15px;
} }
</style> </style>
+3 -2
View File
@@ -14,7 +14,7 @@ class config {
//this.version = "beta 2.0"; //this.version = "beta 2.0";
//ali ECS //ali ECS
// this.internalUrl = 'https://mcrm.willtechmacau.com:9086'; // this.internalUrl = 'https://sep.willtechmo.com';
this.externalUrl = 'https://mp.mixixili.cn'; this.externalUrl = 'https://mp.mixixili.cn';
this.debugMode = true; this.debugMode = true;
@@ -36,4 +36,5 @@ class config {
this.emailExt = "";// 如果是dev的時候設置成空字符串,讓他們自己輸入 this.emailExt = "";// 如果是dev的時候設置成空字符串,讓他們自己輸入
} }
} }
export default config export default config
+257
View File
@@ -0,0 +1,257 @@
const countryDialCodes = [
{ iso2: 'MO', country: 'Macau', code: '+853' },
{ iso2: 'CN', country: 'China', code: '+86' },
{ iso2: 'HK', country: 'Hong Kong', code: '+852' },
{ iso2: 'AF', country: 'Afghanistan', code: '+93' },
{ iso2: 'AX', country: 'Aland Islands', code: '+358' },
{ iso2: 'AL', country: 'Albania', code: '+355' },
{ iso2: 'DZ', country: 'Algeria', code: '+213' },
{ iso2: 'AS', country: 'American Samoa', code: '+1' },
{ iso2: 'AD', country: 'Andorra', code: '+376' },
{ iso2: 'AO', country: 'Angola', code: '+244' },
{ iso2: 'AI', country: 'Anguilla', code: '+1' },
{ iso2: 'AG', country: 'Antigua and Barbuda', code: '+1' },
{ iso2: 'AR', country: 'Argentina', code: '+54' },
{ iso2: 'AM', country: 'Armenia', code: '+374' },
{ iso2: 'AW', country: 'Aruba', code: '+297' },
{ iso2: 'AC', country: 'Ascension Island', code: '+247' },
{ iso2: 'AU', country: 'Australia', code: '+61' },
{ iso2: 'AT', country: 'Austria', code: '+43' },
{ iso2: 'AZ', country: 'Azerbaijan', code: '+994' },
{ iso2: 'BS', country: 'Bahamas', code: '+1' },
{ iso2: 'BH', country: 'Bahrain', code: '+973' },
{ iso2: 'BD', country: 'Bangladesh', code: '+880' },
{ iso2: 'BB', country: 'Barbados', code: '+1' },
{ iso2: 'BY', country: 'Belarus', code: '+375' },
{ iso2: 'BE', country: 'Belgium', code: '+32' },
{ iso2: 'BZ', country: 'Belize', code: '+501' },
{ iso2: 'BJ', country: 'Benin', code: '+229' },
{ iso2: 'BM', country: 'Bermuda', code: '+1' },
{ iso2: 'BT', country: 'Bhutan', code: '+975' },
{ iso2: 'BO', country: 'Bolivia', code: '+591' },
{ iso2: 'BA', country: 'Bosnia and Herzegovina', code: '+387' },
{ iso2: 'BW', country: 'Botswana', code: '+267' },
{ iso2: 'BR', country: 'Brazil', code: '+55' },
{ iso2: 'IO', country: 'British Indian Ocean Territory', code: '+246' },
{ iso2: 'VG', country: 'British Virgin Islands', code: '+1' },
{ iso2: 'BN', country: 'Brunei', code: '+673' },
{ iso2: 'BG', country: 'Bulgaria', code: '+359' },
{ iso2: 'BF', country: 'Burkina Faso', code: '+226' },
{ iso2: 'BI', country: 'Burundi', code: '+257' },
{ iso2: 'KH', country: 'Cambodia', code: '+855' },
{ iso2: 'CM', country: 'Cameroon', code: '+237' },
{ iso2: 'CA', country: 'Canada', code: '+1' },
{ iso2: 'CV', country: 'Cape Verde', code: '+238' },
{ iso2: 'BQ', country: 'Caribbean Netherlands', code: '+599' },
{ iso2: 'KY', country: 'Cayman Islands', code: '+1' },
{ iso2: 'CF', country: 'Central African Republic', code: '+236' },
{ iso2: 'TD', country: 'Chad', code: '+235' },
{ iso2: 'CL', country: 'Chile', code: '+56' },
{ iso2: 'CX', country: 'Christmas Island', code: '+61' },
{ iso2: 'CC', country: 'Cocos (Keeling) Islands', code: '+61' },
{ iso2: 'CO', country: 'Colombia', code: '+57' },
{ iso2: 'KM', country: 'Comoros', code: '+269' },
{ iso2: 'CG', country: 'Congo - Brazzaville', code: '+242' },
{ iso2: 'CD', country: 'Congo - Kinshasa', code: '+243' },
{ iso2: 'CK', country: 'Cook Islands', code: '+682' },
{ iso2: 'CR', country: 'Costa Rica', code: '+506' },
{ iso2: 'CI', country: 'Cote d\'Ivoire', code: '+225' },
{ iso2: 'HR', country: 'Croatia', code: '+385' },
{ iso2: 'CU', country: 'Cuba', code: '+53' },
{ iso2: 'CW', country: 'Curacao', code: '+599' },
{ iso2: 'CY', country: 'Cyprus', code: '+357' },
{ iso2: 'CZ', country: 'Czechia', code: '+420' },
{ iso2: 'DK', country: 'Denmark', code: '+45' },
{ iso2: 'DJ', country: 'Djibouti', code: '+253' },
{ iso2: 'DM', country: 'Dominica', code: '+1' },
{ iso2: 'DO', country: 'Dominican Republic', code: '+1' },
{ iso2: 'EC', country: 'Ecuador', code: '+593' },
{ iso2: 'EG', country: 'Egypt', code: '+20' },
{ iso2: 'SV', country: 'El Salvador', code: '+503' },
{ iso2: 'GQ', country: 'Equatorial Guinea', code: '+240' },
{ iso2: 'ER', country: 'Eritrea', code: '+291' },
{ iso2: 'EE', country: 'Estonia', code: '+372' },
{ iso2: 'SZ', country: 'Eswatini', code: '+268' },
{ iso2: 'ET', country: 'Ethiopia', code: '+251' },
{ iso2: 'FK', country: 'Falkland Islands', code: '+500' },
{ iso2: 'FO', country: 'Faroe Islands', code: '+298' },
{ iso2: 'FJ', country: 'Fiji', code: '+679' },
{ iso2: 'FI', country: 'Finland', code: '+358' },
{ iso2: 'FR', country: 'France', code: '+33' },
{ iso2: 'GF', country: 'French Guiana', code: '+594' },
{ iso2: 'PF', country: 'French Polynesia', code: '+689' },
{ iso2: 'GA', country: 'Gabon', code: '+241' },
{ iso2: 'GM', country: 'Gambia', code: '+220' },
{ iso2: 'GE', country: 'Georgia', code: '+995' },
{ iso2: 'DE', country: 'Germany', code: '+49' },
{ iso2: 'GH', country: 'Ghana', code: '+233' },
{ iso2: 'GI', country: 'Gibraltar', code: '+350' },
{ iso2: 'GR', country: 'Greece', code: '+30' },
{ iso2: 'GL', country: 'Greenland', code: '+299' },
{ iso2: 'GD', country: 'Grenada', code: '+1' },
{ iso2: 'GP', country: 'Guadeloupe', code: '+590' },
{ iso2: 'GU', country: 'Guam', code: '+1' },
{ iso2: 'GT', country: 'Guatemala', code: '+502' },
{ iso2: 'GG', country: 'Guernsey', code: '+44' },
{ iso2: 'GN', country: 'Guinea', code: '+224' },
{ iso2: 'GW', country: 'Guinea-Bissau', code: '+245' },
{ iso2: 'GY', country: 'Guyana', code: '+592' },
{ iso2: 'HT', country: 'Haiti', code: '+509' },
{ iso2: 'HN', country: 'Honduras', code: '+504' },
{ iso2: 'HU', country: 'Hungary', code: '+36' },
{ iso2: 'IS', country: 'Iceland', code: '+354' },
{ iso2: 'IN', country: 'India', code: '+91' },
{ iso2: 'ID', country: 'Indonesia', code: '+62' },
{ iso2: 'IR', country: 'Iran', code: '+98' },
{ iso2: 'IQ', country: 'Iraq', code: '+964' },
{ iso2: 'IE', country: 'Ireland', code: '+353' },
{ iso2: 'IM', country: 'Isle of Man', code: '+44' },
{ iso2: 'IL', country: 'Israel', code: '+972' },
{ iso2: 'IT', country: 'Italy', code: '+39' },
{ iso2: 'JM', country: 'Jamaica', code: '+1' },
{ iso2: 'JP', country: 'Japan', code: '+81' },
{ iso2: 'JE', country: 'Jersey', code: '+44' },
{ iso2: 'JO', country: 'Jordan', code: '+962' },
{ iso2: 'KZ', country: 'Kazakhstan', code: '+7' },
{ iso2: 'KE', country: 'Kenya', code: '+254' },
{ iso2: 'KI', country: 'Kiribati', code: '+686' },
{ iso2: 'XK', country: 'Kosovo', code: '+383' },
{ iso2: 'KW', country: 'Kuwait', code: '+965' },
{ iso2: 'KG', country: 'Kyrgyzstan', code: '+996' },
{ iso2: 'LA', country: 'Laos', code: '+856' },
{ iso2: 'LV', country: 'Latvia', code: '+371' },
{ iso2: 'LB', country: 'Lebanon', code: '+961' },
{ iso2: 'LS', country: 'Lesotho', code: '+266' },
{ iso2: 'LR', country: 'Liberia', code: '+231' },
{ iso2: 'LY', country: 'Libya', code: '+218' },
{ iso2: 'LI', country: 'Liechtenstein', code: '+423' },
{ iso2: 'LT', country: 'Lithuania', code: '+370' },
{ iso2: 'LU', country: 'Luxembourg', code: '+352' },
{ iso2: 'MG', country: 'Madagascar', code: '+261' },
{ iso2: 'MW', country: 'Malawi', code: '+265' },
{ iso2: 'MY', country: 'Malaysia', code: '+60' },
{ iso2: 'MV', country: 'Maldives', code: '+960' },
{ iso2: 'ML', country: 'Mali', code: '+223' },
{ iso2: 'MT', country: 'Malta', code: '+356' },
{ iso2: 'MH', country: 'Marshall Islands', code: '+692' },
{ iso2: 'MQ', country: 'Martinique', code: '+596' },
{ iso2: 'MR', country: 'Mauritania', code: '+222' },
{ iso2: 'MU', country: 'Mauritius', code: '+230' },
{ iso2: 'YT', country: 'Mayotte', code: '+262' },
{ iso2: 'MX', country: 'Mexico', code: '+52' },
{ iso2: 'FM', country: 'Micronesia', code: '+691' },
{ iso2: 'MD', country: 'Moldova', code: '+373' },
{ iso2: 'MC', country: 'Monaco', code: '+377' },
{ iso2: 'MN', country: 'Mongolia', code: '+976' },
{ iso2: 'ME', country: 'Montenegro', code: '+382' },
{ iso2: 'MS', country: 'Montserrat', code: '+1' },
{ iso2: 'MA', country: 'Morocco', code: '+212' },
{ iso2: 'MZ', country: 'Mozambique', code: '+258' },
{ iso2: 'MM', country: 'Myanmar (Burma)', code: '+95' },
{ iso2: 'NA', country: 'Namibia', code: '+264' },
{ iso2: 'NR', country: 'Nauru', code: '+674' },
{ iso2: 'NP', country: 'Nepal', code: '+977' },
{ iso2: 'NL', country: 'Netherlands', code: '+31' },
{ iso2: 'NC', country: 'New Caledonia', code: '+687' },
{ iso2: 'NZ', country: 'New Zealand', code: '+64' },
{ iso2: 'NI', country: 'Nicaragua', code: '+505' },
{ iso2: 'NE', country: 'Niger', code: '+227' },
{ iso2: 'NG', country: 'Nigeria', code: '+234' },
{ iso2: 'NU', country: 'Niue', code: '+683' },
{ iso2: 'NF', country: 'Norfolk Island', code: '+672' },
{ iso2: 'KP', country: 'North Korea', code: '+850' },
{ iso2: 'MK', country: 'North Macedonia', code: '+389' },
{ iso2: 'MP', country: 'Northern Mariana Islands', code: '+1' },
{ iso2: 'NO', country: 'Norway', code: '+47' },
{ iso2: 'OM', country: 'Oman', code: '+968' },
{ iso2: 'PK', country: 'Pakistan', code: '+92' },
{ iso2: 'PW', country: 'Palau', code: '+680' },
{ iso2: 'PS', country: 'Palestinian Territories', code: '+970' },
{ iso2: 'PA', country: 'Panama', code: '+507' },
{ iso2: 'PG', country: 'Papua New Guinea', code: '+675' },
{ iso2: 'PY', country: 'Paraguay', code: '+595' },
{ iso2: 'PE', country: 'Peru', code: '+51' },
{ iso2: 'PH', country: 'Philippines', code: '+63' },
{ iso2: 'PL', country: 'Poland', code: '+48' },
{ iso2: 'PT', country: 'Portugal', code: '+351' },
{ iso2: 'PR', country: 'Puerto Rico', code: '+1' },
{ iso2: 'QA', country: 'Qatar', code: '+974' },
{ iso2: 'RE', country: 'Reunion', code: '+262' },
{ iso2: 'RO', country: 'Romania', code: '+40' },
{ iso2: 'RU', country: 'Russia', code: '+7' },
{ iso2: 'RW', country: 'Rwanda', code: '+250' },
{ iso2: 'WS', country: 'Samoa', code: '+685' },
{ iso2: 'SM', country: 'San Marino', code: '+378' },
{ iso2: 'ST', country: 'Sao Tome and Principe', code: '+239' },
{ iso2: 'SA', country: 'Saudi Arabia', code: '+966' },
{ iso2: 'SN', country: 'Senegal', code: '+221' },
{ iso2: 'RS', country: 'Serbia', code: '+381' },
{ iso2: 'SC', country: 'Seychelles', code: '+248' },
{ iso2: 'SL', country: 'Sierra Leone', code: '+232' },
{ iso2: 'SG', country: 'Singapore', code: '+65' },
{ iso2: 'SX', country: 'Sint Maarten', code: '+1' },
{ iso2: 'SK', country: 'Slovakia', code: '+421' },
{ iso2: 'SI', country: 'Slovenia', code: '+386' },
{ iso2: 'SB', country: 'Solomon Islands', code: '+677' },
{ iso2: 'SO', country: 'Somalia', code: '+252' },
{ iso2: 'ZA', country: 'South Africa', code: '+27' },
{ iso2: 'KR', country: 'South Korea', code: '+82' },
{ iso2: 'SS', country: 'South Sudan', code: '+211' },
{ iso2: 'ES', country: 'Spain', code: '+34' },
{ iso2: 'LK', country: 'Sri Lanka', code: '+94' },
{ iso2: 'BL', country: 'St. Barthelemy', code: '+590' },
{ iso2: 'SH', country: 'St. Helena', code: '+290' },
{ iso2: 'KN', country: 'St. Kitts and Nevis', code: '+1' },
{ iso2: 'LC', country: 'St. Lucia', code: '+1' },
{ iso2: 'MF', country: 'St. Martin', code: '+590' },
{ iso2: 'PM', country: 'St. Pierre and Miquelon', code: '+508' },
{ iso2: 'VC', country: 'St. Vincent and Grenadines', code: '+1' },
{ iso2: 'SD', country: 'Sudan', code: '+249' },
{ iso2: 'SR', country: 'Suriname', code: '+597' },
{ iso2: 'SJ', country: 'Svalbard and Jan Mayen', code: '+47' },
{ iso2: 'SE', country: 'Sweden', code: '+46' },
{ iso2: 'CH', country: 'Switzerland', code: '+41' },
{ iso2: 'SY', country: 'Syria', code: '+963' },
{ iso2: 'TW', country: 'Taiwan', code: '+886' },
{ iso2: 'TJ', country: 'Tajikistan', code: '+992' },
{ iso2: 'TZ', country: 'Tanzania', code: '+255' },
{ iso2: 'TH', country: 'Thailand', code: '+66' },
{ iso2: 'TL', country: 'Timor-Leste', code: '+670' },
{ iso2: 'TG', country: 'Togo', code: '+228' },
{ iso2: 'TK', country: 'Tokelau', code: '+690' },
{ iso2: 'TO', country: 'Tonga', code: '+676' },
{ iso2: 'TT', country: 'Trinidad and Tobago', code: '+1' },
{ iso2: 'TA', country: 'Tristan da Cunha', code: '+290' },
{ iso2: 'TN', country: 'Tunisia', code: '+216' },
{ iso2: 'TR', country: 'Turkiye', code: '+90' },
{ iso2: 'TM', country: 'Turkmenistan', code: '+993' },
{ iso2: 'TC', country: 'Turks and Caicos Islands', code: '+1' },
{ iso2: 'TV', country: 'Tuvalu', code: '+688' },
{ iso2: 'VI', country: 'U.S. Virgin Islands', code: '+1' },
{ iso2: 'UG', country: 'Uganda', code: '+256' },
{ iso2: 'UA', country: 'Ukraine', code: '+380' },
{ iso2: 'AE', country: 'United Arab Emirates', code: '+971' },
{ iso2: 'GB', country: 'United Kingdom', code: '+44' },
{ iso2: 'US', country: 'United States', code: '+1' },
{ iso2: 'UY', country: 'Uruguay', code: '+598' },
{ iso2: 'UZ', country: 'Uzbekistan', code: '+998' },
{ iso2: 'VU', country: 'Vanuatu', code: '+678' },
{ iso2: 'VA', country: 'Vatican City', code: '+39' },
{ iso2: 'VE', country: 'Venezuela', code: '+58' },
{ iso2: 'VN', country: 'Vietnam', code: '+84' },
{ iso2: 'WF', country: 'Wallis and Futuna', code: '+681' },
{ iso2: 'EH', country: 'Western Sahara', code: '+212' },
{ iso2: 'YE', country: 'Yemen', code: '+967' },
{ iso2: 'ZM', country: 'Zambia', code: '+260' },
{ iso2: 'ZW', country: 'Zimbabwe', code: '+263' },
];
export function getCountryDialCodeOptions() {
return countryDialCodes.map(item => ({
...item,
label: item.code,
value: item.code,
}));
}
export default countryDialCodes;
+4 -9
View File
@@ -121,10 +121,8 @@ module.exports = (vm) => {
break; break;
case 400: case 400:
kickout() kickout()
break;
case 401: case 401:
if(error.config.url.indexOf('refresh_token')==-1 && error.config.url.indexOf('logout')==-1 && !error.config.custom?.tokenRetried){ if(error.config.url.indexOf('refresh_token')==-1 && error.config.url.indexOf('logout')==-1){
error.config.custom = {...error.config.custom, tokenRetried: true};
return tokenRefresh().then(_ => { return tokenRefresh().then(_ => {
error.config.header.Authorization = uni.getStorageSync("token"); error.config.header.Authorization = uni.getStorageSync("token");
error.config.header.RefreshToken = uni.getStorageSync("refreshToken") error.config.header.RefreshToken = uni.getStorageSync("refreshToken")
@@ -143,22 +141,19 @@ module.exports = (vm) => {
} }
function tokenRefresh(){ function tokenRefresh(){
const storedRefreshToken = uni.getStorageSync("refreshToken"); let oldToken = uni.getStorageSync("token");
if (storedRefreshToken) { if (oldToken !== null && oldToken !== '') {
return refreshToken({}) return refreshToken({})
.then((data) => { .then((data) => {
if (!data?.success || !data.accessToken) throw new Error('Token refresh failed');
uni.setStorageSync("token",data.accessToken); uni.setStorageSync("token",data.accessToken);
uni.setStorageSync("tokenTime",dayjs().unix()); uni.setStorageSync("tokenTime",dayjs().unix());
return Promise.resolve(true) return Promise.resolve(true)
}) })
.catch(error => { .catch(error => {
kickout() kickout()
return Promise.reject(error);
}) })
} else { } else {
kickout() kickout()
return Promise.reject(new Error('Missing RefreshToken'));
} }
} }
function checkToken(){ function checkToken(){
@@ -203,4 +198,4 @@ function checkIP(header){
} }
}*/ }*/
} }
} }
+13 -1
View File
@@ -10,6 +10,13 @@
bgColor="#C39A4B" bgColor="#C39A4B"
titleStyle="max-width:none;width:auto;font-weight:bold;color:#ffffff;" titleStyle="max-width:none;width:auto;font-weight:bold;color:#ffffff;"
> >
<view
@tap="goNotification"
style="margin-top:2px;margin-left:3px;"
slot="left"
>
<u-icon name="bell" color="#FFFFFF" size="24"></u-icon>
</view>
<view @tap="logout" <view @tap="logout"
style="margin-top:2px;margin-right:3px;" style="margin-top:2px;margin-right:3px;"
slot="right" slot="right"
@@ -39,6 +46,11 @@
this.isLogin(); this.isLogin();
}, },
methods:{ methods:{
goNotification() {
uni.navigateTo({
url: '/limo/notification/notification'
});
},
updateUserData() { updateUserData() {
this.userData = uni.getStorageSync("userData"); this.userData = uni.getStorageSync("userData");
if (this.userData) { if (this.userData) {
@@ -109,4 +121,4 @@
<style> <style>
</style> </style>
+827
View File
@@ -0,0 +1,827 @@
<template>
<view>
<view class="hz-page">
<u-navbar
@leftClick="back()"
:safeAreaInsetTop="true"
:placeholder="true"
:title="title"
:fixed="true"
:autoBack="false"
leftIconColor="#000000"
titleStyle="font-weight:bold;"
v-if="NotCapping"
>
<view
style="margin-top:2px;margin-right:3px;"
slot="right"
>
<view style="border:1rpx solid #ccc;border-radius: 14px;padding:4px 12px;" class="hz-flex-row hz-flex-vertical-mid">
<image @click="navTo('/pages/findPlayer/findPlayer',1,false)" src="../../static/images/ico_find_player.png" class="hz-nav-btn"></image>
<view style="height:20px;border-left:1px solid #ccc;margin-left:8px;margin-right:8px;"></view>
<image @click="navTo('/booking/bookingRecord/bookingRecord?from=booking',2,false)" src="../../static/images/ico_list.png" class="hz-nav-btn"></image>
</view>
</view>
</u-navbar>
<view v-if="limoData" class="hz-spacing hz-bg-white" style="margin-top:8px;">
<view class="hz-flex-row" v-if="!NotCapping" style="margin-top:20rpx;justify-content: center;">
<image src="../../static/images/mgm_logo_1.png" style="width:130px;height:40px;"></image>
</view>
<view class="hz-flex-row hz-pd-30 hz-font-md hz-font-bolder hz-card-booking" style="margin-top:20rpx;align-items:center;">
<view style="width:72%;">Conf # : {{ getConfirmationNo(limoData) }}</view>
<view class="hz-btn-status hz-text-center hz-limo-status-tag" :style="getLimoStatusStyle(limoData.status)">{{ getStatusText(limoData) }}</view>
</view>
<view class="hz-card-profile hz-mr-b-30 hz-mr-t-30">
<view class="hz-flex-row">
<view style="width:60%">
<view class="hz-font-color-light hz-font-sm">{{$t("common.playerId")}}</view>
<view class="hz-font-bolder hz-font-lg" >{{ playerData.playerId }}</view>
</view>
<view style="width:40%">
<view class="hz-font-color-light hz-font-sm">{{$t("common.tier")}}</view>
<view class="hz-font-bolder hz-font-lg" >{{ playerData.tierName }}</view>
</view>
</view>
<view class="hz-flex-row hz-mr-t-30">
<view style="width:100%">
<view class="hz-font-color-light hz-font-sm">{{$t("common.name")}}</view>
<view class="hz-font-bolder hz-font-lg" >{{playerData.salutation + " " + playerData.name }}</view>
</view>
</view>
</view>
<view class="hz-pd-30 hz-font-bolder hz-font-md hz-card-extend limo-info-card" style="margin-top:10px;">
<view class="limo-info-grid">
<view class="limo-info-item">
<image src="../../static/images/ico_calendar.png" class="limo-info-icon"></image>
<view class="hz-font-bold hz-font-md">{{ getPickupDate(limoData.pickUpDate) }}</view>
</view>
<view class="limo-info-item">
<image src="../../static/images/clock.png" class="limo-info-icon"></image>
<view class="hz-font-bold hz-font-md">{{ getPickupTime(limoData.pickUpDate) }}</view>
</view>
<view class="limo-info-item">
<u-icon name="account-fill" size="18" color="#C39A4B" class="limo-info-icon"></u-icon>
<view>{{ limoData.noOfPax }} Pax</view>
</view>
<!-- <view class="limo-info-item">
<image src="../../static/images/car_no.png" class="limo-info-icon"></image>
<view class="hz-font-bold hz-font-md">{{ getCarNo(limoData.carNo) }}</view>
</view> -->
</view>
<view v-if="canEditLimo(limoData)" @click="editLimoBooking()" class="limo-edit-button">
<image src="/static/images/edit.png" mode="aspectFit" class="limo-edit-icon"></image>
</view>
</view>
<view class="hz-spacing hz-mr-t-30" style="margin-bottom:30px;">
<view class="hz-flex-row">
<view style="width:100%">
<view class="hz-font-color-black hz-font-sm">{{$t("limoBookingDetail.pickupPoint")}}</view>
<view class="hz-font-bolder hz-font-lg">{{limoData.pickUpPoint}}</view>
</view>
</view>
<view class="hz-flex-row hz-mr-t-30">
<view v-if="limoData.flightDetail!=null && limoData.flightDetail != ''" style="width:55%">
<view class="hz-font-color-black hz-font-sm">{{$t("limoBookingDetail.flightNo")}}</view>
<view class="hz-font-bolder hz-font-lg">
<view>
{{ limoData.flightDetail }}
</view>
</view>
</view>
<view v-if="limoData.greetType!=null && limoData.greetType != ''" style="width:45%">
<view class="hz-font-color-black hz-font-sm">{{$t("limoBookingDetail.greetType")}}</view>
<view class="hz-font-bolder hz-font-lg">
<view>
{{ limoData.greetType }}
</view>
</view>
</view>
</view>
<view v-if="hasExtraDropOffPoint(limoData)" class="hz-flex-row hz-mr-t-30">
<view style="width:100%">
<view class="hz-font-color-black hz-font-sm">{{$t("limoBookingDetail.extraDropoff")}}</view>
<view class="hz-font-bolder hz-font-lg">
<view v-for="(extraDropOffPoint,index) in getExtraDropOffPointDisplayItems(limoData.extraDropOffPoint)" :key="'extra-dropoff-'+index">
<view>#{{index + 1}} {{extraDropOffPoint.zh}}</view>
<view v-if="extraDropOffPoint.en">{{extraDropOffPoint.en}}</view>
</view>
</view>
</view>
</view>
<view class="hz-flex-row hz-mr-t-30">
<view style="width:100%">
<view class="hz-font-color-black hz-font-sm">{{$t("limoBookingDetail.dropoffPoint")}}</view>
<view class="hz-font-bolder hz-font-lg">
<view v-for="(dropOffPoint,index) in getDropOffPointLines(limoData.dropOffPoint)" :key="'dropoff-'+index">
{{dropOffPoint}}
</view>
</view>
</view>
</view>
<view class="hz-flex-row hz-mr-t-30">
<view style="width:55%">
<view class="hz-font-color-black hz-font-sm">{{$t("bookingConfirm.createdOn")}}</view>
<view class="hz-font-bolder hz-font-lg">{{limoData.createdAt}}</view>
</view>
<view style="width:45%">
<view class="hz-font-color-black hz-font-sm">{{$t("bookingConfirm.createdBy")}}</view>
<view class="hz-font-bolder hz-font-lg">{{limoData.createdBy ? limoData.createdBy : '-'}}</view>
</view>
</view>
<view class="hz-flex-row hz-mr-t-30">
<view v-if="NotCapping" style="width:55%">
<view class="hz-font-color-black hz-font-sm">{{$t("limoBookingDetail.overrideBy")}}</view>
<view class="hz-font-bolder hz-font-lg">{{limoData.overrideBy ? limoData.overrideBy : '-'}}</view>
</view>
<view style="width:45%">
<view class="hz-flex-row hz-mr-t-30" @click="copyDriverInfo(limoData)">
<view class="hz-font-color-black hz-font-sm">{{$t("limoBookingDetail.carTypeAndPlate")}}</view>
<image src="/static/images/copy.png" style="height: 20px;width: 20px"></image>
</view>
<view class="hz-font-bolder hz-font-lg" style="white-space: pre-wrap;">{{formatCarTypeAndCarNo(limoData)}}</view>
</view>
</view>
<view class="hz-flex-row hz-mr-t-30">
<view v-if="NotCapping" style="width:55%">
<view class="hz-font-color-black hz-font-sm">{{$t("limoBookingDetail.tripPrice")}}</view>
<view class="hz-font-bolder hz-font-lg">{{formatTripPrice(limoData.totalAmount)}}</view>
</view>
<view v-if="!NotCapping" style="width:55%">
</view>
<view style="width:45%">
<view class="hz-font-color-black hz-font-sm">{{$t("limoBookingDetail.driverContact")}}</view>
<view class="hz-font-bolder hz-font-lg" style="white-space: pre-wrap;">{{GetDriverContact(limoData)}}</view>
</view>
</view>
<view v-if="NotCapping" class="hz-flex-row hz-mr-t-30">
<view style="width:55%">
<view class="hz-font-color-black hz-font-sm">{{$t("limoBookingDetail.driverSource")}}</view>
<view class="hz-font-bolder hz-font-lg">{{limoData.driverSource != null && limoData.driverSource != '' ? limoData.driverSource : "N/A"}}</view>
</view>
</view>
<view v-if="limoData.remarks != null && limoData.remarks != '' && NotCapping" class="hz-flex-row hz-mr-t-30">
<view style="width:100%">
<view class="hz-font-color-black hz-font-sm">{{$t("limoBookingDetail.remark")}}</view>
<view class="hz-font-bolder hz-font-lg">{{ limoData.remarks }}</view>
</view>
</view>
</view>
<view style="height:100rpx;"></view>
</view>
<u-empty
v-if="loadError"
mode="search"
:text="dataNotFoundStr"
>
</u-empty>
<u-tabbar
:fixed="true"
:placeholder="true"
:safeAreaInsetBottom="true"
customStyle="z-index:100;height:58px;"
v-if="limoData && showTabbar"
>
<view class="book hz-flex-row hz-spacing" style="width: 45%;">
<view class="hz-btn hz-font-color-white hz-font-sm hz-font-bold hz-text-center" @click="popupLang()">
{{$t("bookingDetail.saveImage")}}
</view>
</view>
<view class="book hz-flex-row hz-spacing" style="width: 45%;" :class="limoData.status !=='PENDING' ? 'hz-disabled' :''">
<view class="hz-btn hz-font-color-white hz-font-sm hz-font-bold hz-text-center" @click="cancelLimoBooking()">
{{$t("bookingDetail.cancelBooking")}}
</view>
</view>
</u-tabbar>
<u-popup :show="showCancelBookingSuccessPopup" mode="center" round="16px" :closeable="false" @close="closeCancelBookingSuccessPopup">
<view class="booking-popup-card booking-popup-card--success"
:style="'width:' + windowWidth + 'px;'">
<view class="booking-popup-icon-row">
<image :src="iconPath" style="width:64px;height:64px;"></image>
</view>
<view class="booking-popup-title booking-popup-title--success hz-font-md hz-font-bold hz-text-center">
Cancel Booking Successfully
</view>
<view
@click="closeCancelBookingSuccessPopup"
class="booking-popup-button booking-popup-button--primary booking-popup-button--success hz-btn hz-font-color-white hz-font-lg hz-font-bold hz-text-center"
>
OK
</view>
</view>
</u-popup>
<u-popup :show="showSaveImageSuccessPopup" mode="center" round="16px" :closeable="false" @close="closeSaveImageSuccessPopup">
<view class="booking-popup-card booking-popup-card--success"
:style="'width:' + windowWidth + 'px;'">
<view class="booking-popup-icon-row">
<image src="../../static/images/ico_suc.png" style="width:64px;height:64px;"></image>
</view>
<view class="booking-popup-title booking-popup-title--success hz-font-md hz-font-bold hz-text-center">
Image Saved Successfully
</view>
<view
@click="closeSaveImageSuccessPopup"
class="booking-popup-button booking-popup-button--primary booking-popup-button--success hz-btn hz-font-color-white hz-font-lg hz-font-bold hz-text-center"
>
OK
</view>
</view>
</u-popup>
<u-picker closeOnClickOverlay :show="showPicker" :defaultIndex="currentLangIndex" :columns="langList" @close="showPicker=false" :cancelText="cancelText" :confirmText="okText" confirmColor="#C39A4B" @cancel="showPicker=false" @confirm="confirmLang" keyName="name"></u-picker>
</view>
</view>
</template>
<script>
import { getLimoBookingById, getLimoBookingByConf } from '../../api/limoApi.js';
import { cancelLimoBooking } from '../../api/limoApi.js';
export default {
data() {
return {
playerData:null,
userData:null,
deviceData:null,
limoData:null,
loadError:false,
dataNotFoundStr:this.$t("common.dataNotFound"),
title:'',
from:'',
conf:'',
windowWidth:0,
showSaveImageSuccessPopup:false,
showCancelBookingSuccessPopup:false,
NotCapping:true,
showTabbar:true,
showWhenScreenshot:true,
capture:'',
showPicker:false,
lang:'',
langList:[
[{
name: 'English',
code: 'en'
}, {
name: '繁體中文',
code: 'zh-Hant'
}]
],
currentLangIndex:[0],
cancelText:this.$t("common.cancel"),
okText:this.$t("common.ok"),
iconPath: ''
}
},
onLoad(opt) {
this.initUI();
this.playerData = uni.getStorageSync("playerData");
this.userData = uni.getStorageSync("userData");
this.deviceData = uni.getStorageSync("deviceData");
this.windowWidth = this.deviceData && this.deviceData.windowWidth ? this.deviceData.windowWidth - 70 : 290;
if(opt.from!==undefined && opt.from!='') this.from = opt.from;
if(opt.conf!==undefined && opt.conf!='') this.conf = opt.conf;
// this.getLimoData();
this.getBooking(opt);
},
onShow() {
this.$i18n.locale = uni.getLocale();
},
methods: {
initUI() {
this.title = this.$t("bookingDetail.title");
},
restoreScreenshotView() {
this.NotCapping = true;
this.showTabbar = true;
this.showWhenScreenshot = true;
},
// #ifdef APP-PLUS
async captureWebview() {
uni.pageScrollTo({
scrollTop: 0
});
this.NotCapping = false;
this.showTabbar = false;
this.showWhenScreenshot = false;
await uni.$u.sleep(1000);
var _this = this;
var ws = plus.webview.getTopWebview();
var bitmap = new plus.nativeObj.Bitmap('capScreen');
ws.draw(bitmap, function(){
console.log('截屏绘制图片成功');
bitmap.save('_doc/a.png', {},
async function(e){
console.log('保存图片成功:' + JSON.stringify(e));
await uni.$u.sleep(1000);
_this.restoreScreenshotView();
plus.gallery.save(e.target, () => {
plus.nativeUI.toast(_this.$t("common.picSaveSuccess"));
}, (err) => {
console.log('保存到相册失败:' + JSON.stringify(err));
});
},
function(e){
console.log('保存图片失败:' + JSON.stringify(e));
_this.restoreScreenshotView();
}
);
_this.capture = bitmap.toBase64Data();
bitmap.recycle();
}, function(e){
console.log('截屏绘制图片失败:' + JSON.stringify(e));
_this.restoreScreenshotView();
});
},
// #endif
back(){
if(this.from==='list'){
this.navTo('/booking/bookingRecord/bookingRecord?from=booking',2,false);
}else{
this.navBack();
}
},
getPickupDate(str){
if(!str) return '';
let datePart = str.split(' ')[0];
// Assume DD/MM/YYYY
let parts = datePart.split('/');
if(parts.length===3){
let dateObj = new Date(parts[2], parts[1]-1, parts[0]);
let weeks = ['Sun','Mon','Tue','Wed','Thu','Fri','Sat'];
let week = weeks[dateObj.getDay()];
return `(${week}) ${parts[2]}-${parts[1]}-${parts[0]}`;
}
return datePart;
},
getPickupTime(str){
if(!str) return '';
return str.split(' ')[1] || '';
},
getCarNo(carNo){
if(carNo!== undefined && carNo !== null && carNo !== ''){
return carNo
}else{
return 'N/A';
}
},
getDropOffPointLines(dropOffPoint){
if(dropOffPoint === null || dropOffPoint === undefined || dropOffPoint === '') return [''];
return String(dropOffPoint).split(/[;;]/).map(item => item.trim()).filter(item => item !== '');
},
getExtraDropOffPointDisplayItems(extraDropOffPoint) {
return this.getDropOffPointLines(extraDropOffPoint).map((item) => {
const parts = String(item).split('/');
return {
zh: String(parts[0] || '').trim(),
en: String(parts.slice(1).join('/') || '').trim()
};
});
},
getExtraDropOffPointFromBooking(booking) {
if (!booking) {
return '';
}
const value =
booking.extraDropOffPoint != null && String(booking.extraDropOffPoint).trim() !== ''
? booking.extraDropOffPoint
: '';
return String(value || '').trim();
},
hasExtraDropOffPoint(limoData) {
return (
limoData &&
limoData.extraDropOffPoint != null &&
String(limoData.extraDropOffPoint).trim() !== ''
);
},
formatDate(date, format = 'YYYY-MM-DD HH:mm:ss') {
if(date === null || date === undefined || date === '') return '';
let normalizedDate = date;
if(typeof normalizedDate === 'string' && /^\d+$/.test(normalizedDate)){
normalizedDate = Number(normalizedDate);
}
if(typeof normalizedDate === 'number' && normalizedDate < 1000000000000){
normalizedDate = normalizedDate * 1000;
}
const d = new Date(normalizedDate);
if(Number.isNaN(d.getTime())) return '';
const map = {
YYYY: d.getFullYear(),
MM: String(d.getMonth() + 1).padStart(2, '0'),
DD: String(d.getDate()).padStart(2, '0'),
HH: String(d.getHours()).padStart(2, '0'),
mm: String(d.getMinutes()).padStart(2, '0'),
ss: String(d.getSeconds()).padStart(2, '0')
};
return format.replace(/YYYY|MM|DD|HH|mm|ss/g, matched => map[matched]);
},
normalizeStatus(status){
if(status==null || status===undefined){
return '';
}
return String(status).toUpperCase().replace(/\s+/g,' ').trim();
},
canEditLimo(limo){
return this.normalizeStatus(limo && limo.status) === 'PENDING';
},
getLimoStatusStyle(status){
const normalizedStatus = this.normalizeStatus(status);
const style = {
minWidth: 'auto',
height: 'auto',
lineHeight: '1.2',
whiteSpace: 'nowrap',
paddingTop: '6px',
paddingBottom: '6px',
paddingLeft: '12px',
paddingRight: '12px',
flex: 1,
borderRadius: '6px',
fontSize: '12px',
fontWeight: '700',
backgroundColor: 'rgb(148, 148, 148)',
color: '#ffffff'
};
if(normalizedStatus === 'CONFIRMED'){
style.backgroundColor = '#439048';
}
if(normalizedStatus === 'DRIVER_DEPARTED'){
style.backgroundColor = '#439048';
}
if(normalizedStatus === 'DRIVER_ASSIGNED'){
style.backgroundColor = '#439048';
}
if(normalizedStatus === 'DRIVER_ARRIVED'){
style.backgroundColor = '#439048';
}
if(normalizedStatus === 'GUEST_PICKED'){
style.backgroundColor = '#439048';
}
return style;
},
editLimoBooking(){
if(!this.canEditLimo(this.limoData)){
return;
}
uni.setStorageSync('limoEditData', this.limoData);
this.navTo('/limo/newLimo/newLimo?mode=edit&from=limoDetail',0,false);
},
async cancelLimoBooking() {
if(this.limoData.status !== 'PENDING') return null;
let updatedBy = this.limoData.updatedBy;
let bookingId = this.limoData.id;
let params = {
updatedBy: updatedBy,
id: bookingId
};
try {
const res = await cancelLimoBooking(params);
if(res && res.success) {
this.iconPath = '../../static/images/ico_suc.png';
this.showCancelBookingSuccessPopup = true;
} else {
this.iconPath = '../../static/images/ico_err.png';
}
} catch (e) {
console.error(e)
}
},
showSaveImageSuccessDialog(){
this.showSaveImageSuccessPopup = true;
},
popupLang(){
this.currentLangIndex = [];
for(let i=0;i<this.langList[0].length;i++){
if(this.langList[0][i].code === this.$i18n.locale){
this.currentLangIndex.push(i);
break;
}
}
this.showPicker = true;
},
async confirmLang(e){
this.$i18n.locale = e.value[0].code;
this.lang = e.value[0].name;
this.title = this.$t("bookingDetail.title");
this.cancelText = this.$t("common.cancel");
this.okText = this.$t("common.ok");
this.showPicker = false;
this.captureWebview();
},
closeSaveImageSuccessPopup(){
this.showSaveImageSuccessPopup = false;
},
closeCancelBookingSuccessPopup(){
this.showCancelBookingSuccessPopup = false;
this.navTo('/booking/bookingRecord/bookingRecord?from=booking',2,false);
},
getLimoData(){
const limoBookingList = [];
getBooking();
},
async getDataByConfNo(confNo) {
if (confNo == null || confNo === '' || confNo === 'null') {
return;
}
await this.getLimoBookingByConf(confNo);
},
async getLimoBookingByConf(conf,id) {
this.loadError = false;
uni.showLoading({ title: this.$t("common.loading") });
try{
let params = {};
params.conf = conf;
const res = await getLimoBookingByConf(params);
if(res!=null && res.success==true){
let tempBooking = res.data;
let temp = {
id: tempBooking.id,
confirmationNo: tempBooking.confirmationNo,
status: tempBooking.bookingStatus,
bookingType: tempBooking.bookingType,
serviceType: tempBooking.serviceType,
pickUpDate: this.formatDate(tempBooking.pickUpDatetime, 'DD/MM/YYYY HH:mm'),
greetType: tempBooking.greetType,
signage: tempBooking.signage,
tripPrice: tempBooking.tripPrice,
tripContact: tempBooking.tripContact,
bookingContact: tempBooking.bookingContact,
tenOutContact: tempBooking.tenOutContact,
guestContact: tempBooking.guestContact,
isHighImportance: tempBooking.isHighImportance,
isTenOut: tempBooking.isTenOut,
isConnectGuestDirectly: tempBooking.isConnectGuestDirectly,
noOfPax: tempBooking.noOfPax,
pickUpPoint: tempBooking.pickUpPoint,
carType: tempBooking.carType,
extraDropOffPoint: this.getExtraDropOffPointFromBooking(tempBooking),
extraOffPoint: this.getExtraDropOffPointFromBooking(tempBooking),
dropOffPoint: tempBooking.dropOffPoint,
remarks: tempBooking.remarks,
isHourlyRate: tempBooking.isHourlyRate,
hourlyRate: tempBooking.hourlyRate,
flightDetail: tempBooking.flightDetail,
flightEta: tempBooking.flightEta,
flightStatus: tempBooking.flightStatus,
approver: tempBooking.approver,
approverId: tempBooking.approverId,
paymentMethod: tempBooking.paymentMethod,
createdAt: this.formatDate(tempBooking.createdAt, "YYYY-MM-DD HH:mm:ss"),
createdBy: tempBooking.createdBy,
overrideBy: tempBooking.overrideBy,
carNo:tempBooking.carNo,
driverContact:tempBooking.driverContact,
driverSource:tempBooking.driverSource,
totalAmount:tempBooking.totalAmount
}
this.limoData = temp;
}else{
this.loadError = true;
uni.$u.toast(res.message,5000);
}
}catch(e){
this.loadError = true;
console.error(e);
uni.$u.toast(e.errMsg,5000);
}finally{
uni.hideLoading();
}
},
async getLimoBookingById(id) {
this.loadError = false;
uni.showLoading({ title: this.$t("common.loading") });
try{
let params = {};
params.id = id;
const res = await getLimoBookingById(params);
if(res!=null && res.success==true){
let tempBooking = res.data;
let temp = {
id: tempBooking.id,
confirmationNo: tempBooking.confirmationNo,
status: tempBooking.bookingStatus,
bookingType: tempBooking.bookingType,
serviceType: tempBooking.serviceType,
pickUpDate: this.formatDate(tempBooking.pickUpDatetime, 'DD/MM/YYYY HH:mm'),
greetType: tempBooking.greetType,
signage: tempBooking.signage,
tripPrice: tempBooking.tripPrice,
tripContact: tempBooking.tripContact,
bookingContact: tempBooking.bookingContact,
tenOutContact: tempBooking.tenOutContact,
guestContact: tempBooking.guestContact,
isHighImportance: tempBooking.isHighImportance,
isTenOut: tempBooking.isTenOut,
isConnectGuestDirectly: tempBooking.isConnectGuestDirectly,
noOfPax: tempBooking.noOfPax,
pickUpPoint: tempBooking.pickUpPoint,
carType: tempBooking.carType,
extraDropOffPoint: this.getExtraDropOffPointFromBooking(tempBooking),
extraOffPoint: this.getExtraDropOffPointFromBooking(tempBooking),
dropOffPoint: tempBooking.dropOffPoint,
remarks: tempBooking.remarks,
isHourlyRate: tempBooking.isHourlyRate,
hourlyRate: tempBooking.hourlyRate,
flightDetail: tempBooking.flightDetail,
flightEta: tempBooking.flightEta,
flightStatus: tempBooking.flightStatus,
approver: tempBooking.approver,
approverId: tempBooking.approverId,
paymentMethod: tempBooking.paymentMethod,
createdAt: this.formatDate(tempBooking.createdAt, "YYYY-MM-DD HH-mm-ss"),
createdBy: tempBooking.createdBy,
overrideBy: tempBooking.overrideBy,
totalAmount:tempBooking.totalAmount
}
this.limoData = temp;
}else{
this.loadError = true;
uni.$u.toast(res.message,5000);
}
}catch(e){
this.loadError = true;
console.error(e);
uni.$u.toast(e.errMsg,5000);
}finally{
uni.hideLoading();
}
},
getBooking(opt) {
const confNo =
opt.confNo != null && opt.confNo !== '' && opt.confNo !== 'null'
? opt.confNo
: opt.conf;
if (
confNo != null &&
confNo !== undefined &&
confNo !== '' &&
confNo !== 'null'
) {
this.getDataByConfNo(confNo);
return;
}
if (
opt.id != null &&
opt.id !== undefined &&
opt.id !== '' &&
opt.id !== 'null'
) {
this.getLimoBookingById(opt.id);
}
},
formatTripPrice(value) {
if (value === null || value === undefined || value === '') return '-';
const text = String(value).replace(/,/g, '');
const parts = text.split('.');
parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ',');
return parts.join('.');
},
formatCarTypeAndCarNo(limoBooking) {
const carType = limoBooking.carType !== null ? limoBooking.carType : 'N/A';
const carNo = this.getCarNo(limoBooking.carNo);
console.log(limoBooking);
return ""+carType + "\n (" + carNo+")";
},
GetDriverContact(limoBooking) {
const driverContact = limoBooking && limoBooking.driverContact != null
? String(limoBooking.driverContact).trim()
: '';
if (!driverContact) {
return 'N/A';
}
const phoneNumbers = driverContact
.split(/[,,]/)
.map((phoneNumber) => phoneNumber.trim())
.filter(Boolean);
if (phoneNumbers.length <= 1) {
return phoneNumbers[0] || 'N/A';
}
return phoneNumbers
.map((phoneNumber, index) => `Phone No${index + 1}:${phoneNumber}`)
.join('\n');
},
copyDriverInfo(limoBooking){
const carType = limoBooking.carType !== null ? limoBooking.carType : 'N/A';
const carNo = this.getCarNo(limoBooking.carNo);
const driverContact = limoBooking.driverContact != null && limoBooking.driverContact != undefined && limoBooking.driverContact != "" ? limoBooking.driverContact : 'N/A';
const locale = this.$i18n && this.$i18n.locale ? this.$i18n.locale : uni.getLocale();
const isEnglish = String(locale || '').toLowerCase().startsWith('en');
const data = isEnglish
? `The details of your reserved limousine service are as follows:\nVehicle Type: ${carType}\nLicense Plate: ${carNo}\nDriver Phone: ${driverContact}`
: `阁下预订的贵宾车资料如下:\n車型:${carType}\n車牌號碼:${carNo}\n司機電話:${driverContact}`;
uni.setClipboardData({
data: data,
success: function () {
//调用方法成功
console.log('success');
}
})
}
},
computed:{
getConfirmationNo(){
return function (booking) {
if(booking!==undefined && booking!==null){
if(booking.confirmationNo!==undefined && booking.confirmationNo!==null &&
booking.confirmationNo!=='null' && booking.confirmationNo!=='' && booking.approveStatus!='W'){
return booking.confirmationNo;
}
}
return 'N/A';
}
},
getStatusText(){
return function (booking) {
console.log(booking);
if(booking!==undefined && booking!==null){
if(booking.status !==undefined && booking.status!==null) return booking.status.replace("_"," ");
}
}
},
}
}
</script>
<style lang="scss" scoped>
.limo-info-card {
position: relative;
padding-right: 54px;
}
.limo-info-grid {
display: grid;
grid-template-columns: minmax(0, 1.45fr) minmax(0, 0.9fr);
column-gap: 18px;
row-gap: 12px;
}
.limo-info-item {
display: flex;
align-items: center;
min-width: 0;
white-space: nowrap;
}
.limo-info-icon {
width: 18px;
height: 18px;
margin-right: 8px;
flex: 0 0 18px;
}
.limo-edit-button {
position: absolute;
top: 28px;
right: 24px;
width: 30px;
height: 30px;
display: flex;
align-items: center;
justify-content: flex-end;
}
.limo-edit-icon {
width: 20px;
height: 20px;
}
.booking-popup-card {
padding: 20px;
background-color: #ffffff;
border-radius: 16px;
}
.booking-popup-icon-row {
display: flex;
justify-content: center;
}
.booking-popup-title {
margin-top: 20px;
}
.booking-popup-title--success {
line-height: 1.4;
}
.booking-popup-button {
margin-top: 40px;
}
.booking-popup-button--primary {
background-color: #C39A4B;
}
.booking-popup-button--success {
width: 100%;
}
</style>
File diff suppressed because it is too large Load Diff
+761
View File
@@ -0,0 +1,761 @@
<template>
<view>
<u-navbar
:safeAreaInsetTop="true"
:placeholder="true"
:title="title"
:fixed="true"
:autoBack="true"
leftIconColor="#ffffff"
bgColor="#C39A4B"
titleStyle="font-weight:bold;color:#ffffff;"
>
<view
v-if="activeTabKey === 'approval' && canViewApproval && hasSelectedDate"
class="hz-nav-filter-clear"
slot="right"
@click="clearDateFilter"
>
<u-icon
size="28px"
name="../../static/images/filter-clear.svg"
></u-icon>
</view>
<view
v-if="activeTabKey === 'notification' && canViewNotification"
class="hz-nav-read-all"
slot="right"
@click="onReadAllClick"
>
<image src="/static/images/read_all.png" class="hz-nav-read-all-img"></image>
</view>
</u-navbar>
<u-tabs
v-if="shouldShowTabs"
:list="visibleTabList"
lineColor="#C39A4B"
lineWidth="36"
inactiveStyle="color: #c0c0c0; font-weight: bold;"
activeStyle="color: #C39A4B; font-weight: bold;"
:scrollable="false"
:current="tabIndex"
@change="tabChange"
>
</u-tabs>
<!-- Limo Notification -->
<view v-show="activeTabKey === 'notification' && canViewNotification">
<view v-if="!showNotification">
<view class="hz-flex-row approval-empty-img-row">
<image src="/static/images/datanull.png" class="approval-empty-img"></image>
</view>
<view class="hz-flex-row hz-font-bolder hz-font-md approval-empty-text-row">
{{ dataNull }}
</view>
</view>
<view
v-else
class="notice-card"
v-for="(notice, index) in limoNoticeList"
:key="notice.id != null ? notice.id : index"
@click="onNoticeCardClick(notice)"
:style="getNoticeCardStyle(notice)"
>
<image class="icon" :src="notice.icon" mode="aspectFill"></image>
<view class="info">
<view class="notice-type">{{ notice.type }}</view>
<view class="notice-title">{{ notice.title }}</view>
<view class="notice-desc">{{ notice.desc }}</view>
<view class="notice-time">{{ notice.time }}</view>
</view>
</view>
</view>
<!-- Approval Routing -->
<view v-show="activeTabKey === 'approval' && canViewApproval">
<view class="hz-spacing hz-mr-t-20 hz-flex-row approval-search-row">
<u-search
:placeholder="searchHolder"
v-model="searchInputStr"
:showAction="false"
bgColor="#ffffff"
:label="searchLabel"
borderColor="gray"
class="approval-search-input"
@search="searchApprovalByPlayerId"
></u-search>
<u-icon
name="calendar"
:size="45"
:color="calendarIconColor"
@click="showCalendar = true"
></u-icon>
</view>
<view v-if="!showApprovalList">
<view class="hz-flex-row approval-empty-img-row">
<image src="/static/images/datanull.png" class="approval-empty-img"></image>
</view>
<view class="hz-flex-row hz-font-bolder hz-font-md approval-empty-text-row">
{{ dataNull }}
</view>
</view>
<view v-else>
<uni-card
v-for="(approvalRecord, index) in approvalList"
:key="index"
@click="toDetail(approvalRecord.playerId)"
>
<view class="notification-content">
<view class="icon-container">
<image src="/static/images/task.png" class="task-icon"></image>
</view>
<view class="notification-info">
<view class="notification-title">
{{ $t("notification.pendingApprovalRequest") }}
</view>
<view class="notification-date">
{{ approvalRecord.createdAt }}
</view>
</view>
<view class="notification-time">
{{ getTimeAgo(approvalRecord.createdAt) }}
</view>
</view>
<view class="notification-detail">
{{ approvalRecord.memberLastName }}, {{ approvalRecord.playerId }} ({{ approvalRecord.tier }}), {{ approvalRecord.propertyName }}, {{ approvalRecord.roomTypeCode }}, {{ approvalRecord.checkinDate }} to {{ approvalRecord.checkoutDate }}, {{ approvalRecord.booker }}
</view>
</uni-card>
</view>
</view>
<u-empty
v-if="!hasAnyViewPermission"
mode="list"
:text="$t('notification.noPermission')"
></u-empty>
<u-calendar
v-if="activeTabKey === 'approval' && canViewApproval"
:show="showCalendar"
@close="showCalendar = false"
:confirmText="confirmText"
:confirmDisabledText="confirmText"
color="#C39A4B"
:allowSameDay="true"
:startText="startText"
:endText="endText"
mode="range"
:showMark="false"
title=""
@confirm="handleFilterDate"
:defaultDate="selectedDate"
>
</u-calendar>
<u-modal
:content="msgStr"
:show="showMsg"
:confirmText="yesText"
:showCancelButton="false"
closeOnClickOverlay
@confirm="showMsg = false"
confirmColor="#C39A4B"
@close="showMsg = false"
></u-modal>
</view>
</template>
<script>
import { getNotificationByUserId, changeNotification, countNotification, readAllNotification } from '../../api/notificationApi.js'
import { getApprovalList } from '../../api/approvalApi.js'
export default {
data() {
return {
title: this.$t('notification.title'),
userData: null,
tabList: [
{ name: this.$t('notification.title'), key: 'notification' },
{ name: this.$t('approvalRouting.title'), key: 'approval' }
],
tabIndex: 0,
activeTabKey: 'notification',
pendingTabKey: '',
limoNoticeList: [],
limoEmptyText: this.$t('notification.noData'),
searchInputStr: '',
approvalList: [],
showApprovalList: true,
dataNull: this.$t('notification.noData'),
searchHolder: this.$t('approval.searchStr'),
calendarIconColor: '#000000',
showCalendar: false,
confirmText: this.$t('common.confirm').toUpperCase(),
startText: this.$t('common.start'),
endText: this.$t('common.end'),
selectedDate: [],
searchLabel: this.$t('common.playerId'),
msgStr: '',
showMsg: false,
yesText: this.$t('common.yes'),
notificationCount: 0,
approvalRoutingCount: 0,
showNotification: false
}
},
computed: {
effectiveUserData() {
return this.getEffectiveUserData(this.userData);
},
canViewNotification() {
return !!(this.effectiveUserData && this.effectiveUserData.canViewNotification === true);
},
canViewApproval() {
return !!(this.effectiveUserData && this.effectiveUserData.canViewApproval === true);
},
visibleTabList() {
return this.tabList.filter((tab) => {
if (tab.key === 'notification') {
return this.canViewNotification;
}
if (tab.key === 'approval') {
return this.canViewApproval;
}
return false;
}).map((tab) => ({
...tab,
badge: this.getTabBadge(tab.key)
}));
},
shouldShowTabs() {
return this.visibleTabList.length > 1;
},
pageTitle() {
if (this.visibleTabList.length === 1) {
return this.visibleTabList[0].name;
}
return this.$t('notification.title');
},
hasAnyViewPermission() {
return this.visibleTabList.length > 0;
},
hasSelectedDate() {
return Array.isArray(this.selectedDate) && this.selectedDate.length > 0;
}
},
onLoad(options) {
if (options && options.tab != null && options.tab !== '') {
const tab = Number(options.tab);
if (tab === 1) {
this.pendingTabKey = 'approval';
this.activeTabKey = 'approval';
}
}
this.applyNotificationCountsFromOptions(options || {});
},
onShow() {
this.userData = uni.getStorageSync('userData');
this.syncVisibleTabState(this.pendingTabKey || this.activeTabKey);
this.syncPageTitle();
this.pendingTabKey = '';
this.fetchNotificationCounts();
this.fetchActiveTabData();
},
methods: {
getEffectiveUserData(userData) {
if (userData && userData.data && typeof userData.data === 'object') {
return userData.data;
}
return userData || {};
},
applyNotificationCountsFromOptions(options) {
this.notificationCount = this.parseCountValue(options.notificationCount);
this.approvalRoutingCount = this.parseCountValue(options.approvalRoutingCount);
},
parseCountValue(value) {
const count = Number(value);
return Number.isFinite(count) && count > 0 ? count : 0;
},
getTabBadge(tabKey) {
const count = tabKey === 'approval'
? this.approvalRoutingCount
: this.notificationCount;
if (count <= 0) {
return {
show: false,
value: 0
};
}
return {
show: true,
value: count,
max: 99,
bgColor: '#fa3534',
color: '#ffffff',
customStyle: {
transform: 'translateY(-2px)',
fontSize: '10px',
lineHeight: '10px',
padding: '1px 4px'
}
};
},
parseNotificationCounts(res) {
const data = res && res.data && typeof res.data === 'object' ? res.data : (res || {});
return {
notificationCount: this.parseCountValue(data.notificationCount ?? data.NotificationCount),
approvalRoutingCount: this.parseCountValue(data.approvalRoutingCount ?? data.ApprovalRoutingCount)
};
},
async fetchNotificationCounts() {
const userId = this.getUserIdFromUserData(this.userData);
if (!userId || !this.hasAnyViewPermission) {
this.notificationCount = 0;
this.approvalRoutingCount = 0;
return;
}
try {
const res = await countNotification({ userId });
const counts = this.parseNotificationCounts(res);
if (this.canViewNotification) {
this.notificationCount = counts.notificationCount;
}
if (this.canViewApproval) {
this.approvalRoutingCount = counts.approvalRoutingCount;
}
} catch (error) {
console.error('[Notification] countNotification failed', error);
}
},
syncVisibleTabState(preferredKey) {
const tabs = this.visibleTabList;
if (!tabs.length) {
this.tabIndex = 0;
this.activeTabKey = '';
this.showCalendar = false;
this.syncPageTitle();
return;
}
const nextIndex = tabs.findIndex((tab) => tab.key === preferredKey);
this.tabIndex = nextIndex >= 0 ? nextIndex : 0;
this.activeTabKey = tabs[this.tabIndex].key;
if (this.activeTabKey !== 'approval') {
this.showCalendar = false;
}
this.syncPageTitle();
},
syncPageTitle() {
this.title = this.pageTitle;
},
fetchActiveTabData() {
if (this.activeTabKey === 'notification' && this.canViewNotification) {
this.fetchNotificationList();
return;
}
if (this.activeTabKey === 'approval' && this.canViewApproval) {
this.getApprovalList();
}
},
getUserIdFromUserData(userData) {
const realUserData = this.getEffectiveUserData(userData);
if (userData == null || userData === '') {
return '';
}
const userId = realUserData.userId != null && realUserData.userId !== ''
? realUserData.userId
: realUserData.UserId;
return userId != null && userId !== '' ? userId : '';
},
unwrapNotificationList(response) {
if (Array.isArray(response)) {
return response;
}
if (response && Array.isArray(response.notifications)) {
return response.notifications;
}
if (response && Array.isArray(response.data)) {
return response.data;
}
if (response && Array.isArray(response.records)) {
return response.records;
}
if (response && Array.isArray(response.Records)) {
return response.Records;
}
return [];
},
formatNotificationTime(value) {
if (value == null || value === '') {
return '';
}
const timestamp = Number(value);
if (!Number.isFinite(timestamp)) {
return String(value);
}
const date = new Date(timestamp);
if (Number.isNaN(date.getTime())) {
return String(value);
}
const pad = (num) => String(num).padStart(2, '0');
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}`;
},
getNoticeCardStyle(notice) {
if (notice && Number(notice.status) === 0) {
return { backgroundColor: '#F7F2E8' };
}
return {};
},
async onReadAllClick() {
try {
const res = await readAllNotification({});
if (res.success == true) {
this.fetchNotificationCounts();
this.fetchNotificationList();
} else {
this.msgStr = res.message;
this.showMsg = true;
}
} catch (error) {
console.error('[Notification] readAllNotification failed', error);
}
},
mapNotificationItem(item, index) {
const content = item.content || '';
return {
id: item.id != null ? item.id : index,
type: 'Limo',
title: item.title || '',
desc: content,
confNo: item.confirmationNo,
time: this.formatNotificationTime(item.createdAt || item.updatedAt),
status: item.status != null ? Number(item.status) : 1,
icon: '/static/images/limo_notification.png'
};
},
updateNotificationLists(list) {
this.limoNoticeList = list
.map((item, index) => this.mapNotificationItem(item, index))
.sort((a, b) => {
const statusA = a.status != null ? Number(a.status) : 1;
const statusB = b.status != null ? Number(b.status) : 1;
return statusA - statusB;
});
},
async fetchNotificationList() {
const userId = this.getUserIdFromUserData(this.userData);
if (!userId) {
console.warn('[Notification] userId not found in userData cache');
return;
}
uni.showLoading({
title: this.$t('common.loading')
});
try {
const res = await getNotificationByUserId();
if(res.success == true){
this.showNotification = true;
const list = this.unwrapNotificationList(res);
this.updateNotificationLists(list);
}else {
this.msgStr = res.message;
this.showNotification = false;
if (res.code === 'NO_PERMISSION') {
this.dataNull = this.$t('notification.noPermission');
}
}
} catch (error) {
console.error('[Notification] getNotificationByUserId failed', error);
} finally {
uni.hideLoading();
}
},
tabChange(e) {
const index = e && e.index != null ? e.index : e;
const selectedTab = this.visibleTabList[index];
if (!selectedTab) {
return;
}
this.tabIndex = index;
this.activeTabKey = selectedTab.key;
this.fetchActiveTabData();
},
extractConfNoFromContent(content) {
if (content == null || content === '') {
return '';
}
const match = String(content).match(/confNo#([A-Za-z0-9]+)/i);
return match ? match[1] : '';
},
async onNoticeCardClick(notice) {
if (!notice || notice.id == null || notice.id === '') {
uni.showToast({
title: 'Notification id not found',
icon: 'none'
});
return;
}
if (!notice.confNo) {
uni.showToast({
title: 'ConfNo not found',
icon: 'none'
});
return;
}
try {
await changeNotification({ id: notice.id });
this.fetchNotificationCounts();
} catch (error) {
console.error('[Notification] changeNotification failed', error);
}
if(notice.confNo!=null && notice.confNo!='' && notice.confNo!=undefined){
this.navTo(
'/limo/bookingDetail/bookingDetail?conf=' +
notice.confNo +
'&from=notification',
0,
false
);
}else{
}
},
handleFilterDate(e) {
this.selectedDate = e;
this.showCalendar = false;
if (Array.isArray(this.selectedDate) && this.selectedDate.length > 0) {
this.calendarIconColor = '#C39A4B';
} else {
this.calendarIconColor = '#000000';
}
if (this.canViewApproval) {
this.getApprovalList();
}
},
clearDateFilter() {
this.selectedDate = [];
this.calendarIconColor = '#000000';
if (this.canViewApproval) {
this.getApprovalList();
}
},
toDetail(playerId) {
this.navTo('/pages/playerDetail/playerDetail?playerId=' + playerId, 0, false);
},
getTimeAgo(createdAt) {
if (!createdAt) return '';
const now = new Date();
const created = new Date(createdAt);
const diffMs = now - created;
const diffMins = Math.floor(diffMs / 60000);
const diffHours = Math.floor(diffMins / 60);
const diffDays = Math.floor(diffHours / 24);
if (diffMins < 1) return 'just now';
if (diffMins < 60) return diffMins + 'm';
if (diffHours < 24) return diffHours + 'h';
return diffDays + 'd';
},
async getApprovalList() {
if (!this.getUserIdFromUserData(this.userData)) {
this.userData = uni.getStorageSync('userData');
}
const userId = this.getUserIdFromUserData(this.userData);
if (!userId) {
return;
}
uni.showLoading({
title: this.$t('common.loading')
});
try {
const params = {
userId,
playerId: this.searchInputStr,
rangeDate: this.selectedDate
};
const res = await getApprovalList(params);
if (res.success == true) {
if (res.data !== null && res.data.length > 0) {
this.approvalList = res.data;
const playerIds = this.approvalList.map((item) => item.playerId);
const uniquePlayerIds = [...new Set(playerIds)];
const playerList = uniquePlayerIds.map((playerId) => ({ playerId }));
uni.setStorageSync('approvalPlayerList', playerList);
this.showApprovalList = true;
} else {
this.approvalList = [];
this.showApprovalList = false;
this.dataNull = this.$t('notification.noData');
}
} else {
this.msgStr = res.message;
this.showApprovalList = false;
if (res.errorCode === 'NO_PERMISSION') {
this.dataNull = this.$t('notification.noPermission');
}
}
} catch (e) {
console.error(e);
uni.$u.toast(e.errMsg, 5000);
const request = e.config != undefined ? JSON.stringify(e.config) : '';
const response = e.data != undefined ? JSON.stringify(e.data) : '';
await this.writeLog(
userId,
'find_player',
'',
'[APP][notification.vue][getApprovalList][Catch Exception]' + e.errMsg,
request,
response,
1,
false
);
} finally {
uni.hideLoading();
}
},
searchApprovalByPlayerId() {
if (this.canViewApproval) {
this.getApprovalList();
}
}
}
}
</script>
<style lang="scss" scoped>
.hz-nav-filter-clear {
margin-top: 2px;
margin-right: 3px;
}
.hz-nav-read-all {
margin-top: 2px;
margin-right: 3px;
}
.hz-nav-read-all-img {
width: 20px;
height: 20px;
}
.approval-search-row {
padding-right: 8px;
}
.approval-search-input {
flex: 1;
padding-right: 8px;
}
.approval-empty-img-row {
justify-content: center;
margin-top: 80px;
}
.approval-empty-img {
width: 160px;
height: 160px;
}
.approval-empty-text-row {
justify-content: center;
margin: 20px;
}
.notice-card {
display: flex;
padding: 24rpx;
margin: 30rpx;
border: 1rpx solid #EBEEF5;
border-radius: 20rpx;
box-shadow: rgba(0, 0, 0, 0.08) 0rpx 0rpx 6rpx 2rpx;
cursor: pointer;
}
.icon {
width: 30px;
height: 30px;
flex-shrink: 0;
}
.info {
margin-left: 20rpx;
}
.notice-type {
font-size: 28rpx;
}
.notice-title {
font-size: 26rpx;
color: #333333;
}
.notice-desc {
font-size: 26rpx;
color: #333333;
font-weight: 400;
}
.notice-time {
font-size: 20rpx;
color: #909399;
font-weight: 400;
}
.notification-content {
display: flex;
align-items: center;
position: relative;
}
.icon-container {
margin-right: 10px;
display: flex;
justify-content: center;
align-items: center;
}
.task-icon {
width: 32px;
height: 32px;
}
.notification-info {
flex: 1;
}
.notification-title {
font-weight: bold;
font-size: 14px;
color: #000000;
}
.notification-date {
font-weight: normal;
font-size: 12px;
color: #999;
}
.notification-time {
font-weight: normal;
font-size: 12px;
color: #C39A4B;
background-color: #FFF8E7;
padding: 2px 8px;
border-radius: 10px;
margin-left: auto;
}
.notification-detail {
font-weight: normal;
font-size: 12px;
color: #000000;
margin-top: 6px;
}
</style>
+25 -4
View File
@@ -232,9 +232,9 @@
"bookingDetail.failed":" Failed", "bookingDetail.failed":" Failed",
"bookingDetail.confirmApprove":"Are you sure to approve booking?", "bookingDetail.confirmApprove":"Are you sure to approve booking?",
"bookingDetail.confirmReject":"Are you sure to reject booking?", "bookingDetail.confirmReject":"Are you sure to reject booking?",
"bookingDetail.changeMiscChargeSuccess":"Change Misc Charge Success", "bookingDetail.changeMisChargeSuccess":"Change MisCharge Success",
"bookingDetail.miscChargeLess":"Amount cannot be less than original.", "bookingDetail.miscChargeLess":"Amount cannot be less than original.",
"bookingDetail.changeMiscChargeFail":"Change Misc Charge Fail", "bookingDetail.changeMisChargeFail":"Change MisCharge Fail",
"multipleDate.title":"Multiple Date", "multipleDate.title":"Multiple Date",
"multipleDate.addDate":"ADD DATE", "multipleDate.addDate":"ADD DATE",
"multipleDate.pleaseAddDate":"Please Add Date", "multipleDate.pleaseAddDate":"Please Add Date",
@@ -295,7 +295,7 @@
"hotel.title":"Room", "hotel.title":"Room",
"reservations.title":"Reservations", "reservations.title":"Reservations",
"approval.title":"Approval", "approval.title":"Approval",
"hotel.roombooking":"Room Booking", "hotel.roombooking":"Limo & Room Booking",
"approval.pendingApproval":"Pending Approval", "approval.pendingApproval":"Pending Approval",
"approval.allRequest":"All Request", "approval.allRequest":"All Request",
"approval.searchStr":"Search", "approval.searchStr":"Search",
@@ -319,5 +319,26 @@
"notification.pendingApproval":"Pending Approval", "notification.pendingApproval":"Pending Approval",
"notification.pendingApprovalRequest": "Pending Approval Request", "notification.pendingApprovalRequest": "Pending Approval Request",
"playerDetail.confirm":"CONFIRM", "playerDetail.confirm":"CONFIRM",
"newBooking.validEta":"The ETA time format is incorrect. Please re-enter it, eg: HH:mm" "newBooking.validEta":"The ETA time format is incorrect. Please re-enter it, eg: HH:mm",
"notification.title":"Notification",
"approvalRouting.title":"Approval Routing",
"limoBookingRecord.bookingType":"Booking Type",
"limoBookingRecord.pickupDatetime":"Pick up Datetime",
"limoBookingRecord.pickup":"Pick up",
"limoBookingRecord.dropoff":"Drop off",
"limoBookingRecord.extraDropoff":"Extra Drop off",
"limoBookingDetail.carTypeAndPlate":"Car Type and Plate",
"limoBookingDetail.overrideBy":"Override By",
"limoBookingDetail.tripPrice":"Trip Price",
"limoBookingDetail.driverSource":"Driver Source",
"limoBookingDetail.remark":"Remark",
"limoBookingDetail.driverContact":"Driver Contact",
"limoBookingDetail.pickupPoint":"Pick up point",
"limoBookingDetail.extraDropoff":"Extra Drop-off",
"limoBookingDetail.dropoffPoint":"Drop off point",
"limoBookingDetail.flightNo":"Flight No",
"limoBookingDetail.greetType":"Greet Type",
"limoBookingCopy.carType":"Car Type",
"limoBookingCopy.carNo":"Car No",
"limoBookingCopy.driverContact":"Driver Contact"
} }
+181 -172
View File
@@ -1,4 +1,4 @@
{ {
"common.appName": "MOBILE CRM APP", "common.appName": "MOBILE CRM APP",
"common.userLogin": "用戶登入", "common.userLogin": "用戶登入",
"common.loginsso": "登入", "common.loginsso": "登入",
@@ -13,7 +13,7 @@
"common.room": "間", "common.room": "間",
"common.rooms": "間", "common.rooms": "間",
"common.bookingType": "預訂類型", "common.bookingType": "預訂類型",
"common.misCharge":"雜項費用", "common.misCharge": "雜項費用",
"common.checkInDate": "入住日期", "common.checkInDate": "入住日期",
"common.checkOutDate": "退房日期", "common.checkOutDate": "退房日期",
"common.roomType": "房型", "common.roomType": "房型",
@@ -29,12 +29,11 @@
"common.confirm": "確認", "common.confirm": "確認",
"common.in": "入住", "common.in": "入住",
"common.out": "離開", "common.out": "離開",
"common.start":"開始", "common.start": "開始",
"common.selected": "已選擇", "common.selected": "已選擇",
"common.userLocked":"這個用戶賬戶已鎖定", "common.userLocked": "這個用戶賬戶已鎖定",
"common.ipLocked":"這個IP已鎖定", "common.end": "終止",
"common.end":"終止", "common.rangeError": "日期範圍不正確",
"common.rangeError":"日期範圍不正確",
"common.removeConfirm": "確認要刪除嗎?", "common.removeConfirm": "確認要刪除嗎?",
"common.warning": "系統提示", "common.warning": "系統提示",
"common.login": "登入", "common.login": "登入",
@@ -45,8 +44,9 @@
"common.alterSharerExist": "該客人已添加", "common.alterSharerExist": "該客人已添加",
"common.alterSharerLimit": "同住人限制", "common.alterSharerLimit": "同住人限制",
"common.errorMsg": "錯誤信息", "common.errorMsg": "錯誤信息",
"common.misCharge":"雜項費用",
"common.goBack": "返回", "common.goBack": "返回",
"common.back":"返回", "common.back": "返回",
"common.language": "語言", "common.language": "語言",
"common.version": "版本", "common.version": "版本",
"common.user": "用戶", "common.user": "用戶",
@@ -58,23 +58,23 @@
"common.search": "查詢", "common.search": "查詢",
"common.status": "狀態", "common.status": "狀態",
"common.inputRemark": "請輸入備註", "common.inputRemark": "請輸入備註",
"common.nextBooking":"下一個預訂", "common.nextBooking": "下一個預訂",
"common.to": "到", "common.to": "到",
"common.close":"關閉", "common.close": "關閉",
"common.save":"保存", "common.save": "保存",
"common.inputUserNameAndPassword": "請輸入用戶名和密碼", "common.inputUserNameAndPassword": "請輸入用戶名和密碼",
"common.loginerror":"用戶名或密碼錯誤", "common.loginerror": "用戶名或密碼錯誤",
"common.mfaerror":"登入失敗", "common.mfaerror": "登入失敗",
"common.loginfail":"登入失敗", "common.loginfail": "登入失敗",
"common.connected":"已連接", "common.connected": "已連接",
"common.disconnected":"連接失敗", "common.disconnected": "連接失敗",
"common.submit":"遞交", "common.submit": "遞交",
"common.noPermission":"您沒有權限查看", "common.noPermission": "您沒有權限查看",
"common.rangeTip":"日期范围无效", "common.rangeTip": "日期范围无效",
"common.jailBreakTitle":"禁止使用", "common.jailBreakTitle": "禁止使用",
"common.jailBreakMessage":"禁止越獄過的設備使用該APP", "common.jailBreakMessage": "禁止越獄過的設備使用該APP",
"common.noRecord":"沒有記錄", "common.noRecord": "沒有記錄",
"common.etaTime":"預計時間", "common.etaTime": "預計時間",
"calendar.mon": "一", "calendar.mon": "一",
"calendar.tue": "二", "calendar.tue": "二",
"calendar.wed": "三", "calendar.wed": "三",
@@ -91,6 +91,8 @@
"findPlayer.noPermission": "您沒有權限查看此客人.", "findPlayer.noPermission": "您沒有權限查看此客人.",
"bookingRecord.title": "預定記錄", "bookingRecord.title": "預定記錄",
"bookingRecord.newsBooking": "預訂房間 / 候補名單", "bookingRecord.newsBooking": "預訂房間 / 候補名單",
"bookingRecord.newLimo": "預訂車輛",
"bookingRecord.calendar": "CALENDAR",
"bookingRecord.extend": "續房", "bookingRecord.extend": "續房",
"bookingRecord.group": "關聯", "bookingRecord.group": "關聯",
"bookingRecord.sharer": "同住人", "bookingRecord.sharer": "同住人",
@@ -116,153 +118,12 @@
"newBooking.alertBookingType": "必須選擇預訂類型", "newBooking.alertBookingType": "必須選擇預訂類型",
"newBooking.extension": "續房", "newBooking.extension": "續房",
"newBooking.preference": "注意:特殊需求均無法保證,須視乎實際状況安排", "newBooking.preference": "注意:特殊需求均無法保證,須視乎實際状況安排",
"newBooking.deposit":"押金", "newBooking.deposit": "押金",
"newBooking.postCharge":"房賬", "newBooking.postCharge": "房賬",
"newBooking.waiveDeposit":"免收押金", "newBooking.waiveDeposit": "免收押金",
"newBooking.collectDeposit":"收取押金", "newBooking.collectDeposit": "收取押金",
"newBooking.yesPost":"掛房賬", "newBooking.yesPost": "掛房賬",
"newBooking.noPost":"鎖房賬", "newBooking.noPost": "鎖房賬",
"bookingConfirm.title": "預覽 & 確認",
"bookingConfirm.submitBooking": "遞交預定",
"bookingConfirm.createdOn": "預訂日期",
"bookingConfirm.createdBy": "預訂者",
"bookingConfirm.viewBooking": "預定詳情",
"bookingConfirm.urgentApproval":"緊急審批請求",
"bookingConfirm.submitApproval":"提交審批人",
"bookingConfirm.alert10": "正在處理客人 ",
"bookingConfirm.alert1": "預訂正在處理中",
"bookingConfirm.alert1_1":" 的候補名單!",
"bookingConfirm.alert2": " 的預定!",
"bookingConfirm.alert3": "您的預定未能通過!",
"bookingConfirm.alert4": "需放在候補名單",
"bookingConfirm.alert5": "部分預定未能通過!",
"bookingConfirm.IMAlert1":"請聯繫MSC預訂房間",
"bookingConfirm.IMAlert2":"選擇的日期需放在候補名單, 請申請批准預訂房間",
"bookingConfirm.putInWaitlist": "放入候補名單",
"bookingConfirm.currentStay": "當前入住",
"bookingConfirm.extending": "延長入住",
"bookingConfirm.extensionEmail": "MSC正在處理續房",
"bookingConfirm.extensionProccessing": "續房正在處理中",
"bookingConfirm.addSharer": "新增",
"bookingConfirm.canNotAddSharer": "不能新增同住人",
"bookingConfirm.confirmBooking": "確認預定",
"bookingConfirm.backToRecords": "返回主頁",
"bookingConfirm.viewProceedBooking": "查看並繼續",
"bookingConfirm.wantToWaitList": "是否放入候補名單?",
"bookingConfirm.secondRoom1":"超過可預訂房間數量",
"bookingConfirm.secondRoom2":"其中一晚已超過可預訂房間數量",
"bookingConfirm.secondRoom3":"已預訂套房或以上房型",
"bookingConfirm.secondRoom4":"只可預訂基本房型",
"bookingConfirm.duplicateWaitlist":"已提交候補名單",
"bookingConfirm.remark":"候補名單備註",
"bookingConfirm.numOfRoom":"請求房間數量",
"bookingConfirm.requestReason":"請求原因",
"bookingConfirm.routeToApprover":"發送至審批員",
"bookingConfirm.approvalRoutingRemark":"審批備注",
"bookingDetail.title": "預定詳情",
"bookingDetail.pendingAlert": "預定正在處理中!",
"bookingDetail.waitlistAlert": "預定已列入候補名單!",
"bookingDetail.confirmationNo": "預訂編號",
"bookingDetail.saveImage": "保存圖片",
"bookingDetail.extension": "續房",
"bookingDetail.cancelSuccessful": "取消成功",
"bookingDetail.cancelFailed": "取消失敗",
"bookingDetail.confirmCancel": "你確定要取消預定嗎?",
"bookingDetail.statusChange": "預定狀態已改變",
"bookingDetail.approve":"審批",
"bookingDetail.reject":"拒絕",
"bookingDetail.changeMiscChargeSuccess":"修改雜項費用成功",
"bookingDetail.changeMiscChargeFail":"修改雜項費用失敗",
"bookingDetail.cancelTimeErr":"當前時間段不能取消預定",
"bookingDetail.successfully":"成功",
"bookingDetail.failed":"失敗",
"bookingDetail.toCancelBooking": "確定要取消預訂嗎?",
"bookingDetail.backToBooking": "返回預訂",
"bookingDetail.backToDetail": "返回詳情",
"bookingDetail.SelectDate": "選擇預訂日期",
"bookingDetail.confirmApprove": "確定要批准此預訂嗎?",
"bookingDetail.confirmReject": "確定要拒絕此預訂嗎?",
"bookingDetail.errorDate":"必須選擇連續的日期!",
"bookingDetail.cancelBooking":"取消預定",
"bookingDetail.ETA":"預計時間",
"bookingDetail.miscChargeLess":"金額不可低於原值.",
"bookingDetail.cannotCancel":"不能取消这条记录",
"multipleDate.title": "選擇多個日期",
"multipleDate.addDate": "新增日期",
"multipleDate.pleaseAddDate": "請新增日期",
"multipleDate.reachMaxBookNum": "已達最大多個日期預定",
"submitBooking.title": "遞交",
"submitBooking.alert1": "TPD的狀態發生了改變,請重新選擇",
"submitBooking.submitedSuccessful": "遞交成功",
"submitBooking.roomCriteriaNotQualified":"不符合訂房條件",
"submitBooking.underWaitlist":"放入候補名單",
"submitBooking.copyRemark":"複製備註",
"putInWaitlist.title": "候補名單",
"newWaitlist.title": "创建候補名單",
"newWaitlist.remark": "備註",
"newWaitlist.submitWaitlist": "遞交候補名單",
"newWaitlist.savesuccessful": "成功遞交候補名單!",
"newWaitlist.viewSubmitedWaitlist": "查看候補名單",
"setting.title": "設定",
"region.title":"地區限制",
"region.showMessage":"此應用程序僅限於澳門使用。",
"region.reson":"禁用原因",
"region.resonMsgNotInMacau":"您的定位不在澳門",
"region.resonMsgOpenVPN":"您打開了VPN",
"region.resonMsgNotInMacauOpenVpn":"您的定位不在澳門且打開了VPN",
"region.resonMsglocationErr":"檢查定位時出錯",
"region.resonMsgVPNErr":"檢查VPN時出錯",
"region.resonMsgFunctionErr":"檢查定位和VPN時出錯",
"region.retry":"重試",
"region.commitTitle":"用戶使用協議及聲明",
"region.commitment1":"本人在此承諾遵守所有適用於使用 Mobile CRM (以下簡稱MCRM) 的公司政策及法定義務。尤其,本人所在地不在澳門特別行政區時,本人同意不使用具有博彩數據的MCRM功能。此外,本人承諾不允許任何其他人(無論是否為澳門美高梅員工)使用安裝在本人移動裝置上的MCRM或存取其中的數據。本人亦承諾不將MCRM上的任何信息分享至其他個人設備。本人知悉此類行為將受到法律的刑事處分,並嚴重違反本人合同規定的責任義務。",
"region.commitment2":"I hereby undertake to adhere to all Company’s policies and all legal obligations that are applicable to the usage of Mobile CRM (hereinafter called MCRM). In particular, I agree not to use MCRM functionality with gaming data whenever my location is outside of the Macau SAR. I further undertake not to allow anybody else, whether an MGM employee or not, to use MCRM on my mobile phone or to access the data therein. I also undertake not to share any of the information on MCRM to any other personal devices. I’m aware that such actions are criminally punished by law and constitute a serious breach of my contractual obligations.",
"region.resonMsgNotInMacauIP":"您的IP不在澳門",
"dashboard.title":"主頁",
"dashboard.dataAsOf":"數據更新於:",
"dashboard.achievement":"成就",
"dashboard.kpi":"指標",
"dashboard.teamtarget":"團隊目標",
"dashboard.individualtarget":"個人目標",
"dashboard.newreactivetheo":"New & Reactive Theo",
"dashboard.directoffer":"Direct Offer Redemption Rate",
"dashboard.hostedplayer":"Hosted Player Active Rate",
"dashboard.eventattendance":"Event Attendance (w/gaming action)",
"todo.title":"待辦",
"todo.arrival":"到達",
"todo.departure":"離開",
"todo.inhouse":"在場",
"todo.others":"其他",
"todo.notConfNo":"該條記錄沒有Opera Confirmation No.",
"todo.etaSuccess":"修改ETA成功",
"todo.etaFail":"修改ETA失敗",
"todo.detailFail":"獲取信息失敗",
"todo.missParams":"缺少參數",
"todo.notRecord":"沒有對應的記錄",
"todo.invokeError":"調用ESB API時出錯",
"todo.noPermission":"沒有權限",
"todo.returnError":"返回數據時出錯",
"todo.etaFormatError":"ETA Time格式錯誤",
"todo.etaRequired": "ETA Time不能為空",
"todo.lookupPlayer":"您沒有權限查看該玩家",
"hotel.title":"酒店",
"approval.title": "審批",
"approval.approve": "批准",
"hotel.roombooking":"酒店預定",
"approval.pendingApproval":"等待審批",
"approval.batchApprove": "批量批准",
"approval.batchReject": "批量拒絕",
"approval.approvalFail": "審批失敗",
"approval.allRequest":"所有請求",
"approval.searchStr":"搜索",
"notification.noData":"沒有返回數據",
"notification.noPermission":"沒有權限",
"notification.pendingApproval":"審批列表",
"notification.pendingApprovalRequest": "待批准請求",
"playerDetail.confirm":"確認",
"newBooking.validEta":"ETA 時間格式有誤,請重新輸入 eg: HH:mm",
"bookingRecord.newLimo": "预定车辆",
"bookingRecord.calendar": "CALENDAR",
"newLimo.createTitle": "建立預訂", "newLimo.createTitle": "建立預訂",
"newLimo.editTitle": "編輯預訂", "newLimo.editTitle": "編輯預訂",
"newLimo.sectionBookingInformation": "預訂資訊", "newLimo.sectionBookingInformation": "預訂資訊",
@@ -294,9 +155,152 @@
"newLimo.notApplicable": "不適用", "newLimo.notApplicable": "不適用",
"newLimo.remarksPlaceholder": "", "newLimo.remarksPlaceholder": "",
"newLimo.removeStop": "刪除", "newLimo.removeStop": "刪除",
"approvalRouting.title": "審批路由", "bookingConfirm.title": "預覽 & 確認",
"bookingConfirm.submitBooking": "遞交預定",
"bookingConfirm.createdOn": "預訂日期",
"bookingConfirm.createdBy": "預訂者",
"bookingConfirm.viewBooking": "預定詳情",
"bookingConfirm.urgentApproval": "緊急審批請求",
"bookingConfirm.submitApproval": "提交審批人",
"bookingConfirm.alert10": "正在處理客人 ",
"bookingConfirm.alert1": "預訂正在處理中",
"bookingConfirm.alert1_1": " 的候補名單!",
"bookingConfirm.alert2": " 的預定!",
"bookingConfirm.alert3": "您的預定未能通過!",
"bookingConfirm.alert4": "需放在候補名單",
"bookingConfirm.alert5": "部分預定未能通過!",
"bookingConfirm.IMAlert1": "請聯繫MSC預訂房間",
"bookingConfirm.IMAlert2": "選擇的日期需放在候補名單, 請申請批准預訂房間",
"bookingConfirm.putInWaitlist": "放入候補名單",
"bookingConfirm.currentStay": "當前入住",
"bookingConfirm.extending": "延長入住",
"bookingConfirm.extensionEmail": "MSC正在處理續房",
"bookingConfirm.extensionProccessing": "續房正在處理中",
"bookingConfirm.addSharer": "新增",
"bookingConfirm.canNotAddSharer": "不能新增同住人",
"bookingConfirm.confirmBooking": "確認預定",
"bookingConfirm.backToRecords": "返回主頁",
"bookingConfirm.viewProceedBooking": "查看並繼續",
"bookingConfirm.wantToWaitList": "是否放入候補名單?",
"bookingConfirm.secondRoom1": "超過可預訂房間數量",
"bookingConfirm.secondRoom2": "其中一晚已超過可預訂房間數量",
"bookingConfirm.secondRoom3": "已預訂套房或以上房型",
"bookingConfirm.secondRoom4": "只可預訂基本房型",
"bookingConfirm.duplicateWaitlist": "已提交候補名單",
"bookingConfirm.remark": "候補名單備註",
"bookingConfirm.numOfRoom": "請求房間數量",
"bookingConfirm.requestReason": "請求原因",
"bookingConfirm.routeToApprover": "發送至審批員",
"bookingConfirm.approvalRoutingRemark": "審批備注",
"bookingConfirm.numOfRoom":"請求房間數量",
"bookingConfirm.requestReason":"請求原因",
"bookingConfirm.routeToApprover":"發送至審批員",
"bookingConfirm.approvalRoutingRemark":"審批備注",
"bookingDetail.title": "預定詳情",
"bookingDetail.pendingAlert": "預定正在處理中!",
"bookingDetail.waitlistAlert": "預定已列入候補名單!",
"bookingDetail.confirmationNo": "預訂編號",
"approvalRouting.title":"審批路由",
"bookingDetail.saveImage": "保存圖片",
"bookingDetail.extension": "續房",
"bookingDetail.cancelSuccessful": "取消成功",
"bookingDetail.cancelFailed": "取消失敗",
"bookingDetail.confirmCancel": "你確定要取消預定嗎?",
"bookingDetail.statusChange": "預定狀態已改變",
"bookingDetail.approve": "審批",
"bookingDetail.reject": "拒絕",
"bookingDetail.cancelTimeErr": "當前時間段不能取消預定",
"bookingDetail.successfully": "成功",
"bookingDetail.failed": "失敗",
"bookingDetail.toCancelBooking": "確定要取消預訂嗎?",
"bookingDetail.backToBooking": "返回預訂",
"bookingDetail.backToDetail": "返回詳情",
"bookingDetail.SelectDate": "選擇預訂日期",
"bookingDetail.confirmApprove": "確定要批准此預訂嗎?",
"bookingDetail.confirmReject": "確定要拒絕此預訂嗎?",
"bookingDetail.errorDate": "必須選擇連續的日期!",
"bookingDetail.cancelBooking": "取消預定",
"bookingDetail.ETA": "預計時間",
"bookingDetail.changeMisChargeSuccess": "修改雜項費用成功",
"bookingDetail.changeMisChargeFail": "修改雜項費用失敗",
"bookingDetail.miscChargeLess": "金額不可低於原值.",
"bookingDetail.cannotCancel": "不能取消这条记录",
"multipleDate.title": "選擇多個日期",
"multipleDate.addDate": "新增日期",
"multipleDate.pleaseAddDate": "請新增日期",
"multipleDate.reachMaxBookNum": "已達最大多個日期預定",
"submitBooking.title": "遞交",
"submitBooking.alert1": "TPD的狀態發生了改變,請重新選擇",
"submitBooking.submitedSuccessful": "遞交成功",
"submitBooking.roomCriteriaNotQualified": "不符合訂房條件",
"submitBooking.underWaitlist": "放入候補名單",
"submitBooking.copyRemark": "複製備註",
"putInWaitlist.title": "候補名單",
"newWaitlist.title": "创建候補名單",
"newWaitlist.remark": "備註",
"newWaitlist.submitWaitlist": "遞交候補名單",
"newWaitlist.savesuccessful": "成功遞交候補名單!",
"newWaitlist.viewSubmitedWaitlist": "查看候補名單",
"setting.title": "設定",
"region.title": "地區限制",
"region.showMessage": "此應用程序僅限於澳門使用。",
"region.reson": "禁用原因",
"region.resonMsgNotInMacau": "您的定位不在澳門",
"region.resonMsgOpenVPN": "您打開了VPN",
"region.resonMsgNotInMacauOpenVpn": "您的定位不在澳門且打開了VPN",
"region.resonMsglocationErr": "檢查定位時出錯",
"region.resonMsgVPNErr": "檢查VPN時出錯",
"region.resonMsgFunctionErr": "檢查定位和VPN時出錯",
"region.retry": "重試",
"region.commitTitle": "用戶使用協議及聲明",
"region.commitment1": "本人在此承諾遵守所有適用於使用 Mobile CRM (以下簡稱MCRM) 的公司政策及法定義務。尤其,本人所在地不在澳門特別行政區時,本人同意不使用具有博彩數據的MCRM功能。此外,本人承諾不允許任何其他人(無論是否為澳門美高梅員工)使用安裝在本人移動裝置上的MCRM或存取其中的數據。本人亦承諾不將MCRM上的任何信息分享至其他個人設備。本人知悉此類行為將受到法律的刑事處分,並嚴重違反本人合同規定的責任義務。",
"region.commitment2": "I hereby undertake to adhere to all Company’s policies and all legal obligations that are applicable to the usage of Mobile CRM (hereinafter called MCRM). In particular, I agree not to use MCRM functionality with gaming data whenever my location is outside of the Macau SAR. I further undertake not to allow anybody else, whether an MGM employee or not, to use MCRM on my mobile phone or to access the data therein. I also undertake not to share any of the information on MCRM to any other personal devices. I’m aware that such actions are criminally punished by law and constitute a serious breach of my contractual obligations.",
"region.resonMsgNotInMacauIP": "您的IP不在澳門",
"dashboard.title": "主頁",
"dashboard.dataAsOf": "數據更新於:",
"dashboard.achievement": "成就",
"dashboard.kpi": "指標",
"dashboard.teamtarget": "團隊目標",
"dashboard.individualtarget": "個人目標",
"dashboard.newreactivetheo": "New & Reactive Theo",
"dashboard.directoffer": "Direct Offer Redemption Rate",
"dashboard.hostedplayer": "Hosted Player Active Rate",
"dashboard.eventattendance": "Event Attendance (w/gaming action)",
"todo.title": "待辦",
"todo.arrival": "到達",
"todo.departure": "離開",
"todo.inhouse": "在場",
"todo.others": "其他",
"todo.notConfNo": "該條記錄沒有Opera Confirmation No.",
"todo.etaSuccess": "修改ETA成功",
"todo.etaFail": "修改ETA失敗",
"todo.detailFail": "獲取信息失敗",
"todo.missParams": "缺少參數",
"todo.notRecord": "沒有對應的記錄",
"todo.invokeError": "調用ESB API時出錯",
"todo.noPermission": "沒有權限",
"todo.returnError": "返回數據時出錯",
"todo.etaFormatError": "ETA Time格式錯誤",
"todo.etaRequired": "ETA Time不能為空",
"todo.lookupPlayer": "您沒有權限查看該玩家",
"hotel.title": "酒店",
"reservations.title": "預訂", "reservations.title": "預訂",
"approval.title": "審批",
"hotel.roombooking": "汽車、酒店預定",
"notification.title": "通知", "notification.title": "通知",
"approval.approve": "批准",
"approval.pendingApproval": "等待審批",
"approval.batchApprove": "批量批准",
"approval.batchReject": "批量拒絕",
"approval.approvalFail": "審批失敗",
"approval.allRequest": "所有請求",
"approval.searchStr": "搜索",
"notification.noData": "沒有返回數據",
"notification.noPermission": "沒有權限",
"notification.pendingApproval": "審批列表",
"notification.pendingApprovalRequest": "待批准請求",
"playerDetail.confirm": "確認",
"newBooking.validEta": "ETA 時間格式有誤,請重新輸入 eg: HH:mm",
"limoBookingRecord.bookingType": "預定類型", "limoBookingRecord.bookingType": "預定類型",
"limoBookingRecord.pickupDatetime": "接載日期", "limoBookingRecord.pickupDatetime": "接載日期",
"limoBookingRecord.pickup": "接載地點", "limoBookingRecord.pickup": "接載地點",
@@ -318,5 +322,10 @@
"newLimo.tenOut": "10 Out", "newLimo.tenOut": "10 Out",
"newLimo.tenOutContact": "10 Out 聯絡人", "newLimo.tenOutContact": "10 Out 聯絡人",
"newLimo.contactGuestDirectly": "直接聯絡客人", "newLimo.contactGuestDirectly": "直接聯絡客人",
"newLimo.guestContact": "客人聯絡人" "newLimo.guestContact": "客人聯絡人",
"newLimo.additionalOptions":"更多選項",
"limoBookingDetail.driverSource":"司機源",
"limoBookingDetail.remark":"備注",
"limoBookingDetail.flightNo":"航班號",
"limoBookingDetail.greetType":"問候類型"
} }
+11
View File
@@ -25,6 +25,17 @@ Vue.use(watermark, {
Vue.config.productionTip = false Vue.config.productionTip = false
Vue.prototype.$store = store Vue.prototype.$store = store
Vue.mixin({
onShow() {
uni.$emit('refreshNotificationCount');
this.$nextTick(() => {
const header = this.$refs && this.$refs.headerRef;
if (header && typeof header.fetchNotificationCount === 'function') {
header.fetchNotificationCount();
}
});
}
});
// 注册全局方法 // 注册全局方法
Vue.prototype.isLogin = function(){ Vue.prototype.isLogin = function(){
getApp().isLogin(); getApp().isLogin();
+4 -2
View File
@@ -18,7 +18,8 @@
"modules" : { "modules" : {
"Camera" : {}, "Camera" : {},
"UTS-Proxy" : {}, "UTS-Proxy" : {},
"Geolocation" : {} "Geolocation" : {},
"Push" : {}
}, },
"distribute" : { "distribute" : {
"distribute" : { "distribute" : {
@@ -75,7 +76,8 @@
"system" : { "system" : {
"__platform__" : [ "ios" ] "__platform__" : [ "ios" ]
} }
} },
"push" : {}
}, },
"icons" : { "icons" : {
"ios" : { "ios" : {
@@ -0,0 +1,344 @@
/**
* 坐标转换问题分析和解决方案 - 精确修正版本
* 解决向南偏移250米、向东偏移160米的问题
*/
class FixedCoordinateConverter {
constructor() {
this.a = 6378245.0;
this.ee = 0.00669342162296594323;
this.PI = Math.PI;
// 精确偏移校正参数(基于实际测试数据)
this.OFFSET_CORRECTIONS = {
// 经度偏移校正:向东偏移160米 ≈ 0.00144度(在北纬40度附近)
lngCorrection: -0.00003,
// 纬度偏移校正:向南偏移250米 ≈ 0.00225度
latCorrection: 0.0003
};
}
/**
* 标准算法实现
*/
wgs84ToGcj02Standard(wgsLat, wgsLng) {
if (!this.isInChina(wgsLat, wgsLng)) {
return { lat: wgsLat, lng: wgsLng };
}
let dLat = this.transformLat(wgsLng - 105.0, wgsLat - 35.0);
let dLng = this.transformLng(wgsLng - 105.0, wgsLat - 35.0);
const radLat = wgsLat / 180.0 * this.PI;
let magic = Math.sin(radLat);
magic = 1 - this.ee * magic * magic;
const sqrtMagic = Math.sqrt(magic);
dLat = (dLat * 180.0) / ((this.a * (1 - this.ee)) / (magic * sqrtMagic) * this.PI);
dLng = (dLng * 180.0) / (this.a / sqrtMagic * Math.cos(radLat) * this.PI);
return {
lat: wgsLat + dLat,
lng: wgsLng + dLng
};
}
/**
* 精确修正版本:解决向南偏移250米、向东偏移160米的问题
*/
wgs84ToGcj02Fixed(wgsLat, wgsLng) {
if (!this.isInChina(wgsLat, wgsLng)) {
return { lat: wgsLat, lng: wgsLng };
}
// 使用更精确的偏移计算
const x = wgsLng - 105.0;
const y = wgsLat - 35.0;
let dLat = this.transformLatFixed(x, y);
let dLng = this.transformLngFixed(x, y);
const radLat = wgsLat / 180.0 * this.PI;
const sinLat = Math.sin(radLat);
const cosLat = Math.cos(radLat);
// 修正魔法数计算
const magic = 1.0 - this.ee * sinLat * sinLat;
const sqrtMagic = Math.sqrt(magic);
// 使用更精确的转换公式
const M = this.a * (1.0 - this.ee) / (magic * sqrtMagic); // 子午圈曲率半径
const N = this.a / sqrtMagic; // 卯酉圈曲率半径
dLat = (dLat * 180.0) / (M * this.PI);
dLng = (dLng * 180.0) / (N * cosLat * this.PI);
// 应用精确偏移校正
const correctedLat = wgsLat + dLat + this.OFFSET_CORRECTIONS.latCorrection;
const correctedLng = wgsLng + dLng + this.OFFSET_CORRECTIONS.lngCorrection;
return {
lat: correctedLat,
lng: correctedLng
};
}
/**
* 高精度修正版本:使用自适应校正
*/
wgs84ToGcj02HighPrecision(wgsLat, wgsLng) {
if (!this.isInChina(wgsLat, wgsLng)) {
return { lat: wgsLat, lng: wgsLng };
}
// 获取区域特定的校正参数
const regionCorrection = this.getRegionSpecificCorrection(wgsLat, wgsLng);
const x = wgsLng - 105.0;
const y = wgsLat - 35.0;
let dLat = this.transformLatFixed(x, y);
let dLng = this.transformLngFixed(x, y);
const radLat = wgsLat / 180.0 * this.PI;
const sinLat = Math.sin(radLat);
const cosLat = Math.cos(radLat);
const magic = 1.0 - this.ee * sinLat * sinLat;
const sqrtMagic = Math.sqrt(magic);
const M = this.a * (1.0 - this.ee) / (magic * sqrtMagic);
const N = this.a / sqrtMagic;
dLat = (dLat * 180.0) / (M * this.PI);
dLng = (dLng * 180.0) / (N * cosLat * this.PI);
// 应用区域特定校正
const correctedLat = wgsLat + dLat + regionCorrection.latCorrection;
const correctedLng = wgsLng + dLng + regionCorrection.lngCorrection;
return {
lat: correctedLat,
lng: correctedLng
};
}
/**
* 获取区域特定的校正参数
*/
getRegionSpecificCorrection(lat, lng) {
// 基于纬度的动态校正
const latFactor = Math.cos(lat * this.PI / 180);
// 默认校正值(基于实际测试)
let latCorrection = this.OFFSET_CORRECTIONS.latCorrection;
let lngCorrection = this.OFFSET_CORRECTIONS.lngCorrection * latFactor;
// 针对不同地区的微调
if (lat >= 39.0 && lat <= 41.0 && lng >= 115.0 && lng <= 118.0) {
// 北京地区特殊校正
latCorrection = 0.00220;
lngCorrection = -0.00140 * latFactor;
} else if (lat >= 30.5 && lat <= 32.0 && lng >= 120.5 && lng <= 122.5) {
// 上海地区特殊校正
latCorrection = 0.00230;
lngCorrection = -0.00150 * latFactor;
} else if (lat >= 22.0 && lat <= 24.0 && lng >= 112.5 && lng <= 115.0) {
// 广州深圳地区特殊校正
latCorrection = 0.00240;
lngCorrection = -0.00135 * latFactor;
}
return { latCorrection, lngCorrection };
}
/**
* 使用查表法减少计算误差
*/
wgs84ToGcj02LookupTable(wgsLat, wgsLng) {
if (!this.isInChina(wgsLat, wgsLng)) {
return { lat: wgsLat, lng: wgsLng };
}
// 对于特定区域使用预计算的偏移值
const regionOffset = this.getRegionOffset(wgsLat, wgsLng);
if (regionOffset) {
return {
lat: wgsLat + regionOffset.latOffset,
lng: wgsLng + regionOffset.lngOffset
};
}
// 否则使用高精度算法
return this.wgs84ToGcj02HighPrecision(wgsLat, wgsLng);
}
/**
* 获取区域特定偏移量(针对常见城市的精确偏移)
*/
getRegionOffset(lat, lng) {
const regions = [
// 北京地区 - 修正后的偏移值
{
bounds: { minLat: 39.4, maxLat: 41.0, minLng: 115.4, maxLng: 117.5 },
latOffset: 0.00580, // 修正向南偏移
lngOffset: 0.00506 // 修正向东偏移
},
// 上海地区
{
bounds: { minLat: 30.7, maxLat: 31.9, minLng: 120.9, maxLng: 122.2 },
latOffset: 0.00590,
lngOffset: 0.00496
},
// 广州地区
{
bounds: { minLat: 22.5, maxLat: 23.9, minLng: 112.9, maxLng: 114.8 },
latOffset: 0.00600,
lngOffset: 0.00486
},
// 深圳地区
{
bounds: { minLat: 22.4, maxLat: 22.8, minLng: 113.7, maxLng: 114.6 },
latOffset: 0.00595,
lngOffset: 0.00491
}
];
for (const region of regions) {
if (lat >= region.bounds.minLat && lat <= region.bounds.maxLat &&
lng >= region.bounds.minLng && lng <= region.bounds.maxLng) {
return region;
}
}
return null;
}
/**
* 修正的纬度转换函数
*/
transformLatFixed(x, y) {
let ret = -100.0 + 2.0 * x + 3.0 * y + 0.2 * y * y +
0.1 * x * y + 0.2 * Math.sqrt(Math.abs(x));
ret += (20.0 * Math.sin(6.0 * x * this.PI) +
20.0 * Math.sin(2.0 * x * this.PI)) * 2.0 / 3.0;
ret += (20.0 * Math.sin(y * this.PI) +
40.0 * Math.sin(y / 3.0 * this.PI)) * 2.0 / 3.0;
ret += (160.0 * Math.sin(y / 12.0 * this.PI) +
320.0 * Math.sin(y * this.PI / 30.0)) * 2.0 / 3.0;
return ret;
}
/**
* 修正的经度转换函数
*/
transformLngFixed(x, y) {
let ret = 300.0 + x + 2.0 * y + 0.1 * x * x +
0.1 * x * y + 0.1 * Math.sqrt(Math.abs(x));
ret += (20.0 * Math.sin(6.0 * x * this.PI) +
20.0 * Math.sin(2.0 * x * this.PI)) * 2.0 / 3.0;
ret += (20.0 * Math.sin(x * this.PI) +
40.0 * Math.sin(x / 3.0 * this.PI)) * 2.0 / 3.0;
ret += (150.0 * Math.sin(x / 12.0 * this.PI) +
300.0 * Math.sin(x / 30.0 * this.PI)) * 2.0 / 3.0;
return ret;
}
transformLat(x, y) {
return this.transformLatFixed(x, y);
}
transformLng(x, y) {
return this.transformLngFixed(x, y);
}
isInChina(lat, lng) {
return lng >= 72.004 && lng <= 137.8347 &&
lat >= 0.8293 && lat <= 55.8271;
}
/**
* 计算偏移距离(米)
*/
calculateOffsetDistance(originalLat, originalLng, convertedLat, convertedLng) {
const latDiffMeters = (convertedLat - originalLat) * 111000;
const lngDiffMeters = (convertedLng - originalLng) * 111000 * Math.cos(originalLat * this.PI / 180);
return {
latOffset: latDiffMeters,
lngOffset: lngDiffMeters,
totalDistance: Math.sqrt(latDiffMeters * latDiffMeters + lngDiffMeters * lngDiffMeters)
};
}
/**
* 比较不同算法的转换结果
*/
compareAlgorithms(wgsLat, wgsLng) {
const standard = this.wgs84ToGcj02Standard(wgsLat, wgsLng);
const fixed = this.wgs84ToGcj02Fixed(wgsLat, wgsLng);
const highPrecision = this.wgs84ToGcj02HighPrecision(wgsLat, wgsLng);
const lookup = this.wgs84ToGcj02LookupTable(wgsLat, wgsLng);
return {
original: { lat: wgsLat, lng: wgsLng },
standard: standard,
fixed: fixed,
highPrecision: highPrecision,
lookup: lookup,
offsets: {
standard: this.calculateOffsetDistance(wgsLat, wgsLng, standard.lat, standard.lng),
fixed: this.calculateOffsetDistance(wgsLat, wgsLng, fixed.lat, fixed.lng),
highPrecision: this.calculateOffsetDistance(wgsLat, wgsLng, highPrecision.lat, highPrecision.lng),
lookup: this.calculateOffsetDistance(wgsLat, wgsLng, lookup.lat, lookup.lng)
}
};
}
/**
* 验证修正效果
*/
validateCorrection(wgsLat, wgsLng, expectedGcjLat, expectedGcjLng) {
const result = this.wgs84ToGcj02Fixed(wgsLat, wgsLng);
const error = this.calculateOffsetDistance(expectedGcjLat, expectedGcjLng, result.lat, result.lng);
return {
converted: result,
expected: { lat: expectedGcjLat, lng: expectedGcjLng },
error: error,
isAccurate: error.totalDistance < 10 // 误差小于10米认为准确
};
}
}
// 测试和验证
const fixedConverter = new FixedCoordinateConverter();
// 测试北京天安门坐标
const testLat = 39.9093;
const testLng = 116.3974;
console.log('=== 修正后的坐标转换算法比较 ===');
const comparison = fixedConverter.compareAlgorithms(testLat, testLng);
console.log('原始坐标:', comparison.original);
console.log('标准算法:', comparison.standard);
console.log('修正算法:', comparison.fixed);
console.log('高精度算法:', comparison.highPrecision);
console.log('查表算法:', comparison.lookup);
console.log('偏移分析:', comparison.offsets);
// 导出修正版本
if (typeof module !== 'undefined' && module.exports) {
module.exports = FixedCoordinateConverter;
}
if (typeof window !== 'undefined') {
window.FixedCoordinateConverter = FixedCoordinateConverter;
}
+66 -25
View File
@@ -22,16 +22,6 @@
"disableSwipeBack": true "disableSwipeBack": true
} }
}, },
{
"path": "pages/roomCalendar/roomCalendar",
"style": {
"navigationBarTitleText": "",
"enablePullDownRefresh": false,
"backgroundColor": "#ffffff",
"navigationStyle": "custom",
"disableSwipeBack": true
}
},
{ {
"path": "pages/dashboard/dashboard", "path": "pages/dashboard/dashboard",
"style": { "style": {
@@ -80,10 +70,11 @@
} }
}, },
{ {
"path": "booking/playerRoomCalendar/playerRoomCalendar", "path" : "booking/playerRoomCalendar/playerRoomCalendar",
"style": { "style" :
"navigationBarTitleText": "", {
"enablePullDownRefresh": false, "navigationBarTitleText" : "",
"enablePullDownRefresh" : false,
"backgroundColor": "#ffffff", "backgroundColor": "#ffffff",
"navigationStyle": "custom", "navigationStyle": "custom",
"disableSwipeBack": true "disableSwipeBack": true
@@ -231,6 +222,37 @@
"disableSwipeBack": true "disableSwipeBack": true
} }
}, },
{
"path" : "limo/newLimo/newLimo",
"style" :
{
"navigationBarTitleText": "",
"enablePullDownRefresh" : false,
"backgroundColor": "#ffffff",
"navigationStyle": "custom",
"disableSwipeBack": true
}
},
{
"path": "limo/bookingDetail/bookingDetail",
"style" :
{
"navigationBarTitleText" : "",
"enablePullDownRefresh" : false,
"navigationStyle": "custom",
"disableSwipeBack": true
}
},
{
"path": "limo/notification/notification",
"style": {
"navigationBarTitleText": "",
"enablePullDownRefresh" : false,
"backgroundColor": "#ffffff",
"navigationStyle": "custom",
"disableSwipeBack": true
}
},
{ {
"path" : "pages/roomCount/roomCount", "path" : "pages/roomCount/roomCount",
"style" : "style" :
@@ -242,6 +264,17 @@
"disableSwipeBack": true "disableSwipeBack": true
} }
}, },
{
"path" : "pages/roomCalendar/roomCalendar",
"style" :
{
"navigationBarTitleText": "",
"enablePullDownRefresh" : false,
"backgroundColor": "#ffffff",
"navigationStyle": "custom",
"disableSwipeBack": true
}
},
{ {
"path" : "pages/playerDetail/playerDetail", "path" : "pages/playerDetail/playerDetail",
"style" : "style" :
@@ -280,31 +313,39 @@
"list": [ "list": [
{ {
"pagePath": "pages/dashboard/dashboard", "pagePath": "pages/dashboard/dashboard",
"iconPath": "static/images/ico_home.png", "iconPath": "static/icon/tabbar/ico_home.png",
"selectedIconPath": "static/images/ico_home_active.png", "selectedIconPath": "static/icon/tabbar/ico_home_active.png",
"text": "%dashboard.title%", "text": "%dashboard.title%",
"visible":false "visible":false
}, },
{ {
"pagePath": "salessupport/todoList/todoList", "pagePath": "salessupport/todoList/todoList",
"iconPath": "static/images/ico_todo.png", "iconPath": "static/icon/tabbar/ico_todo.png",
"selectedIconPath": "static/images/ico_todo_active.png", "selectedIconPath": "static/icon/tabbar/ico_todo_active.png",
"text": "%todo.title%", "text": "%todo.title%",
"visible":false "visible":false
}, },
{ {
"pagePath": "pages/findPlayer/findPlayer", "pagePath": "pages/findPlayer/findPlayer",
"iconPath": "static/images/ico_hotel.png", "iconPath": "static/icon/tabbar/calendar-check.png",
"selectedIconPath": "static/images/ico_hotel_active.png", "selectedIconPath": "static/icon/tabbar/calendar-check-fill.png",
"text": "%hotel.title%", "text": "%reservations.title%",
"visible":false "visible":false
}, },
// approvalRouting
// {
// "pagePath": "pages/approval/approval",
// "iconPath": "/static/images/task.png",
// "selectedIconPath": "/static/images/task_active.png",
// "text": "%approval.title%",
// "visible":true
// },
{ {
"pagePath": "pages/roomCalendar/roomCalendar", "pagePath": "pages/roomCalendar/roomCalendar",
"iconPath": "static/images/calendar-line.png", "iconPath": "static/images/calendar-line.png",
"selectedIconPath": "static/images/calendar-fill.png", "selectedIconPath": "static/images/calendar-fill.png",
"text": "%roomCalendar.title%", "text": "%roomCalendar.title%",
"visible": false "visible":false
}, },
// approvalRouting // approvalRouting
// { // {
@@ -316,12 +357,12 @@
// }, // },
{ {
"pagePath": "pages/setting/setting", "pagePath": "pages/setting/setting",
"iconPath": "static/images/ico_set.png", "iconPath": "static/icon/tabbar/ico_set.png",
"selectedIconPath": "static/images/ico_set_active.png", "selectedIconPath": "static/icon/tabbar/ico_set_active.png",
"text": "%setting.title%", "text": "%setting.title%",
"visible":true "visible":true
} }
] ]
}, },
"uniIdRouter": {} "uniIdRouter": {}
} }
+155 -20
View File
@@ -1,7 +1,35 @@
<template> <template>
<view> <view>
<view> <view>
<mgmHeader ref="headerRef" /> <!-- <mgmHeader ref="headerRef" :showNotification="true" /> -->
<u-navbar
:safeAreaInsetTop="true"
:placeholder="true"
:title="title"
:fixed="true"
:autoBack="false"
leftIconColor="#C39A4B"
bgColor="#C39A4B"
titleStyle="font-weight:bold;color:#ffffff;"
>
<view
@tap="goNotification"
class="notification-bell-wrap"
slot="left"
>
<u-icon name="bell" color="#FFFFFF" size="24"></u-icon>
<view v-if="notificationCount > 0" class="notification-badge">
<text class="notification-badge-text">{{ notificationBadgeText }}</text>
</view>
</view>
<view @tap="logout"
style="margin-top:2px;margin-right:3px;"
slot="right"
>
<image src="../../static/images/ico_exit.png" style="width:24px;height:24px;"></image>
</view>
</u-navbar>
<view v-if="showDashboardView"> <view v-if="showDashboardView">
<view class="hz-font-bolder hz-font-l hz-font-color-black hz-text-right" <view class="hz-font-bolder hz-font-l hz-font-color-black hz-text-right"
style="margin-top:36rpx;margin-right:36rpx;"> style="margin-top:36rpx;margin-right:36rpx;">
@@ -109,6 +137,9 @@
import { import {
logout logout
} from '../../api/commonApi'; } from '../../api/commonApi';
import {
countNotification
} from '../../api/notificationApi';
import { import {
formatDate formatDate
} from '../../common/stringUtils'; } from '../../common/stringUtils';
@@ -124,6 +155,7 @@
showMsg: false, showMsg: false,
okText: this.$t("common.ok"), okText: this.$t("common.ok"),
dataNull:this.$t("common.dataNotFound"), dataNull:this.$t("common.dataNotFound"),
title:'',
userData: null, userData: null,
dashboardData: [], dashboardData: [],
dataAsOf:"", dataAsOf:"",
@@ -132,10 +164,13 @@
NFROValue: '0', NFROValue: '0',
IMValue: '0', IMValue: '0',
MRIValue: '0', MRIValue: '0',
showAchievement: false, showKPI:false,
showKPI: false, showAchievement:false,
showDashboardView: false, showDashboardView: false,
userSegment: null userSegment: null,
notificationCount: 0,
notificationCountTimer: null,
isDashboardActive: false
} }
}, },
onLoad(opt) { onLoad(opt) {
@@ -143,19 +178,84 @@
this.initUI(); this.initUI();
}, },
onShow() { onShow() {
this.isDashboardActive = true;
this.playerData = uni.getStorageSync("playerData"); this.playerData = uni.getStorageSync("playerData");
this.getTodoDashboardByuserId(); this.userData = uni.getStorageSync("userData");
this.$nextTick(() => { if (this.userData !== null && this.userData !== undefined){
const header = this.$refs.headerRef;
if(header) {
header.updateUserData();
}
});
if (this.userData !== null && this.userData !== undefined)
this.showTabbarByPermission(this.userData); this.showTabbarByPermission(this.userData);
this.title = this.userData.userName+','+this.userData.userId;
}
this.getTodoDashboardByuserId();
this.fetchNotificationCount();
this.stopNotificationCountTimer();
this.startNotificationCountTimer();
},
onHide() {
this.isDashboardActive = false;
this.stopNotificationCountTimer();
},
onUnload() {
this.isDashboardActive = false;
this.stopNotificationCountTimer();
}, },
methods: { methods: {
goNotification() {
uni.navigateTo({
url: '/limo/notification/notification'
});
},
getUserIdFromUserData(userData) {
if (userData == null || userData === '') {
return '';
}
const userId = userData.userId != null && userData.userId !== ''
? userData.userId
: userData.UserId;
return userId != null && userId !== '' ? userId : '';
},
parseNotificationCount(res) {
if (res == null) {
return 0;
}
const count = res.count ?? res.Count ?? res.data?.count ?? res.data?.Count;
return Number(count) || 0;
},
async fetchNotificationCount() {
this.userData = uni.getStorageSync('userData');
const userId = this.getUserIdFromUserData(this.userData);
if (!userId) {
this.notificationCount = 0;
return;
}
try {
const res = await countNotification({ userId });
this.notificationCount = this.parseNotificationCount(res);
console.log('[Dashboard] notification count res=', res, 'notificationCount=', this.notificationCount);
} catch (error) {
console.error('[Dashboard] countNotification failed', error);
this.notificationCount = 0;
}
},
startNotificationCountTimer() {
if (!this.notificationCountTimer) {
this.notificationCountTimer = setInterval(() => {
if (this.isDashboardActive) {
this.fetchNotificationCount();
}
}, 30000);
}
},
stopNotificationCountTimer() {
if (this.notificationCountTimer) {
clearInterval(this.notificationCountTimer);
this.notificationCountTimer = null;
}
},
formatNumber(num) {
return num?.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",") || "0";
},
normalizeDashboardRecord(record) { normalizeDashboardRecord(record) {
record = record || {}; record = record || {};
return { return {
@@ -169,9 +269,6 @@
userSegment: record.userSegment ?? record.UserSegment, userSegment: record.userSegment ?? record.UserSegment,
}; };
}, },
formatNumber(num) {
return num?.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",") || "0";
},
async getTodoDashboardByuserId() { async getTodoDashboardByuserId() {
uni.showLoading({ uni.showLoading({
title: this.$t("common.loading") title: this.$t("common.loading")
@@ -181,10 +278,6 @@
let params = {}; let params = {};
requestBody = JSON.stringify(params); requestBody = JSON.stringify(params);
const res = await getTodoDashboard(params); const res = await getTodoDashboard(params);
this.dashboardData = [];
this.dataAsOf = "";
this.userSegment = null;
this.MFValue = this.IMValue = this.NFROValue = this.MRIValue = "0";
if (res.data != null && res.success == true) { if (res.data != null && res.success == true) {
const records = Array.isArray(res.data.records) const records = Array.isArray(res.data.records)
? res.data.records ? res.data.records
@@ -194,6 +287,7 @@
if (records.length > 0) { if (records.length > 0) {
this.dashboardData = records.map((record) => this.normalizeDashboardRecord(record)); this.dashboardData = records.map((record) => this.normalizeDashboardRecord(record));
this.showDashboardView = true; this.showDashboardView = true;
// 设置用户类型 // 设置用户类型
this.userSegment = this.dashboardData[0].userSegment; this.userSegment = this.dashboardData[0].userSegment;
this.hasTargetValue = this.dashboardData[0].targetValue !== null && this.dashboardData[0].targetValue !== undefined; this.hasTargetValue = this.dashboardData[0].targetValue !== null && this.dashboardData[0].targetValue !== undefined;
@@ -258,7 +352,7 @@
}else if(res.errorCode == 'INVOKE_TODODASHBOARD_API_ERROR'){ }else if(res.errorCode == 'INVOKE_TODODASHBOARD_API_ERROR'){
this.dataNull = this.$t("todo.invokeError"); this.dataNull = this.$t("todo.invokeError");
}else { }else {
if(String(res.errorCode || res.code || '').toUpperCase().indexOf('NO RECORD') != -1){ if(res.errorCode.toUpperCase().indexOf('NO RECORD') != -1){
this.dataNull = this.$t("common.noRecord"); this.dataNull = this.$t("common.noRecord");
}else{ }else{
this.dataNull = this.$t("todo.returnError"); this.dataNull = this.$t("todo.returnError");
@@ -282,6 +376,12 @@
} }
}, },
computed: { computed: {
notificationBadgeText() {
if (this.notificationCount > 99) {
return '99+';
}
return String(this.notificationCount);
},
getShowAchievement() { getShowAchievement() {
return function(record) { return function(record) {
if (record.type == "Achievement" && record.itemName !=="MF Turnover" && record.itemName !=="IM Turnover" if (record.type == "Achievement" && record.itemName !=="MF Turnover" && record.itemName !=="IM Turnover"
@@ -375,4 +475,39 @@
.im-text-gold { .im-text-gold {
color: #cfa34f; color: #cfa34f;
} }
.notification-bell-wrap {
position: relative;
display: inline-flex;
align-items: center;
justify-content: center;
margin-top: 2px;
margin-left: 3px;
width: 24px;
height: 24px;
overflow: visible;
}
.notification-badge {
position: absolute;
top: -4px;
right: -5px;
min-width: 16px;
height: 16px;
padding: 0 4px;
border-radius: 8px;
background-color: #fa3534;
display: flex;
align-items: center;
justify-content: center;
box-sizing: border-box;
z-index: 10;
}
.notification-badge-text {
color: #ffffff;
font-size: 10px;
line-height: 1;
font-weight: bold;
}
</style> </style>
+224 -84
View File
@@ -1,5 +1,6 @@
<template> <template>
<view> <view>
<u-navbar <u-navbar
:safeAreaInsetTop="true" :safeAreaInsetTop="true"
:placeholder="true" :placeholder="true"
@@ -10,31 +11,33 @@
bgColor="#C39A4B" bgColor="#C39A4B"
titleStyle="font-weight:bold;color:#ffffff;" titleStyle="font-weight:bold;color:#ffffff;"
> >
<view <view
@click="logout" v-if="showNotificationBell"
style="margin-top:2px;margin-right:3px;" @click="goNotification"
slot="right" class="notification-bell-wrap"
> slot="left"
<image src="/static/images/ico_exit.png" style="width:24px;height:24px;"></image> >
</view> <u-icon name="bell" color="#FFFFFF" size="24"></u-icon>
<view <view v-if="notificationCount > 0" class="notification-badge">
v-show="canViewApproval" <text class="notification-badge-text">{{ notificationBadgeText }}</text>
@click="toNotification"
style="margin-top:2px;margin-right:3px; position: relative"
slot="left"
>
<image src="/static/images/160task01.png" style="width:24px;height:24px;"/>
<u-badge :value="countAp" type="error" :offset="[-8, -8]" :show="countAp > 0" :absolute="true"></u-badge>
</view> </view>
</view>
<view @click="logout"
style="margin-top:2px;margin-right:3px;"
slot="right"
>
<image src="../../static/images/ico_exit.png" style="width:24px;height:24px;"></image>
</view>
</u-navbar> </u-navbar>
<view class="hz-text-center" style="margin-top:120rpx;"> <view class="hz-text-center" style="margin-top:120rpx;">
<image src="../../static/images/room_book.png" style="width:128rpx;height:128rpx;"></image> <image src="/static/images/limo.png" class="mr-[40]" style="width: 58px; height: 58px;"></image>
<image src="/static/images/room_book.png" class="find-player-ico"></image>
</view> </view>
<view class="hz-font-bolder hz-font-xl hz-text-center hz-font-color-primary"> <view class="hz-font-bolder hz-font-xl hz-text-center hz-font-color-primary">
{{$t('hotel.roombooking')}} {{$t('hotel.roombooking')}}
</view> </view>
<view class="hz-font-bolder hz-font-xl hz-text-center hz-font-color-dark" style="margin-top:160rpx;"> <view class="hz-font-bolder hz-font-xl hz-text-center hz-font-color-dark mt-[160]">
{{$t('findPlayer.title')}} {{$t('findPlayer.title')}}
</view> </view>
<view style="margin-top:30rpx;margin-right:60rpx;margin-left:60rpx;"> <view style="margin-top:30rpx;margin-right:60rpx;margin-left:60rpx;">
@@ -58,7 +61,8 @@
<script> <script>
import { getApprovalCount } from '../../api/approvalApi.js'; import { getApprovalCount } from '../../api/approvalApi.js';
import {playerLookup} from '../../api/mgmApi.js' import {playerLookup} from '../../api/mgmApi.js'
import { countNotification } from '../../api/notificationApi.js'
import mgmHeader from '@/components/header/header.vue' import mgmHeader from '@/components/header/header.vue'
@@ -67,25 +71,30 @@ import {playerLookup} from '../../api/mgmApi.js'
mgmHeader // 注册组件 mgmHeader // 注册组件
}, },
data() { data() {
return {
// 数据变量 return {
playerIdStr: this.$t("common.playerId"), // 数据变量
searchPlayerId: '', playerIdStr: this.$t("common.playerId"),
userData:null, searchPlayerId: '',
deviceData:null, userData:null,
siteData:null, deviceData:null,
countAp: '', siteData:null,
// 定时器变量 // 界面变量
approvalCountTimer: null, showMsg:false,
// 页面激活态(避免残留定时器回调在非当前页继续请求) title: '',
isFindPlayerActive: false, msgStr:'',
// 界面变量 yesText:this.$t("common.ok"),
showMsg:false, notificationCount: 0,
title: '', approvalRoutingCount: 0,
msgStr:'', limoNotificationCount: 0,
yesText:this.$t("common.ok"), countAp: '',
canViewApproval:false // 定时器变量
} approvalCountTimer: null,
notificationCountTimer: null,
// 页面激活态(避免残留定时器回调在非当前页继续请求)
isFindPlayerActive: false,
canViewApproval:false
}
}, },
onLoad() { onLoad() {
// 初始化界面 // 初始化界面
@@ -95,56 +104,133 @@ import {playerLookup} from '../../api/mgmApi.js'
}, },
onShow() { onShow() {
this.isFindPlayerActive = true; this.isFindPlayerActive = true;
this.getApporvalCount(); // this.getApporvalCount();
this.userData = uni.getStorageSync("userData"); this.userData = uni.getStorageSync("userData");
this.searchPlayerId = ''; this.searchPlayerId = '';
this.playerIdStr=this.$t("common.playerId"); this.playerIdStr=this.$t("common.playerId");
if(this.userData!==null && this.userData!==undefined){ if(this.userData!==null && this.userData!==undefined){
this.title = this.userData.userName+','+this.userData.userId; const realUserData = this.getEffectiveUserData(this.userData);
this.showTabbarByPermission(this.userData); this.showTabbarByPermission(realUserData);
this.title = realUserData.userName+','+realUserData.userId;
} }
this.isLogin(); this.isLogin();
this.$nextTick(() => { this.fetchNotificationCount();
const header = this.$refs.headerRef;
if(header) {
header.updateUserData();
}
});
// 切回页面时,先停后启,避免存在残留定时器 // 切回页面时,先停后启,避免存在残留定时器
this.stopApprovalCountTimer(); this.stopNotificationCountTimer();
this.startApprovalCountTimer(); this.startNotificationCountTimer();
if(this.userData.canViewApproval!==undefined && this.userData.canViewApproval==true) this.canViewApproval = true; this.canViewApproval = this.effectiveUserData.canViewApproval === true;
}, },
onHide() { onHide() {
this.isFindPlayerActive = false; this.isFindPlayerActive = false;
// 切到其他页面时立即停止定时器,避免后台持续调用 // 切到其他页面时立即停止定时器,避免后台持续调用
this.stopApprovalCountTimer(); // this.stopApprovalCountTimer();
this.stopNotificationCountTimer();
}, },
onUnload() { onUnload() {
this.isFindPlayerActive = false; this.isFindPlayerActive = false;
// 页面销毁时兜底清理 // 页面销毁时兜底清理
this.stopApprovalCountTimer(); // this.stopApprovalCountTimer();
this.stopNotificationCountTimer();
}, },
onReady(){ onReady(){
}, },
methods: { methods: {
startApprovalCountTimer() { getEffectiveUserData(userData) {
if (!this.approvalCountTimer) { if (userData && userData.data && typeof userData.data === 'object') {
this.approvalCountTimer = setInterval(() => { return userData.data;
// 双保险:非当前页不请求 }
return userData || {};
},
goNotification() {
const url = this.buildNotificationUrl();
uni.navigateTo({
url
});
},
buildNotificationUrl(extraParams = []) {
const params = extraParams.slice();
if (this.effectiveUserData.canViewNotification === true) {
params.push('notificationCount=' + encodeURIComponent(this.limoNotificationCount));
}
if (this.effectiveUserData.canViewApproval === true) {
params.push('approvalRoutingCount=' + encodeURIComponent(this.approvalRoutingCount));
}
return params.length
? '/limo/notification/notification?' + params.join('&')
: '/limo/notification/notification';
},
getUserIdFromUserData(userData) {
const realUserData = this.getEffectiveUserData(userData);
if (userData == null || userData === '') {
return '';
}
const userId = realUserData.userId != null && realUserData.userId !== ''
? realUserData.userId
: realUserData.UserId;
return userId != null && userId !== '' ? userId : '';
},
parseNotificationCounts(res) {
const data = res && res.data && typeof res.data === 'object' ? res.data : (res || {});
return {
count: Number(data.count ?? data.Count) || 0,
notificationCount: Number(data.notificationCount ?? data.NotificationCount) || 0,
approvalRoutingCount: Number(data.approvalRoutingCount ?? data.ApprovalRoutingCount) || 0,
};
},
async fetchNotificationCount() {
this.userData = uni.getStorageSync('userData');
const userId = this.getUserIdFromUserData(this.userData);
if (!userId) {
this.notificationCount = 0;
this.limoNotificationCount = 0;
this.approvalRoutingCount = 0;
return;
}
try {
const res = await countNotification({ userId });
const counts = this.parseNotificationCounts(res);
this.notificationCount = counts.count;
this.limoNotificationCount = counts.notificationCount;
this.approvalRoutingCount = counts.approvalRoutingCount;
} catch (error) {
console.error('[FindPlayer] countNotification failed', error);
this.notificationCount = 0;
this.limoNotificationCount = 0;
this.approvalRoutingCount = 0;
}
},
startNotificationCountTimer() {
if (!this.notificationCountTimer) {
this.notificationCountTimer = setInterval(() => {
if (this.isFindPlayerActive) { if (this.isFindPlayerActive) {
this.getApporvalCount(); this.fetchNotificationCount();
} }
}, 30000); // 30秒 }, 30000);
} }
}, },
stopApprovalCountTimer() { stopNotificationCountTimer() {
if (this.approvalCountTimer) { if (this.notificationCountTimer) {
clearInterval(this.approvalCountTimer); clearInterval(this.notificationCountTimer);
this.approvalCountTimer = null; this.notificationCountTimer = null;
} }
}, },
// startApprovalCountTimer() {
// if (!this.approvalCountTimer) {
// this.approvalCountTimer = setInterval(() => {
// // 双保险:非当前页不请求
// if (this.isFindPlayerActive) {
// // this.getApporvalCount();
// }
// }, 30000); // 30秒
// }
// },
// stopApprovalCountTimer() {
// if (this.approvalCountTimer) {
// clearInterval(this.approvalCountTimer);
// this.approvalCountTimer = null;
// }
// },
// 数据方法 // 数据方法
async getProfileByPlayerId(){ async getProfileByPlayerId(){
if(!this.searchPlayerId){ if(!this.searchPlayerId){
@@ -155,7 +241,7 @@ import {playerLookup} from '../../api/mgmApi.js'
title: this.$t("common.loading") title: this.$t("common.loading")
}); });
try{ try{
let params = {playerId:this.searchPlayerId,userId:this.userData.userId}; let params = {playerId:this.searchPlayerId,userId:this.effectiveUserData.userId};
const res = await playerLookup(params); const res = await playerLookup(params);
if(res.success == true){ if(res.success == true){
if(res.data !== null){ if(res.data !== null){
@@ -177,38 +263,39 @@ import {playerLookup} from '../../api/mgmApi.js'
uni.$u.toast(e.errMsg,5000); uni.$u.toast(e.errMsg,5000);
var request = e.config!=undefined?JSON.stringify(e.config):""; var request = e.config!=undefined?JSON.stringify(e.config):"";
var response = e.data!=undefined?JSON.stringify(e.data):""; var response = e.data!=undefined?JSON.stringify(e.data):"";
await this.writeLog(this.userData.userId,"find_player","","[APP][findPlayer.vue][getProfileByPlayerId][Catch Exception]"+e.errMsg,request,response,1,false); await this.writeLog(this.effectiveUserData.userId,"find_player","","[APP][findPlayer.vue][getProfileByPlayerId][Catch Exception]"+e.errMsg,request,response,1,false);
}finally{
uni.hideLoading();
}
},
async getApporvalCount(){
if (!this.isFindPlayerActive) return;
try{
let params = {};
const res = await getApprovalCount(params);
if(res.success == true){
if(res.data !== null){
this.countAp = res.data;
}
}
}catch(e){
console.error(e);
uni.$u.toast(e.errMsg,5000);
var request = e.config!=undefined?JSON.stringify(e.config):"";
var response = e.data!=undefined?JSON.stringify(e.data):"";
await this.writeLog(this.userData.userId,"find_player","","[APP][findPlayer.vue][getProfileByPlayerId][Catch Exception]"+e.errMsg,request,response,1,false);
}finally{ }finally{
uni.hideLoading(); uni.hideLoading();
} }
}, },
// async getApporvalCount(){
// if (!this.isFindPlayerActive) return;
// try{
// let params = {};
// const res = await getApprovalCount(params);
// if(res.success == true){
// if(res.data !== null){
// this.countAp = res.data;
// }
// }
// }catch(e){
// console.error(e);
// uni.$u.toast(e.errMsg,5000);
// var request = e.config!=undefined?JSON.stringify(e.config):"";
// var response = e.data!=undefined?JSON.stringify(e.data):"";
// await this.writeLog(this.effectiveUserData.userId,"find_player","","[APP][findPlayer.vue][getProfileByPlayerId][Catch Exception]"+e.errMsg,request,response,1,false);
// }finally{
// uni.hideLoading();
// }
// },
// 界面方法 // 界面方法
confirmMsg(){ confirmMsg(){
this.msgStr = ''; this.msgStr = '';
this.showMsg = false; this.showMsg = false;
}, },
toNotification(){ toNotification(){
this.navTo("/pages/notification/notification",0,false) this.navTo(this.buildNotificationUrl(['tab=1']),0,false)
}, },
clearSearchInput(){ clearSearchInput(){
this.searchPlayerId = ''; this.searchPlayerId = '';
@@ -222,6 +309,24 @@ import {playerLookup} from '../../api/mgmApi.js'
wv.show(); wv.show();
}, },
}, },
computed: {
effectiveUserData() {
return this.getEffectiveUserData(this.userData);
},
showNotificationBell() {
return !!(
this.effectiveUserData &&
(this.effectiveUserData.canViewApproval === true ||
this.effectiveUserData.canViewNotification === true)
);
},
notificationBadgeText() {
if (this.notificationCount > 99) {
return '99+';
}
return String(this.notificationCount);
}
}
} }
</script> </script>
@@ -232,4 +337,39 @@ import {playerLookup} from '../../api/mgmApi.js'
border-style: solid; border-style: solid;
height:30px; height:30px;
} }
.notification-bell-wrap {
position: relative;
display: inline-flex;
align-items: center;
justify-content: center;
margin-top: 2px;
margin-left: 3px;
width: 24px;
height: 24px;
overflow: visible;
}
.notification-badge {
position: absolute;
top: -4px;
right: -5px;
min-width: 16px;
height: 16px;
padding: 0 4px;
border-radius: 8px;
background-color: #fa3534;
display: flex;
align-items: center;
justify-content: center;
box-sizing: border-box;
z-index: 10;
}
.notification-badge-text {
color: #ffffff;
font-size: 10px;
line-height: 1;
font-weight: bold;
}
</style> </style>
+19 -44
View File
@@ -44,13 +44,13 @@
<view style="width: 100%;"> <view style="width: 100%;">
<u-popup :show="showCommitment" :round="10" mode="center" customStyle="margin:0rpx 40rpx;"> <u-popup :show="showCommitment" :round="10" mode="center" customStyle="margin:0rpx 40rpx;">
<view class="commitment-dialog" style="padding: 20px 22px 32px 22px;"> <view style="padding: 20px 22px 32px 22px;">
<h4 class="commitment-title" style="font-size: 18px; display: flex; justify-content: center; padding: 7px;">{{commitTitle}}</h4> <h4 style="font-size: 18px; display: flex; justify-content: center; padding: 7px;">{{commitTitle}}</h4>
<view class="commitment-body" style="height:490px;font-size:13px;color:#333;"> <view style="height:490px;font-size:13px;color:#333;">
<view style="margin-bottom: 24rpx;line-height: 1.7;">{{commitmentText1}}</view> <view style="margin-bottom: 24rpx;line-height: 1.7;">{{commitmentText1}}</view>
<view >{{commitmentText2}}</view> <view >{{commitmentText2}}</view>
</view> </view>
<view class="commitment-footer"> <view >
<view @click="handleConfirm()" class="hz-btn hz-font-color-white hz-font-lg hz-font-bold hz-text-center" >{{confirmText}}</view> <view @click="handleConfirm()" class="hz-btn hz-font-color-white hz-font-lg hz-font-bold hz-text-center" >{{confirmText}}</view>
</view> </view>
</view> </view>
@@ -141,6 +141,18 @@
userName : userData.userName userName : userData.userName
}; };
const res = await userConfirm(params); const res = await userConfirm(params);
// #ifdef APP-PLUS
if (userData != null && userData.userId != null) {
const app = getApp();
if (app) {
app.syncPushDeviceTokenFromNative();
await app.syncPendingPushNotificationsFromNative();
if (app.tryConsumePendingPushNavigation()) {
return;
}
}
}
// #endif
this.checkInMacau(); this.checkInMacau();
}, },
checkInMacau(){ checkInMacau(){
@@ -331,6 +343,7 @@
async goLogin(){ async goLogin(){
await this.$onLaunched; await this.$onLaunched;
await this.getSetting(); await this.getSetting();
const userData = uni.getStorageSync("userData"); const userData = uni.getStorageSync("userData");
let loginTag = false; let loginTag = false;
uni.setStorageSync("todoSwitch","true"); uni.setStorageSync("todoSwitch","true");
@@ -342,9 +355,10 @@
if(loginTag===true){ if(loginTag===true){
this.redirectByPermission(userData); this.redirectByPermission(userData);
} }
// 模拟开启,没有code,默认进来的情况 // 模拟开启,没有code,默认进来的情况
if(this.siteData.mockSSO==true && this.code=='' && loginTag===false){ if(this.siteData.mockSSO==true && this.code=='' && loginTag===false){
// 模拟SSO // 模拟SSO
uni.redirectTo({ uni.redirectTo({
url:'/pages/ssoLogin/ssoLogin' url:'/pages/ssoLogin/ssoLogin'
@@ -391,45 +405,6 @@
</script> </script>
<style> <style>
/* #ifdef H5 */
.commitment-dialog {
display: flex;
flex-direction: column;
box-sizing: border-box;
max-height: calc(100vh - 48px);
max-height: calc(100dvh - 48px);
overflow: hidden;
}
.commitment-title,
.commitment-footer {
flex-shrink: 0;
}
.commitment-body {
min-height: 0;
overflow-y: scroll;
overflow-x: hidden;
overflow-wrap: anywhere;
padding-right: 10px;
scrollbar-gutter: stable;
}
.commitment-body::-webkit-scrollbar {
/* uView globally sets scrollbar width to 0 !important. */
width: 2px !important;
height: 2px !important;
display: block;
}
.commitment-body::-webkit-scrollbar-track {
background: #eee;
border-radius: 4px;
}
.commitment-body::-webkit-scrollbar-thumb {
background: #a67c30;
border-radius: 4px;
}
.commitment-footer {
padding-top: 16px;
}
/* #endif */
.commit-content{ .commit-content{
line-height: 1.5; /* 增加行间距提升可读性 */ line-height: 1.5; /* 增加行间距提升可读性 */
color: #333; /* 深灰提升阅读舒适度 */ color: #333; /* 深灰提升阅读舒适度 */
+5 -227
View File
@@ -1,235 +1,13 @@
<template> <template>
<view> <view></view>
<u-navbar
:safeAreaInsetTop="true"
:placeholder="true"
:title="title"
:fixed="true"
:autoBack="true"
leftIconColor="#000000"
bgColor="#ffffff"
titleStyle="font-weight:bold;color:#000000;"
>
<view style="margin-top:2px;margin-right:3px;" slot="right">
<u-icon
v-if="hasSelectedDate"
@click="clearDateFilter"
size="28px" name="../../static/images/filter-clear.svg"
></u-icon>
</view>
</u-navbar>
<!-- 搜索栏 -->
<view class="hz-spacing hz-mr-t-20 hz-flex-row">
<u-search :placeholder="searchHolder" v-model="searchInputStr" :showAction="false" bgColor="#ffffff" :label="searchLabel"
borderColor="gray" style="padding-right: 8px" @search="searchApprovalByPlayerId()"></u-search>
<u-icon name="calendar" :size="45" :color="calendarIconColor" @click="showCalendar = true"></u-icon>
</view>
<view v-if="!showApprovalList">
<view class="hz-flex-row" style="justify-content: center;margin-top:80px;">
<image src="/static/images/datanull.png" style="width:160px;height:160px;"></image>
</view>
<view class="hz-flex-row hz-font-bolder hz-font-md" style="justify-content: center;margin:20px;">
{{dataNull}}
</view>
</view>
<view v-else>
<uni-card v-for="(approvalRecord,index) in approvalList" :key="index" @click="toDetail(approvalRecord.playerId)">
<view class="notification-content">
<view class="icon-container">
<image src="/static/images/task.png" class="task-icon"></image>
</view>
<view class="notification-info">
<view class="notification-title">
{{ $t("notification.pendingApprovalRequest") }}
</view>
<view class="notification-date">
{{approvalRecord.createdAt}}
</view>
</view>
<view class="notification-time">
{{getTimeAgo(approvalRecord.createdAt)}}
</view>
</view>
<view class="notification-detail">
{{approvalRecord.memberLastName}}, {{approvalRecord.playerId}} ({{approvalRecord.tier}}), {{approvalRecord.propertyName}}, {{approvalRecord.roomTypeCode}}, {{approvalRecord.checkinDate}} to {{approvalRecord.checkoutDate}}, {{approvalRecord.booker}}
</view>
</uni-card>
</view>
<u-calendar :show="showCalendar" @close="showCalendar = false" :confirmText="confirmText" :confirmDisabledText="confirmText"
color="#C39A4B" :allowSameDay="true" :startText="startText" :endText="endText" mode="range" :showMark="false"
title="" @confirm="handleFilterDate" :defaultDate="selectedDate">
</u-calendar>
</view>
</template> </template>
<script> <script>
import {getApprovalList} from '../../api/approvalApi.js'
export default { export default {
data() { onLoad() {
return { uni.redirectTo({
title:this.$t("notification.pendingApproval"), url: '/limo/notification/notification?tab=1'
searchInputStr:'', });
userData:'',
approvalList:[],
showApprovalList:true,
dataNull:this.$t("notification.noData"),
searchHolder:this.$t("approval.searchStr"),
calendarIconColor: '#000000',
showCalendar: false,
confirmText: this.$t("common.confirm").toUpperCase(),
startText: this.$t("common.start"),
endText: this.$t("common.end"),
selectedDate: [],
searchLabel: this.$t("common.playerId")
}
},
onShow() {
this.userData = uni.getStorageSync("userData");
this.getApprovalList();
},
methods: {
handleFilterDate(e) {
this.selectedDate = e;
this.showCalendar = false;
if (Array.isArray(this.selectedDate) && this.selectedDate.length > 0) {
this.calendarIconColor = '#C39A4B';
} else {
this.calendarIconColor = '#000000';
}
this.getApprovalList();
},
clearDateFilter() {
this.selectedDate = [];
this.calendarIconColor = '#000000';
this.getApprovalList();
},
toDetail(playerId){
this.navTo("/pages/playerDetail/playerDetail?playerId="+playerId,0,false)
},
getTimeAgo(createdAt) {
if (!createdAt) return '';
const now = new Date();
const created = new Date(createdAt);
const diffMs = now - created;
const diffMins = Math.floor(diffMs / 60000);
const diffHours = Math.floor(diffMins / 60);
const diffDays = Math.floor(diffHours / 24);
if (diffMins < 1) return 'just now';
if (diffMins < 60) return diffMins + 'm';
if (diffHours < 24) return diffHours + 'h';
return diffDays + 'd';
},
async getApprovalList(){
uni.showLoading({
title: this.$t("common.loading")
});
try{
let params = {
userId: this.userData.userId,
playerId: this.searchInputStr,
rangeDate: this.selectedDate
};
const res = await getApprovalList(params);
if(res.success == true){
if(res.data !== null && res.data.length > 0){
this.approvalList = res.data;
// 提取所有 playerId 到新的 playerList 并去重
const playerIds = this.approvalList.map(item => item.playerId);
const uniquePlayerIds = [...new Set(playerIds)];
this.playerList = uniquePlayerIds.map(playerId => ({ playerId }));
uni.setStorageSync("approvalPlayerList",this.playerList);
this.showApprovalList = true;
} else {
this.showApprovalList = false;
this.dataNull = this.$t("notification.noData");
}
}else{
this.msgStr = res.message;
this.showMsg = true;
this.showApprovalList = false;
if(res.errorCode==="NO_PERMISSION") this.dataNull = this.$t("notification.noPermission");
}
}catch(e){
console.error(e);
uni.$u.toast(e.errMsg,5000);
var request = e.config!=undefined?JSON.stringify(e.config):"";
var response = e.data!=undefined?JSON.stringify(e.data):"";
await this.writeLog(this.userData.userId,"find_player","","[APP][findPlayer.vue][getProfileByPlayerId][Catch Exception]"+e.errMsg,request,response,1,false);
}finally{
uni.hideLoading();
}
},
// 搜索玩家審批請求
searchApprovalByPlayerId() {
this.getApprovalList();
}
},
computed: {
hasSelectedDate() {
return Array.isArray(this.selectedDate) && this.selectedDate.length > 0;
}
} }
} }
</script> </script>
<style lang="scss" scoped>
.notification-content {
display: flex;
align-items: center;
position: relative;
}
.icon-container {
margin-right: 10px;
display: flex;
justify-content: center;
align-items: center;
}
.task-icon {
width: 32px;
height: 32px;
}
.notification-info {
flex: 1;
}
.notification-title {
font-weight: bold;
font-size: 14px;
color: #000000;
}
.notification-date {
font-weight: normal;
font-size: 12px;
color: #999;
}
.notification-time {
font-weight: normal;
font-size: 12px;
color: #C39A4B;
background-color: #FFF8E7;
padding: 2px 8px;
border-radius: 10px;
margin-left: auto;
}
.notification-detail {
font-weight: normal;
font-size: 12px;
color: #000000;
margin-top: 6px;
}
</style>
+25 -3
View File
@@ -370,7 +370,7 @@
const currentRoom = this.roomList[this.currentRoomIndex]; const currentRoom = this.roomList[this.currentRoomIndex];
if (currentRoom && currentRoom.id) { if (currentRoom && currentRoom.id) {
const approvalData = this.approvalGamingDataList.find( const approvalData = this.approvalGamingDataList.find(
item => item.bookingId == currentRoom.id item => (item.bookingId != null ? item.bookingId : item.BookingId) == currentRoom.id
); );
return approvalData || {}; return approvalData || {};
} }
@@ -401,6 +401,26 @@
return value; return value;
}, },
// 将形如:2025-03-18 +12 (W +2) | 2025-03-16 + 16 ... 按日期升序排序 // 将形如:2025-03-18 +12 (W +2) | 2025-03-16 + 16 ... 按日期升序排序
normalizeApprovalGamingData(item) {
if (!item) return {};
return {
bookingId: item.bookingId != null ? item.bookingId : item.BookingId,
playerId: item.playerId != null ? item.playerId : item.PlayerId,
currentTripTheoWinAmount: item.currentTripTheoWinAmount != null ? item.currentTripTheoWinAmount : item.CurrentTripTheoWinAmount,
last2DayTheoWinAmount: item.last2DayTheoWinAmount != null ? item.last2DayTheoWinAmount : item.Last2DayTheoWinAmount,
r3TheoWin: item.r3TheoWin != null ? item.r3TheoWin : item.R3TheoWin,
r3TPD: item.r3TPD != null ? item.r3TPD : item.R3TPD,
r3ActualWin: item.r3ActualWin != null ? item.r3ActualWin : item.R3ActualWin,
r6TheoWin: item.r6TheoWin != null ? item.r6TheoWin : item.R6TheoWin,
r6TPD: item.r6TPD != null ? item.r6TPD : item.R6TPD,
r6ActualWin: item.r6ActualWin != null ? item.r6ActualWin : item.R6ActualWin,
r12TheoWin: item.r12TheoWin != null ? item.r12TheoWin : item.R12TheoWin,
r12TPD: item.r12TPD != null ? item.r12TPD : item.R12TPD,
r12ActualWin: item.r12ActualWin != null ? item.r12ActualWin : item.R12ActualWin,
overlapBooking: item.overlapBooking != null ? item.overlapBooking : item.OverlapBooking,
roomCount: item.roomCount != null ? item.roomCount : item.RoomCount
};
},
sortRoomCountByDate(roomCountValue) { sortRoomCountByDate(roomCountValue) {
if (roomCountValue === null || roomCountValue === undefined || roomCountValue === '') return '-'; if (roomCountValue === null || roomCountValue === undefined || roomCountValue === '') return '-';
const input = String(roomCountValue).trim(); const input = String(roomCountValue).trim();
@@ -524,7 +544,9 @@
if (res.success == true) { if (res.success == true) {
if (res.data !== null) { if (res.data !== null) {
this.bookingListData = res.data.bookingsViewList; this.bookingListData = res.data.bookingsViewList;
this.approvalGamingDataList = res.data.approvalGamingData || []; this.approvalGamingDataList = Array.isArray(res.data.approvalGamingData)
? res.data.approvalGamingData.map(item => this.normalizeApprovalGamingData(item))
: [];
const routingList = res.data.cfgApprovalRoutingList; const routingList = res.data.cfgApprovalRoutingList;
if (Array.isArray(routingList) && routingList.length > 0) { if (Array.isArray(routingList) && routingList.length > 0) {
this.approvalRoutingList = routingList; this.approvalRoutingList = routingList;
@@ -983,4 +1005,4 @@
.dataMargin { .dataMargin {
margin-bottom: 3px; margin-bottom: 3px;
} }
</style> </style>
+4 -23
View File
@@ -165,13 +165,11 @@
propertyTabItemStyle() { propertyTabItemStyle() {
return { return {
height: '78rpx', height: '78rpx',
padding: '0 24rpx', padding: '0',
flexShrink: 0,
}; };
}, },
propertyTabActiveStyle() { propertyTabActiveStyle() {
return { return {
whiteSpace: 'nowrap',
color: '#C39A4B', color: '#C39A4B',
fontFamily: 'Inter-Bold', fontFamily: 'Inter-Bold',
fontSize: '30rpx', fontSize: '30rpx',
@@ -180,7 +178,6 @@
}, },
propertyTabInactiveStyle() { propertyTabInactiveStyle() {
return { return {
whiteSpace: 'nowrap',
color: '#111111', color: '#111111',
fontFamily: 'Inter-Bold', fontFamily: 'Inter-Bold',
fontSize: '30rpx', fontSize: '30rpx',
@@ -197,12 +194,6 @@
this.title = this.$t("roomCalendar.title"); this.title = this.$t("roomCalendar.title");
this.userData = uni.getStorageSync("userData"); this.userData = uni.getStorageSync("userData");
this.showTabbarByPermission(this.userData); this.showTabbarByPermission(this.userData);
if (!this.userData || this.userData.canViewCalendar !== true) {
this.calendarNoPermission = true;
this.calendarReady = false;
return;
}
this.calendarNoPermission = false;
this.loadPropertiesRoomTypes(); this.loadPropertiesRoomTypes();
}, },
onUnload() { onUnload() {
@@ -211,10 +202,6 @@
}, },
methods: { methods: {
async loadPropertiesRoomTypes() { async loadPropertiesRoomTypes() {
if (!this.userData || this.userData.canViewCalendar !== true) {
this.showCalendarNoPermission();
return;
}
try { try {
this.calendarReady = false; this.calendarReady = false;
uni.showLoading({ uni.showLoading({
@@ -222,19 +209,14 @@
}); });
await this.loadCalendarSetting(); await this.loadCalendarSetting();
const res = await getPropertiesRoomTypes({}); const res = await getPropertiesRoomTypes({});
if (this.isNoPermissionResponse(res)) { this.propertyList = res.propertyList;
this.showCalendarNoPermission();
return;
}
this.propertyList = Array.isArray(res && res.propertyList) ? res.propertyList : [];
if (!this.propertyList.length) throw new Error('No calendar properties configured');
this.selectedPropertyCode = this.propertyList[0].code; this.selectedPropertyCode = this.propertyList[0].code;
this.selectDefaultOptions(); this.selectDefaultOptions();
this.buildCalendar(); this.buildCalendar();
await this.loadAvailabilityData(); await this.loadAvailabilityData();
this.calendarRenderKey += 1; this.calendarRenderKey += 1;
this.$nextTick(() => { this.$nextTick(() => {
this.calendarReady = !this.calendarNoPermission; this.calendarReady = true;
}); });
} catch (e) { } catch (e) {
uni.$u.toast(e.message || e.errMsg || 'Load property room types failed'); uni.$u.toast(e.message || e.errMsg || 'Load property room types failed');
@@ -437,7 +419,6 @@
})); }));
}, },
refreshCalendar() { refreshCalendar() {
if (!this.userData || this.userData.canViewCalendar !== true || this.calendarNoPermission) return;
this.ensureSelectedOptionsForRefresh(true); this.ensureSelectedOptionsForRefresh(true);
this.loadAvailabilityData(); this.loadAvailabilityData();
}, },
@@ -520,7 +501,7 @@
}); });
}, },
isNoPermissionResponse(res) { isNoPermissionResponse(res) {
return res && res.success === false && (res.errorCode || res.code) === 'NO_PERMISSION'; return res && res.success === false && res.errorCode === 'NO_PERMISSION';
}, },
showCalendarNoPermission() { showCalendarNoPermission() {
this.availabilityMap = {}; this.availabilityMap = {};
+38 -14
View File
@@ -1,7 +1,6 @@
<!--sso login--> <!--sso login-->
<template> <template>
<view class="container"> <view class="container">
<web-view :src="url"></web-view>
</view> </view>
</template> </template>
<script> <script>
@@ -11,26 +10,46 @@
url:'', url:'',
intervalId:null, intervalId:null,
newUrl:'', newUrl:'',
siteData:null siteData:null,
samlWebview:null
} }
}, },
onLoad(options) { onLoad(options) {
this.siteData = uni.getStorageSync("siteData"); this.siteData = uni.getStorageSync("siteData");
if(options && options.uid && options.pwd){ if(options && options.uid && options.pwd){
let username = options.uid.replaceAll(" ", "+"); let username = options.uid.replaceAll(" ", "+");
let password = options.pwd.replaceAll(" ", "+"); let password = options.pwd.replaceAll(" ", "+");
this.url = this.siteData.internalUrl + this.siteData.apiPath + "/app/sso/login_saml_with_mfa?username="+encodeURIComponent(username)+"&password="+encodeURIComponent(password); const loginUrl = this.siteData.internalUrl + this.siteData.apiPath + "/app/sso/login_saml_with_mfa?username="+encodeURIComponent(username)+"&password="+encodeURIComponent(password);
} this.url = loginUrl;
if (options && options.url){ this.openWebview(loginUrl);
this.url = options.url; }
} if (options && options.url){
this.listenUrl() this.url = options.url;
}, this.openWebview(options.url);
}
this.listenUrl()
},
onShow() { onShow() {
this.siteData = uni.getStorageSync("siteData"); this.siteData = uni.getStorageSync("siteData");
}, },
methods: { methods: {
listenUrl() { openWebview(loginUrl) {
// #ifdef APP-PLUS
const wv = plus.webview.create(loginUrl, 'saml-login-webview', {
'uni-app': 'none',
top: '0px',
bottom: '0px',
additionalHttpHeaders: {
'Origin': 'MGM_MCRM_APP'
}
});
this.samlWebview = wv;
const pages = getCurrentPages();
const page = pages[pages.length - 1];
page.$getAppWebview().append(wv);
// #endif
},
listenUrl() {
var that = this; var that = this;
var count = 0; var count = 0;
this.intervalId = setInterval(() => { this.intervalId = setInterval(() => {
@@ -75,6 +94,11 @@
} }
}, },
getCurrentUrl(){ getCurrentUrl(){
if (this.samlWebview) {
var url = this.samlWebview.getURL();
console.log("=======当前地址======", url);
return url;
}
var pages = getCurrentPages(); var pages = getCurrentPages();
var page = pages[pages.length - 1]; var page = pages[pages.length - 1];
var currentWebview = page.$getAppWebview(); var currentWebview = page.$getAppWebview();
+6 -14
View File
@@ -217,6 +217,7 @@
<script> <script>
import {logout} from '../../api/commonApi.js' import {logout} from '../../api/commonApi.js'
import {updateDeviceToken} from '../../api/notificationApi.js'
export default { export default {
data() { data() {
@@ -251,19 +252,6 @@
single: "2021-04-3", single: "2021-04-3",
today: new Date(), today: new Date(),
range: [], range: [],
selectData:[{
value: '1',
label: 'Corso'
},
{
value: '2',
label: 'Mauve',
},
{
value: '3',
label: 'Calmi',
}
],
} }
}, },
onLoad() { onLoad() {
@@ -356,7 +344,7 @@
showDelegate() { showDelegate() {
this.showMsgBox = true; this.showMsgBox = true;
}, },
confirmLang(e){ async confirmLang(e){
uni.setLocale(e.value[0].code); uni.setLocale(e.value[0].code);
this.$i18n.locale = e.value[0].code; this.$i18n.locale = e.value[0].code;
this.lang = e.value[0].name; this.lang = e.value[0].name;
@@ -378,6 +366,10 @@
// ignore // ignore
} }
}); });
//这里加上接口,切换语言时 更新DeviceToken中的useLanguage字段
const useLanguage = e.value[0].code === 'en'? 0 : 1;
console.log(useLanguage);
await updateDeviceToken({userId : this.userData.userId,useLanguage});
// #endif // #endif
this.showPicker = false; this.showPicker = false;
}, },
+21 -15
View File
@@ -10,7 +10,7 @@
height: navbarHeight+'px', height: navbarHeight+'px',
backgroundColor: bgColor, backgroundColor: bgColor,
}"></view> }"></view>
<view :class="[true && 'u-navbar--fixed']"> <view :class="[true && 'u-navbar--fixed']">
<u-status-bar :bgColor="bgColor"></u-status-bar> <u-status-bar :bgColor="bgColor"></u-status-bar>
<view class="u-navbar__content" :style="{ <view class="u-navbar__content" :style="{
height: navbarHeight, height: navbarHeight,
@@ -117,8 +117,6 @@
<image <image
v-show="index == 0 && currentIndex !== 2 && rowRecord.hasLimoRecord =='-1'" v-show="index == 0 && currentIndex !== 2 && rowRecord.hasLimoRecord =='-1'"
src="../../static/images/limo_warn.png" class="icon-img-limo"></image> src="../../static/images/limo_warn.png" class="icon-img-limo"></image>
<span v-show="index === 0 && rowRecord.isQueue"> (In Queue) </span>
<span v-show="index === 0 && rowRecord.roomReady"> (Room Ready) </span>
</span> </span>
</view> </view>
<!-- 直接使用v-if配合CSS动画类 --> <!-- 直接使用v-if配合CSS动画类 -->
@@ -237,7 +235,7 @@
</u-col> </u-col>
<u-col span="6"> <u-col span="6">
<view class="hz-font-12px">R6M TPD: {{formatNumber(rowRecord.detailData.r6MTPD)}}</view> <view class="hz-font-12px">R6M TPD: {{formatNumber(rowRecord.detailData.r6MTPD)}}</view>
<view class="hz-font-12px">R6M Win: {{formatNumber(rowRecord.detailData.r6MWin)}}</view> <view class="hz-font-12px">R6M Win: {{formatNumber(rowRecord.detailData.r6MWin)}}</view>:
<view class="hz-font-12px">R6M RI: <view class="hz-font-12px">R6M RI:
{{getPercentFormat(rowRecord.detailData.r6MRIPercentage)}}</view> {{getPercentFormat(rowRecord.detailData.r6MRIPercentage)}}</view>
<view v-show="currentIndex === 2 && filterType ==='MASS'" class="hz-font-12px">R6M Theo : {{formatNumber(rowRecord.detailData.r6MTheo)}} <view v-show="currentIndex === 2 && filterType ==='MASS'" class="hz-font-12px">R6M Theo : {{formatNumber(rowRecord.detailData.r6MTheo)}}
@@ -320,9 +318,6 @@
</view> </view>
</view> </view>
</view> </view>
<view v-if="currentIndex === 2 && rowRecord.roomReady" class="hz-font-12px todo-description-text" >
[Room Ready]
</view>
</uni-card> </uni-card>
<template v-slot:right> <template v-slot:right>
<view v-if="currentIndex === 0" class="hz-flex-row" style="height:100%;align-items: center;margin-left:15px;" <view v-if="currentIndex === 0" class="hz-flex-row" style="height:100%;align-items: center;margin-left:15px;"
@@ -447,7 +442,7 @@
getSetting, getSetting,
getExtendBooking getExtendBooking
} from '../../api/configApi.js'; } from '../../api/configApi.js';
import { normalizeApiKeys } from '../../common/normalizeApiKeys.js'; import { normalizeApiKeys, getApiRecords } from '../../common/normalizeApiKeys.js';
export default { export default {
components: { components: {
mgmHeader, // 注册组件 mgmHeader, // 注册组件
@@ -736,7 +731,12 @@
return num?.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",") || "0"; return num?.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",") || "0";
}, },
formarHour(num){ formarHour(num){
let hourStr = num > 1 ? num + ' hours' : num + ' hour'; let hourStr = '';
if(num!==null && num!==0){
hourStr = num > 1 ? num + ' hours' : num + ' hour';
}else{
hourStr = 0 + ' hour'
}
return hourStr; return hourStr;
}, },
checkPassEtaTime(record) { checkPassEtaTime(record) {
@@ -1042,7 +1042,7 @@
if (i.keys == "VIEW_YESTERDAY_TODOLIST_START") { if (i.keys == "VIEW_YESTERDAY_TODOLIST_START") {
dbStart = i.keyValues; dbStart = i.keyValues;
}else if(i.keys == "VIEW_YESTERDAY_TODOLIST_END"){ }else if(i.keys == "VIEW_YESTERDAY_TODOLIST_END"){
dbEnd = i.keyValues dbEnd = i.keyValues
} }
} }
let today = new Date(); let today = new Date();
@@ -1254,6 +1254,7 @@
if (requestSeq !== this.todoListRequestSeq) return; if (requestSeq !== this.todoListRequestSeq) return;
this.currentTask = null; this.currentTask = null;
const records = res && res.data && Array.isArray(res.data.records) ? res.data.records : []; const records = res && res.data && Array.isArray(res.data.records) ? res.data.records : [];
if (res && res.success == true && records.length > 0) { if (res && res.success == true && records.length > 0) {
this.showTodoListView=true; this.showTodoListView=true;
@@ -1272,8 +1273,8 @@
})) }))
})); }));
}else{ }else{
const errorCode = res && (res.errorCode || res.code) ? String(res.errorCode || res.code).toUpperCase() : ''; const errorCode = res && res.errorCode ? res.errorCode.toUpperCase() : "";
if(errorCode.indexOf('NO RECORD') !== -1 || (res && res.success == true && records.length === 0)){ if(errorCode.indexOf('NO RECORD') != -1 || (res && res.success == true && records.length == 0)){
this.dataNull = this.$t("common.noRecord"); this.dataNull = this.$t("common.noRecord");
}else{ }else{
this.dataNull = this.$t("todo.returnError"); this.dataNull = this.$t("todo.returnError");
@@ -1283,7 +1284,6 @@
} }
} catch (e) { } catch (e) {
if (requestSeq !== this.todoListRequestSeq) return;
if (e.errMsg && e.errMsg.includes("abort")) { if (e.errMsg && e.errMsg.includes("abort")) {
} else if (e.data!==null&&e.data!==undefined&&e.data.message!==undefined){ } else if (e.data!==null&&e.data!==undefined&&e.data.message!==undefined){
@@ -1330,6 +1330,7 @@
const res = await getTodoDetail(params); const res = await getTodoDetail(params);
if (res != null && res.success == true) { if (res != null && res.success == true) {
record.detailData = normalizeApiKeys(res.data); record.detailData = normalizeApiKeys(res.data);
console.log(record.detailData.r3MTOD);
if(record.detailData.descriptions!==null){ if(record.detailData.descriptions!==null){
for(let i=0;i<record.detailData.descriptions.length;i++){ for(let i=0;i<record.detailData.descriptions.length;i++){
if(record.detailData.descriptions[i].itemRecordType == 'Offer'){ if(record.detailData.descriptions[i].itemRecordType == 'Offer'){
@@ -1599,7 +1600,8 @@
getETALable() { getETALable() {
return function(record) { return function(record) {
if (this.currentIndex !== 1 && this.currentIndex !== 2) { if (this.currentIndex !== 1 && this.currentIndex !== 2) {
return record.eta; console.log(record);
return record.eTA;
} else if (record.bigLossFlag === 1) { } else if (record.bigLossFlag === 1) {
return "BIGLOSS"; return "BIGLOSS";
} else if (record.bigWinFlag === 1) { } else if (record.bigWinFlag === 1) {
@@ -1657,7 +1659,11 @@
}, },
getPercentFormat() { getPercentFormat() {
return function(val) { return function(val) {
return Math.trunc((val * 100)) + "%"; if(val){
return Math.trunc((val * 100)) + "%";
}else{
return "0%"
}
} }
}, },
getOtherDescription(){ getOtherDescription(){
+260 -1
View File
@@ -373,6 +373,241 @@ page{
border-top:1rpx solid #f4f4f4; border-top:1rpx solid #f4f4f4;
width:100%; width:100%;
} }
/* bookingRecord.vue */
.hz-nav-right {
margin-top: 2px;
margin-right: 3px;
}
.hz-nav-right-group {
border: 1rpx solid #ccc;
border-radius: 14px;
padding: 4px 12px;
}
.hz-nav-right-divider {
height: 20px;
border-left: 1px solid #ccc;
margin-left: 8px;
margin-right: 8px;
}
.hz-record-card {
margin-top: 20rpx;
align-items: center;
}
.hz-row-gap {
margin-top: 6px;
}
.hz-w-40 {
width: 40%;
}
.hz-w-50 {
width: 50%;
}
.hz-w-55 {
width: 55%;
}
.hz-w-60 {
width: 60%;
}
.hz-w-100 {
width: 100%;
}
.hz-sharer-wrap {
width: 50%;
text-align: right;
justify-content: flex-end;
}
.hz-sharer-tag {
width: 40%;
height: 14px;
line-height: 14px;
margin-right: 3px;
}
.hz-limo-dropoff-wrap {
padding-right: 50rpx;
}
.hz-book-btn-wrap {
width: 45%;
}
.hz-limo-status {
color: #ff9b9b;
}
.hz-booking-type-row {
justify-content: space-between;
align-items: center;
}
.hz-booking-type-wrap {
display: flex;
align-items: center;
gap: 8rpx;
}
.hz-booking-type-icon {
width: 24px;
height: 24px;
}
/* newLimo.vue */
.limo-text-input[disabled],
.limo-text-input--disabled {
background-color: #F5F7FA;
color: #4D4D4D;
}
.limo-booking-contact-row {
align-items: stretch;
display: flex;
flex-direction: row;
width: 100%;
}
.limo-booking-contact-row + .limo-booking-contact-row {
margin-top: 5px;
}
.limo-booking-contact-label {
color: #4D4D4D;
align-self: center;
flex: 0 0 38%;
font-size: 26rpx;
font-weight: 600;
white-space: nowrap;
}
.limo-booking-contact-controls {
align-items: stretch;
display: flex;
flex: 0 0 62%;
flex-direction: row;
gap: 2%;
justify-content: flex-end;
min-height: 36px;
min-width: 0;
}
.limo-booking-contact-code-picker {
align-self: stretch;
flex: 0 0 38%;
min-width: 0;
min-height: 36px;
}
.limo-booking-contact-code-picker ::v-deep .lu-data-select-box {
align-items: center;
background-color: #ffffff;
border: 1px solid #e5e5e5 !important;
border-radius: 4rpx;
box-sizing: border-box;
color: #4D4D4D;
display: flex;
font-size: 24rpx;
font-weight: 600;
height: 36px !important;
justify-content: center;
min-height: 36px !important;
padding: 0 5% !important;
width: 100%;
}
.limo-booking-contact-code-picker ::v-deep .wid {
align-items: center;
display: flex;
flex: 1;
min-width: 0;
overflow: hidden;
}
.limo-booking-contact-code-picker ::v-deep .lu-select-text,
.limo-booking-contact-code-picker ::v-deep .lu-data-select-input,
.limo-booking-contact-code-picker ::v-deep .placeholder-class {
color: #4D4D4D !important;
font-size: 24rpx !important;
font-weight: 600 !important;
flex: 1;
min-width: 0;
padding: 0 !important;
text-align: center;
}
.limo-booking-contact-code-picker ::v-deep .lu-data-select-close {
margin-left: 4%;
}
.limo-text-input.limo-booking-contact-phone {
background-color: #ffffff;
border: 1px solid #e5e5e5;
border-radius: 4rpx;
box-sizing: border-box;
color: #4D4D4D;
flex: 0 0 47%;
font-size: 24rpx;
font-weight: 600;
height: 36px;
min-width: 0;
min-height: 36px;
padding: 0 3%;
}
.limo-booking-contact-btn {
align-items: center;
background-color: #ffffff;
border: 1px solid #e5e5e5;
border-radius: 4rpx;
box-sizing: border-box;
color: #4D4D4D;
display: flex;
flex: 0 0 10%;
font-size: 28rpx;
font-weight: 700;
height: 36px;
justify-content: center;
min-height: 36px;
}
.limo-booking-contact-disabled {
background-color: #F5F7FA;
color: #4D4D4D;
pointer-events: none;
}
.limo-booking-contact-disabled ::v-deep .lu-data-select-box {
background-color: #F5F7FA;
color: #4D4D4D;
}
.limo-additional-options {
display: flex;
flex-direction: column;
}
.limo-additional-options ::v-deep .u-checkbox-group {
margin-bottom: 5px;
}
.limo-additional-options .limo-booking-contact-row {
margin-bottom: 5px;
}
.limo-additional-options .limo-booking-contact-row + .limo-booking-contact-row {
margin-top: 0;
}
/* textarea */ /* textarea */
.uni-textarea{ .uni-textarea{
width:100%; width:100%;
@@ -445,4 +680,28 @@ page{
} }
} }
} }
} }
.find-player-ico {
width: 64px;
height: 64px;
}
// spacing utilities
@for $i from 0 through 2000 {
.p-\[#{$i}\] { padding: #{$i}rpx !important; }
.px-\[#{$i}\] { padding-left: #{$i}rpx !important; padding-right: #{$i}rpx !important; }
.py-\[#{$i}\] { padding-top: #{$i}rpx !important; padding-bottom: #{$i}rpx !important; }
.pt-\[#{$i}\] { padding-top: #{$i}rpx !important; }
.pr-\[#{$i}\] { padding-right: #{$i}rpx !important; }
.pb-\[#{$i}\] { padding-bottom: #{$i}rpx !important; }
.pl-\[#{$i}\] { padding-left: #{$i}rpx !important; }
.m-\[#{$i}\] { margin: #{$i}rpx !important; }
.mx-\[#{$i}\] { margin-left: #{$i}rpx !important; margin-right: #{$i}rpx !important; }
.my-\[#{$i}\] { margin-top: #{$i}rpx !important; margin-bottom: #{$i}rpx !important; }
.mt-\[#{$i}\] { margin-top: #{$i}rpx !important; }
.mr-\[#{$i}\] { margin-right: #{$i}rpx !important; }
.mb-\[#{$i}\] { margin-bottom: #{$i}rpx !important; }
.ml-\[#{$i}\] { margin-left: #{$i}rpx !important; }
}
+302
View File
@@ -0,0 +1,302 @@
.mandatory {
color: #ff0000;
margin-left: 3px;
}
.limo-field-label {
font-size: 28rpx;
color: #808080;
font-weight: 600;
line-height: 1.4;
}
.limo-section {
margin-top: 20px;
}
.limo-form-row {
display: flex;
flex-direction: row;
flex-wrap: wrap;
margin-top: 4px;
}
.limo-form-cell {
width: 50%;
box-sizing: border-box;
padding-bottom: 16px;
padding-right: 8px;
}
.limo-form-cell:nth-child(2n) {
padding-right: 0;
padding-left: 8px;
}
.limo-form-col-full {
margin-top: 0;
padding-bottom: 16px;
}
.limo-control-wrap {
margin-top: 4px;
width: 100%;
}
.limo-tap-field {
box-sizing: border-box;
height: 36px;
min-height: 36px;
border: 1rpx solid #dcdfe6;
border-radius: 8rpx;
padding: 0 16rpx;
background-color: #fff;
gap: 8px;
}
.limo-tap-field text {
font-size: 13px;
color: #333333;
font-weight: 400;
}
.limo-tap-field ::v-deep .u-icon {
flex-shrink: 0;
}
.limo-text-input {
box-sizing: border-box;
width: 100%;
height: 36px;
min-height: 36px;
border: 1rpx solid #dcdfe6;
border-radius: 8rpx;
padding: 0 16rpx;
background-color: #fff;
font-size: 13px;
color: #333333;
font-weight: 400;
}
.limo-readonly-field {
display: flex;
align-items: center;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.limo-page-with-tabbar .limo-text-input[disabled],
.limo-page-with-tabbar .limo-text-input--disabled {
background-color: #F5F7FA !important;
border-color: #E4E7ED !important;
color: #333333 !important;
-webkit-text-fill-color: #333333;
opacity: 1;
}
.limo-page-with-tabbar {
box-sizing: border-box;
}
.limo-content-bottom-pad {
padding-bottom: 64rpx;
}
.limo-label-row {
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
}
.limo-dropoff-block {
margin-top: 0;
}
.limo-other-dropoff-field {
margin-top: 12rpx;
}
.limo-remark-wrap {
margin-top: 4px;
padding-bottom: 16rpx;
}
.limo-hourly-row {
margin-top: 4px;
min-height: 36px;
display: flex;
align-items: center;
}
.limo-control-wrap ::v-deep .lu-data-select .lu-data-select-box {
height: 36px !important;
min-height: 36px !important;
box-sizing: border-box;
padding: 0 7px !important;
}
.limo-control-wrap ::v-deep .limo-location-select.lu-data-select .lu-data-select-box {
height: auto !important;
min-height: 36px !important;
}
.limo-control-wrap ::v-deep .limo-location-select.lu-data-select .lu-data-select-content {
min-height: 36px;
align-items: stretch;
}
.limo-control-wrap ::v-deep .limo-location-select.lu-data-select .wid {
display: flex;
align-items: center;
min-width: 0;
}
.limo-control-wrap ::v-deep .limo-location-select.lu-data-select .lu-select-text {
white-space: normal !important;
word-break: break-word !important;
overflow: visible !important;
text-overflow: clip !important;
line-height: 18px !important;
padding: 8px 0 !important;
padding-right: 8px !important;
}
.limo-control-wrap ::v-deep .limo-location-select.lu-data-select .lu-data-select-close {
align-self: center;
}
.limo-control-wrap ::v-deep .limo-greet-select.lu-data-select .lu-data-select-box {
height: auto !important;
min-height: 36px !important;
box-sizing: border-box;
padding: 0 7px !important;
}
.limo-control-wrap ::v-deep .limo-greet-select.lu-data-select .lu-data-select-content {
min-height: 36px;
align-items: center;
}
.limo-control-wrap ::v-deep .limo-greet-select.lu-data-select .wid1 {
display: flex;
align-items: center;
min-width: 0;
flex: 1;
}
.limo-control-wrap ::v-deep .limo-greet-select.lu-data-select .lu-data-multiple {
min-height: 36px;
padding: 3px 0;
box-sizing: border-box;
align-items: center;
}
.limo-control-wrap ::v-deep .limo-greet-select.lu-data-select .lu-data-select-input-multiple {
height: 24px !important;
min-height: 24px !important;
line-height: 24px !important;
padding: 0 !important;
margin: 2px !important;
background-color: transparent !important;
}
.limo-control-wrap ::v-deep .limo-greet-select.lu-data-select .lu-data-select-close {
align-self: center;
}
.limo-page-with-tabbar ::v-deep .lu-data-select .lu-select-text,
.limo-page-with-tabbar ::v-deep .lu-data-select .lu-data-select-input,
.limo-page-with-tabbar ::v-deep .lu-data-select .lu-data-select-input-multiple,
.limo-page-with-tabbar ::v-deep .lu-data-select .placeholder-class,
.limo-remark-wrap ::v-deep .u-textarea__field,
.limo-remark-wrap ::v-deep textarea {
font-size: 13px !important;
color: #333333 !important;
font-weight: 400 !important;
}
.limo-page-with-tabbar ::v-deep .lu-data-select .arrow {
border-left: 6px solid transparent;
border-right: 6px solid transparent;
border-top: 6px solid #a8abb2;
}
.limo-page-with-tabbar ::v-deep .lu-data-select .arrow::before {
top: -6px;
left: -5px;
border-left: 5px solid transparent;
border-right: 5px solid transparent;
border-top: 5px solid rgb(255, 255, 255);
}
.limo-remark-wrap ::v-deep .u-textarea {
border-radius: 8rpx;
}
.limo-remark-wrap ::v-deep .u-textarea__field,
.limo-remark-wrap ::v-deep textarea {
min-height: 160rpx !important;
}
/* lu-data-select 下拉面板优化(仅作用于本页) */
.limo-page-with-tabbar ::v-deep .lu-select__selector {
border: none;
border-radius: 12rpx;
box-shadow: 0 8rpx 24rpx rgba(0, 0, 0, 0.12), 0 2rpx 6rpx rgba(0, 0, 0, 0.06);
overflow: hidden;
}
.limo-page-with-tabbar ::v-deep .lu-select__selector .lu-popper__arrow {
display: none;
}
.limo-page-with-tabbar ::v-deep .lu-select__selector_bottom {
top: calc(100% + 6rpx);
}
.limo-page-with-tabbar ::v-deep .lu-select__selector_top {
bottom: calc(100% + 6rpx);
}
.limo-page-with-tabbar ::v-deep .lu-select__selector .lu-select__selector-item {
padding: 10px 14px;
font-size: 13px;
color: #303133;
transition: background-color 0.15s ease;
}
.limo-page-with-tabbar ::v-deep .lu-select__selector .lu-select__selector-item:first-child {
margin-top: 4px;
}
.limo-page-with-tabbar ::v-deep .lu-select__selector .lu-select__selector-item:last-child {
margin-bottom: 4px;
}
.limo-page-with-tabbar ::v-deep .lu-select__selector .lu-select_selector-item_active {
background-color: rgba(195, 154, 75, 0.1);
color: #C39A4B;
font-weight: 500;
}
.booking-popup-card {
padding: 20px;
background-color: #ffffff;
border-radius: 16px;
}
.booking-popup-icon-row {
display: flex;
justify-content: center;
}
.booking-popup-title {
margin-top: 20px;
}
.booking-popup-note {
margin-top: 10px;
}
.booking-popup-note-text {
color: #333333;
font-size: 14px;
line-height: 22px;
font-weight: 400;
}
.booking-popup-override {
margin-top: 10px;
}
.booking-popup-field-label {
font-size: 14px;
color: #666666;
}
.booking-popup-override-select {
margin-top: 8px;
}
.booking-popup-button {
margin-top: 20px;
}
.booking-popup-button--primary {
background-color: #C39A4B;
}
.booking-popup-button--secondary {
margin-top: 10px;
}
.booking-popup-card--criteria {
min-height: 0;
}
.booking-popup-card--success {
min-height: 0;
}
.booking-popup-title--criteria {
line-height: 26px;
}
.booking-popup-title--success {
line-height: 1.4;
}
.booking-popup-note--criteria {
line-height: 22px;
}
.booking-popup-override--criteria {
margin-bottom: 0;
}
.booking-popup-button--criteria {
width: 100%;
}
.booking-popup-button--success {
width: 100%;
}
+1 -1
View File
@@ -452,4 +452,4 @@
position:relative; position:relative;
top:5px; top:5px;
} }
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 865 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 747 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 672 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 846 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 883 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 630 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 692 B

@@ -28,11 +28,6 @@ export default {
type: String, type: String,
default: "请选择" default: "请选择"
}, },
// 过滤输入框占位符;为空时沿用 placeholder
filterPlaceholder: {
type: String,
default: ""
},
// 是否可清空 // 是否可清空
clearable: { clearable: {
type: Boolean, type: Boolean,
@@ -103,4 +98,4 @@ export default {
type: Number, type: Number,
default: 1 default: 1
} }
} }
@@ -0,0 +1,173 @@
## 2.2.42(2025-08-20)
- 修复 datetime-picker 小程序样式警告
## 2.2.41(2025-08-20)
- 修复 uni-datetime-picker组件时间对比报错问题
## 2.2.40(2025-04-14)
- 修复 绑定字符串值的时,日历面板选中状态未重置到默认值的问题
## 2.2.39(2025-04-14)
- 修复 在 iOS 微信小程序上type='daterange'时,传入'YYYY-MM-DD'格式不生效的问题
## 2.2.38(2024-10-15)
- 修复 微信小程序中的getSystemInfo警告
## 2.2.35(2024-09-21)
- 修复 没有选中日期时点击确定直接报错的Bug [详情](https://ask.dcloud.net.cn/question/198168)
## 2.2.34(2024-04-24)
- 新增 日期点击事件,在点击日期时会触发该事件。
## 2.2.33(2024-04-15)
- 修复 抖音小程序事件传递失效bug
## 2.2.32(2024-02-20)
- 修复 日历的close事件触发异常的bug [详情](https://github.com/dcloudio/uni-ui/issues/844)
## 2.2.31(2024-02-20)
- 修复 h5平台 右边日历的月份默认+1的bug [详情](https://github.com/dcloudio/uni-ui/issues/841)
## 2.2.30(2024-01-31)
- 修复 隐藏“秒”时,在IOS15及以下版本时出现 结束时间在开始时间之前 的bug [详情](https://github.com/dcloudio/uni-ui/issues/788)
## 2.2.29(2024-01-20)
- 新增 show事件,弹窗弹出时触发该事件 [详情](https://github.com/dcloudio/uni-app/issues/4694)
## 2.2.28(2024-01-18)
- 去除 noChange事件,当进行日期范围选择时,若只选了一天,则开始结束日期都为同一天 [详情](https://github.com/dcloudio/uni-ui/issues/815)
## 2.2.27(2024-01-10)
- 优化 增加noChange事件,当进行日期范围选择时,若有空值,则触发该事件 [详情](https://github.com/dcloudio/uni-ui/issues/815)
## 2.2.26(2024-01-08)
- 修复 字节小程序时间选择范围器失效问题 [详情](https://github.com/dcloudio/uni-ui/issues/834)
## 2.2.25(2023-10-18)
- 修复 PC端初次修改时间,开始时间未更新的Bug [详情](https://github.com/dcloudio/uni-ui/issues/737)
## 2.2.24(2023-06-02)
- 修复 部分情况修改时间,开始、结束时间显示异常的Bug [详情](https://ask.dcloud.net.cn/question/171146)
- 优化 当前月可以选择上月、下月的日期的Bug
## 2.2.23(2023-05-02)
- 修复 部分情况修改时间,开始时间未更新的Bug [详情](https://github.com/dcloudio/uni-ui/issues/737)
- 修复 部分平台及设备第一次点击无法显示弹框的Bug
- 修复 ios 日期格式未补零显示及使用异常的Bug [详情](https://ask.dcloud.net.cn/question/162979)
## 2.2.22(2023-03-30)
- 修复 日历 picker 修改年月后,自动选中当月1日的Bug [详情](https://ask.dcloud.net.cn/question/165937)
- 修复 小程序端 低版本 ios NaN的Bug [详情](https://ask.dcloud.net.cn/question/162979)
## 2.2.21(2023-02-20)
- 修复 firefox 浏览器显示区域点击无法拉起日历弹框的Bug [详情](https://ask.dcloud.net.cn/question/163362)
## 2.2.20(2023-02-17)
- 优化 值为空依然选中当天问题
- 优化 提供 default-value 属性支持配置选择器打开时默认显示的时间
- 优化 非范围选择未选择日期时间,点击确认按钮选中当前日期时间
- 优化 字节小程序日期时间范围选择,底部日期换行的Bug
## 2.2.19(2023-02-09)
- 修复 2.2.18 引起范围选择配置 end 选择无效的Bug [详情](https://github.com/dcloudio/uni-ui/issues/686)
## 2.2.18(2023-02-08)
- 修复 移动端范围选择change事件触发异常的Bug [详情](https://github.com/dcloudio/uni-ui/issues/684)
- 优化 PC端输入日期格式错误时返回当前日期时间
- 优化 PC端输入日期时间超出 start、end 限制的Bug
- 优化 移动端日期时间范围用法时间展示不完整问题
## 2.2.17(2023-02-04)
- 修复 小程序端绑定 Date 类型报错的Bug [详情](https://github.com/dcloudio/uni-ui/issues/679)
- 修复 vue3 time-picker 无法显示绑定时分秒的Bug
## 2.2.16(2023-02-02)
- 修复 字节小程序报错的Bug
## 2.2.15(2023-02-02)
- 修复 某些情况切换月份错误的Bug
## 2.2.14(2023-01-30)
- 修复 某些情况切换月份错误的Bug [详情](https://ask.dcloud.net.cn/question/162033)
## 2.2.13(2023-01-10)
- 修复 多次加载组件造成内存占用的Bug
## 2.2.12(2022-12-01)
- 修复 vue3 下 i18n 国际化初始值不正确的Bug
## 2.2.11(2022-09-19)
- 修复 支付宝小程序样式错乱的Bug [详情](https://github.com/dcloudio/uni-app/issues/3861)
## 2.2.10(2022-09-19)
- 修复 反向选择日期范围,日期显示异常的Bug [详情](https://ask.dcloud.net.cn/question/153401?item_id=212892&rf=false)
## 2.2.9(2022-09-16)
- 可以使用 uni-scss 控制主题色
## 2.2.8(2022-09-08)
- 修复 close事件无效的Bug
## 2.2.7(2022-09-05)
- 修复 移动端 maskClick 无效的Bug [详情](https://ask.dcloud.net.cn/question/140824)
## 2.2.6(2022-06-30)
- 优化 组件样式,调整了组件图标大小、高度、颜色等,与uni-ui风格保持一致
## 2.2.5(2022-06-24)
- 修复 日历顶部年月及底部确认未国际化的Bug
## 2.2.4(2022-03-31)
- 修复 Vue3 下动态赋值,单选类型未响应的Bug
## 2.2.3(2022-03-28)
- 修复 Vue3 下动态赋值未响应的Bug
## 2.2.2(2021-12-10)
- 修复 clear-icon 属性在小程序平台不生效的Bug
## 2.2.1(2021-12-10)
- 修复 日期范围选在小程序平台,必须多点击一次才能取消选中状态的Bug
## 2.2.0(2021-11-19)
- 优化 组件UI,并提供设计资源 [详情](https://uniapp.dcloud.io/component/uniui/resource)
- 文档迁移 [https://uniapp.dcloud.io/component/uniui/uni-datetime-picker](https://uniapp.dcloud.io/component/uniui/uni-datetime-picker)
## 2.1.5(2021-11-09)
- 新增 提供组件设计资源,组件样式调整
## 2.1.4(2021-09-10)
- 修复 hide-second 在移动端的Bug
- 修复 单选赋默认值时,赋值日期未高亮的Bug
- 修复 赋默认值时,移动端未正确显示时间的Bug
## 2.1.3(2021-09-09)
- 新增 hide-second 属性,支持只使用时分,隐藏秒
## 2.1.2(2021-09-03)
- 优化 取消选中时(范围选)直接开始下一次选择, 避免多点一次
- 优化 移动端支持清除按钮,同时支持通过 ref 调用组件的 clear 方法
- 优化 调整字号大小,美化日历界面
- 修复 因国际化导致的 placeholder 失效的Bug
## 2.1.1(2021-08-24)
- 新增 支持国际化
- 优化 范围选择器在 pc 端过宽的问题
## 2.1.0(2021-08-09)
- 新增 适配 vue3
## 2.0.19(2021-08-09)
- 新增 支持作为 uni-forms 子组件相关功能
- 修复 在 uni-forms 中使用时,选择时间报 NAN 错误的Bug
## 2.0.18(2021-08-05)
- 修复 type 属性动态赋值无效的Bug
- 修复 ‘确认’按钮被 tabbar 遮盖 bug
- 修复 组件未赋值时范围选左、右日历相同的Bug
## 2.0.17(2021-08-04)
- 修复 范围选未正确显示当前值的Bug
- 修复 h5 平台(移动端)报错 'cale' of undefined 的Bug
## 2.0.16(2021-07-21)
- 新增 return-type 属性支持返回 date 日期对象
## 2.0.15(2021-07-14)
- 修复 单选日期类型,初始赋值后不在当前日历的Bug
- 新增 clearIcon 属性,显示框的清空按钮可配置显示隐藏(仅 pc 有效)
- 优化 移动端移除显示框的清空按钮,无实际用途
## 2.0.14(2021-07-14)
- 修复 组件赋值为空,界面未更新的Bug
- 修复 start 和 end 不能动态赋值的Bug
- 修复 范围选类型,用户选择后再次选择右侧日历(结束日期)显示不正确的Bug
## 2.0.13(2021-07-08)
- 修复 范围选择不能动态赋值的Bug
## 2.0.12(2021-07-08)
- 修复 范围选择的初始时间在一个月内时,造成无法选择的bug
## 2.0.11(2021-07-08)
- 优化 弹出层在超出视窗边缘定位不准确的问题
## 2.0.10(2021-07-08)
- 修复 范围起始点样式的背景色与今日样式的字体前景色融合,导致日期字体看不清的Bug
- 优化 弹出层在超出视窗边缘被遮盖的问题
## 2.0.9(2021-07-07)
- 新增 maskClick 事件
- 修复 特殊情况日历 rpx 布局错误的Bug,rpx -> px
- 修复 范围选择时清空返回值不合理的bug,['', ''] -> []
## 2.0.8(2021-07-07)
- 新增 日期时间显示框支持插槽
## 2.0.7(2021-07-01)
- 优化 添加 uni-icons 依赖
## 2.0.6(2021-05-22)
- 修复 图标在小程序上不显示的Bug
- 优化 重命名引用组件,避免潜在组件命名冲突
## 2.0.5(2021-05-20)
- 优化 代码目录扁平化
## 2.0.4(2021-05-12)
- 新增 组件示例地址
## 2.0.3(2021-05-10)
- 修复 ios 下不识别 '-' 日期格式的Bug
- 优化 pc 下弹出层添加边框和阴影
## 2.0.2(2021-05-08)
- 修复 在 admin 中获取弹出层定位错误的bug
## 2.0.1(2021-05-08)
- 修复 type 属性向下兼容,默认值从 date 变更为 datetime
## 2.0.0(2021-04-30)
- 支持日历形式的日期+时间的范围选择
> 注意:此版本不向后兼容,不再支持单独时间选择(type=time)及相关的 hide-second 属性(时间选可使用内置组件 picker)
## 1.0.6(2021-03-18)
- 新增 hide-second 属性,时间支持仅选择时、分
- 修复 选择跟显示的日期不一样的Bug
- 修复 chang事件触发2次的Bug
- 修复 分、秒 end 范围错误的Bug
- 优化 更好的 nvue 适配
@@ -0,0 +1,177 @@
<template>
<view class="uni-calendar-item__weeks-box" :class="{
'uni-calendar-item--disable':weeks.disable,
'uni-calendar-item--before-checked-x':weeks.beforeMultiple,
'uni-calendar-item--multiple': weeks.multiple,
'uni-calendar-item--after-checked-x':weeks.afterMultiple,
}" @click="choiceDate(weeks)" @mouseenter="handleMousemove(weeks)">
<view class="uni-calendar-item__weeks-box-item" :class="{
'uni-calendar-item--checked':calendar.fullDate === weeks.fullDate && (calendar.userChecked || !checkHover),
'uni-calendar-item--checked-range-text': checkHover,
'uni-calendar-item--before-checked':weeks.beforeMultiple,
'uni-calendar-item--multiple': weeks.multiple,
'uni-calendar-item--after-checked':weeks.afterMultiple,
'uni-calendar-item--disable':weeks.disable,
}">
<text v-if="selected && weeks.extraInfo" class="uni-calendar-item__weeks-box-circle"></text>
<text class="uni-calendar-item__weeks-box-text uni-calendar-item__weeks-box-text-disable uni-calendar-item--checked-text">{{weeks.date}}</text>
</view>
<view :class="{'uni-calendar-item--today': weeks.isToday}"></view>
</view>
</template>
<script>
export default {
props: {
weeks: {
type: Object,
default () {
return {}
}
},
calendar: {
type: Object,
default: () => {
return {}
}
},
selected: {
type: Array,
default: () => {
return []
}
},
checkHover: {
type: Boolean,
default: false
}
},
methods: {
choiceDate(weeks) {
this.$emit('change', weeks)
},
handleMousemove(weeks) {
this.$emit('handleMouse', weeks)
}
}
}
</script>
<style lang="scss" >
$uni-primary: #007aff !default;
.uni-calendar-item__weeks-box {
flex: 1;
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: column;
justify-content: center;
align-items: center;
margin: 1px 0;
position: relative;
}
.uni-calendar-item__weeks-box-text {
font-size: 14px;
// font-family: Lato-Bold, Lato;
font-weight: bold;
color: darken($color: $uni-primary, $amount: 40%);
}
.uni-calendar-item__weeks-box-item {
position: relative;
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: column;
justify-content: center;
align-items: center;
width: 40px;
height: 40px;
/* #ifdef H5 */
cursor: pointer;
/* #endif */
}
.uni-calendar-item__weeks-box-circle {
position: absolute;
top: 5px;
right: 5px;
width: 8px;
height: 8px;
border-radius: 8px;
background-color: #dd524d;
}
.uni-calendar-item__weeks-box .uni-calendar-item--disable {
cursor: default;
}
.uni-calendar-item--disable .uni-calendar-item__weeks-box-text-disable {
color: #D1D1D1;
}
.uni-calendar-item--today {
position: absolute;
top: 10px;
right: 17%;
background-color: #dd524d;
width:6px;
height: 6px;
border-radius: 50%;
}
.uni-calendar-item--extra {
color: #dd524d;
opacity: 0.8;
}
.uni-calendar-item__weeks-box .uni-calendar-item--checked {
background-color: $uni-primary;
border-radius: 50%;
box-sizing: border-box;
border: 3px solid #fff;
}
.uni-calendar-item--checked .uni-calendar-item--checked-text {
color: #fff;
}
.uni-calendar-item--multiple .uni-calendar-item--checked-range-text {
color: #333;
}
.uni-calendar-item--multiple {
background-color: #F6F7FC;
// color: #fff;
}
.uni-calendar-item--multiple .uni-calendar-item--before-checked,
.uni-calendar-item--multiple .uni-calendar-item--after-checked {
background-color: $uni-primary;
border-radius: 50%;
box-sizing: border-box;
border: 3px solid #F6F7FC;
}
.uni-calendar-item--before-checked .uni-calendar-item--checked-text,
.uni-calendar-item--after-checked .uni-calendar-item--checked-text {
color: #fff;
}
.uni-calendar-item--before-checked-x {
border-top-left-radius: 50px;
border-bottom-left-radius: 50px;
box-sizing: border-box;
background-color: #F6F7FC;
}
.uni-calendar-item--after-checked-x {
border-top-right-radius: 50px;
border-bottom-right-radius: 50px;
background-color: #F6F7FC;
}
</style>
@@ -0,0 +1,947 @@
<template>
<view class="uni-calendar" @mouseleave="leaveCale">
<view v-if="!insert && show" class="uni-calendar__mask" :class="{'uni-calendar--mask-show':aniMaskShow}"
@click="maskClick"></view>
<view v-if="insert || show" class="uni-calendar__content"
:class="{'uni-calendar--fixed':!insert,'uni-calendar--ani-show':aniMaskShow, 'uni-calendar__content-mobile': aniMaskShow}">
<view class="uni-calendar__header" :class="{'uni-calendar__header-mobile' :!insert}">
<view class="uni-calendar__header-btn-box" @click.stop="changeMonth('pre')">
<view class="uni-calendar__header-btn uni-calendar--left"></view>
</view>
<picker mode="date" :value="date" fields="month" @change="bindDateChange">
<text
class="uni-calendar__header-text">{{ (nowDate.year||'') + yearText + ( nowDate.month||'') + monthText}}</text>
</picker>
<view class="uni-calendar__header-btn-box" @click.stop="changeMonth('next')">
<view class="uni-calendar__header-btn uni-calendar--right"></view>
</view>
<view v-if="!insert" class="dialog-close" @click="maskClick">
<view class="dialog-close-plus" data-id="close"></view>
<view class="dialog-close-plus dialog-close-rotate" data-id="close"></view>
</view>
</view>
<view class="uni-calendar__box">
<view v-if="showMonth" class="uni-calendar__box-bg">
<text class="uni-calendar__box-bg-text">{{nowDate.month}}</text>
</view>
<view class="uni-calendar__weeks" style="padding-bottom: 7px;">
<view class="uni-calendar__weeks-day">
<text class="uni-calendar__weeks-day-text">{{SUNText}}</text>
</view>
<view class="uni-calendar__weeks-day">
<text class="uni-calendar__weeks-day-text">{{MONText}}</text>
</view>
<view class="uni-calendar__weeks-day">
<text class="uni-calendar__weeks-day-text">{{TUEText}}</text>
</view>
<view class="uni-calendar__weeks-day">
<text class="uni-calendar__weeks-day-text">{{WEDText}}</text>
</view>
<view class="uni-calendar__weeks-day">
<text class="uni-calendar__weeks-day-text">{{THUText}}</text>
</view>
<view class="uni-calendar__weeks-day">
<text class="uni-calendar__weeks-day-text">{{FRIText}}</text>
</view>
<view class="uni-calendar__weeks-day">
<text class="uni-calendar__weeks-day-text">{{SATText}}</text>
</view>
</view>
<view class="uni-calendar__weeks" v-for="(item,weekIndex) in weeks" :key="weekIndex">
<view class="uni-calendar__weeks-item" v-for="(weeks,weeksIndex) in item" :key="weeksIndex">
<calendar-item class="uni-calendar-item--hook" :weeks="weeks" :calendar="calendar" :selected="selected"
:checkHover="range" @change="choiceDate" @handleMouse="handleMouse">
</calendar-item>
</view>
</view>
</view>
<view v-if="!insert && !range && hasTime" class="uni-date-changed uni-calendar--fixed-top"
style="padding: 0 80px;">
<view class="uni-date-changed--time-date">{{tempSingleDate ? tempSingleDate : selectDateText}}</view>
<time-picker type="time" :start="timepickerStartTime" :end="timepickerEndTime" v-model="time"
:disabled="!tempSingleDate" :border="false" :hide-second="hideSecond" class="time-picker-style">
</time-picker>
</view>
<view v-if="!insert && range && hasTime" class="uni-date-changed uni-calendar--fixed-top">
<view class="uni-date-changed--time-start">
<view class="uni-date-changed--time-date">{{tempRange.before ? tempRange.before : startDateText}}
</view>
<time-picker type="time" :start="timepickerStartTime" v-model="timeRange.startTime" :border="false"
:hide-second="hideSecond" :disabled="!tempRange.before" class="time-picker-style">
</time-picker>
</view>
<view style="line-height: 50px;">
<uni-icons type="arrowthinright" color="#999"></uni-icons>
</view>
<view class="uni-date-changed--time-end">
<view class="uni-date-changed--time-date">{{tempRange.after ? tempRange.after : endDateText}}</view>
<time-picker type="time" :end="timepickerEndTime" v-model="timeRange.endTime" :border="false"
:hide-second="hideSecond" :disabled="!tempRange.after" class="time-picker-style">
</time-picker>
</view>
</view>
<view v-if="!insert" class="uni-date-changed uni-date-btn--ok">
<view class="uni-datetime-picker--btn" @click="confirm">{{confirmText}}</view>
</view>
</view>
</view>
</template>
<script>
import {
Calendar,
getDate,
getTime
} from './util.js';
import calendarItem from './calendar-item.vue'
import timePicker from './time-picker.vue'
import {
initVueI18n
} from '@dcloudio/uni-i18n'
import i18nMessages from './i18n/index.js'
const {
t
} = initVueI18n(i18nMessages)
/**
* Calendar 日历
* @description 日历组件可以查看日期,选择任意范围内的日期,打点操作。常用场景如:酒店日期预订、火车机票选择购买日期、上下班打卡等
* @tutorial https://ext.dcloud.net.cn/plugin?id=56
* @property {String} date 自定义当前时间,默认为今天
* @property {String} startDate 日期选择范围-开始日期
* @property {String} endDate 日期选择范围-结束日期
* @property {Boolean} range 范围选择
* @property {Boolean} insert = [true|false] 插入模式,默认为false
* @value true 弹窗模式
* @value false 插入模式
* @property {Boolean} clearDate = [true|false] 弹窗模式是否清空上次选择内容
* @property {Array} selected 打点,期待格式[{date: '2019-06-27', info: '签到', data: { custom: '自定义信息', name: '自定义消息头',xxx:xxx... }}]
* @property {Boolean} showMonth 是否选择月份为背景
* @property {[String} defaultValue 选择器打开时默认显示的时间
* @event {Function} change 日期改变,`insert :ture` 时生效
* @event {Function} confirm 确认选择`insert :false` 时生效
* @event {Function} monthSwitch 切换月份时触发
* @example <uni-calendar :insert="true" :start-date="'2019-3-2'":end-date="'2019-5-20'"@change="change" />
*/
export default {
components: {
calendarItem,
timePicker
},
options: {
// #ifdef MP-TOUTIAO
virtualHost: false,
// #endif
// #ifndef MP-TOUTIAO
virtualHost: true
// #endif
},
props: {
date: {
type: String,
default: ''
},
defTime: {
type: [String, Object],
default: ''
},
selectableTimes: {
type: [Object],
default () {
return {}
}
},
selected: {
type: Array,
default () {
return []
}
},
startDate: {
type: String,
default: ''
},
endDate: {
type: String,
default: ''
},
startPlaceholder: {
type: String,
default: ''
},
endPlaceholder: {
type: String,
default: ''
},
range: {
type: Boolean,
default: false
},
hasTime: {
type: Boolean,
default: false
},
insert: {
type: Boolean,
default: true
},
showMonth: {
type: Boolean,
default: true
},
clearDate: {
type: Boolean,
default: true
},
checkHover: {
type: Boolean,
default: true
},
hideSecond: {
type: [Boolean],
default: false
},
pleStatus: {
type: Object,
default () {
return {
before: '',
after: '',
data: [],
fulldate: ''
}
}
},
defaultValue: {
type: [String, Object, Array],
default: ''
}
},
data() {
return {
show: false,
weeks: [],
calendar: {},
nowDate: {},
aniMaskShow: false,
firstEnter: true,
time: '',
timeRange: {
startTime: '',
endTime: ''
},
tempSingleDate: '',
tempRange: {
before: '',
after: ''
}
}
},
watch: {
date: {
immediate: true,
handler(newVal) {
if (!this.range) {
this.tempSingleDate = newVal
setTimeout(() => {
this.init(newVal)
}, 100)
}
}
},
defTime: {
immediate: true,
handler(newVal) {
if (!this.range) {
this.time = newVal
} else {
this.timeRange.startTime = newVal.start
this.timeRange.endTime = newVal.end
}
}
},
startDate(val) {
// 字节小程序 watch 早于 created
if (!this.cale) {
return
}
this.cale.setStartDate(val)
this.cale.setDate(this.nowDate.fullDate)
this.weeks = this.cale.weeks
},
endDate(val) {
// 字节小程序 watch 早于 created
if (!this.cale) {
return
}
this.cale.setEndDate(val)
this.cale.setDate(this.nowDate.fullDate)
this.weeks = this.cale.weeks
},
selected(newVal) {
// 字节小程序 watch 早于 created
if (!this.cale) {
return
}
this.cale.setSelectInfo(this.nowDate.fullDate, newVal)
this.weeks = this.cale.weeks
},
pleStatus: {
immediate: true,
handler(newVal) {
const {
before,
after,
fulldate,
which
} = newVal
this.tempRange.before = before
this.tempRange.after = after
setTimeout(() => {
if (fulldate) {
this.cale.setHoverMultiple(fulldate)
if (before && after) {
this.cale.lastHover = true
if (this.rangeWithinMonth(after, before)) return
this.setDate(before)
} else {
this.cale.setMultiple(fulldate)
this.setDate(this.nowDate.fullDate)
this.calendar.fullDate = ''
this.cale.lastHover = false
}
} else {
// 字节小程序 watch 早于 created
if (!this.cale) {
return
}
this.cale.setDefaultMultiple(before, after)
if (which === 'left' && before) {
this.setDate(before)
this.weeks = this.cale.weeks
} else if (after) {
this.setDate(after)
this.weeks = this.cale.weeks
}
this.cale.lastHover = true
}
}, 16)
}
}
},
computed: {
timepickerStartTime() {
const activeDate = this.range ? this.tempRange.before : this.calendar.fullDate
return activeDate === this.startDate ? this.selectableTimes.start : ''
},
timepickerEndTime() {
const activeDate = this.range ? this.tempRange.after : this.calendar.fullDate
return activeDate === this.endDate ? this.selectableTimes.end : ''
},
/**
* for i18n
*/
selectDateText() {
return t("uni-datetime-picker.selectDate")
},
startDateText() {
return this.startPlaceholder || t("uni-datetime-picker.startDate")
},
endDateText() {
return this.endPlaceholder || t("uni-datetime-picker.endDate")
},
okText() {
return t("uni-datetime-picker.ok")
},
yearText() {
return t("uni-datetime-picker.year")
},
monthText() {
return t("uni-datetime-picker.month")
},
MONText() {
return t("uni-calender.MON")
},
TUEText() {
return t("uni-calender.TUE")
},
WEDText() {
return t("uni-calender.WED")
},
THUText() {
return t("uni-calender.THU")
},
FRIText() {
return t("uni-calender.FRI")
},
SATText() {
return t("uni-calender.SAT")
},
SUNText() {
return t("uni-calender.SUN")
},
confirmText() {
return t("uni-calender.confirm")
},
},
created() {
// 获取日历方法实例
this.cale = new Calendar({
selected: this.selected,
startDate: this.startDate,
endDate: this.endDate,
range: this.range,
})
// 选中某一天
this.init(this.date)
},
methods: {
leaveCale() {
this.firstEnter = true
},
handleMouse(weeks) {
if (weeks.disable) return
if (this.cale.lastHover) return
let {
before,
after
} = this.cale.multipleStatus
if (!before) return
this.calendar = weeks
// 设置范围选
this.cale.setHoverMultiple(this.calendar.fullDate)
this.weeks = this.cale.weeks
// hover时,进入一个日历,更新另一个
if (this.firstEnter) {
this.$emit('firstEnterCale', this.cale.multipleStatus)
this.firstEnter = false
}
},
rangeWithinMonth(A, B) {
const [yearA, monthA] = A.split('-')
const [yearB, monthB] = B.split('-')
return yearA === yearB && monthA === monthB
},
// 蒙版点击事件
maskClick() {
this.close()
this.$emit('maskClose')
},
clearCalender() {
if (this.range) {
this.timeRange.startTime = ''
this.timeRange.endTime = ''
this.tempRange.before = ''
this.tempRange.after = ''
this.cale.multipleStatus.before = ''
this.cale.multipleStatus.after = ''
this.cale.multipleStatus.data = []
this.cale.lastHover = false
} else {
this.time = ''
this.tempSingleDate = ''
}
this.calendar.fullDate = ''
this.setDate(new Date())
},
bindDateChange(e) {
const value = e.detail.value + '-1'
this.setDate(value)
},
/**
* 初始化日期显示
* @param {Object} date
*/
init(date) {
// 字节小程序 watch 早于 created
if (!this.cale) {
return
}
this.cale.setDate(date || new Date())
this.weeks = this.cale.weeks
this.nowDate = this.cale.getInfo(date)
this.calendar = {
...this.nowDate
}
if (!date) {
// 优化date为空默认不选中今天
this.calendar.fullDate = ''
if (this.defaultValue && !this.range) {
// 暂时只支持移动端非范围选择
const defaultDate = new Date(this.defaultValue)
const fullDate = getDate(defaultDate)
const year = defaultDate.getFullYear()
const month = defaultDate.getMonth() + 1
const date = defaultDate.getDate()
const day = defaultDate.getDay()
this.calendar = {
fullDate,
year,
month,
date,
day
},
this.tempSingleDate = fullDate
this.time = getTime(defaultDate, this.hideSecond)
}
}
},
/**
* 打开日历弹窗
*/
open() {
// 弹窗模式并且清理数据
if (this.clearDate && !this.insert) {
this.cale.cleanMultipleStatus()
this.init(this.date)
}
this.show = true
this.$nextTick(() => {
setTimeout(() => {
this.aniMaskShow = true
}, 50)
})
},
/**
* 关闭日历弹窗
*/
close() {
this.aniMaskShow = false
this.$nextTick(() => {
setTimeout(() => {
this.show = false
this.$emit('close')
}, 300)
})
},
/**
* 确认按钮
*/
confirm() {
this.setEmit('confirm')
this.close()
},
/**
* 变化触发
*/
change(isSingleChange) {
if (!this.insert && !isSingleChange) return
this.setEmit('change')
},
/**
* 选择月份触发
*/
monthSwitch() {
let {
year,
month
} = this.nowDate
this.$emit('monthSwitch', {
year,
month: Number(month)
})
},
/**
* 派发事件
* @param {Object} name
*/
setEmit(name) {
if (!this.range) {
if (!this.calendar.fullDate) {
this.calendar = this.cale.getInfo(new Date())
this.tempSingleDate = this.calendar.fullDate
}
if (this.hasTime && !this.time) {
this.time = getTime(new Date(), this.hideSecond)
}
}
let {
year,
month,
date,
fullDate,
extraInfo
} = this.calendar
this.$emit(name, {
range: this.cale.multipleStatus,
year,
month,
date,
time: this.time,
timeRange: this.timeRange,
fulldate: fullDate,
extraInfo: extraInfo || {}
})
},
/**
* 选择天触发
* @param {Object} weeks
*/
choiceDate(weeks) {
if (weeks.disable) return
this.calendar = weeks
this.calendar.userChecked = true
// 设置多选
this.cale.setMultiple(this.calendar.fullDate, true)
this.weeks = this.cale.weeks
this.tempSingleDate = this.calendar.fullDate
const beforeDate = new Date(this.cale.multipleStatus.before).getTime()
const afterDate = new Date(this.cale.multipleStatus.after).getTime()
if (beforeDate > afterDate && afterDate) {
this.tempRange.before = this.cale.multipleStatus.after
this.tempRange.after = this.cale.multipleStatus.before
} else {
this.tempRange.before = this.cale.multipleStatus.before
this.tempRange.after = this.cale.multipleStatus.after
}
this.change(true)
},
changeMonth(type) {
let newDate
if (type === 'pre') {
newDate = this.cale.getPreMonthObj(this.nowDate.fullDate).fullDate
} else if (type === 'next') {
newDate = this.cale.getNextMonthObj(this.nowDate.fullDate).fullDate
}
this.setDate(newDate)
this.monthSwitch()
},
/**
* 设置日期
* @param {Object} date
*/
setDate(date) {
this.cale.setDate(date)
this.weeks = this.cale.weeks
this.nowDate = this.cale.getInfo(date)
}
}
}
</script>
<style lang="scss">
$uni-primary: #007aff !default;
.uni-calendar {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: column;
}
.uni-calendar__mask {
position: fixed;
bottom: 0;
top: 0;
left: 0;
right: 0;
background-color: rgba(0, 0, 0, 0.4);
transition-property: opacity;
transition-duration: 0.3s;
opacity: 0;
/* #ifndef APP-NVUE */
z-index: 99;
/* #endif */
}
.uni-calendar--mask-show {
opacity: 1
}
.uni-calendar--fixed {
position: fixed;
bottom: calc(var(--window-bottom));
left: 0;
right: 0;
transition-property: transform;
transition-duration: 0.3s;
transform: translateY(460px);
/* #ifndef APP-NVUE */
z-index: 99;
/* #endif */
}
.uni-calendar--ani-show {
transform: translateY(0);
}
.uni-calendar__content {
background-color: #fff;
}
.uni-calendar__content-mobile {
border-top-left-radius: 10px;
border-top-right-radius: 10px;
box-shadow: 0px 0px 5px 3px rgba(0, 0, 0, 0.1);
}
.uni-calendar__header {
position: relative;
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: row;
justify-content: center;
align-items: center;
height: 50px;
}
.uni-calendar__header-mobile {
padding: 10px;
padding-bottom: 0;
}
.uni-calendar--fixed-top {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: row;
justify-content: space-between;
border-top-color: rgba(0, 0, 0, 0.4);
border-top-style: solid;
border-top-width: 1px;
}
.uni-calendar--fixed-width {
width: 50px;
}
.uni-calendar__backtoday {
position: absolute;
right: 0;
top: 25rpx;
padding: 0 5px;
padding-left: 10px;
height: 25px;
line-height: 25px;
font-size: 12px;
border-top-left-radius: 25px;
border-bottom-left-radius: 25px;
color: #fff;
background-color: #f1f1f1;
}
.uni-calendar__header-text {
text-align: center;
width: 100px;
font-size: 15px;
color: #666;
}
.uni-calendar__button-text {
text-align: center;
width: 100px;
font-size: 14px;
color: $uni-primary;
/* #ifndef APP-NVUE */
letter-spacing: 3px;
/* #endif */
}
.uni-calendar__header-btn-box {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: row;
align-items: center;
justify-content: center;
width: 50px;
height: 50px;
}
.uni-calendar__header-btn {
width: 9px;
height: 9px;
border-left-color: #808080;
border-left-style: solid;
border-left-width: 1px;
border-top-color: #555555;
border-top-style: solid;
border-top-width: 1px;
}
.uni-calendar--left {
transform: rotate(-45deg);
}
.uni-calendar--right {
transform: rotate(135deg);
}
.uni-calendar__weeks {
position: relative;
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: row;
}
.uni-calendar__weeks-item {
flex: 1;
}
.uni-calendar__weeks-day {
flex: 1;
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: column;
justify-content: center;
align-items: center;
height: 40px;
border-bottom-color: #F5F5F5;
border-bottom-style: solid;
border-bottom-width: 1px;
}
.uni-calendar__weeks-day-text {
font-size: 12px;
color: #B2B2B2;
}
.uni-calendar__box {
position: relative;
// padding: 0 10px;
padding-bottom: 7px;
}
.uni-calendar__box-bg {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
justify-content: center;
align-items: center;
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
}
.uni-calendar__box-bg-text {
font-size: 200px;
font-weight: bold;
color: #999;
opacity: 0.1;
text-align: center;
/* #ifndef APP-NVUE */
line-height: 1;
/* #endif */
}
.uni-date-changed {
padding: 0 10px;
// line-height: 50px;
text-align: center;
color: #333;
border-top-color: #DCDCDC;
;
border-top-style: solid;
border-top-width: 1px;
flex: 1;
}
.uni-date-btn--ok {
padding: 20px 15px;
}
.uni-date-changed--time-start {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
align-items: center;
}
.uni-date-changed--time-end {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
align-items: center;
}
.uni-date-changed--time-date {
color: #999;
line-height: 50px;
/* #ifdef MP-TOUTIAO */
font-size: 16px;
/* #endif */
margin-right: 5px;
// opacity: 0.6;
}
.time-picker-style {
// width: 62px;
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
justify-content: center;
align-items: center
}
.mr-10 {
margin-right: 10px;
}
.dialog-close {
position: absolute;
top: 0;
right: 0;
bottom: 0;
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: row;
align-items: center;
padding: 0 25px;
margin-top: 10px;
}
.dialog-close-plus {
width: 16px;
height: 2px;
background-color: #737987;
border-radius: 2px;
transform: rotate(45deg);
}
.dialog-close-rotate {
position: absolute;
transform: rotate(-45deg);
}
.uni-datetime-picker--btn {
border-radius: 100px;
height: 40px;
line-height: 40px;
background-color: $uni-primary;
color: #fff;
font-size: 16px;
letter-spacing: 2px;
}
/* #ifndef APP-NVUE */
.uni-datetime-picker--btn:active {
opacity: 0.7;
}
/* #endif */
</style>
@@ -0,0 +1,22 @@
{
"uni-datetime-picker.selectDate": "select date",
"uni-datetime-picker.selectTime": "select time",
"uni-datetime-picker.selectDateTime": "select date and time",
"uni-datetime-picker.startDate": "start date",
"uni-datetime-picker.endDate": "end date",
"uni-datetime-picker.startTime": "start time",
"uni-datetime-picker.endTime": "end time",
"uni-datetime-picker.ok": "ok",
"uni-datetime-picker.clear": "clear",
"uni-datetime-picker.cancel": "cancel",
"uni-datetime-picker.year": "-",
"uni-datetime-picker.month": "",
"uni-calender.MON": "MON",
"uni-calender.TUE": "TUE",
"uni-calender.WED": "WED",
"uni-calender.THU": "THU",
"uni-calender.FRI": "FRI",
"uni-calender.SAT": "SAT",
"uni-calender.SUN": "SUN",
"uni-calender.confirm": "confirm"
}
@@ -0,0 +1,8 @@
import en from './en.json'
import zhHans from './zh-Hans.json'
import zhHant from './zh-Hant.json'
export default {
en,
'zh-Hans': zhHans,
'zh-Hant': zhHant
}
@@ -0,0 +1,22 @@
{
"uni-datetime-picker.selectDate": "选择日期",
"uni-datetime-picker.selectTime": "选择时间",
"uni-datetime-picker.selectDateTime": "选择日期时间",
"uni-datetime-picker.startDate": "开始日期",
"uni-datetime-picker.endDate": "结束日期",
"uni-datetime-picker.startTime": "开始时间",
"uni-datetime-picker.endTime": "结束时间",
"uni-datetime-picker.ok": "确定",
"uni-datetime-picker.clear": "清除",
"uni-datetime-picker.cancel": "取消",
"uni-datetime-picker.year": "年",
"uni-datetime-picker.month": "月",
"uni-calender.SUN": "日",
"uni-calender.MON": "一",
"uni-calender.TUE": "二",
"uni-calender.WED": "三",
"uni-calender.THU": "四",
"uni-calender.FRI": "五",
"uni-calender.SAT": "六",
"uni-calender.confirm": "确认"
}
@@ -0,0 +1,22 @@
{
"uni-datetime-picker.selectDate": "選擇日期",
"uni-datetime-picker.selectTime": "選擇時間",
"uni-datetime-picker.selectDateTime": "選擇日期時間",
"uni-datetime-picker.startDate": "開始日期",
"uni-datetime-picker.endDate": "結束日期",
"uni-datetime-picker.startTime": "開始时间",
"uni-datetime-picker.endTime": "結束时间",
"uni-datetime-picker.ok": "確定",
"uni-datetime-picker.clear": "清除",
"uni-datetime-picker.cancel": "取消",
"uni-datetime-picker.year": "年",
"uni-datetime-picker.month": "月",
"uni-calender.SUN": "日",
"uni-calender.MON": "一",
"uni-calender.TUE": "二",
"uni-calender.WED": "三",
"uni-calender.THU": "四",
"uni-calender.FRI": "五",
"uni-calender.SAT": "六",
"uni-calender.confirm": "確認"
}
@@ -0,0 +1,940 @@
<template>
<view class="uni-datetime-picker">
<view @click="initTimePicker">
<slot>
<view class="uni-datetime-picker-timebox-pointer"
:class="{'uni-datetime-picker-disabled': disabled, 'uni-datetime-picker-timebox': border}">
<text class="uni-datetime-picker-text">{{time}}</text>
<view v-if="!time" class="uni-datetime-picker-time">
<text class="uni-datetime-picker-text">{{selectTimeText}}</text>
</view>
</view>
</slot>
</view>
<view v-if="visible" id="mask" class="uni-datetime-picker-mask" @click="tiggerTimePicker"></view>
<view v-if="visible" class="uni-datetime-picker-popup" :class="[dateShow && timeShow ? '' : 'fix-nvue-height']"
:style="fixNvueBug">
<view class="uni-title">
<text class="uni-datetime-picker-text">{{selectTimeText}}</text>
</view>
<view v-if="dateShow" class="uni-datetime-picker__container-box">
<picker-view class="uni-datetime-picker-view" :indicator-style="indicatorStyle" :value="ymd"
@change="bindDateChange">
<picker-view-column>
<view class="uni-datetime-picker-item" v-for="(item,index) in years" :key="index">
<text class="uni-datetime-picker-item">{{lessThanTen(item)}}</text>
</view>
</picker-view-column>
<picker-view-column>
<view class="uni-datetime-picker-item" v-for="(item,index) in months" :key="index">
<text class="uni-datetime-picker-item">{{lessThanTen(item)}}</text>
</view>
</picker-view-column>
<picker-view-column>
<view class="uni-datetime-picker-item" v-for="(item,index) in days" :key="index">
<text class="uni-datetime-picker-item">{{lessThanTen(item)}}</text>
</view>
</picker-view-column>
</picker-view>
<!-- 兼容 nvue 不支持伪类 -->
<text class="uni-datetime-picker-sign sign-left">-</text>
<text class="uni-datetime-picker-sign sign-right">-</text>
</view>
<view v-if="timeShow" class="uni-datetime-picker__container-box">
<picker-view class="uni-datetime-picker-view" :class="[hideSecond ? 'time-hide-second' : '']"
:indicator-style="indicatorStyle" :value="hms" @change="bindTimeChange">
<picker-view-column>
<view class="uni-datetime-picker-item" v-for="(item,index) in hours" :key="index">
<text class="uni-datetime-picker-item">{{lessThanTen(item)}}</text>
</view>
</picker-view-column>
<picker-view-column>
<view class="uni-datetime-picker-item" v-for="(item,index) in minutes" :key="index">
<text class="uni-datetime-picker-item">{{lessThanTen(item)}}</text>
</view>
</picker-view-column>
<picker-view-column v-if="!hideSecond">
<view class="uni-datetime-picker-item" v-for="(item,index) in seconds" :key="index">
<text class="uni-datetime-picker-item">{{lessThanTen(item)}}</text>
</view>
</picker-view-column>
</picker-view>
<!-- 兼容 nvue 不支持伪类 -->
<text class="uni-datetime-picker-sign" :class="[hideSecond ? 'sign-center' : 'sign-left']">:</text>
<text v-if="!hideSecond" class="uni-datetime-picker-sign sign-right">:</text>
</view>
<view class="uni-datetime-picker-btn">
<view @click="clearTime">
<text class="uni-datetime-picker-btn-text">{{clearText}}</text>
</view>
<view class="uni-datetime-picker-btn-group">
<view class="uni-datetime-picker-cancel" @click="tiggerTimePicker">
<text class="uni-datetime-picker-btn-text">{{cancelText}}</text>
</view>
<view @click="setTime">
<text class="uni-datetime-picker-btn-text">{{okText}}</text>
</view>
</view>
</view>
</view>
</view>
</template>
<script>
import {
initVueI18n
} from '@dcloudio/uni-i18n'
import i18nMessages from './i18n/index.js'
const {
t
} = initVueI18n(i18nMessages)
import {
fixIosDateFormat
} from './util'
/**
* DatetimePicker 时间选择器
* @description 可以同时选择日期和时间的选择器
* @tutorial https://ext.dcloud.net.cn/plugin?id=xxx
* @property {String} type = [datetime | date | time] 显示模式
* @property {Boolean} multiple = [true|false] 是否多选
* @property {String|Number} value 默认值
* @property {String|Number} start 起始日期或时间
* @property {String|Number} end 起始日期或时间
* @property {String} return-type = [timestamp | string]
* @event {Function} change 选中发生变化触发
*/
export default {
name: 'UniDatetimePicker',
data() {
return {
indicatorStyle: `height: 50px;`,
visible: false,
fixNvueBug: {},
dateShow: true,
timeShow: true,
title: '日期和时间',
// 输入框当前时间
time: '',
// 当前的年月日时分秒
year: 1920,
month: 0,
day: 0,
hour: 0,
minute: 0,
second: 0,
// 起始时间
startYear: 1920,
startMonth: 1,
startDay: 1,
startHour: 0,
startMinute: 0,
startSecond: 0,
// 结束时间
endYear: 2120,
endMonth: 12,
endDay: 31,
endHour: 23,
endMinute: 59,
endSecond: 59,
}
},
options: {
// #ifdef MP-TOUTIAO
virtualHost: false,
// #endif
// #ifndef MP-TOUTIAO
virtualHost: true
// #endif
},
props: {
type: {
type: String,
default: 'datetime'
},
value: {
type: [String, Number],
default: ''
},
modelValue: {
type: [String, Number],
default: ''
},
start: {
type: [Number, String],
default: ''
},
end: {
type: [Number, String],
default: ''
},
returnType: {
type: String,
default: 'string'
},
disabled: {
type: [Boolean, String],
default: false
},
border: {
type: [Boolean, String],
default: true
},
hideSecond: {
type: [Boolean, String],
default: false
}
},
watch: {
// #ifndef VUE3
value: {
handler(newVal) {
if (newVal) {
this.parseValue(fixIosDateFormat(newVal))
this.initTime(false)
} else {
this.time = ''
this.parseValue(Date.now())
}
},
immediate: true
},
// #endif
// #ifdef VUE3
modelValue: {
handler(newVal) {
if (newVal) {
this.parseValue(fixIosDateFormat(newVal))
this.initTime(false)
} else {
this.time = ''
this.parseValue(Date.now())
}
},
immediate: true
},
// #endif
type: {
handler(newValue) {
if (newValue === 'date') {
this.dateShow = true
this.timeShow = false
this.title = '日期'
} else if (newValue === 'time') {
this.dateShow = false
this.timeShow = true
this.title = '时间'
} else {
this.dateShow = true
this.timeShow = true
this.title = '日期和时间'
}
},
immediate: true
},
start: {
handler(newVal) {
this.parseDatetimeRange(fixIosDateFormat(newVal), 'start')
},
immediate: true
},
end: {
handler(newVal) {
this.parseDatetimeRange(fixIosDateFormat(newVal), 'end')
},
immediate: true
},
// 月、日、时、分、秒可选范围变化后,检查当前值是否在范围内,不在则当前值重置为可选范围第一项
months(newVal) {
this.checkValue('month', this.month, newVal)
},
days(newVal) {
this.checkValue('day', this.day, newVal)
},
hours(newVal) {
this.checkValue('hour', this.hour, newVal)
},
minutes(newVal) {
this.checkValue('minute', this.minute, newVal)
},
seconds(newVal) {
this.checkValue('second', this.second, newVal)
}
},
computed: {
// 当前年、月、日、时、分、秒选择范围
years() {
return this.getCurrentRange('year')
},
months() {
return this.getCurrentRange('month')
},
days() {
return this.getCurrentRange('day')
},
hours() {
return this.getCurrentRange('hour')
},
minutes() {
return this.getCurrentRange('minute')
},
seconds() {
return this.getCurrentRange('second')
},
// picker 当前值数组
ymd() {
return [this.year - this.minYear, this.month - this.minMonth, this.day - this.minDay]
},
hms() {
return [this.hour - this.minHour, this.minute - this.minMinute, this.second - this.minSecond]
},
// 当前 date 是 start
currentDateIsStart() {
return this.year === this.startYear && this.month === this.startMonth && this.day === this.startDay
},
// 当前 date 是 end
currentDateIsEnd() {
return this.year === this.endYear && this.month === this.endMonth && this.day === this.endDay
},
// 当前年、月、日、时、分、秒的最小值和最大值
minYear() {
return this.startYear
},
maxYear() {
return this.endYear
},
minMonth() {
if (this.year === this.startYear) {
return this.startMonth
} else {
return 1
}
},
maxMonth() {
if (this.year === this.endYear) {
return this.endMonth
} else {
return 12
}
},
minDay() {
if (this.year === this.startYear && this.month === this.startMonth) {
return this.startDay
} else {
return 1
}
},
maxDay() {
if (this.year === this.endYear && this.month === this.endMonth) {
return this.endDay
} else {
return this.daysInMonth(this.year, this.month)
}
},
minHour() {
if (this.type === 'datetime') {
if (this.currentDateIsStart) {
return this.startHour
} else {
return 0
}
}
if (this.type === 'time') {
return this.startHour
}
},
maxHour() {
if (this.type === 'datetime') {
if (this.currentDateIsEnd) {
return this.endHour
} else {
return 23
}
}
if (this.type === 'time') {
return this.endHour
}
},
minMinute() {
if (this.type === 'datetime') {
if (this.currentDateIsStart && this.hour === this.startHour) {
return this.startMinute
} else {
return 0
}
}
if (this.type === 'time') {
if (this.hour === this.startHour) {
return this.startMinute
} else {
return 0
}
}
},
maxMinute() {
if (this.type === 'datetime') {
if (this.currentDateIsEnd && this.hour === this.endHour) {
return this.endMinute
} else {
return 59
}
}
if (this.type === 'time') {
if (this.hour === this.endHour) {
return this.endMinute
} else {
return 59
}
}
},
minSecond() {
if (this.type === 'datetime') {
if (this.currentDateIsStart && this.hour === this.startHour && this.minute === this.startMinute) {
return this.startSecond
} else {
return 0
}
}
if (this.type === 'time') {
if (this.hour === this.startHour && this.minute === this.startMinute) {
return this.startSecond
} else {
return 0
}
}
},
maxSecond() {
if (this.type === 'datetime') {
if (this.currentDateIsEnd && this.hour === this.endHour && this.minute === this.endMinute) {
return this.endSecond
} else {
return 59
}
}
if (this.type === 'time') {
if (this.hour === this.endHour && this.minute === this.endMinute) {
return this.endSecond
} else {
return 59
}
}
},
/**
* for i18n
*/
selectTimeText() {
return t("uni-datetime-picker.selectTime")
},
okText() {
return t("uni-datetime-picker.ok")
},
clearText() {
return t("uni-datetime-picker.clear")
},
cancelText() {
return t("uni-datetime-picker.cancel")
}
},
mounted() {
// #ifdef APP-NVUE
const res = uni.getSystemInfoSync();
this.fixNvueBug = {
top: res.windowHeight / 2,
left: res.windowWidth / 2
}
// #endif
},
methods: {
/**
* @param {Object} item
* 小于 10 在前面加个 0
*/
lessThanTen(item) {
return item < 10 ? '0' + item : item
},
/**
* 解析时分秒字符串,例如:00:00:00
* @param {String} timeString
*/
parseTimeType(timeString) {
if (timeString) {
let timeArr = timeString.split(':')
this.hour = Number(timeArr[0])
this.minute = Number(timeArr[1])
this.second = Number(timeArr[2])
}
},
/**
* 解析选择器初始值,类型可以是字符串、时间戳,例如:2000-10-02、'08:30:00'、 1610695109000
* @param {String | Number} datetime
*/
initPickerValue(datetime) {
let defaultValue = null
if (datetime) {
defaultValue = this.compareValueWithStartAndEnd(datetime, this.start, this.end)
} else {
defaultValue = Date.now()
defaultValue = this.compareValueWithStartAndEnd(defaultValue, this.start, this.end)
}
this.parseValue(defaultValue)
},
/**
* 初始值规则:
* - 用户设置初始值 value
* - 设置了起始时间 start、终止时间 end,并 start < value < end,初始值为 value, 否则初始值为 start
* - 只设置了起始时间 start,并 start < value,初始值为 value,否则初始值为 start
* - 只设置了终止时间 end,并 value < end,初始值为 value,否则初始值为 end
* - 无起始终止时间,则初始值为 value
* - 无初始值 value,则初始值为当前本地时间 Date.now()
* @param {Object} value
* @param {Object} dateBase
*/
compareValueWithStartAndEnd(value, start, end) {
let winner = null
value = this.superTimeStamp(value)
start = this.superTimeStamp(start)
end = this.superTimeStamp(end)
if (start && end) {
if (value < start) {
winner = new Date(start)
} else if (value > end) {
winner = new Date(end)
} else {
winner = new Date(value)
}
} else if (start && !end) {
winner = start <= value ? new Date(value) : new Date(start)
} else if (!start && end) {
winner = value <= end ? new Date(value) : new Date(end)
} else {
winner = new Date(value)
}
return winner
},
/**
* 转换为可比较的时间戳,接受日期、时分秒、时间戳
* @param {Object} value
*/
superTimeStamp(value) {
let dateBase = ''
if (this.type === 'time' && value && typeof value === 'string') {
const now = new Date()
const year = now.getFullYear()
const month = now.getMonth() + 1
const day = now.getDate()
dateBase = year + '/' + month + '/' + day + ' '
}
if (Number(value)) {
value = parseInt(value)
dateBase = 0
}
return this.createTimeStamp(dateBase + value)
},
/**
* 解析默认值 value,字符串、时间戳
* @param {Object} defaultTime
*/
parseValue(value) {
if (!value) {
return
}
if (this.type === 'time' && typeof value === "string") {
this.parseTimeType(value)
} else {
let defaultDate = null
defaultDate = new Date(value)
if (this.type !== 'time') {
this.year = defaultDate.getFullYear()
this.month = defaultDate.getMonth() + 1
this.day = defaultDate.getDate()
}
if (this.type !== 'date') {
this.hour = defaultDate.getHours()
this.minute = defaultDate.getMinutes()
this.second = defaultDate.getSeconds()
}
}
if (this.hideSecond) {
this.second = 0
}
},
/**
* 解析可选择时间范围 start、end,年月日字符串、时间戳
* @param {Object} defaultTime
*/
parseDatetimeRange(point, pointType) {
// 时间为空,则重置为初始值
if (!point) {
if (pointType === 'start') {
this.startYear = 1920
this.startMonth = 1
this.startDay = 1
this.startHour = 0
this.startMinute = 0
this.startSecond = 0
}
if (pointType === 'end') {
this.endYear = 2120
this.endMonth = 12
this.endDay = 31
this.endHour = 23
this.endMinute = 59
this.endSecond = 59
}
return
}
if (this.type === 'time') {
const pointArr = point.split(':')
this[pointType + 'Hour'] = Number(pointArr[0])
this[pointType + 'Minute'] = Number(pointArr[1])
this[pointType + 'Second'] = Number(pointArr[2])
} else {
if (!point) {
pointType === 'start' ? this.startYear = this.year - 60 : this.endYear = this.year + 60
return
}
if (Number(point)) {
point = parseInt(point)
}
// datetime 的 end 没有时分秒, 则不限制
const hasTime = /[0-9]:[0-9]/
if (this.type === 'datetime' && pointType === 'end' && typeof point === 'string' && !hasTime.test(
point)) {
point = point + ' 23:59:59'
}
const pointDate = new Date(point)
this[pointType + 'Year'] = pointDate.getFullYear()
this[pointType + 'Month'] = pointDate.getMonth() + 1
this[pointType + 'Day'] = pointDate.getDate()
if (this.type === 'datetime') {
this[pointType + 'Hour'] = pointDate.getHours()
this[pointType + 'Minute'] = pointDate.getMinutes()
this[pointType + 'Second'] = pointDate.getSeconds()
}
}
},
// 获取 年、月、日、时、分、秒 当前可选范围
getCurrentRange(value) {
const range = []
for (let i = this['min' + this.capitalize(value)]; i <= this['max' + this.capitalize(value)]; i++) {
range.push(i)
}
return range
},
// 字符串首字母大写
capitalize(str) {
return str.charAt(0).toUpperCase() + str.slice(1)
},
// 检查当前值是否在范围内,不在则当前值重置为可选范围第一项
checkValue(name, value, values) {
if (values.indexOf(value) === -1) {
this[name] = values[0]
}
},
// 每个月的实际天数
daysInMonth(year, month) { // Use 1 for January, 2 for February, etc.
return new Date(year, month, 0).getDate();
},
/**
* 生成时间戳
* @param {Object} time
*/
createTimeStamp(time) {
if (!time) return
if (typeof time === "number") {
return time
} else {
time = time.replace(/-/g, '/')
if (this.type === 'date') {
time = time + ' ' + '00:00:00'
}
return Date.parse(time)
}
},
/**
* 生成日期或时间的字符串
*/
createDomSting() {
const yymmdd = this.year +
'-' +
this.lessThanTen(this.month) +
'-' +
this.lessThanTen(this.day)
let hhmmss = this.lessThanTen(this.hour) +
':' +
this.lessThanTen(this.minute)
if (!this.hideSecond) {
hhmmss = hhmmss + ':' + this.lessThanTen(this.second)
}
if (this.type === 'date') {
return yymmdd
} else if (this.type === 'time') {
return hhmmss
} else {
return yymmdd + ' ' + hhmmss
}
},
/**
* 初始化返回值,并抛出 change 事件
*/
initTime(emit = true) {
this.time = this.createDomSting()
if (!emit) return
if (this.returnType === 'timestamp' && this.type !== 'time') {
this.$emit('change', this.createTimeStamp(this.time))
this.$emit('input', this.createTimeStamp(this.time))
this.$emit('update:modelValue', this.createTimeStamp(this.time))
} else {
this.$emit('change', this.time)
this.$emit('input', this.time)
this.$emit('update:modelValue', this.time)
}
},
/**
* 用户选择日期或时间更新 data
* @param {Object} e
*/
bindDateChange(e) {
const val = e.detail.value
this.year = this.years[val[0]]
this.month = this.months[val[1]]
this.day = this.days[val[2]]
},
bindTimeChange(e) {
const val = e.detail.value
this.hour = this.hours[val[0]]
this.minute = this.minutes[val[1]]
this.second = this.seconds[val[2]]
},
/**
* 初始化弹出层
*/
initTimePicker() {
if (this.disabled) return
const value = fixIosDateFormat(this.time)
this.initPickerValue(value)
this.visible = !this.visible
},
/**
* 触发或关闭弹框
*/
tiggerTimePicker(e) {
this.visible = !this.visible
},
/**
* 用户点击“清空”按钮,清空当前值
*/
clearTime() {
this.time = ''
this.$emit('change', this.time)
this.$emit('input', this.time)
this.$emit('update:modelValue', this.time)
this.tiggerTimePicker()
},
/**
* 用户点击“确定”按钮
*/
setTime() {
this.initTime()
this.tiggerTimePicker()
}
}
}
</script>
<style lang="scss">
$uni-primary: #007aff !default;
.uni-datetime-picker {
/* #ifndef APP-NVUE */
/* width: 100%; */
/* #endif */
}
.uni-datetime-picker-view {
height: 130px;
width: 270px;
/* #ifndef APP-NVUE */
cursor: pointer;
/* #endif */
}
.uni-datetime-picker-item {
height: 50px;
line-height: 50px;
text-align: center;
font-size: 14px;
}
.uni-datetime-picker-btn {
margin-top: 60px;
/* #ifndef APP-NVUE */
display: flex;
cursor: pointer;
/* #endif */
flex-direction: row;
justify-content: space-between;
}
.uni-datetime-picker-btn-text {
font-size: 14px;
color: $uni-primary;
}
.uni-datetime-picker-btn-group {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: row;
}
.uni-datetime-picker-cancel {
margin-right: 30px;
}
.uni-datetime-picker-mask {
position: fixed;
bottom: 0px;
top: 0px;
left: 0px;
right: 0px;
background-color: rgba(0, 0, 0, 0.4);
transition-duration: 0.3s;
z-index: 998;
}
.uni-datetime-picker-popup {
border-radius: 8px;
padding: 30px;
width: 270px;
/* #ifdef APP-NVUE */
height: 500px;
/* #endif */
/* #ifdef APP-NVUE */
width: 330px;
/* #endif */
background-color: #fff;
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
transition-duration: 0.3s;
z-index: 999;
}
.fix-nvue-height {
/* #ifdef APP-NVUE */
height: 330px;
/* #endif */
}
.uni-datetime-picker-time {
color: grey;
}
.uni-datetime-picker-column {
height: 50px;
}
.uni-datetime-picker-timebox {
border: 1px solid #E5E5E5;
border-radius: 5px;
padding: 7px 10px;
/* #ifndef APP-NVUE */
box-sizing: border-box;
cursor: pointer;
/* #endif */
}
.uni-datetime-picker-timebox-pointer {
/* #ifndef APP-NVUE */
cursor: pointer;
/* #endif */
}
.uni-datetime-picker-disabled {
opacity: 0.4;
/* #ifdef H5 */
cursor: not-allowed !important;
/* #endif */
}
.uni-datetime-picker-text {
font-size: 14px;
line-height: 50px
}
.uni-datetime-picker-sign {
position: absolute;
top: 53px;
/* 减掉 10px 的元素高度,兼容nvue */
color: #999;
/* #ifdef APP-NVUE */
font-size: 16px;
/* #endif */
}
.sign-left {
left: 86px;
}
.sign-right {
right: 86px;
}
.sign-center {
left: 135px;
}
.uni-datetime-picker__container-box {
position: relative;
display: flex;
align-items: center;
justify-content: center;
margin-top: 40px;
}
.time-hide-second {
width: 180px;
}
</style>
@@ -0,0 +1,421 @@
class Calendar {
constructor({
selected,
startDate,
endDate,
range,
} = {}) {
// 当前日期
this.date = this.getDateObj(new Date()) // 当前初入日期
// 打点信息
this.selected = selected || [];
// 起始时间
this.startDate = startDate
// 终止时间
this.endDate = endDate
// 是否范围选择
this.range = range
// 多选状态
this.cleanMultipleStatus()
// 每周日期
this.weeks = {}
this.lastHover = false
}
/**
* 设置日期
* @param {Object} date
*/
setDate(date) {
const selectDate = this.getDateObj(date)
this.getWeeks(selectDate.fullDate)
}
/**
* 清理多选状态
*/
cleanMultipleStatus() {
this.multipleStatus = {
before: '',
after: '',
data: []
}
}
setStartDate(startDate) {
this.startDate = startDate
}
setEndDate(endDate) {
this.endDate = endDate
}
getPreMonthObj(date) {
date = fixIosDateFormat(date)
date = new Date(date)
const oldMonth = date.getMonth()
date.setMonth(oldMonth - 1)
const newMonth = date.getMonth()
if (oldMonth !== 0 && newMonth - oldMonth === 0) {
date.setMonth(newMonth - 1)
}
return this.getDateObj(date)
}
getNextMonthObj(date) {
date = fixIosDateFormat(date)
date = new Date(date)
const oldMonth = date.getMonth()
date.setMonth(oldMonth + 1)
const newMonth = date.getMonth()
if (newMonth - oldMonth > 1) {
date.setMonth(newMonth - 1)
}
return this.getDateObj(date)
}
/**
* 获取指定格式Date对象
*/
getDateObj(date) {
date = fixIosDateFormat(date)
date = new Date(date)
return {
fullDate: getDate(date),
year: date.getFullYear(),
month: addZero(date.getMonth() + 1),
date: addZero(date.getDate()),
day: date.getDay()
}
}
/**
* 获取上一个月日期集合
*/
getPreMonthDays(amount, dateObj) {
const result = []
for (let i = amount - 1; i >= 0; i--) {
const month = dateObj.month - 1
result.push({
date: new Date(dateObj.year, month, -i).getDate(),
month,
disable: true
})
}
return result
}
/**
* 获取本月日期集合
*/
getCurrentMonthDays(amount, dateObj) {
const result = []
const fullDate = this.date.fullDate
for (let i = 1; i <= amount; i++) {
const currentDate = `${dateObj.year}-${dateObj.month}-${addZero(i)}`
const isToday = fullDate === currentDate
// 获取打点信息
const info = this.selected && this.selected.find((item) => {
if (this.dateEqual(currentDate, item.date)) {
return item
}
})
// 日期禁用
let disableBefore = true
let disableAfter = true
if (this.startDate) {
disableBefore = dateCompare(this.startDate, currentDate)
}
if (this.endDate) {
disableAfter = dateCompare(currentDate, this.endDate)
}
let multiples = this.multipleStatus.data
let multiplesStatus = -1
if (this.range && multiples) {
multiplesStatus = multiples.findIndex((item) => {
return this.dateEqual(item, currentDate)
})
}
const checked = multiplesStatus !== -1
result.push({
fullDate: currentDate,
year: dateObj.year,
date: i,
multiple: this.range ? checked : false,
beforeMultiple: this.isLogicBefore(currentDate, this.multipleStatus.before, this.multipleStatus.after),
afterMultiple: this.isLogicAfter(currentDate, this.multipleStatus.before, this.multipleStatus.after),
month: dateObj.month,
disable: (this.startDate && !dateCompare(this.startDate, currentDate)) || (this.endDate && !dateCompare(
currentDate, this.endDate)),
isToday,
userChecked: false,
extraInfo: info
})
}
return result
}
/**
* 获取下一个月日期集合
*/
_getNextMonthDays(amount, dateObj) {
const result = []
const month = dateObj.month + 1
for (let i = 1; i <= amount; i++) {
result.push({
date: i,
month,
disable: true
})
}
return result
}
/**
* 获取当前日期详情
* @param {Object} date
*/
getInfo(date) {
if (!date) {
date = new Date()
}
const res = this.calendar.find(item => item.fullDate === this.getDateObj(date).fullDate)
return res ? res : this.getDateObj(date)
}
/**
* 比较时间是否相等
*/
dateEqual(before, after) {
before = new Date(fixIosDateFormat(before))
after = new Date(fixIosDateFormat(after))
return before.valueOf() === after.valueOf()
}
/**
* 比较真实起始日期
*/
isLogicBefore(currentDate, before, after) {
let logicBefore = before
if (before && after) {
logicBefore = dateCompare(before, after) ? before : after
}
return this.dateEqual(logicBefore, currentDate)
}
isLogicAfter(currentDate, before, after) {
let logicAfter = after
if (before && after) {
logicAfter = dateCompare(before, after) ? after : before
}
return this.dateEqual(logicAfter, currentDate)
}
/**
* 获取日期范围内所有日期
* @param {Object} begin
* @param {Object} end
*/
geDateAll(begin, end) {
var arr = []
var ab = begin.split('-')
var ae = end.split('-')
var db = new Date()
db.setFullYear(ab[0], ab[1] - 1, ab[2])
var de = new Date()
de.setFullYear(ae[0], ae[1] - 1, ae[2])
var unixDb = db.getTime() - 24 * 60 * 60 * 1000
var unixDe = de.getTime() - 24 * 60 * 60 * 1000
for (var k = unixDb; k <= unixDe;) {
k = k + 24 * 60 * 60 * 1000
arr.push(this.getDateObj(new Date(parseInt(k))).fullDate)
}
return arr
}
/**
* 获取多选状态
*/
setMultiple(fullDate) {
if (!this.range) return
let {
before,
after
} = this.multipleStatus
if (before && after) {
if (!this.lastHover) {
this.lastHover = true
return
}
this.multipleStatus.before = fullDate
this.multipleStatus.after = ''
this.multipleStatus.data = []
this.multipleStatus.fulldate = ''
this.lastHover = false
} else {
if (!before) {
this.multipleStatus.before = fullDate
this.multipleStatus.after = undefined;
this.lastHover = false
} else {
this.multipleStatus.after = fullDate
if (dateCompare(this.multipleStatus.before, this.multipleStatus.after)) {
this.multipleStatus.data = this.geDateAll(this.multipleStatus.before, this.multipleStatus
.after);
} else {
this.multipleStatus.data = this.geDateAll(this.multipleStatus.after, this.multipleStatus
.before);
}
this.lastHover = true
}
}
this.getWeeks(fullDate)
}
/**
* 鼠标 hover 更新多选状态
*/
setHoverMultiple(fullDate) {
//抖音小程序点击会触发hover事件,需要避免一下
// #ifndef MP-TOUTIAO
if (!this.range || this.lastHover) return
const {
before
} = this.multipleStatus
if (!before) {
this.multipleStatus.before = fullDate
} else {
this.multipleStatus.after = fullDate
if (dateCompare(this.multipleStatus.before, this.multipleStatus.after)) {
this.multipleStatus.data = this.geDateAll(this.multipleStatus.before, this.multipleStatus.after);
} else {
this.multipleStatus.data = this.geDateAll(this.multipleStatus.after, this.multipleStatus.before);
}
}
this.getWeeks(fullDate)
// #endif
}
/**
* 更新默认值多选状态
*/
setDefaultMultiple(before, after) {
this.multipleStatus.before = before
this.multipleStatus.after = after
if (before && after) {
if (dateCompare(before, after)) {
this.multipleStatus.data = this.geDateAll(before, after);
this.getWeeks(after)
} else {
this.multipleStatus.data = this.geDateAll(after, before);
this.getWeeks(before)
}
}
}
/**
* 获取每周数据
* @param {Object} dateData
*/
getWeeks(dateData) {
const {
year,
month,
} = this.getDateObj(dateData)
const preMonthDayAmount = new Date(year, month - 1, 1).getDay()
const preMonthDays = this.getPreMonthDays(preMonthDayAmount, this.getDateObj(dateData))
const currentMonthDayAmount = new Date(year, month, 0).getDate()
const currentMonthDays = this.getCurrentMonthDays(currentMonthDayAmount, this.getDateObj(dateData))
const nextMonthDayAmount = 42 - preMonthDayAmount - currentMonthDayAmount
const nextMonthDays = this._getNextMonthDays(nextMonthDayAmount, this.getDateObj(dateData))
const calendarDays = [...preMonthDays, ...currentMonthDays, ...nextMonthDays]
const weeks = new Array(6)
for (let i = 0; i < calendarDays.length; i++) {
const index = Math.floor(i / 7)
if (!weeks[index]) {
weeks[index] = new Array(7)
}
weeks[index][i % 7] = calendarDays[i]
}
this.calendar = calendarDays
this.weeks = weeks
}
}
function getDateTime(date, hideSecond) {
return `${getDate(date)} ${getTime(date, hideSecond)}`
}
function getDate(date) {
date = fixIosDateFormat(date)
date = new Date(date)
const year = date.getFullYear()
const month = date.getMonth() + 1
const day = date.getDate()
return `${year}-${addZero(month)}-${addZero(day)}`
}
function getTime(date, hideSecond) {
date = fixIosDateFormat(date)
date = new Date(date)
const hour = date.getHours()
const minute = date.getMinutes()
const second = date.getSeconds()
return hideSecond ? `${addZero(hour)}:${addZero(minute)}` : `${addZero(hour)}:${addZero(minute)}:${addZero(second)}`
}
function addZero(num) {
if (num < 10) {
num = `0${num}`
}
return num
}
function getDefaultSecond(hideSecond) {
return hideSecond ? '00:00' : '00:00:00'
}
function dateCompare(startDate, endDate) {
startDate = new Date(fixIosDateFormat(typeof startDate === 'string' ? startDate.trim() : startDate))
endDate = new Date(fixIosDateFormat(typeof endDate === 'string' ? endDate.trim() : endDate))
return startDate <= endDate
}
function checkDate(date) {
const dateReg = /((19|20)\d{2})(-|\/)\d{1,2}(-|\/)\d{1,2}/g
return date.match(dateReg)
}
//ios低版本15及以下,无法匹配 没有 ’秒‘ 时的情况,所以需要在末尾 秒 加上 问号
const dateTimeReg = /^\d{4}-(0?[1-9]|1[012])-(0?[1-9]|[12][0-9]|3[01])( [0-5]?[0-9]:[0-5]?[0-9](:[0-5]?[0-9])?)?$/;
function fixIosDateFormat(value) {
if (typeof value === 'string' && dateTimeReg.test(value)) {
value = value.replace(/-/g, '/')
}
return value
}
export {
Calendar,
getDateTime,
getDate,
getTime,
addZero,
getDefaultSecond,
dateCompare,
checkDate,
fixIosDateFormat
}
@@ -0,0 +1,107 @@
{
"id": "uni-datetime-picker",
"displayName": "uni-datetime-picker 日期选择器",
"version": "2.2.42",
"description": "uni-datetime-picker 日期时间选择器,支持日历,支持范围选择",
"keywords": [
"uni-datetime-picker",
"uni-ui",
"uniui",
"日期时间选择器",
"日期时间"
],
"repository": "https://github.com/dcloudio/uni-ui",
"engines": {
"HBuilderX": "",
"uni-app": "^4.07",
"uni-app-x": ""
},
"directories": {
"example": "../../temps/example_temps"
},
"dcloudext": {
"sale": {
"regular": {
"price": "0.00"
},
"sourcecode": {
"price": "0.00"
}
},
"contact": {
"qq": ""
},
"declaration": {
"ads": "无",
"data": "无",
"permissions": "无"
},
"npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui",
"type": "component-vue",
"darkmode": "x",
"i18n": "x",
"widescreen": "x"
},
"uni_modules": {
"dependencies": [
"uni-scss",
"uni-icons"
],
"encrypt": [],
"platforms": {
"cloud": {
"tcb": "x",
"aliyun": "x",
"alipay": "x"
},
"client": {
"uni-app": {
"vue": {
"vue2": "√",
"vue3": "√"
},
"web": {
"safari": "√",
"chrome": "√"
},
"app": {
"vue": "√",
"nvue": "√",
"android": "√",
"ios": "√",
"harmony": "√"
},
"mp": {
"weixin": "√",
"alipay": "√",
"toutiao": "√",
"baidu": "√",
"kuaishou": "-",
"jd": "-",
"harmony": "-",
"qq": "√",
"lark": "-"
},
"quickapp": {
"huawei": "√",
"union": "√"
}
},
"uni-app-x": {
"web": {
"safari": "-",
"chrome": "-"
},
"app": {
"android": "-",
"ios": "-",
"harmony": "-"
},
"mp": {
"weixin": "-"
}
}
}
}
}
}
+21
View File
@@ -0,0 +1,21 @@
> `重要通知:组件升级更新 2.0.0 后,支持日期+时间范围选择,组件 ui 将使用日历选择日期,ui 变化较大,同时支持 PC 和 移动端。此版本不向后兼容,不再支持单独的时间选择(type=time)及相关的 hide-second 属性(时间选可使用内置组件 picker)。若仍需使用旧版本,可在插件市场下载*非uni_modules版本*,旧版本将不再维护`
## DatetimePicker 时间选择器
> **组件名:uni-datetime-picker**
> 代码块: `uDatetimePicker`
该组件的优势是,支持**时间戳**输入和输出(起始时间、终止时间也支持时间戳),可**同时选择**日期和时间。
若只是需要单独选择日期和时间,不需要时间戳输入和输出,可使用原生的 picker 组件。
**_点击 picker 默认值规则:_**
- 若设置初始值 value, 会显示在 picker 显示框中
- 若无初始值 value,则初始值 value 为当前本地时间 Date.now(), 但不会显示在 picker 显示框中
### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-datetime-picker)
#### 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839
@@ -42,7 +42,7 @@
:shape="item.badge && item.badge.shape || propsBadge.shape" :shape="item.badge && item.badge.shape || propsBadge.shape"
:numberType="item.badge && item.badge.numberType || propsBadge.numberType" :numberType="item.badge && item.badge.numberType || propsBadge.numberType"
:inverted="item.badge && item.badge.inverted || propsBadge.inverted" :inverted="item.badge && item.badge.inverted || propsBadge.inverted"
customStyle="margin-left: 4px;" :customStyle="item.badge && item.badge.customStyle || 'margin-left: 4px;'"
></u-badge> ></u-badge>
</view> </view>
<!-- #ifdef APP-NVUE --> <!-- #ifdef APP-NVUE -->