| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145 |
- // 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
- },
- }
|