总结
=============================================================
从转行到现在,差不多两年的时间,虽不能和大佬相比,但也是学了很多东西。我个人在学习的过程中,习惯简单做做笔记,方便自己复习的时候能够快速理解,现在将自己的笔记分享出来,和大家共同学习。
开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】
个人将这段时间所学的知识,分为三个阶段:
第一阶段: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)