// ==UserScript== // @name 米尼优家辅助-公安网 // @namespace https://scriptcat.org/ // @description 连通多域页面,简化操作 // @version 0.1.0 // @author EmpyrealTear // @icon https://c1.beyondh.com/maxpms/5.2.1.2/favicon.ico // @match .*://mn.beyondh.com:8101/* // @connect mn.beyondh.com // @match .*://jx.hdtyhotel.com:8000/* // @connect jx.hdtyhotel.com // @require https://scriptcat.org/lib/1167/1.0.0/%E8%84%9A%E6%9C%AC%E7%8C%ABUI%E5%BA%93.js // @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 // @require https://scriptcat.org/lib/2628/6.2.0.1/moduleRaid.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== const options = { menus: { isFixWidth: { toStr: (x) => '调整宽度:' + (x ? '✓' : '✕') }, isMinPanel: { toStr: (x) => '最小化面板:' + (x ? '✓' : '✕') }, }, loads: function () { Object.keys(this.menus).forEach(v => { let val = GM_getValue(v) this.menus[v]['_menu'] = GM_registerMenuCommand(this.menus[v].toStr(val), () => { GM_setValue(v, !val) Object.keys(this.menus).forEach(v => GM_unregisterMenuCommand(this.menus[v]['_menu'])) this.loads() }) }) } } class DataBase { constructor(key, data) { this.key = key this.data = data this.Init().Save() } get value() { return this.data } set value(val) { this.data = val; this.Save() } Init() { let val = GM_getValue(this.key) if (val != null) this.data = JSON.parse(val) return this } Save() { GM_setValue(this.key, JSON.stringify(this.data)) return this } Listener(resolve, timeout = 1000) { setInterval(() => resolve(this), timeout) return this } } const ast = new CAT_UI(); 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) => { return new Promise((resolve, reject) => { $.ajax({ url: url, type: 'GET', xhrFields: { responseType: 'blob' }, success: (e) => resolve(e), error: (e) => reject(e) }) }) }, GM_GET: (url, cookie) => { return new Promise((resolve, reject) => { GM_xmlhttpRequest({ method: 'GET', url: url, cookie: cookie, responseType: 'json', onload: (xhr) => resolve(xhr), onerror: (err) => reject(err) }) }) }, GM_POST: (url, data, cookie) => { return new Promise((resolve, reject) => { GM_xmlhttpRequest({ method: 'POST', url: url, data: JSON.stringify(data), cookie: cookie, headers: { 'content-type': 'application/json' }, responseType: 'json', onload: (xhr) => resolve(xhr), onerror: (err) => reject(err) }) }) }, GM_GETBlob: (url, cookie) => { return new Promise((resolve, reject) => { GM_xmlhttpRequest({ method: 'GET', url: url, cookie: cookie, responseType: 'blob', onload: (xhr) => resolve(xhr), onerror: (err) => reject(err) }) }) }, } const utils = { isEmptyString: (str) => { return !(str != null && str.trim().length > 0) }, fixWidth: () => { if (GM_getValue('isFixWidth')) { $('.con-children-passenger').css('width', '500px') $('.con-children-room').css('width', 'calc(100% - 500px)') elmGetter.each('[class^="room-item room-"]', (e) => { $(e).css('width', '126px') }) } else { $('.con-children-passenger').css('width', '') $('.con-children-room').css('width', '') elmGetter.each('[class^="room-item room-"]', (e) => $(e).css('width', '')) } //双击打开明细 let roomPersonRef = jQuery('.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 = jQuery('.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 } }) }) }, blobToBase64: (blob) => { return new Promise((resolve, reject) => { let rd = new FileReader() rd.onload = (e) => { resolve(e.target.result) } rd.readAsDataURL(blob) }) }, uuid: () => { let d = new Date().getTime(); let uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { let r = (d + Math.random() * 16) % 16 | 0 d = Math.floor(d / 16) return (c == 'x' ? r : (r & 0x3 | 0x8)).toString(16); }) return uuid }, isBase64Image: (str) => { let regex = /^data:image\/(png|jpg|jpeg|gif);base64,/ return str != null && str.length > 0 && regex.test(str) }, asyncPool: async (arr, process = (item, arr) => item, begin = (v) => v, end = (v) => v, poolLimit = 10, percentComplete = (pct) => pct) => { let ret = [] let executing = new Set() let arr_res = new Array(arr.length) let completeCount = 0 // begin percentComplete(completeCount / arr.length * 100) arr = begin(arr) // process for (let [index, item] of arr.entries()) { var p = Promise.resolve().then(async () => { try { let res = await process(item, arr) arr_res[index] = res } catch (err) { console.warn(err) arr_res[index] = err } return }).finally(() => { percentComplete((++completeCount) / arr.length * 100) }) ret.push(p) executing.add(p) let clean = () => executing.delete(p) p.then(clean).catch(clean) if (executing.size >= poolLimit) { await Promise.race(executing) } } // end return Promise.all(ret).then(() => { percentComplete(completeCount / arr.length * 100) return end(arr_res) }) } } const apis = { beyondh: { SearchGuestFolioCheckIn: () => { // 获取住客在住单 return ajax.GM_GET(`https://mn.beyondh.com:8111/API/GuestFolio/SearchGuestFolio?PageSize=100&PageIndex=1&IsDesc=false&Status%5B%5D=I&IsMeiTuanOrder=false&DateRangeType=RecentWithOneYear`, cookies.beyondh.value) }, GetCustomerForEdit: (id) => { // 获取住客信息 return ajax.GM_GET(`https://mn.beyondh.com:8111/API/Customer/GetCustomerForEdit?CustomerId=${id}`, cookies.beyondh.value) }, UploadCustomerPhoto: (id, photoBase64) => { // 上传住客照片 return ajax.GM_POST( `https://mn.beyondh.com:8111/API/Customer/UploadCustomerPhoto`, { CustomerId: id, CustomerPhoto: photoBase64 }, cookies.beyondh.value) }, } } //暴露变量 方便调试 unsafeWindow.jQuery = $; // unsafeWindow.CAT_UI = CAT_UI; // unsafeWindow.React = React; // unsafeWindow.ReactDOM = ReactDOM; // unsafeWindow.jsxLoader = jsxLoader; // unsafeWindow.ast = ast; unsafeWindow.ModuleRaid = ModuleRaid; unsafeWindow.ajax = ajax; options.loads() // 综合面板 var config = new DataBase('config', { headerPoint: { x: 1406, y: 728 }, isMini: true, }) var cookies = { beyondh: new DataBase('cookie_beyndh', null), hdtyhotel: new DataBase('cookie_hdtyhotel', null) } Object.keys(cookies).forEach(v => { cookies[v].Listener((target) => { if (location.host.includes(v)) target.value = document.cookie }) }) var photoCache = new DataBase('photo_cache', []) var publicData = null // 初始化面板 function initPanel(x, y) { let init = false let tbData = [] let setTbConfig, tbConfig = { data: [], tbloading: false, searchValue: '', multipleSearch: false, publicDataLoading: false } let tbCols = [ // { title: 'publicID', dataIndex: 'publicId', key: 'publicId', width: 100, ellipsis: true, align: 'left' }, // { title: 'ID', dataIndex: 'CheckinId', key: 'CheckinId', width: 100, ellipsis: true, align: 'left' }, // { title: '类型', dataIndex: 'CheckinType', key: 'CheckinType', width: 100, ellipsis: true, align: 'center' }, { title: '公安号', dataIndex: 'publicRoomNumber', key: 'publicRoomNumber', width: 80, ellipsis: true, align: 'center' }, { title: '房态号', dataIndex: 'RoomNumber', key: 'RoomNumber', width: 80, ellipsis: true, align: 'center' }, // { title: '客源', dataIndex: 'ContractName', key: 'ContractName', width: 100, ellipsis: true, align: 'center' }, // { title: '客人ID', dataIndex: 'CustomerId', key: 'CustomerId', width: 100, ellipsis: true, align: 'left' }, { title: '姓名', dataIndex: 'CustomerName', key: 'CustomerName', ellipsis: true, align: 'center' }, // { title: '证件号', dataIndex: 'CustomerIdCardNumber', key: 'CustomerIdCardNumber', width: 200, ellipsis: true, align: 'left' }, // { title: '住址', dataIndex: 'CustomerAddress', key: 'CustomerAddress', width: 300, ellipsis: true, align: 'left' }, // { title: '证件照', dataIndex: 'CustomerPhotoUrl', key: 'CustomerPhotoUrl', width: 300, ellipsis: true, align: 'left' }, { title: '证件照', dataIndex: 'CustomerPhotoUrl', key: 'CustomerPhotoUrl', width: 70, ellipsis: true, align: 'left' }, { title: '操作', dataIndex: 'operation', width: 140, align: 'center', render: (col, record, index) => { 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.isEmptyString(record.CustomerName)) setFormItem('name', record.CustomerName) if (!utils.isEmptyString(record.CustomerIdCardNumber)) setFormItem('cardId', record.CustomerIdCardNumber) if (!utils.isEmptyString(record.CustomerAddress)) setFormItem('address', record.CustomerAddress) if (!utils.isEmptyString(record.CustomerPhotoUrl)) if (utils.isBase64Image(record.CustomerPhotoUrl)) img.__vue__._data.photoZjz = record.CustomerPhotoUrl else if (/^http/.test(record.CustomerPhotoUrl)) { ajax.GETBlob(record.CustomerPhotoUrl).then(v => { utils.blobToBase64(v).then((data) => { img.__vue__._data.photoZjz = data }) }) } setFormItem('nation', null) $('.el-select-dropdown li.el-select-dropdown__item > span > span:contains("汉族")').click() } }) ] if (record.UUID != null) opts.push(CAT_UI.Button('删除', { size: 'mini', onClick: () => { photoCache.value = photoCache.value.filter(v => v.UUID != record.UUID) onTableDataChange() } })) return opts }, }, ] let timerSearch = null const renderDiffRoom = () => { if (tbData.length > 0) { let roomGroups = {} tbData.forEach(v => { 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 onSearchValueChange = (value) => { setTbConfig({ ...tbConfig, searchValue: value }) if (timerSearch) { clearTimeout(timerSearch) } timerSearch = setTimeout(() => { onTableDataChange().then(() => { // 对比人数 if (publicData) checkTableData() else renderDiffRoom() }) }, 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) return JSON.stringify(a).localeCompare(JSON.stringify(b)) else return roomA - roomB } else { return -1 } }) let filterData = [...photoCache.value, ...tbData] 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))) } setTbConfig({ ...tbConfig, data: filterData, tbloading: false }) return arr } const onTableDataChange = () => { return apis.beyondh.SearchGuestFolioCheckIn().then(async (xhr) => { tbData = [] let data = xhr.response?.Content?.Content if (data) { 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, RoomNumber: v.GuestFolioDetailModels[0].RoomNumber[0] } await apis.beyondh.GetCustomerForEdit(v.CustomerInfo.CustomerId).then((xhr2) => { let customerInfo = xhr2.response?.Content row = { ...row, CustomerPhotoUrl: customerInfo?.PhotoUrl, CustomerAddress: customerInfo?.Address, } }) return row }, arr => { setTbConfig({ ...tbConfig, tbloading: true }) return arr }, filterTbData) } }) } const checkTableData = () => { let tb = publicData 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, }) } }) tbData = [...tbData, ...diffData] filterTbData(tbData) let diff = tbData.filter(v => v.publicRoomNumber != v.RoomNumber) let rooms = $('.room-item') rooms.each((i, v) => { let vals = $(v).find('.value') let room = vals[0].textContent if (diff.some(v => v.publicRoomNumber == room || v.RoomNumber == room)) $(v).css('background', '#ffae74') else $(v).css('background', '') }) } } CAT_UI.createPanel({ min: GM_getValue('isMinPanel'), 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 (/\/A010000004/.test(arguments[1])) { const getter = Object.getOwnPropertyDescriptor(XMLHttpRequest.prototype, "responseText").get Object.defineProperty(xhr, "responseText", { get: () => { utils.fixWidth() let result = getter.call(xhr) return result }, }) publicData = null setTbConfig({ ...tbConfig, publicDataLoading: true }) const mR = new ModuleRaid({ target: unsafeWindow }) let results = mR.findConstructor('A010000027') if (results.length > 0) { let ajaxPost = results[0][1]?.exports?.j let size = 10 let zuulCompanyInfo = JSON.parse(unsafeWindow.sessionStorage.getItem('hdty-hotel-bs:zuulCompanyInfo')) let formdata = { guestFlag: "0", andor: "and", current: 1, size: size, companyId: zuulCompanyInfo.companyId, orgId: zuulCompanyInfo.orgId } ajaxPost(formdata).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 ajaxPost({ ...formdata, current: 1 + item }).then(e => tb = e?.data?.data?.records) return tb }) retex.forEach(x => { if (x != null && x.length > 0) ret.push(...x) }) } publicData = ret CAT_UI.Message.success("公安现住人员缓存完毕") onSearchValueChange(tbConfig.searchValue) setTbConfig({ ...tbConfig, publicDataLoading: false }) } }) } } return xhrOpen.apply(xhr, arguments) } onTableDataChange() 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.isEmptyString(v)).sort().join('|') ) } else { onSearchValueChange(id) } } } }) const mR = new ModuleRaid({ target: unsafeWindow }) var axiosRequestUse = mR.findConstructor('_.interceptors.request.use')[0][1].exports.a.interceptors.request.handlers if (axiosRequestUse.length > 0) { let originAxiosRequestUse = axiosRequestUse[0].fulfilled axiosRequestUse[0].fulfilled = function () { let _this = this let ret = originAxiosRequestUse.apply(_this, arguments) if (/A010000027/.test(arguments[0].url)) ret.cancelToken = null return ret } } var axiosResponseUse = mR.findConstructor('_.interceptors.response.use')[0][1].exports.a.interceptors.response.handlers if (axiosResponseUse.length > 0) { let originAxiosResponseUse = axiosResponseUse[0].fulfilled axiosResponseUse[0].fulfilled = async function () { let _this = this let ret = originAxiosResponseUse.apply(_this, arguments) if (/A010000028/.test(arguments[0].request.responseURL)) { let cardId = ret?.data?.data?.vo?.cardId if (cardId) { let dialog = $('[role="dialog"][aria-label="房间入住旅客信息"] 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()) if (res.length > 0) { if (!utils.isEmptyString(res[0].CustomerPhotoUrl)) { let base64 = null await ajax.GETBlob(res[0].CustomerPhotoUrl).then(v => base64 = v) if (base64) await utils.blobToBase64(base64).then((data) => { let img = data.replace(/data:image\/\w+;base64,/, '') ret.data.data.vo.list.push({ base64: img, flag: "1" }) }) } } } } return ret } } } 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.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 }] onTableDataChange() } } } }), ], { 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.headerPoint.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, headerPoint: { x: Math.max(0, rect.x), y: Math.max(0, rect.y) } } console.log(config) }) }, }) } if (location.host.includes('hdtyhotel')) { utils.fixWidth() // console.log(config) initPanel(config.value.headerPoint.x, config.value.headerPoint.y) } else { // initPanel(config.value.headerPoint.x, config.value.headerPoint.y) }