习题详解

i) 有两个大标题,电视和手机,点击对应的标题,渲染对应的数据
ii) 一个字典作为一个显示单位,定义一个子组件进行渲染(涉及父子组件传参)

3、在第2题基础上,页面最下方有一个 h2 标签,用来渲染用户当前选择的广告(点击哪个广告就是选中哪个广告)
i)当没有点击任何广告,h2 标签显示:未选中任何广告
ii)当点击其中一个广告,如tv1,h2 标签显示:tv1被选中

'''
        <button @click="select = 'tv'" :class="{active: select === 'tv'}">tv</button>
        ...
        
    <div class="wrap" v-if="select === 'tv'">
        <tag v-for="tv in ad_data.tv" :xxx="tv" :key="tv.title" @yyy="choice"></tag>  # v-for指令需要给循环的标签或组件添加key属性设定主键索引缓存
    </div>
    ...
    
    <div class="nav"><h2>{{selectContent}}</h2></div>
    
    
    let tag = {
        props: ['xxx'],
        template: `
      <div class="box" @click="fn(xxx.title)">
            <img :src="xxx.img" alt="">
            <h2>{{xxx.title}}</h2>
        </div>
      `,
        methods: {
            fn(m) {
                this.$emit('yyy', m);
            },
        }

    };
    
    
    new Vue({
        ...
        components: {
            tag,
        },
        data: {
            ad_data,
            select: 'tv',
            selectContent: '未选中任何广告'
        },
        methods: {
            choice(a) {
                if (a) {
                    this.selectContent = `${a}被选中`  # js语法中的模板字符串
                }
            },
        },
    });
'''

js原型补充

'''
    function A() {
    }

    let a1 = new A();
    console.log(a1.num);  // undefined

    A.prototype.num = 100;  // 为A类添加原型
    console.log(a1.num);  // 100
    
    // ES6定义类的语法
	class B {
        constructor(name) {  // 构造器自定义对象属性
            this.name = name
        }
    }
    
	let b1 = new B('蔡启龙');
	console.log(b1.name);  // 蔡启龙
	
	// 推导
	Vue.prototype.$ajax = ...  // 为所有Vue对象添加$ajax属性
'''

Vue项目环境搭建

  • vue: 类似于django
  • node: 类似于python
  • npm: 类似于pip
'''
let a = 1;
undefined

console.log(a);
1
undefined  // console.log函数无返回值, 所以多出一个undefined结果
'''
  • 在系统环境中敲npm|pip可以查看npm|pip指令, 敲npm list|pip list可以查看npm或pip已安装过的包
  • 安装cnpm: 在系统环境中敲npm install -g cnpm --registry=https://registry.npm.taobao.org, cnpm是国内安装源
  • 安装脚手架: 在系统环境中敲cnpm install -g @vue/cli
  • 创建项目: 在系统环境中敲vue project my-projectvue ui
  • 在创建选项中, 选择安装babel插件, 作用: JavaScript编译器, 将es6转成es5

vue根据配置重新构建依赖

  1. 新建文件夹-->存放public文件夹, src文件夹, package.json文件

  2. 打开cmd, 输入执行: cnpm install, 会根据package.json文件重新构建依赖

  3. 如果后期缺少某个依赖, cd到项目所在目录手动安装, 例如cnpm install ajax

  4. 运行项目: cd到项目所在目录, 执行cnpm run serve

  5. src-->router-->index.js, 修改内容:

    '''
    ...
    import About from '../views/About.vue'
    
    ...
    
    const routes = [
      ...
      {
        ...
        component: () => About
      }
    ]
    
    ...
    '''
    

pycharm管理vue项目

Edit Configurations-->npm-->Scripts-->serve

Vue项目目录介绍

node_modules:

  • 当前项目所有依赖, 一般不可移植给其他电脑

public:

  • favicon.ico: 图标
  • index.html: 项目唯一主页

