Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
]
},
"dependencies": {
"minisearch": "^7.2.0"
"minisearch": "^7.2.0",
"mermaid": "^10.9.6"
}
}
838 changes: 838 additions & 0 deletions pnpm-lock.yaml

Large diffs are not rendered by default.

8 changes: 7 additions & 1 deletion scripts/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,11 @@ function hashDir(dir: string): string {
return h.digest('hex').substring(0, 16)
}

// 主题/配置共享哈希:theme/ 与 config/ 任一文件变动 → 所有 per-volume 缓存失效。
// per-volume cacheKey 默认只哈希 docs 目录;改主题/CSS/head/nav/sidebar 后若不并入此哈希,
// 缓存不失效、会输出旧皮(曾踩坑:改 custom.css 后 8/10 卷仍挂旧 CSS,须 --force 才全量重建)。
const SHARED_HASH = hashDir(join(MAIN_VP, 'theme')) + hashDir(join(MAIN_VP, 'config'))

// ── Manifest ────────────────────────────────────────────────

interface ManifestEntry { hash: string; timestamp: string }
Expand Down Expand Up @@ -184,7 +189,8 @@ interface BuildTask {

function prepareTask(vol: Volume, manifest: Manifest): BuildTask {
const volDocDir = join(DOCUMENTS, vol.srcDir)
const cacheKey = existsSync(volDocDir) ? hashDir(volDocDir) : ''
const docsHash = existsSync(volDocDir) ? hashDir(volDocDir) : ''
const cacheKey = docsHash + '|' + SHARED_HASH
const prev = manifest[vol.name]
const cached = !FORCE_REBUILD && prev && prev.hash === cacheKey && existsSync(join(CACHE_DIR, 'output', vol.name))
return { id: vol.name, vol, cacheKey, cached }
Expand Down
7 changes: 7 additions & 0 deletions site/.vitepress/config/shared.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,13 @@ export const sharedBase = {
{},
`(function(){try{var s=localStorage.getItem('awesomeqt-font-size')||'normal';if(s!=='xxsmall'&&s!=='small'&&s!=='normal'&&s!=='large'&&s!=='xxlarge'){s='normal';}document.documentElement.dataset.fontSize=s;}catch(e){}})()`,
],
// 可拖拽侧栏首屏防闪:hydration 前从 localStorage 还原左右栏宽度 CSS 变量,默认 272/256。
// 与 theme/components/ResizableSidebar.vue 的 CONF(key=vp-sidebar-width/vp-aside-width)一致。
[
'script',
{},
`(function(){try{var w=parseInt(localStorage.getItem('vp-sidebar-width'));if(!w||w<200||w>480){w=272;}document.documentElement.style.setProperty('--vp-sidebar-width',w+'px');var a=parseInt(localStorage.getItem('vp-aside-width'));if(!a||a<180||a>360){a=256;}document.documentElement.style.setProperty('--vp-aside-width',a+'px');}catch(e){}})()`,
],
],
}

Expand Down
162 changes: 162 additions & 0 deletions site/.vitepress/theme/components/ChapterHero.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
<script setup>
// 篇首横幅:左侧篇号标记(默认 PART,可经 label 覆盖)+ 标题 + 副标题 + 计数。
// motif 选 theory(同心圆)/ specimen(带斜线的矩形)/ practice(分支树)三种抽象图形。
defineProps({
act: { type: String, required: true },
title: { type: String, required: true },
subtitle: { type: String, default: '' },
motif: { type: String, default: 'theory' },
count: { type: String, default: '' },
label: { type: String, default: 'PART' },
})
</script>

<template>
<div class="chapter-hero" :class="`motif-${motif}`">
<div class="chapter-hero__inner">
<div class="chapter-hero__mark">
<span class="chapter-hero__act-label">{{ label }}</span>
<span class="chapter-hero__act-num">{{ act }}</span>
<svg v-if="motif === 'theory'" class="chapter-hero__motif" viewBox="0 0 40 40" aria-hidden="true">
<circle cx="20" cy="20" r="14" fill="none" stroke="var(--vp-c-brand-1)" stroke-width="1.2" opacity="0.45" />
<circle cx="20" cy="20" r="8" fill="none" stroke="var(--vp-c-brand-1)" stroke-width="1.2" opacity="0.7" />
<circle cx="20" cy="20" r="2" fill="var(--vp-c-brand-1)" />
<g fill="var(--vp-c-text-3)" opacity="0.55">
<circle cx="6" cy="6" r="1" /><circle cx="34" cy="6" r="1" />
<circle cx="6" cy="34" r="1" /><circle cx="34" cy="34" r="1" />
</g>
</svg>
<svg v-else-if="motif === 'specimen'" class="chapter-hero__motif" viewBox="0 0 40 40" aria-hidden="true">
<rect x="6" y="8" width="22" height="24" rx="2" fill="none" stroke="var(--vp-c-brand-1)" stroke-width="1.2" />
<line x1="4" y1="36" x2="36" y2="4" stroke="var(--vp-c-brand-1)" stroke-width="1.6" stroke-linecap="round" />
<circle cx="34" cy="6" r="2.2" fill="var(--vp-c-brand-1)" />
</svg>
<svg v-else class="chapter-hero__motif" viewBox="0 0 40 40" aria-hidden="true">
<path d="M20 6 L20 20 M20 20 L10 30 M20 20 L30 30 M10 30 L6 36 M10 30 L14 36 M30 30 L26 36 M30 30 L34 36"
fill="none" stroke="var(--vp-c-brand-1)" stroke-width="1.3" stroke-linecap="round" />
<circle cx="20" cy="6" r="2.5" fill="var(--vp-c-brand-1)" />
</svg>
</div>
<div class="chapter-hero__body">
<h2 class="chapter-hero__title">{{ title }}</h2>
<p v-if="subtitle" class="chapter-hero__subtitle">{{ subtitle }}</p>
<p v-if="count" class="chapter-hero__count">{{ count }}</p>
</div>
</div>
<div class="chapter-hero__rule">
<span class="chapter-hero__tick" />
</div>
</div>
</template>

