Nuxt 开发指南

下载与资源

官网 |

中文文档 |

中文文档2 |

nuxt-modules |

naive ui |

Unocss |

windicss |

windicss github |

Nuxt 3.0 全栈开发视频 |

安装

"nuxt": ^4.0.3 nodejs: v22.17.1

js 复制代码
1. 直接到github下载模板, https://github.com/nuxt/starter/tree/v4

  


 - 也可以用npm安装

 npm create nuxt <project-name>

 eg:npm create nuxt nuxtstart

  


 - 更改配置: package.json ->  name

  


2. npm install --global yarn   //yarn --version  1.22.22

  yarn config set registry https://registry.npmmirror.com  切换到淘宝源

  yarn install

  


3. yarn dev

http://localhost:3000/

  


//vs code插件

Vetur   Vue 3 Snippets

Naive Ui Snippets   //代码提示

UnoCSS              //代码提示

错误排除

  • 更改配置(nuxt.config.ts)一般要重启。

  • 编译时出错:Named export 'VResizeObserver' not found, nuxt.config.ts 文件中添加如下代码:

build: { transpile: ['naive-ui', "vueuc"] },

  • MongoServerError:E11000 duplicate key error.... 这可能是数据表中的数据格式冲突导致的(有脏数据),删除整个表即可!

  • Naive UI中表格要有 block 属性,否则样式将变得怪异!<n-form block outline-none>

  • Error: [nuxt] A composable that requires access to the Nuxt instance was called outside of a plugin, Nuxt hook, Nuxt middleware, or Vue setup function.

  • useFetch的错误, let {data, error} = await useFetch(url) ????

// data.value == null 都会报错,如下:

// Uncaught (in promise) TypeError: Cannot read properties of null (reading 'scope')

  • Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client ???

  • devtools: { enabled: false }

  • server-api 在Nginx的https报务中无法开启,总有个错误 500 Server Error

全局错误处理

Nuxt是一个全栈框架,这意味着在不同的上下文中,有几种不可避免的用户运行时错误来源:

  1. Vue渲染生命周期中的错误(SSR + SPA)

  2. API或Nitro服务器生命周期中的错误

  3. 服务器和客户端启动错误(SSR + SPA)

js 复制代码
//全局错误页面  error.vue

<template>

  <div class="pt-[50px]">

    <n-result

        status="500"

        title="错误提示"

        :description="error.message"

    >

        <template #icon>

            <img src="~/assets/img/sad.png" alt="喵星">

        </template>

        <template #footer>

            <n-button @click="$router.back()" mr-4>返回上一页</n-button>

            <n-button @click="handleError">回到首页</n-button>

        </template>

    </n-result>

  </div>

</template>

  


<script setup>

import { NButton, NResult } from "naive-ui"

  


defineProps({

  error: Object

})

const handleError = () => clearError({ redirect: '/' })

</script>

渲染模式

浏览器和服务器都可以解释JavaScript代码,将Vue.js组件渲染成HTML元素。这个步骤被称为渲染。Nuxt支持客户端和通用呈现。

客户端渲染 - CSR, 例如 Vue.js、React

通用渲染 - SSR, 例如 Nuxt、Next.js

静态站点生成 - SSG;

混合渲染 - 混合呈现允许每个路由使用路由规则不同的缓存规则,并决定服务器应该如何响应给定URL上的新请求。例如 Nuxt、Next.js

边缘渲染 - edge-side rendering。

naive UI

官网 |

安装指南 |

安装指南2 |

naive-ui-nuxt |

js 复制代码
yarn add naive-ui   //^2.42.0  

  


1. nuxt.config.ts

build: { transpile: ['naive-ui', "vueuc"] },

  


2. app -> plugins -> naiveui.ts

import { setup } from '@css-render/vue3-ssr';

 

export default defineNuxtPlugin((nuxtApp) => {

    if (process.server && nuxtApp.ssrContext) {

        const { collect } = setup(nuxtApp.vueApp || {});

       

        // @ts-ignore

        const originalRender = nuxtApp.ssrContext.renderMeta?.bind(nuxtApp.ssrContext) || (() => ({}));

       

        nuxtApp.ssrContext.renderMeta = () => {

            // @ts-ignore

            const result = originalRender();

            // @ts-ignore

            const headTags = result?.headTags || "";

           

            return {

                headTags: headTags + collect()

            };

        };

    }

});

  


3. 按需引入

<template>

  <n-button>naive-ui</n-button>

</template>

  


<script setup>

  import { NButton } from 'naive-ui'

</script>

消息显示

脱离上下文的API

使用 createDiscreteApi 来创建一系列消息提示,它比较自由,推荐。

js 复制代码
import { createDiscreteApi } from "naive-ui"

// import { useMessage } from "naive-ui" useMessage要有专门设置,暂时不用

  


let { message } = createDiscreteApi(["message"])

message.error("注册失败!\n"+error.value.data, { duration: 5e3 })

// message.error("注册失败!\n"+error.value.data)

message.success("注册成功", { duration: 5e3 })

// message.success("注册成功")

message.warning("How many roads must a man walk down")

message.loading("If I were you, I will realize that I love you more than any other guy")

message.info(

  "I don't know why nobody told you how to unfold your love",

  {

    keepAliveOnHover: true

  }

)

 

// 注意: 如果已经导入 message 而没有使用可能会导致错误!尤其是下拉菜单!

Unocss安装

这是一个按需原子化 CSS 引擎,比起直接使用 TailwindCSS 更轻更快!

它的预设样式可以直接使用 TailwindCSS 和 Windicss 的样式。

js 复制代码
// yarn add @unocss/nuxt   //^0.57.4 "^0.58.8"

yarn add unocss @unocss/nuxt //^66.3.3

  


//配置模块,nuxt.config.ts:

  modules: [

    '@unocss/nuxt',

  ],

  


// 根目录创建 uno.config.ts

import {

  defineConfig, presetAttributify, presetIcons,

  presetTypography, presetUno, presetWebFonts,

  transformerDirectives, transformerVariantGroup

} from 'unocss'

  


export default defineConfig({

  rules: [

      //...

  ],

  shortcuts: [

    // ...

  ],

  theme: {

    colors: {

      // ...

    }

  },

  presets: [

    presetUno(),  //工具类预设

    presetAttributify(),  //属性化模式支持

    presetIcons(),    //icon支持

    presetTypography(),

    presetWebFonts({

      fonts: {

        // ...

      },

    }),

  ],

  transformers: [

    transformerDirectives(),

    transformerVariantGroup(),

  ],

})

Unocss基本语法

原子化css |

互动样式 |

样式参考 |

Tailwind CSS |

颜色样式 |

可以认为Unocss兼具了TailwindCSS 和 Windicss的优点,同时还具有自身的定制性和灵活性。按需引入,体积小,速度快。

css 复制代码
1. 基本使用,可以直接在class中定义。在Unocss省略class直接写样式也是可以的,而且有代码提示。

<div class="bg-blue-200"> Index Page</div>

<div bg-pink-200 text-cyan>我的国家</div>

  


2. 在style中定义

<div class="text"> Index Page</div>

<style>

.text {

  @apply text-xs text-red-300 hover:bg-zinc-900;

}

.btn {

  @apply text-red-700  border border-red-500 !rounded-full;  // rounded-full !important

}

</style>

  


3. 混合的写法也是可以的

.test {

  color: white;

  font-size: 2rem;

  @apply bg-blue;

}

  


3. 属性模式(Attributify Mode)

