// ==UserScript== // @name 米尼优家辅助 // @namespace https://scriptcat.org/ // @description 连通多域页面,简化操作 // @version 0.1.7.1 // @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 // @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 // @grant unsafeWindow // @grant GM_xmlhttpRequest // @grant GM_setClipboard // @grant GM_setValue // @grant GM_getValue // @grant GM_openInTab // @grant GM_info // @grant GM_registerMenuCommand // @grant GM_unregisterMenuCommand // @grant GM_cookie // @noframes // ==/UserScript== // require https://bd-s.tripcdn.cn/modules/hotel/hotel-spider-defence-new/sdt.1001-common.min.9523a027cd7f61152c429eae5b56b319.js // 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(), expireTime: new Date().AddDays(3) } : v) : [...this.value, { ...idcard, date: new Date(), expireTime: new Date().AddDays(3) }] return this.value.length } remove(idCardNumber) { this.value = this.value.filter(v => v.cardnum !== idCardNumber) return this.value.length } removeExpire() { this.value = this.value.filter(v => new Date() <= v?.expireTime ? new Date(v.expireTime) : new Date(v.date).AddDays(3)) } 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: { showCustomerDetail: { toStr: (x) => '[别样红] 显示住客信息:' + (x ? '✓' : '✕') }, allowUploadFaceImg: { toStr: (x) => '[别样红] 允许上传图片:' + (x ? '✓' : '✕') }, disableCreditType: { toStr: (x) => '[别样红] 仅显示AR账快钱:' + (x ? '✓' : '✕') }, showEidPanel: { toStr: (x) => '[别样红] 显示电子证件缓存:' + (x ? '✓' : '✕') }, showCleanStatu: { toStr: (x) => '[别样红] 显示打扫标记:' + (x ? '✓' : '✕') }, showDetailContractName: { toStr: (x) => '[别样红] 显示中介明细:' + (x ? '✓' : '✕') }, modifyWidth: { toStr: (x) => '[公安网] 调整宽度:' + (x ? '✓' : '✕') }, includesGw: { toStr: (x) => '[公安网] 包含国外:' + (x ? '✓' : '✕') }, includesGat: { toStr: (x) => '[公安网] 包含港澳台:' + (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') }, 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) { 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 }) }) }, handleCache(url, xhr) { let searchParams = Object.fromEntries(url.searchParams.entries()) 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 } } 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) } } } if (unsafeWindow.pms) { if (url.pathname?.includes('GetCheckinCustomerDetail')) { // 修改全局规则为展示隐藏信息 unsafeWindow.pms['noEyes'] = GM_getValue('showCustomerDetail') } else if (/GuestFolio|QuickSearchCustomer|GetMixRoomFuturePreorders|getordermix|Crm\/Customer|orders\?operateType/.test(url.href)) { // 在触发查询客单时关闭展示隐藏信息开关,防止未知崩溃 unsafeWindow.pms['noEyes'] = false } } }, // 获取在住列表 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 ?? {} return this._request( 'GET', `/API/Customer/GetCustomerForEdit?CustomerId=${id}`, null, 'json', { Code: 0, Content: data?.[id] } ) }, // 上传客历图片 UploadCustomerPhoto(id, photoBase64) { return this._request('POST', '/API/Customer/UploadCustomerPhoto', { CustomerId: id, CustomerPhoto: photoBase64 }, 'json', null, GM_getValue('allowUploadFaceImg') ?? 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 }, }, 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-v24/' 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-v24/g, ""), "Authorization": encryptType === "base" ? session.getUserToken() : session.getZuulToken(), "AuthorizationHotel": session.getUserToken(), "Client-Type": appEnv.clientTYPE, "Encrypt": encryptEnabled, "Origin": config.baseURL, "Referer": `${config.baseURL}/hotelweb-v24/`, "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', '/A010000028', { 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: () => { if (GM_getValue('modifyWidth')) { $('.con-children-passenger').css('width', '500px') $('.con-children-room').css('width', 'calc(100% - 500px)') elmGetter.each('[class^="room-item room-"]', (e) => { $(e).css('width', '122px') }) } else { $('.con-children-passenger').css('width', '') $('.con-children-room').css('width', '') elmGetter.each('[class^="room-item room-"]', (e) => $(e).css('width', '')) } //双击打开明细 let roomPersonRef = $('.con-room')[0]?.__vue__?.$refs?.roomPersonRef elmGetter.each('[class^="room-item room-"]', (ele) => { $(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.ctrip, 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 // } // } // } } // 初始化面板 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: '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 = jQuery('.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) $(`.el-select-dropdown li.el-select-dropdown__item > span > span.hdty-tips:contains(${nation})`).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', '#ffae74') else $(v).css('background', '') }) } } const checkTableData = () => { // 对比高亮异常数据 let tb = publicData let diff_color = { checkIn: '#dcc7e1', checkOut: '#b2b6b6', changeRoom: '#ffae74', keepIn: '#ff6b81', none: '' } diff_color = { checkIn: '#ffae74', checkOut: '#ffae74', changeRoom: '#ffae74', keepIn: '#ffae74', none: '' } if (tb != null && tb.length > 0) { setTbConfig({ ...tbConfig, tbloading: true }) let diffData = [] tb.forEach((pv, pi) => { let byh = tbData.filter((ov, oi) => { if (ov.CustomerIdCardNumber?.toUpperCase() == pv.vo.cardId?.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 => new RegExp(tbConfig.searchValue).test([v.publicRoomNumber, v.RoomNumber, v.CustomerName].join('')) || (/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) 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) customer && (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: '20px', 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() onSearchValueChange(tbConfig.searchValue) let result = getter.call(xhr) // console.log(apis.hdtyhotel.interceptors().response({ config: { url: arguments[1] }, data: JSON.parse(result) })) return result }, }) } 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) }) let cardId = ret?.data?.data?.vo?.cardId if (cardId) { let form = $('[role="dialog"][aria-label="房间入住旅客信息"] form') let dialog = form.closest('[role="dialog"]') if (dialog.length == 1) { let id = 'scriptcat_uploadBtn' let uploadBtn = $(`#${id}`) if (uploadBtn.length == 0) { uploadBtn = $(``) dialog.find('.hdty-dialog-footer').prepend(uploadBtn) uploadBtn.click((e) => { let field = dialog.find('form > [style!="display: none;"] label:contains("证件号码")~div') if (field.length == 1) { let cardId = field.text().replace(/[^\w]/g, '') let res = tbData.filter(v => v.CustomerIdCardNumber?.toUpperCase() == cardId?.toUpperCase()) if (res.length == 1) { let id = res[0].CustomerId let src1 = dialog.find('form > [style!="display: none;"] img[data-pswp-uid=1]').attr('src') let src2 = dialog.find('form > [style!="display: none;"] img[data-pswp-uid=2]').attr('src') if (src1 != src2) { let confirmed = confirm("确定要继续上传吗?"); if (confirmed) { apis.beyondh.UploadCustomerPhoto(id, src1.replace(/data:image\/\w+;base64,/, '')).then((v) => { CAT_UI.Message.success("上传完毕") }, (v) => { CAT_UI.Message.info("上传失败") console.error(v) }) } else { CAT_UI.Message.info("已取消上传") } } else { CAT_UI.Message.info("图片内容一致") } } else { CAT_UI.Message.info("未匹配到在住单") console.log(res) } } }) } let res = tbData.filter(v => v.CustomerIdCardNumber?.toUpperCase() == cardId?.toUpperCase()) let roomPersonRef = $('.con-room')[0]?.__vue__?.$refs?.roomPersonRef if (res.length > 0 && roomPersonRef) { if (!utils.isEmpty(res[0].CustomerPhotoUrl)) { if (utils.isBase64Image(res[0].CustomerPhotoUrl)) { setTimeout(() => { let data = res[0].CustomerPhotoUrl let img = data.replace(/data:image\/\w+;base64,/, '') roomPersonRef.$set(roomPersonRef.$data, 'currentGnAndGatScenePhotoAll', data) roomPersonRef.$set(roomPersonRef.$data.formData.vo.list, '1', { base64: img, flag: '1' }) }, 300) } else if (/^http/.test(res[0].CustomerPhotoUrl)) { (async () => { let base64 = null await apis.beyondh.GetPhotoData(res[0].CustomerPhotoUrl).then(v => base64 = v.response) if (utils.isBase64Image(base64)) { setTimeout(() => { let img = base64.replace(/data:image\/\w+;base64,/, '') roomPersonRef.$set(roomPersonRef.$data, 'currentGnAndGatScenePhotoAll', base64) roomPersonRef.$set(roomPersonRef.$data.formData.vo.list, '1', { base64: img, flag: '1' }) }, 300) } else { await utils.blobToBase64(base64).then((data) => { let img = data.replace(/data:image\/\w+;base64,/, '') roomPersonRef.$set(roomPersonRef.$data, 'currentGnAndGatScenePhotoAll', data) roomPersonRef.$set(roomPersonRef.$data.formData.vo.list, '1', { base64: img, flag: '1' }) }) } })() } } } } } return result }, }) } 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 }, }) } } 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) } } if ($(e.target).text()?.trim() == '刷新房态图') { // 仅在点击按钮时缓存两边房态 loadHdtyCheckInList().then(() => onTableDataChange().then(() => { publicData ? checkTableData() : renderDiffRoom() })) } } }) } return CAT_UI.Space([ CAT_UI.Space([ CAT_UI.Input({ 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({ 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({ defaultChecked: tbConfig.includesCheckOut, checkedText: '含退房', uncheckedText: '仅入住', onChange: (val) => { tbConfig.includesCheckOut = val onSearchValueChange(tbConfig.searchValue) } }) }), CAT_UI.Button('暂存', { type: 'primary', onClick: (e) => { let src = jQuery('.con-hardware')[0].__vue__._data.photoZjz if (src != null && src.length > 0 && photoCache.value.every(v => v.src != src)) { let formElement = jQuery('#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: jQuery('#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 - 120 }, 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.value = idCardCache.value.filter(v => (new Date().getTime() - new Date(v.date).getTime()) / (1000 * 3600 * 24) <= 3) 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: 150, ellipsis: true, align: 'center', render: (col, record, index) => `${record?._origin?.facenum ? '网证' : '卡证'} ${record.IdCardNumber.slice(0, 6) + '**' + record.IdCardNumber.slice(14, 18)}` }, { 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('showEidPanel')) 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('showEidPanel')) eidPanel = create_eidPanel(20, rect1.bottom, 580, 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 }) const xhrOpen = XMLHttpRequest.prototype.open; XMLHttpRequest.prototype.open = function () { const xhr = this let url = new URL(arguments[1]) xhr.addEventListener('load', () => apis.beyondh.handleCache(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) }, }) } 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('showCustomerDetail') } elmGetter.get('.picture-uploader').then(div => { setTimeout(() => { let props = utils.getReactStateNode($('div.ant-col-11 > div:nth-child(2)').get(0)).return.stateNode.props 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) => { 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) }, }) $(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) }) } 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 (GM_getValue('showCleanStatu') && /api\/Room\/GetMixAllRoomDetails/.test(arguments[1])) { const sum = (arr) => { if (arr == undefined) return undefined let res = 0 for (let i of arr) res += i return res } Object.defineProperty(xhr, "responseText", { get: () => { let result = getter.call(xhr) let roomInfo = JSON.parse(result) let status = cleanStatus.value roomInfo.Content = roomInfo.Content.map(v => { // 非脏房自动清除打扫标志 if (v.RoomStatus == 'VC' || v.RoomStatus == 'OC') { delete status[v.RoomNumber] } return { ...v, CleanStatus: status[v.RoomNumber] } }) cleanStatus.value = status const handle = async () => { let roomSet = {}, groupSet = {}, roomDetails await apis.beyondh.SearchGuestFolioCheckIn().then(v => roomDetails = v.response.Content.Content) for (let i = 0; i < roomInfo.Content.length; i++) { let id = parseInt(roomInfo.Content[i].RoomNumber) 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] if (roomInfo.Content[i].GroupOrRelationNumber != -1) { let groupNumber = roomInfo.Content[i].GroupOrRelationNumber if (groupNumber in groupSet) { groupSet[groupNumber].push(roomInfo.Content[i]) } else { groupSet[groupNumber] = [roomInfo.Content[i]] } } } // for (let k of Object.keys(groupSet)) { // if (groupSet[k].length == 1) { // let checkinId = groupSet[k][0].CheckinId // let menulist = apis.GetCheckinMenuModelMix(checkinId) // if (menulist?.Content?.MenuItemList?.length > 1) { // // console.log(menulist?.Content?.MenuItemList.filter(v => v.CheckinId != checkinId)) // groupSet[k].push(...(menulist?.Content?.MenuItemList.filter(v => v.CheckinId != checkinId).map(v => ({ ...v, GroupOrRelationNumber: parseInt(k) })) ?? [])) // } // } // } 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('介') } } } }) }) }) } handle() let iconFilter = $('[owl-ignore="true"][class="row_dl real_time"] > dd[class="filter clear_float"]') let filters = { xie: { name: '赫程', icon: '赫', regex: /^赫/, isToggle: false }, vip: { name: '会员', icon: '会', regex: /^[会银金钻]/, isToggle: false }, offline: { name: '会员', icon: '小', regex: /^[小直]/, isToggle: false }, other: { name: '中介', icon: '介', regex: /^介/, isToggle: false }, } if (iconFilter.length > 0 && GM_getValue('showDetailContractName')) { let other = iconFilter.find('span:contains("其它")').closest('div') other.click(e => { if (other.attr('class') != 'select_tag selected') { Object.keys(filters).forEach(k => { let flt = filters[k] if ($(`#scriptcat_filter_${k}`).length == 0) { let id = `scriptcat_filter_${k}` let ele = $(`