Nuxt3 功能篇

Nuxt3 功能篇

1、加载效果

🍎添加单页面加载效果

单页面进行使用

javascript 复制代码
<div v-show="isLoading" class="preloader fixed inset-0 bg-white text-primary flex justify-center items-center z-50">
  <NuxtImg src="/images/preloader.gif" alt="w-20" />
</div>


<script setup>
import { ref, onMounted, onBeforeUnmount } from 'vue'

// 响应式状态
const isLoading = ref(true)

// 预加载器
onMounted(() => {
  // 模拟预加载器消失
  setTimeout(() => {
    isLoading.value = false
  }, 500)
})
</script>

🍎全局加载效果

app\layouts\default.vue放到这里

javascript 复制代码
<!-- layouts/default.vue -->
<template>
  <div>
    <!-- <Header/> -->
    <!-- 页面主体内容 -->
    <GlobalLoading/>
    <NuxtPage/>
    <!-- <Footer /> -->
  </div>
</template>
<script setup>
</script>
<style scoped>
</style>

加载组件GlobalLoading.vue

javascript 复制代码
<!-- 预加载器 -->
<template>
  <!-- preloader -->
  <div v-if="isLoading" class="preloader fixed inset-0 bg-white text-primary flex justify-center items-center z-50">
    <NuxtImg src="/images/preloader.gif" alt="w-20" />
  </div>
  <!-- preloader end -->
</template>
<script setup>
import { ref, onMounted, onBeforeUnmount } from 'vue';
const isLoading = ref(true)
onMounted(() => {
  setTimeout(() => {
    isLoading.value = false
  },100)
})
</script>
<style scoped></style>

2、返回顶部

javascript 复制代码
<!-- back to top -->
<button id="back-to-top" ref="backToTop" @click="scrollToTop" class="fixed bottom-10 right-5 z-10 h-12 w-12 bg-primary rounded-full flex items-center justify-center text-white transition-all duration-300 invisible">
  <i class="las la-arrow-up"></i>
</button>


const backToTop = ref(null) //返回顶部

// 返回顶部
const scrollToTop = () => {
  if (window.pageYOffset < 50) {
    backToTop.value.classList.add("invisible")
  } else {
    backToTop.value.classList.remove("invisible")
  }
  window.scrollTo({
    top: 0,
    behavior: 'smooth'
  })
}

3、添加主题设置面板

主题设置面板能帮助我们设置想要的主题和颜色

javascript 复制代码
<!-- Theme settings -->
<div ref="settings"  class="fixed top-1/2 -translate-y-1/2 -right-60 z-20 flex gap-4 transition-all duration-300 settings-wrapper" id="settings">
  <button @click="toggleSettings" type="button" id="settings_toggler" class="h-10 w-10 rounded-md text-white shrink-0 bg-primary flex items-center justify-center">
    <i class="las la-cog text-white text-2xl animate-spin" style="animation-duration: 2.5s"></i>
  </button>
  <div class="bg-white rounded-lg p-4 shadow-md w-60 space-y-2">
    <h2 class="text-lg font-semibold text-gray-700">Theme Color</h2>

    <div class="space-y-1">
      <h4 class="text-base font-normal text-gray-500">Primary Color</h4>
      <ul class="flex gap-4">
        <button data-color="79 70 229" class="h-8 w-8 rounded-full bg-[rgb(79,70,229)] block change_prime_color text-white">
          <i class="las la-check invisible"></i>
        </button>
        <li>
          <button data-color="52 152 219" class="h-8 w-8 rounded-full bg-[rgb(52,152,219)] block change_prime_color text-white">
            <i class="las la-check invisible"></i>
          </button>
        </li>
        <li>
          <button data-color="26 188 156" class="h-8 w-8 rounded-full bg-[rgb(26,188,156)] block change_prime_color text-white">
            <i class="las la-check invisible"></i>
          </button>
        </li>
        <li>
          <button data-color="247 159 31" class="h-8 w-8 rounded-full bg-[rgb(247,159,31)] block change_prime_color text-white">
            <i class="las la-check invisible"></i>
          </button>
        </li>
      </ul>
    </div>

    <div class="space-y-1">
      <h4 class="text-base font-normal text-gray-500">Secondary Color</h4>
      <ul class="flex gap-4">
        <li>
          <button data-color="24 24 27" class="h-8 w-8 rounded-full bg-[rgb(24,24,27)] block change_secon_color text-white">
            <i class="las la-check invisible"></i>
          </button>
        </li>
        <li>
          <button data-color="30 41 59" class="h-8 w-8 rounded-full bg-[rgb(30,41,59)] block change_secon_color text-white">
            <i class="las la-check invisible"></i>
          </button>
        </li>
        <li>
          <button data-color="55 65 81" class="h-8 w-8 rounded-full bg-[rgb(55,65,81)] block change_secon_color text-white">
            <i class="las la-check invisible"></i>
          </button>
        </li>
        <li>
          <button data-color="68 64 60" class="h-8 w-8 rounded-full bg-[rgb(68,64,60)] block change_secon_color text-white">
            <i class="las la-check invisible"></i>
          </button>
        </li>
      </ul>
    </div>
  </div>