<button

  bg="blue-400 hover:blue-500 dark:blue-500 dark:hover:blue-600"

  text="sm white"

  font="mono light"

  p="y-2 x-4"

  border="2 rounded blue-200"

>

  Button

</button>

  


4. `uno.config.ts`中定义 rules 或 shortcuts

rules: [

  ['custom-rule', { color: 'red' }],

  ['main2', { margin: '0.25rem', padding: '2rem', color: 'red',

  'background-color': 'blue'}],

],

shortcuts: {

  // shortcuts to multiple utilities

  'custom-shortcut': 'text-lg text-orange hover:text-teal',

  'btn': 'py-2 px-4 font-semibold rounded-lg shadow-md',

  'btn-green': 'text-white bg-green-500 hover:bg-green-700',

  // single utility alias

  'red': 'text-red-100'

}

  


/* //常用语句 */

bg-purple-100  背景颜色

text-purple-100 文本颜色

text-center   水平对齐 可用于字体和按键的中心对齐

align-middle  垂直对齐

!w-[80%]    //width: 80% !important;

shadow-sm  盒子阴影

cursor-pointer 指针样式

outline-none 无边框

font-italic  斜体

  


hover:bg-purple-500 鼠标悬停时

focus:bg-purple-900  鼠标点击时

flex justify-center items-center  元素居中

flex justify-between  两端对齐

min-h-sceen / min-h-100vh 高度满屏

min-h-80vh 最小高度(可以延伸)

h-80vh / calc(100vh - 10px)

w-full / w-100vw 宽度满屏

  


w-[95%] lg:w-[85%] ~= w-95vw  lg:w-85vw

  


文字和水平线

<div flex justify-center items-center><p m-l-4 text-4>区块链</p> <hr style="border: 1px solid gray; width: 75%;"></div>

  


<div flex ><div ml-auto>右对齐</div></div>

<div flex ><div ml-auto px-2 hover:bg-red>X</div></div> //删除按键

m-auto / mx-auto 居中

ml-auto 右对齐

mr-auto 左对齐

px-4 / py-4  左右/上下两边内边距(padding: 1rem;) 其它写法:px-[20px]

pt-4  上部内边距1rem(padding-top: 1rem;)

mx-4 / my-4  左右/上下两边外边距

  


animate-fade-in  渐入

<div border-t-dashed border-1></div>  虚线分割线

  


/* flex布局,两端对齐 */

<div flex mb-2 justify-between>  

  <p mr-2>自定义角色</p>

  <div w-65vw lg:w-42vw> xxx </div>

</div>

  


 /* 响应式 */

根据常用的设备分辨率方案,默认内置了 5 个断点,sm、md、lg、xl、2xl :

断点前缀  最小宽度  CSS

sm  640px @media (min-width: 640px) { ... }

md  768px @media (min-width: 768px) { ... }

lg  1024px  @media (min-width: 1024px) { ... }

xl  1280px  @media (min-width: 1280px) { ... }

2xl 1536px  @media (min-width: 1536px) { ... }

  


/* 定位手机屏幕 */

这种方式最令人们惊讶的地方是,要为移动设备设计样式,您需要使用无前缀的功能类,而不是带 sm: 前缀的版本。不要将 sm: 理解为"在小屏幕上",而应将其视为"在小断点处"

请注意,我们不必为 sm 断点或 xl 断点指定背景色,您只需要指定一个功能类何时开始生效,而不是何时结束。

  


使用无前缀的功能类来定位移动设备,并在较大的断点处覆盖它们,下面是一些案例:

<div class="text-center sm:text-left"></div>  //在手机端居中,在屏幕变大时靠左

flex-1 flex justify-end lg:hidden  //手机端时显示,屏幕变大时隐藏

relative hidden lg:flex items-center ml-auto  //在电脑端的样式(在手机端时隐藏)

hidden lg:flex items-center px-4  //在电脑端的样式(在手机端时隐藏)

hidden lg:block ml-auto //在电脑端的样式(在手机端时隐藏)

ml-auto lg:hidden//在手机端时居中,电脑端隐藏

icones图标

官网 |

说明 |

html 复制代码
yarn add @iconify-json/mdi   //注意按需安装!  //1.2.3

//yarn add @iconify-json/vscode-icons  

  


<div class="i-mdi-github text-3xl" />

<div class="i-mdi-twitter" />

<div class="i-mdi-arrow-collapse-right" />

<div class="i-mdi-alpha-i-circle" />  //提示

<div class="i-mdi-chevron-down" /> //向下的

  
  


<!-- 手机端 -->

<div class="ml-auto mr-2 bold lg:hidden" @click="activate('top')">

    <div class="i-mdi-dots-vertical" text-2xl/>

</div>

  


<div class="i-mdi-chevron-double-right text-2xl"

  mt-20 block lg:hidden  

  text-gray-600

  @click="activate('left')"

/>

全局变量配置

js 复制代码
//app.config.ts

export default defineAppConfig({

    title: 'Hello Nuxt888',

    theme: {

      dark: true,

      colors: {

        primary: '#ff0000'

      }

    }

  })

 //app.vue

 {{ appConfig.title }}

 const appConfig = useAppConfig()

  


 //另外一个简洁的用法是写在composables中,export即可引用

export const title = "hello nuxt"

页面关键词配置

全局配置 |

配置 |

js 复制代码
//nuxt.config.ts

app: {

  head: {

      titleTemplate: "%s - 固定标题",

      title: "这是首页",

      charset: "utf-8",

      htmlAttrs: {

          lang: "zh-cn"

      },

      meta: [

          { name: "description", content: "首页描述" },

          { name: "keywords", content: "首页关键词" },

      ]

  }

},

  


//index.vue 对单独页面的设置,会覆盖全局配置

useHead({

  title:"首页index",

  meta:[

      { name:"description",content:"首页描述2" },

      { name:"keywords",content:"首页关键词2" },

  ],

})

```  

  


## 全局CSS

```js

// 方法一

1. assets -> main.css

2. nuxt.config.ts中配置:

  css: [

    "~/assets/main.css",

  ],

  


// 方法二

//在 app.vue 中引入样式。注意不是在<style>中!

<script setup>

import "~/assets/main.css";

</script>  

  


// 方法三

//或者直接在 `uno.config.ts`中定义 shortcuts,它也是相当于全局的

shortcuts: {

  // shortcuts to multiple utilities

  'containerX': 'w-[95%] mx-auto text-4.4 lg:w-[85%]'

  'btn': 'py-2 px-4 font-semibold rounded-lg shadow-md',

  'btn-green': 'text-white bg-green-500 hover:bg-green-700',

  // single utility alias

  'red': 'text-red-100'

}

全局js

引用本地js

Nuxt入门

js 复制代码
在Nuxt中引用第三方js等资源文件,可将文件放在/assets或/public目录下

区别

    /assets目录下的文件会被webpack编译

    /public目录下的文件不会被编译

第三方文件放置在/public目录下

    /public/videojs/video-js.css

    /public/videojs/video.js

    /public/videojs/videojs-contrib-hls.js

  


  head: {

    ...

    link: [

      ...

      { rel: 'stylesheet', href: '/videojs/video-js.css' }

    ],

    script: [

      { src: '/videojs/video.js' },

      { src: '/videojs/videojs-contrib-hls.js' }

    ]

  },

外部js和css的引入

js 复制代码
可组合函数: useHead, useHead只能与组件的setup和生命周期钩子一起使用

