feat(docs): enhance interactive demos and improve documentation
- Add new interactive components for frontend routing, browser rendering pipeline, and database transactions - Improve existing demos with better visuals, explanations, and examples - Update documentation structure and content for better clarity - Add new utility scripts and update package.json with new commands - Fix formatting and alignment in documentation tables
This commit is contained in:
+175
-397
@@ -1,126 +1,74 @@
|
||||
<template>
|
||||
<div class="route-matching-demo">
|
||||
<div class="demo-header">
|
||||
<h4>路由匹配机制</h4>
|
||||
<p class="demo-desc">输入URL路径,查看路由是如何匹配和解析参数的</p>
|
||||
<span class="icon">🎯</span>
|
||||
<span class="title">路由匹配</span>
|
||||
<span class="subtitle">URL如何找到对应组件</span>
|
||||
</div>
|
||||
|
||||
<div class="demo-container">
|
||||
<div class="intro-text">
|
||||
想象你在<span class="highlight">查字典</span>:输入一个词,字典会帮你找到对应的解释。路由匹配也是这样,浏览器根据URL路径,在路由配置中找到最匹配的那一项,然后渲染对应组件。
|
||||
</div>
|
||||
|
||||
<div class="demo-content">
|
||||
<div class="input-section">
|
||||
<h5>📍 测试路径</h5>
|
||||
<div class="input-group">
|
||||
<label>测试路径</label>
|
||||
<div class="path-input-wrapper">
|
||||
<span class="path-prefix">/</span>
|
||||
<input
|
||||
v-model="testPath"
|
||||
type="text"
|
||||
placeholder="user/123/posts"
|
||||
class="path-input"
|
||||
@keyup.enter="testMatch"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<button class="test-btn" @click="testMatch">
|
||||
<span class="btn-icon">▶</span>
|
||||
测试匹配
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="routes-section">
|
||||
<div class="section-header">
|
||||
<h5>已定义的路由</h5>
|
||||
<span class="route-count">{{ routes.length }} 条</span>
|
||||
</div>
|
||||
|
||||
<div class="routes-list">
|
||||
<div
|
||||
v-for="route in routes"
|
||||
:key="route.path"
|
||||
:class="['route-item', { matched: matchedRoute?.path === route.path }]"
|
||||
<span class="input-prefix">/</span>
|
||||
<input
|
||||
v-model="testPath"
|
||||
type="text"
|
||||
placeholder="user/123"
|
||||
class="path-input"
|
||||
@input="testMatch"
|
||||
>
|
||||
<div class="route-path">
|
||||
<span class="route-pattern">{{ route.path }}</span>
|
||||
<span v-if="route.hasParams" class="param-badge">含参数</span>
|
||||
</div>
|
||||
<div class="hint-text">试试:user/123 或 products/electronics/456</div>
|
||||
</div>
|
||||
|
||||
<div class="result-section">
|
||||
<h5>🎯 匹配结果</h5>
|
||||
<div v-if="matchResult && matchResult.matched" class="match-success">
|
||||
<div class="success-icon">✅</div>
|
||||
<div class="result-details">
|
||||
<div class="result-row">
|
||||
<span class="label">匹配路由:</span>
|
||||
<code class="value">{{ matchResult.route.path }}</code>
|
||||
</div>
|
||||
<div v-if="Object.keys(matchResult.params).length" class="params-box">
|
||||
<span class="label">提取参数:</span>
|
||||
<div class="params-list">
|
||||
<span v-for="(value, key) in matchResult.params" :key="key" class="param-tag">
|
||||
{{ key }} = {{ value }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="route-name">{{ route.name }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="match-fail">
|
||||
<div class="fail-icon">❌</div>
|
||||
<div>未找到匹配的路由</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="matchResult" class="result-section">
|
||||
<div class="result-header">
|
||||
<h5>匹配结果</h5>
|
||||
<span :class="['match-status', matchResult.matched ? 'success' : 'fail']">
|
||||
{{ matchResult.matched ? '匹配成功' : '无匹配路由' }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div v-if="matchResult.matched" class="match-details">
|
||||
<div class="detail-item">
|
||||
<span class="detail-label">匹配路由</span>
|
||||
<span class="detail-value code">{{ matchResult.route.path }}</span>
|
||||
<div class="routes-list">
|
||||
<h5>📋 已定义的路由</h5>
|
||||
<div class="routes-grid">
|
||||
<div
|
||||
v-for="route in routes"
|
||||
:key="route.path"
|
||||
:class="['route-item', { matched: matchedRoute?.path === route.path }]"
|
||||
>
|
||||
<code class="route-path">{{ route.path }}</code>
|
||||
<span class="route-name">{{ route.name }}</span>
|
||||
</div>
|
||||
<div class="detail-item">
|
||||
<span class="detail-label">路由名称</span>
|
||||
<span class="detail-value">{{ matchResult.route.name }}</span>
|
||||
</div>
|
||||
|
||||
<div v-if="Object.keys(matchResult.params).length > 0" class="params-section">
|
||||
<div class="detail-label">路径参数</div>
|
||||
<div class="params-list">
|
||||
<div
|
||||
v-for="(value, key) in matchResult.params"
|
||||
:key="key"
|
||||
class="param-item"
|
||||
>
|
||||
<span class="param-key">{{ key }}</span>
|
||||
<span class="param-arrow">→</span>
|
||||
<span class="param-value">{{ value }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-else class="no-match">
|
||||
<div class="no-match-icon">❓</div>
|
||||
<p>路径 "{{ testPath }}" 未匹配到任何路由</p>
|
||||
<ul class="suggestions">
|
||||
<li>检查路径拼写是否正确</li>
|
||||
<li>确认路径是否以斜杠开头</li>
|
||||
<li>查看是否缺少必要的参数</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tips-section">
|
||||
<h5>路由匹配规则速查</h5>
|
||||
<div class="tips-grid">
|
||||
<div class="tip-item">
|
||||
<code>/user</code>
|
||||
<span>精确匹配 /user</span>
|
||||
</div>
|
||||
<div class="tip-item">
|
||||
<code>/user/:id</code>
|
||||
<span>匹配 /user/123,id=123</span>
|
||||
</div>
|
||||
<div class="tip-item">
|
||||
<code>/user/:id?</code>
|
||||
<span>id可选,匹配 /user 或 /user/123</span>
|
||||
</div>
|
||||
<div class="tip-item">
|
||||
<code>/user/:id+</code>
|
||||
<span>匹配一个或多个,如 /user/1/2</span>
|
||||
</div>
|
||||
<div class="tip-item">
|
||||
<code>/user/:id*</code>
|
||||
<span>匹配零个或多个</span>
|
||||
</div>
|
||||
<div class="tip-item">
|
||||
<code>/user(.*)*</code>
|
||||
<span>通配符,匹配任意路径</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="info-box">
|
||||
<span class="icon">💡</span>
|
||||
<strong>匹配规则:</strong>路由按定义顺序匹配,先定义的优先。动态参数(:id)可以匹配任意值,但精确匹配优先级更高。
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -128,7 +76,7 @@
|
||||
<script setup>
|
||||
import { ref, computed } from 'vue'
|
||||
|
||||
const testPath = ref('user/123/posts')
|
||||
const testPath = ref('user/123')
|
||||
const matchResult = ref(null)
|
||||
const matchedRoute = ref(null)
|
||||
|
||||
@@ -137,14 +85,11 @@ const routes = [
|
||||
{ path: '/user', name: '用户列表', hasParams: false },
|
||||
{ path: '/user/:id', name: '用户详情', hasParams: true },
|
||||
{ path: '/user/:id/posts', name: '用户文章', hasParams: true },
|
||||
{ path: '/products', name: '产品列表', hasParams: false },
|
||||
{ path: '/products/:category/:id', name: '产品详情', hasParams: true },
|
||||
{ path: '/search', name: '搜索结果', hasParams: false },
|
||||
{ path: '/:path(.*)*', name: '404页面', hasParams: true }
|
||||
]
|
||||
|
||||
const parsePath = (path) => {
|
||||
// 移除开头的斜杠
|
||||
const cleanPath = path.replace(/^\//, '')
|
||||
return cleanPath.split('/').filter(Boolean)
|
||||
}
|
||||
@@ -152,20 +97,17 @@ const parsePath = (path) => {
|
||||
const matchPath = (routePath, testPath) => {
|
||||
const routeParts = parsePath(routePath)
|
||||
const testParts = parsePath(testPath)
|
||||
|
||||
const params = {}
|
||||
|
||||
for (let i = 0; i < routeParts.length; i++) {
|
||||
const routePart = routeParts[i]
|
||||
const testPart = testParts[i]
|
||||
|
||||
// 通配符匹配
|
||||
if (routePart === '(.*)*' || routePart === ':path(.*)*') {
|
||||
params['pathMatch'] = testParts.slice(i).join('/')
|
||||
return { matched: true, params }
|
||||
}
|
||||
|
||||
// 动态参数匹配
|
||||
if (routePart.startsWith(':')) {
|
||||
const paramName = routePart.replace(/^:/, '').replace(/\?$/, '')
|
||||
const isOptional = routePart.endsWith('?')
|
||||
@@ -180,13 +122,11 @@ const matchPath = (routePath, testPath) => {
|
||||
}
|
||||
}
|
||||
|
||||
// 精确匹配
|
||||
if (routePart !== testPart) {
|
||||
return { matched: false, params: {} }
|
||||
}
|
||||
}
|
||||
|
||||
// 检查是否有剩余的测试路径部分(除非是通配符路由)
|
||||
if (testParts.length > routeParts.length) {
|
||||
const lastRoutePart = routeParts[routeParts.length - 1]
|
||||
if (!lastRoutePart || (!lastRoutePart.includes('*') && !lastRoutePart.endsWith('+'))) {
|
||||
@@ -227,380 +167,218 @@ const testMatch = () => {
|
||||
}
|
||||
}
|
||||
|
||||
// 自动测试初始路径
|
||||
testMatch()
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.route-matching-demo {
|
||||
padding: 20px;
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 8px;
|
||||
background: var(--vp-c-bg-soft);
|
||||
border-radius: 12px;
|
||||
margin: 20px 0;
|
||||
padding: 1rem;
|
||||
margin: 1rem 0;
|
||||
max-height: 600px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.demo-header {
|
||||
text-align: center;
|
||||
margin-bottom: 24px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
|
||||
.demo-header h4 {
|
||||
margin: 0 0 8px 0;
|
||||
color: var(--vp-c-text-1);
|
||||
}
|
||||
.demo-header .icon { font-size: 1.25rem; }
|
||||
.demo-header .title { font-weight: bold; font-size: 1rem; }
|
||||
.demo-header .subtitle { color: var(--vp-c-text-2); font-size: 0.85rem; margin-left: 0.5rem; }
|
||||
|
||||
.demo-desc {
|
||||
margin: 0;
|
||||
.intro-text {
|
||||
font-size: 0.9rem;
|
||||
color: var(--vp-c-text-2);
|
||||
font-size: 14px;
|
||||
line-height: 1.6;
|
||||
margin-bottom: 1rem;
|
||||
padding: 0.75rem;
|
||||
background: var(--vp-c-bg);
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.demo-container {
|
||||
.intro-text .highlight {
|
||||
color: var(--vp-c-brand-1);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.demo-content {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 20px;
|
||||
margin-bottom: 20px;
|
||||
gap: 1rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.input-section {
|
||||
.input-section, .result-section {
|
||||
background: var(--vp-c-bg);
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
padding: 0.75rem;
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
}
|
||||
|
||||
.input-group {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.input-group label {
|
||||
display: block;
|
||||
margin-bottom: 8px;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
h5 {
|
||||
margin: 0 0 0.5rem 0;
|
||||
font-size: 0.85rem;
|
||||
color: var(--vp-c-text-2);
|
||||
}
|
||||
|
||||
.path-input-wrapper {
|
||||
.input-group {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background: var(--vp-c-bg-soft);
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 6px;
|
||||
overflow: hidden;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.path-prefix {
|
||||
padding: 10px 8px 10px 12px;
|
||||
.input-prefix {
|
||||
padding: 0.5rem;
|
||||
color: var(--vp-c-text-3);
|
||||
font-family: monospace;
|
||||
font-size: 14px;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
.path-input {
|
||||
flex: 1;
|
||||
border: none;
|
||||
background: transparent;
|
||||
padding: 10px 12px 10px 0;
|
||||
font-size: 14px;
|
||||
padding: 0.5rem;
|
||||
font-size: 0.85rem;
|
||||
color: var(--vp-c-text-1);
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.test-btn {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
padding: 10px 16px;
|
||||
background: var(--vp-c-brand);
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
transition: background 0.2s;
|
||||
}
|
||||
|
||||
.test-btn:hover {
|
||||
background: var(--vp-c-brand-dark);
|
||||
}
|
||||
|
||||
.btn-icon {
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
.routes-section {
|
||||
background: var(--vp-c-bg);
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
}
|
||||
|
||||
.section-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.section-header h5 {
|
||||
margin: 0;
|
||||
font-size: 14px;
|
||||
color: var(--vp-c-text-1);
|
||||
}
|
||||
|
||||
.route-count {
|
||||
font-size: 12px;
|
||||
color: var(--vp-c-text-3);
|
||||
background: var(--vp-c-bg-soft);
|
||||
padding: 2px 8px;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.routes-list {
|
||||
max-height: 280px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.route-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 10px 12px;
|
||||
border-radius: 6px;
|
||||
margin-bottom: 4px;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.route-item:hover {
|
||||
background: var(--vp-c-bg-soft);
|
||||
}
|
||||
|
||||
.route-item.matched {
|
||||
background: rgba(66, 184, 131, 0.1);
|
||||
border: 1px solid rgba(66, 184, 131, 0.3);
|
||||
}
|
||||
|
||||
.route-path {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.route-pattern {
|
||||
font-family: 'Monaco', 'Menlo', monospace;
|
||||
font-size: 13px;
|
||||
color: var(--vp-c-text-1);
|
||||
}
|
||||
|
||||
.param-badge {
|
||||
font-size: 10px;
|
||||
color: var(--vp-c-brand);
|
||||
background: var(--vp-c-brand-soft);
|
||||
padding: 2px 6px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.route-name {
|
||||
font-size: 12px;
|
||||
.hint-text {
|
||||
font-size: 0.7rem;
|
||||
color: var(--vp-c-text-3);
|
||||
}
|
||||
|
||||
.result-section {
|
||||
background: var(--vp-c-bg);
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.result-header {
|
||||
.match-success {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 16px;
|
||||
padding-bottom: 12px;
|
||||
border-bottom: 1px solid var(--vp-c-divider);
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
.result-header h5 {
|
||||
margin: 0;
|
||||
font-size: 14px;
|
||||
color: var(--vp-c-text-1);
|
||||
}
|
||||
|
||||
.match-status {
|
||||
padding: 4px 12px;
|
||||
border-radius: 12px;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.match-status.success {
|
||||
background: rgba(39, 201, 63, 0.15);
|
||||
color: #27c93f;
|
||||
}
|
||||
|
||||
.match-status.fail {
|
||||
background: rgba(255, 95, 86, 0.15);
|
||||
color: #ff5f56;
|
||||
}
|
||||
|
||||
.match-details {
|
||||
display: grid;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.detail-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.detail-label {
|
||||
width: 80px;
|
||||
font-size: 13px;
|
||||
color: var(--vp-c-text-3);
|
||||
.success-icon {
|
||||
font-size: 1.5rem;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.detail-value {
|
||||
font-size: 14px;
|
||||
color: var(--vp-c-text-1);
|
||||
.result-details {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.detail-value.code {
|
||||
font-family: 'Monaco', 'Menlo', monospace;
|
||||
.result-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.label {
|
||||
font-size: 0.75rem;
|
||||
color: var(--vp-c-text-3);
|
||||
min-width: 60px;
|
||||
}
|
||||
|
||||
.value {
|
||||
font-size: 0.8rem;
|
||||
color: var(--vp-c-text-1);
|
||||
font-family: monospace;
|
||||
background: var(--vp-c-bg-soft);
|
||||
padding: 4px 8px;
|
||||
padding: 0.25rem 0.5rem;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.params-section {
|
||||
margin-top: 8px;
|
||||
padding-top: 12px;
|
||||
.params-box {
|
||||
padding-top: 0.5rem;
|
||||
border-top: 1px solid var(--vp-c-divider);
|
||||
}
|
||||
|
||||
.params-list {
|
||||
margin-top: 12px;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
gap: 0.5rem;
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
|
||||
.param-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
background: var(--vp-c-bg-soft);
|
||||
padding: 6px 12px;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.param-key {
|
||||
font-family: 'Monaco', 'Menlo', monospace;
|
||||
font-size: 13px;
|
||||
.param-tag {
|
||||
background: var(--vp-c-brand-soft);
|
||||
padding: 0.25rem 0.5rem;
|
||||
border-radius: 4px;
|
||||
font-size: 0.75rem;
|
||||
font-family: monospace;
|
||||
color: var(--vp-c-brand);
|
||||
}
|
||||
|
||||
.param-arrow {
|
||||
font-size: 12px;
|
||||
color: var(--vp-c-text-3);
|
||||
}
|
||||
|
||||
.param-value {
|
||||
font-size: 13px;
|
||||
color: var(--vp-c-text-1);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.no-match {
|
||||
.match-fail {
|
||||
text-align: center;
|
||||
padding: 32px;
|
||||
}
|
||||
|
||||
.no-match-icon {
|
||||
font-size: 48px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.no-match p {
|
||||
color: var(--vp-c-text-2);
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.suggestions {
|
||||
text-align: left;
|
||||
display: inline-block;
|
||||
padding: 1rem;
|
||||
color: var(--vp-c-text-3);
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.suggestions li {
|
||||
margin: 4px 0;
|
||||
.fail-icon {
|
||||
font-size: 1.5rem;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.tips-section {
|
||||
margin-top: 20px;
|
||||
padding: 20px;
|
||||
.routes-list {
|
||||
background: var(--vp-c-bg);
|
||||
border-radius: 8px;
|
||||
padding: 0.75rem;
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.tips-section h5 {
|
||||
margin: 0 0 16px 0;
|
||||
font-size: 14px;
|
||||
.routes-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.route-item {
|
||||
background: var(--vp-c-bg-soft);
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 6px;
|
||||
padding: 0.5rem 0.75rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.25rem;
|
||||
}
|
||||
|
||||
.route-item.matched {
|
||||
border-color: var(--vp-c-brand);
|
||||
background: rgba(66, 184, 131, 0.1);
|
||||
}
|
||||
|
||||
.route-path {
|
||||
font-size: 0.75rem;
|
||||
font-family: monospace;
|
||||
color: var(--vp-c-text-1);
|
||||
}
|
||||
|
||||
.tips-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.tip-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
padding: 12px;
|
||||
background: var(--vp-c-bg-soft);
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.tip-item code {
|
||||
font-family: 'Monaco', 'Menlo', monospace;
|
||||
font-size: 13px;
|
||||
color: var(--vp-c-brand);
|
||||
background: transparent;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.tip-item span:last-child {
|
||||
font-size: 12px;
|
||||
.route-name {
|
||||
font-size: 0.7rem;
|
||||
color: var(--vp-c-text-3);
|
||||
}
|
||||
|
||||
.info-box {
|
||||
background: var(--vp-c-bg-alt);
|
||||
padding: 0.75rem;
|
||||
border-radius: 6px;
|
||||
font-size: 0.85rem;
|
||||
color: var(--vp-c-text-2);
|
||||
}
|
||||
|
||||
.info-box .icon { margin-right: 0.25rem; }
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.demo-container {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.detail-item {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.detail-label {
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.tips-grid {
|
||||
.demo-content {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user