src:

  • assets: 资源文件夹, 一般建三个子文件夹: img, css, js
  • components: 小组件, 打开视图组件文件xxx.vue需要下载vue.js
  • views: 页面视图组件
  • router: 路由脚本
  • store: 仓库脚本
  • App.vue: 根视图组件
  • main.js: 全局脚本文件

package.json: 主要的配置文件

Vue项目生命周期

资源导入说明

main.js是整个项目的入口

vue语法中的 import App from './App' , 类似于python中的 from './App' import xxx as App

v-proj\src\main.js

  • import 别名 from 资源
    • 资源直接写名字时, 资源在node_modules文件夹中
    • 资源写路径时, 例如: './App.vue' , . 表示当前文件所在文件夹的路径,
    • 按路径导入资源时可以省略后缀, 例如:'./App.vue', 等价于 './App'

生命周期流程

  1. 启动项目, 加载主脚本文件main.js
    • 加载vue环境, 创建根组件完成渲染
    • 加载系统已有的第三方环境: router, store
    • 加载自定义的第三方环境与自己配置的环境
  2. router被加载, 就会解析router文件夹下的index.js脚本文件, 完成路由---组件的映射关系
  3. 新建视图组件: v-proj\src\views-->xxx.vue,
  4. 在v-proj\src\router\index.js中配置视图组件,
  5. 设置路由跳转: <router-link to="/about">About</router-link>

代码

'''
# v-proj\public\index.html
...
<html lang="en">
  ...
  <body>
    ...
    <div id="my_app"></div>
    ...
  </body>
</html>


# v-proj\src\main.js
import Vue from 'vue'  # 导入node_modules文件夹中的资源
import App from './App'
import router from './router'
import store from './store'

// 简写:
    new Vue({
      router,
      store,
      render: h => h(App)
    }).$mount('#my_app');  # 将根视图组件对应的数据渲染到public\index.html对应的挂载点

// 完整写法:  
    // new Vue({
    //     el: '#my_app',
    //     router: router,
    //     store: store,
    //     render: function (handle) {
    //         return handle(App);
    //     }
    // });
    

# 在v-proj\src\views中新建User.vue视图组件
<template>  # 视图组件通过router\index.js中对应配置渲染到根视图组件中的<router-view/>时的标识
    <span>User页面</span>
</template>
...


# v-proj\src\router\index.js
import Vue from 'vue'
import VueRouter from 'vue-router'
...
import User from '../views/User.vue'

Vue.use(VueRouter);

const routes = [
    ...,
    {
        path: '/user',
        ...
        ...
        component: User
    },

];

const router = new VueRouter({
    routes
});

export default router


# v-proj\src\App.vue
<template>  # main.js中对应函数读取根视图数据并渲染到挂载点的标识
  <div id="xxx">  # 根视图组件必须有且仅有一个块级根标签
    ...
      <router-link to="/user">User</router-link>  # 设置路由跳转
    ...
    <router-view/>  # 页面视图组件在根组件中的渲染占位符, 通常为一个, 也可以为多个
  </div>
</template>

...
'''

小组件的使用

template标签负责组件的html结构, 有且只能有一个根标签

script标签负责组件的js逻辑,

  • 组件需要要在script标签中通过vue语法导出外界才能导入使用该组件
  • 如果没有在script标签中通过vue语法导出, 则默认只加载页面HTML结构

style标签负责组件的css样式, scoped参数控制样式的组件化, 使样式只在对应的组件内部起作用

'''
# v-proj\src\components\component1.vue
<template>
    <div><h1>小组件</h1></div>
</template>

<script>
    export default {
        data() {
            return {};
        },
        methods: {},
    }
</script>

<style scoped>
    h1 {
        color: red;
    }
</style>


# v-proj\src\views\Home2.vue
<template>
    <div>
        <h1>Home页面组件</h1>
        <component1></component1>  # 页面组件中渲染小组件
    </div>
</template>

<script>
    import component1 from '../components/component1'  # 页面组件中导入小组件

    export default {
        components: {
            component1,  # 页面组件中注册小组件
        },
    }
</script>

<style scoped>
    h1 {
        color: aqua;
    }
</style>
'''

