wangtao hace 1 año
padre
commit
c00729cd4d

+ 4 - 1
index.html

@@ -6,11 +6,14 @@
     <link rel="icon" type="image/svg+xml" href="/vite.svg" />
     <meta name="viewport" content="width=device-width, initial-scale=1.0" />
     <title>Vite + Vue + TS</title>
+    <!--preload-links-->
 </head>
-
 <body>
     <div id="app"><!--ssr-outlet--></div>
     <script type="module" src="/src/entry-client.ts"></script>
+    <script>
+        window.__INITIAL_STATE__ = '<!--vuex-state-->' 
+    </script>
 </body>
 
 </html>

La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 2 - 5161
package-lock.json


+ 7 - 4
package.json

@@ -4,20 +4,23 @@
     "version": "0.0.0",
     "type": "module",
     "scripts": {
-        "dev": "node server",
+        "dev": "vite",
         "dev:ssr": "cross-env NODE_ENV=development  node server",
         "prod:ssr": "cross-env NODE_ENV=production  node server",
         "build": "vue-tsc && vite build",
         "preview": "vite preview",
-        "build:client": "vite build --outDir dist/client",
-        "build:server": "vite build --outDir dist/server --ssr src/entry-server.ts"
+        "build:client": "vite build --outDir dist/client --ssrManifest",
+        "build:server": "vite build --outDir dist/server --ssr src/entry-server.ts",
+        "build:ssr": "npm run build:client && npm run build:server"
     },
     "dependencies": {
         "axios": "^1.6.7",
         "element-plus": "^2.5.6",
         "vue": "^3.4.19",
         "vue-i18n": "^9.9.0",
-        "vue-router": "^4.0.13"
+        "vue-router": "^4.0.13",
+        "vuex": "^4.0.2",
+        "vuex-router-sync": "^5.0.0"
     },
     "devDependencies": {
         "@typescript-eslint/eslint-plugin": "^6.21.0",

+ 18 - 7
server.js

@@ -4,11 +4,12 @@ import { fileURLToPath } from 'url'
 import express from 'express'
 import serverStatic from 'serve-static'
 import { createServer as createViteServer } from 'vite'
+import ssrManifest from './dist/client/.vite/ssr-manifest.json' assert { type: "json" }
 const isProd = process.env.NODE_ENV  === 'production'
 const __dirname = path.dirname(fileURLToPath(
     import.meta.url))
-
 async function createServer() {
+    console.log(process.env.NODE_ENV)
     const app = express()
     // 以中间件模式创建 Vite 应用,并将 appType 配置为 'custom'
         // 这将禁用 Vite 自身的 HTML 服务逻辑
@@ -18,7 +19,6 @@ async function createServer() {
             appType: 'custom'
         })
     if(!isProd){
-        
         // 使用 vite 的 Connect 实例作为中间件
         // 如果你使用了自己的 express 路由(express.Router()),你应该使用 router.use
         // 当服务器重启(例如用户修改了 vite.config.js 后),
@@ -35,6 +35,7 @@ async function createServer() {
         const url = req.originalUrl
         let template
         let render
+        let html
         try {
             if(!isProd){
                  // 1. 读取 index.html
@@ -63,7 +64,7 @@ async function createServer() {
                     path.resolve(__dirname, 'dist/client/index.html'),
                     'utf-8',
                 )
-                render  = require('./dist/entry-server.ts').render
+                render  = (await vite.ssrLoadModule('/src/entry-server.ts')).render
             }
            
             
@@ -74,10 +75,20 @@ async function createServer() {
             // 4. 渲染应用的 HTML。这假设 entry-server.js 导出的 `render`
             //    函数调用了适当的 SSR 框架 API。
             //    例如 ReactDOMServer.renderToString()
-            const appHtml = await render(url)
-
-            // 5. 注入渲染后的应用程序 HTML 到模板中。
-            const html = template.replace(`<!--ssr-outlet-->`, appHtml)
+            const manifest =  ssrManifest
+            if(!isProd){
+                const { appHtml, state } = await render(url,manifest)
+                // 5. 注入渲染后的应用程序 HTML 到模板中。
+                html = template
+                .replace(`<!--ssr-outlet-->`, appHtml)
+                .replace('\'<!--vuex-state-->\'', JSON.stringify(state))
+            }else{
+                const { appHtml, state,preloadLinks } = await render(url,manifest)
+                html = template
+                .replace(`<!--preload-links-->`, preloadLinks)
+                .replace(`<!--ssr-outlet-->`, appHtml)
+                .replace('\'<!--vuex-state-->\'', JSON.stringify(state))
+            }
 
             // 6. 返回渲染后的 HTML。
             res.status(200).set({ 'Content-Type': 'text/html' }).end(html)

+ 5 - 5
src/App.vue

@@ -1,14 +1,14 @@
 <script setup lang="ts">
-// import { useRouter  } from 'vue-router'
 import headerCommon from '@/components/layout/header.vue'
-import footerCommon from '@/components/layout/footer.vue'
-
-// const router = useRouter()
+// import footerCommon from '@/components/layout/footer.vue'
 </script>
 
 <template>
+  <!-- {{ store.state.count }}
+  <button type="button" @click="setmutation">mutation</button>
+  <button type="button" @click="setaction">action</button> -->
     <headerCommon />
-    <footerCommon />
+    <!-- <footerCommon /> -->
     <router-view />
 </template>
 

+ 22 - 31
src/components/layout/header.vue

@@ -1,44 +1,35 @@
-<script setup lang="ts">
-import { ref } from 'vue'
-import { getMenu } from '@/api/index'
-// import indexedDb from '@/utils/indexedDb'
-const currentone:any= ref("") //当前页
-const menuList:any = ref([])//菜单列表
-// const uskyDb = new indexedDb("uskyDb") //创建或连接DB数据库
-// uskyDb.openStore("menu","id",['list'])//打开数据库menu
-getMenuList()
-currentone.value = "/"
+<script lang="ts">
+import { defineComponent, ref } from 'vue'
+import { useStore  } from 'vuex'
+
+
+export default defineComponent({
+    setup(){
+        const store = useStore()
+        const currentone = ref("")
+        const menuList:any = ref([])//菜单列表
+        // menuList.value = store.state.menuList
+        return { store,currentone }
+    },
+    async asyncData({store, route,a}){
+        return store.dispatch('getMenuList')
+    }
+})
+
 
-//获取菜单
-function getMenuList(){
-    getMenu().then((res:any)=>{
-        menuList.value = res.data
-        setTimeout(()=>{
-            // addOrEditDb("menu",res.data)
-        },10)
-    })
-}
-//新增、修改数据
-// function addOrEditDb(storeName:string,data:any){
-//     uskyDb.updateItem(storeName,{
-//         id:1,
-//         list:data
-//   })
-// }
 </script>
 <template>
   <header class="header-absolute sticky-header">
-       <!-- {{ menuList }} -->
             <div class="custom-container-one">
                 <div class="mainmenu-area  d-flex align-items-center justify-content-center">
                     <div class="logo">
-                        <a href="/"><img src="@/assets/img/logo-white.png" alt="uskylogo"></a>
+                        <a href="/"><img src="@/assets/img/logo.png" alt="uskylogo"></a>
                     </div>
                     <div class="d-flex align-items-center ">
                         <nav class="main-menu">
                             <div class="menu-items">
-                                <ul v-if="menuList.length>0">
-                                    <li v-for="item in menuList" :key="item.id" 
+                                <ul v-if="store.state.menuList.length>0">
+                                    <li v-for="item in store.state.menuList" :key="item.id" 
                                     :class="!item.sname ? (currentone=='/template'+item.categoryStyle+'/' ? 'active' : item.categoryStyle == '1' ? 'active' : '') : currentone == '/template'+item.categoryStyle+'/?dup=' + item.sname ? 'active' : ''    " 
                                     >
                                         <a  v-if="item.sname" :href="item.categoryStyle==1?'/?dup='+item.sname:'/template'+item.categoryStyle+'/?dup='+item.sname">{{item.categoryName}}</a>
@@ -54,7 +45,7 @@ function getMenuList(){
                                 </ul>
                             </div>
                         </nav>
-                        <div class="apply-expre" v-if="menuList.length>0" @click="gogo()">申请体验</div>
+                        <div class="apply-expre" v-if="store.state.menuList.length>0" @click="gogo()">申请体验</div>
                     </div>
                 </div>
                 <div>

+ 26 - 1
src/entry-client.ts

@@ -1,6 +1,9 @@
 import { createApp } from './main'
 // import indexedDb from './utils/indexedDb'
-const { app, router } = createApp()
+const { app, router, store } = createApp()
+if(window.__INITIAL_STATE__){
+    store.replaceState(window.__INITIAL_STATE__)
+}
 // router.beforeEach((to,from,next)=>{
 //     const uskyDb = new indexedDb("uskyDb") //创建或连接DB数据库
 //     uskyDb.openStore("menu","id",['list']).then((res:any)=>{
@@ -8,5 +11,27 @@ const { app, router } = createApp()
 //     })
 // })
 router.isReady().then(() => {
+    //比较to、from不同时执行数据预取
+    router.beforeResolve((to,from,next)=>{
+        let toComponent = router.resolve(to).matched.flatMap(record=>Object.values(record.components))
+        let fromComponent = router.resolve(from).matched.flatMap(record=>Object.values(record.components))
+        let actived = toComponent.filter((c,i)=>{
+            return fromComponent[i] !==c
+        })
+        if(!actived.length){
+            return next()
+        }
+        //对所有匹配的路由组件调用`asyncData()
+        Promise.all(actived.map((Component:any) =>{
+            if(Component.asyncData){
+                return Component.asyncData({
+                    store,
+                    route: router.currentRoute
+                })
+            }
+        })).then(()=>{
+            next()
+        })
+    })
     app.mount('#app')
 })

+ 61 - 4
src/entry-server.ts

@@ -1,9 +1,66 @@
 import { createApp } from './main'
 import { renderToString } from 'vue/server-renderer'
-export async function render(url: string) {
-    const { app, router } = createApp()
+export async function render(url: string,manifest:any) {
+    const { app, router, store } = createApp()
     await router.push(url)
     await router.isReady()
-    const html = renderToString(app)
-    return html
+    const matchedComponents = router.currentRoute.value.matched.flatMap(record =>Object.values(record.components))
+    //对所有匹配的路由组件调用`asyncData()
+    await Promise.all(matchedComponents.map((Component:any) =>{
+        if(Component.asyncData){
+            return Component.asyncData({
+                store,
+                route: router.currentRoute
+            })
+        }
+    }))
+    const context:any = {}
+    const appHtml = await renderToString(app,context)
+    const state = store.state
+    // preloadLinks 文件预加载
+    if(import.meta.env.PROD){
+        const preloadLinks = renderLinks(context.modules,manifest)
+        return { appHtml, state, preloadLinks }
+    }else{
+        return { appHtml, state }
+    }
+}
+
+//文件路径获取
+function renderLinks(modules:any,manifest:any){
+    console.log(90,modules)
+    let links = ""
+    modules.forEach((id:any) => {
+        if(id){
+            const files = manifest[id]
+            if(files){
+                files.forEach((file:any)=>{
+                    links += renderPreloadLink(file)
+                })
+            }
+        }
+    });
+    return links
+}
+
+//文件格式
+function renderPreloadLink(file:any){
+    if(file.endsWith('.js')){
+        return `<link rel="modulepreload" crossorigin href="${file}">`
+    }else if(file.endsWith('.css')){
+        return `<link rel="stylesheet" href="${file}">`
+    }else if(file.endsWith('.woff')){
+        return `<link rel="preload" href="${file}" as="font" type="font/woff" crossorigin>`
+    }else if(file.endsWith('.woff2')){
+        return `<link rel="preload" href="${file}" as="font" type="font/woff2" crossorigin>`
+    }else if(file.endsWith('.gif')){
+        return `<link rel="preload" href="${file}" as="image" type="image/gif">`
+    }else if(file.endsWith('.jpg')){
+        return `<link rel="preload" href="${file}" as="image" type="image/jpg">`
+    }else if(file.endsWith('.png')){
+        return `<link rel="preload" href="${file}" as="image" type="image/png">`
+    }else{
+        return ''
+    }
+
 }

+ 7 - 5
src/main.ts

@@ -1,17 +1,19 @@
 import { createSSRApp } from 'vue'
 import './style.css'
 import App from './App.vue'
-import router from './router'
+import { createSSRRouter } from './router'
 import ElementPlus from 'element-plus'
 import 'element-plus/dist/index.css'
-import i18n from './langes/i18n'
-
+import { store } from './store'
+// import { sync } from 'vuex-router-sync'
+const router = createSSRRouter()
 
 
 export function createApp() {
     const app = createSSRApp(App)
+    // sync(store,router)
     app.use(router)
+    app.use(store)
     app.use(ElementPlus)
-    app.use(i18n)
-    return { app, router }
+    return { app, router , store}
 }

+ 17 - 8
src/router/index.ts

@@ -1,6 +1,9 @@
-import home from '@/views/home/index.vue'
-import mine from '@/views/mine/index.vue'
-import { createRouter, createWebHashHistory, createMemoryHistory, createWebHistory } from 'vue-router'
+// import home from '@/views/home/index.vue'
+// import mine from '@/views/mine/index.vue'
+const home = () => import('@/views/home/homeIndex.vue')
+const mine = () => import('@/views/mine/index.vue')
+// const header = () => import('@/views/mine/index.vue')
+import { createRouter, createMemoryHistory, createWebHistory } from 'vue-router'
 
 const routes = [
     {
@@ -23,9 +26,15 @@ const routes = [
     },
 ]
 
-const router = createRouter({
-    history:  import.meta.env.SSR ? createMemoryHistory() : createWebHistory(),
-    routes
-})
+// const router = createRouter({
+//     history:  import.meta.env.SSR ? createMemoryHistory() : createWebHistory(),
+//     routes
+// })
 
-export default router
+// export default router
+export function createSSRRouter(){
+    return createRouter({
+        history:  import.meta.env.SSR ? createMemoryHistory() : createWebHistory(),
+        routes
+    })
+}

+ 39 - 0
src/store/index.ts

@@ -0,0 +1,39 @@
+import { createStore } from 'vuex'
+import { getMenu } from '@/api/index'
+// import { InjectionKey } from 'vue'
+
+// export interface AllStateTypes {
+//     count:number,
+// }
+// export const key :IN
+export const store = createStore({
+    state:{
+        count:1,
+        menuList:[]
+    },
+    mutations:{
+        setCount(state,dd){
+            state.count += dd
+            return state.count
+        },
+        setMenuList(state,data){
+            state.menuList = data
+            return state.menuList
+        }
+    },
+    actions:{
+        fatchCount({commit,state},payload){
+            setTimeout(() => {
+                    commit('setCount',payload)
+            }, 3000);
+        },
+        getMenuList({ commit }){
+            return new Promise(resolve=>{
+                getMenu().then((res:any)=>{
+                    commit('setMenuList',res.data)
+                    resolve(true)
+                })
+            })
+        }
+    }
+})

+ 30 - 0
src/views/home/homeIndex.vue

@@ -0,0 +1,30 @@
+<script lang="ts">
+import { defineComponent, ref } from 'vue'
+import { useStore  } from 'vuex'
+import uskylogo from '@/assets/img/logo.png'
+export default defineComponent({
+    setup(){
+        const store = useStore()
+        const img = uskylogo
+        return { store, img }
+        // const currentone:any= ref("") //当前页
+        
+        // currentone.value = "/"
+        //     setTimeout(()=>{
+        //     let a = store.state
+        //         console.log(1,a)
+        // },3000)
+    },
+    async asyncData({store}:any){
+        return store.dispatch('getMenuList')
+    }
+})
+</script>
+<template>
+        {{ store.state.menuList }}
+        <img src="https://pic.rmb.bdstatic.com/bjh/914b8c0f9814b14c5fedeec7ec6615df5813.jpeg" >
+
+</template>
+<style lang="scss" scoped>
+
+</style>

+ 0 - 6
src/views/home/index.vue

@@ -1,6 +0,0 @@
-<script setup lang="ts">
-
-</script>
-<template>
-  <div>首页</div>
-</template>

La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 0 - 34
vite.config.ts.timestamp-1709647159776-4d3677cc2e081.mjs


Algunos archivos no se mostraron porque demasiados archivos cambiaron en este cambio