基于vue3.0和element-plus的组件库
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

428 lines
10 KiB

<template>
<div class="json-view-demo">
<section class="demo-panel">
<div class="demo-panel__header">
<div>
<h2>JSON View</h2>
<p>Read-only JSON tree with collapse controls, virtualization, and low-node CSS-based indentation guides.</p>
</div>
</div>
<div class="demo-grid">
<div class="demo-card textarea">
<h3>Source</h3>
<textarea v-model="source" class="demo-source"></textarea>
<p v-if="parseError" class="demo-error">{{ parseError }}</p>
</div>
<div class="demo-card options">
<h3>Options</h3>
<label class="demo-option">
<span>Virtual</span>
<input v-model="options.virtual" type="checkbox" />
</label>
<label class="demo-option">
<span>Dynamic height</span>
<input v-model="options.dynamicHeight" type="checkbox" />
</label>
<label class="demo-option">
<span>Show line guides</span>
<input v-model="options.showLine" type="checkbox" />
</label>
<label class="demo-option">
<span>Show line numbers</span>
<input v-model="options.showLineNumber" type="checkbox" />
</label>
<label class="demo-option">
<span>Show toggles</span>
<input v-model="options.showIcon" type="checkbox" />
</label>
<label class="demo-option">
<span>Show collapsed length</span>
<input v-model="options.showLength" type="checkbox" />
</label>
<label class="demo-option">
<span>Theme</span>
<select v-model="options.theme">
<option value="light">light</option>
<option value="dark">dark</option>
</select>
</label>
<label class="demo-option">
<span>Indent</span>
<input v-model.number="options.indent" min="1" max="6" type="number" />
</label>
<label class="demo-option">
<span>Height</span>
<input v-model.number="options.height" min="180" max="720" step="20" type="number" />
</label>
<label class="demo-option">
<span>Item height</span>
<input v-model.number="options.itemHeight" min="18" max="48" step="2" type="number" />
</label>
<label class="demo-option">
<span>Collapse deeper than</span>
<input v-model.number="options.deep" min="1" max="8" type="number" />
</label>
<label class="demo-option">
<span>Collapse length over</span>
<input v-model.number="options.collapsedNodeLength" min="1" max="40" type="number" />
</label>
</div>
</div>
</section>
<section class="demo-panel">
<div class="demo-panel__header">
<div>
<h3>Parsed Input</h3>
<p>Primary viewer using the current source text, including the built-in copy menu and one custom item.</p>
</div>
</div>
<JsonView
:data="parsedData"
:virtual="options.virtual"
:dynamic-height="options.dynamicHeight"
:show-line="options.showLine"
:show-line-number="options.showLineNumber"
:show-icon="options.showIcon"
:show-length="options.showLength"
:theme="options.theme"
:indent="options.indent"
:height="options.height"
:item-height="options.itemHeight"
:deep="normalizeNumber(options.deep)"
:collapsed-node-length="normalizeNumber(options.collapsedNodeLength)"
:menu-items="menuItems"
/>
</section>
<section class="demo-panel">
<div class="demo-panel__header">
<div>
<h3>Large Virtualized Payload</h3>
<p>Stress case with a large array to exercise flattening and virtualization.</p>
</div>
</div>
<JsonView
:data="largeData"
:virtual="true"
:dynamic-height="false"
:show-line="true"
:show-line-number="true"
:show-icon="true"
:show-length="true"
theme="dark"
:indent="2"
:height="420"
:item-height="22"
:collapsed-node-length="18"
/>
</section>
<section class="demo-panel">
<div class="demo-panel__header">
<div>
<h3>JS Collections And Cycles</h3>
<p>Verifies support for `Map`, `Set`, and cyclic references that are not representable in JSON text.</p>
</div>
</div>
<JsonView
:data="jsData"
:virtual="true"
:dynamic-height="true"
:show-line="true"
:show-line-number="true"
:show-icon="true"
:show-length="true"
theme="light"
:indent="2"
:height="320"
:item-height="20"
/>
</section>
<section class="demo-panel">
<div class="demo-panel__header">
<div>
<h3>Vue Reactivity Wrappers</h3>
<p>Exercises nested `ref`, `computed`, `reactive`, and reactive `Map`/`Set` values.</p>
</div>
</div>
<JsonView
:data="vueWrappedData"
:virtual="true"
:dynamic-height="true"
:show-line="true"
:show-line-number="true"
:show-icon="true"
:show-length="true"
theme="dark"
:indent="2"
:height="320"
:item-height="20"
/>
</section>
</div>
</template>
<script setup lang="ts">
import { computed, reactive, ref } from "vue";
import { JsonView } from "noob-mengyxu";
const initialData = {
id: "case-1024",
owner: {
id: 7,
name: "Avery Stone",
roles: ["admin", "ops", "audit"],
},
metrics: {
total: 1532,
successRate: 0.9821,
active: true,
notes: null,
},
timeline: [
{ at: 1710000000, event: "created" },
{ at: 1710003600, event: "validated" },
{ at: 1710007200, event: "published" },
],
payload: {
summary: "This is a longer string intended to demonstrate optional dynamic row height handling in the viewer.",
tags: ["alpha", "beta", "gamma"],
nested: {
one: { two: { three: { four: "deep value" } } },
mixed: [1, "two", false, null, { end: true }],
},
},
};
const source = ref(JSON.stringify(initialData, null, 2));
const parseError = ref("");
const options = reactive({
virtual: true,
dynamicHeight: true,
showLine: true,
showLineNumber: false,
showIcon: true,
showLength: true,
theme: "light" as "light" | "dark",
indent: 2,
height: 360,
itemHeight: 20,
deep: 4,
collapsedNodeLength: 10,
});
const parsedData = computed(() => {
try {
parseError.value = "";
return JSON.parse(source.value);
} catch (error) {
parseError.value = error instanceof Error ? error.message : String(error);
return initialData;
}
});
const largeData = computed(() => ({
meta: {
generatedAt: "2026-04-09T11:30:00Z",
totalRows: 1500,
},
items: Array.from({ length: 1500 }, (_, index) => ({
id: index + 1,
group: `batch-${Math.floor(index / 50)}`,
active: index % 3 === 0,
score: Number((Math.sin(index / 10) * 100).toFixed(3)),
tags: [`tag-${index % 5}`, `tag-${index % 7}`, `tag-${index % 11}`],
details: {
owner: `user-${index % 23}`,
region: ["us", "eu", "apac"][index % 3],
comment: `Item ${
index + 1
} contains enough text to keep the viewer honest about large arrays and scrolling behavior.`,
},
})),
}));
const jsData = computed(() => {
const owner = {
id: 7,
name: "Avery Stone",
};
const cycleRoot: Record<string, unknown> = {
id: "cyclic-root",
owner,
};
const linked = {
parent: cycleRoot,
owner,
};
cycleRoot.self = cycleRoot;
cycleRoot.linked = linked;
return {
owner,
pairMap: new Map<unknown, unknown>([
["owner", owner],
[42, { status: "ok" }],
[{ kind: "object-key" }, new Set(["alpha", "beta"])],
]),
valueSet: new Set<unknown>(["alpha", 42, owner, cycleRoot]),
cycleRoot,
};
});
const vueWrappedData = computed(() => {
const status = ref("ready");
const score = ref(42);
const doubledScore = computed(() => score.value * 2);
const owner = reactive({
id: ref(7),
name: "Avery Stone",
role: computed(() => "ops"),
});
const wrappedMap = reactive(
new Map<unknown, unknown>([
["status", status],
["doubled", doubledScore],
["owner", owner],
])
);
const wrappedSet = reactive(new Set<unknown>([status, doubledScore, owner]));
const state = reactive({
status,
doubledScore,
owner,
wrappedMap,
wrappedSet,
nested: {
currentScore: score,
summary: computed(() => `${status.value}:${doubledScore.value}`),
},
});
const rawState = state as unknown as Record<string, unknown>;
rawState.self = ref(state);
return ref({
root: state,
derived: computed(() => ({
active: status.value === "ready",
ownerName: owner.name,
})),
});
});
const menuItems = ({ value, copy }: { value: unknown; copy: (text: string) => Promise<boolean> }) => [
{
key: "copy-type",
label: "Copy Type",
onSelect: async () => {
const type =
value === null ? "null" : Array.isArray(value) ? "array" : typeof value === "object" ? "object" : typeof value;
await copy(type);
},
},
];
function normalizeNumber(value: number) {
return Number.isFinite(value) && value > 0 ? value : undefined;
}
</script>
<style lang="scss" scoped>
.json-view-demo {
display: grid;
gap: 20px;
padding: 20px;
}
.demo-panel {
background: #fff;
border: 1px solid #dfe4ea;
border-radius: 14px;
display: grid;
gap: 16px;
padding: 18px;
}
.demo-panel__header h2,
.demo-panel__header h3 {
margin: 0 0 6px;
}
.demo-panel__header p {
color: #5b6472;
margin: 0;
}
.demo-grid {
display: flex;
flex-direction: row;
gap: 16px;
}
.demo-card {
display: flex;
flex-direction: column;
gap: 12px;
}
.demo-card.textarea {
flex: 1;
min-width: 0;
}
.demo-card.options {
/* margin: 20px; */
}
.demo-card h3 {
margin: 0;
}
.demo-source {
border: 1px solid #cbd5e1;
border-radius: 10px;
font: 13px/1.5 ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, Liberation Mono, monospace;
min-height: 260px;
padding: 12px;
resize: vertical;
}
.demo-option {
align-items: center;
display: grid;
gap: 10px;
grid-template-columns: 1fr auto;
}
.demo-option input,
.demo-option select {
min-width: 96px;
}
.demo-error {
color: #b42318;
margin: 0;
}
@media (max-width: 960px) {
.demo-grid {
grid-template-columns: 1fr;
}
}
</style>