
> vue提供内置组件transition进行状态变化的过渡 ## **transition基本使用** ## **基本使用** ```text <div> <p>Acom</p> <button @click="isShow = !isShow">change</button> <transition name="fade"><div v-if="isShow">这是个div</div></transition> </div> ``` 当transition组件中的元素被插入或移除时: 1. Vue 会自动检测目标元素是否应用了 CSS 过渡或动画。如果是,则一些 css过渡或动画会在适当的时机被添加和移除。 2. 如果有作为监听器的 [JavaScript 钩子](https://staging-cn.vuejs.org/guide/built-ins/transition.html#javascript-hooks),这些钩子函数会在适当时机被调用。 3. 如果没有探测到 CSS 过渡或动画、也没有提供 JavaScript 钩子,那么 DOM 的插入、删除操作将在浏览器的下一个动画帧后执行。 ### **过渡类(vue3)** ```text .fade-enter-from { opacity: 0; } .fade-enter-to { opacity: 1; } .fade-enter-active, .fade-leave-active { transition: opacity 0.5s; } .fade-leave-from { opacity: 1; } .fade-leave-to { opacity: 0; } ``` ### **过渡类(vue2)** ```text .fade-enter { opacity: 0; } .fade-enter-to { opacity: 1; } .fade-enter-active, .fade-leave-active { transition: opacity 0.5s; } .fade-leave { opacity: 1; } .fade-leave-to { opacity: 0; } ``` > 也就是说,vue3的改动就是说把元素插入之前和元素离开之前的类改了名字,我说怎么写的时候进入时没动画,百度害*人~


# **基于vue3实现ssr应用** - 大致流程 ## **搭建Node Server** 1. 依赖 -> koa webpack webpack-cli webpack-node-externals 2. 打包配置 排除包 ```text const path = require("path"); const external = require("webpack-node-externals"); module.exports = { target: "node", // 去除 path fs ... mode: "development", entry: "./src/server/index.js", output: { filename: "server_bundle.js", path: path.resolve(__dirname, "../build/server"), }, externals: [external()], //排除node_module的包 }; "start": "nodemon ./src/server/index.js", "build:server": "webpack --config ./config/wp.config.js", "server": "node ./build/server/server_bundle.js" ``` ## **vue3 + SSR 搭建** 1. 依赖项 -> vue vue-loader babel-loader @babel/preset-env 2. 挂载根组件, 导出创建SSRAPP的方法 ```text import { createSSRApp } from "vue"; import App from "./App.vue"; // 防止跨请求状态污染 // 返回app让页面每次创建不同的app export default function createApp() { return createSSRApp(App); } ``` 3. 访问/ssr时,处理响应,拿到返回的字符串并渲染 ```text // 拿到vue的str const appStringHTML = await renderToString(ssrApp); ctx.body = ` <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Document</title> </head> <body> <div id="app">${appStringHTML}</div> </body> </html> `; ``` 4. 对页面进行激活(水合Hydration) 1. 创建app -> createApp() 2. 打包配置 ```text const path = require("path"); const { VueLoaderPlugin } = require("vue-loader/dist"); module.exports = { target: "web", mode: "development", entry: "./src/client/index.js", output: { filename: "client_bundle.js", path: path.resolve(__dirname, "../build/client"), }, module: { rules: [ { test: /\.js$/, loader: "babel-loader", options: { presets: ["@babel/preset-env"], }, }, { test: /\.vue$/, loader: "vue-loader", }, ], }, plugins: [new VueLoaderPlugin()], // 不用externals需要的依赖文件, 因为交互需要使用 }; ``` 3. 打包后的资源通过静态资源共享挂载到script上 ```text ctx.body = ` <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Document</title> </head> <body> <div id="app">${appStringHTML}</div> </body> <script>${jsStr}</script> </html> `; ``` 4. 报警告flags **VUE_OPTIONS_API**, **VUE_PROD_DEVTOOLS** are not explicitly defined. You are running the esm-bundler build of Vue, which expects these compile-time feature flags to be globally injected via the bundler config in order to get better tree-shaking in the production bundle. 1. 不使用optionsapi...,让它treeshaking ```text new DefinePlugin({ __VUE_OPTIONS_API__: false, __VUE_PROD_DEVTOOLS__: false, }), ], ``` ## **跨请求状态污染问题** - 每次访问重新创建一个ssrAPP ## **效果**



> iframe 没有 click 事件,所以要监听内部点击,可以从内部触发,或给父盒子添加 click 监听 ## 基本实现 ```javascript import { isSylvia } from "@/config/live2d"; import { IModalConfig } from "@/services/user"; import { CircularProgress } from "@nextui-org/progress"; import clsx from "clsx"; import { sample } from "lodash"; import { useEffect, useImperativeHandle, useState } from "react"; import ModelPreviewCover from "../ModelPreviewCover"; interface PixiLive2DRenderProps { currModel: IModalConfig; isCubismCoreLoaded: boolean; intimacy: number; } export default function ModalRender({ currModel, isCubismCoreLoaded, intimacy, ref, iframeWindow, setIframeWindow, }: PixiLive2DRenderProps) { const [loadingState, setLoadingState] = useState(true); const randomMotion = () => { const ins = iframeWindow?.ins; if (!ins?.motion) return; const actions = intimacy > 80 ? [0, 1, 2] : [0, 2]; ins?.motion("Tap", sample(actions)); }; useImperativeHandle(ref, () => ({ randomMotion, })); if (!currModel?.uuid) return; return ( <div className="flex items-center justify-center relative"> {/* {isCubismCoreLoaded && window?.location?.host === "wingman.bestie.icu" && ( <FpsDisplay app={modelApp} /> )} */} <div className={clsx( "w-[600px] h-[1000px] object-cover", !loadingState ? "opacity-0" : "opacity-100", )} onClick={(e) => { randomMotion?.(); }} > <iframe src={`/bestie/${currModel?.uuid}`} frameBorder="0" onLoad={(e) => { const cw = e?.target?.contentWindow; if (cw) { setIframeWindow(cw); } }} title="Live2D" className={clsx( "w-[600px] h-[1000px] object-cover pointer-events-none", !loadingState ? "opacity-0" : "opacity-100", )} /> </div> {!loadingState && ( <div className="flex justify-center items-center absolute inset-0 bg-center left-1/2 -translate-x-[45%]" style={{ width: 600, height: 1000, }} > <ModelPreviewCover width={600} height={isSylvia(currModel?.uuid) ? 1200 : 1000} modelUuid={currModel?.uuid} className={clsx( isSylvia(currModel?.uuid) ? "w-full h-full !object-cover !-translate-x-8" : "", )} /> <CircularProgress aria-label="Loading..." color="warning" size="lg" className="absolute left-1/2 top-1/2 -translate-y-1/2 -translate-x-1/2" /> </div> )} </div> ); } ```


