|
@@ -2,6 +2,8 @@
|
|
|
import { watch } from 'vue'
|
|
import { watch } from 'vue'
|
|
|
import { lang } from '@/composables/config'
|
|
import { lang } from '@/composables/config'
|
|
|
|
|
|
|
|
|
|
+const TEXT_NODE_CLASS = 'v-t-node'
|
|
|
|
|
+
|
|
|
/**
|
|
/**
|
|
|
* 解析绑定值
|
|
* 解析绑定值
|
|
|
* 支持:'key' 或 ['key', param]
|
|
* 支持:'key' 或 ['key', param]
|
|
@@ -16,33 +18,67 @@ const parseBinding = (val) => {
|
|
|
return { key: val, param: undefined }
|
|
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) => {
|
|
const updateElementText = (el, text) => {
|
|
|
if (!el) return
|
|
if (!el) return
|
|
|
- if (el.tagName === 'INPUT' || el.tagName === 'TEXTAREA') {
|
|
|
|
|
- el.value = text
|
|
|
|
|
- } else if ('textContent' in el) {
|
|
|
|
|
- el.textContent = text
|
|
|
|
|
- } else if ('innerText' in el) {
|
|
|
|
|
- el.innerText = text
|
|
|
|
|
|
|
+ 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
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-/**
|
|
|
|
|
- * 获取全局 i18n 实例
|
|
|
|
|
- */
|
|
|
|
|
const getI18n = () => {
|
|
const getI18n = () => {
|
|
|
return typeof globalThis !== 'undefined' && globalThis.__i18n ? globalThis.__i18n : null
|
|
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 {
|
|
export default {
|
|
|
mounted(el, binding) {
|
|
mounted(el, binding) {
|
|
|
const setState = (val) => {
|
|
const setState = (val) => {
|
|
|
const { key, param } = parseBinding(val)
|
|
const { key, param } = parseBinding(val)
|
|
|
el._vTKey = key
|
|
el._vTKey = key
|
|
|
el._vTParam = param
|
|
el._vTParam = param
|
|
|
|
|
+ el._vTBindingSig = bindingSignature(val)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
const update = () => {
|
|
const update = () => {
|
|
@@ -50,7 +86,6 @@ export default {
|
|
|
const key = el._vTKey
|
|
const key = el._vTKey
|
|
|
const param = el._vTParam
|
|
const param = el._vTParam
|
|
|
|
|
|
|
|
- // 无 key 时显示 --
|
|
|
|
|
if (!key) {
|
|
if (!key) {
|
|
|
updateElementText(el, '')
|
|
updateElementText(el, '')
|
|
|
return
|
|
return
|
|
@@ -59,10 +94,7 @@ export default {
|
|
|
let text = ''
|
|
let text = ''
|
|
|
if (i18n) {
|
|
if (i18n) {
|
|
|
const translated = i18n.global.t(key, param)
|
|
const translated = i18n.global.t(key, param)
|
|
|
- // 如果翻译结果等于 key 本身,说明未找到翻译,显示 --
|
|
|
|
|
text = translated === key ? '' : translated
|
|
text = translated === key ? '' : translated
|
|
|
- } else {
|
|
|
|
|
- text = ''
|
|
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
updateElementText(el, text)
|
|
updateElementText(el, text)
|
|
@@ -74,7 +106,6 @@ export default {
|
|
|
el._vTSetState = setState
|
|
el._vTSetState = setState
|
|
|
el._vTUpdate = update
|
|
el._vTUpdate = update
|
|
|
|
|
|
|
|
- // 监听语言变化,更新文本
|
|
|
|
|
el._vTStopWatch = watch(
|
|
el._vTStopWatch = watch(
|
|
|
() => lang.value,
|
|
() => lang.value,
|
|
|
() => {
|
|
() => {
|
|
@@ -89,6 +120,9 @@ export default {
|
|
|
},
|
|
},
|
|
|
|
|
|
|
|
updated(el, binding) {
|
|
updated(el, binding) {
|
|
|
|
|
+ const sig = bindingSignature(binding.value)
|
|
|
|
|
+ if (sig === el._vTBindingSig) return
|
|
|
|
|
+
|
|
|
if (el._vTSetState) el._vTSetState(binding.value)
|
|
if (el._vTSetState) el._vTSetState(binding.value)
|
|
|
if (el._vTUpdate) el._vTUpdate()
|
|
if (el._vTUpdate) el._vTUpdate()
|
|
|
},
|
|
},
|
|
@@ -98,9 +132,14 @@ export default {
|
|
|
el._vTStopWatch()
|
|
el._vTStopWatch()
|
|
|
delete el._vTStopWatch
|
|
delete el._vTStopWatch
|
|
|
}
|
|
}
|
|
|
- if (el._vTUpdate) delete el._vTUpdate
|
|
|
|
|
- if (el._vTSetState) delete el._vTSetState
|
|
|
|
|
- if (el._vTKey) delete el._vTKey
|
|
|
|
|
- if (el._vTParam) delete el._vTParam
|
|
|
|
|
- }
|
|
|
|
|
-}
|
|
|
|
|
|
|
+ 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
|
|
|
|
|
+ },
|
|
|
|
|
+}
|