2024年Web前端最新Vue3项目配置,Web前端高级工程师每日面试题精选

总结

=============================================================

从转行到现在,差不多两年的时间,虽不能和大佬相比,但也是学了很多东西。我个人在学习的过程中,习惯简单做做笔记,方便自己复习的时候能够快速理解,现在将自己的笔记分享出来,和大家共同学习。

开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】

个人将这段时间所学的知识,分为三个阶段:

第一阶段:HTML&CSS&JavaScript基础

第二阶段:移动端开发技术

第三阶段:前端常用框架

  • 推荐学习方式:针对某个知识点,可以先简单过一下我的笔记,如果理解,那是最好,可以帮助快速解决问题;如果因为我的笔记太过简陋不理解,可以关注我以后我还会继续分享。

  • 大厂的面试难在,针对一个基础知识点,比如JS的事件循环机制,不会上来就问概念,而是换个角度,从题目入手,看你是否真正掌握。所以对于概念的理解真的很重要。


### 权限


#### 用户登录成功后将用户信息存储到store中


`src/views/login/index.vue`



function handleLogin() {
loginFormRef.value.validate((valid: boolean) => {
if (valid) {
state.loading = true;
user.login(state.loginForm).then(() => {
router.push({ path: state.redirect || ‘/’, query: state.otherQuery });
state.loading = false;
}).catch(() => {
state.loading = false;
handleCaptchaGenerate();
});
} else {
return false;
}
});
}


#### store


`src/store/modules/user.ts`



import { defineStore } from ‘pinia’;
import { LoginFormData } from ‘@/types/api/system/login’;
import { UserState } from ‘@/types/store/user’;

import { localStorage } from ‘@/utils/storage’;
import { login, logout } from ‘@/api/system/sys_login’;
import { getUserPerm } from ‘@/api/system/user’;
import { resetRouter } from ‘@/router’;

const useUserStore = defineStore({
id: ‘user’,
state: (): UserState => ({
userId: 0,
openId: ‘’,
token: localStorage.get(‘token’) || ‘’,
nickname: ‘’,
avatarUrl: ‘’,
roleNames: [],
permissionTreeList: [],
}),
actions: {
async RESET_STATE() {
this.$reset();
},
/**
* 登录
*/
login(loginData: LoginFormData) {
const { username, password, code, uuid } = loginData;
return new Promise((resolve, reject) => {
login({
username: username.trim(),
password: password,
grant_type: ‘captcha’,
code: code,
uuid: uuid,
}).then((response) => {
const { tokenType, value } = response.data;
const token = tokenType + ’ ’ + value;
localStorage.set(‘token’, token);
this.token = token;
resolve(token);
}).catch((error) => {
reject(error);
});
});
},
/**
* 获取用户信息(昵称、头像、角色集合、权限集合)
*/
getUserInfo() {
return new Promise((resolve, reject) => {
getUserPerm().then(({ data }: any) => {
if (!data) {
return reject(‘Verification failed, please Login again.’);
}
const { userId, openId, nickname, avatarUrl, roleNames, permissionTreeList } = data;
this.userId = userId;
this.openId = openId;
this.nickname = nickname;
this.avatarUrl = avatarUrl;
this.roleNames = roleNames;
this.permissionTreeList = permissionTreeList;
resolve(data);
}).catch((error: any) => {
reject(error);
});
});
},

/\*\*

* 注销
*/
logout() {
return new Promise((resolve, reject) => {
logout().then(() => {
localStorage.remove(‘token’);
this.RESET_STATE();
resetRouter();
resolve(null);
}).catch((error) => {
reject(error);
});
});
},

/\*\*

* 清除 Token
*/
resetToken() {
return new Promise((resolve) => {
localStorage.remove(‘token’);
this.RESET_STATE();
resolve(null);
});
},
},
});

export default useUserStore;


`src/store/modules/permission.ts`



import { PermissionState } from ‘@/types/store/permission’;
import { RouteRecordRaw } from ‘vue-router’;
import { defineStore } from ‘pinia’;
import { constantRoutes } from ‘@/router’;
import useStore from ‘@/store’;

const modules = import.meta.glob(‘…/…/views/**/**.vue’);
export const Layout = () => import(‘@/layout/index.vue’);
export const parentView = () => import(‘@/layout/parentView.vue’);

export const filterAsyncRoutes = (
routes: RouteRecordRaw[],
roleNames: string[]
) => {
const res: RouteRecordRaw[] = [];
routes.forEach((route) => {
const tmp = { …route } as any;
if (tmp.component === ‘Layout’) {
tmp.component = Layout;
} else if (tmp.component === ‘parentView’) {
tmp.component = parentView
} else {
const component = modules[../../views/${tmp.component}.vue] as any;
if (component) {
tmp.component = modules[../../views/${tmp.component}.vue];
} else {
tmp.component = modules[../../views/error-page/404.vue];
}
}
res.push(tmp);
if (tmp.children) {
tmp.children = filterAsyncRoutes(tmp.children, roleNames);
}

});
return res;
};

