@tnotesjs/core 0.1.15 → 0.1.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (115) hide show
  1. package/README.md +87 -87
  2. package/dist/{chunk-FVKQPBPP.js → chunk-4MO7F7Q4.js} +2005 -1769
  3. package/dist/cli/index.js +1 -1
  4. package/dist/vitepress/config/index.js +212 -4
  5. package/package.json +1 -1
  6. package/types/config.ts +61 -61
  7. package/types/index.ts +11 -11
  8. package/types/note.ts +33 -33
  9. package/vitepress/assets/icons/icon__check.svg +3 -3
  10. package/vitepress/assets/icons/icon__clipboard.svg +7 -7
  11. package/vitepress/assets/icons/icon__close.svg +1 -1
  12. package/vitepress/assets/icons/icon__collapse.svg +1 -1
  13. package/vitepress/assets/icons/icon__confirm.svg +1 -1
  14. package/vitepress/assets/icons/icon__copy.svg +4 -4
  15. package/vitepress/assets/icons/icon__focus.svg +1 -1
  16. package/vitepress/assets/icons/icon__fold.svg +3 -3
  17. package/vitepress/assets/icons/icon__folder.svg +1 -1
  18. package/vitepress/assets/icons/icon__fullscreen.svg +1 -1
  19. package/vitepress/assets/icons/icon__fullscreen_exit.svg +1 -1
  20. package/vitepress/assets/icons/icon__github.svg +3 -3
  21. package/vitepress/assets/icons/icon__mindmap.svg +1 -1
  22. package/vitepress/assets/icons/icon__next.svg +1 -1
  23. package/vitepress/assets/icons/icon__number_gray.svg +1 -1
  24. package/vitepress/assets/icons/icon__number_purple.svg +1 -1
  25. package/vitepress/assets/icons/icon__prev.svg +1 -1
  26. package/vitepress/assets/icons/icon__restore.svg +1 -1
  27. package/vitepress/assets/icons/icon__rotate.svg +4 -4
  28. package/vitepress/assets/icons/icon__search.svg +1 -1
  29. package/vitepress/assets/icons/icon__sidebar_collapsed.svg +1 -1
  30. package/vitepress/assets/icons/icon__sidebar_opened.svg +1 -1
  31. package/vitepress/assets/icons/icon__totop.svg +5 -5
  32. package/vitepress/assets/icons/icon__vscode.svg +5 -5
  33. package/vitepress/assets/icons/icon__zoom_fit.svg +1 -1
  34. package/vitepress/assets/icons/icon__zoom_in.svg +1 -1
  35. package/vitepress/assets/icons/icon__zoom_out.svg +1 -1
  36. package/vitepress/assets/icons/icon__zoom_reset.svg +1 -1
  37. package/vitepress/assets/icons/index.ts +39 -39
  38. package/vitepress/components/BilibiliOutsidePlayer/BilibiliOutsidePlayer.vue +20 -20
  39. package/vitepress/components/CodeBlockFullscreen/CodeBlockFullscreen.vue +373 -373
  40. package/vitepress/components/CodeBlockFullscreen/index.ts +115 -115
  41. package/vitepress/components/CodeBlockFullscreen/styles.css +64 -64
  42. package/vitepress/components/Discussions/Discussions.module.scss +32 -32
  43. package/vitepress/components/Discussions/Discussions.vue +211 -211
  44. package/vitepress/components/EnWordList/EnWordList.module.scss +124 -124
  45. package/vitepress/components/EnWordList/EnWordList.vue +543 -543
  46. package/vitepress/components/EnWordList/RightClickMenu.module.scss +22 -22
  47. package/vitepress/components/EnWordList/RightClickMenu.vue +66 -66
  48. package/vitepress/components/Footprints/Footprints.module.scss +93 -93
  49. package/vitepress/components/Footprints/Footprints.vue +377 -377
  50. package/vitepress/components/Layout/AboutModal.module.scss +233 -233
  51. package/vitepress/components/Layout/AboutModal.vue +105 -105
  52. package/vitepress/components/Layout/AboutPanel.vue +270 -270
  53. package/vitepress/components/Layout/ContentCollapse.vue +603 -603
  54. package/vitepress/components/Layout/CustomSidebar.vue +878 -608
  55. package/vitepress/components/Layout/DocBeforeControls.vue +149 -149
  56. package/vitepress/components/Layout/DocFooter.vue +233 -233
  57. package/vitepress/components/Layout/ImagePreview.module.scss +201 -201
  58. package/vitepress/components/Layout/ImagePreview.vue +281 -281
  59. package/vitepress/components/Layout/Layout.module.scss +661 -661
  60. package/vitepress/components/Layout/Layout.vue +618 -559
  61. package/vitepress/components/Layout/NoteStatus.vue +140 -140
  62. package/vitepress/components/Layout/SidebarItems.vue +1083 -268
  63. package/vitepress/components/Layout/SidebarNavBefore.vue +171 -108
  64. package/vitepress/components/Layout/Swiper.vue +167 -167
  65. package/vitepress/components/Layout/ToggleFullContent.module.scss +11 -11
  66. package/vitepress/components/Layout/ToggleFullContent.vue +35 -35
  67. package/vitepress/components/Layout/ToggleSidebar.module.scss +11 -11
  68. package/vitepress/components/Layout/ToggleSidebar.vue +36 -36
  69. package/vitepress/components/Layout/composables/useCollapseControl.ts +88 -88
  70. package/vitepress/components/Layout/composables/useNoteConfig.ts +119 -119
  71. package/vitepress/components/Layout/composables/useNoteSave.ts +178 -178
  72. package/vitepress/components/Layout/composables/useNoteValidation.ts +85 -85
  73. package/vitepress/components/Layout/composables/useRedirect.ts +113 -113
  74. package/vitepress/components/Layout/composables/useVSCodeIntegration.ts +85 -85
  75. package/vitepress/components/Layout/homeReadme.data.ts +124 -124
  76. package/vitepress/components/MarkMap/MarkMap.module.scss +159 -159
  77. package/vitepress/components/MarkMap/MarkMap.vue +405 -405
  78. package/vitepress/components/Mermaid/Mermaid.module.scss +275 -275
  79. package/vitepress/components/Mermaid/Mermaid.vue +365 -365
  80. package/vitepress/components/NotesTable/NotesTable.module.scss +77 -77
  81. package/vitepress/components/NotesTable/NotesTable.vue +98 -98
  82. package/vitepress/components/NotesTable/README.md +67 -67
  83. package/vitepress/components/Settings/Settings.module.scss +375 -433
  84. package/vitepress/components/Settings/Settings.vue +376 -306
  85. package/vitepress/components/SidebarCard/MindMapView.vue +484 -484
  86. package/vitepress/components/SidebarCard/NotesTrendChart.vue +108 -108
  87. package/vitepress/components/SidebarCard/SidebarCard.vue +946 -946
  88. package/vitepress/components/Tooltip/Tooltip.vue +70 -70
  89. package/vitepress/components/constants.ts +109 -91
  90. package/vitepress/components/notesConfig.data.ts +73 -73
  91. package/vitepress/components/sidebar.data.ts +59 -59
  92. package/vitepress/components/tnotes-config.data.ts +21 -21
  93. package/vitepress/components/utils.ts +26 -26
  94. package/vitepress/config/index.ts +169 -167
  95. package/vitepress/configs/constants.ts +26 -26
  96. package/vitepress/configs/head.config.ts +25 -25
  97. package/vitepress/configs/index.ts +9 -9
  98. package/vitepress/configs/markdown-it.d.ts +23 -23
  99. package/vitepress/configs/markdown.config.ts +366 -366
  100. package/vitepress/configs/theme.config.ts +108 -108
  101. package/vitepress/plugins/buildProgressPlugin.ts +434 -434
  102. package/vitepress/plugins/getNoteByConfigIdPlugin.ts +107 -107
  103. package/vitepress/plugins/renameNotePlugin.ts +68 -68
  104. package/vitepress/plugins/sidebarStructurePlugin.ts +260 -0
  105. package/vitepress/plugins/updateConfigPlugin.ts +63 -63
  106. package/vitepress/theme/index.ts +137 -137
  107. package/vitepress/theme/styles/base.scss +50 -50
  108. package/vitepress/theme/styles/components/404.scss +31 -31
  109. package/vitepress/theme/styles/components/collapse.scss +175 -175
  110. package/vitepress/theme/styles/components/markmap.scss +101 -101
  111. package/vitepress/theme/styles/components/swiper.scss +255 -255
  112. package/vitepress/theme/styles/index.scss +25 -25
  113. package/vitepress/theme/styles/layout.scss +70 -62
  114. package/vitepress/theme/styles/utilities.scss +39 -39
  115. package/vitepress/theme/styles/vitepress-override.scss +25 -25
