121 lines
3.5 KiB
Vue
121 lines
3.5 KiB
Vue
<!--
|
||
YAML ↔ JSON 转换工具:基于 js-yaml 在浏览器本地完成 YAML 与 JSON 互转,内容不上传。
|
||
-->
|
||
<template>
|
||
<div class="page-wrap">
|
||
<h2 class="page-title">YAML ↔ JSON 转换</h2>
|
||
<p class="page-desc">YAML 与 JSON 格式互转,全部在浏览器本地完成,内容不会上传。</p>
|
||
|
||
<!-- YAML 隐式类型解析提示:未加引号的日期/数字字符串可能被解析为对应类型 -->
|
||
<t-alert
|
||
class="tip-alert"
|
||
theme="warning"
|
||
message="提示:YAML 解析可能将未加引号的日期或数字字符串解析为日期/数字类型,若有歧义建议给字符串加引号。"
|
||
/>
|
||
|
||
<t-card class="tool-section" title="转换">
|
||
<t-radio-group v-model="direction" variant="default-filled">
|
||
<t-radio-button value="y2j">YAML → JSON</t-radio-button>
|
||
<t-radio-button value="j2y">JSON → YAML</t-radio-button>
|
||
</t-radio-group>
|
||
<t-textarea
|
||
v-model="input"
|
||
class="input-area"
|
||
:placeholder="placeholder"
|
||
:autosize="{ minRows: 8, maxRows: 16 }"
|
||
/>
|
||
<div class="form-actions">
|
||
<t-button theme="primary" @click="convert">转换</t-button>
|
||
</div>
|
||
</t-card>
|
||
|
||
<ErrorAlert :message="error" @close="error = ''" />
|
||
|
||
<t-card v-if="output" class="tool-section" title="转换结果">
|
||
<t-textarea v-model="output" :autosize="{ minRows: 6, maxRows: 16 }" readonly />
|
||
<div class="form-actions">
|
||
<CopyButton :text="output" />
|
||
</div>
|
||
</t-card>
|
||
|
||
<HistoryPanel
|
||
:items="history.items"
|
||
@select="onHistorySelect"
|
||
@remove="history.remove"
|
||
@clear="history.clear"
|
||
/>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
import { computed, ref } from 'vue'
|
||
import { load, dump } from 'js-yaml'
|
||
import ErrorAlert from '@/components/common/ErrorAlert.vue'
|
||
import CopyButton from '@/components/common/CopyButton.vue'
|
||
import HistoryPanel from '@/components/common/HistoryPanel.vue'
|
||
import { useHistory } from '@/composables/useHistory'
|
||
|
||
type Direction = 'y2j' | 'j2y'
|
||
|
||
const direction = ref<Direction>('y2j')
|
||
const input = ref('')
|
||
const output = ref('')
|
||
const error = ref('')
|
||
|
||
const history = useHistory('yaml-json')
|
||
|
||
// 占位提示随方向切换,放在脚本中避免模板内多行表达式
|
||
const placeholder = computed(() =>
|
||
direction.value === 'y2j'
|
||
? '输入 YAML 内容,例如:\nname: 张三\nage: 18'
|
||
: '输入 JSON 内容,例如:\n{ "name": "张三", "age": 18 }',
|
||
)
|
||
|
||
function convert() {
|
||
error.value = ''
|
||
output.value = ''
|
||
const text = input.value.trim()
|
||
if (!text) {
|
||
error.value = '请输入要转换的内容'
|
||
return
|
||
}
|
||
try {
|
||
if (direction.value === 'y2j') {
|
||
// load 返回 unknown;空文档会返回 undefined,此时按解析失败处理
|
||
const data = load(text)
|
||
if (data === undefined) {
|
||
error.value = 'YAML 内容为空或无法解析'
|
||
return
|
||
}
|
||
output.value = JSON.stringify(data, null, 2)
|
||
} else {
|
||
output.value = dump(JSON.parse(text))
|
||
}
|
||
// 历史只保留前 300 字符,避免长文本撑爆 localStorage
|
||
history.add(input.value.slice(0, 300))
|
||
} catch (e) {
|
||
error.value = `转换失败:${(e as Error).message}`
|
||
}
|
||
}
|
||
|
||
function onHistorySelect(text: string) {
|
||
input.value = text
|
||
convert()
|
||
}
|
||
</script>
|
||
|
||
<style scoped>
|
||
.input-area {
|
||
margin-top: 12px;
|
||
}
|
||
.form-actions {
|
||
display: flex;
|
||
gap: 8px;
|
||
margin-top: 16px;
|
||
flex-wrap: wrap;
|
||
}
|
||
.tip-alert {
|
||
margin-bottom: 12px;
|
||
}
|
||
</style>
|