/**
* 侧边栏权限路由
*/
const usePermissionStore = defineStore({
id: ‘permission’,
state: (): PermissionState => ({
routes: [],
addRoutes: [],
}),
actions: {
setRoutes(routes: RouteRecordRaw[]) {
this.addRoutes = routes;
this.routes = constantRoutes.concat(routes);
},
generateRoutes(roleNames: string[]) {
const { user } = useStore();
const accessedRoutes = filterAsyncRoutes(user.permissionTreeList, roleNames);
return new Promise((resolve, reject) => {
this.setRoutes(accessedRoutes);
resolve(accessedRoutes);
});
},
},
});

export default usePermissionStore;


#### router


`src/router/index.ts`



import { createRouter, createWebHashHistory, RouteRecordRaw } from ‘vue-router’;
import useStore from ‘@/store’;

// 静态路由
export const constantRoutes: Array = [
{
path: ‘/login’,
component: () => import(‘@/views/login/index.vue’)
},
{
path: ‘/test’,
component: () => import(‘@/views/test/index.vue’)
},
{
path: ‘/404’,
component: () => import(‘@/views/error-page/404.vue’)
},
];

// 创建路由
const router = createRouter({
history: createWebHashHistory(),
routes: constantRoutes as RouteRecordRaw[],
});

// 重置路由
export function resetRouter() {
const { permission } = useStore();
permission.routes.forEach((route) => {
const name = route.name;
if (name && router.hasRoute(name)) {
router.removeRoute(name);
}
});
}

export default router;


#### 刷新路由时权限


`permission.ts`



import router from ‘@/router’;
import { ElMessage } from ‘element-plus’;
import useStore from ‘@/store’;
import NProgress from ‘nprogress’;
import ‘nprogress/nprogress.css’;
NProgress.configure({ showSpinner: false }); // 进度环显示/隐藏

// 白名单路由
const whiteList = [‘/login’, ‘/auth-redirect’];

router.beforeEach(async (to, from, next) => {
NProgress.start();
const { user, permission } = useStore();
const hasToken = user.token;
if (hasToken) {
// 登录成功,跳转到首页
if (to.path === ‘/login’) {
next({ path: ‘/’ });
NProgress.done();
} else {
const hasGetUserInfo = user.roleNames.length > 0;
if (hasGetUserInfo) {
if (to.matched.length === 0) {
from.name ? next({ name: from.name as any }) : next(‘/401’);
} else {
next();
}
} else {
try {
await user.getUserInfo();
const roleNames = user.roleNames;
const accessRoutes: any = await permission.generateRoutes(roleNames);
accessRoutes.forEach((route: any) => {
router.addRoute(route);
});
next({ …to, replace: true });
} catch (error) {
// 移除 token 并跳转登录页
await user.resetToken();
ElMessage.error((error as any) || ‘Has Error’);
next(/login?redirect=${to.path});
NProgress.done();
}
}
}
} else {
// 未登录可以访问白名单页面(登录页面)
if (whiteList.indexOf(to.path) !== -1) {
next();
} else {
next(/login?redirect=${to.path});
NProgress.done();
}
}
});

router.afterEach(() => {
NProgress.done();
});


`main.ts`



// 路由权限
import ‘@/permission’;


### 动态路由布局


#### 动态路由布局相关页面


具体见源码`src/layout`部分


`src/layout/index.vue`




`src/layout/parentView.vue`




`src/layout/components/AppMain.vue`




`src/layout/components/index.ts`



export { default as Navbar } from ‘./Navbar.vue’;
export { default as AppMain } from ‘./AppMain.vue’;
export { default as TagsView } from ‘./TagsView/index.vue’;


`src/layout/components/Navbar.vue`



import useStore from ‘@/store’;

// 组件依赖
import Breadcrumb from ‘@/components/Breadcrumb/index.vue’;
import Hamburger from ‘@/components/Hamburger/index.vue’;

const { app, user, tagsView } = useStore();

const route = useRoute();
const router = useRouter();

const sidebar = computed(() => app.sidebar);
const device = computed(() => app.device);
const avatarUrl = computed(() => user.avatarUrl);
const nickname = computed(() => user.nickname);

function toggleSideBar() {
app.toggleSidebar();
}

function logout() {
ElMessageBox.confirm(‘确定注销并退出系统吗?’, ‘提示’, {
confirmButtonText: ‘确定’,
cancelButtonText: ‘取消’,
type: ‘warning’,
}).then(() => {
user
.logout()
.then(() => {
tagsView.delAllViews();
})
.then(() => {
router.push(/login?redirect=${route.fullPath});
});
});
}


`src/layout/components/Sidebar/index.vue`




`src/layout/components/Sidebar/Link.vue`




`src/layout/components/Sidebar/Logo.vue`




`src/layout/components/Sidebar/SidebarItem.vue`



:base-path=“resolvePath(child.path)” class=“nest-menu” />


`src/layout/components/TagsView/index.vue`