<script setup lang="ts">

  useHead({

  script: [

    { src: 'https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.0/jquery.min.js' }

  ]

  })

</script>

  


//这里如果需要将js放置body区域末尾,直接添加参数

script: [

  {

    src: 'https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.0/jquery.min.js',

    body:true

  }

]  

 

//引入外部css

<script setup lang="ts">

useHead({

  link: [

    {

      rel: 'preconnect',

      href: 'https://fonts.googleapis.com'

    },

    {

      rel: 'stylesheet',

      href: 'https://fonts.googleapis.com/css2?family=Roboto&display=swap',

      crossorigin: ''

    }

  ]

})

</script>

全局函数utils

Nuxt使用 utils/ 目录在整个应用程序中使用auto-imports自动导入辅助函数和其他实用程序!

js 复制代码
export function test2() {

    console.log(12793, "test.js2")

}

  


//也可以自定义导出目录

//nuxt.config.ts

imports: {

  dirs: ["apis"]

}

composables

在composables/目录中编写自己的自动导入可重用函数。但它只导入顶层函数,如有二级目录,则必须在config中配置!

js 复制代码
export const newFun = (i) => {

    return i+5

  }

  


//composables/gets/foo.js

imports: {

    dirs: ["composables/**"]

},  

```  

  


## 中间件

middleware 目录,Nuxt提供了一个可定制的路由中间件框架,可以在整个应用程序中使用,非常适合在导航到特定路由之前提取想要运行的代码。

```js

//middleware -> search.js

export default defineNuxtRouteMiddleware((to,from)=>{

    const { type,page } = to.params

    const { keyword } = to.query

  


    if(!keyword){

        return abortNavigation("搜索关键词不能为空")

    }

  


    if(!(["course","column"].includes(type)) || isNaN(+page)){

        return abortNavigation("页面不存在")

    }

})

//index.vue

definePageMeta({

    middleware:["search"]

})

  


//eg2

//中间件可以获取目标路由 to 和来源路由 from,还有两个很常用的工具方法:

abortNavigation(error):跳过,留在 from;

navigateTo(route):指定跳转目标。

export default defineNuxtRouteMiddleware((to, from) => {

  if (to.params.id === '1') {

    return abortNavigation()

  }

  return navigateTo('/')

})

  


//全局中间件

//命名时带上 global

middleware -> search.global.js

  


另外,中间件全名不能使用小驼峰的方法,要使用 `-` !

插件

js 复制代码
1. plugins/目录 -> myPlugin.ts

2. myPlugin.ts

export default defineNuxtPlugin(nuxtApp => {

  // Doing something with nuxtApp

})

export default defineNuxtPlugin(() => {

  return {

    provide: {

      hello: (msg: string) => `Hello ${msg}!`

    }

  }

})

<template>

  <div>

    {{ $hello('world') }}

  </div>

</template>

<script setup lang="ts">

// alternatively, you can also use it here

const { $hello } = useNuxtApp()

</script>

  


//Nuxt 上下文:NuxtApp

我们看到定义插件时,可以获取到 nuxtApp 对象,该对象是 NuxtApp 的实例,实际上是 Nuxt 提供的运行时上下文,可以同时用于客户端和服务端,并能帮我们访问 Vue实例、运行时钩子、运行时配置的变量、内部状态等。

  


我们需要了解 nuxtApp 一些重要的方法和属性以便使用:

    provide (name, value):定义全局变量和方法;

    hook(name, cb):定义 nuxt 钩子函数;

    vueApp:获取 vue 实例;

    ssrContext:服务端渲染时的上下文;

    payload:从服务端到客户端传递的数据和状态;

    isHydrating:用于检测是否正在客户端注水过程中。

layouts布局

js 复制代码
layouts -> default.vue, <slot/>可以将其它页面插入

eg:

<template>

    <div class="body">

        <NavBar/>      

        <main class="containerX">

            <slot/>

        </main>

    </div>

</template>

  


//指定页面布局

layouts -> login.vue

//pages -> login.vue

definePageMeta({

    layout:"login",

    middleware:["only-visitor"]

})

另外,命名不能使用小驼峰的方法,要使用 `-` !

Grid栅格系统

NaiveUI的Grid栅格系统,可以布局响应式的样式。

cols number | ResponsiveDescription 24 一般是分成24列,每列用 span 表示占比

html 复制代码
<n-grid x-gap="12" :cols="4">

  <n-gi>

    <div class="light-green" />

  </n-gi>

  <n-gi>

    <div class="green" />

  </n-gi>

  <n-gi>

    <div class="light-green" />

  </n-gi>

  <n-gi>

    <div class="green" />

  </n-gi>

</n-grid>

  


//响应式布局 分成4列,占比是手机端时 0:4, 电脑端时 1:3

<n-divider>Screen 响应式</n-divider>

<n-grid x-gap="20" cols="4" item-responsive responsive="screen">

  <n-grid-item span="0 l:1">

    <div class="light-green">

      m 以下:不显示<br>

      m 到 l:占据空间 1<br>

      l 以上:占据空间 2

    </div>

  </n-grid-item>

  <n-grid-item span="4 l:3">

    <div class="green">

      2

    </div>

  </n-grid-item>

</n-grid>

页面和路由

Nuxt会自动使用Vue Router在底层创建路由,页面的名字就是路由地址。

新建 pages 文件夹,在此文件夹内创建页面。

js 复制代码
pages/about.vue ->  localhost:3000/about

  


默认情况下`pages/index.vue`是根路径 /

  


//二级路由: 页面文件在一个目录下

pages/user/info.vue ->  localhost:3000/user/info

  


//二级路由中保留父组件内容

1. 在同级目录下创建同名的 vue 文件,eg: pages/user + pages/user.vue

2. `user.vue`中加入`<NuxtPage/>`即可索引到下一级的页面(page)文件

  


//组件导入则用slot

<slot/>

动态路由

如果您在文件名中使用方括号 [ ],它将被转换为 动态路由 参数。您可以在文件名或目录中混合使用多个参数。

如果您希望参数是 可选的,必须使用双括号 [[]] 括起来,例如 ~/pages/[[slug]]/index.vue 或 ~/pages/[[slug]].vue 将匹配 / 和 /test。

目录结构

-| pages/

---| index.vue

---| users-[group]/

-----| [id].vue

在上述示例中,您可以通过 $route 对象在组件中访问 group/id:

pages/users-[group]/[id].vue

js 复制代码
pages/users-[group]/[id].vue

<template>

  <p>{{ $route.params.group }} - {{ $route.params.id }}</p>

</template>

  


访问路由可以使用全局的 useRoute 函数,它与 Options API 中的 this.$route 功能相同。

const route = useRoute()

const group = route.params.group

const id = route.params.id

获取当前路径

useRoute 返回当前路由, 必须在setup函数、插件或路由中间件中调用。

在Vue组件的模板中,可以使用$route访问路由。

js 复制代码
const route = useRoute()  // == $route

console.log(route.path) // '/about'

  


//获取路径id

$route.params.id

  


//除了动态参数和查询参数, useRoute() 还提供了以下与当前路由相关的计算引用:

fullPath: 与当前路由关联的编码URL,包含path、query和hash

hash: 以#开头的URL的解码hash部分

matched: 与当前路由位置相匹配的归一化路由数组

meta: 附加到记录的自定义数据

name: 路由记录的唯一名称

path: URL的编码路径名部分

redirectedFrom: 在到达当前路由位置之前试图访问的路由位置

useRouter

useRouter 返回路由器实例,必须在设置函数、插件或路由中间件中调用。

