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>