Vue3vite+setup+ts项目总结

项目说明

项目概述

本项目纯粹是为了学习和巩固刚学的vue3组合式api和语法糖,再加上网上流行vite+ts+vue,所以便有了这个历时四天的vue3前端项目。不同于别人写的vue后台管理系统项目,这个是纯前端,没有一丝后端。

在这个项目里,你可以了解vue3与vue2不同之处,也能对vue3的常用知识点进行学以致用。项目中遇到的问题和解决办法也归纳到了使用技巧中,另外也可以作为一个vue3初级项目进行二次开发。

本项目的实现一部分基于我的毕设作品,一部分则是搜集网上优秀创作者分享的知识。在这个资源共享的时代,我也希望自己能够将学到的东西系统的整理起来然后分享给大家,这也就是开源项目的意义所在,哪怕这个项目毫无商业价值。

关于vue后台管理系统,这里推荐一个vite-vue3-template是一个管理后台系统中前端解决方案,后续我会同步分享给大家

基于vite+vue3语法糖setup+ts 所以本项目代码全部基于steup语法糖写法 具体格式如下:

<script lang='ts' setup>
import { useRoute, useRouter } from "vue-router"
import { reactive, toRefs, ref, computed, watch, onMounted } from 'vue'
// 接收路由传递过来的参数
const route = useRoute()
const router = useRouter()
onMounted(() => {console.log(hid.value)
})
</script>
为什么使用setup语法糖

因为相比组合式api的写法更加的简洁,不需要return定义的属性、方法,也不需要再注册组件,直接引入即可

setup是Vue3.0后推出的语法糖,并且在Vue3.2版本进行了大更新,像写普通JS一样写vue组件,对于开发者更加友好了

按需引入computed、watch、directive等选项,一个业务逻辑可以集中编写在一起,让代码更加简洁便于浏览。

项目首页预览

创建项目

使用vite创建vue3项目

首先,使用vite创建vue3项目(推荐安装最新版)

Vite 需要 Node.js 版本 >= 12.0.0

npm init vite@latest vue项目名称 -- --template vue

图示:


切换到当前目录、安装依赖、运行

cd 项目名
npm install
npm run dev
使用vite的注意事项

注意:使用ts,所有的js文件都需要使用ts后缀

配置vite.config.ts

支持alias别名@

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import * as path from 'path'
// https://vitejs.dev/config/
export default defineConfig({resolve: {alias: {'@': path.resolve(__dirname, 'src'),}},plugins: [vue()]
})

作用是@直接指向src,对于多级src自己目录可以直接替换…/,确保引入的文件路径不会因为文件本身位置的改变而受到影响

import Header from '../components/header.vue'
=>
import Header from '@/components/header.vue'

如果你的path出现了红色波浪线,则还需要安装 @types/node

npm install --save @types/node

引入vue-router路由的写法变化

路径配置 .vue 不可省略,但ts文件可以不要后缀,并且component推荐使用异步加载的写法