配置自定义全局样式

  1. 在v-proj\src\assets文件夹中新建css文件夹并新建css文件, 然后在该css文件中书写自定义的全局css样式
  2. 在main.js文件中导入全局css样式文件
'''
# v-proj\src\assets\css\global.css
ul {
    list-style: none; /*去除无序列表项的小圆点*/
}


# day68\v-proj\src\main.js

// 配置全局样式
import './assets/css/global.css'  // 只需加载文件时的导入方式

// 其他配置方式:
// import xxx from './assets/css/global.css'  // 要使用文件内容时的导入方式
// import '@/assets/css/global.css'  // @代表src文件夹的绝对路径
// require('@/assets/css/global.css');  // 官方推荐加载静态文件的方式, 可以用变量接收结果
'''

路由逻辑跳转

导航栏组件及其样式: copy

  • <router-link> 通过 to 属性指定目标地址, 默认渲染成带有正确链接的 <a> 标签, 例如
    • <router-link to="/course">课程页</router-link>: <a href="#/course" class="">课程页</a>
  • 当目标路由成功激活时, 链接元素自动设置一个表示激活的 CSS 类名
    • class="router-link-exact-active router-link-active": router-link-exact-active是精确匹配规则, router-link-active是全包含匹配规则
'''
# Nav.vue
                <li class="logo" @click="goHome"><img src="@/assets/img/lufei.svg" alt=""></li>
                
                <li class="route">
                    # <router-link to="/course">课程页</router-link>
                    <router-link :to="{name: 'course'}">课程页</router-link>  <!--类似于前端的反向解析-->
                </li>
                
<script>
    export default {
        name: "Nav",  // 组件的名字
        methods: {
            goHome() {
                // console.log(this.$router);  // VueRouter {…}, 控制路由跳转
                // console.log(this.$route);  // {name: "home", ..., path: "/", ...}, 控制路由数据
                if (this.$route.path !== '/') {  // 判断当前路由是否为home路由, 避免跳转重复路由报错
                    // this.$router.push('/');  
                    // this.$router.go(-1)  // go是历史记录前进后退, 正为前进, 负为后退, 数字为步数
                    this.$router.push({name: 'home'})
                }
            },
        }
    }
</script>


# v-proj\src\router\index.js
...

const routes = [
    {
        path: '/',
        name: 'home',  // 类似于反向解析中url的别名
        component: Home
    },
    ...
];

...
'''

路由重定向

'''
# v-proj\src\router\index.js
...

const routes = [
    ...,
    {
        path: '/xxx',
        redirect: '/'
    },
];

...
'''

组件的生命周期钩子

  1. 一个组件从创建到销毁的过程中, 几个特殊的时间节点回调的方法
  2. 这些方法都是vue组件实例的成员
'''
# v-proj\src\views\Home.vue
<script>
    export default {
        data() {
            return {
                back_data: 'Owen',
            };
        },
        beforeCreate() {
            console.log('Home组件要被创建了...');
            console.log(this.back_data)  // undefined
        },
        created() {  // 在该钩子中完成前端对后端数据的请求
            console.log('Home组件创建成功!');
            console.log(this.back_data)  // Owen
        },
        beforeMount() {
            console.log('Home组件准备挂载...');
        },
        mounted() {  // 对于特别耗时的数据请求, 可以延后到组件初步加载成功后, 在该钩子中继续请求
            console.log('Home组件挂载完成!');
        },
        destroyed() {  // 应用场景: 离开页面时可以在该钩子中进行二次确认
            console.log('Home组件销毁成功了!');
        },
    }
</script>
'''

路由传参