`src/layout/components/TagsView/ScrollPane.vue`






---


### 按钮权限


#### 自定义指令 - 按钮权限


`src/directive/permission/index.ts`



import useStore from ‘@/store’;
import { Directive, DirectiveBinding } from ‘vue’;

// 自定义权限指令v-hasPerm v-hasRole

/**
* 按钮权限校验
* array : v-hasPerm=“[‘sys:user:add’,‘sys:user:edit’]”
* single : v-hasPerm=“‘sys:user:add’”
*/
export const hasPerm: Directive = {
mounted(el: HTMLElement, binding: DirectiveBinding) {
const { user } = useStore();
const { value } = binding;

// 当前路由
const currentRouteUrl = window.location.hash.replace('#/', '')
console.log('当前路由:', currentRouteUrl)
const btnPermList = getBtnPermList(currentRouteUrl, user.permissionTreeList, [])
console.log('拥有的按钮权限:', btnPermList);
if (value) {
  // DOM绑定需要的按钮权限标识
  const requiredPerms = value instanceof Array ? value : [value];
  console.log('需要的按钮权限:', requiredPerms);
  const hasPerm = btnPermList.some(btnPerm => {
    return requiredPerms.includes(btnPerm);
  });
  if (!hasPerm) {
    el.parentNode && el.parentNode.removeChild(el);
  }
} else {
  throw new Error(
    "need perms! Like v-has-perm=\"['sys:user:add','sys:user:edit']\""
  );
}

},
};

/**
* 获取当前路由下的按钮权限
* @param currentRouteUrl 当前路由url ex: system/user/index
* @param permissionTreeList 权限菜单树
* @param btnPermList 按钮权限
* @returns 按钮权限
*/
function getBtnPermList(currentRouteUrl: string, permissionTreeList: any, btnPermList: Array): Array {
if (permissionTreeList) {
permissionTreeList.forEach((e: {
meta: any;
component: string;
children: any;
}) => {
if (e.component === currentRouteUrl || e.component === currentRouteUrl + ‘/index’) {
e.meta.btnPermList.forEach((btnPerm: string) => {
btnPermList.push(btnPerm)
})
}
const childList = e.children
if (childList) {
getBtnPermList(currentRouteUrl, childList, btnPermList)
} else {
return btnPermList
}
});
}
return btnPermList
}

/**
* 角色权限校验
*/
export const hasRole: Directive = {
mounted(el: HTMLElement, binding: DirectiveBinding) {
const { value } = binding;
if (value) {
// DOM绑定需要的角色编码
const requiredRoles = value;
const { user } = useStore();
const hasRole = user.roleNames.some((perm) => {
return requiredRoles.includes(perm);
});
if (!hasRole) {
el.parentNode && el.parentNode.removeChild(el);
}
} else {
throw new Error(“need roles! Like v-has-role=”[‘admin’,‘test’]“”);
}
},
};


`src/directive/index.ts`



export { hasPerm, hasRole } from ‘./permission’;


#### 注册自定义指令


`main.ts`



// 自定义指令(按钮权限)
import * as directive from ‘@/directive’;
Object.keys(directive).forEach((key) => {
app.directive(key, (directive as { [key: string]: Directive })[key]);
});


#### 页面使用demo



<el-button v-hasPerm=“‘add’” type=“primary” @click=“handleCreate”>添加




---


### debug调试


#### 方式一:`debugger`


1. 代码中添加`debugger`
2. 浏览器`F12` 即可进入调试



methods: {
async refreshTableData() {
debugger
this.$refs.baseTable.refresh()
}
}


#### 方式二:vscode


![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/bcd59ba8a8984591a676f0670347a929.png#pic_center)


`launch.json`



> 
> 有个这个文件后,可直接在vscode中F5打开当前服务
> 
> 


### 最后

**小编的一位同事在校期间连续三年参加ACM-ICPC竞赛。从参赛开始,原计划每天刷一道算法题,实际上每天有时候不止一题,一年最终完成了 600+:**

**凭借三年刷题经验,他在校招中很快拿到了各大公司的offer。**



**入职前,他把他的刷题经验总结成1121页PDF书籍,作为礼物赠送给他的学弟学妹,希望同学们都能在最短时间内掌握校招常见的算法及解题思路。**

![](https://img-blog.csdnimg.cn/img_convert/01358b165616eb9429e16241cf00d213.png)

**整本书,我仔细看了一遍,作者非常细心地将常见核心算法题和汇总题拆分为4个章节。**

![](https://img-blog.csdnimg.cn/img_convert/3425ac600577268ce7d1f69b2002194d.png)

**[开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】](https://bbs.csdn.net/forums/4304bb5a486d4c3ab8389e65ecb71ac0)**

**而对于有时间的同学,作者还给出了他结合众多数据结构算法书籍,挑选出的一千多道题的解题思路和方法,以供有需要的同学慢慢研究。**

![](https://img-blog.csdnimg.cn/img_convert/c941214256693bd4eb05782f3f676721.png)



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值