@@ -1,108 +1,171 @@
1
- <template>
2
- <div class="sidebar-toggle-wrapper">
3
- <div class="group group-left">
4
- <!-- 展开/折叠全部按钮 -->
5
- <button
6
- class="toggle-btn"
7
- @click="$emit('toggle-expand')"
8
- :title="isExpanded ? '全部折叠' : '全部展开'"
9
- >
10
- <img :src="icon__fold" class="toggle-icon" alt="切换展开折叠" />
11
- </button>
12
- <span class="group-label">目录</span>
13
- </div>
14
-
15
- <div class="group group-right">
16
- <!-- 聚焦到当前笔记按钮 -->
17
- <button
18
- class="toggle-btn"
19
- @click="$emit('focus-current')"
20
- title="聚焦到当前笔记(点击切换多个位置)"
21
- >
22
- <img :src="icon__focus" class="toggle-icon" alt="聚焦当前笔记" />
23
- </button>
24
-
25
- <!-- 笔记编号显示切换按钮 -->
26
- <button
27
- class="toggle-btn"
28
- @click="$emit('toggle-note-id')"
29
- :title="showNoteId ? '隐藏笔记编号' : '显示笔记编号'"
30
- >
31
- <img
32
- :src="showNoteId ? icon__number_purple : icon__number_gray"
33
- class="toggle-icon"
34
- alt="切换笔记编号"
35
- />
36
- </button>
37
- </div>
38
- </div>
39
- </template>
40
-
41
- <script setup lang="ts">
42
- import { icon__fold, icon__number_purple, icon__number_gray, icon__focus } from '../../assets/icons'
43
-
44
- defineProps<{
45
- isExpanded: boolean
46
- showNoteId: boolean
47
- }>()
48
-
49
- defineEmits<{
50
- 'toggle-expand': []
51
- 'toggle-note-id': []
52
- 'focus-current': []
53
- }>()
54
- </script>
55
-
56
- <style scoped>
57
- .sidebar-toggle-wrapper {
58
- display: flex;
59
- align-items: center;
60
- justify-content: space-between;
61
- padding: 12px 8px 16px;
62
- border-bottom: 1px solid var(--vp-c-divider);
63
- margin-bottom: 16px;
64
- }
65
-
66
- .group {
67
- display: flex;
68
- align-items: center;
69
- gap: 4px;
70
- }
71
-
72
- .group-label {
73
- font-size: 13px;
74
- color: var(--vp-c-text-2);
75
- user-select: none;
76
- }
77
-
78
- .toggle-btn {
79
- display: inline-flex;
80
- align-items: center;
81
- justify-content: center;
82
- padding: 8px;
83
- cursor: pointer;
84
- background: transparent;
85
- border: none;
86
- border-radius: 6px;
87
- transition: all 0.2s ease;
88
- }
89
-
90
- .toggle-btn:hover {
91
- background: var(--vp-c-bg-soft);
92
- }
93
-
94
- .toggle-btn:active .toggle-icon {
95
- transform: scale(0.95);
96
- }
97
-
98
- .toggle-icon {
99
- width: 20px;
100
- height: 20px;
101
- display: block;
102
- transition: transform 0.2s ease;
103
- }
104
-
105
- .toggle-btn:hover .toggle-icon {
106
- transform: scale(1.1);
107
- }
108
- </style>
1
+ <template>
2
+ <div class="sidebar-toggle-wrapper">
3
+ <div class="toolbar-row">
4
+ <div class="group group-left">
5
+ <!-- 展开/折叠全部按钮 -->
6
+ <button
7
+ class="toggle-btn"
8
+ @click="$emit('toggle-expand')"
9
+ :title="isExpanded ? '全部折叠' : '全部展开'"
10
+ >
11
+ <img :src="icon__fold" class="toggle-icon" alt="切换展开折叠" />
12
+ </button>
13
+ <a
14
+ :class="[
15
+ 'group-label',
16
+ 'group-label-link',
17
+ { 'is-active': isCatalogActive },
18
+ ]"
19
+ :href="catalogLink"
20
+ :aria-current="isCatalogActive ? 'page' : undefined"
21
+ >
22
+ 目录
23
+ </a>
24
+ </div>
25
+
26
+ <div class="group group-right">
27
+ <!-- 聚焦到当前笔记按钮 -->
28
+ <button
29
+ class="toggle-btn"
30
+ @click="$emit('focus-current')"
31
+ title="聚焦到当前笔记(点击切换多个位置)"
32
+ >
33
+ <img :src="icon__focus" class="toggle-icon" alt="聚焦当前笔记" />
34
+ </button>
35
+
36
+ <!-- 笔记编号显示切换按钮 -->
37
+ <button
38
+ class="toggle-btn"
39
+ @click="$emit('toggle-note-id')"
40
+ :title="showNoteId ? '隐藏笔记编号' : '显示笔记编号'"
41
+ >
42
+ <img
43
+ :src="showNoteId ? icon__number_purple : icon__number_gray"
44
+ class="toggle-icon"
45
+ alt="切换笔记编号"
46
+ />
47
+ </button>
48
+ </div>
49
+ </div>
50
+ </div>
51
+ </template>
52
+
53
+ <script setup lang="ts">
54
+ import { useData, useRoute } from 'vitepress'
55
+ import { computed } from 'vue'
56
+
57
+ import {
58
+ icon__fold,
59
+ icon__number_purple,
60
+ icon__number_gray,
61
+ icon__focus,
62
+ } from '../../assets/icons'
63
+
64
+ defineProps<{
65
+ isExpanded: boolean
66
+ showNoteId: boolean
67
+ }>()
68
+
69
+ defineEmits<{
70
+ 'toggle-expand': []
71
+ 'toggle-note-id': []
72
+ 'focus-current': []
73
+ }>()
74
+
75
+ const { site } = useData()
76
+ const route = useRoute()
77
+
78
+ const base = computed(() => site.value.base || '/')
79
+ const catalogLink = computed(() => `${base.value}README`)
80
+ const catalogPath = computed(() => normalizeRoutePath(catalogLink.value))
81
+ const isCatalogActive = computed(() => {
82
+ const currentPath = normalizeRoutePath(route.path)
83
+
84
+ return currentPath === catalogPath.value || currentPath === '/README'
85
+ })
86
+
87
+ function normalizeRoutePath(path: string): string {
88
+ const normalizedPath = path.split(/[?#]/)[0].replace(/\.html$/, '')
89
+
90
+ return normalizedPath.length > 1
91
+ ? normalizedPath.replace(/\/$/, '')
92
+ : normalizedPath
93
+ }
94
+ </script>
95
+
96
+ <style scoped>
97
+ .sidebar-toggle-wrapper {
98
+ position: sticky;
99
+ top: 0;
100
+ z-index: 2;
101
+ display: flex;
102
+ flex-direction: column;
103
+ gap: 8px;
104
+ padding: 10px 8px 12px;
105
+ /* border-bottom: 1px solid var(--vp-c-divider); */
106
+ margin-bottom: 10px;
107
+ background: var(--vp-sidebar-bg-color, var(--vp-c-bg));
108
+ }
109
+
110
+ .toolbar-row {
111
+ display: flex;
112
+ align-items: center;
113
+ justify-content: space-between;
114
+ gap: 8px;
115
+ }
116
+
117
+ .group {
118
+ display: flex;
119
+ align-items: center;
120
+ gap: 4px;
121
+ }
122
+
123
+ .group-label {
124
+ font-size: 13px;
125
+ color: var(--vp-c-text-2);
126
+ text-decoration: none;
127
+ user-select: none;
128
+ }
129
+
130
+ .group-label-link:hover {
131
+ color: var(--vp-c-brand-1);
132
+ }
133
+
134
+ .group-label-link.is-active {
135
+ color: var(--vp-c-brand-1);
136
+ font-weight: 600;
137
+ }
138
+
139
+ .toggle-btn {
140
+ display: inline-flex;
141
+ align-items: center;
142
+ justify-content: center;
143
+ width: 30px;
144
+ height: 30px;
145
+ padding: 0;
146
+ cursor: pointer;
147
+ background: transparent;
148
+ border: none;
149
+ border-radius: 6px;
150
+ transition: all 0.2s ease;
151
+ }
152
+
153
+ .toggle-btn:hover {
154
+ background: var(--vp-c-bg-soft);
155
+ }
156
+
157
+ .toggle-btn:active .toggle-icon {
158
+ transform: scale(0.95);
159
+ }
160
+
161
+ .toggle-icon {
162
+ width: 18px;
163
+ height: 18px;
164
+ display: block;
165
+ transition: transform 0.2s ease;
166
+ }
167
+
168
+ .toggle-btn:hover .toggle-icon {
169
+ transform: scale(1.1);
170
+ }
171
+ </style>
@@ -1,167 +1,167 @@
1
- <template></template>
2
-
3
- <script setup>
4
- import { useData, onContentUpdated } from 'vitepress'
5
- import { ref, onMounted, onBeforeUnmount } from 'vue'
6
-
7
- import 'swiper/css'
8
-
9
- const vpData = useData()
10
-
11
- const swiperInstances = ref([])
12
-
13
- const initSwiper = () => {
14
- // 先清理旧实例
15
- destroySwiper()
16
-
17
- // 只在客户端动态加载
18
- import('swiper').then(({ default: Swiper }) => {
19
- import('swiper/modules').then(({ Navigation, Pagination }) => {
20
- const wrappers = document.querySelectorAll('.tn-swiper')
21
- wrappers.forEach((wrap) => {
22
- const container = wrap.querySelector('.swiper-container')
23
- const tabsEl = wrap.querySelector('.tn-swiper-tabs')
24
- if (!container || !tabsEl) return
25
-
26
- const instance = new Swiper(container, {
27
- // loop: false,
28
- // TODO 可配置
29
- // effect: 'fade',
30
- speed: 0, // 禁用动画
31
- on: {
32
- slideChange: () => {
33
- updateActiveTab(wrap, instance.activeIndex)
34
- updateContainerHeight(container, instance.activeIndex)
35
- },
36
- // 在初始化时也设置一次高度
37
- afterInit: () => {
38
- setTimeout(() => {
39
- updateContainerHeight(container, instance.activeIndex)
40
- }, 0)
41
- },
42
- },
43
- })
44
-
45
- // 生成 tabs(文案来自 data-title;为空则为 'img')
46
- const slides = wrap.querySelectorAll('.swiper-slide')
47
- const isTnTabNavVisible = slides.length >= 2
48
- tabsEl.innerHTML = ''
49
-
50
- // 微调 tabs 容器样式
51
- if (isTnTabNavVisible) {
52
- tabsEl.style.padding = '0 0.8rem 0 3rem'
53
- } else {
54
- tabsEl.style.padding = '0 0.8rem'
55
- }
56
-
57
- // 左按钮 - 上一页
58
- if (isTnTabNavVisible) {
59
- const prevBtn = document.createElement('button')
60
- prevBtn.type = 'button'
61
- prevBtn.className = 'tn-tab-nav tn-tab-prev'
62
- prevBtn.textContent = '<'
63
- prevBtn.title = '上一页'
64
- prevBtn.addEventListener('click', () => {
65
- if (instance.activeIndex === 0) {
66
- // 到头 -> 跳到最后一张
67
- instance.slideTo(slides.length - 1)
68
- } else {
69
- instance.slidePrev()
70
- }
71
- setTimeout(() => {
72
- updateContainerHeight(container, instance.activeIndex)
73
- }, 0)
74
- })
75
- tabsEl.appendChild(prevBtn)
76
-
77
- const line = document.createElement('span')
78
- line.className = 'tn-tab-nav tab-tab-line'
79
- line.textContent = '/'
80
- tabsEl.appendChild(line)
81
- }
82
-
83
- slides.forEach((slide, i) => {
84
- const label = slide.getAttribute('data-title') || 'img'
85
- const btn = document.createElement('button')
86
- btn.type = 'button'
87
- btn.className = 'tn-tab' + (i === 0 ? ' active' : '')
88
- btn.textContent = label
89
- btn.addEventListener('click', () => {
90
- instance.slideTo(i)
91
- // 点击tab时更新高度
92
- setTimeout(() => {
93
- updateContainerHeight(container, i)
94
- }, 0)
95
- })
96
- tabsEl.appendChild(btn)
97
- })
98
-
99
- // 右按钮 - 下一页
100
- if (isTnTabNavVisible) {
101
- const nextBtn = document.createElement('button')
102
- nextBtn.type = 'button'
103
- nextBtn.className = 'tn-tab-nav tn-tab-next'
104
- nextBtn.textContent = '>'
105
- nextBtn.title = '下一页'
106
- nextBtn.addEventListener('click', () => {
107
- if (instance.activeIndex === slides.length - 1) {
108
- // 到头 -> 跳到第一张
109
- instance.slideTo(0)
110
- } else {
111
- instance.slideNext()
112
- }
113
- setTimeout(() => {
114
- updateContainerHeight(container, instance.activeIndex)
115
- }, 0)
116
- })
117
- tabsEl.appendChild(nextBtn)
118
- }
119
-
120
- swiperInstances.value.push(instance)
121
- })
122
- })
123
- })
124
- }
125
-
126
- function updateActiveTab(wrap, activeIndex) {
127
- const btns = wrap.querySelectorAll('.tn-swiper-tabs .tn-tab')
128
- btns.forEach((b, i) => b.classList.toggle('active', i === activeIndex))
129
- }
130
-
131
- // 新增函数:根据当前slide中的图片高度更新容器高度
132
- function updateContainerHeight(container, activeIndex) {
133
- const slides = container.querySelectorAll('.swiper-slide')
134
- if (slides[activeIndex]) {
135
- const img = slides[activeIndex].querySelector('img')
136
- if (img && img.complete) {
137
- // 如果图片已加载完成,直接设置高度
138
- container.style.height = img.offsetHeight + 'px'
139
- } else if (img) {
140
- // 如果图片未加载完成,等待加载完成后设置高度
141
- img.onload = () => {
142
- container.style.height = img.offsetHeight + 'px'
143
- }
144
- }
145
- }
146
- }
147
-
148
- function destroySwiper() {
149
- swiperInstances.value.forEach((inst) => {
150
- try {
151
- inst.destroy(true, true)
152
- } catch {}
153
- })
154
- swiperInstances.value = []
155
- }
156
-
157
- onBeforeUnmount(destroySwiper)
158
-
159
- onMounted(() => {
160
- initSwiper()
161
- })
162
-
163
- // 监听内容更新(包括 HMR 和路由变化)
164
- onContentUpdated(() => {
165
- initSwiper()
166
- })
167
- </script>
1
+ <template></template>
2
+
3
+ <script setup>
4
+ import { useData, onContentUpdated } from 'vitepress'
5
+ import { ref, onMounted, onBeforeUnmount } from 'vue'
6
+
7
+ import 'swiper/css'
8
+
9
+ const vpData = useData()
10
+
11
+ const swiperInstances = ref([])
12
+
13
+ const initSwiper = () => {
14
+ // 先清理旧实例
15
+ destroySwiper()
16
+
17
+ // 只在客户端动态加载
18
+ import('swiper').then(({ default: Swiper }) => {
19
+ import('swiper/modules').then(({ Navigation, Pagination }) => {
20
+ const wrappers = document.querySelectorAll('.tn-swiper')
21
+ wrappers.forEach((wrap) => {
22
+ const container = wrap.querySelector('.swiper-container')
23
+ const tabsEl = wrap.querySelector('.tn-swiper-tabs')
24
+ if (!container || !tabsEl) return
25
+
26
+ const instance = new Swiper(container, {
27
+ // loop: false,
28
+ // TODO 可配置
29
+ // effect: 'fade',
30
+ speed: 0, // 禁用动画
31
+ on: {
32
+ slideChange: () => {
33
+ updateActiveTab(wrap, instance.activeIndex)
34
+ updateContainerHeight(container, instance.activeIndex)
35
+ },
36
+ // 在初始化时也设置一次高度
37
+ afterInit: () => {
38
+ setTimeout(() => {
39
+ updateContainerHeight(container, instance.activeIndex)
40
+ }, 0)
41
+ },
42
+ },
43
+ })
44
+
45
+ // 生成 tabs(文案来自 data-title;为空则为 'img')
46
+ const slides = wrap.querySelectorAll('.swiper-slide')
47
+ const isTnTabNavVisible = slides.length >= 2
48
+ tabsEl.innerHTML = ''
49
+
50
+ // 微调 tabs 容器样式
51
+ if (isTnTabNavVisible) {
52
+ tabsEl.style.padding = '0 0.8rem 0 3rem'
53
+ } else {
54
+ tabsEl.style.padding = '0 0.8rem'
55
+ }
56
+
57
+ // 左按钮 - 上一页
58
+ if (isTnTabNavVisible) {
59
+ const prevBtn = document.createElement('button')
60
+ prevBtn.type = 'button'
61
+ prevBtn.className = 'tn-tab-nav tn-tab-prev'
62
+ prevBtn.textContent = '<'
63
+ prevBtn.title = '上一页'
64
+ prevBtn.addEventListener('click', () => {
65
+ if (instance.activeIndex === 0) {
66
+ // 到头 -> 跳到最后一张
67
+ instance.slideTo(slides.length - 1)
68
+ } else {
69
+ instance.slidePrev()
70
+ }
71
+ setTimeout(() => {
72
+ updateContainerHeight(container, instance.activeIndex)
73
+ }, 0)
74
+ })
75
+ tabsEl.appendChild(prevBtn)
76
+
77
+ const line = document.createElement('span')
78
+ line.className = 'tn-tab-nav tab-tab-line'
79
+ line.textContent = '/'
80
+ tabsEl.appendChild(line)
81
+ }
82
+
83
+ slides.forEach((slide, i) => {
84
+ const label = slide.getAttribute('data-title') || 'img'
85
+ const btn = document.createElement('button')
86
+ btn.type = 'button'
87
+ btn.className = 'tn-tab' + (i === 0 ? ' active' : '')
88
+ btn.textContent = label
89
+ btn.addEventListener('click', () => {
90
+ instance.slideTo(i)
91
+ // 点击tab时更新高度
92
+ setTimeout(() => {
93
+ updateContainerHeight(container, i)
94
+ }, 0)
95
+ })
96
+ tabsEl.appendChild(btn)
97
+ })
98
+
99
+ // 右按钮 - 下一页
100
+ if (isTnTabNavVisible) {
101
+ const nextBtn = document.createElement('button')
102
+ nextBtn.type = 'button'
103
+ nextBtn.className = 'tn-tab-nav tn-tab-next'
104
+ nextBtn.textContent = '>'
105
+ nextBtn.title = '下一页'
106
+ nextBtn.addEventListener('click', () => {
107
+ if (instance.activeIndex === slides.length - 1) {
108
+ // 到头 -> 跳到第一张
109
+ instance.slideTo(0)
110
+ } else {
111
+ instance.slideNext()
112
+ }
113
+ setTimeout(() => {
114
+ updateContainerHeight(container, instance.activeIndex)
115
+ }, 0)
116
+ })
117
+ tabsEl.appendChild(nextBtn)
118
+ }
119
+
120
+ swiperInstances.value.push(instance)
121
+ })
122
+ })
123
+ })
124
+ }
125
+
126
+ function updateActiveTab(wrap, activeIndex) {
127
+ const btns = wrap.querySelectorAll('.tn-swiper-tabs .tn-tab')
128
+ btns.forEach((b, i) => b.classList.toggle('active', i === activeIndex))
129
+ }
130
+
131
+ // 新增函数:根据当前slide中的图片高度更新容器高度
132
+ function updateContainerHeight(container, activeIndex) {
133
+ const slides = container.querySelectorAll('.swiper-slide')
134
+ if (slides[activeIndex]) {
135
+ const img = slides[activeIndex].querySelector('img')
136
+ if (img && img.complete) {
137
+ // 如果图片已加载完成,直接设置高度
138
+ container.style.height = img.offsetHeight + 'px'
139
+ } else if (img) {
140
+ // 如果图片未加载完成,等待加载完成后设置高度
141
+ img.onload = () => {
142
+ container.style.height = img.offsetHeight + 'px'
143
+ }
144
+ }
145
+ }
146
+ }
147
+
148
+ function destroySwiper() {
149
+ swiperInstances.value.forEach((inst) => {
150
+ try {
151
+ inst.destroy(true, true)
152
+ } catch {}
153
+ })
154
+ swiperInstances.value = []
155
+ }
156
+
157
+ onBeforeUnmount(destroySwiper)
158
+
159
+ onMounted(() => {
160
+ initSwiper()
161
+ })
162
+
163
+ // 监听内容更新(包括 HMR 和路由变化)
164
+ onContentUpdated(() => {
165
+ initSwiper()
166
+ })
167
+ </script>
@@ -1,11 +1,11 @@
1
- .contentToggleBtn {
2
- width: 1.5rem;
3
- height: 1.5rem;
4
- padding: 3px;
5
- cursor: pointer;
6
- transition: all 0.2s;
7
-
8
- &:hover {
9
- background: var(--vp-c-bg-alt);
10
- }
11
- }
1
+ .contentToggleBtn {
2
+ width: 1.5rem;
3
+ height: 1.5rem;
4
+ padding: 3px;
5
+ cursor: pointer;
6
+ transition: all 0.2s;
7
+
8
+ &:hover {
9
+ background: var(--vp-c-bg-alt);
10
+ }
11
+ }