// directives/vT.js import { watch } from 'vue' import { lang } from '@/composables/config' const TEXT_NODE_CLASS = 'v-t-node' /** * 解析绑定值 * 支持:'key' 或 ['key', param] */ const parseBinding = (val) => { if (Array.isArray(val)) { const [key, ...rest] = val if (rest.length === 0) return { key, param: undefined } if (rest.length === 1) return { key, param: rest[0] } return { key, param: rest } } return { key: val, param: undefined } } const isFormField = (el) => { const tag = el?.tagName?.toUpperCase?.() || '' return tag === 'INPUT' || tag === 'TEXTAREA' } /** * 在宿主内创建独立文本节点,避免直接改 button/view 的 textContent * 导致 Vue patch 时清空子树、click 等事件失效 */ const ensureTextNode = (el) => { if (isFormField(el)) return el if (el._vTTextEl?.parentNode === el) { return el._vTTextEl } if (typeof document === 'undefined') { return el } const node = document.createElement('span') node.className = TEXT_NODE_CLASS node.setAttribute('data-v-t', '') node.style.pointerEvents = 'none' el.appendChild(node) el._vTTextEl = node return node } const updateElementText = (el, text) => { if (!el) return const target = ensureTextNode(el) if (isFormField(target)) { target.value = text return } if ('textContent' in target) { target.textContent = text } else if ('innerText' in target) { target.innerText = text } } const getI18n = () => { return typeof globalThis !== 'undefined' && globalThis.__i18n ? globalThis.__i18n : null } const bindingSignature = (val) => { if (Array.isArray(val)) return JSON.stringify(val) return String(val ?? '') } export default { mounted(el, binding) { const setState = (val) => { const { key, param } = parseBinding(val) el._vTKey = key el._vTParam = param el._vTBindingSig = bindingSignature(val) } const update = () => { const i18n = getI18n() const key = el._vTKey const param = el._vTParam if (!key) { updateElementText(el, '') return } let text = '' if (i18n) { const translated = i18n.global.t(key, param) text = translated === key ? '' : translated } updateElementText(el, text) } setState(binding.value) update() el._vTSetState = setState el._vTUpdate = update el._vTStopWatch = watch( () => lang.value, () => { const i18n = getI18n() if (i18n?.global?.locale?.value !== undefined) { i18n.global.locale.value = lang.value } update() }, { immediate: false, flush: 'sync' } ) }, updated(el, binding) { const sig = bindingSignature(binding.value) if (sig === el._vTBindingSig) return if (el._vTSetState) el._vTSetState(binding.value) if (el._vTUpdate) el._vTUpdate() }, unmounted(el) { if (el._vTStopWatch) { el._vTStopWatch() delete el._vTStopWatch } if (el._vTTextEl?.parentNode === el) { el.removeChild(el._vTTextEl) } delete el._vTTextEl delete el._vTUpdate delete el._vTSetState delete el._vTKey delete el._vTParam delete el._vTBindingSig }, }