别样红辅助(大道店)
// ==UserScript==
// @name 别样红辅助(大道店)
// @namespace https://bbs.tampermonkey.net.cn/
// @version 0.4.3
// @description 别样红辅助
// @author EmpyrealTear
// @icon https://c1.beyondh.com/maxpms/5.2.1.2/favicon.ico
// @match .*://mn.beyondh.com:8101/*
// @require https://cdn.bootcdn.net/ajax/libs/jquery/3.7.1/jquery.min.js
// @require https://scriptcat.org/lib/513/2.0.0/ElementGetter.js
// @grant unsafeWindow
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_registerMenuCommand
// @grant GM_unregisterMenuCommand
// ==/UserScript==
(function () {
'use strict'
const options = {
menus: {
isShowCleaner: { toStr: (x) => '显示保洁:' + (x ? '✓' : '✕') },
isShowDetailContractName: { toStr: (x) => '显示中介明细:' + (x ? '✓' : '✕') },
isHighlightBalanceltPrice: { toStr: (x) => '高亮余额<房价:' + (x ? '✓' : '✕') },
isHighlightBalancegtPrice: { toStr: (x) => '高亮余额>房价:' + (x ? '✓' : '✕') },
isShowGuestRecords: { toStr: (x) => '显示客历入住:' + (x ? '✓' : '✕') },
isAllowUploadFaceImg: { toStr: (x) => '显示上传照片:' + (x ? '✓' : '✕') },
isShowFaceImg: { toStr: (x) => '显示住客照片:' + (x ? '✓' : '✕') },
},
register: function (name) {
let val = GM_getValue(name)
this.menus[name][!val ? '_new' : '_old'] = GM_registerMenuCommand(this.menus[name].toStr(val), () => {
GM_setValue(name, !val)
this.register(name)
})
GM_unregisterMenuCommand(this.menus[name][val ? '_new' : '_old'])
},
loads: function () { Object.keys(this.menus).forEach(v => this.register(v)) }
}
options.loads()
const getReactStateNode = (dom) => {
let key = Object.keys(dom).find(key => key.startsWith("__reactInternalInstance$"))
let internalInstance = dom[key]
if (internalInstance == null) return null
return internalInstance._debugOwner ?? internalInstance.return ?? internalInstance._currentElement._owner
}
const sum = (arr) => {
if (arr == undefined)
return undefined
let res = 0
for (let i of arr)
res += i
return res
}
const getCleaner = (id) => {
if ((id >= 301 && id <= 311) || (id >= 601 && id <= 611))
return '谢'
else if ((id >= 401 && id <= 411) || id == 706 || (id >= 708 && id <= 711) || (id >= 807 && id <= 812))
return '易'
else
return '段'
}
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
}
}
const apis = {
GetMixAllRoomDetails: () => {
// 获取房态信息
let res = ajax.GET('https://mn.beyondh.com:8111/api/Room/GetMixAllRoomDetails')
if (res == null) return res
for (let i = 0; i < res.Content.length; i++) {
let id = parseInt(res.Content[i].RoomNumber)
res.Content[i].Cleaner = getCleaner(id)
}
return res
},
GetMixSingleRoomOccupationInfo: (id) => {
// 获取房间摘要信息
return ajax.GET(`https://mn.beyondh.com:8111/api/Room/GetMixSingleRoomOccupationInfo?roomNumber=${id}`)
},
SearchGuestFolioCheckIn: () => {
// 获取住客在住单
return ajax.GET(`https://mn.beyondh.com:8111/API/GuestFolio/SearchGuestFolio?PageSize=100&PageIndex=1&IsDesc=false&Status%5B%5D=I&IsMeiTuanOrder=false&DateRangeType=RecentWithOneYear`)
},
SearchGuestFolioCheckOut: (keywords, pageIndex = 1, pageSize = 10) => {
// 获取住客退房单(不含挂账单)
return ajax.GET(`https://mn.beyondh.com:8111/API/GuestFolio/SearchGuestFolio?PageSize=${pageSize}&PageIndex=${pageIndex}&IsDesc=false&Status%5B%5D=O&Keywords=${keywords}&IsMeiTuanOrder=false&DateRangeType=RecentWithOneYear`)
},
GetCustomerForEdit: (id) => {
// 获取住客信息
return ajax.GET(`https://mn.beyondh.com:8111/API/Customer/GetCustomerForEdit?CustomerId=${id}`)
},
GetBillStatistics: (billIds) => {
// 获取订单收付总额
return ajax.POST(
`https://mn.beyondh.com:8111/API/Bill/GetBillStatistics`,
Object.prototype.toString.call(billIds) === '[object Array]' ? billIds : [billIds]
)
},
GetCustomerPhoto: (id) => {
// 获取住客照片
return ajax.GET(`https://mn.beyondh.com:8111/API/Customer/GetCustomerPhoto?customerId=${id}`)
},
UploadCustomerPhoto: (id, photoBase64) => {
// 上传住客照片
return ajax.POST(
`https://mn.beyondh.com:8111/API/Customer/UploadCustomerPhoto`, {
CustomerId: id,
CustomerPhoto: photoBase64
})
},
}
unsafeWindow.jQuery = $
unsafeWindow.getReactStateNode = getReactStateNode
unsafeWindow.apis = apis
elmGetter.selector('jquery')
const xhrSend = XMLHttpRequest.prototype.send;
const CheckinType = { Normal: '正常', Hour2: '2H', Hour4: '4H', Hour8: '8H', Internal: '自用', LongTerm: '长包', Free: '免费' }
XMLHttpRequest.prototype.send = function (data) {
const xhr = this
if (GM_getValue('isShowGuestRecords') && /API\/Customer\/SearchCustomerCheckinsByPost/.test(xhr.url)) {
// 修复客历无法显示历史入住记录
let xhrSendBody = JSON.parse(data)
// let idCardNumber = apis.GetCustomerForEdit(xhrSendBody.CustomerId).Content.IdCardNumber
let props = getReactStateNode(jQuery('div.ant-col-11 > div:nth-child(2)')[0]).return.stateNode.props
let idCardNumber = props.customerDetail.IdCardNumber
let checkouts = apis.SearchGuestFolioCheckOut(idCardNumber, xhrSendBody.PageIndex, xhrSendBody.PageSize)
const getter = Object.getOwnPropertyDescriptor(XMLHttpRequest.prototype, "responseText").get
Object.defineProperty(xhr, "responseText", {
get: () => {
let result = JSON.parse(getter.call(xhr))
let rows = []
for (let i of checkouts.Content.Content) {
let detail = i.GuestFolioDetailModels[0]
let bill = undefined // apis.GetBillStatistics([i.BillId])
rows.push({
RoomNumber: detail.RoomNumber[0], // 房号
ArriveTime: i.ArriveTime, // 抵店时间
DepatureTime: i.DepatureTime, // 离店时间
Days: i.CheckinTypeId.startsWith('Hour') ? CheckinType[i.CheckinTypeId] : detail.DailyPrices.length, // 天数
PriceRoomTypeName: `${detail.ControlRoomTypeName}`, // 房型
FirstDayPrice: detail.FirstDayPrice, // 房价
DailyPrices: detail.DailyPrices, // 房价
RoomChargeAmount: bill == undefined ? (i.Contract.Name ?? '散客') : bill.Content?.CreditAmount, // 总房费
OtherChargeAmount: i.CheckinMemo, // 其他消费
})
}
result.Content = { ...checkouts.Content, Content: rows }
return JSON.stringify(result)
},
})
} else if (GM_getValue('isAllowUploadFaceImg') && /API\/Configure\/UploadFaceImage/.test(xhr.url)) {
// 上传住客图片
let props = getReactStateNode(jQuery('div.ant-col-11 > div:nth-child(2)')[0]).return.stateNode.props
let reader = new FileReader()
reader.readAsDataURL(data)
reader.onload = (e) => apis.UploadCustomerPhoto(props.customerDetail.CustomerId, e.target.result.replace('data:image/jpeg;base64,', ''))
const getter = Object.getOwnPropertyDescriptor(XMLHttpRequest.prototype, "response").get
Object.defineProperty(xhr, "response", {
get: () => {
let result = JSON.parse(getter.call(xhr))
if (result.Content == false)
result = { Code: 0, Message: "上传完毕", Content: true }
return JSON.stringify(result)
}
})
}
return xhrSend.apply(xhr, arguments)
}
const xhrOpen = XMLHttpRequest.prototype.open;
XMLHttpRequest.prototype.open = function () {
const xhr = this
if (/api\/Room\/GetMixAllRoomDetails/.test(arguments[1])) {
const getter = Object.getOwnPropertyDescriptor(XMLHttpRequest.prototype, "responseText").get
Object.defineProperty(xhr, "responseText", {
get: () => {
let result = getter.call(xhr)
let roomInfo = JSON.parse(result)
let roomSet = {}
let roomDetails = apis.SearchGuestFolioCheckIn().Content.Content
for (let i = 0; i < roomInfo.Content.length; i++) {
let id = parseInt(roomInfo.Content[i].RoomNumber)
roomInfo.Content[i].Cleaner = getCleaner(id)
let guests = roomDetails
.filter(v => v.GuestFolioDetailModels[0].RoomNumber.some(num => num == roomInfo.Content[i].RoomNumber))
roomInfo.Content[i].Remark = guests.map(v => v.CheckinMemo).join(';')
if (guests.length > 0) {
roomInfo.Content[i].Balance = sum(guests.map(v => v.Balance))
roomInfo.Content[i].TodayPrice = guests[0].GuestFolioDetailModels[0].FirstDayPrice.ActualPrice
roomInfo.Content[i].DailyPrices = guests[0].GuestFolioDetailModels[0].DailyPrices
}
roomSet[roomInfo.Content[i].RoomNumber] = roomInfo.Content[i]
}
// console.log(roomSet)
let cleans = {}
let rooms = roomInfo.Content
for (let i = 0; i < rooms.length; i++) {
let k = rooms[i].Cleaner
if (!(k in cleans))
cleans[k] = 0
// VD=空脏; VC=空净; V_C=空夜; OOO=维修; OK=已检; OD=住脏; OC=住净; ED=预退;
if (rooms[i].CustomerStatus == 'IsEstArrival' || /VD|OD|OC/.test(rooms[i].RoomStatus))
// if (rooms[i].CustomerStatus == 'IsEstArrival' || /OD|OC/.test(rooms[i].RoomStatus))
cleans[k] += 1
}
// 统计需保洁数量
jQuery('#cleaner').remove()
jQuery('div>div.small:contains("出租率")').parent().prepend(`<div id='cleaner' style='padding: 0 3px;font-size: smaller;'>(${Object.keys(cleans).map((v) => `${v}:${cleans[v]}`).join(';')}) </div>`)
elmGetter.each('.room_number_default', (e) => {
let roomId = e.text()
let id = roomId.replace(/\s\(.+\)/, '')
// 添加保洁显示
if (GM_getValue('isShowCleaner')) {
if (!/[\(\)]/.test(roomId))
e.text(`${roomId} (${roomSet[roomId].Cleaner})`)
} else {
if (/[\(\)]/.test(roomId))
e.text(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('isShowDetailContractName')) {
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)) {
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 roomInfo = roomSet[id]
// 高亮余额≠房价
if (GM_getValue('isHighlightBalanceltPrice') && roomInfo.Balance != undefined) {
// 每日5:50后完成夜审
let nowPrice = roomInfo.DailyPrices?.filter(v => new Date(v.Date).AddDays(1).AddMinutes(60 * 6 - 10) > Date.now())[0]?.ActualPrice
if (roomInfo.Balance < (nowPrice == undefined ? 0 : nowPrice) && !/免费/.test(roomInfo.Remark)) {
e.parent().css('border', '2px dashed #f24e4c')
} else if (GM_getValue('isHighlightBalancegtPrice') && roomInfo.Balance > (nowPrice == undefined ? 0 : nowPrice)) {
e.parent().css('border', '2px dashed #fed100')
} else e.parent().css('border', '')
} else e.parent().css('border', '')
})
return result
},
})
} else if (GM_getValue('isAllowUploadFaceImg') && /API\/Checkin\/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
return JSON.stringify(result)
},
})
// 显示住客照片
const showFaceimg = () => {
if (!GM_getValue('isShowFaceImg')) return
let elm = document.querySelector('div.ant-col-11 > div:nth-child(2)')
let props = getReactStateNode(elm).return.stateNode.props
let src = 'data:image/png;base64,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'
if (props.customerDetail.PhotoStatus)
src = apis.GetCustomerPhoto(props.customerDetail.CustomerId).Content
if (jQuery('#zjz').length == 0)
jQuery('form > div[class=ant-row]').append(jQuery(`<div id="zjz" class="ant-col ant-col-24">
<div class="ant-row ant-form-item text" style="row-gap: 0px;">
<div class="ant-col ant-col-4 ant-form-item-label"><label class="" title="证件照片">证件照片</label></div>
<div class="ant-col ant-col-20 ant-form-item-control"><img src="${src}" style="width: 102px;"></div>
</div></div>`))
else
jQuery('#zjz img').attr('src', src)
}
jQuery('div.united_r div[owl-ignore] > div.cell_info').click(() => setTimeout(showFaceimg, 200))
setTimeout(showFaceimg, 200)
}
return xhrOpen.apply(xhr, arguments)
}
})();