Course.vue

  • 在created钩子中前端对后端数据的请求
  • 如果组件加载静态数据并将其当做参数传给其他组件使用, 需要用require方法
'''
# v-proj\src\views\Course.vue
<template>
    <div class="course">
        ...
            <CourseTag v-for="course in courses" :course="course"></CourseTag>
        ...
    </div>
</template>

<script>
    ...
    import CourseTag from '../components/CourseTag'

    export default {
        ...
        components: {
            ...
            CourseTag,
        },
        data() {
            return {
                courses: [],
            };
        },
        created() {  // 在该钩子中完成前端对后端数据的请求
            // 前后端开发时从后端获取数据
            this.courses = [
                {
                    id: 1,
                    title: '西游记',

                    // 如果组件加载静态数据并将其当做参数传给其他组件使用, 需要用require方法
                    img: require('../assets/img/111.jpg'),  // 实际项目中为后端图片链接
                    // img: '../assets/img/111.jpg'  // 直接写路径无法加载图片
                },
                ...                    
            ]
        },
        mounted() {  // 如果是特别耗时的数据请求, 可以延后到组件初步加载成功后, 再慢慢请求
            console.log('Course组件挂载完成!');
        },
        destroyed() {  // 应用场景: 例如二次确认是否离开页面
            console.log('Course组件销毁成功了!');
        },
    }
</script>
'''

CourseTag.vue

  • 第一种路由传参: 在url后面通过?拼接参数
  • 第二种路由传参: 在url中通过模板字符串传递参数变量
  • this.$router.push: 控制路由跳转
'''
# v-proj\src\components\CourseTag.vue
<template>
    <div class="box">
        <img :src="course.img" alt="" @click="goDetail(course.id)">

        # 第一种路由传参: 在url后面通过?拼接参数
        # <router-link :to="`/course/detail?pk=${course.id}`"><h2>{{course.title}}</h2></router-link>
        """
        <router-link :to="{
            name: 'course-detail',
            query: {pk: course.id}
        }">
            <h2>{{course.title}}</h2>
        </router-link>
        """
		
		# 第二种路由传参: 在url中通过模板字符串传递参数变量
        <router-link :to="`/course/${course.id}/detail`"><h2>{{course.title}}</h2></router-link>
    </div>
</template>

<script>
    export default {
        props: ['course',],  
        methods: {
            goDetail(pk) {
                // this.$router.push(`/course/detail?pk=${pk}`);
                this.$router.push({
                    // path: '/course/detail',
                    name: 'course-detail',  // 控制点击后跳转的url
                    query: {pk: pk}  // 控制跳转的url?后拼接的参数
                })
            }
        }
    };
</script>
'''

index.js

'''
...
import CourseDetail from '../views/CourseDetail.vue'

...

const routes = [
    ...
    {
        // 第一种路由传参对应的配置
        // path: '/course/detail',

        // 第二种路由传参对应的配置
        path: '/course/:pk/detail',  // :pk为vue语法中的有名分组

        name: 'course-detail',
        component: CourseDetail
    },
];

...
'''

CourseDetail.vue

  • 在标签中获取当前组件中的数据可以直接通过变量名获取, 而不需要使用this.的形式
  • this.$route存放当前页的url信息
'''
# v-proj\src\views\CourseDetail.vue
<template>
    <div class="course-detail">
        <button @click="$router.go(-1)">返回课程页</button>  <!--在标签中不需要通过this.获取-->
        ...
    </div>
</template>

<script>
    export default {
        data() {
            return {
                pk: 0
            };
        },
        created() {
            // 获取路由传递的参数: 课程id
            // console.log(this.$route);  // {..., path: "/course/detail", ..., query: {pk: "3"}, …}
            // this.pk = this.$route.query.pk;  // query接收路径?后面传递过来的参数
            this.pk = this.$route.params.pk || this.$route.query.pk;  // params接收根据有名分组从路径字符串匹配到的参数
        },
    }
</script>
'''