</div>
<!-- Theme settings end -->

样式写好以后调整一我们的按钮事件

javascript 复制代码
const settings = ref(null)
// 设置面板切换
const toggleSettings = (e) => {
	e.stopPropagation();
	console.log("toggle",settings.value);
  if (settings.value.classList.contains("right-4")) {
    settings.value.classList.replace("right-4", "-right-60")
  } else {
    settings.value.classList.replace("-right-60", "right-4")
  }
}

现在我们的面板就可以点开了,接下来只需要点击颜色以后更新我们本地存储的颜色即可

javascript 复制代码
// 颜色主题
const activePrimaryColor= ref(null) // 激活的颜色
const activeSecondaryColor = ref(null) // 激活的颜色


const primaryColors= ref([
	{ name: 'Indigo', value: '79,70,229',numvalue:'79 70 229'},
	{ name: 'Blue', value: '52,152,219',numvalue:'52 152 219'},
	{ name: 'Teal', value: '26,188,156',numvalue:'26 188 156'},
	{ name: 'Orange', value: '247,159,31',numvalue:'247 159 31'}
])
const secondaryColors = ref([
  { name: 'Black', value: '24,24,27', numvalue: '24 24 27' },
  { name: 'Dark Blue', value: '30,41,59', numvalue: '30 41 59' },
  { name: 'Gray', value: '55,65,81', numvalue: '55 65 81' },
  { name: 'Brown', value: '68,64,60', numvalue: '68 64 60' }
])


// 主要颜色切换
const changePrimaryColor = (item) => {
	console.log(item,'changePrimaryColor');

	// 更新活动颜色
  activePrimaryColor.value = item.numvalue;
	
  console.log(activePrimaryColor.value,'activePrimaryColor');

	let lsName="nefte_primary_color";
	root_theme.value.style.setProperty(`--color-primary`,activePrimaryColor.value)
  localStorage.setItem(lsName, activePrimaryColor.value)

	toggleSettings(); // 关闭设置栏
}

// 次要颜色切换
const changeSecondaryColor = (item) => {
  activeSecondaryColor.value = item.numvalue; // 更新活动颜色
	let lsName="nefte_secondary_color";
	root_theme.value.style.setProperty(`--color-secondary`,activeSecondaryColor.value)
  localStorage.setItem(lsName, activeSecondaryColor.value)
	toggleSettings(); // 关闭设置栏
}

// 设置面板切换
const toggleSettings = (e) => {
	// e.stopPropagation();
	// console.log("toggleSettings",settings.value);
  if (settings.value.classList.contains("right-4")) {
    settings.value.classList.replace("right-4", "-right-60")
  } else {
    settings.value.classList.replace("-right-60", "right-4")
  }
}

ok ,尝试一下,我们的颜色切换主题ok

4、发送邮件

🍎配置文件.env

这里我们以自己的qq为例,可以看看之前的邮件文章

javascript 复制代码
.env

EMAIL_USER=xxx@qq.com
EMAIL_PASS=xxx
EMAIL_HOST=smtp.qq.com
EMAIL_PORT=587

🍎接口server\api\send-email.post.ts 配置

这里我们写一下我们接口,前端传过来的部分,其实最主要的就是其中的email字段

javascript 复制代码
{
    "name": "林太白",
    "email": "xxx@qq.com",
    "phone": "xxx",
    "message": "内容信息"
}
javascript 复制代码
// server/api/send-email.post.ts
import nodemailer from 'nodemailer'

// 创建邮件传输器
const transporter = nodemailer.createTransport({
  host: process.env.EMAIL_HOST,
  port: parseInt(process.env.EMAIL_PORT || '465'),
  secure: false, // true for 465, false for other ports
  auth: {
    user: process.env.EMAIL_USER,
    pass: process.env.EMAIL_PASS,
  },
})