// 导入路由对象
import { createRouter,createWebHistory } from 'vue-router'
// 路径配置 .vue 不可省略
const routes = [{path: '/',name: 'index',//在vite中使用如下引入组件的写法component: () => import('@/components/index.vue') //.vue不可省略}
]
解决axios跨域请求

server与plugins同级

server: {proxy: {// 字符串简写写法// '/myApi': 'http://apis.juhe.cn/',// 选项写法"/myApi": {target: "http://apis.juhe.cn/",changeOrigin: true,rewrite: (path) => path.replace(/^\/myApi/, ""),},// 引入多个api"/api": {target: "https://v2.alapi.cn",changeOrigin: true,rewrite: (path) => path.replace(/^\/api/, ""),},},},

解释一下哈:假设我想访问聚合数据的天气预报的api接口:

`http://apis.juhe.cn/simpleWeather/query?city=${city}&key=cffe158caf3fe63aa2959767a503bbfe`

那么这里的‘/myApi’代表全局 axios 默认值,即http://apis.juhe.cn/,我们处理跨域请求就是对接口的默认值进行允许跨域的配置,而不是针对整个接口地址

配置完成后,我们使用下面的方式就可以成功get到我们的数据了,针对不同的api我们指定对应的axios 默认值就可以啦

requestData() {axios.defaults.baseURL = '/myApi'//对输入的城市中文进行编码处理let city = encodeURI(this.city)let api = `/simpleWeather/query?city=${city}&key=cffe158caf3fe63aa2959767a503bbfe`this.axios.get(api).then((response) => {this.weatherData = response.dataconsole.log(response.data)})
}

题外话:如果你想使用聚合数据的api接口,需要注册以及实名认证获取自己的key,上面的key就是我自己注册的

安装依赖

本项目中用到的依赖

package.json中"dependencies" {} 里面的内容

  "axios": "^0.27.2","element-plus": "^2.2.17","jsonwebtoken": "^8.5.1","jwt-decode": "^3.1.2","mockjs": "^1.1.0","moment": "^2.29.4","pinia": "^2.0.22","pinia-plugin-persist": "^1.0.0","swiper": "^8.4.2","v-viewer": "^3.0.10","vue": "^3.2.37","vue-axios": "^3.4.1","vue-easy-lightbox": "^1.8.2","vue-router": "^4.1.5","vuex": "^4.0.2"
axios vue-axios

拦截请求和响应 转换请求和响应数据 取消请求 自动转换JSON数据

npm install axios vue-axios --save
element-plus

vue3推荐的Ui组件库

npm install element-plus --save
jsonwebtoken jwt-decode

一个用于生成token,一个用于解析token

注意:本项目没有使用到,ts报错找不到jwt,应该是需要配置.d.ts文件

mockjs

模拟生成数据,常用于项目上线的数据测试,但本项目没用到过,不过保留了mock.js文件供大家参考

npm install  mockjs --save
moment

用户时间格式的处理转换,比如注册状态的时间需要转换成标准格式

npm install moment --save
pinia vuex pinia-plugin-persist

用于状态管理,vue3中我们更推荐使用pinia,但是本项目中使用vuex足够了,因为pinia的写法我还不是很熟悉

pinia-plugin-persist用于持久化存储,自行百度

npm install pinia pinia-plugin-persist --save
npm install vuex --save
swiper

在vue3中使用swiper轮播可以满足特定的轮播需求,尽管elementplus的轮播比swiper配置更简单,ui更好看,但功能性不如swiper。不过目前网上的资料并不多

npm install swiper@latest --save
v-viewer vue-easy-lightbox

图片预览插件 v-viewer适配vue3,而vue-photo-preview目前好像不适配vue3,因为我使用时会报错

npm install v-viewer@next --save
npm insall vue-easy-lightbox --save
vue-router

vue路由管理,其中的route和router的使用场景想必大家都you心得体会了

npm install vue-router@4
route 常用于获取对应的name,path,params,query等,传参 一个跳转的路由对象
router 常用于路由跳转 VueRouter的一个对象
//路由跳转
router.push({path:’/path’})
router.replace({path:’/path’})
//获取传过来的key
route.query.key
// 获取当前路由path
router.currentRoute.value.path

补充:params传参合query传参的区别

params参数在地址栏中不会显示,query会显示

网页刷新后params参数会不存在

全局注册

main.ts 部分全局注册不起作用,局部再次引入即可

import { createApp } from "vue";
import "./style.css";
import App from "./App.vue";
const app = createApp(App);
// 引入路由
import router from "./router/index";
// 导入vue-axios模块
import VueAxios from "vue-axios";
import axios from "axios";
// 全局跨域请求基本配置
// axios.defaults.baseURL = '/api';
//导入Element Plus
import ElementPlus from "element-plus";
import "element-plus/dist/index.css";
//导入所有图标并进行全局注册
import * as ElementPlusIconsVue from "@element-plus/icons-vue";
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {app.component(key, component);
}
// 导入Pinia状态管理器
// import store from './pinia/index'
// 引入vuex
import store from "./store";
// 图片预览器
import "viewerjs/dist/viewer.css";
import VueViewer from "v-viewer";
app.use(VueViewer, {defaultOptions: {},
});
// 时间处理
import moment from 'moment'
// 下面的vue3全局注册方法在ts中不起作用,需要进一步配置
app.config.globalProperties.$moment = moment
app.use(VueAxios, axios)
app.use(router);
app.use(ElementPlus);
app.use(store);
app.mount("#app");
//全局导入过滤器  vue3不支持filters过滤器了,下面方法也不起效果,直接用计算属性算了
//在ts中,目前axios全局注册后不起作用,局部重新引入即可

使用技巧

分离css和js

当vue文件中的css和js代码过多时,我们可以导入到外部css和ts文件中

//css的引入方式
@import "@/assets/css/index.css"
//js的引入方式
import Header from "@/assets/js/header"
js和ts的区别

ts是js的超集,主要是定义类型

ts报错类型“string | null”的参数不能赋给类型“string”的参数。 不能将类型“null”分配给类型“string”
JSON.parse(localStorage.getItem(key)|| '0')
TypeScript + vue3.0设置全局对象或者属性,出现ts类型错误

以本项目vue3语法糖setup+ts为moment注册全局属性为例:app.config.globalProperties.$moment = moment,

在组件中使用$moment会有ts报错

const time = $moment().format('MMMM Do YYYY, h:mm:ss a')
//找不到名称“$moment”。你是否指的是“moment”?ts

添加下列代码可解决:

方案1:src/@types/moment.d.ts。 若没有types文件夹则可以创建

import moment from 'moment'
declare module '@vue/runtime-core' {export interface ComponentCustomProperties {$moment: typeof moment}
}
import { getCurrentInstance } from 'vue'
const app = getCurrentInstance()
const time = app?.proxy?.$moment().format('MMMM Do YYYY, h:mm:ss a')

方案2:重新导入该依赖也可以,区别就是一个全局一个局部,在不注重ts的类型约束下,不使用ts也行

import moment from 'moment'
const time = moment().format("YYYY-MM-DD HH:mm:ss")
判断对象数组是否包含某个对象

有这么一个需求:创建一个loginData数组来模拟后台数据库中的用户登录账号和密码信息,用户信息为对象类型

const data = {name: name.value,password: password.value}
const loginData = [{},{},{}...
]

当用户输入的用户信息与该数组里面的某个对象匹配时,则登录成功,否则提示登录失败

这意味着我们需要判断一个对象数组中是否包含某个对象,并且对象要全等

// 判断用户登录的数据是否与模拟数据库匹配
function findObj(arr: [], obj: {}) {for (let i = 0; i < arr.length; i++) {if (JSON.stringify(arr[i]) == JSON.stringify(obj)) {return i}}return -1
}
const flag = findObj(arr, data)
if(flag != -1) {console.log('匹配成功,登录成功')
} else {console.log('匹配失败,登录失败')
}
如何配置swiper

下面的链接或许可以帮到你,说说我的方法

访问https://swiperjs.com/demos#centered,然后按下图操作

css变量注入 vue3.2新增
<template><span>Jerry</span>
</template><script setup>import { reactive } from 'vue'const state = reactive({color: 'red'})
</script><style scoped>span {// 使用v-bind绑定state中的变量color: v-bind('state.color');}
</style>

使用上面的方式可以更加方便的操控元素样式了,都不需要指定标签,但是使用了变量注入的代码不能分离到外部css,否则会获取不到的

在我的项目中,可以用来设置主题和header导航栏的样式

使用css变量注入实现header导航栏的样式切换

1.默认进入首页(path: ‘/index’)时header背景显示透明色transition,文字颜色显示白色white

2.鼠标移入时,背景显示白色,文字颜色显示#606266;鼠标移出还原

3.滚动条移动时,背景显示白色,文字颜色显示#606266

4.切换到非首页的路由path时,样式固定为背景显示白色,文字颜色显示#606266

<el-menu :default-active="activeIndex" class="el-menu-demo":class="{'menu_fixed': activeIndex.search('index') == -1}" mode="horizontal" :ellipsis="false"@select="handleSelect" @mouseover="ShowInput($event)" @mouseleave="HideInput($event)">...
</el-menu>
import { useRouter } from 'vue-router'
// 调用路由方法及注册
const router = useRouter()
// 获取当前路由path
const activeIndex = ref(router.currentRoute.value.path)
// 定义css变量
const state = reactive({color: 'white',bgColor: 'rgba(0, 0, 0, .3)'
})
// 鼠标移入
function ShowInput(event: any) {state.color = '#606266'state.bgColor = 'white'
}
// 鼠标移出
function HideInput(event: any) {if (router.currentRoute.value.path.search('index') != -1) {state.color = 'white'state.bgColor = 'rgba(0, 0, 0, .3)'} else {}
}
/* 首页-header导航栏固定样式 */
.el-menu-demo {height: 70px;/*这个颜色是控制非el-menu元素内部的外部元素颜色 */color: white;transition: 0.6s;background: v-bind('state.bgColor');
}
/* 首页-固定文字样式 */
.el-menu-demo .el-menu-item {color: v-bind('state.color');
}
/* 非首页固定样式 */
.menu_fixed {background: white;
}
.menu_fixed .el-menu-item {color: #606266;
}

首先默认的class选择器是el-menu-demo和el-menu-demo .el-menu-item分别控制背景颜色和文字颜色(这里文字颜色指的是el-menu内部元素el-menu-item的文字颜色)

menu_fixed作为动态class根据当前路由是否为‘/index’来生效或不生效,并且class类针对相同属性优先选择后者,这意味着步骤4可以很轻易的实现

而针对步骤3,我们需要对滚动条进行监听

// 实时滚动条高度
const scrollTop = () => {let scroll = document.documentElement.scrollTop || document.body.scrollTop;if (scroll > 70) {state.color = '#606266'state.bgColor = 'white'} else {state.color = 'white'state.bgColor = 'rgba(0, 0, 0, .3)'}
}
onMounted(() => {// 监听滚动条位置window.addEventListener('scroll', scrollTop, true)
})

到此,header的样式切换就完成啦,如果不使用css变量注入,则需要写四个class类选择器去操控,总之css变量注入的功能很香

如何使用JS将两个数组合并为一个数组
var a=[1,2,3];
var b=[4,5,6];
a.push.apply(a,b);
console.log(a)
//=> a = [1,2,3,4,5,6]
关于 js Promise 中如何取到 [[PromiseValue]] 值

关于这个问题在项目中遇到过,我封装了一个api去调用一个名人名言接口,结果给我返回了Promise {<pending>},原因是封装写法双重return导致的

api.ts

// 封装axios的外部api请求
import axios from "axios";
const AxiosApi = {// 这里双重return返回得到的是promise<pending>,需要then方法取出async FamousQuotes(id: any) {axios.defaults.baseURL = '/api'// 这里的typeid=1可以自定义更换 具体查询地址:https://www.alapi.cn/api/view/7   参数范围1-45let api = `/api/mingyan?token=wPsstR6XUhVezM8Y&format=json&typeid=${id}`;const text = await axios.get(api).then((res: any) => {return res.data.data.content}).catch((err: any) => {console.log("双重return需要先定义外面的text,然后赋值在最外面return")})return text},
};
export default AxiosApi;

据悉,Promise 有三个阶段,处于 pending 阶段 既可以把获得数据直接放在 Promise.resolve(pdata); 然后就可以通过 then(re=>{}) 处理数据了

import AxiosApi from '@/tools/api'
onMounted(() => {Promise.resolve(AxiosApi.FamousQuotes(1)).then(function (result: any) {console.log(result)text.value = result})
}
vue3+ts全局注册axios不起作用

类似上面的全局属性,方法见相关链接中的VUE3(十三)main.ts中全局引入axios

解决头部使用 position:fixed; 固定定位后遮住下方内容的问题

最优解:在设置了positon的盒子外部再设置一个相同高度的div即可,本项目中的header就是固定定位,轮播图则是绝对定位

vue3使用计算属性代替过滤器,实现对显示文字字数的限制

vue3移除了filters过滤器,所以该选择computed计算属性来实现

//template
<pre>{{ ellipsisText(text,44) }}</pre>
//ts
const text = ref('')
const ellipsisText = computed(() => {return function (val: any, len: any) {return val.length > len ? val.slice(0, len) + "..." : val}
})

上述例子表明对text的内容进行字数限制超过44个字符后用…来代替

CSS3背景图片background属性简写/连写
background:#F00 url('img/images.png') no-repeat fixed center / cover

主要是注意background-size的写法,需要用/ cover

参考链接:https://cloud.tencent.com/developer/article/1538122

Vue3中的watch监听
const a = ref('')
const b = ref('')
//
const c = reactive({name: 'lzm', age: 22,more: {phone: '153xxxx9723'
}
})
//
const props = defineProps({name: '',
})
//
watch( ,(newValue,oldValue) => {})
监听 第一个参数 注意
监听单个ref数据 a 注意不是a.value
监听多个ref数据 [a,b] 多个用数组格式
监听reactive中的单个属性 () => c.name 注意第一个参数是一个箭头函数
监听reactive中的多个属性 [() => c.name, () =>c.more.phone]
同时监听ref和reactive [a, () =>c.name]

补充

监听 第一个参数 props本身是一个Proxy(响应式)对象
监听props props 监听整个对象时不需要使用箭头函数格式
监听props.name () => props.name 监听reactive中的单个属性

同理路由也是如此,route.query.key是路由对象route里面的属性

//监听路由query中key的变化
watch(() => route.query.key, (val: any) => {})
css渐变写法 从左到右颜色渐变

使用渐变色可以让我们的页面颜色更加的舒适,本项目的首页旅游景点轮播图的背景颜色就是渐变色

background:linear-gradient(to right,#fff,#000)
background: linear-gradient(90deg,#fff,#000)
process.env.BASE_URL

process.env是node的一个系统变量,在vue cli中,会对这个process.env做自己的操作,默认情况,再不添加任何自定义环境变量文件的情况下,process.env只有两个属性:

{"NODE_ENV": "development","BASE_URL": "/"
}

不管是在development还是production或者其他模式,默认的BASE_URL都是/

我的理解:意味着process.env.BASE_URL默认指向的是public文件夹,与publicPath: "./"差不多

因为本项目内容的图片基本都放在public文件夹,直接使用绝对路径img/1.png就能直接引用

public和assets文件夹的区别:

public中的文件,是不会经过编译的,打包后会生成dist文件夹,public中的文件只是复制一遍,存放静态资源。图片存放在public里,由于不会被压缩所以会很占内存,以本项目为例,图片资源占了99%,代码文件反而只有几百KB

assets里面主要存放js和css,打包后会被压缩,存放动态资源,存放图片需要动态引入

assets文件夹更适合放置会经常变动的资源,public文件夹更适合放置不会变动的资源

尝试直接打印process.env.BASE_URL,结果报错

Uncaught (in promise) ReferenceError: process is not defined

vite里面引入图片

假设图片放在public/img文件夹中,直接按下面方式引入即可

引入 public 中的资源永远应该使用根绝对路径 —— 举个例子,public/icon.png 应该在源码中被引用为 /icon.png

<img src="img/2.jpg" alt="" />
<img src="/img/2.jpg" alt="" />

引入assets里的图片,尝试直接相对路径引入

<!--- 直接引入img、svg可以--->
<img src="@/assets/img/2.jpg" alt="" />
<img src="@/assets/logo.svg" alt="" />
<!--- 但是el-image 不行 --->

尝试使用require引入报错:Uncaught (in promise) ReferenceError: require is not defined

原因:因为 require 是属于 Webpack 的方法,而Vite其不支持require,所以我们需要去寻找 Vite 静态资源处理的方法

这意味着使用require引入模块也不行,比如引入生成token的jsonwebtoken需要采用下面的引入方式

const jwt = require(‘jsonwebtoken’)

但是这在vite中会报错

正确引用assets里面图片资源的方式

// 1 vite3目前好像支持直接引入assets里面的图片了
<img src="@/assets/img/2.jpg" alt="" />    //需要配置@别名
<img src="../assets/img/2.jpg" alt="" />
// 2
<img :src="data:images" alt="" />
const images = new URL('../assets/img/2.jpg', import.meta.url).href

el-image支持直接引入public的图片,但不支持assets里的图片

直接引入<el-image src="@/assets/img/2.jpg"></el-image>

浏览器会报错:GET http://127.0.0.1:5173/assets/img/1.jpg 404 (Not Found)

watch监听当前路由

当前路由:router.currentRoute.value.path

当前路由参数:route.query.key(key为query传递的参数) || route.params.key

watch监听事件默认首次不会触发,加上{immediate: true,deep: true} 可触发

import { useRouter } from 'vue-router'
const router = useRouter()
watch(() => router.currentRoute.value.path,(toPath) => {//要执行的方法
},
{immediate: true,deep: true}
)
// {immediate: true,deep: true} 要加上,不加的首页不会触发要执行的方法
搜索功能的实现

template模块结合了elementplus的autoComplete自动提示框

<el-autocomplete v-model="searchKey" :fetch-suggestions="querySearch" popper-class="my-autocomplete"placeholder="Search You want to search" :trigger-on-focus="false" :popper-append-to-body="false"@select="handleSelectInput" @keyup.enter.native="handleKeyup"><template #suffix><el-icon class="el-input__icon" @click="handleIconClick"><Search /></el-icon></template><template #default="{ item }"><div class="value">{{ item.value }}</div><span class="link">{{ item.link }}</span></template>
</el-autocomplete>

script部分

import { reactive, toRefs, ref, computed, watch, onMounted,defineProps } from 'vue'
import { useRouter } from 'vue-router'
import { ElMessage } from 'element-plus';
// 声明props
const props = defineProps({pathIndex: {type: String,default: ''}
})
// 调用路由方法及注册
const router = useRouter()
// 获取当前路由path
// console.log(router.currentRoute.value.path)
//autoComplete业务
interface LinkItem {value: stringen_value: string
}
const links = ref<LinkItem[]>([])
const searchKey = ref('')
// 获取到输入值,传递给searchKey接收
const querySearch = (queryString: string, cb: any) => {searchKey.value = queryStringconst results = queryString? links.value.filter(createFilter(queryString)): links.valuecb(results)
}
const createFilter = (queryString: any) => {return (restaurant: any) => {return (restaurant.value.toLowerCase().indexOf(queryString.toLowerCase()) === 0)}
}
// 提示数据列表
const loadAll = () => {return [{ value: '张家界', en_value: 'Zhangjiajie' },{ value: '民宿', en_value: 'Mudi Stream' },{ value: '牧笛溪', en_value: 'Hotel B&B' },]
}
// 封装跳转逻辑处理
function HandleEvent() {if (searchKey.value != '') {if (router.currentRoute.value.path != '/search') {ElMessage({message: "跳转成功!",duration: 1000,type: 'success'})}router.push({path: '/search',query: { key: searchKey.value }})//传递成功后将关键词初始化searchKey.value = ''} else {ElMessage({message: "还没有输入关键词哦!请重新输入",duration: 1000,type: 'warning'})}
}
// 获取用户选择的关键词
const handleSelectInput = (item: LinkItem) => {searchKey.value = item.valueHandleEvent()
}
// 点击事件 获取用户输入的关键词
const handleIconClick = (ev: Event) => {HandleEvent()
}
// 键盘事件 enter触发 获取用户输入的关键词
const handleKeyup = () => {//这一步根据用户按enter键时对应的触发HandleEvent()
}
// watch监听路由变化默认清空存在的关键词    使用router.currentRoute.value.path也可以
watch(() => props.pathIndex, (newVale, oldValue) => {// console.log(newVale, oldValue)if (searchKey.value.length > 0) {searchKey.value = ''}
})
onMounted(() => {links.value = loadAll()
})

上面的代码看似复杂,其实一分解就一目了然了

首先获取用户输入的关键词,在此状态过程中,我们需要监控用户输入的关键词是否为空,不为空才提示跳转成功,然后使用路由router.push()方法跳转到搜索列表(path:’/search’),否则提示用户输入关键词

而触发路由跳转有两种方式,一是点击el-icon图标跳转,而是监听键盘事件enter跳转

获取关键词可直接通过autocomplete业务提供的方法去接收,这里我们定义响应式ref变量searchKey去完成这项光荣的任务

用户输入后或输入状态中跳转其他路由时,我们将其关键词重置为空,来提升用户体验

涉及知识点:子组件使用props接收父组件传递过来的数据、watch监听事件的使用、路由跳转及传参、使用elementplus提供的autocomplete和ElMessage以及el-icon图标等

上述代码只是传递关键词给搜索列表,重头戏在于搜索列表search/index.vue如何返回与关键词匹配的数据,这里我们使用search()方法

script部分代码

// 后台接收路由传递过来的参数
const route = useRoute()
const router = useRouter()
const searchKey = ref<any>(route.query.key)
const resultFlag: any = ref(false)
const siteData: any = Mock.getSiteData()
const hotelData: any = Mock.getHotelData()
// 数组合并
siteData.push.apply(siteData, hotelData)
const searchData = ref<any>([])
// 监听路由本身
watch(() => route.query.key, (val: any) => {// console.log(val)searchKey.value = valif (val != '') {searchData.value = siteData.filter((data: any) => {return data.title.toLowerCase().search(val) != -1})if (searchData.value.length > 0) {resultFlag.value = trueconsole.log("成功返回搜索匹配后的数据:")} else {ElMessage({ message: "未匹配到符合条件的内容,请再试试吧!", duration: 1000, type: 'info' })searchData.value = []}} else {ElMessage({ message: "还没有输入关键词哦", duration: 1000, type: 'warning' })searchData.value = []}
})
// 初次进入状态
function filterData() {// route.query.keyif (searchKey.value != '') {searchData.value = siteData.filter((data: any) => {return data.title.toLowerCase().search(searchKey.value) != -1})if (searchData.value.length > 0) {resultFlag.value = trueconsole.log("成功返回搜索匹配后的数据:")} else {ElMessage({ message: "未匹配到符合条件的内容,请再试试吧!", duration: 1000, type: 'info' })searchData.value = []}} else {ElMessage({ message: "还没有输入关键词哦", duration: 1000, type: 'warning' })}
}
// 处理字数超出限制
const ellipsisText = computed(() => {return function (val: any, len: any) {return val.length > len ? val.slice(0, len) + "..." : val}
})
// 处理路由跳转
function skipPath(id: any, type: any) {// 分类索引内容直接写成路由path更直接,都不需要判断了  //简写写法router.push(type+'?id='+id)
}
onMounted(() => {filterData()// console.log(route.query.key,searchKey.value)
})

继续解剖代码,首先是跳转成功后,我们将后台数据组合成为一个新数组,然后与关键词进行匹配,最后返回条件匹配过滤后的数组——数据

这里分了两种情况,一是从其他路由页面搜索返回关键词跳转到/search,二是本地路由直接搜索返回关键词,这时是关键词的变化,但本质上都是关键词变化,所以需要监控当前路由的关键词来实时更新搜索内容

并且我们可以发现,上述代码存在冗余,可进一步优化,比如结合watch监听当前路由中的{immediate: true,deep: true}实现首次触发,将filterData()方法的代码块等效替换

开写项目

项目结构

额,这目录结构自己写肯定不现实吧!尝试搜索vscode如何导出目录结构,1秒搞定!

步骤如下:

  1. vscode安装插件,project-tree
  2. 安装之后按ctrl+shift+p,并输入Project Tree回车
  3. 点击要生成目录的项目,回车
  4. 将项目目录生成并存储到README.md中
```
vite+vue+setup+ts
├─ .gitignore
├─ index.html
├─ package-lock.json
├─ package.json     //存放项目的依赖配置
├─ public       //静态资源文件夹  存放页面图标和不支持 JavaScript 情况时的页面。
├─ README en.md
├─ README.md        //项目说明文件
├─ src      //存放 vue 项目的源代码
│  ├─ @types
│  │  └─ moment.d.ts
│  ├─ App.vue
│  ├─ assets        //资源文件,比如存放 css,图片等资源
│  │  ├─ css
│  │  │  ├─ footer.css
│  │  │  ├─ header.css
│  │  │  ├─ index.css
│  │  │  ├─ list.css
│  │  │  └─ login.css
│  │  ├─ ts
│  ├─ components        //组件文件夹,用来存放 vue 的公共组件(注册于全局,在整个项目中通过关键词便可直接输出)
│  │  ├─ autoSearch.vue     //搜索功能组件
│  │  ├─ mainHeader.vue     //头部导航栏组件   header
│  │  ├─ style.css      //swiper样式文件
│  │  ├─ swiper.vue     //swiper轮播图组件
│  │  ├─ upload-plus.vue        //图片上传组件
│  │  └─ uplodimg.vue       图片上传组件
│  ├─ main.ts       //是项目的入口文件,作用是初始化 vue 实例,并引入所需要的插件
│  ├─ mock      //模拟生成数据
│  │  ├─ index.ts       //本项目数据源文件
│  │  └─ Mock.js    //模拟生成数据,暂未使用
│  ├─ router        //用来存放 index.js,这个 js 用来配置路由
│  │  ├─ index.ts
│  │  ├─ routes.ts
│  │  └─ web-routes.ts
│  ├─ store     //vuex状态管理
│  │  ├─ index.ts
│  │  ├─ storage.ts
│  │  └─ user.ts
│  ├─ style.css     //全局css
│  ├─ tools     //用来存放工具类 js
│  │  ├─ api.ts     //封装axios访问每日一言接口
│  │  ├─ text.ts
│  │  └─ upload.ts
│  ├─ views     //用来放主体页面,虽然和组件文件夹都是 vue 文件,但 views 下的 vue 文件是可以用来充当路由 view 的
│  │  ├─ about.vue      //关于我们,该页面设置了登录访问权限
│  │  ├─ Backup.vue     //置顶功能组件
│  │  ├─ footer.vue     //底部footer文件
│  │  ├─ Home.vue       //主页
│  │  ├─ hotel
│  │  │  └─ index.vue
│  │  ├─ index  //首页内容区
│  │  │  ├─ aboutus.vue
│  │  │  ├─ lpimg.vue
│  │  │  ├─ merchant.vue
│  │  │  ├─ newslist.vue
│  │  │  ├─ routelist.vue
│  │  │  ├─ sitelist.vue
│  │  │  ├─ tourlist.vue
│  │  │  └─ travelphoto.vue
│  │  ├─ Index.vue      //首页载体
│  │  ├─ login      //登录
│  │  │  ├─ login-elform.vue
│  │  │  ├─ login.vue
│  │  │  └─ register.vue
│  │  ├─ search     //搜索结果返回列表
│  │  │  └─ index.vue
│  │  ├─ site       //景点
│  │  │  ├─ index.vue       //景点列表
│  │  │  └─ webdetail.vue       //景点详情
│  │  └─ test.vue       //测试文件
│  └─ vite-env.d.ts
├─ tsconfig.json
├─ tsconfig.node.json
└─ vite.config.ts       //vue 的配置文件```

仔细发现,部分组件的存放位置是有问题的

功能实现
首页模块

首页布局模仿响应式页面、header导航栏切换样式、切换主题背景、页面置顶功能(为什么不使用elementplus自带的,给忘了)、调用每日一言接口、下载本地md文件功能

搜索模块

搜索功能的简单实现(匹配包含搜索,不是模糊搜索)

路由模块

路由跳转功能、路由前置守卫登录状态页面拦截

状态管理

保存登录信息和注销功能

非首页模块

景点、酒店列表、景点详情

登录模块

模拟登录判断、el-form表单规则

其他模块

测试图片插件、时间格式处理

项目缺陷

1.没有引入评论模块,但是需要数据库的支持,真没法子

2.页面缺乏响应式,后续学习响应式布局

3.使用ts只是纯粹进行基本类型定义规范约束,其他未涉及功能实现

具体实现 & 项目下载

纯粹的复制粘贴过于麻烦,直接分享资源才是王道。
有需要的小伙伴点击此处下载 密码:6rv1
本文档pdf下载 密码:6zw9

登录账号信息:

用户名:lzm 密码:123456

相关链接

说明:以下链接是本项目开发所参考的相关文章或官方文档,感谢这些作者的知识分享!!!

官方文档

Vue3官网:https://v3.cn.vuejs.org/
Vite官网:https://cn.vitejs.dev/
VueRouter官网:https://next.router.vuejs.org/zh/

axios中文文档:https://www.axios-http.cn/docs/intro

vite3中文文档:https://vitejs.cn/vite3-cn/guide/assets.html#importing-asset-as-url

Typed.js: https://mattboldt.com/demos/typed-js/

csdn、简书、掘金

vite.config.js如何配置多个跨域 https://blog.csdn.net/weixin_52691965/article/details/120888332

使用Vite构建Vue3项目,对路由Vue Router 4.x的设置 https://blog.csdn.net/xjtarzan/article/details/119736309

v-viewer:vue3图片查看器 https://blog.csdn.net/ymzhaobth/article/details/122127852

ts报错类型“string | null”的参数不能赋给类型“string”的参数。 不能将类型“null”分配给类型“string”

TypeScript + vue3.0设置全局对象或者属性,出现ts类型错误 https://blog.csdn.net/zlguaizhang/article/details/123404276

Vue3 ts setup getCurrentInstance 使用时遇到的问题及解决

[Vue3] 如何注册和使用全局方法(setup 语法糖中) https://fishpi.cn/article/1662111299980?p=1&m=0

vue3时间转换插件-Moment.js的使用 https://blog.csdn.net/qq_43548590/article/details/121853437

判断对象数组是否包含某个对象 https://blog.csdn.net/qq_44869043/article/details/105981164

Vue3推荐的替代Vuex的新一代状态管理工具:Pinia 配置教程 https://blog.csdn.net/xjtarzan/article/details/123665620

Swiper Vue.js Components https://swiperjs.com/vue#swiper-props

Vue3/Vite中使用Swiper8基础入门教程 https://blog.csdn.net/weixin_59250190/article/details/125990065

vue3 setup语法糖请注意在script标签里加setup,不用return https://blog.csdn.net/qq_54753561/article/details/121044074

如何使用JS将两个数组合并为一个数组 https://blog.csdn.net/qq_43237365/article/details/101553667

分享一个Navicat Premium绿色版,无需破解 http://www.itmind.net/18710.html

关于 js Promise 中如何取到 [[PromiseValue]] 值 https://blog.csdn.net/weixin_44732337/article/details/103297283

VUE3(十三)main.ts中全局引入axios https://blog.csdn.net/qq_39708228/article/details/114662431

解决头部使用 position:fixed; 固定定位后遮住下方内容的问题 https://blog.csdn.net/lolhuxiaotian/article/details/122229393

vue3使用计算属性代替过滤器,实现对显示文字字数的限制 https://blog.csdn.net/weixin_39225682/article/details/119178581

CSS3背景图片background属性简写/连写 https://cloud.tencent.com/developer/article/1538122

Vue3中的watch监听 https://blog.csdn.net/qq_40323256/article/details/127134151

Vue3.2单文件组件setup的语法总结 https://www.jianshu.com/p/8c351aa6f373

让 vite 支持 require https://blog.csdn.net/qq_43806488/article/details/126616481 未亲测

Vue3中Vuex的使用 https://blog.csdn.net/qq_45934504/article/details/123462736

手把手教你vue3引入vue-router路由 https://blog.csdn.net/weixin_45966674/article/details/122260952

如何利用Vue3和element-plus实现图片上传组件 https://www.yisu.com/zixun/690747.html

vscode自动生成项目目录结构 https://blog.csdn.net/liuliuliuliumin123/article/details/113959649

网站工具

在线屏幕颜色提取器取色器拾色器工具
[typed.js]-JavaScript打字动画库

聚合数据-api接口

在线excel转json工具

ALAPI-api接口

最后,觉得有用顺手给我一个赞,欢迎评论区讨论哈!

vue3使用vite+setup+ts写一个初级前端项目相关推荐

  1. 写给初级前端的面试经验

    最近到了金三银四的跳槽季,很多人都会面临跳槽找工作,并且再过几个月又会到毕业季,越来越多的毕业生会面临这个问题. 同样,我们组因为业务需要(我们今年倒是还没有人员离职,感动╭(╯^╰)╮)需要进行社招 ...

  2. 快2023年了,一个初级前端开发要达到什么水平?

    第一.明确自己的岗位定位 不同的工作岗位职责自然也是不一样的,那么入行不久的前端开发工程师在工作中需要做一些什么样的工作内容呢? 初级前端工程师在企业中往往负责以下几种类型的工作: 参与产品需求讨论 ...

  3. 手写一个微前端框架(内含源码地址)

    来源:伊撒尔 https://zhuanlan.zhihu.com/p/169800579 halo,大家好,我是 132,前阵子冥思了一会儿微前端,然后周六日趁热打铁,马上写了一个微前端框架,名叫 ...

  4. 新手如何写一个好的项目描述文件

    新手如何写一个项目的README.md 想写好一个README文件首先需要熟悉MarkDown语法,再次就不过多讲解了. 主要分为几个大的结构就像写简历一样123罗列出来即可; 1.项目概况 2.技术 ...

  5. 手把手教你构建一个web前端项目,全网最详细教程!

    为什么80%的码农都做不了架构师?>>>    1. 选择现成的项目模板还是自己搭建项目骨架 搭建一个前端项目的方式有两种:选择现成的项目模板.自己搭建项目骨架. 选择一个现成项目模 ...

  6. 如何利用qiankun快速搭建一个微前端项目

    前言 小伙伴们大家好.前一篇文章跟大家分享了一些关于微前端的知识点,包括什么是微前端,为什么要用微前端以及如何实现一个微前端,在文章的最后我们还提到了能够实现微前端的两个库:single-spa和qi ...

  7. 一个初级前端结合css、div谈一谈屏幕尺寸、分辨率、缩放概念题

    写在前面: 本文作为本人学习总结之用,同时分享给大家~ 个人前端博客网站:https://zhangqiang.hk.cn 欢迎加入博主的前端学习qq交流群::706947563,专注前端开发,共同学 ...

  8. 一个初级嵌入式项目经理的成长和迷茫(杂谈)

    时间过得真快,转眼间已经毕业快2年了,回过头来想一想两年的时间不算长也不算短,两年的时间可以做很多事情,两年的时间也可以让一个人荒废. 回想当初在学校实验室奋战的日子总是快乐而充实的,从使用Altiu ...

  9. 如何写一个好的项目策划

    项目的特征 项目有一个独特的.可定义的目标或最终的产品 项目是独一无二的 项目是一次性活动 项目含有不确定性 项目需要项目成员复杂而努力的工作产出的变革(创新) 项目管理的五个要素 范围管理 时间管理 ...

最新文章

  1. druid连接池mysql5.7_Spring Boot 使用Druid连接池整合Mybatis-Plus连接Mysql数据库
  2. 计算机是如何按照时钟来顺序工作的
  3. what is the thinking routine of the open source?
  4. seo模拟点击软件_网站用软件刷排名好不好?
  5. Spring MVC Boot Cloud 技术教程汇总
  6. Knative 核心概念介绍:Build、Serving 和 Eventing 三大核心组件
  7. 网页开发部署-开发工具MyEclips+Tomcat+mysql
  8. wcf 基础教程 第一讲 wcf基础知识
  9. 剑指offer——面试题11:数值的整数次方
  10. 证书重新生成_Kubernates证书过期问题的解决
  11. mysql_config缺失_安装 mysqlclient 报 mysql_config not found
  12. 如何看旷视南京负责人魏秀参跳槽高校工作?
  13. Wider Face数据集详解
  14. 区块链是什么通俗解释_区块链是什么?1个例子通俗解释,小白秒懂!
  15. java heartbeat
  16. 火狐 dns_如何在Firefox中通过HTTPS启用DNS
  17. 中国身份证号码验证,支持15 18位,可验证成功90 的身份证号
  18. js获取ISO8601规范时间,使用UTC时间,格式为:YYYY-MM-DDThh:mm:ssZ
  19. 从拉马努金到张益唐——数学是一个整体
  20. CCS报错#10010 errors encountered during linking;

热门文章

  1. 多部电梯具有联动性的测试用例
  2. android NDK编译openblas和向量检索库faiss
  3. java毕业设计星之语明星周边产品销售网站Mybatis+系统+数据库+调试部署
  4. 关于使用不同单片机 工程报错总结(STC12C5A60S2)
  5. FreeModbus开源协议简介
  6. Symbian开发FAQ(转)
  7. 网格交易 python代码_两小段代码轻松搞定网格交易法
  8. mac os 视频播放器 免费
  9. windows使用双网卡同时上内外网
  10. 使用Betaflight Configurator飞控刷写固件时各步骤的含义