Layout.vue 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228
  1. <template>
  2. <Layout v-if="showLayout">
  3. <template #doc-before> <!-- 正文插槽 -->
  4. <div v-if="isDev" class="doc-action-bar">
  5. <!-- <button @click="exportDocx">导出为 Word</button> -->
  6. </div>
  7. </template>
  8. <template #doc-after> <!-- 正文插槽 -->
  9. <ClientOnly>
  10. <!-- 页面正文之后插入评论 -->
  11. <Waline v-if="showWaline" ref="walineRef" />
  12. </ClientOnly>
  13. </template>
  14. <template #nav-bar-content-after>
  15. <ClientOnly>
  16. <div style="margin-left: 15px">
  17. <n-dropdown :options="personOptions" @select="handlePersonSelect">
  18. <n-button text style="display: flex; justify-content: center" >
  19. <template #icon>
  20. <n-icon><PersonIcon /></n-icon>
  21. </template>
  22. </n-button>
  23. </n-dropdown>
  24. </div>
  25. </ClientOnly>
  26. </template>
  27. </Layout>
  28. </template>
  29. <script lang="ts" setup>
  30. import { ref, onMounted, watch, Component, h, useTemplateRef } from 'vue-demi'
  31. import { useRouter, useRoute, withBase, useData } from 'vitepress'
  32. import DefaultTheme from 'vitepress/theme'
  33. import { createDiscreteApi, NButton, NIcon, NDropdown, DropdownOption } from 'naive-ui'
  34. import { Exit as ExitIcon, Person as PersonIcon } from '@vicons/ionicons5'
  35. import { useCasdoor } from 'casdoor-vue-sdk'
  36. import type Waline from './components/Waline.vue'
  37. import { setWalineUserInfoFromToken } from '../plugins/waline'
  38. import { exportDomToWord } from '../plugins/DomToDocx'
  39. const isDev = import.meta.env.DEV;
  40. const { frontmatter } = useData()
  41. // async function exportDocx() {
  42. // const blob = await exportDomToWord('.content .content-container .main')
  43. // if (blob) {
  44. // const url = URL.createObjectURL(blob)
  45. // const a = document.createElement('a')
  46. // a.href = url
  47. // a.download = (frontmatter.value.title ?? "null") + ".docx"
  48. // document.body.appendChild(a)
  49. // a.click()
  50. // // 清理
  51. // document.body.removeChild(a)
  52. // URL.revokeObjectURL(url)
  53. // }
  54. // }
  55. function renderIcon(icon: Component) {
  56. return () => {
  57. return h(NIcon, null, {
  58. default: () => h(icon)
  59. })
  60. }
  61. }
  62. const walineRef = ref<InstanceType<typeof Waline> | undefined>(undefined)
  63. const personOptions = ref([
  64. {
  65. label: '退出登录',
  66. key: 'logout',
  67. icon: renderIcon(ExitIcon)
  68. }
  69. ])
  70. const { Layout } = DefaultTheme
  71. const route = useRoute()
  72. const router = useRouter()
  73. let islogin = false
  74. if (!islogin && import.meta.env.DEV) {
  75. console.log('开发环境');
  76. islogin = true;
  77. }
  78. console.log({env: import.meta.env});
  79. const showLayout = ref(false)
  80. const showWaline = ref(false)
  81. const { dialog, massage } = createDiscreteApi(["dialog", "massage"])
  82. const casdoorSdk = useCasdoor()
  83. console.log("route: ", route)
  84. function renderLayout(pathname: string) {
  85. if (!islogin && !pathname.startsWith(withBase('/casdoor/'))) {
  86. // 查询登录状态
  87. fetch<string>(withBase('/api/islogin'), {
  88. method: "GET",
  89. credentials: "include"
  90. }).then(async res => {
  91. // 已登录
  92. const token = await res.text()
  93. // 尝试提取用户信息
  94. setWalineUserInfoFromToken(token)
  95. islogin = true
  96. showLayout.value = true
  97. }).catch(err => {
  98. console.error("未登录")
  99. console.error(err)
  100. // 跳转到登录页面
  101. const signinUrl = casdoorSdk.getSigninUrl()
  102. window.location.href = signinUrl.replace(/^https?:\/\/[^/]+/, '')
  103. })
  104. } else if (islogin || pathname.startsWith(withBase('/casdoor/'))) {
  105. showLayout.value = true
  106. } else {
  107. showLayout.value = false
  108. }
  109. }
  110. function renderWaline(pathname: string) {
  111. // Casdoor,不加载waline
  112. if (pathname.startsWith(withBase('/casdoor/'))) {
  113. showWaline.value = false
  114. } else {
  115. if (showWaline.value) {
  116. // 更新waline
  117. walineRef.value?.update()
  118. } else {
  119. showWaline.value = true
  120. }
  121. }
  122. }
  123. onMounted(async () => {
  124. const url = new URL(window.location.href)
  125. const pathname = url.pathname
  126. console.log("url: ", url)
  127. console.log("pathname: ", pathname)
  128. renderLayout(pathname)
  129. renderWaline(pathname)
  130. watch(
  131. () => route.path,
  132. (newPath, oldPath) => {
  133. console.log('📦 路由变化:', oldPath, '➡️', newPath)
  134. renderWaline(pathname)
  135. renderLayout(pathname)
  136. }
  137. )
  138. })
  139. const handlePersonSelect = (key: string | number, option: DropdownOption) => {
  140. if (key === 'logout') {
  141. handleExitClick()
  142. }
  143. }
  144. const handleExitClick = async () => {
  145. dialog.warning({
  146. title: '提示',
  147. content: '确定要退出登录?',
  148. positiveText: '确定',
  149. negativeText: '取消',
  150. draggable: true,
  151. onPositiveClick: async () => {
  152. // 请求登出
  153. await fetch(withBase('/api/exit'), {
  154. method: "POST",
  155. credentials: "include"
  156. }).then(res => {
  157. const text = res.text()
  158. console.log(text)
  159. islogin = false
  160. const signinUrl = casdoorSdk.getSigninUrl()
  161. // 跳转到登录页面
  162. window.location.href = signinUrl.replace(/^https?:\/\/[^/]+/, '')
  163. }).catch(err => {
  164. massage.error("登出失败")
  165. console.error("登出失败")
  166. console.error(err)
  167. })
  168. },
  169. onNegativeClick: () => {
  170. return
  171. }
  172. })
  173. }
  174. </script>
  175. <style lang="css">
  176. .vp-doc h5 {
  177. margin: 24px 0 0;
  178. letter-spacing: -0.01em;
  179. }
  180. /* 禁止选中内容(禁止复制) */
  181. /* .vp-doc {
  182. user-select: none;
  183. -webkit-user-select: none;
  184. -moz-user-select: none;
  185. -ms-user-select: none;
  186. } */
  187. .doc-action-bar {
  188. display: flex;
  189. justify-content: flex-end; /* 右对齐 */
  190. margin-bottom: 1em;
  191. }
  192. .doc-action-bar button {
  193. background-color: #3eaf7c;
  194. color: white;
  195. border: none;
  196. padding: 6px 12px;
  197. border-radius: 4px;
  198. cursor: pointer;
  199. }
  200. </style>