feat(i18n) multi-lang on uart page: Chinese, French and English.

This commit is contained in:
kerms 2024-09-23 16:01:46 +02:00
parent dd1b9adc0d
commit d7d7c94f53
14 changed files with 637 additions and 204 deletions

95
package-lock.json generated
View File

@ -10,7 +10,7 @@
"dependencies": {
"@vueuse/core": "^10.9.0",
"ansi_up": "^6.0.2",
"element-plus": "^2.7.3",
"element-plus": "^2.8.1",
"mitt": "^3.0.1",
"pinia": "^2.1.7",
"vue": "^3.4.21",
@ -979,31 +979,29 @@
}
},
"node_modules/@volar/language-core": {
"version": "2.1.4",
"resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-2.1.4.tgz",
"integrity": "sha512-ROfPepDxZ5Eq+Unbx3M9QcHT7MoE9tYdbkuzLTtxG5rfkEi5RwsDPncjANMOq/gHhIIDlWgqWwS2nXWMGsuj4w==",
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-2.4.0.tgz",
"integrity": "sha512-FTla+khE+sYK0qJP+6hwPAAUwiNHVMph4RUXpxf/FIPKUP61NFrVZorml4mjFShnueR2y9/j8/vnh09YwVdH7A==",
"dev": true,
"dependencies": {
"@volar/source-map": "2.1.4"
"@volar/source-map": "2.4.0"
}
},
"node_modules/@volar/source-map": {
"version": "2.1.4",
"resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-2.1.4.tgz",
"integrity": "sha512-mCg8IiPZmHZVzqL4Owg+BzQ5ZTG1cVwATxrkrFPZpcAin97Xa3MbchxVhHtHTWTT8ER8bJh5xVjeVxsSN++FUA==",
"dev": true,
"dependencies": {
"muggle-string": "^0.4.0"
}
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-2.4.0.tgz",
"integrity": "sha512-2ceY8/NEZvN6F44TXw2qRP6AQsvCYhV2bxaBPWxV9HqIfkbRydSksTFObCF1DBDNBfKiZTS8G/4vqV6cvjdOIQ==",
"dev": true
},
"node_modules/@volar/typescript": {
"version": "2.1.4",
"resolved": "https://registry.npmjs.org/@volar/typescript/-/typescript-2.1.4.tgz",
"integrity": "sha512-Mt7wOLPkomFnUfVpb5IHlPhSpD7FJAn+FHSsovePmqFNQzFLz16wrpHjAkorPiAnP0847w71NL5fIJyWbAsR8Q==",
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/@volar/typescript/-/typescript-2.4.0.tgz",
"integrity": "sha512-9zx3lQWgHmVd+JRRAHUSRiEhe4TlzL7U7e6ulWXOxHH/WNYxzKwCvZD7WYWEZFdw4dHfTD9vUR0yPQO6GilCaQ==",
"dev": true,
"dependencies": {
"@volar/language-core": "2.1.4",
"path-browserify": "^1.0.1"
"@volar/language-core": "2.4.0",
"path-browserify": "^1.0.1",
"vscode-uri": "^3.0.8"
}
},
"node_modules/@vue/compiler-core": {
@ -1052,6 +1050,16 @@
"@vue/shared": "3.4.21"
}
},
"node_modules/@vue/compiler-vue2": {
"version": "2.7.16",
"resolved": "https://registry.npmjs.org/@vue/compiler-vue2/-/compiler-vue2-2.7.16.tgz",
"integrity": "sha512-qYC3Psj9S/mfu9uVi5WvNZIzq+xnXMhOwbTFKKDD7b1lhpnn71jXSFdTQ+WsIEk0ONCd7VV2IMm7ONl6tbQ86A==",
"dev": true,
"dependencies": {
"de-indent": "^1.0.2",
"he": "^1.2.0"
}
},
"node_modules/@vue/devtools-api": {
"version": "6.6.1",
"resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.6.1.tgz",
@ -1096,18 +1104,19 @@
}
},
"node_modules/@vue/language-core": {
"version": "2.0.7",
"resolved": "https://registry.npmjs.org/@vue/language-core/-/language-core-2.0.7.tgz",
"integrity": "sha512-Vh1yZX3XmYjn9yYLkjU8DN6L0ceBtEcapqiyclHne8guG84IaTzqtvizZB1Yfxm3h6m7EIvjerLO5fvOZO6IIQ==",
"version": "2.0.29",
"resolved": "https://registry.npmjs.org/@vue/language-core/-/language-core-2.0.29.tgz",
"integrity": "sha512-o2qz9JPjhdoVj8D2+9bDXbaI4q2uZTHQA/dbyZT4Bj1FR9viZxDJnLcKVHfxdn6wsOzRgpqIzJEEmSSvgMvDTQ==",
"dev": true,
"dependencies": {
"@volar/language-core": "~2.1.3",
"@volar/language-core": "~2.4.0-alpha.18",
"@vue/compiler-dom": "^3.4.0",
"@vue/compiler-vue2": "^2.7.16",
"@vue/shared": "^3.4.0",
"computeds": "^0.0.1",
"minimatch": "^9.0.3",
"path-browserify": "^1.0.1",
"vue-template-compiler": "^2.7.14"
"muggle-string": "^0.4.1",
"path-browserify": "^1.0.1"
},
"peerDependencies": {
"typescript": "*"
@ -1977,9 +1986,9 @@
"dev": true
},
"node_modules/element-plus": {
"version": "2.7.3",
"resolved": "https://registry.npmjs.org/element-plus/-/element-plus-2.7.3.tgz",
"integrity": "sha512-OaqY1kQ2xzNyRFyge3fzM7jqMwux+464RBEqd+ybRV9xPiGxtgnj/sVK4iEbnKnzQIa9XK03DOIFzoToUhu1DA==",
"version": "2.8.1",
"resolved": "https://registry.npmjs.org/element-plus/-/element-plus-2.8.1.tgz",
"integrity": "sha512-p11/6w/O0+hGvPhiN3jrcgh+XG+eg5jZlLdQVYvcPHZYhhCh3J3YeZWW1JO/REPES1vevkboT6VAi+9wHA8Dsg==",
"dependencies": {
"@ctrl/tinycolor": "^3.4.1",
"@element-plus/icons-vue": "^2.3.1",
@ -3249,9 +3258,9 @@
}
},
"node_modules/micromatch": {
"version": "4.0.7",
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.7.tgz",
"integrity": "sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==",
"version": "4.0.8",
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
"integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
"dev": true,
"dependencies": {
"braces": "^3.0.3",
@ -5042,6 +5051,12 @@
"vue": ">=3.2.13"
}
},
"node_modules/vscode-uri": {
"version": "3.0.8",
"resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.0.8.tgz",
"integrity": "sha512-AyFQ0EVmsOZOlAnxoFOGOq1SQDWAB7C6aqMGS23svWAllfOaxbuFvcT8D1i8z3Gyn8fraVeZNNmN6e9bxxXkKw==",
"dev": true
},
"node_modules/vue": {
"version": "3.4.21",
"resolved": "https://registry.npmjs.org/vue/-/vue-3.4.21.tgz",
@ -5132,31 +5147,21 @@
"vue": "^3.2.0"
}
},
"node_modules/vue-template-compiler": {
"version": "2.7.16",
"resolved": "https://registry.npmjs.org/vue-template-compiler/-/vue-template-compiler-2.7.16.tgz",
"integrity": "sha512-AYbUWAJHLGGQM7+cNTELw+KsOG9nl2CnSv467WobS5Cv9uk3wFcnr1Etsz2sEIHEZvw1U+o9mRlEO6QbZvUPGQ==",
"dev": true,
"dependencies": {
"de-indent": "^1.0.2",
"he": "^1.2.0"
}
},
"node_modules/vue-tsc": {
"version": "2.0.7",
"resolved": "https://registry.npmjs.org/vue-tsc/-/vue-tsc-2.0.7.tgz",
"integrity": "sha512-LYa0nInkfcDBB7y8jQ9FQ4riJTRNTdh98zK/hzt4gEpBZQmf30dPhP+odzCa+cedGz6B/guvJEd0BavZaRptjg==",
"version": "2.0.29",
"resolved": "https://registry.npmjs.org/vue-tsc/-/vue-tsc-2.0.29.tgz",
"integrity": "sha512-MHhsfyxO3mYShZCGYNziSbc63x7cQ5g9kvijV7dRe1TTXBRLxXyL0FnXWpUF1xII2mJ86mwYpYsUmMwkmerq7Q==",
"dev": true,
"dependencies": {
"@volar/typescript": "~2.1.3",
"@vue/language-core": "2.0.7",
"@volar/typescript": "~2.4.0-alpha.18",
"@vue/language-core": "2.0.29",
"semver": "^7.5.4"
},
"bin": {
"vue-tsc": "bin/vue-tsc.js"
},
"peerDependencies": {
"typescript": "*"
"typescript": ">=5.0.0"
}
},
"node_modules/vuetify": {

View File

@ -18,7 +18,7 @@
"dependencies": {
"@vueuse/core": "^10.9.0",
"ansi_up": "^6.0.2",
"element-plus": "^2.7.3",
"element-plus": "^2.8.1",
"mitt": "^3.0.1",
"pinia": "^2.1.7",
"vue": "^3.4.21",

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M0 0h24v24H0z" fill="none"/><path d="M12.87 15.07l-2.54-2.51.03-.03c1.74-1.94 2.98-4.17 3.71-6.53H17V4h-7V2H8v2H1v1.99h11.17C11.5 7.92 10.44 9.75 9 11.35 8.07 10.32 7.3 9.19 6.69 8h-2c.73 1.63 1.73 3.17 2.98 4.56l-5.09 5.02L4 19l5-5 3.11 3.11.76-2.04zM18.5 10h-2L12 22h2l1.12-3h4.75L21 22h2l-4.5-12zm-2.62 7l1.62-4.33L19.12 17h-3.24z"/></svg>

After

Width:  |  Height:  |  Size: 411 B

View File

@ -1,19 +1,48 @@
import { createI18n } from 'vue-i18n';
import {createI18n} from 'vue-i18n';
import zh from '@/locales/zh'
import en from '@/locales/en'
import fr from '@/locales/fr'
// const locale = localStorage.getItem('lang') || 'zh';
export const locale = 'zh';
const userLanguage = navigator.language || 'en';
// Get the language code (e.g., 'en' from 'en-US')
export const locale = userLanguage.split('-')[0];
const messages = {
zh,
en,
fr,
} as const;
type Locale = keyof typeof messages;
export const availableLanguages = Object.keys(messages);
// export const locale = 'zh';
console.log(userLanguage, locale, availableLanguages)
const i18n = createI18n({
globalInjection: true,
legacy: false,
locale: locale,
fallbackLocale: 'zh',
messages: {
zh,
// en,
}
messages: messages
});
export function getFlagFromLang(lang: string) {
if (lang === 'zh') {
return '🇨🇳';
} else if (lang === 'en') {
return '🇺🇸';
} else if (lang === 'fr') {
return '🇫🇷';
}
return '🏳️';
}
export function setLang(lang: string): void {
if (availableLanguages.includes(lang)) {
i18n.global.locale.value = lang as Locale;
}
}
export default i18n;

View File

@ -1,3 +1,127 @@
export default {
disconnected: "disconnected"
emoji: {
flag: "🇺🇸",
},
disconnected: "Disconnected",
connected: "Connected",
connecting: "Connecting",
ws: {
disconnected: "Disconnected",
connected: "Connected",
connecting: "Connecting",
},
page: {
home: "Home",
wifi: "Wi-Fi",
about: "About",
uart: "Uart",
feedback: "Feedback",
close: "Close",
update: "Update",
fullscreen: "Fullscreen",
windowed: "Windowed"
},
uart: {
port: "Port",
startCommunication: "Start Communication",
stopCommunication: "Stop Communication",
commonlyUsed: "Common",
baudrate: "Baud Rate",
customBaud: "Custom Baud",
use: "Use",
actual: "Actual",
dataBits: "Data Bits",
stopBits: "Stop Bits",
parity: "Parity",
parityNone: "None",
parityOdd: "Odd",
parityEven: "Even",
flowControl: "Flow Control",
send: "Send",
clear: "Clear",
clearTooltip: "Only clears the display area, can be restored with refresh.",
updateTooltip: "Sync with cache + filter",
autoUpdateTooltip: "Only stop refreshing the display area; the background continues to receive data.",
receive: "Receive",
displayOptions: "Display Options",
display: "Display",
show: "Show",
text: "Text",
timestamp: "Timestamp",
enable: "Enable",
lineWrap: "Line Wrap",
highlight: "Highlight",
frameBreakStrategy: "Frame Break Strategy",
priority: "Priority",
rule: "Rule",
ruleTips:
"<p>Timeout=-1: Disable timeout frame break</p>" +
"<p>Timeout=0: Immediate break, any received data is considered complete</p>" +
"<p>Match after break: Typical \\n scenario</p>" +
"<p>Match before break: For scenarios with special frame headers</p>" +
"<p>Fixed byte frame break: Useful for large data transfer, e.g., break frame every 1024 bytes for easy data viewing</p>",
value: "Value",
timeout: "Timeout",
match: "Match",
byte: "Byte",
begin: "b",
end: "b",
other: "Other",
decodeAnsiEscapeCodes: "Decode ANSI Escape Codes",
ansiTooltips:
"<p>ANSI escape codes have many uses for terminals and text, such as changing text colors, among other effects.</p>\n" +
"<p>\n Learn more ->\n <a target=\"_blank\" href=\"https://en.wikipedia.org/wiki/ANSI_escape_code\">" +
"https://en.wikipedia.org/wiki/ANSI_escape_code\n </a></p>",
filter: "Filter",
textAndEscape: "Text with \\n\\x support",
autoUpdateNewData: "Auto-refresh new data",
updateFrequency: "Data Display Update Interval (ms)",
updateFrequencyTooltip: "Increasing the interval can reduce CPU usage.",
addHeader: "Add Header",
addFooter: "Add Footer",
passthrough: "Passthrough",
proxy: "Proxy",
serverPort: "Server Port",
connectedClient: "Connected Client",
refresh: "Refresh",
interface: "Interface",
noClientConnected: "No Client Connected",
import: "Import",
export: "Export",
reset: "Reset",
resetTooltip: "Takes effect after refreshing the page.",
saveToLocal: "Save to Local",
saveToLocalTooltip: "If multiple pages exist, they will overwrite each other.",
add: "Add",
edit: "Edit",
drag: "Drag",
ipChangeAlert: "Changing the IP address will cause the configuration to be lost.",
layout: "Layout",
landscape: "Landscape",
portrait: "Portrait",
responsive: "Responsive",
configPannel: "Config",
displayPannel: "Display",
macroPannel: "Quick Send",
autoScrollToBottom: "Auto Scroll",
clearScreen: "Clear",
autoUpdate: "Auto Update",
tempDisplayTooltip: "Data that does not meet the frame-break rules (e.g., not timed out) is temporarily displayed in real-time in this area. If it exceeds 8192 bytes, it will automatically break frames.",
loopSend: "Loop Send",
loopSendTooltip: "The actual frequency is affected by the interface refresh rate. For more accuracy, you can try turning off 'auto-refresh'.",
sendFormat: "Send Format",
cachedFrame: "Cached",
format: "Format",
}
};

128
src/locales/fr.ts Normal file
View File

@ -0,0 +1,128 @@
export default {
emoji: {
flag: "🇫🇷",
},
disconnected: "Déconnecté",
connected: "Connecté",
connecting: "Connexion..",
ws: {
disconnected: "Déconnecté",
connected: "Connecté",
connecting: "Connexion..",
},
page: {
home: "Accueil",
wifi: "Wi-Fi",
about: "À propos",
uart: "Uart",
feedback: "Feedback",
close: "Fermer",
update: "Mise à jour",
fullscreen: "Plein écran",
windowed: "Fenêtré",
},
uart: {
port: "Port",
startCommunication: "Démarrer la communication",
stopCommunication: "Arrêter la communication",
commonlyUsed: "Fréquemment utilisé",
baudrate: "Taux de Baud",
customBaud: "Baud",
use: "Utiliser",
actual: "Actuel",
dataBits: "Bits de Données",
stopBits: "Bits d'Arrêt",
parity: "Parité",
parityNone: "Aucune",
parityOdd: "Impair(Odd)",
parityEven: "Pair(Even)",
flowControl: "Contrôle de Flux",
send: "Envoyer",
clear: "Effacer",
clearTooltip: "Ne supprime que la zone d'affichage, peut être restaurée en actualisant.",
updateTooltip: "Synchroniser avec le cache + filtrer",
autoUpdateTooltip: "Arrête uniquement le rafraîchissement de la zone d'affichage ; l'arrière-plan continue de recevoir des données.",
receive: "Recevoir",
displayOptions: "Options d'Affichage",
display: "Affichage",
show: "Afficher",
text: "Texte",
timestamp: "Horodatage",
enable: "Activer",
lineWrap: "Retour à la Ligne",
highlight: "Surligner",
frameBreakStrategy: "Stratégie de Coupure de Trame",
priority: "Priorité",
rule: "Règle",
ruleTips:
"<p>Délai d'expiration=-1 : Désactiver la coupure de trame par délai d'expiration</p>" +
"<p>Délai d'expiration=0 : Coupure immédiate, toutes données reçues sont considérées complètes</p>" +
"<p>Match après coupure : Scénario typique \\n</p>" +
"<p>Match avant coupure : Pour des scénarios avec en-têtes de trame spécifiques</p>" +
"<p>Coupure de trame par octets fixes : Utile pour le transfert de grandes quantités de données, par exemple, couper la trame tous les 1024 octets pour faciliter la visualisation des données</p>",
value: "Valeur",
timeout: "Timeout",
match: "Match",
byte: "Byte",
begin: "b",
end: "b",
other: "Autres",
decodeAnsiEscapeCodes: "Décode Échappement ANSI",
ansiTooltips:
"<p>Les codes d'échappement ANSI ont de nombreuses utilisations pour les terminaux et le texte, comme changer les couleurs du texte, entre autres effets.</p>" +
"<p>\n En savoir plus ->\n " +
"<a target=\"_blank\" href=\"https://en.wikipedia.org/wiki/ANSI_escape_code\">\n" +
"https://en.wikipedia.org/wiki/ANSI_escape_code\n </a>\n</p>",
filter: "Filtrer",
textAndEscape: "Texte;supporte\\n\\x",
autoUpdateNewData: "Auto-update nouvelles données",
updateFrequency: "Délais rafraîchissement des Données (ms)",
updateFrequencyTooltip: "Augmenter l'intervalle peut réduire l'utilisation des ressources CPU.",
addHeader: "Ajouter un En-tête",
addFooter: "Ajouter un Pied de page",
passthrough: "Passage Direct",
proxy: "Proxy",
serverPort: "Port Serveur",
connectedClient: "Client Connecté",
refresh: "Rafraîchir",
interface: "Interface",
noClientConnected: "Aucun Client Connecté",
import: "Importer",
export: "Exporter",
reset: "Réinitialiser",
resetTooltip: "Prend effet après le rafraîchissement de la page.",
saveToLocal: "Enregistrer Localement",
saveToLocalTooltip: "S'il existe plusieurs pages, elles se chevaucheront mutuellement.",
add: "Ajouter",
edit: "Éditer",
drag: "Glisser",
ipChangeAlert: "Le changement d'adresse IP entraînera la perte de la configuration.",
layout: "Disposition",
landscape: "Paysage",
portrait: "Portrait",
responsive: "Résponsive",
configPannel: "Configuration",
displayPannel: "Données",
macroPannel: "Envoie Rapide",
autoScrollToBottom: "Auto Scroll",
clearScreen: "Effacer",
autoUpdate: "Auto Update",
tempDisplayTooltip: "es données qui ne respectent pas les règles de rupture de trame (par exemple : non expirées) s'affichent temporairement en temps réel dans cette zone. Au-delà de 8192 octets, une rupture de trame est automatique.",
loopSend: "Envoi en Boucle",
loopSendTooltip: "La fréquence réelle est influencée par le taux de rafraîchissement de l'interface. Pour plus de précision, vous pouvez essayer de désactiver 'l'actualisation automatique'.",
sendFormat: "Format d'Envoi",
cachedFrame: "Cache",
format: "Format",
}
};

View File

@ -7,8 +7,8 @@ type NestedKeyOf<ObjectType extends object> = {
: `${Key}`
}[keyof ObjectType & (string | number)];
type TranslationKeys = NestedKeyOf<typeof zh>;
export type TranslationKeys = NestedKeyOf<typeof zh>;
export function translate<K extends TranslationKeys>(key: K | string): string {
return i18n.global.t(key.toLowerCase());
export function translate(key: TranslationKeys | string): string {
return i18n.global.t(key);
}

View File

@ -1,7 +1,11 @@
export default {
emoji: {
flag: "🇨🇳",
},
disconnected: "未连接",
connected: "已连接",
connecting: "连接中",
use: "使用",
ws: {
disconnected: "未连接",
@ -13,9 +17,115 @@ export default {
home: "主页",
wifi: "Wi-Fi",
about: "关于",
uart: "UART透传",
uart: "UART",
feedback: "反馈",
close: "关闭",
update: "更新",
fullscreen: "全屏",
windowed: "窗口",
},
uart: {
port: "接口",
startCommunication: "开始数据收发",
stopCommunication: "停止数据收发",
commonlyUsed: "常用",
baudrate: "波特率",
customBaud: "自定义波特率",
use: "使用",
actual: "实际",
dataBits: "数据位",
stopBits: "停止位",
parity: "校验位",
parityNone: "无(None)",
parityOdd: "奇(Odd)",
parityEven: "偶(Even)",
flowControl: "流控制",
send: "发送",
clear: "清空",
clearTooltip: "仅清除显示区域,可用刷新恢复",
updateTooltip: "与缓存同步+过滤",
autoUpdateTooltip: "仅停止刷新显示区,后台继续接收数据",
receive: "接收",
displayOptions: "显示选项",
display: "显示框",
show: "显示",
text: "文本",
timestamp: "时间戳",
enable: "启用",
lineWrap: "换行",
highlight: "高亮",
frameBreakStrategy: "断帧策略",
priority: "优先级",
rule: "规则",
ruleTips:
"<p>超时=-1 禁用超时断帧</p>" +
"<p>超时=0 当机立断,收到任何数据都视为完整数据</p>" +
"<p>匹配断后:典型\\n的场景</p>" +
"<p>匹配断前:用于有特殊帧头的场景</p>" +
"<p>固定字节断帧传输大量数据比如可以每隔1024字节断帧方便查看数据</p>",
value: "值",
timeout: "超时",
match: "匹配",
byte: "字节",
begin: "断",
end: "断",
other: "其他",
decodeAnsiEscapeCodes: "解码ANSI转义码",
ansiTooltips:
"<p>ANSI转义码对终端和文本有很多作用比如改变文本颜色等。</p>\n" +
"<p>\n" +
" 简单了解->\n" +
" <a target=\"_blank\" href=\"https://yunsi.studio/wireless-debugger/docs/uart-webhost/ansi-escape-code\">\n" +
" https://yunsi.studio/wireless-debugger/docs/uart-webhost/ansi-escape-code\n" +
" </a>\n" +
"</p>",
filter: "过滤",
textAndEscape: "文本,支持\\n\\x",
autoUpdateNewData: "新数据自动刷新",
updateFrequency: "数据显示刷新间隔(ms)",
updateFrequencyTooltip: "提高间隔可减少CPU资源的使用",
addHeader: "增加帧头",
addFooter: "增加帧尾",
passthrough: "透传",
proxy: "透传",
serverPort: "服务器端口",
connectedClient: "已连接的客户端",
refresh: "刷新",
interface: "接口",
noClientConnected: "无客户端连接",
import: "导入",
export: "导出",
reset: "重置",
resetTooltip: "刷新页面后生效",
saveToLocal: "保存到本地",
saveToLocalTooltip: "若存在多个页面,会相互覆盖",
add: "添加",
edit: "编辑",
drag: "拖拽",
ipChangeAlert: "IP地址改变会导致配置丢失",
layout: "布局",
landscape: "横/行",
portrait: "竖/列",
responsive: "自适应",
configPannel: "设置窗",
displayPannel: "数据窗",
macroPannel: "快捷窗",
autoScrollToBottom: "自动滚动到底部",
clearScreen: "清屏",
autoUpdate: "自动刷新",
tempDisplayTooltip: "未满足断帧规则的数据未超时暂时实时显示在此区域。超过8192字节自动断帧",
loopSend: "循环发送",
loopSendTooltip: "实际频率受界面刷新率影响,如需要更精确,可以尝试关闭‘自动刷新’",
sendFormat: "发送格式",
cachedFrame: "缓存帧数",
format: "格式化",
}
}

View File

@ -364,18 +364,18 @@ export const useDataViewerStore = defineStore('text-viewer', () => {
const frameBreakSize = ref(0);
const frameBreakRules = ref([{
name: '超时(ms)',
name: 'timeout',
type: 'number',
min: -1,
draggable: false,
transformData: breakDelay,
}, {
name: '匹配',
name: 'match',
type: 'text',
draggable: true,
transformData: breakSequence,
}, {
name: '字节(B)',
name: 'byte',
type: 'number',
min: 0,
draggable: true,
@ -437,9 +437,9 @@ export const useDataViewerStore = defineStore('text-viewer', () => {
function reloadFrameBreak() {
/* function and ref can not be stored in localStorage */
for (let i = 0; i < frameBreakRules.value.length; i++) {
if (frameBreakRules.value[i].name === "超时(ms)") {
if (frameBreakRules.value[i].name === "timeout") {
frameBreakRules.value[i].transformData = breakDelay;
} else if (frameBreakRules.value[i].name === "匹配") {
} else if (frameBreakRules.value[i].name === "match") {
frameBreakRules.value[i].transformData = breakSequence;
} else {
frameBreakRules.value[i].transformData = breakSize;

View File

@ -38,15 +38,24 @@
<div class="custom-style flex justify-center">
<el-segmented v-model="store.winLayoutMode" :options="layoutOptions" size="small"/>
</div>
<el-checkbox label="自适应" v-model="store.winAutoLayout" border size="small"
:disabled="store.winLayoutMode==='col'"/>
<el-checkbox label="设置窗" v-model="store.winLeft.show" border size="small" :disabled="store.winAutoLayout"/>
<el-checkbox label="数据窗" v-model="winDataView.show" border size="small" :disabled="store.winAutoLayout"/>
<el-checkbox label="快捷窗" v-model="store.winRight.show" border size="small" :disabled="store.winAutoLayout"/>
<el-checkbox v-model="store.winAutoLayout" border size="small"
:disabled="store.winLayoutMode==='col'">
{{ $t('uart.responsive') }}
</el-checkbox>
<el-checkbox v-model="store.winLeft.show" border size="small" :disabled="store.winAutoLayout">
{{ $t("uart.configPannel") }}
</el-checkbox>
<el-checkbox v-model="winDataView.show" border size="small" :disabled="store.winAutoLayout">
{{ $t('uart.displayPannel') }}
</el-checkbox>
<el-checkbox v-model="store.winRight.show" border size="small" :disabled="store.winAutoLayout">
{{ $t('uart.macroPannel') }}
</el-checkbox>
</div>
<template #reference>
<el-button class="min-h-full" type="primary" :size="layoutConf.isMedium ? 'small' : 'default'">布局
<el-button class="min-h-full" type="primary" :size="layoutConf.isMedium ? 'small' : 'default'">
{{ $t('uart.layout') }}
</el-button>
</template>
</el-popover>
@ -56,7 +65,7 @@
</template>
<script setup lang="ts">
import {onMounted, onUnmounted, reactive, type Ref, ref, type UnwrapRef, watch} from "vue";
import {computed, onMounted, onUnmounted, reactive, type Ref, ref, type UnwrapRef, watch} from "vue";
import {breakpointsTailwind, useBreakpoints} from '@vueuse/core'
import {useDataViewerStore} from '@/stores/dataViewerStore';
import * as api from '@/api';
@ -83,6 +92,7 @@ import {isDevMode} from "@/composables/buildMode";
import {useWsStore} from "@/stores/websocket";
import {useUartStore} from "@/stores/useUartStore";
import TextDataMacro from "@/views/text-data-viewer/textDataMacro.vue";
import {translate} from "@/locales";
const store = useDataViewerStore()
const wsStore = useWsStore()
@ -100,13 +110,13 @@ const layoutConf = reactive({
isMedium: breakpoints.smaller("lg"),
});
const layoutOptions = [{
label: '横/行',
const layoutOptions = computed(() => [{
label: translate("uart.landscape"),
value: 'row'
}, {
label: '竖/列',
label: translate("uart.portrait"),
value: 'col'
}]
}]);
interface WinProperty {
show: boolean;

View File

@ -1,7 +1,7 @@
<template>
<nav class="relative px-2 py-0.5 sm:py-1 flex justify-between items-center border-b h-full">
<div class="flex">
<button @click.prevent="sideMenuOpen=true" class="flex items-center hover:text-blue-600 pl-1 mx-4">
<button @click.prevent="sideMenuOpen=true" class="flex items-center hover:text-blue-600 pl-1 mx-2 sm:mx-4">
<svg class="block h-3 lg:h-4 lg:w-4 fill-current" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
<title>导航侧栏</title>
<path d="M0 3h20v2H0V3zm0 6h20v2H0V9zm0 6h20v2H0v-2z"></path>
@ -20,7 +20,7 @@
<!-- <router-link to="/" class="flex items-center text-sm text-blue-600 font-bold">主页</router-link>-->
<!-- <a class="flex items-center text-sm text-blue-600 font-bold" href="/">主页6</a>-->
<div class="flex pt-0.5 sm:pt-1 ml-4 text-sm items-center sm:hidden">
<div class="flex pt-0.5 sm:pt-1 ml-4 text-xs items-center sm:hidden">
<router-link :to="route.fullPath">{{ route.meta.title }}</router-link>
</div>
</div>
@ -36,6 +36,20 @@
<!-- <a class="md:ml-auto md:mr-3"></a>-->
<div class="flex h-full">
<div id="page-spec-slot" class="content-center h-full flex flex-row"></div>
<div class="mr-2">
<el-select v-model="language" class="min-w-20 h-full" @change="handleLanguageChange">
<el-option value="en">🇺🇸 English</el-option>
<el-option value="zh">🇨🇳 简体中文</el-option>
<el-option value="fr">🇫🇷 Français</el-option>
<template #label>
<div class="flex">
<InlineSvg name="translate" class="w-4 mr-1"></InlineSvg>
{{ languageFlag }}
</div>
</template>
</el-select>
</div>
<div class="lg:hidden">
<el-button :type="wsColor" size="small" class="transition duration-1000 min-h-full">
<InlineSvg v-show="wsColor!=='success'" name="link-off" class="mr-2" width="20"></InlineSvg>
@ -81,9 +95,9 @@
<div>
<el-button @click="toggle">
<InlineSvg v-if="!isFullscreen" name="open-in-full" width="16px" fill="#000000"></InlineSvg>
<p v-if="!isFullscreen">全屏</p>
<p v-if="!isFullscreen">{{ translate('page.fullscreen') }}</p>
<InlineSvg v-if="isFullscreen" name="close-fullscreen" width="16px" fill="#000000"></InlineSvg>
<p v-if="isFullscreen">缩小</p>
<p v-if="isFullscreen">{{ translate('page.windowed') }}</p>
</el-button>
</div>
</template>
@ -109,7 +123,7 @@
<script lang="ts" setup>
import InlineSvg from "@/components/InlineSvg.vue";
import {computed, type Ref, ref} from "vue";
import {computed, type ComputedRef, type Ref, ref} from "vue";
import {useWsStore} from "@/stores/websocket";
import {translate} from "@/locales";
import {ControlEvent} from "@/api";
@ -117,11 +131,21 @@ import {useRoute} from "vue-router";
import { useFullscreen } from '@vueuse/core'
import {useUpdateStore} from "@/stores/useUpdateStore";
import {isOTAEnabled} from "@/composables/buildMode";
import {getFlagFromLang, locale, setLang} from "@/i18n"
const wsStore = useWsStore();
const updateStore = useUpdateStore();
const {isFullscreen, toggle} = useFullscreen();
const route = useRoute();
const language = ref(locale);
const languageFlag = computed(() => {
return getFlagFromLang(language.value);
});
function handleLanguageChange(lang: string) {
setLang(lang);
}
const sideMenuItemClass = "block p-4 text-sm font-semibold hover:bg-blue-50 hover:text-blue-600 rounded flex"
const sideMenuOpen = ref(false);
@ -144,7 +168,7 @@ const wsColor = computed(() => {
});
const wsState = computed(() => {
return translate(wsStore.state);
return translate(wsStore.state.toLocaleLowerCase());
});
type Item = {
@ -154,7 +178,7 @@ type Item = {
badge?: Ref<boolean>;
};
const menuItems: Item[] = ([
const menuItems: ComputedRef<Item[]> = computed(() => ([
{
name: translate("page.uart"),
href: "/uart",
@ -165,9 +189,10 @@ const menuItems: Item[] = ([
name: translate("page.feedback"),
href: "/feedback",
},
]);
]));
const sideBarItems: Item[] = ([
const sideBarItems: ComputedRef<Item[]> = computed(() => {
const items: Item[] = [
{
name: translate("page.uart"),
href: "/uart",
@ -181,15 +206,18 @@ const sideBarItems: Item[] = ([
name: translate("page.feedback"),
href: "/feedback",
},
]);
if (isOTAEnabled()) {
sideBarItems.push({
];
if (isOTAEnabled()) {
items.push({
name: translate("page.update"),
href: "/update",
badge: computed(() => updateStore.canUpdate),
})
}
}
return items;
});
</script>
@ -217,5 +245,9 @@ if (isOTAEnabled()) {
padding: 0;
}
.el-select :deep(.el-select__wrapper) {
@apply h-full;
}
</style>

View File

@ -1,14 +1,14 @@
<template>
<div>
<el-tabs v-model="store.configPanelTab" class="mx-2 custom-tabs fit">
<el-tab-pane label="接口" name="first" class="min-h-80">
<el-tab-pane name="first" class="min-h-80">
<template #label>{{ $t("uart.port") }}</template>
<div class="flex flex-col gap-2">
<el-form :size="store.winLeft.show ? '' : 'small'">
<el-form-item
label="波特率"
class="mb-2"
>
<template #label>{{ $t("uart.baudrate") }}</template>
<div class="flex w-full">
<el-select v-model="store.uartBaud" :teleported="false" @change="onUartBaudChange">
<template #header>
@ -16,17 +16,17 @@
<div class="flex gap-0">
<el-input-number
v-model="uartCustomBaud"
placeholder="自定义波特率"
:placeholder="translate('uart.customBaud')"
size="small"
:controls="false"
:min="110"
class="flex-grow"
></el-input-number>
<el-button size="small" @click="onUseCustomUartBaud">使用</el-button>
<el-button size="small" @click="onUseCustomUartBaud">{{ $t('uart.use') }}</el-button>
<!-- <el-button size="small" @click="onConfirm" class="ml-0">增加</el-button>-->
</div>
<el-option-group label="常用">
<el-option-group :label="translate('uart.commonlyUsed')">
<el-option
v-for="item in store.predefinedUartBaudFrequent"
:key="item.baud"
@ -35,7 +35,7 @@
/>
</el-option-group>
<el-option-group label="其他">
<el-option-group :label="translate('uart.other')">
<el-option
v-for="item in store.uartBaudList"
:key="item.baud"
@ -48,9 +48,9 @@
</el-select>
</div>
</el-form-item>
<p class="text-xs">实际波特率:{{ store.uartBaudReal }}</p>
<p class="text-xs">{{ $t('uart.actual') }} {{ $t('uart.baudrate') }}:{{ store.uartBaudReal }}</p>
<el-form-item label="数据位" class="mb-2">
<el-form-item :label="translate('uart.dataBits')" class="mb-2">
<el-select v-model="store.uartConfig.data_bits" :teleported="false"
placeholder="Select" @change="onUartConfigChange">
<el-option
@ -62,7 +62,7 @@
</el-select>
</el-form-item>
<el-form-item label="校验位" class="mb-2">
<el-form-item :label="translate('uart.parity')" class="mb-2">
<el-select v-model="store.uartConfig.parity" :teleported="false"
placeholder="Select" @change="onUartConfigChange">
<el-option
@ -74,7 +74,7 @@
</el-select>
</el-form-item>
<el-form-item label="停止位">
<el-form-item :label="translate('uart.stopBits')">
<el-select v-model="store.uartConfig.stop_bits" :teleported="false"
placeholder="Select" @change="onUartConfigChange">
<el-option
@ -92,7 +92,7 @@
:disabled="wsStore.state !== ControlEvent.CONNECTED"
@click="store.acceptIncomingData = !store.acceptIncomingData"
>
{{ store.acceptIncomingData ? "停止数据收发" : "开始数据收发" }}
{{ store.acceptIncomingData ? $t("uart.stopCommunication") : $t("uart.startCommunication") }}
</el-button>
</div>
</el-form-item>
@ -102,39 +102,40 @@
</el-tab-pane>
<!-- ///////////////////////////////////////////////////////////////// -->
<el-tab-pane label="显示框" name="second">
<el-tab-pane name="second">
<template #label>{{ $t("uart.displayPannel") }}</template>
<div class="flex flex-col">
<el-collapse v-model="collapseActiveName">
<el-collapse-item name="1">
<template #title>
显示选项
{{ $t('uart.displayOptions') }}
</template>
<template #default>
<div class="flex flex-col gap-2">
<div class="flex flex-col">
<el-checkbox border v-model="store.showText" label="显示文本"/>
<el-checkbox border v-model="store.showText" :label="translate('uart.text')"/>
</div>
<div class="flex flex-col">
<el-checkbox border v-model="store.showHex" label="显示HEX"/>
<el-checkbox border v-model="store.showHex" label="HEX"/>
</div>
<div class="flex flex-col">
<el-checkbox border v-model="store.showHexdump" label="显示HEXDUMP"/>
<el-checkbox border v-model="store.showHexdump" label="HEXDUMP"/>
</div>
<div class="flex flex-col">
<el-checkbox border v-model="store.showTimestamp">显示时间戳</el-checkbox>
<el-checkbox border v-model="store.showTimestamp" :label="translate('uart.timestamp')"/>
</div>
<div class="flex flex-col">
<el-checkbox border v-model="store.enableLineWrap" label="启用换行"/>
<el-checkbox border v-model="store.enableLineWrap" :label="translate('uart.lineWrap')"/>
</div>
<el-tag type="success">
<el-text type="success">RX HEXDUMP高亮选色</el-text>
<el-text type="success">RX HEXDUMP {{ $t("uart.highlight") }}</el-text>
<el-color-picker v-model="store.RxHexdumpColor" show-alpha :predefine="store.predefineColors"
size="small"/>
</el-tag>
<el-tag type="primary">
<el-text type="primary">TX HEXDUMP高亮选色</el-text>
<el-text type="primary">TX HEXDUMP {{ $t("uart.highlight") }}</el-text>
<el-color-picker v-model="store.TxHexdumpColor" show-alpha :predefine="store.predefineColors"
size="small"/>
</el-tag>
@ -142,33 +143,26 @@
</template>
</el-collapse-item>
<el-collapse-item name="2">
<template #title>
断帧策略
</template>
<el-collapse-item name="2" :title="translate('uart.frameBreakStrategy')">
<VueDraggable v-model="store.frameBreakRules" target="tbody" handle=".sort-target"
:animation="150"
:on-move="checkMove">
<table class="w-full bg-white">
<thead>
<tr class="text-sm h-7">
<th>优先级</th>
<th>{{ $t('uart.priority') }}</th>
<th>
<div class="flex justify-center">
规则
{{ translate('uart.rule' as TranslationKeys) }}
<el-tooltip placement="top" effect="light">
<template #content>
<p>超时=-1 禁用超时断帧</p>
<p>超时=0 当机立断收到任何数据都视为完整数据</p>
<p>匹配断后典型\n的场景</p>
<p>匹配断前用于有特殊帧头的场景</p>
<p>固定字节断帧传输大量数据比如可以每隔1024字节断帧方便查看数据</p>
<div v-html="translate('uart.ruleTips')"></div>
</template>
<InlineSvg name="help" class="w-4 text-gray-500 cursor-help"></InlineSvg>
</el-tooltip>
</div>
</th>
<th></th>
<th>{{ translate('uart.value' as TranslationKeys) }}</th>
</tr>
</thead>
<tbody class="text-xs text-center">
@ -176,20 +170,22 @@
<td :class="item.draggable ? 'sort-target' : ''">
{{ item.draggable ? index : 'NaN' }}
</td>
<td :class="item.draggable ? 'sort-target' : ''">{{ item.name }}</td>
<td :class="item.draggable ? 'sort-target' : ''">
{{ translate("uart." + item.name) }}
</td>
<td>
<div v-if="item.type === 'number'">
<el-input-number v-if="item.name === '超时(ms)'" v-model="store.frameBreakDelay" :min="item.min || 0" size="small" style="width: 100px"/>
<el-input-number v-if="item.name === 'timeout'" v-model="store.frameBreakDelay" :min="item.min || 0" size="small" style="width: 100px"/>
<el-input-number v-else v-model="store.frameBreakSize" :min="item.min || 0" size="small" style="width: 100px"/>
</div>
<div v-else>
<el-input class="break-input" v-model="store.frameBreakSequence" placeholder="文本;支持\n\x" size="small"
<el-input class="break-input" v-model="store.frameBreakSequence" :placeholder="translate('uart.textAndEscape')" size="small"
style="width: 100px">
<template #prepend>
<el-button size="small" @click="store.frameBreakAfterSequence = false">
<span
:class="store.frameBreakAfterSequence ? 'text-gray-400' : 'text-blue-400 font-bold'">
{{ translate("uart.begin") }}
</span>
</el-button>
</template>
@ -197,7 +193,7 @@
<el-button size="small" @click="store.frameBreakAfterSequence = true">
<span
:class="store.frameBreakAfterSequence ? 'text-blue-400 font-bold' : 'text-gray-300'">
{{ translate("uart.end") }}
</span>
</el-button>
</template>
@ -210,10 +206,7 @@
</VueDraggable>
</el-collapse-item>
<el-collapse-item name="3">
<template #title>
其他
</template>
<el-collapse-item name="3" :title="translate('uart.other')">
<template #default>
<div class="flex flex-col gap-2">
<el-tooltip
@ -222,30 +215,24 @@
placement="right-start"
>
<template #content>
<p>ANSI转义码对终端和文本有很多作用比如改变文本颜色等</p>
<p>
简单了解->
<el-link target="_blank" href="https://zhuanlan.zhihu.com/p/390666800">
https://zhuanlan.zhihu.com/p/390666800
</el-link>
</p>
<div v-html="translate('uart.ansiTooltips')"></div>
</template>
<el-checkbox border v-model="store.enableAnsiDecode">解析ANSI转义码</el-checkbox>
<el-checkbox border v-model="store.enableAnsiDecode">{{ translate('uart.decodeAnsiEscapeCodes') }}</el-checkbox>
</el-tooltip>
<el-input v-model="store.filterValue" placeholder="文本;支持\n\x" clearable>
<el-input v-model="store.filterValue" :placeholder="translate('uart.textAndEscape')" clearable>
<template #prepend>
过滤
{{ translate("uart.filter") }}
</template>
</el-input>
<div class="border rounded flex flex-col">
<el-checkbox border v-model="store.dataFilterAutoUpdate">新数据自动刷新</el-checkbox>
<el-checkbox border v-model="store.dataFilterAutoUpdate">{{ translate('uart.autoUpdateNewData') }}</el-checkbox>
<el-tooltip content="提高间隔可减少CPU资源的使用" placement="right" effect="light"
<el-tooltip :content="translate('uart.updateFrequencyTooltip')" placement="right" effect="light"
:show-after="500">
<div class="flex gap-4 p-2">
<el-text>数据显示刷新间隔(ms)</el-text>
<el-text>{{ translate('uart.updateFrequency') }}</el-text>
<el-input-number
:step="10"
:min="10"
@ -300,34 +287,36 @@
</el-tab-pane>
<!-- ///////////////////////////////////////////////////////////// -->
<el-tab-pane label="发送" name="third">
<el-tab-pane :label="translate('uart.send')" name="third">
<template #label>{{ $t("uart.send") }}</template>
<div class="flex flex-col gap-2">
<el-input v-model="store.textPrefixValue" placeholder="支持\n\x" clearable>
<el-input v-model="store.textPrefixValue" :placeholder="translate('uart.textAndEscape')" clearable>
<template #prepend>
{{ `添加帧头►` }}
{{ translate('uart.addHeader') }}
</template>
</el-input>
<el-input v-model="store.textSuffixValue" placeholder="支持\n\x" clearable>
<el-input v-model="store.textSuffixValue" :placeholder="translate('uart.textAndEscape')" clearable>
<template #append>
添加帧尾
{{ translate('uart.addFooter') }}
</template>
</el-input>
</div>
</el-tab-pane>
<el-tab-pane label="透传" name="fourth" class="min-h-80">
<el-tab-pane :label="translate('uart.proxy')" name="fourth" class="min-h-80">
<template #label>{{ $t("uart.passthrough") }}</template>
<div class="flex flex-col gap-2">
<div class="border rounded bg-white p-2">
<span class="border-r px-2">TCP服务器端口</span>
<span class="border-r px-2">TCP {{ translate('uart.serverPort') }}</span>
<span class="px-2 cursor-not-allowed">1346</span>
</div>
<div>
<p><el-button @click="refreshTCPClientList" size="small" type="primary" :plain="true">刷新</el-button> </p>
<p><el-button @click="refreshTCPClientList" size="small" type="primary" :plain="true">{{ translate('uart.refresh') }}</el-button> {{ translate('uart.connectedClient') }}</p>
<el-table :data="dfStore.instanceList.filter((item) => (item.port_info as ISocketInfo).local_port === 1346)" empty-text="无客户端连接">
<el-table :data="dfStore.instanceList.filter((item) => (item.port_info as ISocketInfo).local_port === 1346)" :empty-text="translate('uart.noClientConnected')">
<el-table-column label="IP" prop="port_info.foreign_ip" />
<el-table-column label="端口" prop="port_info.foreign_port"/>
<el-table-column :label="translate('uart.port')" prop="port_info.foreign_port"/>
</el-table>
</div>
</div>
@ -338,7 +327,7 @@
<script setup lang="ts">
import {VueDraggable} from 'vue-draggable-plus'
import {ref} from "vue";
import {computed, ref} from "vue";
import {useDataViewerStore} from "@/stores/dataViewerStore";
import {useWsStore} from "@/stores/websocket";
import {globalNotify} from "@/composables/notification";
@ -349,6 +338,7 @@ import {useDataFlowStore} from "@/stores/useDataFlowStore";
import {wt_data_flow_get_instance_list, type ISocketInfo} from "@/api/apiDataFlow";
import {uart_set_baud, uart_set_config} from "@/api/apiUart";
import {useUartStore} from "@/stores/useUartStore";
import {translate, type TranslationKeys} from "@/locales";
const store = useDataViewerStore()
const uartStore = useUartStore()
@ -375,18 +365,18 @@ const uartDataBitsOptions = [
}
]
const uartParityOptions = [
const uartParityOptions = computed(() => [
{
key: 0,
label: "无none",
label: translate("uart.parityNone"),
}, {
key: 1,
label: "奇odd",
label: translate("uart.parityOdd"),
}, {
key: 2,
label: "偶even",
label: translate("uart.parityEven"),
}
]
]);
const uartStopBitsOptions = [

View File

@ -1,25 +1,27 @@
<template>
<div class="flex items-center mb-2 flex-wrap gap-2">
<el-button type="primary" @click="importSettings">导入</el-button>
<el-button type="warning" @click="exportSettings">导出</el-button>
<el-button type="primary" @click="importSettings">{{ translate('uart.import') }}</el-button>
<el-button type="warning" @click="exportSettings">{{ translate('uart.export') }}</el-button>
<el-tooltip
effect="light"
placement="top"
:show-after="500"
>
<template #content>
<p>刷新页面后生效</p>
<p>{{ translate('uart.resetTooltip') }}</p>
</template>
<el-button type="info" @click="resetSettings">重置</el-button>
<el-button type="info" @click="resetSettings">{{ translate('uart.reset') }}</el-button>
</el-tooltip>
<el-tooltip
effect="light"
placement="top"
:show-after="500"
>
<template #content>
<p>若存在多个页面会相互覆盖</p>
<p>{{ translate('uart.saveToLocalTooltip') }}</p>
</template>
<el-checkbox border v-model="store.autoSaveSettings">保存至本地</el-checkbox>
<el-checkbox border v-model="store.autoSaveSettings">{{ translate('uart.saveToLocal') }}</el-checkbox>
</el-tooltip>
</div>
@ -27,17 +29,17 @@
<el-button type="primary" @click="() => {
store.macroData.push({
value: '',
label: '发送',
label: translate('uart.send'),
id: store.macroId,
})
store.macroId++;
}">添加
}">{{ translate('uart.add') }}
</el-button>
<el-checkbox v-model="editMode" border>编辑</el-checkbox>
<el-checkbox v-model="draggableEnabled" border>拖拽</el-checkbox>
<el-checkbox v-model="editMode" border>{{ translate('uart.edit') }}</el-checkbox>
<el-checkbox v-model="draggableEnabled" border>{{ translate('uart.drag') }}</el-checkbox>
</div>
<div>
<el-alert v-if="store.ipChangeAlert" @close="store.ipChangeAlert=false">IP地址改变会导致配置丢失</el-alert>
<el-alert v-if="store.ipChangeAlert" @close="store.ipChangeAlert=false">{{ translate('uart.ipChangeAlert') }}</el-alert>
</div>
<VueDraggable v-model="store.macroData" handle=".sort-target"
@ -67,6 +69,7 @@ import {VueDraggable} from "vue-draggable-plus";
import {onMounted, ref} from "vue";
import {globalNotify, globalNotifyRightSide} from "@/composables/notification";
import {useDataViewerStore} from "@/stores/dataViewerStore";
import {translate} from "../../locales";
const editMode = ref(false);
const draggableEnabled = ref(true);

View File

@ -18,18 +18,18 @@
</el-popover>
<div class="flex">
<el-checkbox size="small" v-model="store.forceToBottom" label="自动滚动至底部" border/>
<el-checkbox size="small" v-model="store.forceToBottom" :label="translate('uart.autoScrollToBottom')" border/>
<el-tooltip
class="box-item"
effect="light"
placement="top"
>
<template #content>
<p>仅清除显示区域可用刷新恢复</p>
<p>{{ translate('uart.clearTooltip') }}</p>
</template>
<el-button size="small" @click="store.clearFilteredBuff">
<InlineSvg class="h-5" name="trash"></InlineSvg>
清屏
{{ $t('uart.clearScreen') }}
</el-button>
</el-tooltip>
@ -39,10 +39,10 @@
placement="top"
>
<template #content>
<p>与缓存同步+过滤</p>
<p>{{ translate('uart.clearTooltip') }}</p>
</template>
<el-button size="small" @click="store.refreshFilteredBuff">
刷新
{{ $t('page.update') }}
</el-button>
</el-tooltip>
<el-tooltip
@ -51,10 +51,10 @@
placement="top"
>
<template #content>
<p>仅停止刷新显示区后台继续接收数据</p>
<p>{{ translate('uart.autoUpdateTooltip') }}</p>
</template>
<el-checkbox size="small" border v-model="store.dataFilterAutoUpdate">
自动刷新
{{ $t('uart.autoUpdate') }}
</el-checkbox>
</el-tooltip>
</div>
@ -77,7 +77,7 @@
<p class="text-nowrap text-sm text-sky-500" v-else-if="item.type === 0" type="primary" v-show="store.showTimestamp">
<span>{{ item.time }}</span>TX-|</p>
<p class="text-nowrap text-sm text-amber-800" v-else type="primary" v-show="store.showTimestamp">
<span>{{ item.time }}</span>未发送|</p>
<span>{{ item.time }}</span>NS-|</p>
<p v-show="store.showText"
v-html="item.str"></p>
@ -111,7 +111,7 @@
<p class="text-nowrap text-sm text-sky-500" v-else-if="item.type === 0" type="primary" v-show="store.showTimestamp">
<span>{{ item.time }}</span>TX-|</p>
<p class="text-nowrap text-sm text-amber-800" v-else type="primary" v-show="store.showTimestamp">
<span>{{ item.time }}</span>未发送|</p>
<span>{{ item.time }}</span>NS-|</p>
<p v-show="store.showText"
v-html="item.str"></p>
</div>
@ -132,7 +132,7 @@
<div class="shrink-0 flex h-8 mt-0.5 text-xs">
<div class="flex shrink-0">
<el-tooltip content="未满足断帧规则的数据未超时暂时实时显示在此区域。超过8192字节自动断帧" effect="light">
<el-tooltip :content="translate('uart.tempDisplayTooltip')" effect="light">
<InlineSvg name="help" class="w-3.5 h-3.5 text-gray-500 cursor-help"></InlineSvg>
</el-tooltip>
<p></p>
@ -153,10 +153,10 @@
</el-tag>
</el-link>
<el-tooltip content="实际频率受界面刷新率影响,如需要更精确,可以尝试关闭‘自动刷新’" placement="right" effect="light" :show-after="1000">
<el-tooltip :content="translate('uart.loopSendTooltip')" placement="right" effect="light" :show-after="1000">
<div class="flex align-center">
<el-checkbox v-model="store.enableLoopSend" class="font-mono font-bold max-h-5" size="small" border>
循环发送(ms)
{{ translate('uart.loopSend') }}(ms)
</el-checkbox>
<el-input-number
v-model="store.loopSendFreq"
@ -170,7 +170,7 @@
</el-tooltip>
<el-link @click="store.isSendTextFormat = !store.isSendTextFormat">
<el-tag class="font-mono font-bold" size="small">发送格式{{ store.isSendTextFormat ? "文本" : "HEX" }}</el-tag>
<el-tag class="font-mono font-bold" size="small">{{ translate('uart.sendFormat') }}{{ store.isSendTextFormat ? translate("uart.text") : "HEX" }}</el-tag>
</el-link>
</div>
<div class="flex gap-2">
@ -189,7 +189,7 @@
<el-link class="flex" @click="store.clearDataBuff" type="warning">
<InlineSvg class="h-5" name="trash"></InlineSvg>
</el-link>
<span class="align-text-bottom">缓存帧数: {{ store.dataBufLength }}/30000</span>
<span class="align-text-bottom">{{ translate('uart.cachedFrame') }}: {{ store.dataBufLength }}/30000</span>
</el-tag>
</div>
</div>
@ -197,14 +197,14 @@
<div class="flex flex-row font-mono">
<el-input type="textarea" :autosize="{ minRows: 1, maxRows: 6}" v-model="store.uartInputTextBox" clearable
:placeholder="store.isSendTextFormat ?
'输入文本,支持\\n\\x转义' :
'输入HEX格式'"
translate('uart.textAndEscape') :
'HEX'"
@keydown="handleTextboxKeydown"
></el-input>
<el-tooltip content="Ctrl+回车" placement="top" :auto-close="500">
<el-tooltip content="Ctrl+Enter" placement="top" :auto-close="500">
<el-button type="primary"
@click="onSendClick">
{{ (store.isSendTextFormat || store.isHexStringValid) ? "发送" : "格式化" }}
{{ (store.isSendTextFormat || store.isHexStringValid) ? translate("uart.send") : translate("格式化") }}
</el-button>
</el-tooltip>
</div>
@ -217,6 +217,7 @@ import InlineSvg from "@/components/InlineSvg.vue";
import TextDataConfig from "@/views/text-data-viewer/textDataConfig.vue";
import {debouncedWatch} from "@vueuse/core";
import {globalNotify} from "@/composables/notification";
import {translate} from "../../locales";
const count = ref(0);
const vuetifyVirtualScrollBarRef = ref(document.body);