1. docker 1. **清除所有未使用或悬空镜像、容器、卷和网络** ```javascript docker system prune -a ``` 2. git 1. git 同步远程分支 ```bash git remote prune origin ```


> 最近在写国际化适配阿拉伯语时,发现在跑翻译的时候,有一些json解析翻译出来有些引号被替换,导致JSON.parse 报错,其中几个页面会直接error但是并没有发现,所以打算写一个测试,测试所有页面是否加载正常。 ### 组件单元测试(vitest) 1. 准备工作 1. vitest.config.mts ```typescript import react from "@vitejs/plugin-react"; import tsconfigPaths from "vite-tsconfig-paths"; import { defineConfig } from "vitest/config"; export default defineConfig({ plugins: [tsconfigPaths(), react()], test: { globals: true, environment: "jsdom", setupFiles: ["./vitest.setup.ts"], include: ["**/*.test.{js,jsx,ts,tsx}"], exclude: ["**/node_modules/**", "**/dist/**"] } }); ``` b. vitest.setup.ts ```typescript import { cleanup } from "@testing-library/react"; import { afterEach, vi } from "vitest"; afterEach(() => { cleanup(); vi.clearAllMocks(); }); ``` 2. 基本使用 1. 判断 渲染组件后关键DOM是否存在 ```typescript import Footer from "@/components/Footer"; import { render, screen } from "@testing-library/react"; import { expect, test } from "vitest"; test("test", () => { render(<Footer />); expect(screen.getByText("[赣ICP备2022002397号]")).toBeDefined(); }); ``` ### e2e测试(playwright) 1. 准备工作 1. playwright.config.ts ```typescript import { defineConfig, devices } from "@playwright/test"; import path from "path"; // Use process.env.PORT by default and fallback to port 3000 const PORT = process.env.PORT || 3000; // Set webServer.url and use.baseURL with the location of the WebServer respecting the correct set port const baseURL = `http://localhost:${PORT}`; // Reference: https://playwright.dev/docs/test-configuration export default defineConfig({ // Timeout per test timeout: 30 * 1000, // Test directory testDir: path.join(__dirname, "e2e"), // If a test fails, retry it additional 2 times retries: 0, // Artifacts folder where screenshots, videos, and traces are stored. outputDir: "test-results/", // Run your local dev server before starting the tests: // https://playwright.dev/docs/test-advanced#launching-a-development-web-server-during-the-tests webServer: { command: "pnpm run dev", url: baseURL, timeout: 120 * 1000, reuseExistingServer: !process.env.CI }, use: { // Use baseURL so to make navigations relative. // More information: https://playwright.dev/docs/api/class-testoptions#test-options-base-url baseURL, // Retry a test if its failing with enabled tracing. This allows you to analyze the DOM, console logs, network traffic etc. // More information: https://playwright.dev/docs/trace-viewer trace: "retry-with-trace" // All available context options: https://playwright.dev/docs/api/class-browser#browser-new-context // contextOptions: { // ignoreHTTPSErrors: true, // }, }, projects: [ { name: "Desktop Chrome", use: { ...devices["Desktop Chrome"] } } // { // name: 'Desktop Firefox', // use: { // ...devices['Desktop Firefox'], // }, // }, // { // name: 'Desktop Safari', // use: { // ...devices['Desktop Safari'], // }, // }, // Test against mobile viewports. // { // name: "Mobile Chrome", // use: { // ...devices["Pixel 5"] // } // }, // { // name: "Mobile Safari", // use: devices["iPhone 12"] // } ] }); ``` 1. 基本使用 1. 加载所有页面是否正常 svg 过渡变换 并且包含预览 codebox ```typescript import { RouteInfo, scanRoutes } from "@/scripts/scanRoutes"; import { expect, test } from "@playwright/test"; import { filter } from "lodash"; const routes = scanRoutes(); const isStaticRoute = filter(routes, (r: RouteInfo) => !r.isDynamic); const generatePageTestByRoute = (route: RouteInfo) => { test.describe(`<${route.path}> 加载测试`, () => { test(`<${route.path}> 加载成功`, async ({ page }) => { // 检查页面响应状态码 const response = await page.goto(route.path); expect(response?.status()).toBe(200); }); if (route.path.includes("svgTransform")) { test(`<${route.path}> svg 过渡变换 并且包含预览 codebox`, async ({ page }) => { const response = await page.goto(route.path); expect(response?.status()).toBe(200); const htmlContent = await page.content(); const bol = htmlContent.includes("IconTransformProps"); expect(bol).toBe(true); }); } }); }; isStaticRoute.forEach(generatePageTestByRoute); ```
