Improve mobile reading layout and typography
Make classic text chapters easier to read on phones with responsive styles, collapsed sidebar groups, and less intrusive install prompts on doc pages. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+14
-1
@@ -41,9 +41,22 @@ export default defineConfig({
|
|||||||
['meta', { name: 'mobile-web-app-capable', content: 'yes' }],
|
['meta', { name: 'mobile-web-app-capable', content: 'yes' }],
|
||||||
['meta', { name: 'apple-mobile-web-app-capable', content: 'yes' }],
|
['meta', { name: 'apple-mobile-web-app-capable', content: 'yes' }],
|
||||||
['meta', { name: 'apple-mobile-web-app-status-bar-style', content: 'black-translucent' }],
|
['meta', { name: 'apple-mobile-web-app-status-bar-style', content: 'black-translucent' }],
|
||||||
|
[
|
||||||
|
'meta',
|
||||||
|
{
|
||||||
|
name: 'viewport',
|
||||||
|
content: 'width=device-width, initial-scale=1.0, viewport-fit=cover',
|
||||||
|
},
|
||||||
|
],
|
||||||
],
|
],
|
||||||
themeConfig: {
|
themeConfig: {
|
||||||
logo: { src: '/favicon.ico', width: 24, height: 24 },
|
logo: { src: '/favicon.ico', width: 24, height: 24 },
|
||||||
|
sidebarMenuLabel: '目录',
|
||||||
|
returnToTopLabel: '回到顶部',
|
||||||
|
docFooter: {
|
||||||
|
prev: '上一篇',
|
||||||
|
next: '下一篇',
|
||||||
|
},
|
||||||
nav: [
|
nav: [
|
||||||
{ text: '首页', link: '/' },
|
{ text: '首页', link: '/' },
|
||||||
{ text: '五行', link: '/金、木、水、火、土 - 五行/' },
|
{ text: '五行', link: '/金、木、水、火、土 - 五行/' },
|
||||||
@@ -52,7 +65,7 @@ export default defineConfig({
|
|||||||
{ text: '中医', link: '/中医宝典/' },
|
{ text: '中医', link: '/中医宝典/' },
|
||||||
],
|
],
|
||||||
sidebar: generateSidebar(),
|
sidebar: generateSidebar(),
|
||||||
outline: { level: [2, 4] },
|
outline: { level: [2, 3], label: '本页目录' },
|
||||||
search: {
|
search: {
|
||||||
provider: 'local',
|
provider: 'local',
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ export function generateSidebar(): Record<string, SidebarItem[]> {
|
|||||||
if (rootFiles.length > 0) {
|
if (rootFiles.length > 0) {
|
||||||
groups.push({
|
groups.push({
|
||||||
text: '典籍',
|
text: '典籍',
|
||||||
collapsed: false,
|
collapsed: true,
|
||||||
items: rootFiles.map((file) => ({
|
items: rootFiles.map((file) => ({
|
||||||
text: titleFromFilename(file.name),
|
text: titleFromFilename(file.name),
|
||||||
link: toLink(file.name),
|
link: toLink(file.name),
|
||||||
@@ -103,7 +103,7 @@ export function generateSidebar(): Record<string, SidebarItem[]> {
|
|||||||
if (items.length === 0) continue
|
if (items.length === 0) continue
|
||||||
groups.push({
|
groups.push({
|
||||||
text: dir.name,
|
text: dir.name,
|
||||||
collapsed: false,
|
collapsed: true,
|
||||||
items,
|
items,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, onMounted, ref } from 'vue'
|
import { computed, onMounted, ref } from 'vue'
|
||||||
|
import { useRoute } from 'vitepress'
|
||||||
|
|
||||||
|
const route = useRoute()
|
||||||
const showAndroidInstall = ref(false)
|
const showAndroidInstall = ref(false)
|
||||||
const showIOSHint = ref(false)
|
const showIOSHint = ref(false)
|
||||||
const dismissed = ref(false)
|
const dismissed = ref(false)
|
||||||
|
const isMobile = ref(false)
|
||||||
let deferredPrompt: BeforeInstallPromptEvent | null = null
|
let deferredPrompt: BeforeInstallPromptEvent | null = null
|
||||||
|
|
||||||
interface BeforeInstallPromptEvent extends Event {
|
interface BeforeInstallPromptEvent extends Event {
|
||||||
@@ -11,11 +14,22 @@ interface BeforeInstallPromptEvent extends Event {
|
|||||||
userChoice: Promise<{ outcome: 'accepted' | 'dismissed' }>
|
userChoice: Promise<{ outcome: 'accepted' | 'dismissed' }>
|
||||||
}
|
}
|
||||||
|
|
||||||
const visible = computed(
|
const isDocPage = computed(() => route.path !== '/')
|
||||||
() => !dismissed.value && (showAndroidInstall.value || showIOSHint.value),
|
|
||||||
)
|
const visible = computed(() => {
|
||||||
|
if (dismissed.value) return false
|
||||||
|
if (isMobile.value && isDocPage.value) return false
|
||||||
|
return showAndroidInstall.value || showIOSHint.value
|
||||||
|
})
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
|
isMobile.value = window.matchMedia('(max-width: 768px)').matches
|
||||||
|
|
||||||
|
if (sessionStorage.getItem('install-app-dismissed') === '1') {
|
||||||
|
dismissed.value = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
const isStandalone =
|
const isStandalone =
|
||||||
window.matchMedia('(display-mode: standalone)').matches ||
|
window.matchMedia('(display-mode: standalone)').matches ||
|
||||||
(window.navigator as Navigator & { standalone?: boolean }).standalone
|
(window.navigator as Navigator & { standalone?: boolean }).standalone
|
||||||
@@ -45,6 +59,7 @@ async function installApp() {
|
|||||||
|
|
||||||
function dismiss() {
|
function dismiss() {
|
||||||
dismissed.value = true
|
dismissed.value = true
|
||||||
|
sessionStorage.setItem('install-app-dismissed', '1')
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -55,7 +70,7 @@ function dismiss() {
|
|||||||
可将「道德经」安装到桌面,像 App 一样使用。
|
可将「道德经」安装到桌面,像 App 一样使用。
|
||||||
</p>
|
</p>
|
||||||
<p v-else class="install-app__text">
|
<p v-else class="install-app__text">
|
||||||
iPhone:点击 Safari 底部分享按钮,选择「添加到主屏幕」。
|
iPhone:Safari 底部分享 →「添加到主屏幕」,阅读更方便。
|
||||||
</p>
|
</p>
|
||||||
<div class="install-app__actions">
|
<div class="install-app__actions">
|
||||||
<button
|
<button
|
||||||
@@ -76,8 +91,8 @@ function dismiss() {
|
|||||||
.install-app {
|
.install-app {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
right: 16px;
|
right: 16px;
|
||||||
bottom: 16px;
|
bottom: calc(16px + env(safe-area-inset-bottom, 0));
|
||||||
z-index: 100;
|
z-index: 50;
|
||||||
max-width: min(92vw, 360px);
|
max-width: min(92vw, 360px);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -104,8 +119,8 @@ function dismiss() {
|
|||||||
.install-app__primary,
|
.install-app__primary,
|
||||||
.install-app__ghost {
|
.install-app__ghost {
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
padding: 8px 12px;
|
padding: 10px 14px;
|
||||||
font-size: 13px;
|
font-size: 14px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -120,4 +135,12 @@ function dismiss() {
|
|||||||
background: transparent;
|
background: transparent;
|
||||||
color: var(--vp-c-text-2);
|
color: var(--vp-c-text-2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.install-app {
|
||||||
|
left: 12px;
|
||||||
|
right: 12px;
|
||||||
|
max-width: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -0,0 +1,160 @@
|
|||||||
|
:root {
|
||||||
|
--vp-font-family-base: 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei',
|
||||||
|
'Noto Sans SC', 'Source Han Sans SC', sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 旧版 markdown 里的 <font size="4"> 在手机上过大 */
|
||||||
|
.vp-doc font {
|
||||||
|
font-size: inherit !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vp-doc {
|
||||||
|
line-height: 1.85;
|
||||||
|
letter-spacing: 0.02em;
|
||||||
|
word-break: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vp-doc h1 {
|
||||||
|
font-size: clamp(1.45rem, 5vw, 2rem);
|
||||||
|
line-height: 1.35;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vp-doc h2 {
|
||||||
|
font-size: clamp(1.2rem, 4.2vw, 1.55rem);
|
||||||
|
margin-top: 1.6em;
|
||||||
|
padding-bottom: 0.35em;
|
||||||
|
border-bottom: 1px solid var(--vp-c-divider);
|
||||||
|
}
|
||||||
|
|
||||||
|
.vp-doc h3 {
|
||||||
|
font-size: clamp(1.05rem, 3.6vw, 1.25rem);
|
||||||
|
}
|
||||||
|
|
||||||
|
.vp-doc p {
|
||||||
|
margin: 1em 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 道德经等用缩进写的“伪代码块”,改成正文排版 */
|
||||||
|
.vp-doc pre {
|
||||||
|
white-space: pre-wrap;
|
||||||
|
word-break: break-word;
|
||||||
|
overflow-x: auto;
|
||||||
|
font-family: var(--vp-font-family-base);
|
||||||
|
font-size: 1rem;
|
||||||
|
line-height: 1.9;
|
||||||
|
padding: 14px 16px;
|
||||||
|
border-radius: 10px;
|
||||||
|
border: 1px solid var(--vp-c-divider);
|
||||||
|
background: var(--vp-c-bg-soft);
|
||||||
|
}
|
||||||
|
|
||||||
|
.vp-doc :not(pre) > code {
|
||||||
|
font-size: 0.92em;
|
||||||
|
word-break: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vp-doc img {
|
||||||
|
display: block;
|
||||||
|
max-width: 100% !important;
|
||||||
|
height: auto !important;
|
||||||
|
margin: 1em auto;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vp-doc table {
|
||||||
|
display: block;
|
||||||
|
overflow-x: auto;
|
||||||
|
-webkit-overflow-scrolling: touch;
|
||||||
|
font-size: 0.92rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vp-doc blockquote {
|
||||||
|
margin: 1.2em 0;
|
||||||
|
padding: 0.8em 1em;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 底部上一篇 / 下一篇:加大触控区域 */
|
||||||
|
.VPDocFooter .pager-link {
|
||||||
|
min-height: 48px;
|
||||||
|
padding: 12px 14px;
|
||||||
|
border-radius: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.VPDocFooter .pager-link .title {
|
||||||
|
font-size: 0.95rem;
|
||||||
|
line-height: 1.45;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 960px) {
|
||||||
|
.VPContent {
|
||||||
|
padding-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.VPDoc .container {
|
||||||
|
padding: 0 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.VPDoc .content {
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vp-doc {
|
||||||
|
padding: 12px 0 88px;
|
||||||
|
font-size: 17px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vp-doc h1 {
|
||||||
|
margin-bottom: 0.6em;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 手机隐藏右侧大纲,避免挤压正文 */
|
||||||
|
.VPDoc .aside {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.VPDoc.has-aside .content-container {
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 侧边栏目录项加大点击区域 */
|
||||||
|
.VPSidebarItem .text {
|
||||||
|
line-height: 1.5;
|
||||||
|
padding-top: 4px;
|
||||||
|
padding-bottom: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.VPSidebarItem.level-0 > .item > .text {
|
||||||
|
font-size: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 顶栏留白,适配刘海屏 */
|
||||||
|
.VPNavBar {
|
||||||
|
padding-top: env(safe-area-inset-top, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 搜索框不占太多宽度 */
|
||||||
|
.VPNavBarSearch {
|
||||||
|
flex-shrink: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 640px) {
|
||||||
|
.VPNavBarTitle .title {
|
||||||
|
font-size: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vp-doc {
|
||||||
|
font-size: 18px;
|
||||||
|
line-height: 1.9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vp-doc pre {
|
||||||
|
font-size: 17px;
|
||||||
|
padding: 12px 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.VPDocFooter {
|
||||||
|
padding-bottom: calc(12px + env(safe-area-inset-bottom, 0));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
import DefaultTheme from 'vitepress/theme'
|
import DefaultTheme from 'vitepress/theme'
|
||||||
import { h } from 'vue'
|
import { h } from 'vue'
|
||||||
import InstallApp from './InstallApp.vue'
|
import InstallApp from './InstallApp.vue'
|
||||||
|
import './custom.css'
|
||||||
|
|
||||||
if (typeof window !== 'undefined') {
|
if (typeof window !== 'undefined') {
|
||||||
import('virtual:pwa-register').then(({ registerSW }) => {
|
import('virtual:pwa-register').then(({ registerSW }) => {
|
||||||
|
|||||||
Reference in New Issue
Block a user