// ==UserScript==
// @name 米尼优家辅助
// @namespace https://scriptcat.org/
// @description 连通多域页面,简化操作
// @version 0.1.9.5
// @author EmpyrealTear
// @icon data:image/x-icon;base64,AAABAAEAEBAAAAEAIABoBAAAFgAAACgAAAAQAAAAIAAAAAEAIAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAD///8A////AP///wD///8A4OHhH4WGiIVISEzJIyQo8CMkKPBISEzJhYaIheDh4R////8A////AP///wD///8A////AP///wD///8AqKinZTM0MPMnKCX/KSom/ykqJv8pKib/KSom/ykqJv8xMy/zqKinZf///wD///8A////AP///wD8/PwBaWlnsScoJf8nKCP/NTQu/yYoJf8lJiT/Jygl/ycoJf8lJSL/MDAr/ysrKP9paWex/Pz8Af///wD///8Ah4iGjScoJf8mJyT/LjI0/2lfTf9aU0X/Kywo/yIkIv8mJB7/Nzk3/1NPRf8oKSb/Jygl/4eIho3///8A39/fJCwtKvgnKCX/JiQe/y5CXf9AXIP/ZV1N/2pfS/89OCz/Okld/0xpkf9IRDr/IyUi/ycoJf8oKSb939/fJISEgpEnKCX/Jygl/ycmIv8qMDT/PG+4/zhgmv9TV1r/VGuK/z52xv9LYHv/NTEm/yUmI/8nKCX/Jygl/4SEgpFRUk/NJygl/ycoJf8nKCX/JSMd/zhbjP9Becr/O2+4/z50wf8/cLf/XVxX/zMxKv8iJCL/Jygl/ycoJf9RUk/NNzg26icoJf8nKCX/JSUh/ywoH/8+XIX/QXbD/0B0vv9BdcD/PGms/1pXUf9uYk3/Pjw0/yQlI/8nKCX/Nzg26jc4NuonKCX/JiUg/zEzMv9DYo7/QnfD/0B0vv9Ac7z/QHO9/0B1wv84ZaX/TlVd/3NnUv9OSj//JSYk/zc4NupRUk/NJiYi/ysyOP8+ZJn/P3G6/z5tsP8/cbj/QHS9/0BzvP9CcbT/P3C3/zdnq/9CTl//Qz80/yYnJP9RUk/NhISCkScoJP8oLC3/KS81/yktL/8nKSf/Mktq/0F5yv9IbqP/SklD/yUqLP8qLzL/Jiww/yQlI/8nKCX/hISCkd/f3yQsLSr4Jyck/ycnIv8nJyL/JiUe/yoxNv89cbv/TmOB/zk0J/8kJCD/Jyci/ycnI/8nKCX/LC0q+N/f3yT///8Ah4iGjScoJf8nKCX/Jygl/ycoJf8mJiH/Ol+T/09VWv8pKCL/Jygl/ycoJf8nKCX/Jygl/4eIho3///8A////APz8/AFqa2muJygl/ycoJf8nKCX/JiYh/y89Tv8vMjL/Jicj/ycoJf8nKCX/Jygl/2praa78/PwB////AP///wD///8A////AKiop2UyMzDzKCkm/ygpJv8nJyL/Jygk/ygpJv8oKSb/MjMw86iop2X///8A////AP///wD///8A////AP///wD///8A3+DhH4GChoVBQkjJHBwj8BwcJPBBQkjJgYKGhd/g4R////8A////AP///wD///8A+B8AAPAPAADAAwAAgAEAAIABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAQAAgAEAAMADAADwDwAA+B8AAA==
// @match *://mn.beyondh.com:8101/*
// @connect mn.beyondh.com
// @match *://mn.bak3.beyondh.com:8101/*
// @connect mn.bak3.beyondh.com
// @match *://jx.hdtyhotel.com:8000/*
// @connect jx.hdtyhotel.com
// @match *://ebooking.ctrip.com/*
// @connect ebooking.ctrip.com
// @match *://me.meituan.com/*
// @connect me.meituan.com
// @require https://scriptcat.org/lib/1167/1.0.0/脚本猫UI库.js
// @require https://scriptcat.org/lib/513/2.1.0/ElementGetter.js
// @require https://scriptcat.org/lib/2628/6.2.0.1/moduleRaid.js
// @require https://cdn.bootcdn.net/ajax/libs/jquery/3.7.1/jquery.min.js
// @require https://cdn.bootcdn.net/ajax/libs/crypto-js/4.2.0/crypto-js.min.js
// @require https://unpkg.com/sm-crypto@0.3.9/dist/sm4.js
// @require https://bd-s.tripcdn.cn/modules/hotel/hotel-spider-defence-new/sdt.1001-common.min.9523a027cd7f61152c429eae5b56b319.js
// @grant unsafeWindow
// @grant GM_xmlhttpRequest
// @grant GM_setClipboard
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_openInTab
// @grant GM_addStyle
// @grant GM_info
// @grant GM_registerMenuCommand
// @grant GM_unregisterMenuCommand
// @grant GM_cookie
// @grant GM_notification
// @noframes
// ==/UserScript==
/* ==UserConfig==
别样红:
freePrice:
title: 钻卡免费房价
description: 用于确定钻卡免费房房价是否填写正确
type: number
default: 149
pageCount:
title: 缓存页数
description: 别样红房态点击刷新时自动缓存的最近住客页数
type: number
default: 1
idCardCacheExpires:
title: 缓存证件信息保留天数
description: 原件或电子证件办理入住时自动缓存信息,并记录最后的记载时间
type: number
default: 3
preventAbnormalBilling:
title: 阻止异常结账
type: checkbox
default: true
redactDetail:
title: 脱敏住客信息
type: checkbox
default: true
allowUploadFaceImg:
title: 允许上传证件照
type: checkbox
default: true
showFaceImg:
title: 展示住客证件照
type: checkbox
default: false
showCleanStatu:
title: 展示已打扫标记
type: checkbox
default: true
showDetailContractName:
title: 展示中介明细
type: checkbox
default: false
公安网:
roomWidth:
title: 房间宽度
description: 房态界面单个房间的宽度,根据自己需要调整,小于或等于0则默认不调整
type: number
default: -1
floorBreak:
title: 房间分楼层换行
type: checkbox
default: true
compareMethod:
title: 对比模式
type: select
default: 住客信息
values: [住客信息,房间人数]
hightlightMethod:
title: 高亮模式
type: select
default: 同色高亮
values: [不高亮,同色高亮,异色高亮]
includesGw:
title: 包含国外
type: checkbox
default: false
includesGat:
title: 包含港澳台
type: checkbox
default: false
美团:
displaySalePrice:
title: 展示卖价
type: checkbox
default: true
携程:
displaySalePrice:
title: 展示卖价
type: checkbox
default: true
enableNotify:
title: 启用气泡通知
type: checkbox
default: false
lastPriceChange:
title: 最后一次调价id
type: text
==/UserConfig== */
const ctripSignature = window.signature
delete window.signature
Date.prototype.Format = function (fmt) {
var o = {
'M+': this.getMonth() + 1, //月份
'd+': this.getDate(), //日
'h+': this.getHours(), //小时
'm+': this.getMinutes(), //分
's+': this.getSeconds(), //秒
'q+': Math.floor((this.getMonth() + 3) / 3), //季度
S: this.getMilliseconds(), //毫秒
}
if (/(y+)/.test(fmt))
fmt = fmt.replace(RegExp["$1"], (this.getFullYear() + '').substr(4 - RegExp.$1.length))
for (var k in o)
if (new RegExp('(' + k + ')').test(fmt))
fmt = fmt.replace(RegExp["$1"], RegExp["$1"].length == 1 ? o[k] : ('00' + o[k]).substr(('' + o[k]).length))
return fmt
}
Date.prototype.AddDays = function (days) { return new Date(this.getTime() + days * 24 * 60 * 60 * 1000) }
Date.prototype.AddMinutes = function (minutes) { return new Date(this.getTime() + minutes * 60 * 1000) }
Date.prototype.AddMonths = function (m) { return this.setMonth(this.getMonth() + m) }
class DataStore {
constructor(key, data) {
this.key = key
this.defaultData = data
if (!GM_getValue(key))
GM_setValue(key, data)
}
get value() { return GM_getValue(this.key, this.defaultData) }
set value(val) { GM_setValue(this.key, val) }
Listener(resolve, timeout = 1000) {
setInterval(() => resolve(this), timeout)
return this
}
}
class IDCardCache extends DataStore {
constructor() { super('idcard_cache', []) }
async readCard(verifyCode) {
if (!/^\d{6}$/.test(verifyCode)) return
try {
const { data } = await apis.hdtyhotel.getEID(verifyCode)
if (data?.data) {
CAT_UI.Message.success("查询成功")
this.push(data.data)
}
} catch (error) {
CAT_UI.Message.info(error.data?.msg || "查询失败")
}
}
push(idcard) {
this.value = this.value.some(v => v.cardnum === idcard.cardnum)
? this.value.map(v => v.cardnum === idcard.cardnum
? { ...idcard, base64: idcard.facenum ? idcard.base64 : v.base64, date: new Date() } : v)
: [...this.value, { ...idcard, date: new Date() }]
return this.value.length
}
remove(idCardNumber) {
this.value = this.value.filter(v => v.cardnum !== idCardNumber)
return this.value.length
}
removeExpire() {
let expireDay = GM_getValue('别样红.idCardCacheExpires')
this.value = this.value.filter(v => new Date() <= new Date(v.date).AddDays(expireDay))
}
sort(compareFn) {
const defaultCompare = (a, b) => new Date(b.date) - new Date(a.date)
return this.value.sort(typeof compareFn === 'function' ? compareFn : defaultCompare)
}
asBeyondhCustomers(compareFn, withOrigin = false) {
return this.sort(compareFn).map(v => IDCardCache.asBeyondhCustomer(v, withOrigin))
}
static asBeyondhCustomer(idcard, withOrigin = false) {
let customer = apis.beyondh.GetBaseInfoFromIdCard({
Name: idcard.name,
IdCardNumber: idcard.cardnum ?? idcard.cardId,
Address: idcard.address,
PersonalCredentialType: 'C01',
Race: `R${idcard.nation}`,
...(idcard.base64 ? { PhotoStatus: true, Photo: idcard.base64, PhotoUrl: `data:image/jpeg;base64,${idcard.base64}` }
: idcard.personPhotoDTOList ? { PhotoStatus: true, Photo: idcard.personPhotoDTOList?.[0]?.imgBase64, PhotoUrl: `data:image/jpeg;base64,${idcard.personPhotoDTOList?.[0]?.imgBase64}` }
: {}),
Mobile: '',
InputTime: new Date(idcard.date)
})
if (withOrigin)
customer._origin = idcard
return customer
}
}
const options = {
menus: {
highlightBalancenePrice: { toStr: (x) => '[别样红] 高亮余额≠房价:' + (x ? '✓' : '✕') },
disableCreditType: { toStr: (x) => '[别样红] 仅显示AR账快钱:' + (x ? '✓' : '✕') },
},
loads() {
Object.keys(this.menus).forEach(k => {
let val = GM_getValue(k)
this.menus[k]['_menu'] = GM_registerMenuCommand(this.menus[k].toStr(val), () => {
GM_setValue(k, !val)
Object.keys(this.menus).forEach(iv => GM_unregisterMenuCommand(this.menus[iv]['_menu']))
this.loads()
})
})
}
}
const ajax = {
GET(url, isAsync = false) {
let res = null
$.ajax({ url: url, type: 'GET', xhrFields: { withCredentials: true }, dataType: 'json', async: isAsync, success: (e) => { res = e } })
return res
},
POST(url, data, isAsync = false) {
let res = null
$.ajax({ url: url, type: 'POST', xhrFields: { withCredentials: true }, data: JSON.stringify(data), contentType: 'application/json', dataType: 'json', async: isAsync, success: (e) => { res = e } })
return res
},
GETBlob: (url) => new Promise((resolve, reject) => $.ajax({ url: url, type: 'GET', xhrFields: { responseType: 'blob' }, success: resolve, error: reject })),
GM_GET: (url, cookie) => new Promise((resolve, reject) => GM_xmlhttpRequest({ url: url, method: 'GET', cookie: cookie, responseType: 'json', onload: resolve, onerror: reject })),
GM_POST: (url, data, cookie) => new Promise((resolve, reject) => GM_xmlhttpRequest({ url: url, method: 'POST', data: JSON.stringify(data), cookie: cookie, responseType: 'json', headers: { 'content-type': 'application/json' }, onload: resolve, onerror: reject })),
GM_GETBlob(url, cookie, headers) {
return new Promise((resolve, reject) => {
let _config = { url: url, method: 'GET', cookie: cookie, responseType: 'blob', onload: resolve, onerror: reject }
if (headers)
_config.headers = headers
GM_xmlhttpRequest(_config)
})
},
}
const utils = {
isEmpty: str => !str || `${str}`.trim().length == 0,
uuid: () => 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, v => (v === 'x' ? (Math.random() * 16 | 0) : (Math.random() * 4 | 8)).toString(16)),
blobToBase64: blob => new Promise((resolve, reject) => {
const reader = new FileReader()
reader.onload = () => resolve(reader.result)
reader.onerror = reject
reader.readAsDataURL(blob)
}),
isBase64Image: str => /^data:image\/(png|jpg|jpeg|gif);base64,/.test(str || ''),
getImageBase64(img) {
let canvas = document.createElement('canvas')
canvas.width = img.naturalWidth
canvas.height = img.naturalHeight
let ctx = canvas.getContext('2d')
ctx.drawImage(img, 0, 0)
return canvas.toDataURL('image/jpeg')
},
sum(arr) {
if (arr == undefined) return undefined
let res = 0
for (let i of arr) res += i ?? -1
return res
},
getRandomIntInclusive(min, max) {
const minCeiled = Math.ceil(min)
const maxFloored = Math.floor(max)
return Math.floor(Math.random() * (maxFloored - minCeiled + 1) + minCeiled)
},
asyncPool: async (arr, process = x => x, begin = x => x, end = x => x, limit = 10, onProgress = () => { }) => {
const pool = new Set()
const results = []
arr = begin(arr)
for (const [i, item] of arr.entries()) {
const task = Promise.resolve().then(() => process(item, arr))
.then(res => results[i] = res)
.catch(err => (console.warn(err), results[i] = err))
.finally(() => { pool.delete(task); onProgress(results.filter(x => x !== undefined).length / arr.length * 100) })
pool.add(task)
if (pool.size >= limit) await Promise.race(pool)
}
await Promise.all(pool)
onProgress(100)
return end(results)
},
getReactStateNode(dom) {
if (dom == null) return null
const key = Object.keys(dom).find(k => k.startsWith('__reactInternalInstance$'))
const instance = dom[key]
return instance?._debugOwner ?? instance?.return ?? instance?._currentElement?._owner ?? null
},
isPlainObject(obj) { return Object.prototype.toString.call(obj) === '[object Object]' },
deepMerge(target, source, arrayPrimaryKeys) {
if (typeof source !== 'object' || source === null)
return target
for (let key in source) {
let sourceValue = source[key], targetValue = target[key]
if (targetValue === undefined) {
target[key] = sourceValue
} else if (utils.isPlainObject(targetValue) && utils.isPlainObject(sourceValue)) {
target[key] = utils.deepMerge(Object.assign({}, targetValue), sourceValue)
} else if (Array.isArray(targetValue) && Array.isArray(sourceValue)) {
let pkey = arrayPrimaryKeys?.[key]
if (pkey)
target[key] = Object.values(utils.deepMerge(
Object.fromEntries(targetValue.map((v, i) => ([v?.[pkey] ?? i, v]))),
Object.fromEntries(sourceValue.map((v, i) => ([v?.[pkey] ?? i, v])))
))
else
target[key] = sourceValue
} else {
target[key] = sourceValue
}
}
return target
},
}
const apis = {
beyondh: {
globalVar: () => cookies?.beyondh?.value ?? {},
pmsEnums: () => {
let key = 'pmsEnums'
let val = GM_getValue(key)
if (hrefKeyword == 'beyondh' && !val) {
val = Object.fromEntries(["Province", "City", "District"].map(x => ([x, unsafeWindow.pmsEnums[x].map(v => Object.values(v))])))
GM_setValue(key, val)
}
return {
Province: val?.Province?.map(v => ({ Value: v[0], EnumString: v[1], Description: v[2] })),
City: val?.City?.map(v => ({ Value: v[0], EnumString: v[1], Description: v[2] })),
District: val?.District?.map(v => ({ Value: v[0], EnumString: v[1], Description: v[2] })),
}
},
_request(method, endpoint, data = null, responseType = 'json', defaultResponse, gmRequest = false) {
const { host, cookie } = this.globalVar()
if (host == null || cookie == null) return
const baseUrl = `https://${host?.replace('8101', '8111')}`
let _config = responseType == 'blob' ? {
method: method,
url: endpoint,
cookie: cookie?.split(';')?.map(v => v.trim())?.filter(v => /^(_lxsdk_cuid=|_lxsdk=|_lx_utm=|_lxsdk_s=|Hm_lvt_|Hm_lpvt_|HMACCOUNT)/.test(v))?.join('; '),
responseType: responseType,
headers: {
"accept": "image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8",
"referer": `https://${host}/`,
"sec-fetch-dest": 'image',
"sec-fetch-mode": 'no-cors',
"sec-fetch-site": 'same-site',
"sec-fetch-storage-access": null,
}
} : {
method: method,
url: `${baseUrl}${endpoint}`,
cookie: cookie?.split(';')?.map(v => v.trim())?.join('; '),
responseType: responseType,
headers: {
"accept": "application/json",
"origin": `https://${host}`,
"referer": `https://${host}/`,
"sec-fetch-dest": 'empty',
"sec-fetch-mode": 'cors',
"sec-fetch-site": 'same-site',
"sec-fetch-storage-access": null,
}
}
if (method == 'POST' && data) {
_config.data = JSON.stringify(data)
_config.headers = {
..._config.headers,
'accept': 'application/json, text/javascript, */*; q=0.01',
'content-type': 'application/json',
'priority': 'u=0, i'
}
}
return new Promise((resolve, reject) => {
if (gmRequest)
GM_xmlhttpRequest({ ..._config, onload: resolve, onerror: reject })
else
resolve({ response: defaultResponse })
})
},
asyncHandle(url, xhr) {
let searchParams = Object.fromEntries(url.searchParams.entries())
requestIdleCallback(() => {
if (/lsapi\/idcard\/pms/.test(url.href)) {
// 缓存读卡数据
let data = JSON.parse(xhr.response)
let idcard = data?.idCard
if (idcard && idcard.idNumbers) {
let customer = {
facenum: null,
name: idcard.guestName,
sex: "3",
sexValue: `${idcard.sex}性`,
nation: pmsEnums.RaceType?.find(v => v.Description.includes(idcard.nation)).EnumString.replace('R', ''),
nationValue: `${idcard.nation}族`,
birthday: idcard.birthday,
cardtype: "11",
cardtypeValue: "身份证",
cardnum: idcard.idNumbers,
region: null,
regionValue: idcard.signAndIssueOrg,
address: idcard.address,
inputtime: null,
yjbm: null,
base64: idcard.photoInfo,
tel: null,
date: new Date(),
validityPeriodStart: idcard.validityPeriodStart,
validityPeriodEnd: idcard.validityPeriodEnd,
}
idCardCache.push(customer)
}
} else if (/GetMixAllRoomDetails/.test(url.pathname)) {
// 缓存数据
let data = JSON.parse(xhr.response)
let val = guestFolioCache.value
let checkIns = Object.fromEntries((data?.Content?.filter(v => v.CheckinId)
?.map(v => v.OrderRelationshipPerson.map(x => ({ ...v, ...x })))?.flat(Infinity) ?? []).map(v => {
let customer = val?.checkIns?.[v.CheckinId]
return [v.CheckinId, customer ?? {
"OrderId": v.OrderId,
"CheckinId": v.CheckinId,
"IsRelationMainCheckin": v.IsGroupOrRelation,
"BillId": v.BillId,
"CustomerInfo": {
"Name": v.UserName,
},
"Contract": {
"Name": v.ContractName,
"Type": v.CustomerSource,
},
"ArriveTime": v.ActualArriveTime,
"DepatureTime": v.BenefitDepartureTime,
"StatusId": v.Type,
"OrderSourceId": v.OrderSource,
"CheckinTypeId": v.CheckinTypeId,
"OccupationId": v.OccupationId,
"Channel": v.OrderSource,
"ChannelName": v.AgentAnalysisChannel,
"GuestFolioDetailModels": [
{
"OccupationId": v.OccupationId,
"CheckinId": v.CheckinId,
"ControlRoomTypeId": v.RoomTypeId,
"ControlRoomTypeName": v.RoomTypeName,
"PriceRoomTypeId": v.RoomTypeId,
"PriceRoomTypeName": v.RoomTypeName,
"RoomCount": 1,
"RoomNumber": [v.RoomNumber],
}
],
}]
}))
guestFolioCache.value = { ...val, checkIns: checkIns }
// 添加更多其它过滤项
apis.beyondh.filterOtherIcon()
// 更好的房间信息展示
apis.beyondh.betterRoomDisplay(data)
} else if (/Checkin\/GetCheckin$/.test(url.pathname)) {
// 缓存数据
let data = JSON.parse(xhr.response)?.Content
let checkinId = searchParams?.CheckinId
if (checkinId) {
let val = guestFolioCache.value
let detail = {
"OrderId": data.OrderId,
"OrderNo": data.OrderSn,
"CheckinId": data.CheckinId,
"CheckinNo": data.CheckinNo,
"IsRelationMainCheckin": data.IsMainCheckIn,
"GroupNo": "",
"BillId": data.BillId,
"BillNo": data.BillNo,
"CustomerInfo": data.CheckinCustomer,
"PriceStakeholder": data.OccupationModel.PriceStakeholder,
"Contract": data.OccupationModel.Contract,
"ArriveTime": data.ActualArriveTime,
"DepatureTime": data.BenefitDepartureTime,
"StatusId": data.StatusId,
"OccupationId": data.OccupationId,
"CheckinMemo": data.Memo,
"Channel": data.Channel,
"ChannelName": data.ChannelName,
"GuestFolioDetailModels": [
{
"OccupationId": data.OccupationModel.OccupationId,
"CheckinId": data.OccupationModel.MainCheckinId,
"ControlRoomTypeId": data.OccupationModel.ControlRoomTypeId,
"ControlRoomTypeName": data.OccupationModel.ControlRoomTypeName,
"PriceRoomTypeId": data.OccupationModel.PriceRoomTypeId,
"PriceRoomTypeName": data.OccupationModel.PriceRoomTypeName,
"RoomCount": 1,
"RoomNumber": [data.OccupationModel.RoomNumber],
"FirstDayPrice": data.OccupationModel.DailyPrices[0],
"DailyPrices": data.OccupationModel.DailyPrices,
"IsPriceChange": data.OccupationModel.IsPriceChange
}
],
"IsCreditCheckin": data.IsCreditCheckin,
}
if (data.StatusId == 'I')
guestFolioCache.value = utils.deepMerge(val, { checkIns: { [checkinId]: detail } })
else if (['O', 'S'].includes(data.StatusId)) {
let checkOuts = Object.entries(utils.deepMerge(val?.checkOuts ?? {}, { [checkinId]: detail }))
?.filter(v => new Date(v[1]?.DepatureTime) >= new Date(new Date().setHours(5, 50, 0)))
let checkIns = Object.entries(val?.checkIns ?? {}).filter(v => v[0] != checkinId)
guestFolioCache.value = { ...val, checkIns: Object.fromEntries(checkIns), checkOuts: Object.fromEntries(checkOuts) }
}
}
} else if (/SearchGuestFolio/.test(url.pathname)) {
// 缓存数据
let data = JSON.parse(xhr.response)
if (searchParams?.['Status[]'] == 'I') {
// 在住
let customers = data?.Content?.Content?.map(v => ([v.CheckinId, v])) ?? []
if (customers.length > 0) {
let val = guestFolioCache.value
guestFolioCache.value = utils.deepMerge(val, { checkIns: Object.fromEntries(customers) })
}
} else if (searchParams?.SearchType == 'TodayDeparture' || ['O', 'S'].includes(searchParams?.['Status[]'])) {
// 今日离店
let customers = data?.Content?.Content?.map(v => ([v.CheckinId, v])) ?? []
if (customers.length > 0) {
let val = guestFolioCache.value
let checkOuts = Object.entries(utils.deepMerge(val?.checkOuts ?? {}, Object.fromEntries(customers)))
?.filter(v => new Date(v[1]?.DepatureTime) >= new Date(new Date().setHours(5, 50, 0)))
guestFolioCache.value = { ...val, checkOuts: Object.fromEntries(checkOuts) }
}
}
} else if (/FilterCustomerByKey/.test(url.pathname)) {
// 缓存数据
let data = JSON.parse(xhr.response)
let val = guestFolioCache.value
let cardId = searchParams?.cardId
if (cardId?.length == 18) {
let filterCustomers = data?.Content?.map(v => {
let id = v.CustomerId
return [id, { ...v, expireTime: new Date().AddDays(3) }]
}) ?? []
let customers = Object.entries(utils.deepMerge(val?.customers ?? {}, Object.fromEntries(filterCustomers)))
?.filter(v => new Date() <= new Date(v[1].expireTime))
guestFolioCache.value = { ...val, customers: Object.fromEntries(customers) }
}
} else if (/GetCustomerPhoto/.test(url.pathname)) {
// 缓存数据
let data = JSON.parse(xhr.response)
let val = guestFolioCache.value
let id = searchParams?.customerId
if (id) {
let photo = { [id]: { PhotoUrl: data.Content, expireTime: new Date().AddDays(3) } }
guestFolioCache.value = utils.deepMerge(val, { customers: photo })
}
} else if (/GetCheckinCustomerDetail|GetCustomerForEdit/.test(url.pathname)) {
// 缓存数据
let data = JSON.parse(xhr.response)
let val = guestFolioCache.value
if (data?.Content?.CustomerId) {
let customer = { [data?.Content?.CustomerId]: { ...data.Content, expireTime: new Date().AddDays(3) } }
let customers = Object.entries(utils.deepMerge(val?.customers ?? {}, customer))
?.filter(v => new Date() <= new Date(v[1].expireTime))
guestFolioCache.value = { ...val, customers: Object.fromEntries(customers) }
}
} else if (/GetCheckinMenuModelMix/.test(url.pathname)) {
let checkinId = searchParams?.checkinId
if (checkinId) {
let data = JSON.parse(xhr.response)
let val = guestFolioCache.value
let menulist = data.Content?.MenuItemList?.filter(v => v.CheckinStatusId == 'I')?.map(v => {
if (v.Child) {
return v.Child?.filter(v => v.CheckinStatusId == 'I')
?.map(x => ([[x.CheckinId], { CheckinId: x.CheckinId, Balance: x.Balance, MenuItemList: data.Content?.MenuItemList }]))
}
return [[[v.CheckinId], { CheckinId: v.CheckinId, Balance: v.Balance, MenuItemList: data.Content?.MenuItemList }]]
})?.flat(1)
// console.log(Object.fromEntries(menulist))
if (menulist?.length > 0) {
guestFolioCache.value = utils.deepMerge(val, { checkIns: Object.fromEntries(menulist) })
// console.log(Object.values(guestFolioCache.value.checkIns).filter(v => v.CheckinId == checkinId))
}
}
}
})
if (unsafeWindow.pms) {
if (url.pathname?.includes('GetCheckinCustomerDetail')) {
// 修改全局规则为展示隐藏信息
unsafeWindow.pms['noEyes'] = GM_getValue('别样红.redactDetail')
} else if (/GuestFolio|QuickSearchCustomer|GetMixRoomFuturePreorders|getordermix|Crm\/Customer|orders\?operateType/.test(url.href)) {
// 在触发查询客单时关闭展示隐藏信息开关,防止未知崩溃
unsafeWindow.pms['noEyes'] = false
}
}
},
getRoomPriceAndBalance: (arr) => {
let price = 0, balance = 0, groupNumber = -1, rooms = []
if (Object.prototype.toString.call(arr) == '[object Array]') {
for (let i = 0; i < arr.length; i++) {
let res = apis.beyondh.getRoomPriceAndBalance(arr[i])
price += res.price
balance += res.balance
groupNumber = res.groupNumber
if (res.rooms.length > 0) rooms = [...rooms, ...res.rooms]
}
} else {
// 每日5:49后完成夜审
let prices = arr.DailyPrices?.filter(v => new Date(v.Date).AddDays(1).AddMinutes(60 * 6 - 11) > Date.now())
if (prices != undefined)
price += utils.sum(prices.map(v => v.ActualPrice ?? 0))
balance += arr.Balance
rooms.push(arr)
groupNumber = arr.GroupOrRelationNumber
}
return { price: Math.round(price * 100) / 100, balance: Math.round(balance * 100) / 100, rooms: rooms, groupNumber: groupNumber }
},
filters: {
xie: { name: '赫程', icon: '赫', regex: /^赫/, isToggle: false },
vip: { name: '会员', icon: '会', regex: /^[会银金钻直]/, isToggle: false },
nolvl: { name: '会员', icon: '小', regex: /^[小]/, isToggle: false },
other: { name: '中介', icon: '介', regex: /^介/, isToggle: false },
},
filterOtherIcon: () => {
let iconFilter = $('[owl-ignore="true"][class="row_dl real_time"] > dd[class="filter clear_float"]')
if (iconFilter.length > 0 && GM_getValue('别样红.showDetailContractName')) {
let other = iconFilter.find('span:contains("其它")').closest('div')
const resetOtherFilter = () => {
Object.keys(apis.beyondh.filters).forEach(k => apis.beyondh.filters[k].isToggle = false)
$('[id^=scriptcat_filter_]').remove()
$('.room_number_default').each((i, v) => $(v).closest('[owl-ignore="true"]').css('display', ''))
$('[class^=script_floorbreak]').remove()
}
other.click(() => {
if (other.attr('class') != 'select_tag selected') {
Object.keys(apis.beyondh.filters).forEach(k => {
if ($(`#scriptcat_filter_${k}`).length == 0) {
let id = `scriptcat_filter_${k}`
let ele = $(`
${apis.beyondh.filters[k].icon}${apis.beyondh.filters[k].name}
`)
ele.click(() => {
apis.beyondh.filters[k].isToggle = !apis.beyondh.filters[k].isToggle
ele.attr('class', apis.beyondh.filters[k].isToggle ? 'select_tag selected' : 'select_tag')
ele.find('[class="iconfont g_icon"]').css('display', apis.beyondh.filters[k].isToggle ? '' : 'none')
$('[class^=script_floorbreak]').remove()
$('[owl-ignore][class$=FloorBreak]').before($(''))
let notSelected = Object.values(apis.beyondh.filters).every(fv => !fv.isToggle)
$('.room_number_default').each((i, v) => {
let roomElement = $(v).closest('[owl-ignore="true"]')
roomElement.css('display', notSelected || Object.values(apis.beyondh.filters).some(fv => fv.isToggle
&& fv.regex.test(roomElement.find('.icon_group').text())) ? '' : 'none')
})
},)
iconFilter.append(ele)
}
})
elmGetter.get('span[style^=color]:contains("清空")').then(div => $(div).click(resetOtherFilter))
} else {
resetOtherFilter()
}
})
}
},
betterRoomDisplay: async (data) => {
let roomSet = {}, groupSet = {}, roomDetails
await apis.beyondh.SearchGuestFolioCheckIn().then(v => roomDetails = v.response.Content.Content)
for (let i = 0; i < data.Content.length; i++) {
let guests = roomDetails
.filter(v => v.GuestFolioDetailModels?.[0]?.RoomNumber?.some(num => num == data.Content[i].RoomNumber))
data.Content[i].Remark = guests.map(v => v.CheckinMemo).join(';')
if (guests.length > 0) {
data.Content[i].Balance = utils.sum(guests.map(v => v?.Balance))
data.Content[i].TodayPrice = guests?.[0]?.GuestFolioDetailModels?.[0]?.FirstDayPrice?.ActualPrice
data.Content[i].DailyPrices = guests?.[0]?.GuestFolioDetailModels?.[0]?.DailyPrices
data.Content[i].MenuItemList = guests?.[0]?.MenuItemList
}
roomSet[data.Content[i].RoomNumber] = data.Content[i]
if (data.Content[i].GroupOrRelationNumber != -1) {
let groupNumber = data.Content[i].GroupOrRelationNumber
if (groupNumber in groupSet) {
groupSet[groupNumber].push(data.Content[i])
} else {
groupSet[groupNumber] = [data.Content[i]]
}
}
}
for (let k of Object.keys(groupSet)) {
if (groupSet[k].length == 1) {
let checkinId = groupSet[k][0].CheckinId
let menulist = groupSet[k][0]?.MenuItemList
if (menulist?.length > 1) {
groupSet[k].push(...(menulist.filter(v => v.CheckinId != checkinId).map(v => ({ ...v, GroupOrRelationNumber: parseInt(k) })) ?? []))
}
}
}
// console.log(roomSet, groupSet, roomDetails)
elmGetter.each('.room_number_default', (e) => {
let roomId = e.text()
let id = roomId.replace(/\s\(.+\)/, '')
// 修改中介小图标
e.nextAll('.icon_group').each((i, v) => {
elmGetter.get('.iconfont', v).then(elm => {
let name = roomSet[id]?.ContractName
if (name != undefined && /小程序|直客通|赫程/.test(name)) {
if (GM_getValue('别样红.showDetailContractName')) {
if (/[小直赫介银金钻]/.test(elm.text())) {
let txt = name[0], icon = 'icon_circle'
if (/小程序|直客通/.test(name)) {
let remark = roomSet[id]?.Remark
if (/[银金钻][卡石]/.test(remark)) {
txt = remark.replace(/\n/g, '').replace(/.*([银金钻])[卡石].*/, '$1')
icon = 'icon_circle icon_circle_' + (/直客通/.test(name) ? 'lv' : 'vacant_status')
}
} else if (/赫程/.test(name)) {
txt = '赫'
icon = 'icon_circle icon_circle_xie'
}
elm.parent().attr('class', icon)
elm.text(txt)
}
} else {
if (elm.text() != '介' && !elm.text().includes('抵')) {
elm.parent().attr('class', 'icon_circle')
elm.text('介')
}
}
}
})
})
let room = roomSet[id]
// 高亮余额≠房价
if (GM_getValue('highlightBalancenePrice') && room.Balance != undefined) {
// 每日5:49后完成夜审
let cur = apis.beyondh.getRoomPriceAndBalance(room)
let grp = room.GroupOrRelationNumber == -1 ? cur : apis.beyondh.getRoomPriceAndBalance(groupSet[room.GroupOrRelationNumber])
e.parent().css('border', (cur.price > 0 && cur.balance == cur.price) ||
(grp.price > 0 && grp.balance == grp.price) ||
(/钻[卡石].*免费?房/.test(room.Remark) && grp.price - grp.balance == GM_getValue('别样红.freePrice')) ||
(!/钻[卡石].*免费?房/.test(room.Remark) && (cur.balance == cur.price || grp.price == grp.balance))
? '' : '2px dashed #f24e4c')
} else e.parent().css('border', '')
})
},
// 获取在住列表
SearchGuestFolioCheckIn() {
let data = Object.values(guestFolioCache.value?.checkIns ?? {})
return this._request(
'GET', '/API/GuestFolio/SearchGuestFolio?PageSize=200&PageIndex=1&IsDesc=false&Status%5B%5D=I&Keywords=&SortField=&IsMeiTuanOrder=false&DateRangeType=RecentWithOneYear', null, 'json',
{ Code: 0, Content: { Content: data, PageCount: 1, PageIndex: 0, PageSize: data.length, RecordCount: data.length } }
)
},
// 获取今日离店列表
SearchGuestFolioTodayCheckOut() {
let data = Object.values(guestFolioCache.value?.checkOuts ?? {})
return this._request(
'GET', '/API/GuestFolio/QuickSearchGuestFolio?PageSize=200&PageIndex=1&IsDesc=false&SearchType=TodayDeparture&SortField=&KeyWord=', null, 'json',
{ Code: 0, Content: { Content: data, PageCount: 1, PageIndex: 0, PageSize: data.length, RecordCount: data.length } }
)
},
// 获取客历明细
GetCustomerForEdit(id) {
let data = guestFolioCache.value?.customers ?? {}
let photos = guestFolioCache.value?.photos ?? {}
let detail = data?.[id]
if (detail) {
let photo = Object.entries(photos).find(v => v[0]?.replace('.jpg', '')?.split('-')?.[1] == `${id}`)
if (photo)
detail.PhotoUrl = photo?.[1]?.base64
}
return this._request(
'GET', `/API/Customer/GetCustomerForEdit?CustomerId=${id}`, null, 'json',
{ Code: 0, Content: detail }
)
},
// 上传客历图片
UploadCustomerPhoto(id, photoBase64) {
return this._request('POST', '/API/Customer/UploadCustomerPhoto', { CustomerId: id, CustomerPhoto: photoBase64 }, 'json', null, GM_getValue('别样红.allowUploadFaceImg') ?? false)
},
// 获取客历图片
GetCustomerPhoto(id) {
return this._request('GET', `/API/Customer/GetCustomerPhoto?customerId=${id}`, null, 'json', null, GM_getValue('别样红.showFaceImg') ?? false)
},
// 获取图片数据
GetPhotoData(url) {
let data = guestFolioCache.value?.photos ?? {}
let base64 = data?.[new URL(url).pathname.split('/').pop()]?.base64
return this._request('GET', url, null, 'blob', base64, base64 == null)
},
// 获取或新增客历
GetOrAddCustomer(customer) { return this._request('POST', '/API/Customer/GetOrAddCustomer', customer) },
// 更新或新增客历
UpdateOrAddCustomer(idcard) {
let customer = IDCardCache.asBeyondhCustomer(idcard)
this.GetOrAddCustomer(customer).then(v => {
if (v.status === 200 && v.response?.Content) {
const data = v.response.Content
Object.assign(customer, { CustomerId: data.CustomerId, Mobile: data.Mobile })
customer.PhotoStatus && this.UploadCustomerPhoto(data.CustomerId, customer.Photo)
}
});
},
GetBaseInfoFromIdCard(customer) {
let id = customer.IdCardNumber
const { Province, City, District } = this.pmsEnums() // unsafeWindow.pmsEnums
if (id.length === 18) {
customer.PersonalCredentialType = 'C01'
customer.Birthday = id.slice(6, 10) + '-' + id.slice(10, 12) + '-' + id.slice(12, 14) + ' 00:00:00'
customer.Province = Province?.find(item => item.EnumString.split('_')[1].startsWith(id.slice(0, 2)))?.Description ?? ''
customer.City = City?.find(item => item.EnumString.split('_')[1].startsWith(id.slice(0, 4)))?.Description ?? ''
customer.District = District?.find(item => item.EnumString.split('_')[1].startsWith(id.slice(0, 6)))?.Description ?? ''
customer.Gender = id.slice(16, 17) % 2 ? 2 : 1
} else if (id.length > 18) {
console.error('身份证号码格式不正确')
return null
}
return customer
},
// 获取消费付款总额
GetBillStatistics(ids) {
return this._request('POST', '/API/Bill/GetBillStatistics', ids, 'json', null, true)
},
// 获取订单概要
GetCheckinSimpleDetail(id) {
return this._request('GET', `/API/Checkin/GetCheckinSimpleDetail?CheckinId=${id}`, null, 'json', null, true)
},
},
hdtyhotel: {
$global: {
dictItem: {
gender: { man: "1", woman: "2" },
personType: {
gws: "F", // 国外
gns: "L", // 国内
gn: "0", // 国内大陆
xg: "1", // 香港
am: "2", // 澳门
tw: "3", // 台湾
gat: "4", // 港澳台
},
cardTypeL: {
sfz: "11", // 身份证
hkb: "13", // 户口本
jsz: "72", // 驾驶证
},
cardTypeF: {
wgryjjlsfz: "34", // 外国人永久居留身份证
wgrybmz: "60", // 边境通行证
},
cardTypeGat: {
twjmlwdltxz: "06", // 台湾居民来往大陆通行证(一次有效)
twjmjzz: "07", // 台湾居民居住证
gajmjzz: "08", // 港澳居民居住证
gajmlwndtxz: "24", // 港澳居民来往内地通行证(回乡证)
},
cjfs: { // 采集方式
dkcj: "10", // 读卡采集
sbycj: "20", // 设备采集
sdlr: "30", // 手动录入
wzrz: "40", // 网络认证
zmd: "50", // 正面对比(或智能门栋)
sdwz: "41", // 手动外采(或实地外采)
wcnpz: "32", // 无差别拍照
},
faceCompareResult: {
noCompare: "0",
success: "1",
error: "2",
}
},
webVersion: 'Web9.0'
},
uuid(length, maxIndex) {
const chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
maxIndex = maxIndex || chars.length
if (length)
return Array.from({ length }, () => chars[Math.random() * maxIndex | 0]).join('')
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, v => {
const r = Math.random() * 16 | 0
return (v === 'x' ? r : (r & 0x3 | 0x8)).toString(16)
})
},
session: {
getItem: (key, prefix = 'hdty-hotel-bs') => cookies.hdtyhotel.value?.session?.[prefix ? `${prefix}:${key}` : key] ?? null,
getUserToken() { return this.getItem("token") },
getUserAuthority(resource, parentResource) {
let authorities = this.getItem("authorities") || {};
let authority = {};
if (parentResource && authorities[parentResource]) {
authority = authorities[parentResource];
if (resource) {
let hasAuthority = authority.children?.[resource]?.hasAuthority
return hasAuthority || false
}
}
return authority
},
getUserInfo() { return this.getItem("user") || {} },
getUserId() { return this.getUserInfo().id },
getUsername() { return this.getUserInfo().username },
getUserXm() { return this.getUserInfo().xm },
getUserGmsfhm() { return this.getUserInfo().gmsfhm },
getAppId() { return this.getUserInfo().appId },
getZuulToken() { return this.getItem("zuulToken") },
getZuulRefreshToken() { return this.getItem("zuulRefreshToken") },
},
cryptoJSUtils: {
DEFAULT_KEY: "c6afbe1035624ea4b378c36a25a44974",
md5: input => input && CryptoJS.MD5(input).toString(),
sha256: input => input && CryptoJS.SHA256(input).toString(),
md5median(input) { return input && this.md5(input).substr(8, 16) },
toHex: wordArray => wordArray?.toString(),
hexToWordArray: hexString => hexString && CryptoJS.enc.Hex.parse(hexString),
toUtf8: wordArray => wordArray && CryptoJS.enc.Utf8.stringify(wordArray),
utf8ToWordArray: utf8String => utf8String && CryptoJS.enc.Utf8.parse(utf8String),
toBase64: wordArray => wordArray && CryptoJS.enc.Base64.stringify(wordArray),
base64ToWordArray: base64String => base64String && CryptoJS.enc.Base64.parse(base64String),
aesEncrypt(plaintext, key, iv) {
if (!plaintext) return
const keyHex = CryptoJS.enc.Utf8.parse(key)
const ivHex = CryptoJS.enc.Utf8.parse(iv)
const encrypted = CryptoJS.AES.encrypt(plaintext, keyHex, {
iv: ivHex,
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.ZeroPadding
})
const ciphertext = encrypted.ciphertext.toString()
const wordArray = CryptoJS.enc.Hex.parse(ciphertext)
return CryptoJS.enc.Base64.stringify(wordArray)
},
aesDecrypt(ciphertext, key, iv) {
if (!ciphertext) return
const keyHex = CryptoJS.enc.Utf8.parse(key)
const ivHex = CryptoJS.enc.Utf8.parse(iv)
return CryptoJS.AES.decrypt(ciphertext, keyHex, {
iv: ivHex,
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.ZeroPadding
}).toString(CryptoJS.enc.Utf8)
},
aesEncryptNetworkData(data, key = this.DEFAULT_KEY) {
if (!data) return
const text = typeof data === 'object' ? JSON.stringify(data) : data.toString()
const md5Key = this.md5median(key)
return this.aesEncrypt(text, md5Key.toLowerCase(), md5Key.toUpperCase())
},
aesDecryptNetworkData(ciphertext, key = this.DEFAULT_KEY) {
if (!ciphertext) return
const cleanText = ciphertext.replace(/[\r\n]/g, "")
const md5Key = this.md5median(key)
const result = this.aesDecrypt(cleanText, md5Key.toLowerCase(), md5Key.toUpperCase())
try { return JSON.parse(result) } catch { return result }
},
},
interceptors() {
let session = this.session
let cryptoJSUtils = this.cryptoJSUtils
let appEnv = cookies.hdtyhotel.value.appEnv
let nonce = this.uuid(16, 16)
let webVersion = this.$global.webVersion
let baseRoute = '/hotelweb-v28/'
const signature = function (timestamp, useUserToken, secret = "097fb8239d80415fb22e08c0b8e3e2a4") {
timestamp = timestamp || Date.now()
let md5Key = cryptoJSUtils.md5median(secret).toLowerCase()
let signatureStr = [
cryptoJSUtils.aesEncrypt(nonce, md5Key, md5Key.toUpperCase()),
timestamp,
nonce,
useUserToken ? session.getUserToken() || session.getZuulToken() : session.getZuulToken()
].sort().join('')
return cryptoJSUtils.sha256(signatureStr)
}
const handleErrorResponse = function (response) {
switch (response.data.code) {
case 10003:
console.warn("您的账号已在另一处登录")
break
case 403:
console.warn(response.data.msg)
break
case 406:
console.warn(response.data.msg)
break
default:
if (response.data.code.toString().length === 4 &&
response.data.code.toString().substring(0, 2) === "20") {
if (response.request.url.indexOf("A010000081") < 0) {
console.warn(response.data.msg)
} else {
console.warn(response.data.msg)
}
} else if (!/P000000041|P000000001|Y000000031|P000000107|Y000000018|A010000148/.test(response.request.url)) {
console.warn(response.data.msg)
}
}
return Promise.reject(response)
}
return {
request(config) {
let isBaseAPI = config.baseURL === appEnv.baseURL
let encryptType = isBaseAPI ? "base" : "zuul"
let signatureEnabled = isBaseAPI ? appEnv.signatureENABLED : appEnv.signatureZuulENABLED
let encryptEnabled = isBaseAPI ? appEnv.encryptENABLED : appEnv.encryptZuulENABLED
config.headers = {
"Content-Type": "application/json;charset=UTF-8",
"Accept": "application/json;charset=UTF-8",
"Route-Path": baseRoute.replace(/\/hotelweb-v28/g, ""),
"Authorization": encryptType === "base" ? session.getUserToken() : session.getZuulToken(),
"AuthorizationHotel": session.getUserToken(),
"Client-Type": appEnv.clientTYPE,
"Encrypt": encryptEnabled,
"Origin": config.baseURL,
"Referer": `${config.baseURL}/hotelweb-v28/`,
"sec-fetch-site": "same-origin",
"sec-fetch-storage-access": null,
}
if (signatureEnabled) {
let timestamp = Date.now()
let { deviceType, deviceId, companyId, softwareCode } = session.getItem("zuulSocketLockInfo")
let { secretType, userName } = session.getItem("gmInfo")
config.headers = {
...config.headers,
"Timestamp": timestamp,
"Nonce": nonce,
"Signature": signature(timestamp, "base" === encryptType),
"SignatureHotel": signature(timestamp, true),
"Parameter": JSON.stringify({
deviceType: deviceType === '2' ? '0' : deviceType,
clientType: '5', clientId: appEnv.clientId,
deviceId, companyId, softwareCode, secretType, userName,
orgId: '', createUserId: '', createUserName: '',
softwareVersion: webVersion,
})
}
}
if (encryptEnabled) {
if (config.encrypt === undefined) config.encrypt = true
let path = config.url.split("/").pop()
let isSpecialPath = [
"P000000106", "P000000109", "P000000107", "P000000108",
"Y000000015", "Y000000014", "Y000000013", "Y000000012",
"Y000000011", "Y000000010", "Y000000009", "Y000000008"
].includes(path)
let useSM4 = session.getItem("gmInfo").secretType !== "3" || isSpecialPath
if (config.encrypt) {
if (config.data)
config.data = useSM4
? cryptoJSUtils.aesEncryptNetworkData(config.data)
: sm4.encrypt(JSON.stringify(config.data), session.getItem("gmInfo").key)
if (config.params)
useSM4
? Object.keys(config.params).forEach((k) => config.params[k] = cryptoJSUtils.toBase64(cryptoJSUtils.utf8ToWordArray(cryptoJSUtils.aesEncryptNetworkData(config.params[k]))))
: Object.keys(config.params).forEach((k) => {
var r = new SM4().encrypt_ecb(Hex.decode(session.getItem("gmInfo").key), Hex.utf8StrToBytes(config.params[k]))
config.params[k] = cryptoJSUtils.toBase64(cryptoJSUtils.utf8ToWordArray(Hex.encode(r, 0, r.length)))
})
if (config.url.includes("?"))
config.url = config.url.replace(/([^?=&]+)=([^&]*)/g, (_, k, v) => {
let dv = ''
if (session.getItem("gmInfo").secretType === "3") {
let dv_enc = new SM4().encrypt_ecb(Hex.decode(session.getItem("gmInfo").key), Hex.utf8StrToBytes(v))
dv = Hex.encode(dv_enc, 0, dv_enc.length)
} else {
dv = cryptoJSUtils.aesEncryptNetworkData(v)
}
return `${k}=${cryptoJSUtils.toBase64(cryptoJSUtils.utf8ToWordArray(dv))}`
})
}
}
return config
},
response(response) {
if (response.data.code && response.data.code !== 200)
return handleErrorResponse(response)
if (response.data.isEncrypt) {
let path = response.config.url.split("/").pop()
let isSpecialPath = [
"P000000106", "P000000109", "P000000107", "P000000108",
"Y000000015", "Y000000014", "Y000000013", "Y000000012",
"Y000000011", "Y000000010", "Y000000009", "Y000000008"
].includes(path)
let useSM4 = session.getItem("gmInfo").secretType == "3" && !isSpecialPath
if (response.data.data && response.data.data.length > 0)
response.data.data = useSM4
? JSON.parse(sm4.decrypt(response.data.data, session.getItem("gmInfo").key).replace(/\u0000/g, "").trim())
: cryptoJSUtils.aesDecryptNetworkData(response.data.data)
response.data.isEncrypt = false
}
return response
},
}
},
_request: function (method, endpoint, data) {
let { host, cookie } = cookies.hdtyhotel.value
let interceptors = this.interceptors()
let baseURL = `https://${host}`
let _config = {
method: method,
baseURL: baseURL,
url: `${baseURL}${endpoint}`,
data: data,
cookie: cookie,
responseType: 'json',
}
_config = interceptors.request(_config)
return new Promise((resolve, reject) => {
GM_xmlhttpRequest({
..._config,
onload(xhr) {
if (xhr.status == 200) {
xhr.config = { url: xhr.finalUrl }
xhr.request = { ..._config, unencryptedData: data }
xhr.data = JSON.parse(xhr.responseText)
xhr = interceptors.response(xhr)
resolve(xhr)
}
},
onerror: reject,
ontimeout() { console.error("请求超时") },
})
})
},
getCheckInList(current = 1, size = 10, guestFlag = '0') {
let zuulCompanyInfo = this.session.getItem('zuulCompanyInfo')
return this._request('POST', '/A010000027', {
companyId: zuulCompanyInfo.companyId,
orgId: zuulCompanyInfo.orgId,
guestFlag: guestFlag,
andor: "and",
current: current,
size: size,
})
},
getCheckInListgw(current = 1, size = 10, guestFlag = '99') {
let zuulCompanyInfo = this.session.getItem('zuulCompanyInfo')
return this._request('POST', '/A010000029', {
companyId: zuulCompanyInfo.companyId,
orgId: zuulCompanyInfo.orgId,
guestFlag: guestFlag,
andor: "and",
current: current,
size: size,
})
},
getCheckInListgat(current = 1, size = 10, guestFlag = '4') {
let zuulCompanyInfo = this.session.getItem('zuulCompanyInfo')
return this._request('POST', '/A010000027', {
companyId: zuulCompanyInfo.companyId,
orgId: zuulCompanyInfo.orgId,
guestFlag: guestFlag,
andor: "and",
current: current,
size: size,
})
},
getCheckInListByRoomNumber(roomNumber) {
let zuulCompanyInfo = this.session.getItem('zuulCompanyInfo')
return this._request('POST', '/A010000003', {
companyId: zuulCompanyInfo.companyId,
loginRoom: roomNumber,
})
},
getGuestDetail(guestId, isGetPhoto = '1') {
let zuulCompanyInfo = this.session.getItem('zuulCompanyInfo')
return this._request('POST', '/A010000176', {
orgId: zuulCompanyInfo.orgId,
id: guestId,
isGetPhoto: isGetPhoto,
})
},
getAge(cardId) {
let zuulCompanyInfo = this.session.getItem('zuulCompanyInfo')
return this._request('POST', '/A010000148', {
birthday: cardId.slice(6, 14),
cardId: cardId,
companyId: zuulCompanyInfo.companyId,
orgId: zuulCompanyInfo.orgId,
})
},
getRegion(region) {
return this._request('POST', `/Y000000018?id=${region}`)
},
getEID(verifyCode) { return this._request('POST', '/Y000000045', { verifyCode: verifyCode }) },
handleChange(loginRoom, guests) {
const personType = this.$global.dictItem.personType
let zuulCompanyInfo = this.session.getItem('zuulCompanyInfo')
return this._request('POST', '/A010000062', {
list: (Array.isArray(guests) ? guests : [guests]).map(v => ({
companyId: zuulCompanyInfo.companyId,
orgId: zuulCompanyInfo.orgId,
guestId: v.id,
guestType: v.guestFlag == personType.gws ? personType.gws : personType.gns,
loginRoom: loginRoom,
}))
})
},
handleCheckOut(guests) {
const personType = this.$global.dictItem.personType
let zuulCompanyInfo = this.session.getItem('zuulCompanyInfo')
return this._request('POST', '/A010000061', {
list: (Array.isArray(guests) ? guests : [guests]).map(v => ({
companyId: zuulCompanyInfo.companyId,
orgId: zuulCompanyInfo.orgId,
guestId: v.id,
guestType: v.guestFlag == personType.gws ? personType.gws : personType.gns,
}))
})
},
handleCheckIn(loginRoom, guest) {
if (guest.PersonalCredentialType != 'C01') return
let idcard = guest.IdCardNumber
if (idcard?.length != 18) return
let zuulCompanyInfo = this.session.getItem('zuulCompanyInfo')
guest = apis.beyondh.GetBaseInfoFromIdCard({
"CustomerId": 0,
"FromType": 0,
"PhotoStatus": true,
"PhotoUrl": "",
"Name": "",
"MemberId": "",
"MemberCardId": "",
"PersonalCredentialType": "C01",
"IdCardNumber": "",
"Nationality": "",
"City": "",
"Gender": 2,
"Race": "",
"Birthday": "",
"Address": "",
"Province": "",
"District": "",
"Mobile": ""
})
function calculateAge(birthday) {
const birthDate = new Date(birthday)
const today = new Date()
let age = today.getFullYear() - birthDate.getFullYear()
const monthDifference = today.getMonth() - birthDate.getMonth()
const dayDifference = today.getDate() - birthDate.getDate()
if (monthDifference < 0 || (monthDifference === 0 && dayDifference < 0))
age--
return age
}
if (calculateAge(idcard.slice(6, 10) + '-' + idcard.slice(10, 12) + '-' + idcard.slice(12, 14)) < 18) {
console.warn('未成年入住请手动操作')
return
}
let loginTime = new Date()
let loginDay = 1
let formData = {
loginRoom: loginRoom,
notCardId: '',
name: guest.Name,
orgId: zuulCompanyInfo.orgId,
cardType: this.$global.dictItem.cardTypeL.sfz,
cardId: idcard,
nation: guest.Race.replace('R', ''),
region: idcard.slice(0, 6),
birthday: idcard.slice(6, 14),
sex: idcard.slice(16, 17) % 2 ? this.$global.dictItem.gender.woman : this.$global.dictItem.gender.man,
address: guest.Address,
guestFlag: this.$global.dictItem.personType.gn,
loginDay: loginDay,
loginTime: loginTime.Format('yyyyMMddhhmmss'),
estimateTime: loginTime.AddDays(loginDay).Format('yyyyMMdd') + "120000",
mobileNum: "",
isCar: "0",
carCode: "",
idcardOrgName: "",
idcardStartDate: "",
idcardEndDate: "",
checkId: "",
personPhotoDTOList: [{ "flag": "0", "imgBase64": "" }],
softwareVersion: this.$global.webVersion,
selfFlag: "0",
collectType: this.$global.cjfs.sdlr,
nonageAdd: {},
faceCompareResult: "0",
faceCompareScore: "0",
}
let eid = idCardCache.value.find(v => v?.cardnum == idcard)
if (eid) {
if (eid?.facenum) {
formData.notCardId = eid.facenum
formData.faceCompareResult = '1'
formData.faceCompareScore = '100'
}
// formData.personPhotoDTOList[0].imgBase64 = eid.
}
// return this._request('POST', '/A010000005', formData)
return formData
},
modifyWidth: () => {
let roomWidth = GM_getValue('公安网.roomWidth')
$('.con-children-passenger').css('width', roomWidth >= 0 ? '500px' : '')
$('.con-children-room').css('width', roomWidth >= 0 ? 'calc(100% - 500px)' : '')
$('.con-children-room .room-list .scriptcat-flex-break').remove()
//双击打开明细
let roomPersonRef = $('.con-room')[0]?.__vue__?.$refs?.roomPersonRef
elmGetter.each('[class^="room-item room-"]', (ele) => {
let houseList = $('.con-room')[0]?.__vue__?.houseList
let roomCode = $(ele).find('.value')[0]?.textContent
$(ele).css('width', roomWidth >= 0 ? `${roomWidth}px` : '') // 调整房间元素宽度
if (houseList && roomCode && GM_getValue('公安网.floorBreak')) {
let flowList = houseList.filter(house => house.roomCode.startsWith(roomCode[0]))
if (roomCode == flowList[flowList.length - 1].roomCode && !$(ele).closest('.con-room-item').next().hasClass('scriptcat-flex-break'))
$(ele).closest('.con-room-item').after($(''))
}
$(ele).dblclick((e) => {
let room = $(e.target).closest('.room-item').find('.item-info > span.value')
let houseList = $('.con-room')[0]?.__vue__?.houseList
if (roomPersonRef != null && houseList != null) {
roomPersonRef.row = houseList.filter(v => v.roomCode == room[0].textContent)[0]
roomPersonRef.beforeLoadForm()
roomPersonRef.visible = true
}
})
})
},
},
ctrip: {
signature: ctripSignature,
spiderRedirectProcess(resp) {
if (resp) {
if (resp.code == 2100 || resp.code == 2101 || resp.code === 2200) {
throw new Error("spiderRedirectError")
}
}
return resp
},
xAjax(options) {
var self = this, reqTime = new Date().getTime();
var key = self.signature(options)
var hoteluuidkeys = ""
if (options.data) {
var json = JSON.parse(options.data);
json.spiderkey = key;
json.hoteluuidkeys = hoteluuidkeys;
if (self.signature) {
json.spiderVersion = "2.0";
}
options.data = JSON.stringify(json)
} else {
if (self.signature) {
options.data = JSON.stringify({
'spiderkey': key,
'hoteluuidkeys': hoteluuidkeys,
'spiderVersion': '2.0'
})
} else {
options.data = JSON.stringify({ 'spiderkey': key, 'hoteluuidkeys': hoteluuidkeys })
}
}
if (options.success) {
var _success = options.success
options.success = function (resp) {
resp = self.spiderRedirectProcess(resp)
resp.reqTime = reqTime
_success(resp)
}
} else {
options.success = self.spiderRedirectProcess
}
return $.ajax(options)
},
getroompricereq() {
// https://ebooking.ctrip.com/ebkovsroom/api/inventory/search/getroompricereq
let { host, cookie } = cookies.ctrip.value
var _config = {
method: 'POST',
baseURL: `https://${host}/`,
url: `https://${host}/ebkovsroom/api/inventory/search/getroompricereq`,
data: JSON.stringify({ "roomIds": [], "changeDate": new Date().format('yyyy-MM-dd'), "submitMonth": new Date().format('yyyy-MM'), "submitor": "", "appStatus": "A", "reqId": -1 }),
cookie: cookie,
responseType: 'json',
headers: {
"accept": "application/json, text/javascript, */*; q=0.01",
"accept-language": "zh-CN,zh-TW;q=0.9,zh;q=0.8,en;q=0.7,en-GB;q=0.6,en-US;q=0.5",
"cache-control": "no-cache",
"content-type": "application/json",
"pragma": "no-cache",
"priority": "u=0, i",
"sec-ch-ua": "\"Chromium\";v=\"142\", \"Microsoft Edge\";v=\"142\", \"Not_A Brand\";v=\"99\"",
"sec-ch-ua-mobile": "?0",
"sec-ch-ua-platform": "\"Windows\"",
"sec-fetch-dest": "empty",
"sec-fetch-mode": "cors",
"sec-fetch-site": "same-origin",
"x-requested-with": "XMLHttpRequest"
},
onload: (e) => console.log(e)
}
return new Promise((resolve, reject) => {
GM_xmlhttpRequest({
..._config,
onload(xhr) {
if (xhr.status == 200) {
xhr.config = { url: xhr.finalUrl }
xhr.request = { ..._config, unencryptedData: data }
xhr.data = JSON.parse(xhr.responseText)
resolve(xhr)
}
},
onerror: reject,
ontimeout() { console.error("请求超时") },
})
})
}
}
}
// 初始化面板
function create_guestDiffPanel(x = 0, y = 0, mini = false) {
let init = false
let tbData = []
let setTbConfig, tbConfig = {
data: [],
tbloading: false,
searchValue: '',
multipleSearch: false,
includesCheckOut: false,
publicDataLoading: false
}
let tbCols = [
{ title: '公安号', dataIndex: 'publicRoomNumber', key: 'publicRoomNumber', width: 70, ellipsis: true, align: 'center' },
{ title: '房态号', dataIndex: 'RoomNumber', key: 'RoomNumber', width: 70, ellipsis: true, align: 'center' },
// { title: '姓名', dataIndex: 'CustomerName', key: 'CustomerName', ellipsis: true, align: 'center' },
{ title: '姓名', dataIndex: 'name', ellipsis: true, align: 'center', render: (col, record) => `${record.CustomerName} ${record?.CustomerIdCardNumber?.slice(14, 18)}` },
{ title: '状态', dataIndex: 'Status', key: 'Status', width: 60, ellipsis: true, align: 'center' },
{ title: '照片', dataIndex: 'CustomerPhotoUrl', key: 'CustomerPhotoUrl', width: 60, ellipsis: true, align: 'left' },
{
title: '操作', dataIndex: 'operation', width: 75, align: 'center',
render: (col, record) => {
const setFormItem = (label, value) => {
let passengerForm = $('.con-children-passenger #pane-local .el-form-item')
let labelItem = passengerForm.find(`.el-form-item__label[for="${label}"]`)
let inputItem = labelItem.closest('div').find('.el-form-item__content input')
if (inputItem.length > 0 && value != null) {
inputItem.val(value)
inputItem[0].dispatchEvent(new Event('input'))
}
return inputItem
}
let img = $('.con-hardware')[0]
let opts = [
CAT_UI.Button('填写', {
size: 'mini', onClick: () => {
if (!utils.isEmpty(record.CustomerName)) setFormItem('name', record.CustomerName)
if (!utils.isEmpty(record.CustomerIdCardNumber)) setFormItem('cardId', record.CustomerIdCardNumber)
if (!utils.isEmpty(record.CustomerAddress)) setFormItem('address', record.CustomerAddress)
if (record.CustomerPhotoUrl) {
if (utils.isBase64Image(record.CustomerPhotoUrl))
img.__vue__._data.photoZjz = record.CustomerPhotoUrl
else if (/^http/.test(record.CustomerPhotoUrl))
apis.beyondh.GetPhotoData(record.CustomerPhotoUrl).then(v => {
if (utils.isBase64Image(v.response))
img.__vue__._data.photoZjz = v.response
else
utils.blobToBase64(v.response).then((data) => { img.__vue__._data.photoZjz = data })
})
else if (record.CustomerId != null)
apis.beyondh.GetCustomerForEdit(record.CustomerId).then((customerXhr) => {
let customerInfo = customerXhr.response?.Content
let { PhotoUrl, Address } = customerInfo
if (!utils.isEmpty(Address)) setFormItem('address', Address)
if (/^http/.test(PhotoUrl)) {
apis.beyondh.GetPhotoData(PhotoUrl).then(v => {
if (utils.isBase64Image(v.response))
img.__vue__._data.photoZjz = v.response
else
utils.blobToBase64(v.response).then((data) => { img.__vue__._data.photoZjz = data })
})
}
})
}
if (record?.Race) {
let nation = record.Race.replace('R', '')
setFormItem('nation', null)
let elmmatch = `.el-select-dropdown li.el-select-dropdown__item > span > span.hdty-tips:contains('${nation}')`
let target = $('label[for=nation]').parent().find(elmmatch)
if (target.length > 0)
target.click()
else
$(elmmatch).click()
}
}
})
]
if (record.UUID != null)
opts.push(CAT_UI.Button('删除', {
size: 'mini', onClick: () => {
photoCache.value = photoCache.value.filter(v => v.UUID != record.UUID)
onSearchValueChange(tbConfig.searchValue)
}
}))
return opts
},
},
]
let timerSearch = null
const renderDiffRoom = () => {
if (tbData.length > 0) {
let roomGroups = {}
tbData.forEach(v => {
if (v.Status != '退房') {
if (roomGroups[v.RoomNumber]) {
roomGroups[v.RoomNumber].push(v)
} else {
roomGroups[v.RoomNumber] = [v]
}
}
})
let rooms = $('.room-item')
rooms.each((i, v) => {
let vals = $(v).find('.value')
let room = vals[0].textContent
let count = vals[2].textContent
if (count != (roomGroups[room] == null ? 0 : roomGroups[room].length))
$(v).css('background', GM_getValue('公安网.hightlightMethod') == '不高亮' ? '' : '#ffae74')
else
$(v).css('background', '')
})
}
}
const checkTableData = () => {
if (GM_getValue('公安网.compareMethod') == '房间人数') {
return renderDiffRoom()
}
// 对比高亮异常数据
const colorSets = {
不高亮: { checkIn: '', checkOut: '', changeRoom: '', keepIn: '', none: '' },
同色高亮: { checkIn: '#ffae74', checkOut: '#ffae74', changeRoom: '#ffae74', keepIn: '#ffae74', none: '' },
异色高亮: { checkIn: '#dcc7e1', checkOut: '#b2b6b6', changeRoom: '#ffae74', keepIn: '#ff6b81', none: '' },
}
let tb = publicData
let diff_color = colorSets[GM_getValue('公安网.hightlightMethod')]
const hideIdcard = (id) => id?.replace(/(?<=^[0-9]{2})[0-9]{12}/g, (v) => '*'.repeat(v.length))
const hideName = (name) => name?.replace(/(?<=^.).+/g, (v) => '*'.repeat(v.length))
// const redactIdcard = (id) => id TODO:脱敏公安网信息
if (tb != null && tb.length > 0) {
setTbConfig({ ...tbConfig, tbloading: true })
let diffData = []
tb.forEach((pv, pi) => {
let byh = tbData.filter((ov, oi) => {
// 模糊对比名字与证件号
if (hideIdcard(ov.CustomerIdCardNumber?.toUpperCase()) == pv.vo.cardId?.toUpperCase()
&& hideName(ov.CustomerName?.toUpperCase()) == pv.vo.name?.toUpperCase()) {
tbData[oi].publicId = pv.vo.id
tbData[oi].publicValue = pv.vo
tbData[oi].publicRoomNumber = pv.vo.loginRoom
return true
} else {
return false
}
})
if (byh.length != 1) {
// console.log(pv.vo, byh)
diffData.push({
publicId: pv.vo.id,
publicValue: pv.vo,
publicRoomNumber: pv.vo.loginRoom,
CustomerName: pv.vo.name,
CustomerIdCardNumber: pv.vo.cardId,
RoomNumber: null,
CustomerPhotoUrl: null,
Race: `R${pv.vo.nation}`,
Status: '待定'
})
}
})
tbData = [...tbData, ...diffData]
filterTbData(tbData)
let diff = tbData.filter(v => (v.publicRoomNumber != v.RoomNumber && v.Status != '退房') || v.Status != '在住')
let isHdtyhotel = location.href.includes('hdtyhotel')
$(isHdtyhotel ? '.room-item' : '.room_number_default').each((i, v) => {
let room = isHdtyhotel ? $(v).find('.value')[0]?.textContent : $(v).text()?.replace(/\s\(.+\)/, '')
let background = diff_color.none
if (diff.some(v => utils.isEmpty(v.publicRoomNumber) && v.Status == '在住' && v.RoomNumber == room)) {
// 入住:别样红在住,且公安网未登记
background = diff_color.checkIn
} else if (diff.some(v => v.publicRoomNumber == room && v.RoomNumber == room && v.Status == '退房')) {
// 退房:公安网已登记,且别样红今日退房
background = diff_color.checkOut
} else if (diff.some(v => (v.publicRoomNumber == room || v.RoomNumber == room) &&
!utils.isEmpty(v.publicRoomNumber) && !utils.isEmpty(v.RoomNumber) && v.publicRoomNumber != v.RoomNumber)) {
// 换房:公安网别样红均已登记过(含今日退房),但房间号不一致
background = diff_color.changeRoom
} else if (diff.some(v => ((v.publicRoomNumber == room && utils.isEmpty(v.RoomNumber)) ||
(v.RoomNumber == room && utils.isEmpty(v.publicRoomNumber))) && v.Status == '待定')) {
// 待定:公安网已登记,且别样红未登记 或者 使用了暂存功能
background = diff_color.keepIn
}
if (isHdtyhotel)
$(v).css('background', background)
else
$(v).css('color', background == diff_color.none ? '' : '#ffae74')
})
}
}
const onSearchValueChange = (value) => {
setTbConfig({ ...tbConfig, searchValue: value })
if (timerSearch)
clearTimeout(timerSearch)
timerSearch = setTimeout(() => {
// tbData?.length > 0 ? publicData ? renderDiffRoom() : checkTableData() : $('.room-item').css('background', '')
onTableDataChange().then(v => tbData?.length > 0 ? publicData ? checkTableData() : renderDiffRoom() : $('.room-item').css('background', ''))
}, 500)
}
const filterTbData = (arr) => {
tbData = [...arr]
tbData = tbData.sort((a, b) => {
let roomA = parseInt(a.RoomNumber),
roomB = parseInt(b.RoomNumber),
publicRoomA = parseInt(a.publicRoomNumber ?? '-1'),
publicRoomB = parseInt(b.publicRoomNumber ?? '-1')
if ((publicRoomA == roomA && publicRoomB == roomB) || (publicRoomA < 0 && publicRoomB < 0)) {
if (roomA == roomB) {
if (roomA.Status == roomB.Status)
return JSON.stringify(a).localeCompare(JSON.stringify(b))
else
return roomA.Status.localeCompare(roomB.Status)
}
else
return roomA - roomB
} else {
return -1
}
})
let filterData = [...tbData]
if (!tbConfig.includesCheckOut) {
filterData = filterData.filter(v => !(utils.isEmpty(v.publicRoomNumber) && v.Status == '退房'))
}
if (tbConfig.searchValue.length > 0) {
filterData = filterData.filter(v => [v.publicRoomNumber, v.RoomNumber, v.CustomerName].some(x => new RegExp(tbConfig.searchValue).test(`${x}`))
|| (/http/.test(tbConfig.searchValue) && new RegExp(`^(?!http)`).test(v.CustomerPhotoUrl)))
}
filterData = [...photoCache.value, ...filterData]
setTbConfig({ ...tbConfig, data: filterData, tbloading: false })
return arr
}
const onTableDataChange = () => {
return apis.beyondh.SearchGuestFolioTodayCheckOut().then(async (checkOutXhr) => {
return apis.beyondh.SearchGuestFolioCheckIn().then(async (checkInXhr) => {
tbData = []
let checkOut = checkOutXhr.response?.Content?.Content
let checkIn = checkInXhr.response?.Content?.Content
// console.log(checkOut)
// console.log(checkIn)
// todo: bug
checkOut = checkOut?.filter(v => !checkIn.some(x => x.CustomerInfo?.IdCardNumber?.toUpperCase() == v.CustomerInfo?.IdCardNumber?.toUpperCase()))
let data = [
...(checkOut?.map(v => { v.checkOut = true; return v }) ?? []),
...(checkIn ?? [])
]
if (data.length > 0) {
let eids = idCardCache.value
let customers = guestFolioCache.value?.customers ?? {}
return await utils.asyncPool(data, async (v, arr) => {
let row = {
CheckinId: v.CheckinId,
CheckinType: v.CheckinTypeId,
ContractName: v.Contract.Id == 0 ? v.PriceStakeholder.MemberCardId == null ? '散客' : '会员' : v.Contract.Name,
CustomerId: v.CustomerInfo.CustomerId,
CustomerName: v.CustomerInfo.Name,
CustomerIdCardNumber: v.CustomerInfo.IdCardNumber?.toUpperCase(),
RoomNumber: v.GuestFolioDetailModels[0].RoomNumber[0],
Status: v.checkOut ? '退房' : '在住'
}
let customer = Object.values(customers)?.find(v => v?.IdCardNumber?.toUpperCase() == row.CustomerIdCardNumber)
if (customer) {
await apis.beyondh.GetCustomerForEdit(customer.CustomerId).then(v => customer = v.response.Content)
row = { ...row, CustomerAddress: customer.Address, CustomerPhotoUrl: customer.PhotoUrl, Race: customer.Race }
}
let eid = eids.find(v => v.cardnum?.toUpperCase() == row.CustomerIdCardNumber)
eid && (row = { ...row, CustomerAddress: eid.address, CustomerPhotoUrl: `data:image/jpeg;base64,${eid.base64}` })
return row
}, arr => {
setTbConfig({ ...tbConfig, tbloading: true })
return arr
}, filterTbData)
}
})
})
}
const loadHdtyCheckInList = () => {
const getList = async (handle, size, msg) => {
await handle.call(apis.hdtyhotel, 1, size).then(async (e) => {
let data = e?.data?.data
if (data != null) {
let ret = []
ret.push(...data.records)
if (data.total > size) {
let arr = Array.from({ length: Math.ceil((data.total - size) / size) }, (v, i) => i + 1)
let retex = await utils.asyncPool(arr, async (item, arr) => {
let tb = null
await handle.call(apis.hdtyhotel, 1 + item, size).then(e => tb = e?.data?.data?.records)
return tb
})
retex.forEach(x => {
if (x != null && x.length > 0)
ret.push(...x)
})
}
publicData = publicData == null ? ret : [...publicData, ...ret]
if (msg)
CAT_UI.Message.success(msg)
}
})
}
return new Promise((resolve, reject) => {
(async () => {
publicData = null
setTbConfig({ ...tbConfig, publicDataLoading: true })
await getList(apis.hdtyhotel.getCheckInList, 10)
if (GM_getValue('公安网.includesGw'))
await getList(apis.hdtyhotel.getCheckInListgw, 10)
if (GM_getValue('公安网.includesGat'))
await getList(apis.hdtyhotel.getCheckInListgat, 10)
resolve()
onSearchValueChange(tbConfig.searchValue)
setTbConfig({ ...tbConfig, publicDataLoading: false })
})()
})
}
return CAT_UI.createPanel({
min: mini,
onMin: (min) => {
let panelConfig = config.value.panel
config.value = {
...config.value,
panel: {
...panelConfig,
[hrefKeyword]: { ...panelConfig[hrefKeyword], mini: min }
}
}
},
point: { x: x, y: y },
header: {
title: () => {
[tbConfig, setTbConfig] = CAT_UI.useState(tbConfig)
return CAT_UI.Space([
CAT_UI.Icon.ScriptCat({
style: { width: '15px', verticalAlign: 'middle' },
draggable: false
}),
], { style: { marginLeft: '0px' } })
},
style: { background: 'transparent' },
},
render: () => {
if (!init) {
init = true
const xhrOpen = XMLHttpRequest.prototype.open;
XMLHttpRequest.prototype.open = function () {
const xhr = this
if (location.host.includes('beyondh')) {
if (/api\/Room\/GetMixAllRoomDetails/.test(arguments[1])) {
loadHdtyCheckInList()
}
} else if (location.host.includes('hdtyhotel')) {
if (/\/A010000004/.test(arguments[1])) {
const getter = Object.getOwnPropertyDescriptor(XMLHttpRequest.prototype, "responseText").get
Object.defineProperty(xhr, "responseText", {
get: () => {
apis.hdtyhotel.modifyWidth()
loadHdtyCheckInList().then(v => onSearchValueChange(tbConfig.searchValue))
let result = getter.call(xhr)
// console.log(apis.hdtyhotel.interceptors().response({ config: { url: arguments[1] }, data: JSON.parse(result) }))
return result
},
configurable: true
})
} else if (/A010000028/.test(arguments[1])) {
const getter = Object.getOwnPropertyDescriptor(XMLHttpRequest.prototype, "responseText").get
Object.defineProperty(xhr, "responseText", {
get: () => {
let result = getter.call(xhr)
let ret = apis.hdtyhotel.interceptors().response({
config: { url: xhr.responseURL },
data: JSON.parse(result)
})
if (ret?.data?.data?.vo) {
let form = $('[role="dialog"][aria-label="房间入住旅客信息"] form')
let dialog = form.closest('[role="dialog"]')
if (dialog.length == 1) {
let id = 'scriptcat_maskingBtn'
let maskingBtn = $(`#${id}`)
if (maskingBtn.length == 0) {
maskingBtn = $(``)
dialog.find('.hdty-dialog-footer').prepend(maskingBtn)
maskingBtn.click((e) => {
if (form[0]?.__vue__?._props?.model?.vo) {
let vid = form[0].__vue__._props.model.vo.id
apis.hdtyhotel.getGuestDetail(vid).then(v => {
if (v.data?.data?.vo) {
form[0].__vue__._props.model.vo = v.data.data.vo
}
})
}
})
}
}
}
return result
},
configurable: true
})
} else if (/Y000000045/.test(arguments[1])) {
// 缓存电子证件信息
const getter = Object.getOwnPropertyDescriptor(XMLHttpRequest.prototype, "responseText").get
Object.defineProperty(xhr, "responseText", {
get: () => {
let result = getter.call(xhr)
let ret = apis.hdtyhotel.interceptors().response({
config: { url: xhr.responseURL },
data: JSON.parse(result)
})
if (ret.data?.data) {
let idcard = ret.data.data
idCardCache.push(idcard)
// apis.beyondh.UpdateOrAddCustomer(idcard)
}
return result
},
configurable: true
})
}
}
return xhrOpen.apply(xhr, arguments)
}
document.body.addEventListener('click', function (e) {
if (location.host.includes('beyondh')) {
// let room = $(e.target.parentElement).find('.room_number_default')
// if (room.length == 1) {
// onSearchValueChange(room.text().replace(/[^0-9]+/, ''))
// }
} else if (location.host.includes('hdtyhotel')) {
let room = $(e.target).closest('.room-item').find('.item-info > span.value')
if (room.length > 1) {
let id = $(room[0]).text().replace(/[^0-9]+/, '')
if (tbConfig.searchValue == 'http')
return
if (tbConfig.multipleSearch) {
let pre = (tbConfig.searchValue || '').split('|')
let isdelete = pre.some(v => v == id)
onSearchValueChange((isdelete ? pre.filter(v => v != id) : [...pre, id])
.filter(v => !utils.isEmpty(v)).sort().join('|')
)
} else {
onSearchValueChange(id)
}
}
}
})
}
return CAT_UI.Space([
CAT_UI.Space([
CAT_UI.Input({
size: 'mini',
value: tbConfig.searchValue,
allowClear: true,
addBefore: publicData == null ? CAT_UI.Icon.IconLoading({ spin: tbConfig.publicDataLoading }) :
CAT_UI.Icon.IconCheckCircleFill({ style: { color: 'green' } }),
addAfter: CAT_UI.Icon.IconSearch({ onClick: () => onSearchValueChange(tbConfig.searchValue) }),
onChange: onSearchValueChange,
onPressEnter: () => onSearchValueChange(tbConfig.searchValue),
}),
CAT_UI.moudles.Tooltip.render({
content: '是否搜索多个房间',
children: CAT_UI.moudles.Switch.render({
size: 'mini',
defaultChecked: tbConfig.multipleSearch,
checkedText: '多选',
uncheckedText: '单选',
onChange: (val) => {
tbConfig.multipleSearch = val
onSearchValueChange(tbConfig.searchValue)
}
})
}),
CAT_UI.moudles.Tooltip.render({
content: '是否显示已退房人员',
children: CAT_UI.moudles.Switch.render({
size: 'mini',
defaultChecked: tbConfig.includesCheckOut,
checkedText: '含退房',
uncheckedText: '仅入住',
onChange: (val) => {
tbConfig.includesCheckOut = val
onSearchValueChange(tbConfig.searchValue)
}
})
}),
CAT_UI.Button('暂存', {
type: 'primary',
size: 'mini',
onClick: (e) => {
let src = $('.con-hardware')[0].__vue__._data.photoZjz
if (src != null && src.length > 0 && photoCache.value.every(v => v.src != src)) {
let formElement = $('#pane-local [class="con-all-person local"] > .el-form')
if (formElement.length > 0) {
let formData = formElement[0]?.__vue__?._props?.model
photoCache.value = [...photoCache.value, {
UUID: utils.uuid(),
RoomNumber: $('#pane-local > .con-all-person')[0].__vue__._data.currentRoom?.roomCode,
CustomerName: formData?.name,
CustomerIdCardNumber: formData?.cardId,
CustomerAddress: formData?.address,
CustomerPhotoUrl: src,
Status: '待定'
}]
// onTableDataChange()
onSearchValueChange(tbConfig.searchValue)
}
}
}
}),
], { style: { display: 'flex', 'justify-content': 'space-evenly' } }),
CAT_UI.Table({
columns: tbCols,
data: tbConfig.data,
loading: tbConfig.tbloading,
border: true,
borderCell: true,
size: 'mini',
style: { width: 484 },
rowKey: 'CheckinId',
fixedHeader: false,
scroll: { y: document.body.clientHeight - config.value.panel[hrefKeyword].y - 130 },
pagination: false
})
], { direction: 'vertical' })
},
onReady(panel) {
panel.onDraggableStop((e) => {
let element = panel.shadowRoot.querySelector('section')
let rect = element.getBoundingClientRect()
config.value = {
...config.value,
panel: {
...config.value.panel,
[hrefKeyword]: { ...config.value.panel[hrefKeyword], x: Math.max(0, rect.x), y: Math.max(0, rect.y) }
}
}
})
},
})
}
function create_eidPanel(x = 0, y = 0, width = 100, height = 100, mini = false) {
let init = false
let eidPanel
idCardCache.removeExpire()
let tbData = idCardCache.asBeyondhCustomers(null, true)
let setTbConfig, tbConfig = {
data: [...tbData],
tbloading: false,
verifyCode: '',
}
let tbCols = [
// { title: '证件号', dataIndex: 'IdCardNumber', key: 'CustomerIdCardNumber', width: 170, ellipsis: true, align: 'center' },
{
title: 'ID', dataIndex: 'cardId', width: 200, ellipsis: true, align: 'center',
// render: (col, record, index) => `${record?._origin?.facenum ? '网证' : '卡证'} ${record.IdCardNumber.slice(0, 6) + '**' + record.IdCardNumber.slice(14, 18)}`
render: (col, record, index) => `${record?._origin?.facenum ? '网证' : '卡证'} ${record.IdCardNumber}`
},
{ title: '姓名', dataIndex: 'Name', key: 'CustomerName', ellipsis: true, align: 'center' },
{ title: '性别', dataIndex: 'sex', width: 60, align: 'center', render: (col, record, index) => record.IdCardNumber.charAt(16) % 2 ? '男' : '女' },
...(window.devicePixelRatio > 1 ? [] : [{ title: '住址', dataIndex: 'Address', key: 'CustomerAddress', width: 60, ellipsis: true, align: 'left' }]),
{ title: '时间', dataIndex: 'date', width: 120, align: 'center', render: (col, record, index) => record?.InputTime?.Format('MM-dd hh:mm') },
{
title: '操作', dataIndex: 'operation', width: 60, align: 'center',
render: (col, record, index) => {
let opts = CAT_UI.Space([
CAT_UI.Icon.IconCopy({
onClick: () => {
if ($('.customer_from').length > 0) {
let fiberNode = utils.getReactStateNode($('.customer_from')[0])?.memoizedProps?.children[0]?._owner
if (fiberNode) {
Object.keys(record).forEach(k => !utils.isEmpty(record[k]) && fiberNode.stateNode.onFieldChange(k, record[k]))
fiberNode.stateNode.loadCustomerByIdNumber(record.IdCardNumber)
GM_setClipboard(record.IdCardNumber)
CAT_UI.Message.success('已复制身份证号')
setTimeout(() => {
let customerid = Object.values(guestFolioCache.value?.customers ?? {})
?.find(v => v?.IdCardNumber?.toUpperCase() == record.IdCardNumber)
?.CustomerId
if (customerid && record.PhotoStatus)
apis.beyondh.UploadCustomerPhoto(customerid, record.Photo)
}, 1000)
// apis.beyondh.GetOrAddCustomer(fiberNode.stateNode.props.customer).then(v => {
// if (v.status == 200) {
// let data = v.response?.Content
// if (data) {
// fiberNode.stateNode.onFieldChange('CustomerId', data.CustomerId)
// fiberNode.stateNode.onFieldChange('Mobile', data.Mobile)
// if (record.PhotoStatus) {
// apis.beyondh.UploadCustomerPhoto(data.CustomerId, record.Photo)
// }
// }
// }
// })
}
}
if ($('.quick_checkin').length > 0) {
let fiberNode = utils.getReactStateNode($('.quick_checkin')[0])?.stateNode
if (fiberNode) {
GM_setClipboard(record.IdCardNumber)
CAT_UI.Message.success('已复制身份证号')
// apis.beyondh.GetOrAddCustomer(record).then(v => {
// if (v.status == 200) {
// let data = v.response?.Content
// if (data) {
// record.CustomerId = data.CustomerId
// record.Mobile = data.Mobile
// if (record.PhotoStatus) {
// apis.beyondh.UploadCustomerPhoto(data.CustomerId, record.Photo)
// }
// }
// }
// })
}
}
}
}),
CAT_UI.Icon.IconDelete({
onClick: () => {
idCardCache.remove(record.IdCardNumber)
tbData = idCardCache.asBeyondhCustomers(null, true)
setTbConfig({ ...tbConfig, data: tbData })
}
})
], { style: { display: 'flex', 'justify-content': 'space-evenly' } })
return opts
},
},
]
const onReadCard = async (value) => {
await idCardCache.readCard(value)
tbData = idCardCache.asBeyondhCustomers(null, true)
setTbConfig({ ...tbConfig, data: [...tbData] })
}
eidPanel = CAT_UI.createPanel({
min: mini,
point: { x: x, y: y },
header: {
title: () => {
[tbConfig, setTbConfig] = CAT_UI.useState(tbConfig)
return CAT_UI.Space([
CAT_UI.Icon.ScriptCat({
style: { width: '24px', verticalAlign: 'middle' },
draggable: 'false',
}),
], { style: { marginLeft: '0px' } })
},
style: { background: 'transparent' },
},
render: () => {
if (!init) init = true
return CAT_UI.Space([
CAT_UI.Space([
CAT_UI.Input({
placeholder: '请输入6位验证码',
value: tbConfig.verifyCode,
allowClear: true,
addAfter: CAT_UI.Icon.IconSearch({ onClick: () => onReadCard(tbConfig.verifyCode) }),
onChange: (v) => setTbConfig({ ...tbConfig, verifyCode: v }),
onPressEnter: () => onReadCard(tbConfig.verifyCode),
}),
], { style: { display: 'flex', 'justify-content': 'space-evenly' } }),
CAT_UI.Table({
columns: tbCols,
data: tbConfig.data,
loading: tbConfig.tbloading,
border: true,
borderCell: true,
size: 'mini',
style: { width: width - 10, height: height },
rowKey: 'IdCardNumber',
fixedHeader: false,
scroll: { y: height - 20 },
pagination: false
})
], { direction: 'vertical' })
}
})
return eidPanel
}
function main() {
if (location.host.includes('hdtyhotel')) {
apis.hdtyhotel.modifyWidth()
create_guestDiffPanel(config.value.panel[hrefKeyword].x, config.value.panel[hrefKeyword].y, config.value.panel[hrefKeyword].mini)
} else if (location.host.includes('beyondh')) {
// create_guestDiffPanel(config.value.panel[hrefKeyword].x, config.value.panel[hrefKeyword].y, config.value.panel[hrefKeyword].mini)
apis.beyondh.pmsEnums()
const nearest = (node, query) => node.find(query).length > 0 ? node.find(query) : node.closest(query)
let eidPanel
let observer = new MutationObserver((mutations) => {
mutations.forEach(mutation => {
Array.from(mutation.addedNodes).forEach(node => {
if (node.classList?.contains('ant-modal-root')) {
let titleElement = node.querySelector('.ant-modal-title')
if (titleElement) {
let modalTitle = titleElement.textContent
if (/预订转入住|更新客人信息|增加同住人|客历维护/.test(modalTitle)) {
let rect = node.querySelector('.ant-modal-content').getBoundingClientRect()
if (!eidPanel && GM_getValue('别样红.idCardCacheExpires') >= 0)
eidPanel = create_eidPanel(rect.right + 3, rect.top, document.body.clientWidth - rect.right - 12, rect.height - 60)
}
}
let img = $(node).find('img')
if (img.length > 0) {
img.on('load', function (event) {
let target = $(event.target)
let src = target.attr('src')
if (src?.includes('pmscos.beyondh.com/private')) {
target.attr('crossorigin', 'anonymous')
let base64 = utils.getImageBase64(target.get(0))
if (utils.isBase64Image(base64)) {
let val = guestFolioCache.value
let key = new URL(src).pathname.split('/').pop()
let photo = { [key]: { base64: base64, expireTime: new Date().AddDays(3) } }
let photos = Object.entries(utils.deepMerge(val?.photos ?? {}, photo))?.filter(v => new Date() <= new Date(v[1].expireTime))
guestFolioCache.value = { ...val, photos: Object.fromEntries(photos) }
img.off('load')
target.attr('crossorigin', '')
}
}
})
}
} else if (nearest($(node), '.quick_checkin')) {
let modalNode = nearest($(node), '.quick_checkin')
if (modalNode.length > 0) {
setTimeout(() => {
if ($('.quick_checkin').length > 0) {
let rect1 = modalNode.find('.quick_checkin_info')[2].getBoundingClientRect()
let rect2 = modalNode.find('.quick_checkin_footer')[0].getBoundingClientRect()
if (!eidPanel && GM_getValue('别样红.idCardCacheExpires') > 0)
eidPanel = create_eidPanel(20, rect1.bottom, 620, rect2.top - rect1.bottom - 100)
}
}, 300)
}
}
})
Array.from(mutation.removedNodes).forEach(node => {
if (eidPanel) {
if (nearest($(node), '.quick_checkin').length > 0) {
eidPanel.remove(), eidPanel = null
} else if (nearest($(node), '.ant-modal-root').length > 0) {
eidPanel.remove(), eidPanel = null
}
}
})
})
})
observer.observe(document.body, { childList: true, subtree: true })
if (location.pathname == '/' || location.pathname == '/realtime')
$(document).ready(function () {
$(document).on('mousedown', function (event) {
let pageCount = GM_getValue('别样红.pageCount')
if ($(event.target).closest('button').text()?.trim() == '刷新' && (pageCount != 0 || event.button == 2)) {
const loadCheckInList = (pageIndex = 1) => {
apis.beyondh._request(
'GET', `/API/GuestFolio/SearchGuestFolio?PageSize=10&PageIndex=${pageIndex}&IsDesc=true&Status%5B%5D=I&Keywords=&SortField=ArriveTime&IsMeiTuanOrder=false&DateRangeType=RecentWithOneYear`,
null, 'json', null, true
).then((xhr) => {
let data = xhr.response
// 最近入住
let customers = data?.Content?.Content?.map(v => ([v.CheckinId, v])) ?? []
if (customers.length > 0) {
let val = guestFolioCache.value
guestFolioCache.value = utils.deepMerge(val, { checkIns: Object.fromEntries(customers) })
}
if (((data.Content.PageIndex + 1 < pageCount) || pageCount < 0 || event.button == 2)
&& (data.Content.PageIndex + 1 < data.Content.PageCount))
setTimeout(() => loadCheckInList(pageIndex + 1), utils.getRandomIntInclusive(150, 250))
else {
CAT_UI.Message.success("缓存完毕")
if (event.button == 2) {
$(event.target).closest('button').click()
}
}
})
}
setTimeout(() => loadCheckInList(), utils.getRandomIntInclusive(150, 250))
}
// else if (/^[0-9]+$/.test($(event.target).text()?.trim()) && $(event.target).prev().text()?.includes('Ar账名称:') && event.button == 0) {
// let id = $(event.target).text()?.trim()
// let ar = $(event.target).prev().text()?.trim()?.replace('Ar账名称:', '')
// let billSource = {
// '携程': {
// url: 'https://ebooking.ctrip.com/ebkorderv2/api/order/domestic/orderDetail',
// method: 'POST',
// data: {
// "orderKey": null,
// "formID": 7146050347,
// "token": "",
// "sourceType": "Ebooking",
// "ctripOrderSourceType": "EBK",
// "hotel": 96281557,
// "ignoreReadLog": false,
// "orderID": "1132543394348607",
// "spiderkey": "1001-common-6UOKLTyZoWaBj1XrdUW50wF3yB5JOtxsSwA6E0FRHkro6yqUw8kJb7EGhYo6jcoynfjdpJtdyNzekOWO5vg8yL9yULyQ0Jf6JbZj7pin6Wg7ylaI0MiQYdmeOmKMnIQ3RNlycXwbkvgtJHOR3Xythv6NiGoyAmJg1y1HJkE87efzi0Yl4vAdY38emTJl0Way8Y6tJTgIhfxc4RM1yOLRUmRnPRPSRsBEGnva9JdgwXZvMSWhMYDkIMPWDMvnzESQJpXxhlYamvh5vLfeS9wfhi07eFmJnMy4HxnY51RhkEQkJhOjzkvLsvoXJLkvXmEUtwTZJ0Be9ov3ZwlkWAmYPsesOE7FwF3vaGyB0e8UjAtimHynZi8ceb4J56YZ5vL8JLPv3ARdZwgPJSOEHdYhbiObjPpR3fK5SIfY3sKFoYgJflw3ZylTYQgwqpjStwf8YkSwtmiGZrNTvOYQfIcZR6XWm5YQTE1NRkaJFBR5YkvmXWApxp9vhOeSFYadiQ9Y7leHav46epY8mRDwaQW0feQmEaPj5sWB0EdbW0aY7YOpjb0ihjnSrTDKp1e6QEtnWQdr4fRSMEMYghv9piU4rTnR5zvb8YPaWPNePbRQkW7mjoHWHlRsMRbhIqYFQvqlRb3e1TRMsv1fY3qWt1en4R67WZ0in0YthrcfIh3",
// "hoteluuidkeys": "",
// "spiderVersion": "2.0"
// }
// },
// '飞猪': {
// url: '',
// method: 'POST',
// data: {}
// },
// '抖音': {
// url: '',
// method: 'POST',
// data: {}
// },
// '小程序': {
// url: '',
// method: 'POST',
// data: {}
// },
// }
// }
})
})
const xhrOpen = XMLHttpRequest.prototype.open;
XMLHttpRequest.prototype.open = function () {
const xhr = this
let url = arguments[1].startsWith('http') ? new URL(arguments[1]) : new URL(`http://localhost/${arguments[1].replace(/^\//, '')}`)
xhr.addEventListener('load', () => apis.beyondh.asyncHandle(url, xhr))
const getter = Object.getOwnPropertyDescriptor(XMLHttpRequest.prototype, "responseText").get
if (GM_getValue('disableCreditType') && /GetCreditTypeName/.test(url.pathname)) {
// 仅显示AR帐及快钱支付
Object.defineProperty(xhr, "responseText", {
get: () => {
let result = JSON.parse(getter.call(xhr))
result.Content = result.Content.filter(v => /C9100|C9230/.test(v))
return JSON.stringify(result)
},
configurable: true
})
} else if (GM_getValue('别样红.allowUploadFaceImg') && /GetShowUploadFaceImageButton/.test(arguments[1])) {
// 显示上传图片按钮
const getter = Object.getOwnPropertyDescriptor(XMLHttpRequest.prototype, "responseText").get
Object.defineProperty(xhr, "responseText", {
get: () => {
let result = JSON.parse(getter.call(xhr))
result.Content = true
if (unsafeWindow.pms) {
unsafeWindow.pms['noEyes'] = GM_getValue('别样红.redactDetail')
}
elmGetter.get('.picture-uploader').then(div => {
setTimeout(() => {
if (div?.length > 0) {
var customRequest = (data) => {
let file = data.file
let newfile = new File([file], file.name.replace(/ /g, ''), { type: file.type })
newfile['uid'] = file.uid
let reader = new FileReader()
reader.readAsDataURL(newfile)
reader.onload = (e) => {
let props = utils.getReactStateNode($('div.ant-col-11 > div:nth-child(2)').get(0)).return.stateNode.props
apis.beyondh.UploadCustomerPhoto(props.customerDetail.CustomerId,
e.target.result.replace('data:image/jpeg;base64,', ''))
}
}
utils.getReactStateNode(div.get(0)).return.stateNode.customRequest = customRequest
utils.getReactStateNode(div.get(0)).memoizedProps.customRequest = customRequest
}
}, 500)
})
return JSON.stringify(result)
},
configurable: true
})
$(document.querySelector('div.ant-col-11 > div:nth-child(2)')).click(e => {
let inputLabel = $(e.target).closest('.ant-form-item').find('.ant-form-item-label').text()?.trim()
let inputContent = $(e.target).closest('.ant-form-item-control-input-content').text()?.trim()
if (`${inputContent}`.length > 0 && `${inputLabel}` == '客人姓名')
GM_setClipboard(inputContent)
})
const showFaceimg = async () => {
if (!GM_getValue('别样红.showFaceImg')) return
let elm = document.querySelector('div.ant-col-11 > div:nth-child(2)')
let props = utils.getReactStateNode(elm).return.stateNode.props
let src = 'data:image/png;base64,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'
let { checkinInfo, customerDetail } = props
let val = guestFolioCache.value
guestFolioCache.value = utils.deepMerge(val, { customers: { [customerDetail.CustomerId]: { ...customerDetail, expireTime: new Date().AddDays(3) } } })
if (customerDetail.PhotoStatus) {
apis.beyondh.GetCustomerForEdit(customerDetail.CustomerId).then(async xhr => {
if (xhr.response?.Content?.PhotoUrl)
src = xhr.response?.Content?.PhotoUrl
else
await apis.beyondh.GetCustomerPhoto(customerDetail.CustomerId).then(v => src = v.response?.Content)
if ($('#zjz').length == 0) {
let img = $(``)
img.find('img').on('load', function (event) {
let target = $(event.target)
let src = target.attr('src')
if (src?.includes('pmscos.beyondh.com/private')) {
let base64 = utils.getImageBase64(target.get(0))
if (utils.isBase64Image(base64)) {
let val = guestFolioCache.value
let key = new URL(src).pathname.split('/').pop()
let photo = { [key]: { base64: base64, expireTime: new Date().AddDays(3) } }
let photos = Object.entries(utils.deepMerge(val?.photos ?? {}, photo))?.filter(v => new Date() <= new Date(v[1].expireTime))
guestFolioCache.value = { ...val, photos: Object.fromEntries(photos) }
img.off('load')
}
}
})
$('form > div[class=ant-row]').append(img)
}
else
$('#zjz img').attr('src', src)
})
} else $('#zjz img').attr('src', src)
}
$('div.united_r div[owl-ignore] > div.cell_info').click(() => setTimeout(showFaceimg, 500))
setTimeout(showFaceimg, 300)
} else if (GM_getValue('别样红.showCleanStatu') && /GetMixSingleRoomOccupationInfo/.test(arguments[1])) {
// 增加已打扫按钮标记
let roomNumber = arguments[1].split('=')[1]
let btn = $(``)
$('.contextmenu_div_buttons').append(btn)
btn.find('button').click((ev) => {
let status = cleanStatus.value
status[roomNumber] == 'Cleaned' ? (delete status[roomNumber]) : status[roomNumber] = 'Cleaned'
cleanStatus.value = status
let element = utils.getReactStateNode($('.contextmenu_div').get(0))
element.memoizedProps.refreshRealTimeInfos()
element.memoizedProps.closeContextMenu()
})
} else if (/api\/Room\/GetMixAllRoomDetails/.test(arguments[1])) {
Object.defineProperty(xhr, "responseText", {
get: () => {
let result = getter.call(xhr)
let data = JSON.parse(result)
if (GM_getValue('别样红.showCleanStatu')) {
let status = cleanStatus.value
data.Content = data.Content.map(v => {
if (v.RoomStatus == 'VC' || v.RoomStatus == 'OC') delete status[`${v.RoomNumber}`] // 非脏房自动清除打扫标志
return { ...v, CleanStatus: status[`${v.RoomNumber}`] ?? null }
})
cleanStatus.value = status
}
return JSON.stringify(data)
},
configurable: true
})
}
return xhrOpen.apply(xhr, arguments)
}
const xhrSend = XMLHttpRequest.prototype.send;
XMLHttpRequest.prototype.send = function (data) {
const xhr = this
if (GM_getValue('别样红.allowUploadFaceImg') && /API\/Configure\/UploadFaceImage/.test(xhr.url)) {
// 上传住客图片
const getter = Object.getOwnPropertyDescriptor(XMLHttpRequest.prototype, "response").get
Object.defineProperty(xhr, "response", { get: () => JSON.stringify({ ...JSON.parse(getter.call(xhr)), Content: true }), configurable: true })
}
if (GM_getValue('别样红.preventAbnormalBilling') && $('.ant-modal-title').text() == '结账退房') {
if (/AddBillItem/.test(xhr.url)) {
let xhrSendBody = JSON.parse(data)
if (parseFloat(`${xhrSendBody.Amount}`) != 0) {
// 防止未清账误点结账
return null
}
}
// else if (/GenerateRoomRent/.test(xhr.url)) { TODO:房价未审核完的尽量先阻止自动生成房费
// let xhrSendBody = JSON.parse(data)
// let state = utils.getReactStateNode($('.checkin_info_summary').closest('div:not([class])')[0])?.stateNode?.state
// }
}
return xhrSend.apply(xhr, arguments)
}
} else if (location.host.includes('meituan')) {
const xhrOpen = XMLHttpRequest.prototype.open;
XMLHttpRequest.prototype.open = function () {
const xhr = this
const getter = Object.getOwnPropertyDescriptor(XMLHttpRequest.prototype, "responseText").get
if (GM_getValue('美团.displaySalePrice') && /queryListAndTag/.test(arguments[1])) {
Object.defineProperty(xhr, "responseText", {
get: () => {
let result = JSON.parse(getter.call(xhr))
result.data.realRoomRelations = result.data.realRoomRelations.map(v => {
v.logicRoomRelations.map(v2 => {
v2.goodsList = v2.goodsList.map(v3 => {
v3.priceChangeMode = 8
return v3
})
return v2
})
return v
})
return JSON.stringify(result)
},
configurable: true
})
}
return xhrOpen.apply(xhr, arguments)
}
} else if (location.host.includes('ctrip')) {
// 轮询检查最后一次调价记录,是否为携程调价助手自动调价
if (GM_getValue('携程.enableNotify'))
setInterval(() => {
apis.ctrip.getroompricereq().then(v => {
let data = v.response?.data?.reqList
if (data && data.length > 0) {
if (!/^EBK/.test(data[0].operater)) {
let reqId = data[0].reqId
if (GM_getValue('携程_lastPriceChange') != reqId) {
if (GM_getValue('携程.enableNotify'))
GM_notification(`${data[0].source}于${data[0].operateDate}发起调价`, '调价提醒')
GM_setValue('携程_lastPriceChange', reqId)
}
}
}
})
}, 1000 * 60 * 5)
const xhrOpen = XMLHttpRequest.prototype.open;
XMLHttpRequest.prototype.open = function () {
const xhr = this
const getter = Object.getOwnPropertyDescriptor(XMLHttpRequest.prototype, "responseText").get
if (GM_getValue('携程.displaySalePrice') && /ebkovsroom\/api\/inventory\/getRcProductList/.test(arguments[1])) {
const setRoomPPPriceAuthority = (room, value = 'T') => {
// origin: https://ws-s.tripcdn.cn/modules/EBooking/ebkroom-resource/1.1.69/js/inventory/calendarv9.js
// isHidePrice => ppPriceAuthority: T=显示卖价, F=隐藏卖价
return { ...room, roomInfos: room.roomInfos.map(info => ({ ...info, ppPriceAuthority: value })) }
}
Object.defineProperty(xhr, "responseText", {
get: () => {
let result = JSON.parse(getter.call(xhr))
result.data = result.data.map(rooms => {
if (Object.prototype.toString.call(rooms) == '[object Array]')
return rooms.map(room => setRoomPPPriceAuthority(room))
return setRoomPPPriceAuthority(rooms)
})
return JSON.stringify(result)
},
configurable: true
})
}
return xhrOpen.apply(xhr, arguments)
}
}
}
elmGetter.selector('jquery')
new CAT_UI()
// 综合面板
const config = new DataStore('config', {
panel: {
beyondh: { x: 0, y: 0, mini: true },
hdtyhotel: { x: 0, y: 0, mini: true },
ctrip: { x: 0, y: 0, mini: true },
}
})
const cookies = {
beyondh: new DataStore('cookie_beyondh', null), // 别样红
hdtyhotel: new DataStore('cookie_hdtyhotel', null), // 公安网
ctrip: new DataStore('cookie_ctrip', null), // 携程
}
Object.keys(cookies).forEach(v => {
cookies[v].Listener((target) => {
if (location.host.includes(v)) {
let data = { host: location.host, cookie: document.cookie }
if (location.host.includes('hdtyhotel')) {
data.appEnv = appEnv
data.session = Object.entries(sessionStorage).reduce((o, v) => {
try { o[v[0]] = JSON.parse(v[1]) } catch { o[v[0]] = v[1] }
return o
}, {})
}
target.value = data
}
})
})
const idCardCache = new IDCardCache()
const hrefKeyword = Object.keys(config.value.panel).filter(k => location.href.includes(k))[0]
const photoCache = new DataStore('photo_cache', [])
const guestFolioCache = new DataStore('guestFolio_cache', {})
const cleanStatus = new DataStore('cleanStatus_cache', {})
var publicData = null
options.loads()
main()
//暴露变量 方便调试
unsafeWindow.jQuery = $;
unsafeWindow.ScriptCat = {
// CAT_UI,
// React,
// ReactDOM,
// jsxLoader,
// ast,
// ModuleRaid, // var mR = new ScriptCat.ModuleRaid({ target: window, entrypoint: 'webpackJsonpmaxpms' })
GM_xmlhttpRequest,
GM_getValue,
ajax,
apis,
utils,
guestFolioCache
}