<style scoped>
.chapter-hero {
margin: 1rem 0 2.5rem;
padding: 2rem 1.5rem 1.5rem;
background: var(--vp-c-bg-soft);
border-radius: 12px;
border-left: 4px solid var(--vp-c-brand-1);
position: relative;
}

.chapter-hero__inner {
display: flex;
align-items: center;
gap: 2rem;
flex-wrap: wrap;
}

.chapter-hero__mark {
display: flex;
flex-direction: column;
align-items: center;
gap: 0.25rem;
flex-shrink: 0;
min-width: 80px;
}

.chapter-hero__act-label {
font-size: 0.7rem;
font-weight: 700;
letter-spacing: 0.2em;
color: var(--vp-c-text-3);
}

.chapter-hero__act-num {
font-size: 2.6rem;
font-weight: 800;
line-height: 1;
color: var(--vp-c-brand-1);
}

.chapter-hero__motif {
width: 40px;
height: 40px;
margin-top: 0.5rem;
}

.chapter-hero__body {
flex: 1;
min-width: 200px;
}

.chapter-hero__title {
font-size: 1.8rem;
font-weight: 700;
margin: 0 0 0.4rem;
color: var(--vp-c-text-1);
line-height: 1.25;
border: none;
padding: 0;
}

.chapter-hero__subtitle {
font-size: 1rem;
color: var(--vp-c-text-2);
margin: 0 0 0.5rem;
line-height: 1.6;
}

.chapter-hero__count {
font-size: 0.85rem;
font-weight: 600;
color: var(--vp-c-brand-1);
margin: 0;
letter-spacing: 0.04em;
}

.chapter-hero__rule {
margin-top: 1.5rem;
height: 1px;
background: var(--vp-c-divider);
position: relative;
}

.chapter-hero__tick {
position: absolute;
left: 0;
top: -3px;
width: 7px;
height: 7px;
background: var(--vp-c-brand-1);
transform: rotate(45deg);
}

@media (max-width: 639px) {
.chapter-hero {
padding: 1.5rem 1rem 1rem;
}
.chapter-hero__inner {
gap: 1rem;
}
.chapter-hero__mark {
min-width: 60px;
}
.chapter-hero__act-num {
font-size: 2rem;
}
.chapter-hero__title {
font-size: 1.4rem;
}
}
</style>
109 changes: 109 additions & 0 deletions site/.vitepress/theme/components/ModuleCard.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
<script setup>
import { computed } from 'vue'
import { withBase } from 'vitepress'

// 模块卡:左侧装饰条 + 罗马序号 + 名称(可带英文术语)+ 一句话说明。
// 装饰条悬浮加宽,整卡可作链接。供教程/实例库等聚合页直接 <ModuleCard .../> 使用。
const props = defineProps({
number: { type: Number, required: true },
name: { type: String, required: true },
en: { type: String, default: '' },
oneLine: { type: String, required: true },
link: { type: String, default: '' },
})

const ROMAN = ['', 'I', 'II', 'III', 'IV', 'V', 'VI', 'VII', 'VIII', 'IX', 'X', 'XI', 'XII']
const roman = computed(() => ROMAN[props.number] || String(props.number))
</script>

<template>
<component
:is="link ? 'a' : 'div'"
:href="link ? withBase(link) : undefined"
class="module-card"
>
<span class="module-card__rail" />
<div class="module-card__main">
<span class="module-card__num">{{ roman }}</span>
<h3 class="module-card__name">
{{ name }}
<span v-if="en" class="module-card__en">{{ en }}</span>
</h3>
<p class="module-card__line">{{ oneLine }}</p>
</div>
</component>
</template>

<style scoped>
.module-card {
position: relative;
display: flex;
gap: 0.75rem;
padding: 1rem 1.1rem 1rem 0.85rem;
background: var(--vp-c-bg);
border: 1px solid var(--vp-c-divider);
border-radius: 10px;
text-decoration: none;
color: inherit;
transition: border-color 0.25s ease, transform 0.25s ease, box-shadow 0.25s ease;
}

a.module-card:hover {
border-color: var(--vp-c-brand-1);
transform: translateY(-3px);
box-shadow: 0 8px 20px rgba(0, 0, 0, 0.06);
}

.module-card__rail {
width: 3px;
flex-shrink: 0;
border-radius: 2px;
background: var(--vp-c-brand-1);
opacity: 0.5;
transition: opacity 0.25s ease, width 0.25s ease;
}

a.module-card:hover .module-card__rail {
opacity: 1;
width: 5px;
}

.module-card__main {
flex: 1;
min-width: 0;
}

.module-card__num {
display: block;
font-size: 0.78rem;
font-weight: 700;
letter-spacing: 0.12em;
color: var(--vp-c-brand-1);
margin-bottom: 0.25rem;
}

.module-card__name {
font-size: 1.02rem;
font-weight: 600;
margin: 0 0 0.3rem;
color: var(--vp-c-text-1);
line-height: 1.35;
border: none;
padding: 0;
}

.module-card__en {
font-size: 0.75rem;
font-style: italic;
font-weight: 400;
color: var(--vp-c-text-3);
margin-left: 0.4rem;
}

.module-card__line {
font-size: 0.85rem;
color: var(--vp-c-text-2);
margin: 0;
line-height: 1.55;
}
</style>
Loading
Loading