FooTok项目杂谈
前言
老项目新谈,用vue3重构ing
主题演示
待填充…
构建工具+技术栈
编译软件:vscode
开发框架: vue3
打包工具:vite
UI:element-plus
icon:阿里巴巴
其他:pinia, vue-router,git
插件:pubsub,nanoid,less-loader
后端: nodejs,mysql
part0 路由&pinia
定义路由,以及重定向。
当时在router里面把路径写错了导致页面跳转问题出现。!!!
vue3的路由写法1
2
3
4
5
6
7
8
9
10
11
12import {
createRouter,
createWebHashHistory
} from "vue-router"
const routes = [{
xxxx
}]
const router = createRouter({
history: createWebHashHistory(),
routes,
});
export default router
pinia写法
part1 mainFrame
这个部分是关于整个页面框架的
element-plus
全局引入和按需引入,没什么好说的。注意的是官网的例子部分是ts,不太友好。
农历的类
使用到了一个可以返回农历值的方法,将他封装了起来,放进了plugins。最后暴露方法出来。
css变量
这次使用了css变量来定义全部的颜色变量,方便后期修改
注意使用css变量的框架不要设置style:scoped
否则后面的读不出来数据。
part2 login
这个部分是关于login页面的
还待解决的问题:分辨率不同footok的标题向下位置不同
注意router的引入1
import { useRoute, useRouter } from "vue-router";
注册的判断
关于id的正则表达式:/^[a-zA-Z0-9_-]{4,16}$/
(只支持字母,数字,下划线,减号)
关于密码的正则:/^.*(?=.{6,})(?=.*\d)(?=.*[A-Z])(?=.*[a-z]).*$/
(最少六位加大小写),并且注意不能空密码空账号
注意watch方法中,reactive的变量检测使用()=>obj.xxx
例子:1
2
3
4
5
6
7
8
9
10
11
12
13watch(
() => userData.password,
(newValue) => {
let patt = /^.*(?=.{6,})(?=.*\d)(?=.*[A-Z])(?=.*[a-z]).*$/;
if (!patt.test(newValue)) {
passwordTips.value = "您的密码强度不够";
isSuccess.passWordSuccess = false;
} else {
passwordTips.value = "";
isSuccess.passWordSuccess = true;
}
}
);
登陆和注册以及axios
发送:注意发送的时候不要发响应式的对象,因为后端不需要你的响应式对象,它需要一个普通对象,所以可以使用toRaw将你的数据变成普通对象之后发送。
接收:axios的问题是要知道code和数据来自哪里。这里发现后端发来的数据中,code来自data
1 | axios |
注册逻辑:200则注册成功,在session里面放sid,这样router里面就能放行,并且跳转。如果异常则是用户名已存在。(网络异常的判断?)
登陆逻辑:200登陆成功,否则用户名存在。
(登陆的优化?安全?token?jwt?)
part3 indexAside
这个部分是关于两侧aside页面的
用到了视口单位进行了响应式。
part4 foodSwiper
这个部分是关于轮播图页面的
三个需求
- 展示轮播图片 五个一循环 点击换一些 再切换五个
目前还没解决的问题是 根据用户请求 - 点赞功能
- 收藏功能
轮播图片
采用的element的走马灯并进行了魔改。具体魔改的是下方的指示灯
遇到的问题:
- axios请求图片之后需要滑动走马灯才显示的问题
原因:没有设置渲染条件,走马灯没读到数据。
解决方法:v-if
数据的长度,有数据就显示 - 点击换一些过一会屏幕空白
原因:没有初始化数据数组长度
解决方法:先初始化再赋值1
2
3
4
5
6
7
8
9
10
11
12
13var getfood = () => {
foodMsgObj.foodMsg = [];
axiosFoodMsg()
.then((res) => {
foodMsgObj.foodMsg = res;
}) //初始化走马灯文字
.then(() => {
let btn = document.getElementsByClassName("el-carousel__button");
for (let i = 0; i < btn.length; i++) {
btn[i].innerText = wordObj[i];
}
});
}; - 魔改的指示灯不显示
原因:赋值完dom还没有渲染完,所以魔改的灯没有值。
解决方法:如上,使用异步。 - 魔改的指示灯背景色 这个是css问题 已经解决。
亮点:封装了axios方法到hook,方便后面请求使用。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20import axios from 'axios'
export default async function () {
var arr = [];
let idObj ={
id:parseInt(sessionStorage.getItem('sid'))
}
await axios.post("/api/swiper",idObj).then((res) => {
// if(res.status ===200){
// arr = res.data;
// }
if (res.data.code === 200) {
arr = res.data.data.records;
}
console.log(arr);
}).catch((err) => {
console.log(err);
})
return arr
}axios注意事项:
- 注意res的data和data里面的data问题。
- 注意return,它返回的是一个
promise
对象,解析promise
对象需要使用then方法点赞功能
性能优化:防抖后确认和第一次的状态不一致才发送请求改变状态
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39var updateLikeTime = null;
var likeInit;
var zan = (id) => {
let arr = foodMsgObj.foodMsg.filter((item) => {
return item.id === id;
})[0];
//如果是第一次 那么记录初始点赞状态
if (!arr.zanfirstClick) {
likeInit = arr.islike;
arr.zanfirstClick = true;
}
//点赞和取消点赞
if (!arr.islike) {
arr.likenum++;
arr.islike = 1;
clearTimeout(updateLikeTime);
} else {
arr.likenum--;
arr.islike = 0;
clearTimeout(updateLikeTime);
}
//如果最后一次操作和初始状态不一样 才设置定时器发送请求
if (likeInit !== arr.islike) {
updateLikeTime = setTimeout(() => {
let updateData = {
userid: sessionStorage.getItem("sid"),
id: id,
islike: arr.islike,
};
axios.post("/api/updatelike", updateData).then((res) => {
console.log(res);
if (res.status === 200) {
arr.zanfirstClick = false;
console.log(id,"点赞转换成功");
}
});
}, 3000);
}
};收藏功能
同上 逻辑类似不赘述part5 foodMap
需求如下:这个部分是关于美食地图的
- 热点地图 点击切换区域
- axios请求后显示相应的收录数,收录排行前二的数据
- 点击数据 跳转对应的详情页(产生通用接口传id查sql跳转)
- 查看所有直接跳转美食目录
热点地图
这个玩意当时纠结了很久,怎么点击一个地图的热点区域并高亮该区域
目前知道的方法就是map标签,里面添加area标签以及poly属性处理多边形。前提还要和图片进行一个绑定。原理是通过点击热点区域动态改变图片src写到这你以为很简单?看到上面的coords属性了吗,这个是坐标的区域,也就意味着每个多边形的坐标都需要知道,以至于…1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16//代码太长删掉了一部分 值得一提的是usemap和map中的id绑定,但此时只有写name即可视为绑定。
<img :src="mainMap" alt="" usemap="#map" id="mainmap" />
<map name="map">
<area
shape="poly"
:coords="huanan1"
class="huanan"
@click.native="changeMap"
/>
<area
shape="poly"
:coords="huanan2"
class="huanan"
@click.native="changeMap"
/>
</map>
这只是一部分,后面的坐标还有很多。是通过ps的方式找坐标的,也就意味着要一个一个点打…
交互部分
剩下的功能倒不是很难实现,也没什么值得一提的地方,除了后端这个数据
前端就只能慢慢的解构赋值了
热点地图自适应(已解决)
原先问题:没有办法解决当缩放的时候,失去热点区域的问题,因为图片被缩放了,但是热点区域没有发生改变,原来的坐标还是在屏幕对应的位置 所以会出错。
解决方法:先记录图片初始化的大小,x坐标乘现在宽/初始宽的比例,y坐标乘现在高/初始高的比例,得到缩放后的比例。
过程中遇到的问题和注意事项:
- 坐标是字符串 转数组才能操作 操作完要转回字符串。
- 需要暴露两个obj 一个是初始坐标obj,另外一个暴露的obj会变成响应式后面使用。如果只暴露一个,那么响应式的就算暂存了地址中的数据也会发生变化。
- 替换坐标的时候,不能采用
obj = function返回的obj
这样的形式,会丢失响应式。建议直接obj.xx = xxfunction(xx)...
这样的形式保证不会出错。 - 缩放设置定时器避免重复计算。
上代码
hook里面的vue里面的1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48const coordsObj = {
huanan1: huanan1,
huanan2: huanan2,
huanan3: huanan3,
huabei: huabei,
huazhong: huazhong,
huadong: huadong,
xibu: xibu
}
const initCoords = {
huanan1: huanan1,
huanan2: huanan2,
huanan3: huanan3,
huabei: huabei,
huazhong: huazhong,
huadong: huadong,
xibu: xibu
}
function setCoords(arr, w, h) {
let img = document.getElementById("mainmap");
let newWidth = img.width;
let newHeight = img.height;
let widthpercent = newWidth / w;
let heightpercent = newHeight / h;
arr = arr.split(',')
for (let i = 0; i < arr.length; i++) {
// x坐标
if (i % 2 !== 0) {
arr[i] = Math.round(parseInt(arr[i]) * widthpercent);
} else {
//y
arr[i] = Math.round(parseInt(arr[i]) * heightpercent);
}
}
var newPosition = "";
for (var j = 0; j < arr.length; j++) {
newPosition += arr[j];
if (j < arr.length - 1) {
newPosition += ",";
}
}
return newPosition;
}
export {
coordsObj,
initCoords,
setCoords,
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21let mycoordsObj = reactive(coordsObj);
//热点区域自适应
//初始化热点
let time = null;
function initCoord(delay) {
clearTimeout(time);
time = setTimeout(() => {
mycoordsObj.huanan1 = setCoords(initCoords.huanan1, 767, 586);
mycoordsObj.huanan2 = setCoords(initCoords.huanan2, 767, 586);
mycoordsObj.huanan3 = setCoords(initCoords.huanan3, 767, 586);
mycoordsObj.huabei = setCoords(initCoords.huabei, 767, 586);
mycoordsObj.huazhong = setCoords(initCoords.huazhong, 767, 586);
mycoordsObj.huadong = setCoords(initCoords.huadong, 767, 586);
mycoordsObj.xibu = setCoords(initCoords.xibu, 767, 586);
}, delay);
}
initCoord(200);
//当缩放的时候 自适应 且设置定时器避免多次计算
window.onresize = function () {
initCoord(1500);
};性能优化
- png转webp,从400k变100k直升四倍,注意格式工厂的webp转换底色会变绿
- 依旧是定时器,避免重复计算。
part6 foodCatalog
这个部分是关于美食目录的
需求如下:
- 分页显示图片,内容,点击图片跳转详情页
- 根据不同标签显示不同种类图片
骨架屏的使用
第一次使用element的骨架屏,用法也比较简单,需要在el-skeleton
标签内放两个template,一个用于骨架显示,一个用于实际显示。遇到的问题:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35<el-skeleton :loading="loading" animated :throttle="500">
<!-- 骨架显示 -->
<template #template>
<el-skeleton-item
variant="image"
style="
width: 24%;
height: 32%;
margin-bottom: 5px;
margin-right: 5px;
"
v-for="item in 12"
:key="item"
/>
</template>
<!-- 实际图片显示 -->
<template #default>
<div class="foodBox">
<el-card :body-style="{ padding: '0px' }" v-for="item in foodData" :key="item.id"
><el-image :src="item.foodbigimg" />
<span>
<p>{{ item.foodname }}</p>
</span>
</el-card>
</div>
</template>
</el-skeleton>
<el-pagination
background
class="pagin"
layout="prev, pager, next"
@current-change="handleCurrentChange"
:currentPage="currentPage"
:total="pageSize"
/>
项目中一页显示12个,骨架屏里面也有十二个内容,想着切换的条件应该是图片loading完毕就显示。所以给el-image
的load事件绑定了loading,但是实际上并没有用。猜想可能是图片不是同时渲染,所以给出的boolean值也随时间变化,不是固定值,所以骨架屏整个的切换无法进行。
解决方法:
不等图片渲染完再取消了,直接收到请求之后取消。分页器
element的分页器也是第一次使用,当时遇到的问题就是不知道怎么响应当前页数,就是点到第几页显示对应的数字。后面从文档中了解到使用@current-change="handleCurrentChange"
绑定当前页,默认传入的是当前页的值val 这样就可以传参到后端了。
性能优化
- 骨架屏:请求回来之前使用骨架屏,响应之后渲染图片,增加用户体验
- css的loading,这个和骨架屏的原理其实差不多,选了骨架屏
- 依旧是png转webp,可以到达更小的size其实,只需要缩略图
vite
链接hash问题
vite会自动把链接变成哈希值,所以是不能直接引入某某链接的,需要使用一个方法1
2
3
4
5//getAssetsImages.js
export default function (fileName,imgName) {
return new URL(`/src/assets/img/${fileName}/${imgName}`,
import.meta.url).href;
}
这个js由于使用的场景太多 直接封装之后放入了hook使用。后面考虑全局使用。
vite配置项的问题
vite的配置项和之前vuecli不太一样,而且确保能完全使用它的功能,还需要将vite的版本升到最新。所以当时配置的时候删库重新init了vite项目,这个问题需要非常注意。
检验的方法是:init之后康康有没有vite.config.js
具体配置
代码1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33import {
defineConfig
} from 'vite'
import vue from '@vitejs/plugin-vue'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [vue()],
base: './',
build: {
target: 'modules',
outDir: 'dist', //指定输出路径
assetsDir: 'assets', // 指定生成静态资源的存放路径
minify: 'terser' // 混淆器,terser构建后文件体积更小
},
server: {
cors: true, // 默认启用并允许任何源
open: true, // 在服务器启动时自动在浏览器中打开应用程序
//反向代理配置,注意rewrite写法,开始没看文档在这里踩了坑
proxy: {
// '/api': {
// target:'http://isinpc.natappfree.cc',
// changeOrigin: true,
// rewrite: (path) => path.replace(/^\/api/, '')
// }
'/api': {
target: 'http://localhost:8081',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, '')
}
}
}
})
这里的proxy是配置转发,前端使用ajax或者axios的时候,就会将localhost:3000
自动转换成对应的值。这里方便合作还使用到了内网穿透的api,来换切换使用。