矿机租赁系统代码更新
This commit is contained in:
87
power_leasing/src/utils/noEmojiGuard.js
Normal file
87
power_leasing/src/utils/noEmojiGuard.js
Normal file
@@ -0,0 +1,87 @@
|
||||
/**
|
||||
* 全局输入表情符号拦截守卫(极简,无侵入)
|
||||
* 作用:拦截所有原生 input/textarea 的输入事件,移除 Emoji,并重新派发 input 事件以同步 v-model
|
||||
* 注意:
|
||||
* - 跳过正在输入法合成阶段(compositionstart ~ compositionend),避免影响中文输入
|
||||
* - 默认对所有可编辑 input/textarea 生效;如需个别放行,可在元素上加 data-allow-emoji="true"
|
||||
*/
|
||||
export const initNoEmojiGuard = () => {
|
||||
if (typeof window === 'undefined') return
|
||||
if (window.__noEmojiGuardInitialized) return
|
||||
window.__noEmojiGuardInitialized = true
|
||||
|
||||
// 覆盖常见 Emoji、旗帜、杂项符号、ZWJ、变体选择符、组合键帽
|
||||
const emojiPattern = /[\u{1F300}-\u{1FAFF}]|[\u{1F1E6}-\u{1F1FF}]|[\u{2600}-\u{26FF}]|[\u{2700}-\u{27BF}]|[\u{FE0F}]|[\u{200D}]|[\u{20E3}]/gu
|
||||
|
||||
/**
|
||||
* 判断是否是需要拦截的可编辑元素
|
||||
* @param {EventTarget} el 事件目标
|
||||
* @returns {boolean}
|
||||
*/
|
||||
const isEditableTarget = (el) => {
|
||||
if (!el || !(el instanceof Element)) return false
|
||||
if (el.getAttribute && el.getAttribute('data-allow-emoji') === 'true') return false
|
||||
const tag = el.tagName
|
||||
if (tag === 'INPUT') {
|
||||
const type = (el.getAttribute('type') || 'text').toLowerCase()
|
||||
// 排除不会产生文本的类型
|
||||
const disallow = ['checkbox', 'radio', 'file', 'hidden', 'button', 'submit', 'reset', 'range', 'color', 'date', 'datetime-local', 'month', 'time', 'week']
|
||||
return disallow.indexOf(type) === -1
|
||||
}
|
||||
if (tag === 'TEXTAREA') return true
|
||||
return false
|
||||
}
|
||||
|
||||
// 记录输入法合成状态
|
||||
const setComposing = (el, composing) => {
|
||||
try { el.__noEmojiComposing = composing } catch (e) {}
|
||||
}
|
||||
const isComposing = (el) => !!(el && el.__noEmojiComposing)
|
||||
|
||||
// 结束合成时做一次清洗
|
||||
document.addEventListener('compositionstart', (e) => {
|
||||
if (!isEditableTarget(e.target)) return
|
||||
setComposing(e.target, true)
|
||||
}, true)
|
||||
document.addEventListener('compositionend', (e) => {
|
||||
if (!isEditableTarget(e.target)) return
|
||||
setComposing(e.target, false)
|
||||
sanitizeAndRedispatch(e.target)
|
||||
}, true)
|
||||
|
||||
// 主输入拦截:捕获阶段尽早处理
|
||||
document.addEventListener('input', (e) => {
|
||||
const target = e.target
|
||||
if (!isEditableTarget(target)) return
|
||||
if (isComposing(target)) return
|
||||
sanitizeAndRedispatch(target)
|
||||
}, true)
|
||||
|
||||
/**
|
||||
* 清洗目标元素的值并在变更时重新派发 input 事件
|
||||
* @param {HTMLInputElement|HTMLTextAreaElement} target
|
||||
*/
|
||||
function sanitizeAndRedispatch(target) {
|
||||
const before = String(target.value ?? '')
|
||||
if (!before) return
|
||||
if (!emojiPattern.test(before)) return
|
||||
const selectionStart = target.selectionStart
|
||||
const selectionEnd = target.selectionEnd
|
||||
const after = before.replace(emojiPattern, '')
|
||||
if (after === before) return
|
||||
target.value = after
|
||||
try {
|
||||
// 重置光标,尽量贴近原位置
|
||||
if (typeof selectionStart === 'number' && typeof selectionEnd === 'number') {
|
||||
const removed = before.length - after.length
|
||||
const nextPos = Math.max(0, selectionStart - removed)
|
||||
target.setSelectionRange(nextPos, nextPos)
|
||||
}
|
||||
} catch (e) {}
|
||||
// 重新派发 input 事件以同步 v-model
|
||||
const evt = new Event('input', { bubbles: true })
|
||||
target.dispatchEvent(evt)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user