在Vue组件的模板中,你可以使用$router 来访问路由器。

js 复制代码
const router = useRouter()

  


//几个重要参数:  

url: http://localhost:3000/user/userinfo

router.name: 'user-userinfo'

router.fullPath: '/user/userinfo'

router.path: '/user/userinfo'

  


//几个重要的方法

router.back()

//$router.back() 返回上一页

router.forward()

router.go()

router.push({ path: "/home" })

router.replace({ hash: "#bio" })

back: 如果可能的话,回溯历史,和router.go(-1)一样。

forward: 如果可能的话,和 router.go(1)一样,在历史上前进。

go: 在历史中向前或向后移动,而不受 router.back() 和 router.forward()中强制执行的等级限制。

  
  


//刷新页面

let router = useRouter()

router.go(0)
js 复制代码
// 将 'to' 作为字符串传递

await navigateTo('/search')

  


// ... 或者作为路由对象

await navigateTo({ path: '/search' })

  


// 动态路由

await navigateTo({ path: '/post/'+permlink})

//post动态页面接受方法

const route = useRoute()

cosnt permlink = route.params.permlink

  


// ... 或者作为带有查询参数的路由对象

await navigateTo({

  path: '/search',

  query: {

    page: 1,

    sort: 'asc'

  }

})

//serarch页面接受方法

const route = useRoute()

const query = route.query //用此方法接受参数

生命周期函数

手册

由于 Nuxt 整合了 Vue、Nitro 前后端两个运行时,再加上它自身的创建过程,因此框架生命周期钩子分为三类:

Nuxt 钩子;

Vue App 钩子;

Nitro App 钩子。

js 复制代码
const nuxtApp = useNuxtApp()

eg:

nuxtApp.hook("page:start",(e)=>{

        bar.value?.start()

        // console.log("page:start");

    })

nuxtApp.hook("page:finish", () => {

  window.scrollTo(0, 0)

})

Vue的钩子函数

参考

js 复制代码
//有时无法自动运行

onMounted(() => {

    console.log(699, "moundted")

    function x (){

      console.log(722, "moundted")

    }

    x()

})

  


//beforeMount mounted

翻页时默认回到顶部

js 复制代码
//app -> router.options.js

export default {

    scrollBehavior (to, from, savedPosition) {

        // 在按下 后退/前进 按钮时。就会像浏览器的原生表现那样

        if(savedPosition){

            return savedPosition

        }

  


        return {

            top:0

        }

    }

}

组件

js 复制代码
components -> NaveBar.vue,

<NaveBar /> 可直接导入无需引入

嵌套组件

js 复制代码
components -> Ui -> Menu.vue

<UiMenu>

  test

</UiMenu>

// 或者这样写:

<ui-menu>

  test

</ui-menu>

子组件接受父组件参数

defineProps来接受父组件传来的值

js 复制代码
//index.vue

<Menu :active = "active"></Menu>

<NuxtPage :collection="defaultCollection" />

  


//Menu.vue 接受参数

defineProps({

  active:{

      type:Boolean,

      default:false

  }

})

  


const props = defineProps({

    title: String,

    info: String,

    author: String,

  })

// 等价于以 字符串数组声明 props

//const props = defineProps(['title', 'info', 'author']);

// 如果在setup中使用则直接 props.title

子组件向父组件传递参数或方法

defineExpose用法

defineExpose来导出自身的值或函数

js 复制代码
//Roles.vue

<script setup>

let sonmsg = '这是子数据'

const open = () => { console.log("hello")}

//把数据导出

defineExpose({

  sonmsg,

  open

})

</script>

  


//index.vue

<template>

<div>

  <Roles ref="RoleRef"></Roles>

</div>

</template>

<script setup>

let RoleRef = ref(null)  //数据挂载到RoleRef上

//在函数中使用

const onSubmit = () => {

  RoleRef.value.open()

  console.log(566, "imgmodel", RoleRef.value.sonmsg)

}

</script>

获取数据

参考

获取数据推荐useFetchfetch这两个函数就可以。

js 复制代码
useFetch

//express

res.status(200).send({

  bot: "hello world"

})

//前端    

const url = "http://localhost:6200/test"

const fetchConfig = {

  method: 'POST',

  headers: {

    'Content-Type': 'application/json'

  },

  body: {

    prompt: "hello",

    temperature: 0.3

  }

}

const { data, pending, error, refresh } = await useFetch(url, fetchConfig)

// console.log(866, data.value.bot)

//参数:

baseURL?: string  //可以配置公共部分的url,再和url拼接

lazy?: boolean  //和pending 一起使用。为true时,先加载页面的pending,得到数据后再展示

transform?: (input: DataT) => DataT // 处理返回的数据

  


//如果有两个以上useFetch,得到的data会重名,可以另起名字

let { data:dataX,  error:errorX } = await getHttp('/pay/order', token.value)

  


// 错误处理 error

//express

return res.status(422).send("没有授权!")

  


let { data, error } = await useFetch(url, optionX)

if(error.value) {

  console.log(444, error.value)

  message.error("失败!\n"+error.value.data, { duration: 5e3 })

  return

}

更改查询结果的名称

transform 函数来对结果拦截,并做出一些处理逻辑。

js 复制代码
const { data: mountains } = await useFetch('/api/mountains', {

  transform: (mountains) => {

    return mountains.map(mountain => ({ title: mountain.title, description: mountain.description }))

  }

})

  


// 只选择模板中使用的字段

const { data: mountain } = await useFetch('/api/mountains/everest', {

  pick: ['title', 'description']

})

<template>

  <h1>{{ mountain.title }}</h1>

  <p>{{ mountain.description }}</p>

</template>

  
  


const { data, pending, error, refresh } = await useFetch("/api/getonepost", config, {

  transform: (res) => {

    return res.result

  }

})

$fetch

$fetch是与useFetch类似的封装函数,不过$fetch使用却是大大不同。

useFetch会和表格关联,造成一些不必要的http访问,$fetch则不会。

$fetch不能直接捕获到错误,只能通过 try catch

js 复制代码
const baseURL = "http://localhost:6200"  

const option = {

  method: "POST",

  headers: {

    'Content-Type': 'application/json'

  },

  baseURL

}

const loginApi2 = async (url, body) => {

  let obj = {

      ...option,

      body

  }

  let res

  try {

    res = await $fetch(url, obj)

    console.log(566, "login api2", res)

  } catch (error) {

    res = error

    console.log(444, "login api2 error", error)

  }

  return res

}

fetch

fetch是浏览器原生的方法,非常简洁实用。像streaming(推流)也只能用 fetch 来实现,其它方法实现不了!

js 复制代码
//注意:一定要加headers, 否则无法传值!

const fetchConfig = {

  method: 'POST',

  headers: {

      'Content-Type': 'application/json',

  },

  body: JSON.stringify({

      prompt: prompt,

      temperature: 0.3

  })

  }

const s = await fetch(url, fetchConfig)

console.log(559, s)

// 它得到的结果是直接传送回来的对象

  


eg:

let option = {

  method: "POST",

  headers: {

    'Content-Type': 'application/json',

    'Authorization': 'Bearer ' + captchaToken.value

  },

  body: JSON.stringify(form)

}

let response = await fetch(baseURL+'/userapi/register', option)

if(response.ok){

  let resX = await response.json()

  message.success("注册成功", { duration: 5e3 })

} else {

  let err = await response.text()

  message.error("错误!\n"+err, { duration: 5e3 })

}