export default defineEventHandler(async (event) => {
  console.log('发送邮件');// 发送邮件
  try {
    // 读取请求体
    const body = await readBody(event)
    
    // 基本验证
    if (!body.email || !body.name ||!body.phone || (!body.text && !body.message)) {
      throw createError({
        statusCode: 400,
        statusMessage: '缺少必要字段邮箱、姓名、电话、内容',
      })
    }
    // 准备邮件选项
    const mailOptions = {
      from: `${body.name}<${process.env.EMAIL_USER}>`, // 发件人
      to: body.email,
      subject: body.name,
      text: body.message,
      html: body.message,
      // 添加附件(如果有)
      attachments: body.attachments || [],
    }
    
    // 发送邮件
    const info = await transporter.sendMail(mailOptions)
    
    // 返回成功响应
    return {
      success: true,
      code: 200,
      msg: '邮件发送成功',
      // data: {
      //   messageId: info.messageId,
      //   response: info.response,
      // },
    }
    
  } catch (error) {
    console.error('邮件发送错误:', error)
    
    // 返回错误响应
    return {
      success: false,
      msg:  '邮件发送失败',
      statusCode:  500,
    }
  }
})

🍎前端调用

在页面上写好直接提交from即可

javascript 复制代码
const submitForm = async () => {
	console.log("Form submitted",form.value);
  try {
    // 调用 API
    const res = await $fetch('/api/send-email', {
      method: 'POST',
      body: form.value
    })
		console.log(res,'res');
    if(res.code == 200) {}else{}
  } catch (error) {

	} finally {
    
  }
}

5、拨号功能

方式一

javascript 复制代码
<a :href="'tel:' + 110" rel="external nofollow">
    拨打电话(123) 123-456
</a>

方式二

javascript 复制代码
方法二
<a @click="callPhone" rel="external nofollow">拨打电话</a>
callPhone(){
      window.location.href = 'tel://110'
},

报错处理

🍎'allow-scripts' permission is not set

javascript 复制代码
Blocked script execution in '<URL>' because the document's 
frame is sandboxed and the 'allow-scripts' permission is not set.

这里我们使用的代码如下

javascript 复制代码
<iframe class="w-full h-96" 
  src="https://www.amap.com/" sandbox="" allowfullscreen="" loading="lazy">
</iframe>

🍎多跟节点报错

Transition renders non-element root node that cannot be animated.

javascript 复制代码
runtime-core.esm-bun...er.js?v=3bb06854:50 [Vue warn]: Component inside 
<Transition> renders non-element root node that cannot be animated.
javascript 复制代码
这个Vue警告提示你:
  在<Transition>组件内渲染了一个非元素根节点,
  这会导致动画无法正常工作。

错误原因:
<Transition>组件只能用来包裹单个元素节点,而你尝试在其中渲染了多个节点或非元素节点
(如文本节点、注释节点或多个元素节点)。

🍎处理方式,在提示的部分,最外层使用div包裹

javascript 复制代码
<div>
  内容
  xxx 
</div>

🍎造成这种现象的复盘

产生这个现象的过程

javascript 复制代码
点击这个没有跟的页面,再点击其他页面的时候,首次是空白的
javascript 复制代码
HeaderSection.vue 之中

<header class="header fixed w-full left-0 top-0 shadow z-10"></header>


about页面之中
<template>
    <ThemeSetting />
		<HeaderSection	/>
		<div>我是身体</div>
    <FooterSection />
</template>
 
相关推荐
BingoGo3 分钟前
PHP 集成 FFmpeg 处理音视频处理完整指南
后端·php
RaidenLiu7 分钟前
从 Provider 迈向 Riverpod 3:核心架构与迁移指南
前端·flutter
前端进阶者8 分钟前
electron-vite_18Less和Sass共用样式指定
前端
数字人直播11 分钟前
稳了!青否数字人分享3大精细化AI直播搭建方案!
前端·后端
江城开朗的豌豆13 分钟前
我在项目中这样处理useEffect依赖引用类型,同事直呼内行
前端·javascript·react.js
听风的码16 分钟前
Vue2封装Axios
开发语言·前端·javascript·vue.js
转转技术团队16 分钟前
前端安全防御策略
前端
掘金一周23 分钟前
被老板逼出来的“表格生成器”:一个前端的自救之路| 掘金一周 8.21
前端·人工智能·后端
白嫖叫上我26 分钟前
js如何循环HTMLCollection
javascript
cc_z28 分钟前
vue代码优化
前端·vue.js