3-1 登陆页面布局开发
视口(Viewport)
目的:让手机的小屏幕尽可能完整显示整个网页,即实现理想视口
1)布局视口layout viewport
iOS, Android 都将布局视口分辨率设置为 980px,故 PC端的网页能在手机端呈现,不过元素很小
2)视觉视口visual viewport
用户看到的网站区域,可通过放缩操作改变;
缩放值 = 理想视口宽度 / 视觉视口宽度
3)理想视口ideal viewport
指布局视口宽度=屏幕宽度,无需左右滚动条
定位/浮动后脱离标准流
表现:元素像飞起来一样;在z轴的另一层显示,原先空间被其他元素占据
后果:元素宽度由内容宽度决定(自动撑开);
3-2 路由守卫-实现基础登陆校验功能
定义:通过跳转或取消的方式守卫导航,比如登录鉴权(没有登录不能进入个人中心页)等;
有多种机会植入路由导航过程中:全局的, 单个路由独享的, 或者组件级的
可以简单的理解为一座房子的门口的保安,想要进入这个房子就必须通过保安的检查,要告诉路由守卫你从哪里来?总不能随便陌生人就给放进去?要到哪里去?然后保安再告诉你下一步该怎么做?如果你的确是这个房子主人允许进入的人,那就让你进入,否则就要打电话给房子主人,跟房主商量(登录注册),给你权限
文件:router/index.js
router:路由对象
route:当前激活路由的状态信息
routes:路由实例的配置项
const routes = [
{
path: "/",
name: "Home",
component: Home,
},
// 功能:若已是登陆状态,不能再访问Login页面
// 即输入Login页面网址,设置跳转到Home页面
{
path: "/login",
name: "Login",
component: Login,
// 何时执行:访问路由页面前
beforeEnter(to, from, next) {
const { isLogin } = localStorage;
isLogin ? next({ name: "Home" }) : next();
// next();继续展示当前页面
},
},
];
// 创建路由实例
const router = createRouter({
//创建hash历史记录
history: createWebHashHistory(),
routes,
});
// 何时执行:页面跳转之前(路由切换之前)
router.beforeEach((to, from, next) => {
// 解构赋值
// 相当于const isLogin = localStorage.isLogin
const { isLogin } = localStorage;
// next();继续展示当前页面
isLogin || to.name === "Login" ? next() : next({ name: "Login" });
});
逻辑:已登录就执行表达式1;未登录就执行表达式2
问题:但执行表达式2跳到登录页之前, 又会执行beforeEach();再次进入登录状态判断,表达式1永不会执行,陷入死循环
解决:加入判断条件|| to.name === "Login
虽未登录, 但若要跳转的是Login页面, 执行表达式1
文件:Login/Login.vue
export default {
name: "Login",
// 相当于调用beforeCreate()和create()
setup() {
// 实例化路由
const router = useRouter();
// 点击登录按钮的事件
const handleLogin = () => {
// 模拟登录:暂不验证用户名和密码
//点击登录按钮,登录状态就变为已登录
localStorage.isLogin = true;
// 跳转到首页
router.push({ name: "Home" });
};
return { handleLogin };
},
};
setup()是围绕beforeCreate()和create()运行,故这俩无需显式定义
换句话说,在beforeCreate()和create()中编写的任何代码都应该直接在 setup ()编写
3-3 注册页面开发&路由串联首页/登录/注册三个页面
文件:router/index.js
const routes = [
{
path: "/",
name: "Home",
component: Home,
},
// 功能:若已是登陆状态,不能再访问register页面
// 即输入register页面网址,设置跳转到Home页面
{
path: "/register",
name: "Register",
component: Register,
beforeEnter(to, from, next) {
const { isLogin } = localStorage;
isLogin ? next({ name: "Home" }) : next();
},
},
// 功能:若已是登陆状态,不能再访问Login页面
// 即输入Login页面网址,设置跳转到Home页面
{
path: "/login",
name: "Login",
component: Login,
beforeEnter(to, from, next) {
const { isLogin } = localStorage;
isLogin ? next({ name: "Home" }) : next();
},
},
];
const router = createRouter({
history: createWebHashHistory(),
routes,
});
router.beforeEach((to, from, next) => {
const { isLogin } = localStorage;
const { name } = to;
const isLoginOrRegister = name === "Login" || name === "Register";
isLogin || isLoginOrRegister ? next() : next({ name: "Login" });
});
逻辑:已登录就执行表达式1;未登录就执行表达式2
问题:但执行表达式2跳到登录页之前, 又会执行beforeEach();再次进入登录状态判断,表达式1永不会执行,陷入死循环
解决:加入判断条件||isLoginOrRegister
虽未登录, 但若要跳转的是Login/Register页面, 执行表达式1
文件:register/Register.js
export default {
name: "Register",
setup() {
const router = useRouter();
// 涉及新知识:将数据存储在数据库;调用接口
// const handleRegister = () => { };
const handleLoginClick = () => {
router.push({ name: "Login" });
};
return { handleLoginClick };
},
};
文件:Login/Login.vue
export default {
name: "Login",
setup() {
const router = useRouter();
const handleLogin = () => {
localStorage.isLogin = true;
router.push({ name: "Home" });
};
// 点击“立即注册”事件:跳转到注册页
const handleRegisterClick = () => {
router.push({ name: "Register" });
};
return { handleLogin, handleRegisterClick };
},
};
3-4 使用axios 发送登陆 Mock 请求
axios:基于promise的用于浏览器和nodejs的HTTP客户端
过程:
1)双向绑定用户名\密码两个输入框;接收其中的数据
2) // 点击“登录”的事件处理函数
const handleLogin = () => {
// 根地址:https://www.fastmock.site/mock/ae8e9031947a302fed5f92425995aa19/jd
// 登录接口:api/user/login;
axios
.post(
"https://www.fastmock.site/mock/ae8e9031947a302fed5f92425995aa19/jd/api/user/login",
{
username: data.username,
password: data.password,
}
)
.then(() => {
localStorage.isLogin = true;
// 切换至首页
router.push({ name: "Home" });
})
.catch(() => {
alert("登陆失败");
});
};
3-5 请求函数的封装
需求:请求某域名下的多个接口,不希望每次发送请求都填写绝对地址
通过配置baseURL,只需传入的相对地址,其会和baseURL拼接成绝对地址
文件:utils/request.js
export const post = (url, data = {}) => {
return new Promise((resolve, reject) => {
axios
.post(url, data, {
baseURL:
"https://www.fastmock.site/mock/ae8e9031947a302fed5f92425995aa19/jd",
headers: {
"Content-Type": "application/json",
},
})
.then(
(response) => {
resolve(response.data);
},
(err) => {
reject(err);
}
);
});
};
文件:Login/Login.vue
优化异步函数
优化前,axios(基于promise,对ajax的封装)
const handleLogin = () => {
axios.post('https://www.fastmock.site/mock/ae8e9031947a302fed5f92425995aa19/jd/api/user/login', {
username: data.username,
password: data.password
}).then(() => {
localStorage.isLogin = true
router.push({ name: 'Home' })
}).catch(() => {
alert('登陆失败')
})
}
优化后,async(基于promise,遵循Generator 函数的语法糖)
async/await 是 ES7 引入的新语法,更加方便的进行异步操作
async 关键字用于函数上(async 函数返回值是 Promise 实例对象)
await 关键字只能用于 async 函数当中,后面可以直接跟一个 Promise 实例对象(await 可以得到异步结果)async/await 让异步代码看起来、表现起来更像同步代码
export default {
name: "Login",
setup() {
const data = reactive({
username: "",
password: "",
});
const router = useRouter();
const handleLogin = async () => {
try {
const result = await post("/api/user/login", {
username: data.username,
password: data.password,
});
if (result?.errno === 0) {
localStorage.isLogin = true;
router.push({ name: "Home" });
} else {
alert("登陆失败");
}
} catch (e) {
alert("请求失败");
}
};
const handleRegisterClick = () => {
router.push({ name: "Register" });
};
return { handleLogin, handleRegisterClick, data };
},
};
3-6 Toast 弹窗组件的开发
默认的弹窗样式不够美观
属于公共组件,放于src下的components文件
.toast {
// 在浏览器窗口垂直水平居中
position: fixed;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
}
fixed相对浏览器窗口进行定位
absolute相对最近的、有定位的祖先元素进行定位
文件:Login/Login.vue
<Toast v-if="data.showToast" :message="data.toastMessage"/>
v-bind 简写为:,作用:1.动态绑定数据或者属性 2.给组件传值
v-model 作用:用于表单控件双向绑定数据
v-on简写为@,作用:绑定事件
export default {
name: "Login",
// 注册Toast弹窗组件
components: { Toast },
setup() {
const data = reactive({
username: "",
password: "",
showToast: false,
toastMessage: "",
});
const router = useRouter();
// 定义变量showToas,控制toast组件是否渲染
const showToast = (message) => {
data.showToast = true;
data.toastMessage = message;
// 弹窗2秒后消失
setTimeout(() => {
data.showToast = false;
data.toastMessage = "";
}, 2000);
};
const handleLogin = async () => {
try {
const result = await post("111/api/user/login", {
username: data.username,
password: data.password,
});
if (result?.errno === 0) {
localStorage.isLogin = true;
router.push({ name: "Home" });
} else {
showToast("登陆失败");
}
} catch (e) {
showToast("请求失败");
}
};
const handleRegisterClick = () => {
router.push({ name: "Register" });
};
return { handleLogin, handleRegisterClick, data };
},
};
3-7 通过代码拆分增加逻辑可维护性
文件:components/Toast.js
export const useToastEffect = () => {
const toastData = reactive({
showToast: false,
toastMessage: ''
})
const showToast = (message) => {
toastData.showToast = true
toastData.toastMessage = message
setTimeout(() => {
toastData.showToast = false
toastData.toastMessage = ''
}, 2000)
}
return { toastData, showToast }
}
组件引入顺序
系统组件-公共组件-页面组件
3-8/9 Setup函数的职责以及注册功能的实现
代码流程控制函数setup()
export default {
name: "Login",
components: { Toast },
//Login相关逻辑和Register相关逻辑抽象出去后,就剩代码执行的流程
//代码流程控制函数
setup() {
const { show, toastMessage, showToast } = useToastEffect();
const { username, password, handleLogin } = useLoginEffect(showToast);
const { handleRegisterClick } = useRegisterEffect();
return {
username,
password,
show,
toastMessage,
handleLogin,
handleRegisterClick,
};
},
};
ref
vue操控虚拟DOM,通过ref()操控原生节点,比如改变节点内容
原理:vue操控虚拟DOM,渲染后,就有了ref属性(不能在模版中用ref进行数据绑定),对组件,获取到了组件的实例对象;对原生节点,与document.getElementById() 作用相同
toRefs作用
https://www.cnblogs.com/IwishIcould/p/14891182.html
data.username,data.password在视图上一个个属性点(.) 会很麻烦,如何不通过点也能正常渲染和自动更新视图呢
const { username, password } = toRefs(data);
可选链( ?. )
允许读取位于连接对象链深处的属性的值,而不必明确验证链中的每个引用是否有效。
功能类似于 . 链式操作符
不同之处在于,在引用为空(nullish ) (null 或者 undefined) 的情况下不会引起错误,该表达式短路返回值是 undefined。
与函数调用一起使用时,如果给定的函数不存在,则返回 undefined。