useLazyFetch

useLazyFetch 为useFetch提供了一个包装器,通过将lazy选项设置为true,在处理程序解析之前触发导航。

默认情况下,useFetch 会阻塞导航,直到它的async处理程序被解决。useLazyFetch则不会阻塞,实时渲染数据!它将有更好的用户体验。

useLazyFetch 与 useFetch 具有相同的签名。

js 复制代码
<template>

  <div v-if="pending">

    Loading ...

  </div>

  <div v-else>

    <div v-for="post in posts">

      <!-- do something -->

    </div>

  </div>

</template>

  


<script setup>

/* Navigation will occur before fetching is complete.

  Handle pending and error states directly within your component's template

*/

const { pending, data: posts } = useLazyFetch('/api/posts')

watch(posts, (newPosts) => {

  // Because posts starts out null, you won't have access

  // to its contents immediately, but you can watch it.

})

</script>

只客户端渲染

js 复制代码
//可以用以下测试是否在客户端:

console.log(12, document.cookie)

console.log(33, window)

注意: setup是在服务端,其中的函数却基本都在客户端渲染!

  


//组件中客户端渲染

<ClientOnly> component 只在客户端呈现它的slot。若只在客户端导入组件,请在客户端插件中注册该组件。

<ClientOnly fallback-tag="span" fallback="Loading comments...">

  客户端渲染

</ClientOnly>

  


//js中的使用

if (process.server){

  //服务器端

}

process.client  //客户端

  


//在路由中设为单页面渲染,也就是关闭ssr

//nuxt.config.ts 如下设置,则gpt页面则只客户端渲染

routeRules: {

    '/gpt': { ssr: false },    

},

状态管理

useState用于创建响应式的且服务端友好的跨组件状态,类似于Vue中的state的功能。

简单的需求只用useState就可以,复杂的话可以结合pinia

js 复制代码
const counter = useState('counter', () => Math.round(Math.random() * 1000))

counter++

counter.value = 20 //重新赋值

  


const user = useState('user', () => {

  return {

    token: token.value,

    user: userObj.value

 }

})

  


//composables/useState.ts

export const useCounter = () => useState<number>('counter', () => 0)

export const useColor = () => useState<string>('color', () => 'pink')

  


<script setup>

const color = useColor() // Same as useState('color')

</script>

  


// 初始化状态

大多数情况下,你可能希望使用异步解析的数据来初始化状态。你可以使用 app.vue 组件和 callOnce 工具函数来实现这一点。

<script setup lang="ts">

const websiteConfig = useState('config')

  


await callOnce(async () => {

  websiteConfig.value = await $fetch('https://my-cms.com/api/website-config')

})

</script>

Pinia

文档

管理全局状态的模块,可以和useState一起使用。

js 复制代码
yarn add pinia @pinia/nuxt

  


modules: [

    // ...

    '@pinia/nuxt',

  ],

useCookie

在你的页面中,组件和插件你可以使用useCookie,一个SSR友好的组合来读写cookies。

js 复制代码
const cookie = useCookie(name, options)

eg:

const counter = useCookie('counter', { maxAge: 60 }) //60秒有效期

counter.value = {

  name: "hello",

  age: 37

}

const token = useCookie('token', {maxAge: 60 * 5})

token.value = "hello world"

  


let user = useCookie('users', {maxAge: 60 * 60 * 24 * 30})  //30天 本地测试会有些问题

user.value = "hello ddskljdsfklj"

console.log(257, "user", user, 886, user.value)

  


// 获取

const s = useCookie('counter')

s.value

  


const balance2 = useCookie('balance', {maxAge: 60 * 60, SameSite: "none"})

  


const hasConsent = useCookie('dialog', {

  sameSite: 'strict',

  default: () => 'no',

  maxAge: 2592000, // 30 days in seconds

});

  


//删除则设值为null即可

s.value = null

const token = useCookie("token")

token.value = null

  


const s = useCookie('counter')

const removeCookie = () => {

    s.value = null

}

  


// 将用户登录成功返回的token存储在cookie当中,用户登录成功的标识

const token = useCookie("token")

token.value = data.value.token

const user = useUser()

user.value = data.value

  


//判断token是否有值

const token = useCookie("token")

token.value == undefined  //无值

或者 :token.value == null

简写: !token.value  //无值则为true

  


//Server端的使用

// server/api/counter.ts

export default defineEventHandler(event => {

  // Read counter cookie

  let counter = getCookie(event, 'counter') || 0

  


  // Increase counter cookie by 1

  setCookie(event, 'counter', ++counter)

  


  // Send JSON response

  return { counter }

})

Layers

能够复用之前项目中的配置、已存在的组件、工具方法等等。Nuxt提供的 layers 特性能使我们非常容易地创建和维护这样的项目基础结构。

以下情况下比较适合使用 layers:

  • 共享可重用配置项;

  • 使用 components 目录共享组件库;

  • 使用 composables 和 utils 目录共享工具函数;

  • 创建 Nuxt 主题;

  • 创建模块预设;

  • 共享标准初始化步骤。

Server

文档

服务器 Server

Nuxt具备的类似于express的功能,具有接受和响应http的能力。

Nuxt自动扫描~/server/api, ~/server/routes, 和 ~/server/middleware目录中的文件,以注册具有HMR支持的API和服务器处理程序。

每个文件都应该导出一个用defineEventHandler()定义的默认函数。

处理程序可以直接返回JSON数据,一个Promise或使用event.node.res.end()发送响应。

js 复制代码
//创建一个新文件server/api/hello.js:

//server/api/hello.js

export default defineEventHandler((event) => {

  return {

    api: 'works'

  }

})

//使用await $fetch('/api/hello')通用地调用这个API

  


eg2:

// server/routes/test.post.js  客户端必须使用POST方法

export default defineEventHandler(async (event) => {

  //获取客户端的body

  const body = await readBody(event)  

  console.log(695,"server body", body)

  return {

    hello: 'world'

  }

})

//客户端调用

const config = {

  method: 'POST',

  headers: {

      'Content-Type': 'application/json'

  },

  body: {

    content: "我是什么",

    temperature: 0.3

  }

}

const { data, pending, error, refresh } = await useFetch("/test", config)

  


// nuxt.config.ts设置

routeRules: {

  // 为 SEO 目的在构建时生成

  '/': { prerender: true },

  // 缓存 5分钟  好像在访问上出问题! 用 useState

  '/api/*': { cache: { maxAge: 60 * 5 } }

},

  


// 抛出错误

throw createError({

  statusCode: 400,

  statusMessage: "This user does not exist.",  //此处只能用英文,中文无法传输!

})

  


// String error where `statusCode` defaults to `500`

throw createError("An error occurred")

服务端工具函数

server/utils 目录中的工具函数,会自动导入

若用默认导出 export default ,则自动导入的函数名为文件名

若用具名导出 export function useSum ,则自动导入的函数名为函数定义的名称。

js 复制代码
// server/utils/sum.ts

export function useSum(a: number, b: number) {

  return a + b;

}

  


//在接口文件中直接使用即可

// routes/index.ts

export default defineEventHandler(() => {

  const sum = useSum(1, 2) // auto-imported

  return { sum }

})

打包

  • SSR

默认情况下,直接执行(nuxt build) yarn build。代码会被打包到.output目录,打包产物分为 public 和 server 两部分。入口为 index.mjs,可以使用 node 或 pm2 等进程管理工具启动服务, node ./.output/server/index.mjs,也可以配合nuxt preview启动预览服务。

