feat: revamp demo

This commit is contained in:
Justineo
2025-09-24 01:27:35 +08:00
committed by GU Yiling
parent 1da2bf7811
commit def0ad5bf5
47 changed files with 4180 additions and 1740 deletions

View File

@ -1,12 +1,14 @@
<script setup>
<script setup lang="ts">
import { provide, computed, ref, watch } from "vue";
import { useUrlSearchParams } from "@vueuse/core";
import { use } from "echarts/core";
import { useScrollLock, useUrlSearchParams } from "@vueuse/core";
import { use, registerTheme } from "echarts/core";
import { CanvasRenderer, SVGRenderer } from "echarts/renderers";
import { INIT_OPTIONS_KEY } from "../src/ECharts";
import { track } from "@vercel/analytics";
import darkTheme from "echarts/lib/theme/dark.js";
import { INIT_OPTIONS_KEY, THEME_KEY } from "../src/ECharts";
import type { InitOptions } from "../src/types";
import LogoChart from "./examples/LogoChart.vue";
import BarChart from "./examples/BarChart.vue";
import LineChart from "./examples/LineChart.vue";
import PieChart from "./examples/PieChart.vue";
@ -19,104 +21,189 @@ import GlChart from "./examples/GlChart.vue";
import ManualChart from "./examples/ManualChart.vue";
import CodeGen from "./CodeGen.vue";
import { useDemoDark } from "./composables/useDemoDark";
import {
getScrollLockTarget,
getScrollbarWidth,
isClient,
setHash,
} from "./utils/dom";
type Renderer = "canvas" | "svg";
use([CanvasRenderer, SVGRenderer]);
const params = useUrlSearchParams();
const initOptions = computed(() => ({
renderer: params.renderer || "canvas",
registerTheme("dark", darkTheme);
const isDark = useDemoDark();
const params = useUrlSearchParams<{ renderer?: Renderer }>();
const selectedRenderer = computed<Renderer>(() =>
params.renderer === "svg" ? "svg" : "canvas",
);
const initOptions = computed<InitOptions>(() => ({
renderer: selectedRenderer.value,
}));
const theme = computed(() => (isDark.value ? "dark" : undefined));
provide(INIT_OPTIONS_KEY, initOptions);
provide(THEME_KEY, theme);
const codeOpen = ref(location.hash === "#codegen");
const lockTarget = ref<HTMLElement | null>(getScrollLockTarget());
const docRoot = isClient ? document.documentElement : null;
if (codeOpen.value) {
track("codegen", { from: "link" });
}
const scrollLock = isClient ? useScrollLock(lockTarget, false) : ref(false);
function openCodegen() {
codeOpen.value = true;
track("codegen", { from: "click" });
}
const initialCodegenOpen = isClient && window.location.hash === "#codegen";
const codeOpen = ref(initialCodegenOpen);
watch(codeOpen, (open) => {
if (open) {
location.hash = "#codegen";
} else {
location.hash = "";
const trackCodegen = (source: "link" | "click"): void => {
if (isClient) {
track("codegen", { from: source });
}
});
};
if (initialCodegenOpen) {
trackCodegen("link");
}
function openCodegen(): void {
codeOpen.value = true;
trackCodegen("click");
}
const applyCodegenState = (open: boolean): void => {
if (isClient) {
if (docRoot) {
docRoot.style.paddingRight = open ? `${getScrollbarWidth()}px` : "";
}
scrollLock.value = open;
}
setHash(open ? "#codegen" : "");
};
watch(codeOpen, applyCodegenState, { immediate: true });
</script>
<template>
<main>
<logo-chart />
<img id="logo" src="/favicon.svg" alt="Vue ECharts" />
<h1>
<a href="https://github.com/ecomfe/vue-echarts">Vue ECharts</a>
</h1>
<p class="desc">
Vue.js component for Apache ECharts. (<a
href="https://github.com/ecomfe/vue-echarts#readme"
>docs</a
>)
</p>
<h2 class="sep">Examples</h2>
<p class="desc">Vue.js component for Apache ECharts.</p>
<p>
<small
>See
<a href="https://echarts.apache.org/examples/en/index.html"
>echarts.apache.org/examples</a
>
for all examples.</small
>
<a href="https://npmjs.com/package/vue-echarts"
><img alt="npm version" src="https://img.shields.io/npm/v/vue-echarts"
/></a>
<a href="https://codecov.io/gh/ecomfe/vue-echarts"
><img
alt="test coverage"
src="https://img.shields.io/codecov/c/github/ecomfe/vue-echarts"
/></a>
</p>
<bar-chart />
<line-chart />
<pie-chart />
<polar-chart />
<scatter-chart />
<geo-chart />
<radar-chart />
<connect-chart />
<gl-chart />
<manual-chart />
<section class="examples-head" aria-label="Examples">
<a
class="examples-deco"
href="https://echarts.apache.org/examples/en/index.html"
target="_blank"
rel="noopener"
aria-label="All examples"
title="All examples"
>
<span class="rule" aria-hidden="true"></span>
<span class="dot" aria-hidden="true"></span>
<span class="dot" aria-hidden="true"></span>
<span class="dot" aria-hidden="true"></span>
<span class="rule" aria-hidden="true"></span>
</a>
</section>
<footer>
<a href="//github.com/Justineo">@Justineo</a>|
<a href="//github.com/ecomfe/vue-echarts/blob/master/LICENSE"
>MIT License</a
>|
<a href="//github.com/ecomfe/vue-echarts">View on GitHub</a>
<BarChart />
<LineChart />
<PieChart />
<PolarChart />
<ScatterChart />
<GeoChart />
<RadarChart />
<ConnectChart />
<GlChart />
<ManualChart />
<footer class="site-footer" aria-label="Footer">
<small class="footer-links">
<a href="//github.com/ecomfe/vue-echarts/blob/master/LICENSE">MIT</a>
<span aria-hidden="true">·</span>
<a href="//github.com/ecomfe/vue-echarts">GitHub</a>
</small>
</footer>
<aside class="renderer">
<button
:class="{
active: initOptions.renderer === 'canvas',
}"
@click="params.renderer = 'canvas'"
<div class="toolbar" role="toolbar" aria-label="Controls">
<div
:class="[
'toggle',
'renderer-toggle',
initOptions.renderer === 'svg' ? 'right-active' : 'left-active',
]"
role="group"
aria-label="Renderer"
>
Canvas
</button>
<button
:class="{
active: initOptions.renderer === 'svg',
}"
@click="params.renderer = 'svg'"
<div class="indicator" aria-hidden="true"></div>
<button
:class="{ active: initOptions.renderer === 'canvas' }"
:aria-pressed="initOptions.renderer === 'canvas'"
type="button"
@click="params.renderer = 'canvas'"
>
Canvas
</button>
<button
:class="{ active: initOptions.renderer === 'svg' }"
:aria-pressed="initOptions.renderer === 'svg'"
type="button"
@click="params.renderer = 'svg'"
>
SVG
</button>
</div>
<div
:class="[
'toggle',
'theme-toggle',
isDark ? 'right-active' : 'left-active',
]"
role="group"
aria-label="Theme"
>
SVG
<div class="indicator" aria-hidden="true"></div>
<button
:class="{ active: !isDark }"
:aria-pressed="!isDark"
type="button"
@click="isDark = false"
>
Light
</button>
<button
:class="{ active: isDark }"
:aria-pressed="isDark"
type="button"
@click="isDark = true"
>
Dark
</button>
</div>
<button class="codegen" type="button" @click="openCodegen">
Generate code
</button>
</aside>
</div>
<aside class="codegen">
<button @click="openCodegen"> <code>import</code> Codegen</button>
</aside>
<code-gen v-model:open="codeOpen" :renderer="initOptions.renderer" />
<CodeGen v-model:open="codeOpen" :renderer="selectedRenderer" />
</main>
</template>
@ -125,6 +212,7 @@ watch(codeOpen, (open) => {
*::before,
*::after {
box-sizing: border-box;
scrollbar-width: thin;
}
html {
@ -133,82 +221,56 @@ html {
body {
margin: 0;
padding: 3em 0 0;
font-family: Inter, "Helvetica Neue", Arial, sans-serif;
font-weight: 300;
color: #666;
padding: 56px 0 0;
font-family: var(--font-sans);
color: var(--muted);
background: var(--bg);
text-align: center;
}
a {
color: inherit;
color: var(--link);
text-decoration: none;
box-shadow: 0 1px 0 0 #42b983;
transition: box-shadow 0.2s;
transition: color 0.2s ease;
&:hover {
box-shadow: 0 2px 0 0 #42b983;
color: var(--accent-strong);
}
}
h1 {
margin-bottom: 1em;
font-family: Inter, "Helvetica Neue", Arial, sans-serif;
margin-bottom: 1rem;
font-family: var(--font-sans);
}
h2 {
margin-top: 1em;
margin-bottom: 1em;
}
.sep {
margin-top: 6em;
margin-bottom: 1.8em;
display: flex;
justify-content: center;
align-items: center;
gap: 12px;
font-size: 1.25em;
color: #7f8c8d;
opacity: 0.6;
&::before,
&::after {
content: "";
display: block;
width: 48px;
border-bottom: 1px dotted currentColor;
}
margin-top: 1rem;
margin-bottom: 1rem;
}
h3 {
margin-top: 2em;
padding-top: 1em;
font-size: 1.2em;
small {
font-weight: 300;
opacity: 0.7;
}
margin-top: 2rem;
padding-top: 1rem;
font-size: 1.2rem;
button {
margin-left: 1em;
margin-left: 1rem;
vertical-align: middle;
}
}
.desc {
margin-bottom: 3em;
color: #7f8c8d;
margin-bottom: 3rem;
color: var(--muted);
a {
color: #42b983;
color: var(--accent);
}
}
p small {
font-size: 0.8em;
color: #7f8c8d;
font-size: 0.8rem;
color: var(--muted);
}
p {
@ -218,99 +280,384 @@ p {
button + select,
select + button,
select + select {
margin-left: 0.5em;
margin-left: 0.5rem;
}
}
input::-webkit-outer-spin-button,
input::-webkit-inner-spin-button {
-webkit-appearance: none;
margin: 0;
}
input[type="number"] {
appearance: textfield;
}
input[type="text"],
input[type="number"] {
cursor: text;
}
pre {
display: inline-block;
padding: 0.8em;
background-color: #f9f9f9;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.125);
padding: 0.8rem 1rem;
background-color: var(--surface-2);
border: 1px solid var(--border);
border-radius: var(--r-s);
text-align: left;
}
pre,
code,
textarea {
font-family:
ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono",
monospace;
font-family: var(--font-mono);
}
footer {
margin: 5em 0 3em;
font-size: 0.5em;
vertical-align: middle;
.examples-head {
display: flex;
justify-content: center;
align-items: center;
gap: 10px;
margin: 3.5rem 0 1.6rem;
color: var(--muted);
}
.examples-head .examples-deco {
display: inline-flex;
align-items: center;
gap: 10px;
color: inherit;
text-decoration: none;
}
.examples-head .examples-deco .rule {
display: inline-block;
width: 64px;
max-width: 20vw;
height: 1px;
background: currentColor;
opacity: 0.35;
}
.examples-head .examples-deco .dot {
width: 4px;
height: 4px;
border-radius: 50%;
background: currentColor;
opacity: 0.6;
display: inline-block;
}
a {
display: inline-block;
margin: 0 5px;
color: #7f8c8d;
font-size: 2em;
.toolbar {
position: fixed;
top: 16px;
left: 16px;
z-index: 1000;
display: inline-flex;
align-items: center;
gap: 12px;
padding: 8px 12px;
border-radius: var(--r-l);
border: 1px solid var(--border);
background: color-mix(in srgb, var(--surface) 50%, transparent);
backdrop-filter: blur(14px);
-webkit-backdrop-filter: blur(14px);
white-space: nowrap;
}
.toggle {
position: relative;
display: inline-flex;
align-items: stretch;
gap: 0;
padding: 2px;
border: 1px solid var(--border);
border-radius: var(--r-m);
background: var(--surface);
height: 2.25rem;
flex-shrink: 0;
overflow: hidden;
}
.toggle .indicator {
position: absolute;
top: 3px;
bottom: 3px;
left: 3px;
width: calc(50% - 3px);
border-radius: calc(var(--r-m) - 3px);
background: color-mix(in srgb, var(--accent) 18%, var(--surface) 82%);
transition: transform 0.25s cubic-bezier(0.34, 1.56, 0.64, 1);
}
.toggle.right-active .indicator {
transform: translateX(100%);
}
.toggle.left-active .indicator {
transform: translateX(0);
}
.toggle button {
position: relative;
z-index: 1;
flex: 1;
border: none;
background: none;
color: var(--muted);
font-size: 0.875rem;
font-weight: 500;
transition: color 0.2s ease;
display: inline-flex;
align-items: center;
justify-content: center;
padding: 0 var(--space-3);
height: auto;
min-width: 0;
}
.toggle button.active {
color: var(--heading);
}
.toggle button:hover,
.toggle button:active {
background: none;
}
.toggle button:hover,
.toggle button:focus-visible {
color: var(--text);
}
.toggle button:focus-visible {
outline: none;
box-shadow: none;
}
.toggle button.active:focus-visible {
box-shadow: var(--focus);
border-radius: calc(var(--r-m) - 3px);
}
.codegen {
font-weight: 500;
padding: 0 1rem;
}
.codegen:hover {
color: var(--heading);
}
@media (max-width: 640px) {
body {
padding-bottom: 96px;
}
.examples-head .examples-deco {
gap: 8px;
}
.examples-head .examples-deco .rule {
width: 36px;
}
.toolbar {
top: auto;
bottom: 20px;
left: 50%;
transform: translateX(-50%);
gap: 8px;
padding: 6px 10px;
border-radius: var(--r-m);
}
.toggle {
border-radius: var(--r-m);
}
.toggle button {
font-size: 0.8rem;
}
.toggle .indicator {
top: 2px;
bottom: 2px;
left: 2px;
width: calc(50% - 2px);
border-radius: calc(var(--r-m) - 4px);
}
.codegen {
display: none;
}
.fig > .echarts {
border-right: none !important;
border-left: none !important;
border-radius: 0 !important;
}
}
.sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border: 0;
}
.fig > .echarts {
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--r-l);
box-shadow: none;
}
.sep {
opacity: 0.8;
}
.sep::before,
.sep::after {
border-bottom-style: solid;
border-bottom-color: color-mix(in srgb, var(--border) 70%, transparent);
}
.dialog {
background: var(--surface);
border: 1px solid var(--border);
}
.message {
background: var(--text);
color: var(--surface);
border: 1px solid color-mix(in srgb, var(--text) 20%, transparent);
}
@media (prefers-reduced-motion: reduce) {
* {
animation: none !important;
transition: none !important;
}
}
h1,
h2,
h3 {
color: #2c3e50;
color: var(--heading);
font-weight: 400;
a,
a:hover {
text-decoration: none;
box-shadow: none;
color: inherit;
}
}
button,
select,
input {
border: 1px solid #4fc08d;
border-radius: 0.5em;
background-color: #fff;
color: #42b983;
cursor: pointer;
input:not([type="checkbox"]):not([type="radio"]) {
font: inherit;
padding: 0 0.5em;
transition: all 0.2s;
font-size: 0.9rem;
color: var(--heading);
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--r-m);
padding: 0 0.75rem;
height: 2.25rem;
display: inline-flex;
align-items: center;
justify-content: center;
box-sizing: border-box;
line-height: 1.2;
}
&:focus {
outline: none;
}
button {
cursor: pointer;
font-weight: 500;
}
&:focus-visible {
box-shadow: 0 0 1px #4fc08d;
}
select {
cursor: pointer;
}
&:active {
background: rgba(79, 192, 141, 0.2);
}
button:hover,
select:hover {
background: var(--surface-2);
}
&[disabled] {
opacity: 0.5;
cursor: not-allowed;
}
button:active {
background: color-mix(in srgb, var(--surface-2) 70%, var(--surface) 30%);
}
button:focus-visible,
select:focus-visible,
input:not([type="checkbox"]):not([type="radio"]):focus-visible {
outline: none;
box-shadow: var(--focus);
}
button[disabled],
button:disabled {
opacity: 0.5;
cursor: not-allowed;
background: var(--surface);
color: var(--muted);
}
button[disabled]:hover,
button:disabled:hover {
background: var(--surface);
color: var(--muted);
}
label {
display: inline-flex;
align-items: center;
justify-content: center;
justify-content: flex-start;
}
button,
input,
label,
select {
font-size: 0.75em;
height: 2em;
x-vue-echarts {
text-align: left;
}
#logo {
display: inline-flex;
width: 128px;
height: 128px;
pointer-events: none;
width: 108px;
height: 108px;
margin-top: 36px;
margin-bottom: 12px;
transform-origin: 50% 50%;
transition: transform 260ms cubic-bezier(0.22, 1, 0.36, 1);
will-change: transform, box-shadow;
border-radius: 50%;
}
#logo:hover {
transform: translateY(-2px) scale(1.045) rotate(-3deg);
animation: logo-pulse 1400ms ease-out infinite;
}
@keyframes logo-pulse {
0% {
box-shadow: 0 0 0 0 color-mix(in srgb, var(--accent) 36%, transparent);
}
70% {
box-shadow: 0 0 0 12px color-mix(in srgb, var(--accent) 0%, transparent);
}
100% {
box-shadow: 0 0 0 0 transparent;
}
}
@media (prefers-reduced-motion: reduce) {
#logo {
transition: none;
}
#logo:hover {
transform: none;
animation: none;
}
}
.modal {
@ -320,7 +667,7 @@ select {
right: 0;
bottom: 0;
left: 0;
background-color: rgba(0, 0, 0, 0.2);
background-color: rgba(2, 6, 23, 0.35);
z-index: 2147483646;
&.open {
@ -331,28 +678,18 @@ select {
img {
position: absolute;
background-color: #404a59;
background-color: var(--surface);
max-width: 80vw;
border: 2px solid #fff;
border-radius: 3px;
box-shadow: 0 0 30px rgba(0, 0, 0, 0.2);
border: 1px solid var(--border);
border-radius: var(--r-s);
box-shadow: var(--shadow);
}
}
@media (max-width: 980px) {
p {
select {
text-indent: calc(50% - 1em);
}
select,
label {
border: 1px solid #4fc08d;
border-radius: 2em;
background-color: #fff;
color: #42b983;
cursor: pointer;
transition: opacity 0.3s;
text-indent: calc(50% - 1rem);
}
button,
@ -360,95 +697,57 @@ select {
select,
label {
flex: 1 0;
margin: 0 0.5em;
padding: 0;
line-height: 2em;
margin: 0 0.5rem;
max-width: 40vw;
border-radius: 0.5em;
font-size: 0.8em;
font-size: 0.8rem;
}
input[type="checkbox"] {
display: none;
&:checked + label {
background: #42b983;
color: #fff;
background: color-mix(in srgb, var(--accent) 30%, var(--surface) 70%);
border-color: color-mix(in srgb, var(--accent) 45%, var(--border) 55%);
color: var(--surface);
}
& + label {
display: inline-flex;
align-items: center;
justify-content: center;
padding: 0 0.75rem;
height: 2.25rem;
border: 1px solid var(--border);
border-radius: var(--r-m);
background: var(--surface);
transition:
background-color 0.15s ease,
color 0.15s ease,
border-color 0.15s ease;
}
}
}
}
.actions {
display: flex;
justify-content: center;
align-items: center;
gap: 8px;
}
.renderer,
.codegen {
button {
position: relative;
border-color: #36485e;
color: rgba(54, 72, 94, 0.8);
font-weight: 500;
&:focus-visible {
box-shadow: 0 0 1px #36485e;
}
&:active {
background: rgba(54, 72, 94, 0.2);
}
}
}
.renderer {
position: fixed;
top: 10px;
left: 10px;
font-size: 16px;
button {
width: 64px;
&.active {
z-index: 1;
background-color: #36485e;
color: #fff;
}
&:first-child {
border-top-right-radius: 0;
border-bottom-right-radius: 0;
}
&:last-child {
left: -1px;
border-top-left-radius: 0;
border-bottom-left-radius: 0;
}
}
}
.codegen {
display: flex;
position: fixed;
top: 10px;
left: 146px;
button {
display: flex;
align-items: center;
padding: 0 4px;
gap: 4px;
}
}
@media (max-width: 480px) {
.codegen {
display: none;
body .codegen {
display: none !important;
}
}
.site-footer {
margin: 4rem 0 1.25rem;
text-align: center;
color: var(--muted);
}
.site-footer a {
color: inherit;
text-decoration: none;
}
.site-footer .footer-links {
font-size: 0.85em;
display: inline-flex;
gap: 0.5rem;
align-items: center;
}
</style>