Compare commits
11 Commits
7be97e7ea7
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2a65ba8c6a | ||
|
|
7c0cbe1320 | ||
|
|
acb1445575 | ||
|
|
3850ce7399 | ||
|
|
d0d8be443b | ||
|
|
d5ff59ac76 | ||
|
|
8b83f63235 | ||
|
|
1071f4db05 | ||
|
|
3435848495 | ||
|
|
00d3c9e4c6 | ||
|
|
3afbee1535 |
@@ -6,12 +6,27 @@
|
|||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>居中专题</title>
|
<title>居中专题</title>
|
||||||
<style>
|
<style>
|
||||||
|
html {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
background-color: rgb(255, 248, 238);
|
background-color: rgb(255, 248, 238);
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
position: fixed;
|
||||||
}
|
}
|
||||||
|
|
||||||
.stage {
|
.stage {
|
||||||
|
position: absolute;
|
||||||
|
left: 50%;
|
||||||
|
top: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
background-color: white;
|
background-color: white;
|
||||||
border: 1px solid rgba(255, 156, 7, 0.25);
|
border: 1px solid rgba(255, 156, 7, 0.25);
|
||||||
border-radius: 20px;
|
border-radius: 20px;
|
||||||
@@ -34,7 +49,7 @@
|
|||||||
|
|
||||||
<body>
|
<body>
|
||||||
<div class="stage">
|
<div class="stage">
|
||||||
<div class="box">把我放到中间</div>
|
<div class="box">把我放到中间 transform: translate(-50%, -50%); </div>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,14 @@
|
|||||||
const userName = "";
|
const userName = "小明";
|
||||||
let currentStage = "";
|
let currentStage = "学习中";
|
||||||
let completedExercises = 0;
|
let completedExercises = 0;
|
||||||
|
completedExercises = 2;
|
||||||
|
console.log('学习者:' + userName);
|
||||||
|
console.log('学习阶段:' + currentStage);
|
||||||
|
console.log('学习次数:' + completedExercises);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// 任务:
|
// 任务:
|
||||||
// 1. 给上面的变量赋一个合理值
|
// 1. 给上面的变量赋一个合理值
|
||||||
|
|||||||
@@ -6,3 +6,11 @@ const hasPaid = true;
|
|||||||
// 1. 把 ageText 转成数字,保存到 ageNumber
|
// 1. 把 ageText 转成数字,保存到 ageNumber
|
||||||
// 2. 分别输出 studentName、ageText、ageNumber、hasPaid 的类型
|
// 2. 分别输出 studentName、ageText、ageNumber、hasPaid 的类型
|
||||||
// 3. 用模板字符串输出一句报名信息
|
// 3. 用模板字符串输出一句报名信息
|
||||||
|
const ageNumber = Number(ageText);
|
||||||
|
console.log(typeof studentName);
|
||||||
|
console.log(typeof ageText);
|
||||||
|
console.log(typeof ageNumber);
|
||||||
|
console.log(typeof hasPaid);
|
||||||
|
alert('姓名:${studentName},年龄:{ageNumber},报名状态:{hasPaid}')
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,31 @@ const score = 86;
|
|||||||
// 1. 用布尔值保存是否及格
|
// 1. 用布尔值保存是否及格
|
||||||
// 2. 用 if...else if...else 判断等级
|
// 2. 用 if...else if...else 判断等级
|
||||||
// 3. 输出分数、是否及格、等级
|
// 3. 输出分数、是否及格、等级
|
||||||
|
/*- 根据分数计算是否及格
|
||||||
|
- 根据分数输出等级
|
||||||
|
- 90 分及以上为 A
|
||||||
|
- 80 到 89 为 B
|
||||||
|
- 60 到 79 为 C
|
||||||
|
- 60 以下为 D*/
|
||||||
let passed = false;
|
let passed = false;
|
||||||
let grade = "";
|
let grade = "";
|
||||||
|
let point = 70;
|
||||||
|
|
||||||
|
if (point >= 90) {
|
||||||
|
grade = 'A'
|
||||||
|
} else if (point >= 80 && point <= 89) {
|
||||||
|
grade = 'B'
|
||||||
|
} else if (point >= 60 && point <= 79) {
|
||||||
|
grade = 'C'
|
||||||
|
} else if (point <= 60) {
|
||||||
|
grade = 'D'
|
||||||
|
}
|
||||||
|
|
||||||
|
if (grade != 'D') {
|
||||||
|
passed = true
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('分数:${point}');
|
||||||
|
console.log('是否及格:${passed}');
|
||||||
|
console.log('等级:${grade}');
|
||||||
|
|
||||||
|
|||||||
@@ -5,3 +5,17 @@
|
|||||||
// 4. 输出 sum
|
// 4. 输出 sum
|
||||||
|
|
||||||
let sum = 0;
|
let sum = 0;
|
||||||
|
for (let i = 1; i <= 7; i++) {
|
||||||
|
console.log(i);
|
||||||
|
if (i == 4) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let j = 1
|
||||||
|
while (j <= 5) {
|
||||||
|
sum = sum + j
|
||||||
|
j++
|
||||||
|
}
|
||||||
|
console.log(sum);
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,30 @@
|
|||||||
function getAverage(score1, score2, score3) {
|
function getAverage(score1, score2, score3) {
|
||||||
|
return (score1 + score2 + score3) / 3
|
||||||
// 返回平均分
|
// 返回平均分
|
||||||
}
|
}
|
||||||
|
|
||||||
function getLevel(average) {
|
function getLevel(average) {
|
||||||
|
if (average >= 90) {
|
||||||
|
return '优秀'
|
||||||
|
} else if (average >= 60 && average < 90) {
|
||||||
|
return '良好'
|
||||||
|
} else {
|
||||||
|
return '继续努力'
|
||||||
|
}
|
||||||
// 根据平均分返回等级描述
|
// 根据平均分返回等级描述
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const average = getAverage(75, 89, 100)
|
||||||
|
const level = getLevel(average)
|
||||||
|
console.log(average);
|
||||||
|
console.log(level);
|
||||||
|
|
||||||
|
|
||||||
// 任务:
|
// 任务:
|
||||||
// 1. 调用上面两个函数
|
// 1. 调用上面两个函数
|
||||||
// 2. 输出平均分和等级
|
// 2. 输出平均分和等级
|
||||||
|
/*- 写一个 `getAverage` 函数
|
||||||
|
- 接收 3 个分数参数
|
||||||
|
- 返回平均分
|
||||||
|
- 再写一个 `getLevel` 函数
|
||||||
|
- 根据平均分返回“优秀”“良好”“继续努力”*/
|
||||||
|
|||||||
@@ -4,3 +4,13 @@ const topics = ["HTML", "CSS", "JavaScript"];
|
|||||||
// 1. 往 topics 里新增一个主题
|
// 1. 往 topics 里新增一个主题
|
||||||
// 2. 输出 topics 的长度
|
// 2. 输出 topics 的长度
|
||||||
// 3. 用循环输出每一项
|
// 3. 用循环输出每一项
|
||||||
|
/*- 创建一个包含 3 个学习主题的数组
|
||||||
|
- 新增 1 个学习主题
|
||||||
|
- 输出数组长度
|
||||||
|
- 依次输出每个学习主题*/
|
||||||
|
topics.push('Vue')
|
||||||
|
console.log(topics.length);
|
||||||
|
for (let i = 0; i < topics.length; i++) {
|
||||||
|
console.log(topics[i]);
|
||||||
|
|
||||||
|
}
|
||||||
|
|||||||
@@ -9,3 +9,15 @@ const course = {
|
|||||||
// 2. 新增 teacher 属性
|
// 2. 新增 teacher 属性
|
||||||
// 3. 输出完整对象
|
// 3. 输出完整对象
|
||||||
// 4. 输出 title 和 teacher
|
// 4. 输出 title 和 teacher
|
||||||
|
/*- 创建一个课程对象
|
||||||
|
- 至少包含名称、课时、是否完结
|
||||||
|
- 修改其中一个属性
|
||||||
|
- 新增一个老师属性
|
||||||
|
- 输出完整对象和其中两个单独属性*/
|
||||||
|
course.finished = true
|
||||||
|
course.teacher = '李老师'
|
||||||
|
console.log(course);
|
||||||
|
console.log(course.title);
|
||||||
|
console.log(course.lessons);
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -6,3 +6,11 @@ const tags = ["变量", "条件", "函数"];
|
|||||||
// 2. 转成大写
|
// 2. 转成大写
|
||||||
// 3. 判断是否包含 JS
|
// 3. 判断是否包含 JS
|
||||||
// 4. 把 tags 用顿号连接成一句话
|
// 4. 把 tags 用顿号连接成一句话
|
||||||
|
/*- 把一段带空格的文本去掉首尾空格
|
||||||
|
- 把结果转成大写
|
||||||
|
- 判断里面是否包含 `JS`
|
||||||
|
- 把一个标签数组拼成一个字符串输出*/
|
||||||
|
const rawTrim = rawTitle.trim()
|
||||||
|
const rawTo = rawTitle.toUpperCase()
|
||||||
|
const result = rawTitle.includes("JS")
|
||||||
|
const tagsJoin = rawTitle.join(",")
|
||||||
@@ -2,10 +2,27 @@ function createCounter() {
|
|||||||
let count = 0;
|
let count = 0;
|
||||||
|
|
||||||
// 返回一个函数
|
// 返回一个函数
|
||||||
|
function a() {
|
||||||
|
count++
|
||||||
|
return count
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const counterA = createCounter()
|
||||||
|
const counterB = createCounter()
|
||||||
|
|
||||||
|
console.log(counterA());
|
||||||
|
console.log(counterA());
|
||||||
|
console.log(counterB());
|
||||||
|
|
||||||
// 任务:
|
// 任务:
|
||||||
// 1. 创建 counterA 和 counterB
|
// 1. 创建 counterA 和 counterB
|
||||||
// 2. 连续调用 counterA 两次
|
// 2. 连续调用 counterA 两次
|
||||||
// 3. 再调用 counterB 一次
|
// 3. 再调用 counterB 一次
|
||||||
// 4. 观察为什么两个计数器互不影响
|
// 4. 观察为什么两个计数器互不影响
|
||||||
|
/*写一个 `createCounter` 函数
|
||||||
|
- 在函数内部定义 `count`
|
||||||
|
- 返回一个内部函数
|
||||||
|
- 每次调用内部函数时,`count` 都加 1
|
||||||
|
- 创建两个不同的计数器
|
||||||
|
- 观察为什么它们各自记住了自己的 `count`*/
|
||||||
|
|||||||
@@ -7,14 +7,35 @@ const scores = [78, 85, 92];
|
|||||||
|
|
||||||
function getAverageScore(list) {
|
function getAverageScore(list) {
|
||||||
// 计算平均分
|
// 计算平均分
|
||||||
|
let sum = 0
|
||||||
|
for (let i = 0; i < list.length; i++) {
|
||||||
|
sum = sum + list[i]
|
||||||
|
}
|
||||||
|
return sum / list.length
|
||||||
}
|
}
|
||||||
|
|
||||||
function getLevel(average) {
|
function getLevel(average) {
|
||||||
// 返回等级
|
// 返回等级
|
||||||
|
if (average >= 80) {
|
||||||
|
return "可以进入下一阶段"
|
||||||
|
} else {
|
||||||
|
return "保持当前等级"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const average = getAverageScore(scores)
|
||||||
|
const level = getLevel(average)
|
||||||
|
|
||||||
|
console.log("姓名:" + student.name + "阶段:" + student.stage + "平均分:" + "average" + "等级:" + level);
|
||||||
|
|
||||||
// 任务:
|
// 任务:
|
||||||
// 1. 计算平均分
|
// 1. 计算平均分
|
||||||
// 2. 计算等级
|
// 2. 计算等级
|
||||||
// 3. 输出姓名、阶段、平均分、等级
|
// 3. 输出姓名、阶段、平均分、等级
|
||||||
// 4. 如果平均分 >= 80,输出“可以进入下一阶段”
|
// 4. 如果平均分 >= 80,输出“可以进入下一阶段”
|
||||||
|
/*一个 `student` 对象
|
||||||
|
- 一个 `scores` 数组
|
||||||
|
- 一个计算平均分的函数
|
||||||
|
- 一个判断等级的函数
|
||||||
|
- 输出姓名、平均分、等级
|
||||||
|
- 如果平均分大于等于 80,再输出“可以进入下一阶段”*/
|
||||||
@@ -13,15 +13,24 @@ function compareScope() {
|
|||||||
|
|
||||||
console.log("块内:", lessonName, chapterName, stage);
|
console.log("块内:", lessonName, chapterName, stage);
|
||||||
}
|
}
|
||||||
|
var lessonName = "变量";
|
||||||
|
let chapterName = "作用域";
|
||||||
|
const stage = "进阶";
|
||||||
|
console.log("块内:", lessonName);
|
||||||
// 任务:
|
// 任务:
|
||||||
// 1. 输出 lessonName
|
// 1. 输出 lessonName
|
||||||
// 2. 不要直接在这里输出 chapterName,否则会报错
|
// 2. 不要直接在这里输出 chapterName,否则会报错
|
||||||
// 3. 用一句注释说明为什么
|
// 3. 用一句注释说明为什么
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
compareScope();
|
compareScope();
|
||||||
|
|
||||||
// 任务:
|
// 任务:
|
||||||
// 4. 试着重新给 const 声明的值赋值
|
// 4. 试着重新给 const 声明的值赋值
|
||||||
// 5. 观察会发生什么
|
// 5. 观察会发生什么
|
||||||
|
/*观察 `var` 声明前为什么能访问到 `undefined`
|
||||||
|
- 用 `var` 声明一个函数内部变量
|
||||||
|
- 用 `let` 声明一个代码块内部变量
|
||||||
|
- 在可访问的位置输出它们
|
||||||
|
- 观察为什么 `var` 在块外还能访问,而 `let` 不行
|
||||||
|
- 再写一段代码证明 `const` 不能被重新赋值*/
|
||||||
@@ -25,3 +25,11 @@
|
|||||||
|
|
||||||
- [starter.js](/Users/lijiaqing/home/wwwroot/front-end-example/03-javascript-core/12-this-keyword/starter.js)
|
- [starter.js](/Users/lijiaqing/home/wwwroot/front-end-example/03-javascript-core/12-this-keyword/starter.js)
|
||||||
- [answer.js](/Users/lijiaqing/home/wwwroot/front-end-example/03-javascript-core/12-this-keyword/answer.js)
|
- [answer.js](/Users/lijiaqing/home/wwwroot/front-end-example/03-javascript-core/12-this-keyword/answer.js)
|
||||||
|
|
||||||
|
function a() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
const b = () => {
|
||||||
|
|
||||||
|
}
|
||||||
@@ -4,15 +4,25 @@ const student = {
|
|||||||
sayHello() {
|
sayHello() {
|
||||||
// 任务:
|
// 任务:
|
||||||
// 1. 用 this.name 输出问候语
|
// 1. 用 this.name 输出问候语
|
||||||
|
console.log(this.name + "你好!");
|
||||||
|
|
||||||
},
|
},
|
||||||
createArrowReporter() {
|
createArrowReporter() {
|
||||||
// 任务:
|
// 任务:
|
||||||
// 2. 返回一个箭头函数
|
// 2. 返回一个箭头函数
|
||||||
// 3. 在箭头函数里输出 this.name
|
// 3. 在箭头函数里输出 this.name
|
||||||
|
return () => {
|
||||||
|
console.log(this.name);
|
||||||
|
|
||||||
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
// 任务:
|
// 任务:
|
||||||
// 4. 调用 student.sayHello()
|
// 4. 调用 student.sayHello()
|
||||||
|
student.sayHello()
|
||||||
// 5. 把 student.sayHello 赋值给 detachedHello 再调用
|
// 5. 把 student.sayHello 赋值给 detachedHello 再调用
|
||||||
|
const detachedHello = student.sayHello
|
||||||
|
detachedHello()
|
||||||
// 6. 调用 createArrowReporter 返回的新函数
|
// 6. 调用 createArrowReporter 返回的新函数
|
||||||
|
student.createArrowReporter()()
|
||||||
|
|||||||
@@ -5,6 +5,20 @@ const students = [
|
|||||||
{ name: "小陈", finished: false },
|
{ name: "小陈", finished: false },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const scores1 = scores.map(item => item = item + '分')
|
||||||
|
const scores2 = scores.filter(item => item > 60)
|
||||||
|
const scores3 = scores.reduce((acc, item) => { return item + acc }, 0)
|
||||||
|
const scores4 = scores.find(item => item >= 90)
|
||||||
|
const scores5 = scores.some(item => item >= 90)
|
||||||
|
const scores6 = students.every(item => item.finished === true)
|
||||||
|
|
||||||
|
console.log("分数:" + scores1);
|
||||||
|
console.log("及格分:" + scores2);
|
||||||
|
console.log("总分:" + scores3);
|
||||||
|
console.log("第一个 >= 90 的分数:" + scores4);
|
||||||
|
console.log("是否存在不及格:" + scores5);
|
||||||
|
console.log("是否都及格:" + scores6);
|
||||||
|
|
||||||
// 任务:
|
// 任务:
|
||||||
// 1. 用 map 生成 ["58分", ...]
|
// 1. 用 map 生成 ["58分", ...]
|
||||||
// 2. 用 filter 筛出及格分
|
// 2. 用 filter 筛出及格分
|
||||||
@@ -12,3 +26,9 @@ const students = [
|
|||||||
// 4. 用 find 找到第一个 >= 90 的分数
|
// 4. 用 find 找到第一个 >= 90 的分数
|
||||||
// 5. 用 some 判断是否存在不及格
|
// 5. 用 some 判断是否存在不及格
|
||||||
// 6. 用 every 判断 students 是否都 finished 为 true
|
// 6. 用 every 判断 students 是否都 finished 为 true
|
||||||
|
/* 用 `map` 生成带单位的新数组
|
||||||
|
- 用 `filter` 找出及格成绩
|
||||||
|
- 用 `reduce` 计算总分
|
||||||
|
- 用 `find` 找出第一条大于等于 90 的成绩
|
||||||
|
- 用 `some` 判断是否有人不及格
|
||||||
|
- 用 `every` 判断是否全部完成考试*/
|
||||||
@@ -19,7 +19,16 @@ function printStepTwo() {
|
|||||||
function updateUser(user) {
|
function updateUser(user) {
|
||||||
// 任务:
|
// 任务:
|
||||||
// 1. 修改 user.city
|
// 1. 修改 user.city
|
||||||
|
user.city = '北京'
|
||||||
}
|
}
|
||||||
|
scoreB = 90
|
||||||
|
console.log('scoreA' + scoreA + ',scoreB' + scoreB);
|
||||||
|
userB.city = '秦皇岛'
|
||||||
|
console.log(userA.city, userB.city);
|
||||||
|
printStepOne()
|
||||||
|
printStepTwo()
|
||||||
|
updateUser(userA)
|
||||||
|
console.log(userA);
|
||||||
|
|
||||||
// 任务:
|
// 任务:
|
||||||
// 2. 修改 scoreB,观察 scoreA 是否变化
|
// 2. 修改 scoreB,观察 scoreA 是否变化
|
||||||
|
|||||||
@@ -6,9 +6,43 @@ const finalComment = null;
|
|||||||
let statusText = "";
|
let statusText = "";
|
||||||
const finishedRecords = [];
|
const finishedRecords = [];
|
||||||
|
|
||||||
|
switch (learningStatus) {
|
||||||
|
case 'review':
|
||||||
|
console.log('已学习')
|
||||||
|
break
|
||||||
|
case 'ing':
|
||||||
|
console.log('学习中');
|
||||||
|
break
|
||||||
|
case 'prepare':
|
||||||
|
console.log('未学习');
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
console.log('状态未知');
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(optionalNote, finalComment);
|
||||||
|
|
||||||
|
for (let i = 0; i < records.length; i++) {
|
||||||
|
if (records[i] === null || records[i] === undefined) {
|
||||||
|
console.log(`第${i + 1}项为空值,停止读取`)
|
||||||
|
break
|
||||||
|
} else {
|
||||||
|
finishedRecords.push(records[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(finishedRecords)
|
||||||
|
|
||||||
|
|
||||||
// 任务:
|
// 任务:
|
||||||
// 1. 用 switch 给 learningStatus 生成说明文字
|
// 1. 用 switch 给 learningStatus 生成说明文字
|
||||||
// 2. 输出 optionalNote 和 finalComment 分别是什么
|
// 2. 输出 optionalNote 和 finalComment 分别是什么
|
||||||
// 3. 用 for 循环读取 records
|
// 3. 用 for 循环读取 records
|
||||||
// 4. 如果遇到 undefined 或 null,就 break
|
// 4. 如果遇到 undefined 或 null,就 break
|
||||||
// 5. 输出 finishedRecords
|
// 5. 输出 finishedRecords
|
||||||
|
/*用 `switch` 根据学习状态输出不同说明
|
||||||
|
- 观察 `undefined` 和 `null` 的区别
|
||||||
|
- 用循环读取学习记录
|
||||||
|
- 如果读到 `undefined` 或 `null`,立即用 `break` 停止循环
|
||||||
|
- 输出停止前已经读取到的内容*/
|
||||||
@@ -2,6 +2,7 @@ const reviewer = {
|
|||||||
name: "林晨",
|
name: "林晨",
|
||||||
stage: "final-review",
|
stage: "final-review",
|
||||||
showTitle() {
|
showTitle() {
|
||||||
|
console.log(`学习营总复盘器`);
|
||||||
// 输出总复盘标题
|
// 输出总复盘标题
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -14,12 +15,47 @@ const lessons = [
|
|||||||
{ title: "综合回顾", score: 95, finished: true },
|
{ title: "综合回顾", score: 95, finished: true },
|
||||||
];
|
];
|
||||||
|
|
||||||
let mentorNote;
|
let mentorNote;//undefined
|
||||||
const reviewComment = null;
|
const reviewComment = null;//null
|
||||||
|
|
||||||
function getStageText(stage) {
|
function getStageText(stage) {
|
||||||
// 用 switch 返回阶段说明
|
// 用 switch 返回阶段说明
|
||||||
|
switch (stage) {
|
||||||
|
case 'final-review':
|
||||||
|
console.log('已学习');
|
||||||
|
break
|
||||||
|
case 'ing-review':
|
||||||
|
console.log('学习中');
|
||||||
|
break
|
||||||
|
case 'start-review':
|
||||||
|
console.log('未学习');
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
console.log('状态未知');
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
reviewer.showTitle()
|
||||||
|
|
||||||
|
console.log(mentorNote, reviewComment);
|
||||||
|
|
||||||
|
const lessons1 = []
|
||||||
|
for (let i = 0; i < lessons.length; i++) {
|
||||||
|
if (lessons[i].score != null) {
|
||||||
|
lessons1.push(lessons[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const sum = lessons.reduce((acc, item) => {
|
||||||
|
return acc + item.score
|
||||||
|
}, 0)
|
||||||
|
console.log("总分:" + sum);
|
||||||
|
console.log(`平均分:${sum / lessons.length}`);
|
||||||
|
|
||||||
|
|
||||||
|
const reviewerAlias = reviewer
|
||||||
|
reviewerAlias.stage = 'ing-review'
|
||||||
|
console.log(reviewer.stage, reviewerAlias.stage);
|
||||||
|
|
||||||
// 任务:
|
// 任务:
|
||||||
// 1. 调用 reviewer.showTitle()
|
// 1. 调用 reviewer.showTitle()
|
||||||
@@ -28,3 +64,11 @@ function getStageText(stage) {
|
|||||||
// 4. 用高阶函数统计课程标题、完成状态和平均分
|
// 4. 用高阶函数统计课程标题、完成状态和平均分
|
||||||
// 5. 创建 reviewerAlias 指向 reviewer,修改 stage,观察原对象是否变化
|
// 5. 创建 reviewerAlias 指向 reviewer,修改 stage,观察原对象是否变化
|
||||||
// 6. 输出最终结果
|
// 6. 输出最终结果
|
||||||
|
/* 一个带方法的 `reviewer` 对象
|
||||||
|
- 一个 `lessons` 数组
|
||||||
|
- 一个用 `switch` 输出阶段说明的函数
|
||||||
|
- 一个用 `for + break` 清洗有效数据的过程
|
||||||
|
- 至少两个数组高阶函数
|
||||||
|
- 对 `undefined` 和 `null` 的判断
|
||||||
|
- 一段对象引用变化的观察代码
|
||||||
|
- 最终输出标题、有效课程、平均分、完成状态*/
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="zh-CN">
|
<html lang="zh-CN">
|
||||||
<head>
|
|
||||||
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>获取元素</title>
|
<title>获取元素</title>
|
||||||
@@ -34,10 +35,13 @@
|
|||||||
border: 1px solid #dce8f8;
|
border: 1px solid #dce8f8;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
|
||||||
|
<body>
|
||||||
<section class="panel">
|
<section class="panel">
|
||||||
<h1 id="page-title">DOM 获取元素练习</h1>
|
<h1 id="page-title">
|
||||||
|
<p>111</p> 获取元素练习
|
||||||
|
</h1>
|
||||||
<button class="start-btn" type="button">开始学习</button>
|
<button class="start-btn" type="button">开始学习</button>
|
||||||
|
|
||||||
<div class="cards">
|
<div class="cards">
|
||||||
@@ -48,5 +52,8 @@
|
|||||||
</section>
|
</section>
|
||||||
|
|
||||||
<script src="./starter.js"></script>
|
<script src="./starter.js"></script>
|
||||||
</body>
|
|
||||||
|
|
||||||
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
@@ -3,3 +3,11 @@
|
|||||||
// 2. 用 querySelector 获取按钮
|
// 2. 用 querySelector 获取按钮
|
||||||
// 3. 用 querySelectorAll 获取全部卡片
|
// 3. 用 querySelectorAll 获取全部卡片
|
||||||
// 4. 在控制台输出标题文字、按钮文字和卡片数量
|
// 4. 在控制台输出标题文字、按钮文字和卡片数量
|
||||||
|
const a = document.getElementById("page-title")
|
||||||
|
console.log(a.textContent);
|
||||||
|
const b = document.querySelector(".start-btn")
|
||||||
|
console.log(b.textContent);
|
||||||
|
const c = document.querySelectorAll(".card")
|
||||||
|
console.log(c.length);
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -3,3 +3,9 @@
|
|||||||
// 2. 修改标题文字
|
// 2. 修改标题文字
|
||||||
// 3. 给标签加上 done 类名
|
// 3. 给标签加上 done 类名
|
||||||
// 4. 修改描述文字颜色
|
// 4. 修改描述文字颜色
|
||||||
|
const a = document.getElementById("title")
|
||||||
|
const b = document.getElementById("description")
|
||||||
|
const c = document.getElementById("badge")
|
||||||
|
a.textContent = "学习状态非常好"
|
||||||
|
a.classList.add("badge.done")
|
||||||
|
b.style.color = "red"
|
||||||
@@ -1,4 +1,22 @@
|
|||||||
// 任务:
|
// 任务:
|
||||||
// 1. 获取新增按钮、删除按钮和列表
|
// 1. 获取新增按钮、删除按钮和列表
|
||||||
// 2. 点击新增按钮时创建一个新的 li 并追加到列表
|
// 2. 点击新增按钮时创建一个新的 li 并追加到列表
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// 3. 点击删除按钮时删除最后一个 li
|
// 3. 点击删除按钮时删除最后一个 li
|
||||||
|
const add = document.getElementById("add-btn")
|
||||||
|
const remove = document.getElementById("remove-btn")
|
||||||
|
const list = document.getElementById("task-list")
|
||||||
|
add.onclick = function () {
|
||||||
|
const a = document.createElement("li")
|
||||||
|
a.textContent = "巧克力"
|
||||||
|
list.appendChild(a)
|
||||||
|
}
|
||||||
|
remove.onclick = function () {
|
||||||
|
const c = list.lastElementChild
|
||||||
|
if (c) {
|
||||||
|
c.remove()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,3 +4,26 @@ let count = 0;
|
|||||||
// 1. 获取数字元素和 3 个按钮
|
// 1. 获取数字元素和 3 个按钮
|
||||||
// 2. 点击按钮时更新 count
|
// 2. 点击按钮时更新 count
|
||||||
// 3. 每次修改后,把最新 count 渲染到页面
|
// 3. 每次修改后,把最新 count 渲染到页面
|
||||||
|
const num = document.getElementById("value")
|
||||||
|
const del = document.getElementById("decrease-btn")
|
||||||
|
const add = document.getElementById("increase-btn")
|
||||||
|
const reset = document.getElementById("reset-btn")
|
||||||
|
|
||||||
|
function ren() {
|
||||||
|
num.textContent = count
|
||||||
|
}
|
||||||
|
|
||||||
|
add.addEventListener("click", function () {
|
||||||
|
count++
|
||||||
|
ren()
|
||||||
|
})
|
||||||
|
|
||||||
|
del.addEventListener("click", function () {
|
||||||
|
count--
|
||||||
|
ren()
|
||||||
|
})
|
||||||
|
|
||||||
|
reset.addEventListener("click", function () {
|
||||||
|
count = 0
|
||||||
|
ren()
|
||||||
|
})
|
||||||
@@ -4,3 +4,17 @@
|
|||||||
// 3. 用 preventDefault() 阻止默认提交
|
// 3. 用 preventDefault() 阻止默认提交
|
||||||
// 4. 读取输入框内容,创建新 li,追加到列表
|
// 4. 读取输入框内容,创建新 li,追加到列表
|
||||||
// 5. 清空输入框
|
// 5. 清空输入框
|
||||||
|
const form = document.getElementById("todo-form")
|
||||||
|
const input = document.getElementById("todo-input")
|
||||||
|
const list = document.getElementById("todo-list")
|
||||||
|
|
||||||
|
form.addEventListener("submit", function (e) {
|
||||||
|
e.preventDefault()
|
||||||
|
const text = input.value.trim()
|
||||||
|
if (text) {
|
||||||
|
const li = document.createElement("li")
|
||||||
|
li.textContent = text
|
||||||
|
list.appendChild(li)
|
||||||
|
input.value = ""
|
||||||
|
}
|
||||||
|
})
|
||||||
@@ -3,3 +3,28 @@
|
|||||||
// 2. 给 lesson-list 绑定点击事件,使用事件委托
|
// 2. 给 lesson-list 绑定点击事件,使用事件委托
|
||||||
// 3. 点击 li 时切换 active 类名
|
// 3. 点击 li 时切换 active 类名
|
||||||
// 4. 点击删除按钮时,阻止冒泡并删除当前 li
|
// 4. 点击删除按钮时,阻止冒泡并删除当前 li
|
||||||
|
const panel = document.getElementById("panel")
|
||||||
|
panel.addEventListener("click", function () {
|
||||||
|
console.log("点击外部")
|
||||||
|
})
|
||||||
|
|
||||||
|
const list = document.getElementById("lesson-list")
|
||||||
|
list.addEventListener("click", function (event) {
|
||||||
|
|
||||||
|
const remove = event.target.closest(".remove-btn")
|
||||||
|
if (remove) {
|
||||||
|
const li = remove.closest(".lesson-item")
|
||||||
|
|
||||||
|
if (li) {
|
||||||
|
li.remove()
|
||||||
|
event.stopPropagation()
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const tog = event.target.closest(".lesson-item")
|
||||||
|
if (tog) {
|
||||||
|
tog.classList.add("active")
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
@@ -3,4 +3,24 @@
|
|||||||
// 2. 点击按钮后清空旧日志
|
// 2. 点击按钮后清空旧日志
|
||||||
// 3. 先追加“开始执行”
|
// 3. 先追加“开始执行”
|
||||||
// 4. 用 setTimeout 延迟追加“异步回调完成”
|
// 4. 用 setTimeout 延迟追加“异步回调完成”
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// 5. 再立刻追加“同步代码结束”
|
// 5. 再立刻追加“同步代码结束”
|
||||||
|
const btn = document.getElementById("run-btn")
|
||||||
|
const list = document.getElementById("log-list")
|
||||||
|
|
||||||
|
function add1(message) {
|
||||||
|
const li = document.createElement("li")
|
||||||
|
li.textContent = message
|
||||||
|
list.appendChild(li)
|
||||||
|
}
|
||||||
|
|
||||||
|
btn.addEventListener("click", function () {
|
||||||
|
list.textContent = ""
|
||||||
|
add1("开始执行")
|
||||||
|
setTimeout(function () {
|
||||||
|
add1("异步回调完成")
|
||||||
|
}, 2000)
|
||||||
|
add1("同步代码结束")
|
||||||
|
})
|
||||||
@@ -12,3 +12,23 @@ function fakeFetchCourses() {
|
|||||||
// 3. 调用 fakeFetchCourses()
|
// 3. 调用 fakeFetchCourses()
|
||||||
// 4. 用 then 渲染课程列表
|
// 4. 用 then 渲染课程列表
|
||||||
// 5. 用 catch 处理错误
|
// 5. 用 catch 处理错误
|
||||||
|
const btn = document.getElementById("load-btn")
|
||||||
|
const text = document.getElementById("status-text")
|
||||||
|
const list = document.getElementById("course-list")
|
||||||
|
|
||||||
|
btn.addEventListener("click", function () {
|
||||||
|
text.textContent = "加载中..."
|
||||||
|
|
||||||
|
fakeFetchCourses()
|
||||||
|
.then(function (result) {
|
||||||
|
text.textContent = "加载成功"
|
||||||
|
const a = result.forEach(item => {
|
||||||
|
const li = document.createElement("li")
|
||||||
|
li.textContent = item
|
||||||
|
list.appendChild(li)
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch(function (error) {
|
||||||
|
list.textContent = "失败" + error
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -16,3 +16,24 @@ function fakeFetchUser() {
|
|||||||
// 3. 用 await 等待 fakeFetchUser()
|
// 3. 用 await 等待 fakeFetchUser()
|
||||||
// 4. 渲染用户信息
|
// 4. 渲染用户信息
|
||||||
// 5. 用 try...catch 处理异常
|
// 5. 用 try...catch 处理异常
|
||||||
|
const btn = document.getElementById("load-user-btn")
|
||||||
|
const status = document.getElementById("user-status")
|
||||||
|
const card = document.getElementById("user-card")
|
||||||
|
|
||||||
|
async function a() {
|
||||||
|
status.textContent = "加载中"
|
||||||
|
card.innerHTML = ""
|
||||||
|
|
||||||
|
try {
|
||||||
|
const a = await fakeFetchUser()
|
||||||
|
status.textContent = "加载成功"
|
||||||
|
card.innerHTML = `
|
||||||
|
<p>姓名:${a.name}</p>
|
||||||
|
<p>角色:${a.role}</p>
|
||||||
|
<p>任务:${a.focus}</p>
|
||||||
|
`
|
||||||
|
} catch (error) {
|
||||||
|
status.textContent = "加载失败"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
btn.addEventListener("click", a)
|
||||||
@@ -20,3 +20,85 @@ function fakeLoadExtraLessons() {
|
|||||||
// 3. 点击列表项时切换 done 状态
|
// 3. 点击列表项时切换 done 状态
|
||||||
// 4. 提交表单时阻止默认提交,并新增课程
|
// 4. 提交表单时阻止默认提交,并新增课程
|
||||||
// 5. 点击加载按钮时显示加载状态,并把异步返回的课程追加到列表
|
// 5. 点击加载按钮时显示加载状态,并把异步返回的课程追加到列表
|
||||||
|
/* 一个课程列表区域
|
||||||
|
- 一个表单区域
|
||||||
|
- 一个添加课程的交互
|
||||||
|
- 一个点击课程切换完成状态的交互
|
||||||
|
- 一个异步加载提示或远程数据模拟
|
||||||
|
- 清晰的状态文案更新*/
|
||||||
|
const page = document.querySelector(".page")
|
||||||
|
const hero = document.querySelector("hero")
|
||||||
|
const text = document.getElementById("status-text")
|
||||||
|
const panel = document.querySelector(".panel")
|
||||||
|
const btn = document.getElementById("load-btn")
|
||||||
|
const list = document.getElementById("lesson-list")
|
||||||
|
const formPanel = document.querySelector(".form-panel")
|
||||||
|
const lessonForm = document.getElementById("lesson-form")
|
||||||
|
const lessonInput = document.getElementById("lesson-input")
|
||||||
|
|
||||||
|
function renderLessons() {
|
||||||
|
lessons.forEach(item => {
|
||||||
|
const li = document.createElement("li")
|
||||||
|
li.classList.add("li")
|
||||||
|
li.setAttribute("title", item.title)
|
||||||
|
li.setAttribute("done", item.done)
|
||||||
|
li.textContent = `
|
||||||
|
标题:${item.title}
|
||||||
|
状态:${item.done}`
|
||||||
|
|
||||||
|
list.appendChild(li)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
renderLessons()
|
||||||
|
|
||||||
|
//event.target,closest,
|
||||||
|
list.addEventListener("click", function (event) {
|
||||||
|
const a = event.target.closest(".li")
|
||||||
|
if (a) {
|
||||||
|
a.textContent = `
|
||||||
|
标题:${a.title}
|
||||||
|
状态:${!a.done}`
|
||||||
|
|
||||||
|
a.done = !a.done
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
lessonForm.addEventListener("submit", function (event) {
|
||||||
|
event.preventDefault()
|
||||||
|
const c = document.createElement("li")
|
||||||
|
const d = lessonInput.value.trim()
|
||||||
|
c.textContent = `
|
||||||
|
标题:${d}
|
||||||
|
状态:true`
|
||||||
|
c.setAttribute("title", d)
|
||||||
|
c.setAttribute("done", true)
|
||||||
|
c.classList.add("li")
|
||||||
|
lessonInput.value = ""
|
||||||
|
list.appendChild(c)
|
||||||
|
})
|
||||||
|
|
||||||
|
async function load() {
|
||||||
|
text.textContent = "加载中"
|
||||||
|
text.innerHTML = ""
|
||||||
|
try {
|
||||||
|
|
||||||
|
const s = await fakeLoadExtraLessons()
|
||||||
|
text.textContent = "加载完成"
|
||||||
|
s.forEach(item => {
|
||||||
|
const li = document.createElement("li")
|
||||||
|
li.classList.add("li")
|
||||||
|
li.setAttribute("title", item.title)
|
||||||
|
li.setAttribute("done", item.done)
|
||||||
|
li.textContent = `
|
||||||
|
标题:${item.title}
|
||||||
|
状态:${item.done}`
|
||||||
|
|
||||||
|
list.appendChild(li)
|
||||||
|
})
|
||||||
|
} catch (error) {
|
||||||
|
text.textContent = "加载失败"
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
btn.addEventListener("click", load)
|
||||||
@@ -4,3 +4,26 @@
|
|||||||
// 3. 把输入内容实时渲染到右侧预览
|
// 3. 把输入内容实时渲染到右侧预览
|
||||||
// 4. 监听阶段下拉框的 change 事件
|
// 4. 监听阶段下拉框的 change 事件
|
||||||
// 5. 更新阶段标签文字
|
// 5. 更新阶段标签文字
|
||||||
|
const nickname = document.getElementById("nickname-input")
|
||||||
|
const goal = document.getElementById("goal-input")
|
||||||
|
const stage = document.getElementById("stage-select")
|
||||||
|
|
||||||
|
nickname.addEventListener("input", function () {
|
||||||
|
const previewName = document.getElementById("preview-name")
|
||||||
|
previewName.textContent = nickname.value
|
||||||
|
if (!nickname.value.trim()) {
|
||||||
|
previewName.textContent = "未填写昵称"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
goal.addEventListener("input", function () {
|
||||||
|
const previewGoal = document.getElementById("preview-goal")
|
||||||
|
previewGoal.textContent = goal.value
|
||||||
|
if (!goal.value.trim()) {
|
||||||
|
previewGoal.textContent = "这里会显示你的学习目标。"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
stage.addEventListener("change", function () {
|
||||||
|
const previewStage = document.getElementById("preview-stage")
|
||||||
|
previewStage.textContent = stage.value
|
||||||
|
})
|
||||||
@@ -4,3 +4,26 @@
|
|||||||
// 3. 点击 clear-active-btn 时移除所有 item 的 active 类名
|
// 3. 点击 clear-active-btn 时移除所有 item 的 active 类名
|
||||||
// 4. 点击帮助链接时,用 preventDefault() 阻止跳转
|
// 4. 点击帮助链接时,用 preventDefault() 阻止跳转
|
||||||
// 5. 在提示文字里输出“已阻止默认跳转”
|
// 5. 在提示文字里输出“已阻止默认跳转”
|
||||||
|
const prependBtn = document.getElementById("prepend-btn")
|
||||||
|
const clear = document.getElementById("clear-active-btn")
|
||||||
|
const helpLink = document.getElementById("help-link")
|
||||||
|
const hintText = document.getElementById("hint-text")
|
||||||
|
const messageList = document.getElementById("message-list")
|
||||||
|
|
||||||
|
prependBtn.addEventListener("click", function () {
|
||||||
|
const li = document.createElement("li")
|
||||||
|
li.classList.add("item")
|
||||||
|
messageList.prepend(li)
|
||||||
|
})
|
||||||
|
|
||||||
|
clear.addEventListener("click", function () {
|
||||||
|
const a = document.querySelectorAll("#message-list li")
|
||||||
|
a.forEach(item => {
|
||||||
|
item.classList.remove("active")
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
helpLink.addEventListener("click", function (event) {
|
||||||
|
event.preventDefault()
|
||||||
|
hintText.textContent = "已阻止默认跳转"
|
||||||
|
})
|
||||||
@@ -9,3 +9,10 @@ const scores = [88, 92, 95];
|
|||||||
// 1. 解构出 name、stage
|
// 1. 解构出 name、stage
|
||||||
// 2. 解构出前两个分数
|
// 2. 解构出前两个分数
|
||||||
// 3. 用模板字符串把信息写入 #output
|
// 3. 用模板字符串把信息写入 #output
|
||||||
|
const { name, stage } = student
|
||||||
|
const [a, b] = scores
|
||||||
|
const output = document.getElementById("output")
|
||||||
|
output.innerHTML = `
|
||||||
|
a = ${a}
|
||||||
|
b = ${b}
|
||||||
|
`
|
||||||
@@ -7,6 +7,14 @@ const user = {
|
|||||||
|
|
||||||
function sumScores(...scores) {
|
function sumScores(...scores) {
|
||||||
// 返回总分
|
// 返回总分
|
||||||
|
/*let total = 0
|
||||||
|
scores.forEach(item => {
|
||||||
|
total = total+item
|
||||||
|
})
|
||||||
|
return total*/
|
||||||
|
return scores.reduce((acc, item) => {
|
||||||
|
return acc + item
|
||||||
|
}, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 任务:
|
// 任务:
|
||||||
@@ -14,3 +22,11 @@ function sumScores(...scores) {
|
|||||||
// 2. 基于 user 生成一个 stage 为 "现代 JS" 的新对象
|
// 2. 基于 user 生成一个 stage 为 "现代 JS" 的新对象
|
||||||
// 3. 调用 sumScores
|
// 3. 调用 sumScores
|
||||||
// 4. 输出结果到 #output
|
// 4. 输出结果到 #output
|
||||||
|
const tracks1 = [...tracks, "ES6+"]
|
||||||
|
const user1 = {
|
||||||
|
...user,
|
||||||
|
stage: "现代 JS"
|
||||||
|
}
|
||||||
|
const sum = sumScores(1, 2, 3, 4)
|
||||||
|
const output = document.getElementById("output")
|
||||||
|
output.textContent = sum
|
||||||
@@ -12,3 +12,7 @@ const tracks = ["DOM", "异步", "模块化"];
|
|||||||
// 1. 把 getLevel 改成箭头函数
|
// 1. 把 getLevel 改成箭头函数
|
||||||
// 2. 把 add 改成箭头函数
|
// 2. 把 add 改成箭头函数
|
||||||
// 3. 用 map + 箭头函数生成 ["[DOM]", ...]
|
// 3. 用 map + 箭头函数生成 ["[DOM]", ...]
|
||||||
|
const getLevel1 = score => score >= 80
|
||||||
|
const add1 = (a, b) => a + b
|
||||||
|
const tracks1 = tracks.map(item => `[${item}]`)
|
||||||
|
console.log(tracks1);
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
# 练习 4:模块基础
|
#
|
||||||
|
练习 4:模块基础
|
||||||
|
|
||||||
## 目标
|
## 目标
|
||||||
|
|
||||||
@@ -9,7 +10,6 @@
|
|||||||
- `export`
|
- `export`
|
||||||
- `import`
|
- `import`
|
||||||
- `type="module"`
|
- `type="module"`
|
||||||
|
|
||||||
## 任务
|
## 任务
|
||||||
|
|
||||||
- 从模块文件里导入课程数据和格式化函数
|
- 从模块文件里导入课程数据和格式化函数
|
||||||
|
|||||||
@@ -1,3 +1,7 @@
|
|||||||
// 任务:
|
// 任务:
|
||||||
// 1. 从 ./course-data.js 导入 courses 和 formatCourse
|
// 1. 从 ./course-data.js 导入 courses 和 formatCourse
|
||||||
// 2. 把格式化后的结果写入 #output
|
// 2. 把格式化后的结果写入 #output
|
||||||
|
import { courses, formatCourse } from "./course-data.js"
|
||||||
|
const a = courses.map(item => formatCourse(item))
|
||||||
|
const output = document.getElementById("output")
|
||||||
|
output.textContent = a
|
||||||
@@ -10,3 +10,17 @@ function loadTracks() {
|
|||||||
// 1. 调用 loadTracks()
|
// 1. 调用 loadTracks()
|
||||||
// 2. 成功时更新状态和输出内容
|
// 2. 成功时更新状态和输出内容
|
||||||
// 3. 失败时更新状态
|
// 3. 失败时更新状态
|
||||||
|
async function load() {
|
||||||
|
const status = document.getElementById("status")
|
||||||
|
status.textContent = "加载中"
|
||||||
|
try {
|
||||||
|
const a = await loadTracks()
|
||||||
|
status.textContent = "加载成功"
|
||||||
|
const output = document.getElementById("output")
|
||||||
|
output.textContent = a
|
||||||
|
} catch (error) {
|
||||||
|
status.textContent = "加载失败"
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
load()
|
||||||
@@ -13,3 +13,16 @@ function loadConfig() {
|
|||||||
// 1. 写一个 async 函数
|
// 1. 写一个 async 函数
|
||||||
// 2. 用 await 等待 loadConfig()
|
// 2. 用 await 等待 loadConfig()
|
||||||
// 3. 用 try...catch 处理流程
|
// 3. 用 try...catch 处理流程
|
||||||
|
async function load() {
|
||||||
|
const status = document.getElementById("status")
|
||||||
|
status.textContent = "加载中"
|
||||||
|
try {
|
||||||
|
const a = await loadConfig()
|
||||||
|
status.textContent = "加载成功"
|
||||||
|
const output = document.getElementById("output")
|
||||||
|
output.textContent = JSON.stringify(a)
|
||||||
|
} catch (error) {
|
||||||
|
status.textContent = "加载失败"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
load()
|
||||||
@@ -1,6 +1,11 @@
|
|||||||
const trainer = {
|
const trainer = {
|
||||||
name: "现代 JS 训练营",
|
name: "现代 JS 训练营",
|
||||||
report() {
|
report() {
|
||||||
|
setTimeout(() => {
|
||||||
|
const a = this.name
|
||||||
|
const output = document.getElementById("output")
|
||||||
|
output.textContent = a
|
||||||
|
}, 1000)
|
||||||
// 任务:
|
// 任务:
|
||||||
// 1. 用 setTimeout
|
// 1. 用 setTimeout
|
||||||
// 2. 在回调里用箭头函数读取 this.name
|
// 2. 在回调里用箭头函数读取 this.name
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
const loadButton = document.getElementById("load-btn");
|
const loadButton = document.getElementById("load-btn");
|
||||||
const status = document.getElementById("status");
|
const status1 = document.getElementById("status");
|
||||||
const output = document.getElementById("output");
|
const output = document.getElementById("output");
|
||||||
|
|
||||||
// 任务:
|
// 任务:
|
||||||
@@ -9,3 +9,17 @@ const output = document.getElementById("output");
|
|||||||
// 4. 调用 res.json() 解析数据
|
// 4. 调用 res.json() 解析数据
|
||||||
// 5. 把 title 和 body 渲染到页面
|
// 5. 把 title 和 body 渲染到页面
|
||||||
// 6. 失败时显示“加载失败”
|
// 6. 失败时显示“加载失败”
|
||||||
|
loadButton.addEventListener("click", async () => {
|
||||||
|
status1.textContent = "加载中..."
|
||||||
|
try {
|
||||||
|
let a = await fetch("https://jsonplaceholder.typicode.com/posts/1")
|
||||||
|
let data = await a.json()
|
||||||
|
status1.textContent = "加载成功"
|
||||||
|
const { title, body } = data
|
||||||
|
output.textContent = `title:${title}
|
||||||
|
body:${body}`
|
||||||
|
} catch (error) {
|
||||||
|
status1.textContent = "加载失败"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -5,3 +5,28 @@
|
|||||||
// 4. 用展开运算符合并技能
|
// 4. 用展开运算符合并技能
|
||||||
// 5. 用模板字符串渲染标题和说明
|
// 5. 用模板字符串渲染标题和说明
|
||||||
// 6. 用 forEach 或 map + 箭头函数渲染列表
|
// 6. 用 forEach 或 map + 箭头函数渲染列表
|
||||||
|
import { baseSummary, loadExtraSkills } from './summary-service.js'
|
||||||
|
async function get() {
|
||||||
|
const title = document.getElementById("title")
|
||||||
|
const intro = document.getElementById("intro")
|
||||||
|
title.textContent = '加载中...'
|
||||||
|
intro.innerHTML = ''
|
||||||
|
try {
|
||||||
|
let load = await loadExtraSkills()
|
||||||
|
title.textContent = '加载成功'
|
||||||
|
const { name, stage, skills } = baseSummary
|
||||||
|
intro.textContent = `
|
||||||
|
name:${name}
|
||||||
|
stage:${stage}`
|
||||||
|
load = [...load, ...skills]
|
||||||
|
load.forEach(item => {
|
||||||
|
const li = document.createElement("li")
|
||||||
|
const skillList = document.getElementById("skill-list")
|
||||||
|
li.textContent = item
|
||||||
|
skillList.appendChild(li)
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
title.textContent = '加载失败'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
get()
|
||||||
@@ -1,7 +1,10 @@
|
|||||||
let age: number = 18;
|
let age: number = 88;
|
||||||
let userName: string = "Tom";
|
let userName: string = "Tom";
|
||||||
let isPaid: boolean = true;
|
let isPaid: boolean = true;
|
||||||
|
|
||||||
// 任务:
|
// 任务:
|
||||||
// 1. 修改上面的值,让它们更像一个学习者资料
|
// 1. 修改上面的值,让它们更像一个学习者资料
|
||||||
// 2. 输出一句完整信息
|
// 2. 输出一句完整信息
|
||||||
|
console.log(`姓名:${userName}
|
||||||
|
年龄:${age}
|
||||||
|
状态:${isPaid}`);
|
||||||
|
|||||||
@@ -2,9 +2,12 @@ const scoreList: number[] = [82, 90, 95];
|
|||||||
|
|
||||||
function sum(a: number, b: number): number {
|
function sum(a: number, b: number): number {
|
||||||
// 返回两个数字之和
|
// 返回两个数字之和
|
||||||
return 0;
|
return a + b;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
console.log(scoreList, sum(2, 3));
|
||||||
|
|
||||||
// 任务:
|
// 任务:
|
||||||
// 1. 改写 sum 的返回值
|
// 1. 改写 sum 的返回值
|
||||||
// 2. 调用 sum
|
// 2. 调用 sum
|
||||||
|
|||||||
@@ -13,3 +13,4 @@ const user: User = {
|
|||||||
// 任务:
|
// 任务:
|
||||||
// 1. 把对象内容改成学习者资料
|
// 1. 把对象内容改成学习者资料
|
||||||
// 2. 输出 name 和 age
|
// 2. 输出 name 和 age
|
||||||
|
console.log(user.name, user.age);
|
||||||
|
|||||||
@@ -2,6 +2,9 @@ function getData<T>(data: T): T {
|
|||||||
// 返回 data
|
// 返回 data
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
const n = getData<number>(100)
|
||||||
|
const s = getData("123")
|
||||||
|
console.log(n, s);
|
||||||
|
|
||||||
// 任务:
|
// 任务:
|
||||||
// 1. 用 number 调用
|
// 1. 用 number 调用
|
||||||
|
|||||||
@@ -4,6 +4,15 @@ interface User {
|
|||||||
name: string;
|
name: string;
|
||||||
age?: number;
|
age?: number;
|
||||||
}
|
}
|
||||||
|
id = "hello"
|
||||||
|
const userA: User = {
|
||||||
|
name: "小李",
|
||||||
|
age: 18
|
||||||
|
}
|
||||||
|
const userB: User = {
|
||||||
|
name: "小柔"
|
||||||
|
}
|
||||||
|
console.log(userA, userB);
|
||||||
|
|
||||||
// 任务:
|
// 任务:
|
||||||
// 1. 把 id 改成字符串也试一次
|
// 1. 把 id 改成字符串也试一次
|
||||||
|
|||||||
@@ -11,8 +11,12 @@ const courses: Course[] = [
|
|||||||
|
|
||||||
function renderCourseLines(list: Course[]): string[] {
|
function renderCourseLines(list: Course[]): string[] {
|
||||||
// 返回渲染后的字符串数组
|
// 返回渲染后的字符串数组
|
||||||
return [];
|
const a = list.map(item => {
|
||||||
|
return item.title
|
||||||
|
})
|
||||||
|
return a;
|
||||||
}
|
}
|
||||||
|
console.log(renderCourseLines(courses));
|
||||||
|
|
||||||
// 任务:
|
// 任务:
|
||||||
// 1. 实现 renderCourseLines
|
// 1. 实现 renderCourseLines
|
||||||
|
|||||||
@@ -18,7 +18,9 @@ function pickFirst<T>(list: T[]): T {
|
|||||||
|
|
||||||
function formatStudent(student: Student): string {
|
function formatStudent(student: Student): string {
|
||||||
// 返回一段摘要文字
|
// 返回一段摘要文字
|
||||||
return "";
|
return `学号:${student.id}
|
||||||
|
姓名:${student.name}
|
||||||
|
年龄:${student.age?.toFixed()}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
const student: Student = {
|
const student: Student = {
|
||||||
@@ -30,6 +32,8 @@ const student: Student = {
|
|||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
console.log(formatStudent(student), pickFirst(student.courses));
|
||||||
|
|
||||||
// 任务:
|
// 任务:
|
||||||
// 1. 实现 formatStudent
|
// 1. 实现 formatStudent
|
||||||
// 2. 用 pickFirst 取第一门课程
|
// 2. 用 pickFirst 取第一门课程
|
||||||
|
|||||||
@@ -1,10 +1,7 @@
|
|||||||
import "./style.css";
|
import "./style.css";
|
||||||
import { lessons } from "./lessons";
|
import { lessons } from "./lessons";
|
||||||
|
|
||||||
function getRequiredElement<T extends Element>(
|
function getRequiredElement<T extends Element>(selector: string, parent: ParentNode = document): T {
|
||||||
selector: string,
|
|
||||||
parent: ParentNode = document,
|
|
||||||
): T {
|
|
||||||
const element = parent.querySelector<T>(selector);
|
const element = parent.querySelector<T>(selector);
|
||||||
|
|
||||||
if (!element) {
|
if (!element) {
|
||||||
@@ -35,34 +32,21 @@ app.innerHTML = `
|
|||||||
<p id="lesson-focus" class="focus"></p>
|
<p id="lesson-focus" class="focus"></p>
|
||||||
<p id="lesson-summary" class="summary"></p>
|
<p id="lesson-summary" class="summary"></p>
|
||||||
</section>
|
</section>
|
||||||
<section class="grid">
|
<section >
|
||||||
<article class="card">
|
<article class="card">
|
||||||
<div class="card-head">
|
<div class="card-head">
|
||||||
<h3>知识点</h3>
|
<h3>知识点</h3>
|
||||||
</div>
|
</div>
|
||||||
<ul id="lesson-points" class="point-list"></ul>
|
<ul id="lesson-points" class="point-list"></ul>
|
||||||
</article>
|
</article>
|
||||||
<article class="card">
|
|
||||||
<div class="card-head">
|
|
||||||
<h3>运行结果</h3>
|
|
||||||
<button id="run-demo" class="run-button" type="button">运行演示</button>
|
|
||||||
</div>
|
|
||||||
<div id="lesson-output" class="output-panel"></div>
|
|
||||||
</article>
|
|
||||||
</section>
|
</section>
|
||||||
<section class="grid code-grid">
|
<section >
|
||||||
<article class="card code-card">
|
<article class="card code-card">
|
||||||
<div class="card-head">
|
<div class="card-head">
|
||||||
<h3>Starter</h3>
|
<h3>Starter</h3>
|
||||||
</div>
|
</div>
|
||||||
<pre><code id="starter-code"></code></pre>
|
<pre><code id="starter-code"></code></pre>
|
||||||
</article>
|
</article>
|
||||||
<article class="card code-card">
|
|
||||||
<div class="card-head">
|
|
||||||
<h3>Answer</h3>
|
|
||||||
</div>
|
|
||||||
<pre><code id="answer-code"></code></pre>
|
|
||||||
</article>
|
|
||||||
</section>
|
</section>
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
@@ -73,10 +57,7 @@ const title = getRequiredElement<HTMLElement>("#lesson-title");
|
|||||||
const focus = getRequiredElement<HTMLElement>("#lesson-focus");
|
const focus = getRequiredElement<HTMLElement>("#lesson-focus");
|
||||||
const summary = getRequiredElement<HTMLElement>("#lesson-summary");
|
const summary = getRequiredElement<HTMLElement>("#lesson-summary");
|
||||||
const pointList = getRequiredElement<HTMLElement>("#lesson-points");
|
const pointList = getRequiredElement<HTMLElement>("#lesson-points");
|
||||||
const output = getRequiredElement<HTMLElement>("#lesson-output");
|
|
||||||
const starterCode = getRequiredElement<HTMLElement>("#starter-code");
|
const starterCode = getRequiredElement<HTMLElement>("#starter-code");
|
||||||
const answerCode = getRequiredElement<HTMLElement>("#answer-code");
|
|
||||||
const runButton = getRequiredElement<HTMLButtonElement>("#run-demo");
|
|
||||||
|
|
||||||
let activeLessonId = lessons[0]?.id ?? "";
|
let activeLessonId = lessons[0]?.id ?? "";
|
||||||
|
|
||||||
@@ -101,12 +82,6 @@ function renderLessonList(): void {
|
|||||||
.join("");
|
.join("");
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderOutput(lines: string[]): void {
|
|
||||||
output.innerHTML = lines
|
|
||||||
.map((line) => `<div class="output-line">${line}</div>`)
|
|
||||||
.join("");
|
|
||||||
}
|
|
||||||
|
|
||||||
function renderActiveLesson(): void {
|
function renderActiveLesson(): void {
|
||||||
const lesson = lessons.find((item) => item.id === activeLessonId);
|
const lesson = lessons.find((item) => item.id === activeLessonId);
|
||||||
|
|
||||||
@@ -117,12 +92,8 @@ function renderActiveLesson(): void {
|
|||||||
title.textContent = `${lesson.id}. ${lesson.title}`;
|
title.textContent = `${lesson.id}. ${lesson.title}`;
|
||||||
focus.textContent = lesson.focus;
|
focus.textContent = lesson.focus;
|
||||||
summary.textContent = lesson.summary;
|
summary.textContent = lesson.summary;
|
||||||
pointList.innerHTML = lesson.keyPoints
|
pointList.innerHTML = lesson.keyPoints.map((item) => `<li>${item}</li>`).join("");
|
||||||
.map((item) => `<li>${item}</li>`)
|
|
||||||
.join("");
|
|
||||||
starterCode.textContent = lesson.starterCode;
|
starterCode.textContent = lesson.starterCode;
|
||||||
answerCode.textContent = lesson.answerCode;
|
|
||||||
renderOutput(lesson.runDemo());
|
|
||||||
renderLessonList();
|
renderLessonList();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -138,14 +109,4 @@ nav.addEventListener("click", (event) => {
|
|||||||
renderActiveLesson();
|
renderActiveLesson();
|
||||||
});
|
});
|
||||||
|
|
||||||
runButton.addEventListener("click", () => {
|
|
||||||
const lesson = lessons.find((item) => item.id === activeLessonId);
|
|
||||||
|
|
||||||
if (!lesson) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
renderOutput(lesson.runDemo());
|
|
||||||
});
|
|
||||||
|
|
||||||
renderActiveLesson();
|
renderActiveLesson();
|
||||||
|
|||||||
28
07-vue2/01-vue-instance-and-data/README.md
Normal file
28
07-vue2/01-vue-instance-and-data/README.md
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
# 练习 1:Vue 实例和数据
|
||||||
|
|
||||||
|
## 目标
|
||||||
|
|
||||||
|
学会写一个最基础的 Vue2 实例,并让页面内容跟着数据变化。
|
||||||
|
|
||||||
|
## 你要练什么
|
||||||
|
|
||||||
|
- `new Vue()`
|
||||||
|
- `el`
|
||||||
|
- `data`
|
||||||
|
- `methods`
|
||||||
|
- 模板插值 `{{ }}`
|
||||||
|
|
||||||
|
## 任务
|
||||||
|
|
||||||
|
请基于页面结构完成以下操作:
|
||||||
|
|
||||||
|
- 显示课程标题和章节数量
|
||||||
|
- 点击按钮后让学习人数加 1
|
||||||
|
- 在控制台输出当前学习人数
|
||||||
|
|
||||||
|
## 文件
|
||||||
|
|
||||||
|
- [starter.html](/Users/lijiaqing/home/wwwroot/front-end-example/07-vue2/01-vue-instance-and-data/starter.html)
|
||||||
|
- [starter.js](/Users/lijiaqing/home/wwwroot/front-end-example/07-vue2/01-vue-instance-and-data/starter.js)
|
||||||
|
- [answer.html](/Users/lijiaqing/home/wwwroot/front-end-example/07-vue2/01-vue-instance-and-data/answer.html)
|
||||||
|
- [answer.js](/Users/lijiaqing/home/wwwroot/front-end-example/07-vue2/01-vue-instance-and-data/answer.js)
|
||||||
24
07-vue2/01-vue-instance-and-data/answer.html
Normal file
24
07-vue2/01-vue-instance-and-data/answer.html
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>Vue 实例和数据</title>
|
||||||
|
<style>
|
||||||
|
body { margin: 0; padding: 32px; font-family: "PingFang SC", sans-serif; background: #f5f7fb; }
|
||||||
|
.panel { max-width: 720px; margin: 0 auto; padding: 24px; border-radius: 18px; background: #fff; border: 1px solid #dce5f2; }
|
||||||
|
button { padding: 10px 16px; border: 0; border-radius: 999px; background: #2d6cdf; color: #fff; cursor: pointer; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<section id="app" class="panel">
|
||||||
|
<h1>{{ title }}</h1>
|
||||||
|
<p>本章共有 {{ lessonCount }} 个小节。</p>
|
||||||
|
<p>当前学习人数:{{ learnerCount }}</p>
|
||||||
|
<button type="button" @click="increaseLearner">加入学习</button>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
|
||||||
|
<script src="./answer.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
14
07-vue2/01-vue-instance-and-data/answer.js
Normal file
14
07-vue2/01-vue-instance-and-data/answer.js
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
new Vue({
|
||||||
|
el: "#app",
|
||||||
|
data: {
|
||||||
|
title: "Vue2 基础入门",
|
||||||
|
lessonCount: 8,
|
||||||
|
learnerCount: 12,
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
increaseLearner() {
|
||||||
|
this.learnerCount += 1;
|
||||||
|
console.log("当前学习人数:", this.learnerCount);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
24
07-vue2/01-vue-instance-and-data/starter.html
Normal file
24
07-vue2/01-vue-instance-and-data/starter.html
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>Vue 实例和数据</title>
|
||||||
|
<style>
|
||||||
|
body { margin: 0; padding: 32px; font-family: "PingFang SC", sans-serif; background: #f5f7fb; }
|
||||||
|
.panel { max-width: 720px; margin: 0 auto; padding: 24px; border-radius: 18px; background: #fff; border: 1px solid #dce5f2; }
|
||||||
|
button { padding: 10px 16px; border: 0; border-radius: 999px; background: #2d6cdf; color: #fff; cursor: pointer; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<section id="app" class="panel">
|
||||||
|
<h1>{{ title }}</h1>
|
||||||
|
<p>本章共有 {{ lessonCount }} 个小节。</p>
|
||||||
|
<p>当前学习人数:{{ learnerCount }}</p>
|
||||||
|
<button type="button" @click="increaseLearner">加入学习</button>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
|
||||||
|
<script src="./starter.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
18
07-vue2/01-vue-instance-and-data/starter.js
Normal file
18
07-vue2/01-vue-instance-and-data/starter.js
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
new Vue({
|
||||||
|
el: "#app",
|
||||||
|
data: {
|
||||||
|
title: "Vue2 基础入门",
|
||||||
|
lessonCount: 8,
|
||||||
|
learnerCount: 12,
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
increaseLearner() {
|
||||||
|
// 任务:
|
||||||
|
// 1. learnerCount 加 1
|
||||||
|
// 2. 在控制台输出最新人数
|
||||||
|
this.learnerCount++
|
||||||
|
console.log(this.learnerCount);
|
||||||
|
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
27
07-vue2/02-template-bindings/README.md
Normal file
27
07-vue2/02-template-bindings/README.md
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
# 练习 2:模板语法和绑定
|
||||||
|
|
||||||
|
## 目标
|
||||||
|
|
||||||
|
学会把数据绑定到属性和事件上,而不是手动改 DOM。
|
||||||
|
|
||||||
|
## 你要练什么
|
||||||
|
|
||||||
|
- `{{ }}`
|
||||||
|
- `v-bind`
|
||||||
|
- `v-on`
|
||||||
|
- 事件方法
|
||||||
|
|
||||||
|
## 任务
|
||||||
|
|
||||||
|
请基于页面结构完成以下操作:
|
||||||
|
|
||||||
|
- 显示课程名和讲师名
|
||||||
|
- 绑定图片地址和课程链接
|
||||||
|
- 点击按钮后切换收藏状态
|
||||||
|
|
||||||
|
## 文件
|
||||||
|
|
||||||
|
- [starter.html](/Users/lijiaqing/home/wwwroot/front-end-example/07-vue2/02-template-bindings/starter.html)
|
||||||
|
- [starter.js](/Users/lijiaqing/home/wwwroot/front-end-example/07-vue2/02-template-bindings/starter.js)
|
||||||
|
- [answer.html](/Users/lijiaqing/home/wwwroot/front-end-example/07-vue2/02-template-bindings/answer.html)
|
||||||
|
- [answer.js](/Users/lijiaqing/home/wwwroot/front-end-example/07-vue2/02-template-bindings/answer.js)
|
||||||
28
07-vue2/02-template-bindings/answer.html
Normal file
28
07-vue2/02-template-bindings/answer.html
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>模板绑定</title>
|
||||||
|
<style>
|
||||||
|
body { margin: 0; padding: 32px; font-family: "PingFang SC", sans-serif; background: #f6f8fb; }
|
||||||
|
.card { max-width: 680px; margin: 0 auto; padding: 24px; border-radius: 20px; background: #fff; border: 1px solid #dde6f5; }
|
||||||
|
img { width: 100%; border-radius: 16px; }
|
||||||
|
.link { display: inline-block; margin-top: 12px; color: #2d6cdf; }
|
||||||
|
button { margin-top: 18px; padding: 10px 16px; border: 0; border-radius: 999px; background: #15233f; color: #fff; cursor: pointer; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<section id="app" class="card">
|
||||||
|
<h1>{{ courseTitle }}</h1>
|
||||||
|
<p>讲师:{{ teacher }}</p>
|
||||||
|
<img :src="coverUrl" :alt="courseTitle" />
|
||||||
|
<a class="link" :href="courseLink" target="_blank">查看课程详情</a>
|
||||||
|
<p>当前状态:{{ isCollected ? "已收藏" : "未收藏" }}</p>
|
||||||
|
<button type="button" @click="toggleCollect">切换收藏状态</button>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
|
||||||
|
<script src="./answer.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
16
07-vue2/02-template-bindings/answer.js
Normal file
16
07-vue2/02-template-bindings/answer.js
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
new Vue({
|
||||||
|
el: "#app",
|
||||||
|
data: {
|
||||||
|
courseTitle: "Vue2 模板语法",
|
||||||
|
teacher: "李老师",
|
||||||
|
coverUrl: "https://picsum.photos/800/320?random=21",
|
||||||
|
courseLink: "https://cn.vuejs.org/",
|
||||||
|
isCollected: false,
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
toggleCollect() {
|
||||||
|
this.isCollected = !this.isCollected;
|
||||||
|
console.log("收藏状态:", this.isCollected ? "已收藏" : "未收藏");
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
62
07-vue2/02-template-bindings/starter.html
Normal file
62
07-vue2/02-template-bindings/starter.html
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>模板绑定</title>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
padding: 32px;
|
||||||
|
font-family: "PingFang SC", sans-serif;
|
||||||
|
background: #f6f8fb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card {
|
||||||
|
max-width: 680px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 24px;
|
||||||
|
border-radius: 20px;
|
||||||
|
background: #fff;
|
||||||
|
border: 1px solid #dde6f5;
|
||||||
|
}
|
||||||
|
|
||||||
|
img {
|
||||||
|
width: 100%;
|
||||||
|
border-radius: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.link {
|
||||||
|
display: inline-block;
|
||||||
|
margin-top: 12px;
|
||||||
|
color: #2d6cdf;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
margin-top: 18px;
|
||||||
|
padding: 10px 16px;
|
||||||
|
border: 0;
|
||||||
|
border-radius: 999px;
|
||||||
|
background: #15233f;
|
||||||
|
color: #fff;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<section id="app" class="card">
|
||||||
|
<h1>{{ courseTitle }}</h1>
|
||||||
|
<p>讲师:{{ teacher }}</p>
|
||||||
|
<img :src="coverUrl" :alt="courseTitle" />
|
||||||
|
<a class="link" :href="courseLink" target="_blank">查看课程详情</a>
|
||||||
|
<p>当前状态:{{ isCollected ? "已收藏" : "未收藏" }}</p>
|
||||||
|
<button type="button" @click="toggleCollect">切换收藏状态</button>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
|
||||||
|
<script src="./starter.js"></script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
20
07-vue2/02-template-bindings/starter.js
Normal file
20
07-vue2/02-template-bindings/starter.js
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
new Vue({
|
||||||
|
el: "#app",
|
||||||
|
data: {
|
||||||
|
courseTitle: "Vue2 模板语法",
|
||||||
|
teacher: "李老师",
|
||||||
|
coverUrl: "https://picsum.photos/800/320?random=21",
|
||||||
|
courseLink: "https://cn.vuejs.org/",
|
||||||
|
isCollected: false,
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
toggleCollect() {
|
||||||
|
// 任务:
|
||||||
|
// 1. 切换 isCollected
|
||||||
|
// 2. 在控制台输出最新收藏状态
|
||||||
|
this.isCollected = !this.isCollected
|
||||||
|
console.log(this.isCollected);
|
||||||
|
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
31
07-vue2/03-v-model-and-form/README.md
Normal file
31
07-vue2/03-v-model-and-form/README.md
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
# 练习 3:v-model 和表单
|
||||||
|
|
||||||
|
## 目标
|
||||||
|
|
||||||
|
学会用 `v-model` 处理输入、选择和实时预览。
|
||||||
|
|
||||||
|
## 你要练什么
|
||||||
|
|
||||||
|
- `v-model`
|
||||||
|
- 输入框
|
||||||
|
- 多行文本
|
||||||
|
- 下拉框
|
||||||
|
- 单选框
|
||||||
|
- 复选框
|
||||||
|
|
||||||
|
## 任务
|
||||||
|
|
||||||
|
请基于页面结构完成以下操作:
|
||||||
|
|
||||||
|
- 输入昵称并实时显示
|
||||||
|
- 输入学习目标并实时显示
|
||||||
|
- 选择当前阶段并显示结果
|
||||||
|
- 选择偏好的学习节奏
|
||||||
|
- 勾选已经掌握的基础能力
|
||||||
|
|
||||||
|
## 文件
|
||||||
|
|
||||||
|
- [starter.html](/Users/lijiaqing/home/wwwroot/front-end-example/07-vue2/03-v-model-and-form/starter.html)
|
||||||
|
- [starter.js](/Users/lijiaqing/home/wwwroot/front-end-example/07-vue2/03-v-model-and-form/starter.js)
|
||||||
|
- [answer.html](/Users/lijiaqing/home/wwwroot/front-end-example/07-vue2/03-v-model-and-form/answer.html)
|
||||||
|
- [answer.js](/Users/lijiaqing/home/wwwroot/front-end-example/07-vue2/03-v-model-and-form/answer.js)
|
||||||
59
07-vue2/03-v-model-and-form/answer.html
Normal file
59
07-vue2/03-v-model-and-form/answer.html
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>v-model 和表单</title>
|
||||||
|
<style>
|
||||||
|
body { margin: 0; padding: 32px; font-family: "PingFang SC", sans-serif; background: #f7f9fc; }
|
||||||
|
.wrap { max-width: 760px; margin: 0 auto; display: grid; gap: 18px; }
|
||||||
|
.card { padding: 22px; border-radius: 18px; background: #fff; border: 1px solid #dde5f3; }
|
||||||
|
input, textarea, select { width: 100%; margin-top: 8px; padding: 12px; border-radius: 12px; border: 1px solid #cfd8ea; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<section id="app" class="wrap">
|
||||||
|
<article class="card">
|
||||||
|
<label>
|
||||||
|
昵称
|
||||||
|
<input v-model="nickname" type="text" placeholder="请输入昵称" />
|
||||||
|
</label>
|
||||||
|
<label>
|
||||||
|
学习目标
|
||||||
|
<textarea v-model="goal" rows="4" placeholder="请输入学习目标"></textarea>
|
||||||
|
</label>
|
||||||
|
<label>
|
||||||
|
当前阶段
|
||||||
|
<select v-model="stage">
|
||||||
|
<option value="入门">入门</option>
|
||||||
|
<option value="进阶">进阶</option>
|
||||||
|
<option value="项目实战">项目实战</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
<div style="margin-top: 16px;">
|
||||||
|
<p>偏好的学习节奏</p>
|
||||||
|
<label><input v-model="pace" type="radio" value="每天学习" /> 每天学习</label>
|
||||||
|
<label><input v-model="pace" type="radio" value="每周集中学习" /> 每周集中学习</label>
|
||||||
|
</div>
|
||||||
|
<div style="margin-top: 16px;">
|
||||||
|
<p>已掌握的基础能力</p>
|
||||||
|
<label><input v-model="skills" type="checkbox" value="HTML" /> HTML</label>
|
||||||
|
<label><input v-model="skills" type="checkbox" value="CSS" /> CSS</label>
|
||||||
|
<label><input v-model="skills" type="checkbox" value="JavaScript" /> JavaScript</label>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
|
||||||
|
<article class="card">
|
||||||
|
<h2>实时预览</h2>
|
||||||
|
<p>昵称:{{ nickname || "未填写" }}</p>
|
||||||
|
<p>学习目标:{{ goal || "还没有输入目标" }}</p>
|
||||||
|
<p>当前阶段:{{ stage }}</p>
|
||||||
|
<p>学习节奏:{{ pace }}</p>
|
||||||
|
<p>已掌握:{{ skills.length ? skills.join("、") : "还没有勾选" }}</p>
|
||||||
|
</article>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
|
||||||
|
<script src="./answer.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
10
07-vue2/03-v-model-and-form/answer.js
Normal file
10
07-vue2/03-v-model-and-form/answer.js
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
new Vue({
|
||||||
|
el: "#app",
|
||||||
|
data: {
|
||||||
|
nickname: "林晨",
|
||||||
|
goal: "希望独立完成一个 Vue2 管理后台页面。",
|
||||||
|
stage: "进阶",
|
||||||
|
pace: "每周集中学习",
|
||||||
|
skills: ["HTML", "CSS"],
|
||||||
|
},
|
||||||
|
});
|
||||||
59
07-vue2/03-v-model-and-form/starter.html
Normal file
59
07-vue2/03-v-model-and-form/starter.html
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>v-model 和表单</title>
|
||||||
|
<style>
|
||||||
|
body { margin: 0; padding: 32px; font-family: "PingFang SC", sans-serif; background: #f7f9fc; }
|
||||||
|
.wrap { max-width: 760px; margin: 0 auto; display: grid; gap: 18px; }
|
||||||
|
.card { padding: 22px; border-radius: 18px; background: #fff; border: 1px solid #dde5f3; }
|
||||||
|
input, textarea, select { width: 100%; margin-top: 8px; padding: 12px; border-radius: 12px; border: 1px solid #cfd8ea; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<section id="app" class="wrap">
|
||||||
|
<article class="card">
|
||||||
|
<label>
|
||||||
|
昵称
|
||||||
|
<input v-model="nickname" type="text" placeholder="请输入昵称" />
|
||||||
|
</label>
|
||||||
|
<label>
|
||||||
|
学习目标
|
||||||
|
<textarea v-model="goal" rows="4" placeholder="请输入学习目标"></textarea>
|
||||||
|
</label>
|
||||||
|
<label>
|
||||||
|
当前阶段
|
||||||
|
<select v-model="stage">
|
||||||
|
<option value="入门">入门</option>
|
||||||
|
<option value="进阶">进阶</option>
|
||||||
|
<option value="项目实战">项目实战</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
<div style="margin-top: 16px;">
|
||||||
|
<p>偏好的学习节奏</p>
|
||||||
|
<label><input v-model="pace" type="radio" value="每天学习" /> 每天学习</label>
|
||||||
|
<label><input v-model="pace" type="radio" value="每周集中学习" /> 每周集中学习</label>
|
||||||
|
</div>
|
||||||
|
<div style="margin-top: 16px;">
|
||||||
|
<p>已掌握的基础能力</p>
|
||||||
|
<label><input v-model="skills" type="checkbox" value="HTML" /> HTML</label>
|
||||||
|
<label><input v-model="skills" type="checkbox" value="CSS" /> CSS</label>
|
||||||
|
<label><input v-model="skills" type="checkbox" value="JavaScript" /> JavaScript</label>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
|
||||||
|
<article class="card">
|
||||||
|
<h2>实时预览</h2>
|
||||||
|
<p>昵称:{{ nickname }}</p>
|
||||||
|
<p>学习目标:{{ goal }}</p>
|
||||||
|
<p>当前阶段:{{ stage }}</p>
|
||||||
|
<p>学习节奏:{{ pace }}</p>
|
||||||
|
<p>已掌握:{{ skills.join("、") }}</p>
|
||||||
|
</article>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
|
||||||
|
<script src="./starter.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
10
07-vue2/03-v-model-and-form/starter.js
Normal file
10
07-vue2/03-v-model-and-form/starter.js
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
new Vue({
|
||||||
|
el: "#app",
|
||||||
|
data: {
|
||||||
|
nickname: "",
|
||||||
|
goal: "",
|
||||||
|
stage: "入门",
|
||||||
|
pace: "每天学习",
|
||||||
|
skills: [],
|
||||||
|
},
|
||||||
|
});
|
||||||
28
07-vue2/04-conditional-and-list-rendering/README.md
Normal file
28
07-vue2/04-conditional-and-list-rendering/README.md
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
# 练习 4:条件渲染和列表渲染
|
||||||
|
|
||||||
|
## 目标
|
||||||
|
|
||||||
|
学会根据状态显示不同内容,并用 `v-for` 渲染一组数据。
|
||||||
|
|
||||||
|
## 你要练什么
|
||||||
|
|
||||||
|
- `v-if`
|
||||||
|
- `v-else`
|
||||||
|
- `v-show`
|
||||||
|
- `v-for`
|
||||||
|
- `:key`
|
||||||
|
|
||||||
|
## 任务
|
||||||
|
|
||||||
|
请基于页面结构完成以下操作:
|
||||||
|
|
||||||
|
- 切换“是否展开课程列表”
|
||||||
|
- 根据是否付费显示不同文案
|
||||||
|
- 用 `v-for` 渲染课程数组
|
||||||
|
|
||||||
|
## 文件
|
||||||
|
|
||||||
|
- [starter.html](/Users/lijiaqing/home/wwwroot/front-end-example/07-vue2/04-conditional-and-list-rendering/starter.html)
|
||||||
|
- [starter.js](/Users/lijiaqing/home/wwwroot/front-end-example/07-vue2/04-conditional-and-list-rendering/starter.js)
|
||||||
|
- [answer.html](/Users/lijiaqing/home/wwwroot/front-end-example/07-vue2/04-conditional-and-list-rendering/answer.html)
|
||||||
|
- [answer.js](/Users/lijiaqing/home/wwwroot/front-end-example/07-vue2/04-conditional-and-list-rendering/answer.js)
|
||||||
32
07-vue2/04-conditional-and-list-rendering/answer.html
Normal file
32
07-vue2/04-conditional-and-list-rendering/answer.html
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>条件渲染和列表渲染</title>
|
||||||
|
<style>
|
||||||
|
body { margin: 0; padding: 32px; font-family: "PingFang SC", sans-serif; background: #f5f8fc; }
|
||||||
|
.panel { max-width: 760px; margin: 0 auto; padding: 24px; border-radius: 18px; background: #fff; border: 1px solid #dbe4f4; }
|
||||||
|
ul { padding-left: 18px; }
|
||||||
|
button { padding: 10px 16px; border: 0; border-radius: 999px; background: #2f6ee5; color: #fff; cursor: pointer; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<section id="app" class="panel">
|
||||||
|
<h1>Vue2 渲染练习</h1>
|
||||||
|
<button type="button" @click="showList = !showList">切换课程列表</button>
|
||||||
|
|
||||||
|
<p v-if="isPaid">你已经开通会员课程。</p>
|
||||||
|
<p v-else>你还没有开通会员课程。</p>
|
||||||
|
|
||||||
|
<ul v-show="showList">
|
||||||
|
<li v-for="course in courses" :key="course.id">
|
||||||
|
{{ course.title }} - {{ course.status }}
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
|
||||||
|
<script src="./answer.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
12
07-vue2/04-conditional-and-list-rendering/answer.js
Normal file
12
07-vue2/04-conditional-and-list-rendering/answer.js
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
new Vue({
|
||||||
|
el: "#app",
|
||||||
|
data: {
|
||||||
|
isPaid: true,
|
||||||
|
showList: true,
|
||||||
|
courses: [
|
||||||
|
{ id: 1, title: "Vue 实例", status: "已完成" },
|
||||||
|
{ id: 2, title: "指令系统", status: "学习中" },
|
||||||
|
{ id: 3, title: "组件通信", status: "未开始" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
});
|
||||||
32
07-vue2/04-conditional-and-list-rendering/starter.html
Normal file
32
07-vue2/04-conditional-and-list-rendering/starter.html
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>条件渲染和列表渲染</title>
|
||||||
|
<style>
|
||||||
|
body { margin: 0; padding: 32px; font-family: "PingFang SC", sans-serif; background: #f5f8fc; }
|
||||||
|
.panel { max-width: 760px; margin: 0 auto; padding: 24px; border-radius: 18px; background: #fff; border: 1px solid #dbe4f4; }
|
||||||
|
ul { padding-left: 18px; }
|
||||||
|
button { padding: 10px 16px; border: 0; border-radius: 999px; background: #2f6ee5; color: #fff; cursor: pointer; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<section id="app" class="panel">
|
||||||
|
<h1>Vue2 渲染练习</h1>
|
||||||
|
<button type="button" @click="showList = !showList">切换课程列表</button>
|
||||||
|
|
||||||
|
<p v-if="isPaid">你已经开通会员课程。</p>
|
||||||
|
<p v-else>你还没有开通会员课程。</p>
|
||||||
|
|
||||||
|
<ul v-show="showList">
|
||||||
|
<li v-for="course in courses" :key="course.id">
|
||||||
|
{{ course.title }} - {{ course.status }}
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
|
||||||
|
<script src="./starter.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
12
07-vue2/04-conditional-and-list-rendering/starter.js
Normal file
12
07-vue2/04-conditional-and-list-rendering/starter.js
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
new Vue({
|
||||||
|
el: "#app",
|
||||||
|
data: {
|
||||||
|
isPaid: false,
|
||||||
|
showList: true,
|
||||||
|
courses: [
|
||||||
|
{ id: 1, title: "Vue 实例", status: "已完成" },
|
||||||
|
{ id: 2, title: "指令系统", status: "学习中" },
|
||||||
|
{ id: 3, title: "组件通信", status: "未开始" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
});
|
||||||
27
07-vue2/05-computed-and-watch/README.md
Normal file
27
07-vue2/05-computed-and-watch/README.md
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
# 练习 5:computed 和 watch
|
||||||
|
|
||||||
|
## 目标
|
||||||
|
|
||||||
|
学会区分“根据已有数据推导结果”和“监听变化做额外动作”。
|
||||||
|
|
||||||
|
## 你要练什么
|
||||||
|
|
||||||
|
- `computed`
|
||||||
|
- `watch`
|
||||||
|
- 数据推导
|
||||||
|
- 监听输入变化
|
||||||
|
|
||||||
|
## 任务
|
||||||
|
|
||||||
|
请基于页面结构完成以下操作:
|
||||||
|
|
||||||
|
- 根据关键字过滤课程列表
|
||||||
|
- 用 `computed` 计算过滤结果数量
|
||||||
|
- 用 `watch` 在关键字变化时输出日志
|
||||||
|
|
||||||
|
## 文件
|
||||||
|
|
||||||
|
- [starter.html](/Users/lijiaqing/home/wwwroot/front-end-example/07-vue2/05-computed-and-watch/starter.html)
|
||||||
|
- [starter.js](/Users/lijiaqing/home/wwwroot/front-end-example/07-vue2/05-computed-and-watch/starter.js)
|
||||||
|
- [answer.html](/Users/lijiaqing/home/wwwroot/front-end-example/07-vue2/05-computed-and-watch/answer.html)
|
||||||
|
- [answer.js](/Users/lijiaqing/home/wwwroot/front-end-example/07-vue2/05-computed-and-watch/answer.js)
|
||||||
28
07-vue2/05-computed-and-watch/answer.html
Normal file
28
07-vue2/05-computed-and-watch/answer.html
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>computed 和 watch</title>
|
||||||
|
<style>
|
||||||
|
body { margin: 0; padding: 32px; font-family: "PingFang SC", sans-serif; background: #f7f9fd; }
|
||||||
|
.panel { max-width: 760px; margin: 0 auto; padding: 24px; border-radius: 18px; background: #fff; border: 1px solid #d9e2f0; }
|
||||||
|
input { width: 100%; padding: 12px; border-radius: 12px; border: 1px solid #ccd7e9; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<section id="app" class="panel">
|
||||||
|
<h1>课程搜索</h1>
|
||||||
|
<input v-model="keyword" type="text" placeholder="输入关键字" />
|
||||||
|
<p>匹配数量:{{ matchedCount }}</p>
|
||||||
|
<ul>
|
||||||
|
<li v-for="item in filteredCourses" :key="item.id">
|
||||||
|
{{ item.title }}
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
|
||||||
|
<script src="./answer.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
30
07-vue2/05-computed-and-watch/answer.js
Normal file
30
07-vue2/05-computed-and-watch/answer.js
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
new Vue({
|
||||||
|
el: "#app",
|
||||||
|
data: {
|
||||||
|
keyword: "",
|
||||||
|
courses: [
|
||||||
|
{ id: 1, title: "Vue 实例入门" },
|
||||||
|
{ id: 2, title: "Vue 指令系统" },
|
||||||
|
{ id: 3, title: "组件通信实战" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
filteredCourses() {
|
||||||
|
const value = this.keyword.trim().toLowerCase();
|
||||||
|
|
||||||
|
if (!value) {
|
||||||
|
return this.courses;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.courses.filter((item) => item.title.toLowerCase().includes(value));
|
||||||
|
},
|
||||||
|
matchedCount() {
|
||||||
|
return this.filteredCourses.length;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
keyword(newValue) {
|
||||||
|
console.log("关键字变化:", newValue);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
50
07-vue2/05-computed-and-watch/starter.html
Normal file
50
07-vue2/05-computed-and-watch/starter.html
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>computed 和 watch</title>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
padding: 32px;
|
||||||
|
font-family: "PingFang SC", sans-serif;
|
||||||
|
background: #f7f9fd;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel {
|
||||||
|
max-width: 760px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 24px;
|
||||||
|
border-radius: 18px;
|
||||||
|
background: #fff;
|
||||||
|
border: 1px solid #d9e2f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
input {
|
||||||
|
width: 100%;
|
||||||
|
padding: 12px;
|
||||||
|
border-radius: 12px;
|
||||||
|
border: 1px solid #ccd7e9;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<section id="app" class="panel">
|
||||||
|
<h1>课程搜索</h1>
|
||||||
|
<input v-model="keyword" type="text" placeholder="输入关键字" />
|
||||||
|
<p>匹配数量:{{ matchedCount }}</p>
|
||||||
|
<ul>
|
||||||
|
<li v-for="item in filteredCourses" :key="item.id">
|
||||||
|
{{ item.title }}
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
|
||||||
|
<script src="./starter.js"></script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
29
07-vue2/05-computed-and-watch/starter.js
Normal file
29
07-vue2/05-computed-and-watch/starter.js
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
new Vue({
|
||||||
|
el: "#app",
|
||||||
|
data: {
|
||||||
|
keyword: "",
|
||||||
|
courses: [
|
||||||
|
{ id: 1, title: "Vue 实例入门" },
|
||||||
|
{ id: 2, title: "Vue 指令系统" },
|
||||||
|
{ id: 3, title: "组件通信实战" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
filteredCourses() {
|
||||||
|
// 任务:返回过滤后的课程列表
|
||||||
|
const value = this.keyword.trim().toUpperCase()
|
||||||
|
return this.courses.filter(item => item.title.toUpperCase().includes(value))
|
||||||
|
},
|
||||||
|
matchedCount() {
|
||||||
|
// 任务:返回 filteredCourses 的数量
|
||||||
|
return this.filteredCourses.length;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
keyword(newValue) {
|
||||||
|
// 任务:在控制台输出关键字变化
|
||||||
|
console.log(`筛选关键词:${newValue}`);
|
||||||
|
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
27
07-vue2/06-class-style-and-event/README.md
Normal file
27
07-vue2/06-class-style-and-event/README.md
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
# 练习 6:动态类名、样式和事件
|
||||||
|
|
||||||
|
## 目标
|
||||||
|
|
||||||
|
学会让组件状态直接影响样式和交互反馈。
|
||||||
|
|
||||||
|
## 你要练什么
|
||||||
|
|
||||||
|
- `:class`
|
||||||
|
- `:style`
|
||||||
|
- `v-on`
|
||||||
|
- 点击切换状态
|
||||||
|
|
||||||
|
## 任务
|
||||||
|
|
||||||
|
请基于页面结构完成以下操作:
|
||||||
|
|
||||||
|
- 点击卡片切换激活状态
|
||||||
|
- 根据激活状态切换类名
|
||||||
|
- 根据进度值动态改变进度条宽度
|
||||||
|
|
||||||
|
## 文件
|
||||||
|
|
||||||
|
- [starter.html](/Users/lijiaqing/home/wwwroot/front-end-example/07-vue2/06-class-style-and-event/starter.html)
|
||||||
|
- [starter.js](/Users/lijiaqing/home/wwwroot/front-end-example/07-vue2/06-class-style-and-event/starter.js)
|
||||||
|
- [answer.html](/Users/lijiaqing/home/wwwroot/front-end-example/07-vue2/06-class-style-and-event/answer.html)
|
||||||
|
- [answer.js](/Users/lijiaqing/home/wwwroot/front-end-example/07-vue2/06-class-style-and-event/answer.js)
|
||||||
31
07-vue2/06-class-style-and-event/answer.html
Normal file
31
07-vue2/06-class-style-and-event/answer.html
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>动态类名、样式和事件</title>
|
||||||
|
<style>
|
||||||
|
body { margin: 0; padding: 32px; font-family: "PingFang SC", sans-serif; background: #f4f8fb; }
|
||||||
|
.panel { max-width: 760px; margin: 0 auto; }
|
||||||
|
.card { padding: 24px; border-radius: 18px; background: #fff; border: 1px solid #d8e4f3; cursor: pointer; transition: all .2s ease; }
|
||||||
|
.card.active { border-color: #2d6cdf; background: #eef4ff; }
|
||||||
|
.progress-track { height: 12px; margin-top: 16px; border-radius: 999px; background: #e6edf8; overflow: hidden; }
|
||||||
|
.progress-bar { height: 100%; background: linear-gradient(90deg, #2d6cdf, #58a2ff); }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<section id="app" class="panel">
|
||||||
|
<article class="card" :class="{ active: isActive }" @click="toggleCard">
|
||||||
|
<h1>{{ title }}</h1>
|
||||||
|
<p>{{ isActive ? "当前卡片已激活" : "点击卡片激活它" }}</p>
|
||||||
|
<div class="progress-track">
|
||||||
|
<div class="progress-bar" :style="{ width: progress + '%' }"></div>
|
||||||
|
</div>
|
||||||
|
<p>当前进度:{{ progress }}%</p>
|
||||||
|
</article>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
|
||||||
|
<script src="./answer.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
14
07-vue2/06-class-style-and-event/answer.js
Normal file
14
07-vue2/06-class-style-and-event/answer.js
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
new Vue({
|
||||||
|
el: "#app",
|
||||||
|
data: {
|
||||||
|
title: "动态样式练习",
|
||||||
|
isActive: false,
|
||||||
|
progress: 35,
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
toggleCard() {
|
||||||
|
this.isActive = !this.isActive;
|
||||||
|
this.progress = this.isActive ? 80 : 35;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
66
07-vue2/06-class-style-and-event/starter.html
Normal file
66
07-vue2/06-class-style-and-event/starter.html
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>动态类名、样式和事件</title>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
padding: 32px;
|
||||||
|
font-family: "PingFang SC", sans-serif;
|
||||||
|
background: #f4f8fb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel {
|
||||||
|
max-width: 760px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card {
|
||||||
|
padding: 24px;
|
||||||
|
border-radius: 18px;
|
||||||
|
background: #fff;
|
||||||
|
border: 1px solid #d8e4f3;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all .2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card.active {
|
||||||
|
border-color: #2d6cdf;
|
||||||
|
background: #eef4ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-track {
|
||||||
|
height: 12px;
|
||||||
|
margin-top: 16px;
|
||||||
|
border-radius: 999px;
|
||||||
|
background: #e6edf8;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-bar {
|
||||||
|
height: 100%;
|
||||||
|
background: linear-gradient(90deg, #2d6cdf, #58a2ff);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<section id="app" class="panel">
|
||||||
|
<article class="card" :class="{ active: isActive }" @click="toggleCard">
|
||||||
|
<h1>{{ title }}</h1>
|
||||||
|
<p>{{ isActive ? "当前卡片已激活" : "点击卡片激活它" }}</p>
|
||||||
|
<div class="progress-track">
|
||||||
|
<div class="progress-bar" :style="{ width: progress + '%' }"></div>
|
||||||
|
</div>
|
||||||
|
<p>当前进度:{{ progress }}%</p>
|
||||||
|
</article>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
|
||||||
|
<script src="./starter.js"></script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
22
07-vue2/06-class-style-and-event/starter.js
Normal file
22
07-vue2/06-class-style-and-event/starter.js
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
new Vue({
|
||||||
|
el: "#app",
|
||||||
|
data: {
|
||||||
|
title: "动态样式练习",
|
||||||
|
isActive: false,
|
||||||
|
progress: 35,
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
toggleCard() {
|
||||||
|
// 任务:
|
||||||
|
// 1. 切换 isActive
|
||||||
|
this.isActive = !this.isActive
|
||||||
|
// 2. 如果激活了,让 progress 增加到 80
|
||||||
|
if (this.isActive) {
|
||||||
|
this.progress = 80
|
||||||
|
} else {
|
||||||
|
this.progress = 35
|
||||||
|
}
|
||||||
|
// 3. 如果取消激活,让 progress 回到 35
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
31
07-vue2/07-lifecycle-and-async/README.md
Normal file
31
07-vue2/07-lifecycle-and-async/README.md
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
# 练习 7:生命周期和异步更新
|
||||||
|
|
||||||
|
## 目标
|
||||||
|
|
||||||
|
学会在合适的生命周期里加载数据,并理解页面更新的时机。
|
||||||
|
|
||||||
|
## 你要练什么
|
||||||
|
|
||||||
|
- `created`
|
||||||
|
- `mounted`
|
||||||
|
- `updated`
|
||||||
|
- `beforeDestroy`
|
||||||
|
- `destroyed`
|
||||||
|
- 模拟异步请求
|
||||||
|
|
||||||
|
## 任务
|
||||||
|
|
||||||
|
请基于页面结构完成以下操作:
|
||||||
|
|
||||||
|
- 进入页面时显示 loading
|
||||||
|
- 在 `mounted` 里模拟异步获取课程数据
|
||||||
|
- 数据返回后渲染课程列表
|
||||||
|
- 在 `updated` 里输出更新日志
|
||||||
|
- 点击按钮时销毁当前实例,并观察销毁日志
|
||||||
|
|
||||||
|
## 文件
|
||||||
|
|
||||||
|
- [starter.html](/Users/lijiaqing/home/wwwroot/front-end-example/07-vue2/07-lifecycle-and-async/starter.html)
|
||||||
|
- [starter.js](/Users/lijiaqing/home/wwwroot/front-end-example/07-vue2/07-lifecycle-and-async/starter.js)
|
||||||
|
- [answer.html](/Users/lijiaqing/home/wwwroot/front-end-example/07-vue2/07-lifecycle-and-async/answer.html)
|
||||||
|
- [answer.js](/Users/lijiaqing/home/wwwroot/front-end-example/07-vue2/07-lifecycle-and-async/answer.js)
|
||||||
25
07-vue2/07-lifecycle-and-async/answer.html
Normal file
25
07-vue2/07-lifecycle-and-async/answer.html
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>生命周期和异步更新</title>
|
||||||
|
<style>
|
||||||
|
body { margin: 0; padding: 32px; font-family: "PingFang SC", sans-serif; background: #f6f8fb; }
|
||||||
|
.panel { max-width: 760px; margin: 0 auto; padding: 24px; border-radius: 18px; background: #fff; border: 1px solid #dce4f1; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<section id="app" class="panel">
|
||||||
|
<h1>生命周期练习</h1>
|
||||||
|
<button type="button" @click="destroyInstance">销毁当前实例</button>
|
||||||
|
<p v-if="loading">数据加载中...</p>
|
||||||
|
<ul v-else>
|
||||||
|
<li v-for="item in courses" :key="item.id">{{ item.title }}</li>
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
|
||||||
|
<script src="./answer.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
36
07-vue2/07-lifecycle-and-async/answer.js
Normal file
36
07-vue2/07-lifecycle-and-async/answer.js
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
new Vue({
|
||||||
|
el: "#app",
|
||||||
|
data: {
|
||||||
|
loading: true,
|
||||||
|
courses: [],
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
console.log("created: 实例已经创建");
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
console.log("mounted: 页面已经挂载");
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
this.courses = [
|
||||||
|
{ id: 1, title: "生命周期基础" },
|
||||||
|
{ id: 2, title: "异步数据渲染" },
|
||||||
|
{ id: 3, title: "组件更新时机" },
|
||||||
|
];
|
||||||
|
this.loading = false;
|
||||||
|
}, 1000);
|
||||||
|
},
|
||||||
|
updated() {
|
||||||
|
console.log("updated: 页面数据已经更新");
|
||||||
|
},
|
||||||
|
beforeDestroy() {
|
||||||
|
console.log("beforeDestroy: 实例即将销毁");
|
||||||
|
},
|
||||||
|
destroyed() {
|
||||||
|
console.log("destroyed: 实例已经销毁");
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
destroyInstance() {
|
||||||
|
this.$destroy();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
25
07-vue2/07-lifecycle-and-async/starter.html
Normal file
25
07-vue2/07-lifecycle-and-async/starter.html
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>生命周期和异步更新</title>
|
||||||
|
<style>
|
||||||
|
body { margin: 0; padding: 32px; font-family: "PingFang SC", sans-serif; background: #f6f8fb; }
|
||||||
|
.panel { max-width: 760px; margin: 0 auto; padding: 24px; border-radius: 18px; background: #fff; border: 1px solid #dce4f1; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<section id="app" class="panel">
|
||||||
|
<h1>生命周期练习</h1>
|
||||||
|
<button type="button" @click="destroyInstance">销毁当前实例</button>
|
||||||
|
<p v-if="loading">数据加载中...</p>
|
||||||
|
<ul v-else>
|
||||||
|
<li v-for="item in courses" :key="item.id">{{ item.title }}</li>
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
|
||||||
|
<script src="./starter.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
48
07-vue2/07-lifecycle-and-async/starter.js
Normal file
48
07-vue2/07-lifecycle-and-async/starter.js
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
new Vue({
|
||||||
|
el: "#app",
|
||||||
|
data: {
|
||||||
|
loading: true,
|
||||||
|
courses: [],
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
console.log("created: 实例已经创建");
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
// 任务:
|
||||||
|
// 1. 模拟异步请求
|
||||||
|
// 2. 1 秒后给 courses 赋值
|
||||||
|
// 3. loading 改成 false
|
||||||
|
console.log("1");
|
||||||
|
|
||||||
|
const thiz = this
|
||||||
|
setTimeout(function () {
|
||||||
|
thiz.courses = [
|
||||||
|
{ id: 1, title: 'HTML' },
|
||||||
|
{ id: 2, title: 'CSS' },
|
||||||
|
{ id: 3, title: 'JS' }
|
||||||
|
]
|
||||||
|
thiz.loading = false
|
||||||
|
}, 1000)
|
||||||
|
},
|
||||||
|
updated() {
|
||||||
|
// 任务:在控制台输出 updated 日志
|
||||||
|
console.log("更新");
|
||||||
|
|
||||||
|
},
|
||||||
|
beforeDestroy() {
|
||||||
|
// 任务:在控制台输出 beforeDestroy 日志
|
||||||
|
console.log("已销毁");
|
||||||
|
|
||||||
|
},
|
||||||
|
destroyed() {
|
||||||
|
// 任务:在控制台输出 destroyed 日志
|
||||||
|
console.log("销毁完成");
|
||||||
|
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
destroyInstance() {
|
||||||
|
// 任务:调用 this.$destroy()
|
||||||
|
this.$destroy()
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
33
07-vue2/08-components-communication-and-final/README.md
Normal file
33
07-vue2/08-components-communication-and-final/README.md
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
# 练习 8:组件通信与综合练习
|
||||||
|
|
||||||
|
## 目标
|
||||||
|
|
||||||
|
把 Vue2 最核心的组件能力串起来,完成一个小型页面。
|
||||||
|
|
||||||
|
## 你要练什么
|
||||||
|
|
||||||
|
- 组件注册(全局 / 局部)
|
||||||
|
- `props`
|
||||||
|
- `props` 校验
|
||||||
|
- `$emit`
|
||||||
|
- `slot`
|
||||||
|
- `ref`
|
||||||
|
|
||||||
|
## 任务
|
||||||
|
|
||||||
|
请基于页面结构完成以下操作:
|
||||||
|
|
||||||
|
- 把课程卡片拆成子组件
|
||||||
|
- 注册一个全局角标组件
|
||||||
|
- 父组件通过 `props` 传课程数据
|
||||||
|
- 给 `props` 补上类型、必填、默认值或校验规则
|
||||||
|
- 子组件点击按钮后通过 `$emit` 通知父组件切换状态
|
||||||
|
- 用 `slot` 自定义按钮文案
|
||||||
|
- 用 `ref` 聚焦输入框
|
||||||
|
|
||||||
|
## 文件
|
||||||
|
|
||||||
|
- [starter.html](/Users/lijiaqing/home/wwwroot/front-end-example/07-vue2/08-components-communication-and-final/starter.html)
|
||||||
|
- [starter.js](/Users/lijiaqing/home/wwwroot/front-end-example/07-vue2/08-components-communication-and-final/starter.js)
|
||||||
|
- [answer.html](/Users/lijiaqing/home/wwwroot/front-end-example/07-vue2/08-components-communication-and-final/answer.html)
|
||||||
|
- [answer.js](/Users/lijiaqing/home/wwwroot/front-end-example/07-vue2/08-components-communication-and-final/answer.js)
|
||||||
43
07-vue2/08-components-communication-and-final/answer.html
Normal file
43
07-vue2/08-components-communication-and-final/answer.html
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>组件通信与综合练习</title>
|
||||||
|
<style>
|
||||||
|
body { margin: 0; padding: 32px; font-family: "PingFang SC", sans-serif; background: #f4f7fb; }
|
||||||
|
.wrap { max-width: 860px; margin: 0 auto; display: grid; gap: 16px; }
|
||||||
|
.toolbar, .card { padding: 20px; border-radius: 18px; background: #fff; border: 1px solid #d9e3f2; }
|
||||||
|
.list { display: grid; gap: 14px; }
|
||||||
|
button { padding: 10px 14px; border: 0; border-radius: 999px; background: #2d6cdf; color: #fff; cursor: pointer; }
|
||||||
|
input { width: 100%; padding: 12px; border-radius: 12px; border: 1px solid #cfd8e8; }
|
||||||
|
.done { color: #1f8f54; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<section id="app" class="wrap">
|
||||||
|
<article class="toolbar">
|
||||||
|
<h1>Vue2 综合练习</h1>
|
||||||
|
<input ref="keywordInput" v-model="keyword" type="text" placeholder="输入关键字过滤课程" />
|
||||||
|
<button type="button" @click="focusInput">聚焦输入框</button>
|
||||||
|
</article>
|
||||||
|
|
||||||
|
<section class="list">
|
||||||
|
<course-card
|
||||||
|
v-for="course in filteredCourses"
|
||||||
|
:key="course.id"
|
||||||
|
:course="course"
|
||||||
|
theme="accent"
|
||||||
|
@toggle="toggleCourse"
|
||||||
|
>
|
||||||
|
<template v-slot:action>
|
||||||
|
切换完成状态
|
||||||
|
</template>
|
||||||
|
</course-card>
|
||||||
|
</section>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
|
||||||
|
<script src="./answer.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
80
07-vue2/08-components-communication-and-final/answer.js
Normal file
80
07-vue2/08-components-communication-and-final/answer.js
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
Vue.component("course-badge", {
|
||||||
|
props: {
|
||||||
|
label: {
|
||||||
|
type: String,
|
||||||
|
default: "默认角标",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
template: `<span style="display:inline-block;margin-bottom:8px;padding:4px 10px;border-radius:999px;background:#eef4ff;color:#2d6cdf;">{{ label }}</span>`,
|
||||||
|
});
|
||||||
|
|
||||||
|
const CourseCard = {
|
||||||
|
props: {
|
||||||
|
course: {
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
theme: {
|
||||||
|
type: String,
|
||||||
|
default: "normal",
|
||||||
|
validator(value) {
|
||||||
|
return ["normal", "accent"].includes(value);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
template: `
|
||||||
|
<article class="card" :style="theme === 'accent' ? 'border-color:#2d6cdf;' : ''">
|
||||||
|
<course-badge :label="course.level"></course-badge>
|
||||||
|
<h2>{{ course.title }}</h2>
|
||||||
|
<p :class="{ done: course.finished }">
|
||||||
|
{{ course.finished ? "已完成" : "学习中" }}
|
||||||
|
</p>
|
||||||
|
<button type="button" @click="$emit('toggle', course.id)">
|
||||||
|
<slot name="action">操作</slot>
|
||||||
|
</button>
|
||||||
|
</article>
|
||||||
|
`,
|
||||||
|
};
|
||||||
|
|
||||||
|
new Vue({
|
||||||
|
el: "#app",
|
||||||
|
components: {
|
||||||
|
CourseCard,
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
keyword: "",
|
||||||
|
courses: [
|
||||||
|
{ id: 1, title: "Vue 实例", finished: true, level: "基础" },
|
||||||
|
{ id: 2, title: "模板语法", finished: false, level: "基础" },
|
||||||
|
{ id: 3, title: "组件通信", finished: false, level: "进阶" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
filteredCourses() {
|
||||||
|
const value = this.keyword.trim().toLowerCase();
|
||||||
|
|
||||||
|
if (!value) {
|
||||||
|
return this.courses;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.courses.filter((course) => course.title.toLowerCase().includes(value));
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
toggleCourse(courseId) {
|
||||||
|
this.courses = this.courses.map((course) => {
|
||||||
|
if (course.id === courseId) {
|
||||||
|
return {
|
||||||
|
...course,
|
||||||
|
finished: !course.finished,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return course;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
focusInput() {
|
||||||
|
this.$refs.keywordInput.focus();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
43
07-vue2/08-components-communication-and-final/starter.html
Normal file
43
07-vue2/08-components-communication-and-final/starter.html
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>组件通信与综合练习</title>
|
||||||
|
<style>
|
||||||
|
body { margin: 0; padding: 32px; font-family: "PingFang SC", sans-serif; background: #f4f7fb; }
|
||||||
|
.wrap { max-width: 860px; margin: 0 auto; display: grid; gap: 16px; }
|
||||||
|
.toolbar, .card { padding: 20px; border-radius: 18px; background: #fff; border: 1px solid #d9e3f2; }
|
||||||
|
.list { display: grid; gap: 14px; }
|
||||||
|
button { padding: 10px 14px; border: 0; border-radius: 999px; background: #2d6cdf; color: #fff; cursor: pointer; }
|
||||||
|
input { width: 100%; padding: 12px; border-radius: 12px; border: 1px solid #cfd8e8; }
|
||||||
|
.done { color: #1f8f54; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<section id="app" class="wrap">
|
||||||
|
<article class="toolbar">
|
||||||
|
<h1>Vue2 综合练习</h1>
|
||||||
|
<input ref="keywordInput" v-model="keyword" type="text" placeholder="输入关键字过滤课程" />
|
||||||
|
<button type="button" @click="focusInput">聚焦输入框</button>
|
||||||
|
</article>
|
||||||
|
|
||||||
|
<section class="list">
|
||||||
|
<course-card
|
||||||
|
v-for="course in filteredCourses"
|
||||||
|
:key="course.id"
|
||||||
|
:course="course"
|
||||||
|
theme="accent"
|
||||||
|
@toggle="toggleCourse"
|
||||||
|
>
|
||||||
|
<template v-slot:action>
|
||||||
|
切换完成状态
|
||||||
|
</template>
|
||||||
|
</course-card>
|
||||||
|
</section>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
|
||||||
|
<script src="./starter.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
74
07-vue2/08-components-communication-and-final/starter.js
Normal file
74
07-vue2/08-components-communication-and-final/starter.js
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
Vue.component("course-badge", {
|
||||||
|
props: {
|
||||||
|
label: {
|
||||||
|
type: String,
|
||||||
|
default: "默认角标",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
template: `<span style="display:inline-block;margin-bottom:8px;padding:4px 10px;border-radius:999px;background:#eef4ff;color:#2d6cdf;">{{ label }}</span>`,
|
||||||
|
});
|
||||||
|
|
||||||
|
const CourseCard = {
|
||||||
|
props: {
|
||||||
|
course: {
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
theme: {
|
||||||
|
type: String,
|
||||||
|
default: "normal",
|
||||||
|
validator(value) {
|
||||||
|
return ["normal", "accent"].includes(value);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
template: `
|
||||||
|
<article class="card" :style="theme === 'accent' ? 'border-color:#2d6cdf;' : ''">
|
||||||
|
<course-badge :label="course.level"></course-badge>
|
||||||
|
<h2>{{ course.title }}</h2>
|
||||||
|
<p :class="{ done: course.finished }">
|
||||||
|
{{ course.finished ? "已完成" : "学习中" }}
|
||||||
|
</p>
|
||||||
|
<button type="button" @click="$emit('toggle', course.id)">
|
||||||
|
<slot name="action">操作</slot>
|
||||||
|
</button>
|
||||||
|
</article>
|
||||||
|
`,
|
||||||
|
};
|
||||||
|
|
||||||
|
new Vue({
|
||||||
|
el: "#app",
|
||||||
|
components: {
|
||||||
|
CourseCard,
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
keyword: "",
|
||||||
|
courses: [
|
||||||
|
{ id: 1, title: "Vue 实例", finished: true, level: "基础" },
|
||||||
|
{ id: 2, title: "模板语法", finished: false, level: "基础" },
|
||||||
|
{ id: 3, title: "组件通信", finished: false, level: "进阶" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
filteredCourses() {
|
||||||
|
// 任务:根据 keyword 过滤课程列表
|
||||||
|
let value = this.keyword.trim().toUpperCase()
|
||||||
|
return this.courses.filter(item => item.title.toUpperCase().includes(value))
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
toggleCourse(courseId) {
|
||||||
|
// 任务:根据 courseId 切换 finished
|
||||||
|
const a = this.courses.find(item => {
|
||||||
|
if (item.id === courseId) {
|
||||||
|
item.finished = !item.finished
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
})
|
||||||
|
},
|
||||||
|
focusInput() {
|
||||||
|
// 任务:通过 ref 聚焦输入框
|
||||||
|
this.$refs.keywordInput.focus()
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
44
07-vue2/09-final-course-dashboard/README.md
Normal file
44
07-vue2/09-final-course-dashboard/README.md
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
# 练习 9:Vue2 综合课程管理面板
|
||||||
|
|
||||||
|
## 目标
|
||||||
|
|
||||||
|
把 Vue2 入门阶段最关键的能力串起来,完成一个带搜索、筛选、统计、组件通信的课程管理面板。
|
||||||
|
|
||||||
|
## 你要练什么
|
||||||
|
|
||||||
|
- `new Vue()`
|
||||||
|
- `data`
|
||||||
|
- `methods`
|
||||||
|
- `computed`
|
||||||
|
- `watch`
|
||||||
|
- `v-model`
|
||||||
|
- `v-if`
|
||||||
|
- `v-show`
|
||||||
|
- `v-for`
|
||||||
|
- `:key`
|
||||||
|
- `:class`
|
||||||
|
- `props`
|
||||||
|
- `props` 校验
|
||||||
|
- `$emit`
|
||||||
|
- `slot`
|
||||||
|
- `ref`
|
||||||
|
|
||||||
|
## 任务
|
||||||
|
|
||||||
|
请基于页面结构完成以下操作:
|
||||||
|
|
||||||
|
- 输入关键字过滤课程名称
|
||||||
|
- 用下拉框切换“全部 / 已完成 / 学习中”
|
||||||
|
- 点击按钮让搜索框获得焦点
|
||||||
|
- 用子组件渲染每一项课程
|
||||||
|
- 子组件通过 `$emit` 通知父组件切换完成状态
|
||||||
|
- 没有匹配结果时显示空状态
|
||||||
|
- 页面底部显示总数、已完成数、当前筛选数
|
||||||
|
- 在 `watch` 里输出关键字变化日志
|
||||||
|
|
||||||
|
## 文件
|
||||||
|
|
||||||
|
- [starter.html](/Users/lijiaqing/home/wwwroot/front-end-example/07-vue2/09-final-course-dashboard/starter.html)
|
||||||
|
- [starter.js](/Users/lijiaqing/home/wwwroot/front-end-example/07-vue2/09-final-course-dashboard/starter.js)
|
||||||
|
- [answer.html](/Users/lijiaqing/home/wwwroot/front-end-example/07-vue2/09-final-course-dashboard/answer.html)
|
||||||
|
- [answer.js](/Users/lijiaqing/home/wwwroot/front-end-example/07-vue2/09-final-course-dashboard/answer.js)
|
||||||
66
07-vue2/09-final-course-dashboard/answer.html
Normal file
66
07-vue2/09-final-course-dashboard/answer.html
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>Vue2 综合课程管理面板</title>
|
||||||
|
<style>
|
||||||
|
body { margin: 0; padding: 32px; font-family: "PingFang SC", sans-serif; background: #f4f7fb; }
|
||||||
|
.page { max-width: 960px; margin: 0 auto; display: grid; gap: 18px; }
|
||||||
|
.panel { padding: 22px; border-radius: 20px; background: #fff; border: 1px solid #d9e4f1; }
|
||||||
|
.toolbar { display: grid; grid-template-columns: 1fr 180px 160px; gap: 12px; }
|
||||||
|
input, select, button { padding: 12px 14px; border-radius: 12px; border: 1px solid #cad6e8; font: inherit; }
|
||||||
|
button { border: 0; background: #2d6cdf; color: #fff; cursor: pointer; }
|
||||||
|
.list { display: grid; gap: 14px; }
|
||||||
|
.course-card { padding: 18px; border-radius: 16px; border: 1px solid #dbe4f1; background: #fbfcfe; }
|
||||||
|
.course-card.is-done { border-color: #6cc18a; background: #f3fbf6; }
|
||||||
|
.meta { display: flex; align-items: center; gap: 10px; margin-bottom: 10px; }
|
||||||
|
.badge { display: inline-flex; padding: 4px 10px; border-radius: 999px; background: #edf4ff; color: #2d6cdf; font-size: 12px; }
|
||||||
|
.badge.advanced { background: #fff0e8; color: #d96a18; }
|
||||||
|
.stats { display: grid; grid-template-columns: repeat(3, 1fr); gap: 12px; }
|
||||||
|
.empty { padding: 18px; border-radius: 16px; background: #fff6e8; color: #a06200; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<section id="app" class="page">
|
||||||
|
<article class="panel">
|
||||||
|
<h1>Vue2 课程管理面板</h1>
|
||||||
|
<div class="toolbar">
|
||||||
|
<input ref="searchInput" v-model="keyword" type="text" placeholder="输入课程关键字" />
|
||||||
|
<select v-model="statusFilter">
|
||||||
|
<option value="all">全部</option>
|
||||||
|
<option value="done">已完成</option>
|
||||||
|
<option value="doing">学习中</option>
|
||||||
|
</select>
|
||||||
|
<button type="button" @click="focusSearch">聚焦搜索框</button>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
|
||||||
|
<section class="panel">
|
||||||
|
<div v-if="!filteredCourses.length" class="empty">暂无匹配结果</div>
|
||||||
|
|
||||||
|
<div v-show="filteredCourses.length" class="list">
|
||||||
|
<course-item
|
||||||
|
v-for="course in filteredCourses"
|
||||||
|
:key="course.id"
|
||||||
|
:course="course"
|
||||||
|
@toggle="toggleCourse"
|
||||||
|
>
|
||||||
|
<template v-slot:action>
|
||||||
|
切换完成状态
|
||||||
|
</template>
|
||||||
|
</course-item>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<footer class="panel stats">
|
||||||
|
<div>总课程数:{{ totalCount }}</div>
|
||||||
|
<div>已完成数:{{ finishedCount }}</div>
|
||||||
|
<div>当前筛选数:{{ visibleCount }}</div>
|
||||||
|
</footer>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
|
||||||
|
<script src="./answer.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
99
07-vue2/09-final-course-dashboard/answer.js
Normal file
99
07-vue2/09-final-course-dashboard/answer.js
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
Vue.component("level-badge", {
|
||||||
|
props: {
|
||||||
|
level: {
|
||||||
|
type: String,
|
||||||
|
default: "基础",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
badgeClass() {
|
||||||
|
return this.level === "进阶" ? "badge advanced" : "badge";
|
||||||
|
},
|
||||||
|
},
|
||||||
|
template: `<span :class="badgeClass">{{ level }}</span>`,
|
||||||
|
});
|
||||||
|
|
||||||
|
const CourseItem = {
|
||||||
|
props: {
|
||||||
|
course: {
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
template: `
|
||||||
|
<article class="course-card" :class="{ 'is-done': course.finished }">
|
||||||
|
<div class="meta">
|
||||||
|
<level-badge :level="course.level"></level-badge>
|
||||||
|
<strong>{{ course.title }}</strong>
|
||||||
|
</div>
|
||||||
|
<p>{{ course.finished ? "当前已完成" : "当前学习中" }}</p>
|
||||||
|
<button type="button" @click="$emit('toggle', course.id)">
|
||||||
|
<slot name="action">操作</slot>
|
||||||
|
</button>
|
||||||
|
</article>
|
||||||
|
`,
|
||||||
|
};
|
||||||
|
|
||||||
|
new Vue({
|
||||||
|
el: "#app",
|
||||||
|
components: {
|
||||||
|
CourseItem,
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
keyword: "",
|
||||||
|
statusFilter: "all",
|
||||||
|
courses: [
|
||||||
|
{ id: 1, title: "Vue 实例基础", finished: true, level: "基础" },
|
||||||
|
{ id: 2, title: "模板语法与指令", finished: false, level: "基础" },
|
||||||
|
{ id: 3, title: "组件通信实战", finished: false, level: "进阶" },
|
||||||
|
{ id: 4, title: "生命周期和异步", finished: true, level: "进阶" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
filteredCourses() {
|
||||||
|
const keyword = this.keyword.trim().toLowerCase();
|
||||||
|
|
||||||
|
return this.courses.filter((course) => {
|
||||||
|
const matchStatus =
|
||||||
|
this.statusFilter === "all" ||
|
||||||
|
(this.statusFilter === "done" && course.finished) ||
|
||||||
|
(this.statusFilter === "doing" && !course.finished);
|
||||||
|
|
||||||
|
const matchKeyword = !keyword || course.title.toLowerCase().includes(keyword);
|
||||||
|
|
||||||
|
return matchStatus && matchKeyword;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
totalCount() {
|
||||||
|
return this.courses.length;
|
||||||
|
},
|
||||||
|
finishedCount() {
|
||||||
|
return this.courses.filter((course) => course.finished).length;
|
||||||
|
},
|
||||||
|
visibleCount() {
|
||||||
|
return this.filteredCourses.length;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
keyword(newValue) {
|
||||||
|
console.log("搜索关键字变化:", newValue);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
toggleCourse(courseId) {
|
||||||
|
this.courses = this.courses.map((course) => {
|
||||||
|
if (course.id === courseId) {
|
||||||
|
return {
|
||||||
|
...course,
|
||||||
|
finished: !course.finished,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return course;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
focusSearch() {
|
||||||
|
this.$refs.searchInput.focus();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
65
07-vue2/09-final-course-dashboard/starter.html
Normal file
65
07-vue2/09-final-course-dashboard/starter.html
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>Vue2 综合课程管理面板</title>
|
||||||
|
<style>
|
||||||
|
body { margin: 0; padding: 32px; font-family: "PingFang SC", sans-serif; background: #f4f7fb; }
|
||||||
|
.page { max-width: 960px; margin: 0 auto; display: grid; gap: 18px; }
|
||||||
|
.panel { padding: 22px; border-radius: 20px; background: #fff; border: 1px solid #d9e4f1; }
|
||||||
|
.toolbar { display: grid; grid-template-columns: 1fr 180px 160px; gap: 12px; }
|
||||||
|
input, select, button { padding: 12px 14px; border-radius: 12px; border: 1px solid #cad6e8; font: inherit; }
|
||||||
|
button { border: 0; background: #2d6cdf; color: #fff; cursor: pointer; }
|
||||||
|
.list { display: grid; gap: 14px; }
|
||||||
|
.course-card { padding: 18px; border-radius: 16px; border: 1px solid #dbe4f1; background: #fbfcfe; }
|
||||||
|
.course-card.is-done { border-color: #6cc18a; background: #f3fbf6; }
|
||||||
|
.meta { display: flex; align-items: center; gap: 10px; margin-bottom: 10px; }
|
||||||
|
.badge { display: inline-flex; padding: 4px 10px; border-radius: 999px; background: #edf4ff; color: #2d6cdf; font-size: 12px; }
|
||||||
|
.stats { display: grid; grid-template-columns: repeat(3, 1fr); gap: 12px; }
|
||||||
|
.empty { padding: 18px; border-radius: 16px; background: #fff6e8; color: #a06200; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<section id="app" class="page">
|
||||||
|
<article class="panel">
|
||||||
|
<h1>Vue2 课程管理面板</h1>
|
||||||
|
<div class="toolbar">
|
||||||
|
<input ref="searchInput" v-model="keyword" type="text" placeholder="输入课程关键字" />
|
||||||
|
<select v-model="statusFilter">
|
||||||
|
<option value="all">全部</option>
|
||||||
|
<option value="done">已完成</option>
|
||||||
|
<option value="doing">学习中</option>
|
||||||
|
</select>
|
||||||
|
<button type="button" @click="focusSearch">聚焦搜索框</button>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
|
||||||
|
<section class="panel">
|
||||||
|
<div v-if="!filteredCourses.length" class="empty">暂无匹配结果</div>
|
||||||
|
|
||||||
|
<div v-show="filteredCourses.length" class="list">
|
||||||
|
<course-item
|
||||||
|
v-for="course in filteredCourses"
|
||||||
|
:key="course.id"
|
||||||
|
:course="course"
|
||||||
|
@toggle="toggleCourse"
|
||||||
|
>
|
||||||
|
<template v-slot:action>
|
||||||
|
切换完成状态
|
||||||
|
</template>
|
||||||
|
</course-item>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<footer class="panel stats">
|
||||||
|
<div>总课程数:{{ totalCount }}</div>
|
||||||
|
<div>已完成数:{{ finishedCount }}</div>
|
||||||
|
<div>当前筛选数:{{ visibleCount }}</div>
|
||||||
|
</footer>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
|
||||||
|
<script src="./starter.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
97
07-vue2/09-final-course-dashboard/starter.js
Normal file
97
07-vue2/09-final-course-dashboard/starter.js
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
Vue.component("level-badge", {
|
||||||
|
props: {
|
||||||
|
level: {
|
||||||
|
type: String,
|
||||||
|
default: "基础",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
template: `<span class="badge">{{ level }}</span>`,
|
||||||
|
});
|
||||||
|
|
||||||
|
const CourseItem = {
|
||||||
|
props: {
|
||||||
|
course: {
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
template: `
|
||||||
|
<article class="course-card" :class="{ 'is-done': course.finished }">
|
||||||
|
<div class="meta">
|
||||||
|
<level-badge :level="course.level"></level-badge>
|
||||||
|
<strong>{{ course.title }}</strong>
|
||||||
|
</div>
|
||||||
|
<p>{{ course.finished ? "当前已完成" : "当前学习中" }}</p>
|
||||||
|
<button type="button" @click="$emit('toggle', course.id)">
|
||||||
|
<slot name="action">操作</slot>
|
||||||
|
</button>
|
||||||
|
</article>
|
||||||
|
`,
|
||||||
|
};
|
||||||
|
|
||||||
|
new Vue({
|
||||||
|
el: "#app",
|
||||||
|
components: {
|
||||||
|
CourseItem,
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
keyword: "",
|
||||||
|
statusFilter: "all",
|
||||||
|
courses: [
|
||||||
|
{ id: 1, title: "Vue 实例基础", finished: true, level: "基础" },
|
||||||
|
{ id: 2, title: "模板语法与指令", finished: false, level: "基础" },
|
||||||
|
{ id: 3, title: "组件通信实战", finished: false, level: "进阶" },
|
||||||
|
{ id: 4, title: "生命周期和异步", finished: true, level: "进阶" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
filteredCourses() {
|
||||||
|
// 任务:
|
||||||
|
// 1. 先按 statusFilter 过滤
|
||||||
|
// 2. 再按 keyword 过滤标题
|
||||||
|
let value = this.keyword.trim().toUpperCase()
|
||||||
|
let a
|
||||||
|
if (this.statusFilter === "all") {
|
||||||
|
a = this.courses.filter(item => item.title.toUpperCase().includes(value))
|
||||||
|
} else if (this.statusFilter === "done") {
|
||||||
|
const b = this.courses.filter(item => item.finished === true)
|
||||||
|
a = b.filter(item => item.title.toUpperCase().includes(value))
|
||||||
|
} else if (this.statusFilter === "doing") {
|
||||||
|
const b = this.courses.filter(item => item.finished === false)
|
||||||
|
a = b.filter(item => item.title.toUpperCase().includes(value))
|
||||||
|
}
|
||||||
|
return a
|
||||||
|
},
|
||||||
|
totalCount() {
|
||||||
|
return this.courses.length;
|
||||||
|
},
|
||||||
|
finishedCount() {
|
||||||
|
// 任务:返回已完成课程数量
|
||||||
|
const b = this.courses.filter(item => item.finished === true)
|
||||||
|
return b.length
|
||||||
|
},
|
||||||
|
visibleCount() {
|
||||||
|
// 任务:返回当前筛选后的数量
|
||||||
|
const a = this.filteredCourses
|
||||||
|
return a.length;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
keyword(newValue) {
|
||||||
|
// 任务:在控制台输出关键字变化
|
||||||
|
console.log(`关键词:${newValue}`);
|
||||||
|
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
toggleCourse(courseId) {
|
||||||
|
// 任务:根据 courseId 切换 finished
|
||||||
|
const a = this.courses.find(item => item.id === courseId)
|
||||||
|
a.finished = !a.finished
|
||||||
|
},
|
||||||
|
focusSearch() {
|
||||||
|
// 任务:通过 ref 聚焦输入框
|
||||||
|
this.$refs.searchInput.focus()
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
158
07-vue2/README.md
Normal file
158
07-vue2/README.md
Normal file
@@ -0,0 +1,158 @@
|
|||||||
|
# Vue2(理解框架思想)
|
||||||
|
|
||||||
|
这部分只解决一个问题:你能不能从“手写 DOM”切换到“数据驱动视图”的思维方式,并理解 Vue2 为什么能让页面开发更高效。
|
||||||
|
|
||||||
|
## 学完后你应该掌握
|
||||||
|
|
||||||
|
- Vue2 是什么,为什么说它是渐进式框架
|
||||||
|
- `new Vue()`、`el`、`data`、`methods` 的基础写法
|
||||||
|
- 模板插值 `{{ }}`
|
||||||
|
- `v-bind` 和 `v-on`
|
||||||
|
- `v-model` 做表单双向绑定,包括文本、单选和复选
|
||||||
|
- `v-if`、`v-show`、`v-for`、`:key`
|
||||||
|
- `computed` 和 `watch` 的基本区别
|
||||||
|
- `:class` 和 `:style` 的动态绑定
|
||||||
|
- 生命周期里的 `created`、`mounted`、`updated`、`beforeDestroy`、`destroyed`
|
||||||
|
- 组件、`props`、`props` 校验、`$emit`
|
||||||
|
- 插槽 `slot`
|
||||||
|
- `ref` 的基本用法
|
||||||
|
- 如何把这些能力组合成一个 Vue2 小页面
|
||||||
|
|
||||||
|
## 这一章在解决什么
|
||||||
|
|
||||||
|
Vue2 不是教你再多学一套“新语法”,而是帮你把页面开发的思路改掉。
|
||||||
|
|
||||||
|
它回答的是:
|
||||||
|
|
||||||
|
- 页面内容为什么应该由数据决定
|
||||||
|
- 一个点击事件为什么不一定要手动改 DOM
|
||||||
|
- 一段重复结构为什么应该交给 `v-for`
|
||||||
|
- 一块页面为什么应该拆成组件
|
||||||
|
- 父组件和子组件之间怎么传数据和传事件
|
||||||
|
|
||||||
|
## 必须建立的 6 个核心意识
|
||||||
|
|
||||||
|
### 1. 视图应该跟着数据走
|
||||||
|
|
||||||
|
不是先找 DOM 再改内容,而是先改数据,让页面自动更新。
|
||||||
|
|
||||||
|
### 2. 指令是模板和数据之间的桥
|
||||||
|
|
||||||
|
像 `v-bind`、`v-on`、`v-model`、`v-if`、`v-for` 这些指令,本质上是在描述“数据怎么影响页面”。
|
||||||
|
|
||||||
|
### 3. 计算属性更适合“基于已有数据得出结果”
|
||||||
|
|
||||||
|
如果一个值可以从别的数据推导出来,优先考虑 `computed`。
|
||||||
|
|
||||||
|
### 4. `watch` 更适合“监听变化后做副作用”
|
||||||
|
|
||||||
|
比如发请求、写日志、同步别的状态。
|
||||||
|
|
||||||
|
### 5. 组件化不是拆文件,而是拆职责
|
||||||
|
|
||||||
|
一个组件只关心一块明确的界面和交互。
|
||||||
|
|
||||||
|
### 6. 父子通信是 Vue 开发主线
|
||||||
|
|
||||||
|
- 父组件通过 `props` 往下传数据
|
||||||
|
- 子组件通过 `$emit` 往上通知事件
|
||||||
|
|
||||||
|
## 全部知识点清单
|
||||||
|
|
||||||
|
### 基础认知
|
||||||
|
|
||||||
|
- Vue2 渐进式框架
|
||||||
|
- 数据驱动视图
|
||||||
|
- 声明式渲染
|
||||||
|
|
||||||
|
### 实例基础
|
||||||
|
|
||||||
|
- `new Vue()`
|
||||||
|
- `el`
|
||||||
|
- `data`
|
||||||
|
- `methods`
|
||||||
|
|
||||||
|
### 模板与指令
|
||||||
|
|
||||||
|
- `{{ }}`
|
||||||
|
- `v-bind`
|
||||||
|
- `v-on`
|
||||||
|
- `v-model`
|
||||||
|
- `radio`
|
||||||
|
- `checkbox`
|
||||||
|
- `v-if`
|
||||||
|
- `v-else`
|
||||||
|
- `v-show`
|
||||||
|
- `v-for`
|
||||||
|
- `:key`
|
||||||
|
|
||||||
|
### 响应式进阶
|
||||||
|
|
||||||
|
- `computed`
|
||||||
|
- `watch`
|
||||||
|
- `:class`
|
||||||
|
- `:style`
|
||||||
|
|
||||||
|
### 生命周期
|
||||||
|
|
||||||
|
- `created`
|
||||||
|
- `mounted`
|
||||||
|
- `updated`
|
||||||
|
- `beforeDestroy`
|
||||||
|
- `destroyed`
|
||||||
|
|
||||||
|
### 组件通信
|
||||||
|
|
||||||
|
- 组件注册(全局 / 局部)
|
||||||
|
- `props`
|
||||||
|
- `props` 校验
|
||||||
|
- `$emit`
|
||||||
|
- `slot`
|
||||||
|
- `ref`
|
||||||
|
- 综合页面组织能力
|
||||||
|
|
||||||
|
## 学习顺序
|
||||||
|
|
||||||
|
1. Vue 实例和数据绑定
|
||||||
|
2. 模板语法和事件绑定
|
||||||
|
3. 表单双向绑定
|
||||||
|
4. 条件渲染和列表渲染
|
||||||
|
5. `computed` 和 `watch`
|
||||||
|
6. 动态类名、样式和事件交互
|
||||||
|
7. 生命周期和异步更新
|
||||||
|
8. 组件通信
|
||||||
|
9. 综合课程管理面板
|
||||||
|
|
||||||
|
## 练习目录
|
||||||
|
|
||||||
|
- [01-vue-instance-and-data/README.md](/Users/lijiaqing/home/wwwroot/front-end-example/07-vue2/01-vue-instance-and-data/README.md)
|
||||||
|
- [02-template-bindings/README.md](/Users/lijiaqing/home/wwwroot/front-end-example/07-vue2/02-template-bindings/README.md)
|
||||||
|
- [03-v-model-and-form/README.md](/Users/lijiaqing/home/wwwroot/front-end-example/07-vue2/03-v-model-and-form/README.md)
|
||||||
|
- [04-conditional-and-list-rendering/README.md](/Users/lijiaqing/home/wwwroot/front-end-example/07-vue2/04-conditional-and-list-rendering/README.md)
|
||||||
|
- [05-computed-and-watch/README.md](/Users/lijiaqing/home/wwwroot/front-end-example/07-vue2/05-computed-and-watch/README.md)
|
||||||
|
- [06-class-style-and-event/README.md](/Users/lijiaqing/home/wwwroot/front-end-example/07-vue2/06-class-style-and-event/README.md)
|
||||||
|
- [07-lifecycle-and-async/README.md](/Users/lijiaqing/home/wwwroot/front-end-example/07-vue2/07-lifecycle-and-async/README.md)
|
||||||
|
- [08-components-communication-and-final/README.md](/Users/lijiaqing/home/wwwroot/front-end-example/07-vue2/08-components-communication-and-final/README.md)
|
||||||
|
- [09-final-course-dashboard/README.md](/Users/lijiaqing/home/wwwroot/front-end-example/07-vue2/09-final-course-dashboard/README.md)
|
||||||
|
|
||||||
|
## 过关标准
|
||||||
|
|
||||||
|
如果你能独立做到下面这些,就说明这一章已经基本过关:
|
||||||
|
|
||||||
|
- 能写出一个基础 Vue2 实例
|
||||||
|
- 能用模板语法展示数据
|
||||||
|
- 能用 `v-model` 处理输入
|
||||||
|
- 能用 `v-model` 处理单选框和复选框
|
||||||
|
- 能用 `v-if`、`v-show`、`v-for` 组织页面
|
||||||
|
- 能分清 `computed` 和 `watch`
|
||||||
|
- 能写出基础组件并使用 `props` / `props` 校验 / `$emit`
|
||||||
|
- 能理解插槽和 `ref` 的常见用途
|
||||||
|
- 能知道 Vue 实例销毁前后会经过哪些生命周期
|
||||||
|
- 能完成一个带组件通信的小页面
|
||||||
|
|
||||||
|
## 学习建议
|
||||||
|
|
||||||
|
- 学 Vue2 时先不要急着记所有 API,先抓住“数据变,页面跟着变”这条主线
|
||||||
|
- 遇到页面不更新时,先看是不是数据没有改到
|
||||||
|
- 遇到组件通信问题时,先分清“数据往下传”还是“事件往上抛”
|
||||||
|
- 学组件时先从 2 个组件开始,不要一上来拆太细
|
||||||
23
08-vue3/01-create-app-and-ref/README.md
Normal file
23
08-vue3/01-create-app-and-ref/README.md
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
# 练习 1:createApp、setup 和 ref
|
||||||
|
|
||||||
|
## 目标
|
||||||
|
|
||||||
|
学会用 Vue3 的 `createApp` 和 `setup()` 启动页面,并用 `ref` 创建最基础的响应式数据。
|
||||||
|
|
||||||
|
## 你要练什么
|
||||||
|
|
||||||
|
- `createApp`
|
||||||
|
- `setup()`
|
||||||
|
- `ref`
|
||||||
|
- 模板插值
|
||||||
|
|
||||||
|
## 任务
|
||||||
|
|
||||||
|
- 显示页面标题和学习人数
|
||||||
|
- 点击按钮后让人数加 1
|
||||||
|
- 在控制台输出最新人数
|
||||||
|
|
||||||
|
## 文件
|
||||||
|
|
||||||
|
- [starter.html](/Users/lijiaqing/home/wwwroot/front-end-example/08-vue3/01-create-app-and-ref/starter.html)
|
||||||
|
- [starter.js](/Users/lijiaqing/home/wwwroot/front-end-example/08-vue3/01-create-app-and-ref/starter.js)
|
||||||
23
08-vue3/01-create-app-and-ref/starter.html
Normal file
23
08-vue3/01-create-app-and-ref/starter.html
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>createApp、setup 和 ref</title>
|
||||||
|
<style>
|
||||||
|
body { margin: 0; padding: 32px; font-family: "PingFang SC", sans-serif; background: #f5f7fb; }
|
||||||
|
.panel { max-width: 720px; margin: 0 auto; padding: 24px; border-radius: 18px; background: #fff; border: 1px solid #dce5f2; }
|
||||||
|
button { padding: 10px 16px; border: 0; border-radius: 999px; background: #2d6cdf; color: #fff; cursor: pointer; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<section id="app" class="panel">
|
||||||
|
<h1>{{ title }}</h1>
|
||||||
|
<p>当前学习人数:{{ learnerCount }}</p>
|
||||||
|
<button type="button" @click="increaseLearner">加入学习</button>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/vue@3/dist/vue.global.js"></script>
|
||||||
|
<script src="./starter.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
20
08-vue3/01-create-app-and-ref/starter.js
Normal file
20
08-vue3/01-create-app-and-ref/starter.js
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
const { createApp, ref } = Vue;
|
||||||
|
|
||||||
|
createApp({
|
||||||
|
setup() {
|
||||||
|
const title = ref("Vue3 基础入门");
|
||||||
|
const learnerCount = ref(16);
|
||||||
|
|
||||||
|
function increaseLearner() {
|
||||||
|
// 任务:
|
||||||
|
// 1. learnerCount 加 1
|
||||||
|
// 2. 在控制台输出最新人数
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
title,
|
||||||
|
learnerCount,
|
||||||
|
increaseLearner,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
}).mount("#app");
|
||||||
23
08-vue3/02-reactive-and-computed/README.md
Normal file
23
08-vue3/02-reactive-and-computed/README.md
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
# 练习 2:reactive 和 computed
|
||||||
|
|
||||||
|
## 目标
|
||||||
|
|
||||||
|
学会把一组相关数据放进 `reactive`,并用 `computed` 推导派生结果。
|
||||||
|
|
||||||
|
## 你要练什么
|
||||||
|
|
||||||
|
- `reactive`
|
||||||
|
- `computed`
|
||||||
|
- 响应式对象
|
||||||
|
- 派生状态
|
||||||
|
|
||||||
|
## 任务
|
||||||
|
|
||||||
|
- 用 `reactive` 管理课程信息
|
||||||
|
- 用 `computed` 计算进度文案
|
||||||
|
- 点击按钮后让完成课时增加
|
||||||
|
|
||||||
|
## 文件
|
||||||
|
|
||||||
|
- [starter.html](/Users/lijiaqing/home/wwwroot/front-end-example/08-vue3/02-reactive-and-computed/starter.html)
|
||||||
|
- [starter.js](/Users/lijiaqing/home/wwwroot/front-end-example/08-vue3/02-reactive-and-computed/starter.js)
|
||||||
25
08-vue3/02-reactive-and-computed/starter.html
Normal file
25
08-vue3/02-reactive-and-computed/starter.html
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>reactive 和 computed</title>
|
||||||
|
<style>
|
||||||
|
body { margin: 0; padding: 32px; font-family: "PingFang SC", sans-serif; background: #f6f8fc; }
|
||||||
|
.panel { max-width: 720px; margin: 0 auto; padding: 24px; border-radius: 18px; background: #fff; border: 1px solid #dbe4f1; }
|
||||||
|
button { padding: 10px 16px; border: 0; border-radius: 999px; background: #1c2f52; color: #fff; cursor: pointer; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<section id="app" class="panel">
|
||||||
|
<h1>{{ course.title }}</h1>
|
||||||
|
<p>总课时:{{ course.totalLessons }}</p>
|
||||||
|
<p>已完成:{{ course.finishedLessons }}</p>
|
||||||
|
<p>{{ progressText }}</p>
|
||||||
|
<button type="button" @click="finishOneLesson">完成一节</button>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/vue@3/dist/vue.global.js"></script>
|
||||||
|
<script src="./starter.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user