js 复制代码
  //默认情况下nuxt是不会将环境变量打包进去的,需要在package.json中配置

  "build": "source .env && nuxt build",

  


  启动 node.js 服务

  `node .output/server/index.mjs`

  `node aijoe_v5/server/index.mjs`

  


  //ecosystem.config.js

  module.exports = {

    apps: [

      {

        name: 'czblog',

        port: '8080',

        exec_mode: 'cluster',

        instances: 'max',

        script: './.output/server/index.mjs'

      }

    ]

  }

  pm2 start ecosystem.config.js
  • SPA

ssr:false + nuxt generate。产物只有 .output/public 中的静态文件,发布 .output/public(还有 dist) 即可。但是 SPA 需要在运行时访问接口获取数据,因此仍然需要提供接口服务才能正常显示页面。

配置 ssr: false,然后执行 yarn generate

js 复制代码
export default defineNuxtConfig({

  ssr: false,

})
  • SSG

nuxt generate。产物只有 .output/public 中的静态文件,发布 .output/public 即可。这种方式会在创建时生成页面内容,因此只需要提供静态服务即可预览。

  • 其他服务:presets,可用于其他非 node 运行时打包,例如 deno,serverless,edge worker 等。产物根据预设不同会有不同,部署需要按照对应的平台进行操作。

启动端口

js 复制代码
//开发环境

//package.json

"dev": "nuxt dev --port 3200"

// 或是这样:

"config": {  // here are the changes

    "nuxt": {

        "host": "0.0.0.0",

        "port": "3333"

    }

}

//或者在 nuxt.config.ts 中设置 只在开发中有效

devServer: {

  port: 9685

}

//或者,在 .env 中设置 `PORT = 9685` 即可!  

这样访问:

 if(process.server){

  console.log(566, process.env.PORT)

 }

  


//设置服务器端口 port, 找到源码,直接修改即可!

源码: .output/server/index.mjs

//const port = destr(process.env.NITRO_PORT || process.env.PORT) || 3e3;

const port = 5896

数据推流

经测试,只有浏览器端的 fetch 才有 streaming 的能力,其它方法不行!

useFetch, $fetch都不行!

js 复制代码
const url = "http://localhost:6200/gptstreaming"

const prompt = "天空为什么是蓝色的?"

let query = [{role: "user", content: prompt}]

let dataObj = {

  method: 'POST',

  headers: {

      'Content-Type': 'application/json',

  },

  body: JSON.stringify({

      query: query,

  })

}

const response = await fetch(url, dataObj)

if (response.ok) {  

  let i = 0

  let getStream = function (reader) {

      return reader.read().then(function (result) {

        // 如果数据已经读取完毕,直接返回

        if (result.done) {

          console.log(889, "result done")

          return

        }

        // 取出本段数据(二进制格式)

        let chunk = result.value

        console.log(226, chunk, typeof chunk)  //unit8array object

        let text = utf8ArrayToStr(chunk) //数据解析

        // 将本段数据追加到网页之中

        messageDiv.innerHTML += text

        // 递归处理下一段数据

        return getStream(reader)

      })

  }

  getStream(response.body.getReader())

}

加密

js 复制代码
npm install bcrypt --save

// npm i node-gyp -g  如果node-pre-gyp ERR!

// npm install bcrypt --unsafe-perm --save

  


import bcrypt from 'bcrypt'

const saltRounds = 10

  


//加密

set(val) {

  let salt = bcrypt.genSaltSync(saltRounds)

  let hash = bcrypt.hashSync(val, salt)

  return hash

}

  


// 验证密码 为true则正确

const isPasswordValid = bcrypt.compareSync(

  req.body.password,

  user.password

)

jwt验证

Json Web Token (JWT) 是一种开放标准 (RFC 7519),它定义了一种紧凑且自包含的方式,用于在各方之间安全地传输信息作为 JSON 对象。此信息可以进行验证和信任,因为它是经过数字签名的。可以使用密钥(使用 HMAC 算法)或使用 RSA 或 ECDSA 对公钥/私钥对对 JWT 进行签名。

可以认为JWT是用户认证的一种协议,可以理解为服务端颁发给客户端的一张身份证,拿到身份证后此后客户端进行接口访问时携带身份证,服务端通过验证以允许客户端访问服务器资源,每一项的含义:

  1. Json表示令牌的原始值是一个Json格式的数据

  2. web表示是在互联网传播

  3. token表示令牌

参考 |

参考2 |

参考3

js 复制代码
npm install jsonwebtoken --save

// yarn add jsonwebtoken

  


import jwt from 'jsonwebtoken'

  


const SECRET = "fdfh85JYxxxxx"

// 生成token

const token = jwt.sign({

  id: String(user._id),

}, SECRET)

  


//时限

let token =  jwt.sign(data, SECRET, {

  expiresIn:  60 * 60 * 24 * 3 // 以s作为单位(目前设置的过期时间为3天)

});

  


//验证

const rawToken = String(req.headers.authorization).split(' ').pop()

const tokenData = jwt.verify(rawToken, SECRET)

// console.log(666, tokenData)  //{ id: 'dddfhxxx', iat: 1684119284, exp: 1684119304 }

  


// 获取用户id

const id = tokenData.id

  


// Authorization: Bearer

const token = useCookie('token')

headers: {

    'Authorization': 'Bearer ' + token.value,

    'Content-Type': 'application/json'

  }

  


//token token过期的错误会在 catch 捕获 `jwt expired`

try{

  // 获取客户端请求头的token

  const rawToken = String(req.headers.authorization).split(' ').pop()

  if(rawToken == "undefined"){

    return res.status(422).send("您尚未登录!")

  }

  const tokenData = jwt.verify(rawToken, SECRET)

  // 获取用户id

  const id = tokenData.id

  let user = await User.findById(id)

  if(user){

    req.user_id = id

    req.user = user

    next()

  } else {

    console.error(111)

    res.status(422).send("无效授权!")

  }

} catch (error) {

  console.error(112,"token auth", error)

  res.status(500).send('Something went wrong')

}

获取用户IP地址

js 复制代码
async function smsGet(){

 let ip = req.headers['x-forwarded-for'] ||

        req.ip ||

        req.connection.remoteAddress ||

        req.socket.remoteAddress ||

        req.connection.socket.remoteAddress || '';

 if(ip.split(',').length>0){

    ip = ip.split(',')[0]

 }

 ip = ip.substr(ip.lastIndexOf(':')+1,ip.length);

  


console.log(123, ip)

}

// smsGet()

图形验证码

文档 |

node生成验证码图片

js 复制代码
npm install svg-captcha --save

  


const svgCaptcha = require('svg-captcha')

  


const codeConfig = {

  size: 6,// 验证码长度

  ignoreChars: '0o1i', // 验证码字符中排除 0o1i

  noise: 2, // 干扰线条的数量

  fontSize:42,

  color:true,//开启文字颜色

  background:"#cc9966",//背景色

  width:150,

  height: 44

}

const captcha = svgCaptcha.create(codeConfig)

//{text:jjkjk, data: dddd}

  


//图形验证码与jwt结合

1. 生成验证码后,将`text`用 jwt生成token, 和 data一起发送给前端。

2. 服务器端接受到用户发送回的 text_user和 token。

3. 服务器解密token中的`text`和用户的text_user比较,一致则正解,反之则错误。

  


//图形验证码与数据库结合

和jwt类似,生成`text`存在数据库, data发给前端。用户填好验证码后提交到数据库后比对。

本地存值localStorage

js 复制代码
// 用localStorage来实现一个预设值

1. 先从localStorage读取,有值则赋给它设初值

const defaultRole = ref(null)

if(process.client){

  let latest = localStorage.getItem("defaultRole")

  console.log(566, "latest", latest)

  if(latest != null){

    defaultRole.value = latest

    console.log(599, "latest")

  }

}  

2. //设置或更新 如果是空值 或是与前值不同,都重新赋值

let latest = localStorage.getItem("defaultRole")

if(latest == null || role != latest){

   localStorage.setItem('defaultRole', role)

   console.log(899, "set lastname")

}

删除div

js删除dom节点或div

js 复制代码
<div id="img1">

  图片1

<input  type="button" onclick="del('img1')" value="删除元素"/>

</div>

  


<script>

function del(divId){

  document.getElementById(divId).remove()

}

</script>

模板字符串语法

字符串的扩展

模板字符串

ES6模板字符串

字符串模板拼接的点击事件

模板字面量是用反引号(`)分隔的字面量,允许多行字符串、带嵌入表达式的字符串插值和一种叫带标签的模板的特殊结构。

js 复制代码
`string text`

  


`string text line 1

 string text line 2`

  


`string text ${expression} string text`

`string text ${fn()} string text`

  


//可以单行,多行,嵌入变量和函数都是可以的。还可以生成div结构

// 模板字符串生成dom元素

const loadingStripe = (uniqueId) => {

    return (

        `<span style='font-size: 20px;color: #191970;' id=${uniqueId}></span>`

    )

}

  


//如果要嵌入点击事件,就要相对麻烦一些

// 需要与onclick结合,再将这个函数挂载到window上

const itemStripe = (username, email, created, uniqueId) => {

  return (

    `

    <div border-dashed border-1 text-3.2 mb-2 id=${uniqueId}>

      <div flex ><div ml-auto px-2 hover:bg-red onclick="deleteUser('${userId}')">X</div></div>

      <div>名字: ${username}</div>  

      <div>email: ${email}</div>

      <div>时间: ${TimesToLocal(created)}</div>

    </div>

    `

  )

}

if(process.client){

  window.deleteUser = deleteUser

}

const deleteUser = async (userId) => {

  let body = {

    id: userId

  }

  let { data,  error} = await postHttp('/deluser', body, 'he')

  if(error.value) {

    message.error("删除失败!\n"+error.value.data, { duration: 5e3 })

    return

  }

  // document.getElementById(id).remove()

  message.success("删除成功!", { duration: 5e3 })

}

//TimesToLocal是全局的方法,时间转换

实时渲染div

html 复制代码
<div v-for="(item,index) in history" :key="index"

  :id=item._id border-dashed border-1 text-3.2 mb-2

  >

    <div flex ><div ml-auto px-1 hover:bg-red @click="delX(item._id)">X</div></div>

    <div>名字: {{item.username}}</div>  

    <div>email: {{item.email}}</div>

</div>

<div text-center><n-button @click="more" v-if="moreFlag" strong secondary>更多</n-button></div>

//js

//把取得的值遍历,塞入temp, 再赋给history。history是响应式的,div就会实时渲染出来!

let history = ref(null)

let temp = []

for (let i in data.value.payhistory) {

    temp.push(data.value.payhistory[i])

  }

history.value = temp

  


//另一种方法

//直接使用document来操作DOM

<div id="item_container"></div>

//js

const itemStripe = (userId, username, email,created, uniqueId) => {

  return (

    `  

    <div border-dashed border-1 text-3.2 mb-2 id=${uniqueId}>

      <div flex ><div ml-auto px-1 hover:bg-red onclick="deleteUser('${userId}','${uniqueId}')">X</div></div>

      <div>名字: ${username}</div>  

      <div>email: ${email}</div>

      <div>时间: ${TimesToLocal(created)}</div>

    </div>

    `

  )

}

let itemContainer = document.querySelector('#item_container')

for (let i in data.value.payhistory) {

  console.log(56, data.value.payhistory[i])

  let userId = data.value.payhistory[i]._id

  let username = data.value.payhistory[i].username

  let email = data.value.payhistory[i].email

  let created = data.value.payhistory[i].created

  let uniqueId = generateUniqueId()

  itemContainer.innerHTML += itemStripe(userId, username, email, created, uniqueId)

}

上传图片至IPFS

js 复制代码
//<!-- 上传图片 -->

<n-upload mt-2

  :default-upload="false"

  list-type="image-card"

  multiple

  @change="handleChangeX"

>

  上传参考图

</n-upload>

  


//js

const handleChangeX = (options) => {

  if(options.fileList.length == 0) {

    return

  }

  handleClick(options.fileList[0].file)

}

const handleClick = async (file) =>{

  let ref_img = await upImage(file)

  prompt.value += ref_img+' '

}

  


//composables

yarn add ipfs-http-client

  


import {create} from 'ipfs-http-client'

import { Buffer } from 'buffer'

  


export const readFile = (file) => {

  if (process.client){

    return new Promise(resolve => {

      let reader = new FileReader()

      reader.readAsArrayBuffer(file)

      reader.onload = (event) => {

        resolve(Buffer.from(event.target.result))

      }

    })

  }  

}

 

export const upImage = async(pos, file) => {

  if (process.client){

    const ipfs = create({ host: 'ipfs.example.com', port: '9059', protocol: 'https' })

    const ipfs_host = "https://example.com/ipfs/"

    let content = await readFile(file)

    let res = await ipfs.add(content)

    return ipfs_host+res.path

  }

}

显示图片

js 复制代码
 <n-image

    width=98%

    mt-3

    src="https://example.com/ipfs/QmQKptGbRRziEjxxxxxx"

    rounded-2 alt="AIJoe Meme"

 />

 //width=98% 可以使得图片自适应大小

  


<div class="contentimg">

  <img

    width=98%

    mt-3

    :src="takeImage(item.body)"

    rounded-2

  />

</div>

<style>

.contentimg {

  width: 98%;

  height: 28rem;

  overflow: hidden;

  margin: auto;

}

.contentimg img {

  width: 100%;

  height: 100%;

  object-fit: cover;

}

</style>

图片自适应宽度或高度

参考

js 复制代码
.container {

  width: 50%;

  height: 300px;

  overflow: hidden; /* 防止图片溢出容器 */

}

  


.container img {

  width: 100%;

  height: 100%;

  object-fit: cover;

}

  


上述代码中,我们定义了一个名为 container 的容器,并设置了宽度为 50% 和高度为 300px。接下来,我们通过 overflow 属性设置了容器的溢出属性为 hidden,以防止图片溢出容器。

  


在 img 标签中,我们使用了 width 和 height 属性将图片的大小设置为与容器相同,并且使用了 object-fit 属性将图片按比例缩放并居中显示。具体来说,object-fit 属性的值为 cover,意味着图片会拉伸或缩小以填充整个容器,并保持原始比例。

mavon-editor

文档

文档2

github

新版安装指南

js 复制代码
1.yarn add mavon-editor@next  //"^3.0.2"

// npm install mavon-editor@next --save

  


2.添加并编辑插件 ~/plugins/mavon-editor.client.js

import mavonEditor from 'mavon-editor'

import 'mavon-editor/dist/css/index.css'

  


export default defineNuxtPlugin((nuxtApp) => {

  nuxtApp.vueApp.use(mavonEditor)

})

  


3. 前端使用

<template>

  <ClientOnly>

    <mavon-editor v-model="content" :toolbars="toolbarsConfig"/>

  </ClientOnly>

</template>

  


<script setup>

const model = ref()

const toolbarsConfig = {

  // ... toolbars config

}

</script>

  


4. 上传图片

//增加两个参数: md、@imgAdd

<mavon-editor v-model="content" ref="md" @imgAdd="upImage" /> <br>

<script setup>

  let md = ref()

  const readFile = (file) => {

    return new Promise(resolve => {

      let reader = new FileReader()

      reader.readAsArrayBuffer(file)

      reader.onload = (event) => {

        resolve(Buffer.from(event.target.result))

      }

    })

   }

  const upImage = async(pos, file) => {

    //换成自己的ipfs主机,或是使用自己的上传逻辑

    const ipfs = create({ host: 'ipfs.example.io', port: '2885', protocol: 'https' })

    const ipfs_host = "https://ipfs.example.io/ipfs/"

    let content = await readFile(file)

    let res = await ipfs.add(content)

    md.value.$img2Url(pos, ipfs_host+res.path)

  }

</script>

  


5. 保存草稿箱

@save="saveMavon"  快捷键 ctrl+s

saveMavon(value,render){

  console.log("this is render"+render)  //html渲染的格式

  console.log("this is value"+value)   //原始格式, String

}

  
  


6. 解析markdown文本

<ClientOnly>  

  <mavon-editor

    v-model="post.result.body"

    :subfield="false"

    defaultOpen="preview"

    :toolbarsFlag="false"

    :editable="false"

    :scrollStyle = true

    :ishljs="true"

  ></mavon-editor>

</ClientOnly>

nuxt-tiptap-editor

文档 |

参考 |

参考 |

js 复制代码
yarn add nuxt-tiptap-editor

// npx nuxi@latest module add tiptap

  


//nuxt.config.ts设置:

//会自动添加

modules: ['nuxt-tiptap-editor']

//component -> TiptapEditor.vue

<template>

  <TiptapEditorContent :editor="editor" />

</template>

<script setup>

const editor = useEditor({

  content: "<p>I'm running Tiptap with Vue.js.</p>",

  extensions: [TiptapStarterKit],

})

const getContent = () => {

  console.log(111, unref(editor)) //是主要的使用方法集 unref(editor).commands

  console.log(9988, unref(editor).getHTML())

  return unref(editor).getHTML() //getText({ blockSeparator: "\n\n" }) getJSON()

}

onBeforeUnmount(() => {

  unref(editor).destroy()

})

//如果父组件调用的话就用 defineExpose的方法

defineExpose({

  getContent  

})

</script>

  


// 父组件 edit.vue

<TiptapEditor ref="contentRef"/>

  


const contentRef = ref()

const content = contentRef.value.getContent()  //即可得到编辑器中的数据

turndown

HTML转Markdown 能将复杂的HTML结构转化为简洁的Markdown,方便编辑和发布。

Turndown是轻量级的JavaScript库,适合前端或Node.js项目,易于集成到Web应用。

js 复制代码
yarn add turndown  //7.2.0

// npm install marked turndown

  


import TurndownService from 'turndown'

  


const turndownService = new TurndownService()

  


const html = '<h1>Hello, World!</h1><p>This is a <em>sample</em> HTML document.</p>'

const markdown = turndownService.turndown(html)

  


console.log(markdown)

  
  


const { marked } = require('marked')

const Turndown = require('turndown')

const turndownService = new Turndown()

 

const html = '<div><h1>Dynamic</h1><p>Content</p></div>'

const markdown = turndownService.turndown(html)

console.log(markdown)

Marked

参考

解析Markdown文本

js 复制代码
yarn add marked

// npm install marked

// const marked = require('marked');

import { marked } from 'marked'

  


const markdown = '# Hello, world!'

const options = {

  gfm: true,

  breaks: true,

  smartLists: true,

  highlight: function(code) {

    return require('highlight.js').highlightAuto(code).value

  }

}

  


const html = marked(markdown, options)

// const html = marked.parse('# Marked in Node.js\n\nRendered by **marked**.')

console.log(html)

  


<div v-html="mdshow(md)"></div>

const mdshow = (md) => marked(md, options)

实时监听和渲染

js 复制代码
//ref() 和 computed() 被自动导入

const count = ref(1)

const double = computed(() => count.value * 2)

  


//使用computed监听路由变化

const route = useRoute()

const pageKey = computed(()=>route.fullPath)

const activeName = computed(()=>route.name)

  
  


//使用watch监听路由变化

watch(route, (to) => {

  console.log('路由',to)

}, {flush: 'pre', immediate: true, deep: true})

  
  


// 使用watch监听数据变化,做出相应逻辑

watch(tagOption, () => {

  console.log(6688, 'tagOption2', tagOption.value[[tagOption.value.length - 1]])

  tagsX.value += tagOption.value[[tagOption.value.length - 1]]

}, {flush: 'pre', immediate: true, deep: true})

  
  


//使用watch监听路由变化,即刻获取数据,渲染页面

// 每当进入页面都会发生一次 watch

// 每当 keywords 数据发生一次变化,就会发生一次 watch

watch(()=>route.query.keywords,(newVal)=>{

    keywords.value = newVal

    getData()

}, {flush: 'pre', immediate: true, deep: true})

实时多选数据进输入框

使用watch实现实时数据更新

js 复制代码
//<!-- 标签选项 -->

<div m-y-3>选择标签或填写:

<n-checkbox-group v-model:value="tagOption">

  <n-space item-style="display: flex;">

    <n-checkbox value=" blockchain " label="区块链" />

    <n-checkbox value=" cryptocurrency " label="加密货币" />

    <n-checkbox value=" bitcoin " label="比特币" />

  </n-space>

</n-checkbox-group>

</div>

<div m-b-3>

<n-input

    v-model:value="tagsX"

    type="text"

    placeholder="输入英文标签,以逗号或空格分隔"

/>

</div>

<script setup>

const tagOption = ref(' ')

const tagsX = ref('cn  cn-reader ')

  


watch(tagOption, () => {

  tagsX.value += tagOption.value[[tagOption.value.length - 1]] //每次增加一条

}, {flush: 'pre', immediate: true, deep: true})

</script>
相关推荐
萌萌哒草头将军1 天前
Nuxt 4.1 版本新功能速览!支持 rolldown-vite 了!
vue.js·vite·nuxt.js
北鸟南游10 天前
用现有bootstrap的模板,改造成nuxt3项目
前端·bootstrap·nuxt.js
郭少14 天前
🔥 放弃 vw!我在官网大屏适配中踩了天坑,用 postcss-px-to-viewport-8-plugin 实现了 Rem 终极方案
vue.js·性能优化·nuxt.js
文艺理科生18 天前
Nuxt.js入门指南-Vue生态下的高效渲染技术
前端·vue.js·nuxt.js
草梅友仁18 天前
草梅 Auth 1.4.0 发布与 ESLint v9 更新 | 2025 年第 33 周草梅周报
vue.js·github·nuxt.js
russo_zhang23 天前
【Nuxt】一行代码实现网站流量的实施监控与统计
vue.js·nuxt.js
前端加油站1 个月前
Nuxt.js 项目性能优化
nuxt.js
Neon12041 个月前
Nuxt.js 国际化配置完整教程(含版本兼容与问题解决)
前端·nuxt.js
白雾茫茫丶1 个月前
Nuxt4.0初体验:一个简约、精美、现代化的个人站点导航!
vue.js·nuxt.js