Vue3.0入门指南

第一章、走进Vue3.0

2-1、下载Vue3.0的单文件核心库

vue3.0 源码下载地址: https://unpkg.com/vue@3.0.0-beta.17/dist/vue.global.js

sp1、github搜索vue, 到vuejs首页:其中"vue"项目是vue2.0版本, 选择"vue-next"项目(vue3.0版本)进入

sp2、选择"Branch:master"-->"Tags"查看最新版本, 通过UNPKG网站来下载我们需要的单文件, 比如: https://unpkg.com/vue@3.0.0-beta.17/dist/vue.global.js

sp3、打开地址, 复制文字保存为.js文件.

2-2、简单指令v-text、v-html、v-bind

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <script src="vue.beta.17.js"></script>
</head>
<body>
    <div id="app">
        <!-- 给标签内容赋值 -->
        <div>{{message1}}</div>
        <!-- 指定标签内部的inner text (同上)-->
        <div v-text="message1"></div>
        <!-- 字符串拼接 -->
        <div v-text="message1+'123'"></div>
        <!-- 三元表达式:如果不为空则hello,否则123 -->
        <div v-text="message1?'hello':'123'"></div>
        <!-- 可以放js表达式 -->
        <div v-text="flag?message:message1"></div>
        <!-- 动态显示html标签 -->
        <div v-html="message2"></div>

        <!-- 对属性进行绑定赋值 -->
        <a v-bind:href="url">点击</a>
        <img v-bind:id="message" v-bind:src="imgUrl"/>

        <!-- 动态的绑定HTML属性的方案 -->
        <a v-bind:[attr]="url">点击</a>
        <a v-bind:[attr+'f']="url">点击</a>
        <!-- v-bind可以省略 -->
        <a :[attr+'f']="url"> 点击</a>
    </div>
</body>
<script>
    const { createApp } = Vue

    const imgUrl = "https://ss0.bdstatic.com/5aV1bjqh_Q23odCf/static/superlanding/img/logo_top.png"
    const message1 = "hello, 8yue"
    const message2 = "<div style='background-color:red'>hello, 9yue"

    const url = 'http://talelin.com'
    const attr = 'hre'
    const flag = true
    
    const app = {
        setup() {
            return {
                imgUrl,
                message1,
                message2,
                url,
                attr,
                flag
            }
        }
    }   
    createApp(app).mount('#app')
</script>
</html>

2-9、v-on指令监听事件

<body>
    <div id="app">
        <!-- 监听用户点击事件: 点击"message2"后响应onClick函数 -->
        <div v-on:click="onClick" v-html="message2"></div>

        <!-- v-on简写 -->
        <div @click="onClick" v-html="message2"></div>
    </div>
</body>
<script>
    const { createApp } = Vue
    const message2 = "<div style='background-color:red'>hello, 9yue"
    const app = {
        setup() {
            // 事件监听函数写在这里
            function onClick(event) {
                console.log(event)
                alert('hello')
            }
            return {,
                message2,
                onClick
            }
        }
    }
    createApp(app).mount('#app')
</script>

2-10、条件渲染之v-if和v-show的选择

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <script src="vue.beta.17.js"></script>
</head>
<body>
    <div id="app">

        <!-- 条件渲染: 比如通过"flag"的true/false来控制 v-text 的显隐 -->
        <div v-if="flag" v-text="message1"></div>
        <!-- v-if和v-show的区别: v-if指令如果条件成立就渲染, 如果条件不成立就不渲染; v-show指令不管你条件成不成立,Dom结构里一定会出现这个div. 如果频繁切换显隐用v-show -->
        <div v-show="flag" v-html="message2"></div>
        
    </div>
</body>
<script>
    const { createApp } = Vue

    const message1 = "hello, 8yue"
    const message2 = "<div style='background-color:red'>hello, 9yue"
    const flag = true
    
    const app = {
        setup() {
            return {
                message1,
                message2,
                flag
            }
        }
    }
    createApp(app).mount('#app')
</script>
</html>

2-11、多项条件渲染

<body>
    <div id="app">
        <!-- if else -->
        <div v-if="flag" v-text="message1"></div>
        <div v-else v-html="message2"></div>

        <!-- if elseif else -->
        <div v-if="number===1" v-text="message1"></div>
        <div v-else-if="number===2" v-html="message2"></div>
        <div v-else>hello, 10月</div>
    </div>
</body>
<script>
    const { createApp } = Vue
    const message1 = "hello, 8yue"
    const message2 = "<div style='background-color:red'>hello, 9yue"
    const flag = false
    const number = 1
    
    const app = {
        setup() {
            return {
                message1,
                message2,
                flag,
                number
            }
        }
    }
    createApp(app).mount('#app')
</script>

2-12、v-for列表渲染及扩展用法及注意事项

<body>
    <div id="app">
        <!-- 普通列表(死数据) -->
       <ul>
           <li>长秀</li>
           <li>长歌</li>
           <li>藏剑</li>
       </ul>

        <!-- 列表渲染--获取item -->
        <!-- in 可以改为 of -->
        <ul v-for="item in titles">
            <li>{{item}}{{index}}</li>
        </ul>
        <!-- 列表渲染--获取序号和item -->
        <!-- 这种写法循环的是ul本身, 与小程序不同 -->
        <ul v-for="(item,index) in titles">
            <li>{{item}}{{index}}</li>
        </ul>

        <!-- vue风格的列表渲染(推荐这种写法) -->
        <!-- 这种写法循环的是li , 把v-for加在什么标签上就是循环什么 -->
        <ul>
            <li v-for="(item,index) of titles">{{item}}{{index}}</li>
        </ul>

        <!-- 遍历对象--获取键、值、序号 -->
        <ul>
            <li v-for="(value,key,index) of book">{{value}}{{key}}{{index}}</li>
        </ul>
    </div>
</body>
<script>
    const { createApp } = Vue

    // 定义数组
    const titles = ['七秀','长歌','藏剑']
    
    const app = {
        setup() {
            return {
                titles,
                book:{
                    title:'百年孤独',
                    author:'马尔克斯',
                    pubdate:'2000'
                }
            }
        }
    }
    createApp(app).mount('#app')
</script>

第二章、Vue3.0中的响应式对象

1-1、双向数据绑定

<body>
    <div id="app">
        <!-- js数据可以流向html, html数据发生变化后可以反向流回js -->
        <!-- v-model双向绑定指令: 实质是v-bind和v-on-->
        <!-- 所以v-model不是双向数据绑定的核心, 核心是响应式对象 -->
        <input v-model="age" type="text">
        <button @click="onClick">提交</button>
    </div>
</body>
<script>
    const { createApp, ref, reactive } = Vue

    // ref()可以把原来的数据类型变为响应式对象 
    const age = ref(18)// ref()把18保证成ref响应式对象,长的类似普通对象{ value:18 }
    console.log(age)

    // reactive()也可以把原来的数据类型变为响应式对象 
    const profile = reactive({
        age:18
    })

    // ref和reactive的区别: ref()传入的是js的基本数据类型; reactive()中传入的是object普通对象
    
    const app = {
        setup() {
            function onClick() {
                // 1.原生js   document.getELe
                // 2.小程序 event
                alert(age.value)//对象取值用.value
            }
            return {
                age,
                onClick
            }
        }
    }
    createApp(app).mount('#app')
</script>

1-3、双向数据绑定与响应式对象

proxy 对对象进行代理一样的拦截, 去操作某一对象下某一个属性时, 无论读取或写入, 都会给你一次机会写写自己的业务逻辑. 它提供了两个方法set(设置数据做监听)、get(获取数据做监听).

响应式对象的使用范畴比双向数据绑定更广

1-4、Vue3.0响应式对象使用场景

<body>
    <div id="app">
        <div>{{number}}</div>
        <button @click="onIncrement">number+1</button>
    </div>
</body>
<script>
    const { createApp, ref, reactive } = Vue
    
    // 实现单向数据绑定, 也要变响应式对象
    // let声明变量
    let number = ref(2)

    const app = {
        setup() {
            function onIncrement() {
                number.value++
                console.log(number.value)
            }
            return {
                number,
                onIncrement
            }
        }
    }
    createApp(app).mount('#app')
</script>

2-1、Vue3.0中的watch函数

<body>
    <div id="app">
        <input v-model="firstName">
        <input v-model="lastName">
        <div>{{firstName+lastName}}</div>
    </div>
</body>
<script>
    const { createApp, ref, reactive, watch } = Vue
    const firstName = ref('')
    const lastName = ref('')

    const app = {
        setup() {
            // 第一个参数: 要监听的响应式对象
            // 第二个参数: 回调函数
            watch(firstName,(newVal, oldVal)=>{
                console.log(newVal, oldVal)
            })
            return {
                firstName,
                lastName
            }
        }
    }
    // watch 监听类似小程序的observers, 钩子函数
    // 作用: 当某一个变量发生改变之后, 我们可以在钩子函数中写一些业务逻辑,做一些事情
    createApp(app).mount('#app')
</script>

2-2、Vue3.0中的watch函数--基本使用范例

<body>
    <div id="app">
        <input v-model="firstName">
        <input v-model="lastName">
        <div>{{fullName}}</div>
    </div>
</body>
<script>
    const { createApp, ref, reactive, watch } = Vue

    const firstName = ref('')
    const lastName = ref('')
    let fullName = ref('')

    const app = {
        setup() {
            // watch监听变量的变化
            // 第一个参数: 要监听的响应式对象
            // 第二个参数: 回调函数
            watch(firstName,(newVal, oldVal)=>{
                fullName.value = firstName.value + lastName.value
                console.log(fullName.value)
            })
            watch(lastName,(newVal, oldVal)=>{
                fullName.value = firstName.value + lastName.value
                console.log(fullName.value)
            })
            return {
                firstName,
                lastName,
                fullName
            }
        }
    }
    createApp(app).mount('#app')
</script>

2-3、Vue3.0中的watch函数--高级用法监听reactive对象

<body>
    <div id="app">
        <input v-model="name.firstName">
        <input v-model="name.lastName">
        <div>{{fullName}}</div>
    </div>
</body>
<script>
    const { createApp, ref, reactive, watch } = Vue

    let fullName = ref('')
    const name = reactive({//reactive的包装是深层次的里面的属性也包装成了响应式对象
        firstName:'',
        lastName:''
    })

    const app = {
        setup() {
            watch(name,(newVal, oldVal)=>{
                fullName.value = name.firstName + name.lastName
                console.log(fullName.value)
            })
            return {
                name,
                fullName
            }
        }
    }
    createApp(app).mount('#app')
</script>

2-4、Vue3.0中的watch函数--高级用法监听reactive对象下单个属性

<body>
    <div id="app">
        <input v-model="name.firstName">
        <input v-model="name.lastName">
        <div>{{name.fullName}}</div>
    </div>
</body>
<script>
    const { createApp, ref, reactive, watch } = Vue
    const name = reactive({//reactive的包装是深层次的里面的属性也包装成了响应式对象
        firstName:'',
        lastName:'',
        fullName:''
    })
    const app = {
        setup() {
            // 监听响应式对象下的部分属性: watch监听回调函数 或 getter方法
            watch(()=>name.firstName,(newVal, oldVal)=>{
                name.fullName = name.firstName + name.lastName
            })
            watch(()=>name.lastName,(newVal, oldVal)=>{
                name.fullName = name.firstName + name.lastName
            })
            return {
                name
            }
        }
    }
    createApp(app).mount('#app')
</script>

3-1、watch小结与引入computed计算属性

建议通常使用watch监听单个属性, 或者监听reactive某一个属性变化. 最好不要监听一整个对象的变化.

computed 计算属性 : 比watch更适合计算一个属性, 并且绑定

3-2、computed函数的基本用法

<body>
    <div id="app">
        <input v-model="firstName">
        <input v-model="lastName">
        <div>{{fullName}}</div>
    </div>
</body>
<script>
    const { createApp, ref, reactive, watch, computed } = Vue

    const firstName = ref('')
    const lastName = ref('')

    // 当我们计算一个属性并且显示它的时候, 最简单的做法就是用computed;
    const app = {
        setup() {
            // computed 计算属性 : 适合计算一个属性, 并且绑定.
            // computed将监听它里面所有的变量, 里面变量发生变化就会触发结果重新计算
            // 计算出来的结果是只读的, 所以fullName是不能被改变的
            const fullName = computed(()=>firstName.value+lastName.value)
            return {
                firstName,
                lastName,
                fullName
            }
        }
    }
    createApp(app).mount('#app')
</script>

3-3、watch、computed、普通js函数的使用场景和区别

<body>
    <div id="app">
        <input v-model="firstName">
        <input v-model="lastName">
        <div>{{getFullName()}}</div>
    </div>
</body>
<script>
    const { createApp, ref, reactive, watch, computed } = Vue
    const firstName = ref('')
    const lastName = ref('')

    /*  watch、computed、普通js函数的区别:
        watch和computed如何选择?在于使用场景上
            watch重点为了监听做事情. 看重的不是返回结果, 而是属性的变化.
            computed重点为了得到一个属性值,看重的是它的返回结果.
        普通js函数和computed的区别:
            computed性能会更好一些, 它有一个计算缓存; 普通js函数没有计算缓存.
            如果computed里面的变量或值没有发生变化, 那么就不会重新执行计算, 而是从计算缓存中读取结果.
        所以优先使用watch和computed
    */
    const app = {
        setup() {
            // 普通js函数
            function getFullName() {
                return firstName.value + lastName.value
            }
            return {
                firstName,
                lastName,
                getFullName
            }
        }
    }
    createApp(app).mount('#app')
</script>

3-4、computed的set和get方法

<body>
    <div id="app">
        <input v-model="firstName">
        <input v-model="lastName">
        <div>{{fullName}}</div>
    </div>
</body>
<script>
    const { createApp, ref, reactive, watch, computed } = Vue

    const firstName = ref('1')
    const lastName = ref('2')

    const app = {
        setup() {
            // 返回值不可以修改
            // const fullName = computed(()=>firstName.value+lastName.value)

            // 高级用法: 可以修改值
            const fullName = computed({
                get: ()=>firstName.value+lastName.value,
                set: (val) => {
                    firstName.value = val
                }
            })
            fullName.value = 7

            return {
                firstName,
                lastName,
                fullName
            }
        }
    }
    createApp(app).mount('#app')
</script>

第三章、 Vue3.0 中的组件

1-1、 Vue3.0中的组件如何编写?

<body>
    <div id="app">
        <!-- 组件的使用 -->
        <test-c></test-c>
    </div>
</body>
<script>
    const { createApp, ref, reactive, watch, computed } = Vue

    const app = {
        setup() {
            return {  
            }
        }
    }
    const vm =  createApp(app)
    // 注册组件 : 这段代码必须在mount('#app')之前调用
    // 第一个参数: 组件的名字
    // 第二个参数: 接收一组组件的选项参数
    vm.component('test-c',{ 
        // 创建自定义组件
        template:'<div>hello</div>'
    })
    vm.mount('#app')
</script>

1-2、Vue4.xCli创建Vue3项目或者使用Vite创建Vue3项目

/*用脚手架快速创建初始实例应用程序的两种方法: cli创建 或 vite创建
vue的cli创建应用程序:
    sp1.终端命令
        安装npm, 验证 $ npm -v
        安装vue的脚手架工具-cli, $ sudo npm i -g @vue/cli
    sp2.创建vue的应用程序
        $ cd 存放项目的文件夹目录下
        $ vue create 项目名
            可以自定义安装, 通过空格选中和取消选中
    sp3.项目升级到vue3.0
        $ vue add vue-next

vite是vue3提供的小工具, 旨在替换webpack
    sp1. 创建一个名为old-time-vite的项目
        回退到项目根目录 $ cd ..
        $ npm init vite-app old-time-vite
    sp2.运行项目
        $ cd old-time-vite/
        $ npm install
        $ npm run dev
*/

1-3、一切皆组件

组件化编程: 一切都是组件

1-4、VueCli应用程序目录结构解析

cli最重要的两个功能是帮助我们构建和打包; vite也可以不过刚出来,它里面没有webpack,它的模式是用浏览器的标准模块加载的方式, 和cli的webpack不太一样,它的优点vite要求它的库所有版本都是vue3.0版本的.但cli比较成熟, 比较推荐cli来构建项目.

-- src //源码文件
    -- components //组件目录
    -- App.vue // 应用入口文件: 严格意义来讲就是一个组件
        <template>组件模版(骨架)</template>
        <script>业务逻辑</script>
        <style>样式</style>
    --  main.js // JavaScript的入口文件: vue启动的时候它要去执行的一个文件,类似小程序的app.js
-- .eslintrc.js // 是一个语法编码检测的一个文件
-- babel.config.js // 浏览器兼容方面的配置
-- package.json
-- package-lock.json // 记录我们npm包的一个文件

项目部署 $ npm run build  #打包后,项目自动生成一个dist目录.dis目录就是一个生成编译之后的所有文件.那dis文件去部署就行了

1-5、Vue里自定义组件的定义与引入

// 创建HelloWorld.vue组件, 在HelloWorld.vue中:
<template>
  <div class="hello">
    <h1>{{ msg }}</h1>
  </div>
</template>
<script>
export default {
  name: 'HelloWorld',
  props: {
    msg: String
  }
}
</script>

// 引入组件, 在App.vue中:
<template>
  <HelloWorld msg="Welcome to Your Vue.js App"/>
</template>
<script>
import HelloWorld from './components/HelloWorld.vue'
export default {
  name: 'App',
  components: {
    HelloWorld
  }
}
</script>

2-1、导入resetcss并显示一张图片

//通常我们做H5开发的时候都要引入一个重置浏览器默认样式的一个css的文件, 主要作用是把默认属性值全部归零.
sp1.将重置浏览器样式文件reset.css放到assets文件夹下的style文件中
sp2.在main.js文件中引入reset.css文件:
import './assets/style/reset.css' //要用单引号

2-2、我的第一个vue3.0自定义组件

sp1. 在components文件中, 新建自定义组件swiper.vue(文件名尽可能命名为小写), 在swiper.vue中:
<template>
    <img class="size" src="../assets/home/logo.png" >
</template>
<script>
export default {
  name: 'Swiper'//通常写成跟文件名是相同的,首字母大写,且为两个空格
}
</script>
//scoped表示在style里写的样式,仅仅局限于当前组件的内部,不会影响其他组件
<style scoped>
    .size{
        width:100%;   
    }
</style>

sp2.在App.vue中, 引入swiper.vue组件:
<template>
  <!-- <Swiper></Swiper> -->
    <!-- 可大写可小写,使用组件时推荐小写 -->
    <swiper></swiper>
</template>

<script>
// 引入组件Swiper
import Swiper from './components/swiper.vue'
export default {
  name: 'App',
  components: {
    Swiper//定义Swiper, 简写了Swiper: Swiper
  }
}
</script>

2-5、使用require加载图片

如何给vue定义一个属性, 然后让调用方将属性的值传入组件内部来使用?
在swiper.vue中:
<script>
export default {
  name: 'Swiper',
  //自定义属性,比如提供一个数组
  props:  ['url'],
  setup(props){
    // 打印传递过来的参数
    // 这个props可以帮我们引用我们定义的属性
      console.log(props.url)
  }
}
</script>

在App.vue中:
<template>
  <!-- 这是一个静态的img图片, 直接写路径就可以被webpack打包,正确显示出来 -->
  <img src="./assets/home/logo.png" alt="">
  <!-- 这个图片应该是被webpack打包后才能显示的, 使用require()函数来使路径字符串变成require加载过后的一个资源模块 -->
  <!-- 不加v-bind 会被当做普通字符串,require()实际上是js的导入模块的方法 -->
  <!-- 直接写路径不能被加载出来是因为webpack的问题,由于传入的值是动态的,实际上webpack是没有办法打包这个动态资源的 -->
  <swiper :url="require('./assets/home/logo.png')"></swiper>
</template>

2-6、Vue3 setup函数在组件中的使用

<template>
  <div id="app">
    <!-- <img src="./assets/home/logo.png" alt=""> -->
    <!-- 我们一定要传一个被webpack打包资源的位置 -->
    <!-- sp2.把m3绑定到组件的url属性里面去 -->
    <swiper :url="m3" ></swiper>
  </div>
</template>

<script>
// 引入组件Swiper
import Swiper from './components/swiper.vue'
// 除了使用require加载资源之外, 还可以使用import加载资源模块
// m3代表图片被打包之后的资源位置
// import m3 from './assets/home/logo.png'// 加载图片到webpack里面去
// 有时我们不知道相对路径前是几个点, 这时我们可以用@符号代替, @代表src
import m3 from '@/assets/home/logo.png'

export default {
  name: 'App',
  components: {
    Swiper//定义Swiper
  },
  // 把一个变量绑定到一个组件上面
  setup(){
    return {
      m3 // sp1.把m3绑定到组件的url属性里面去
    }
  }
}
</script>

2-7、props属性传值时需要注意使用v-bind指令确定类型

在App.vue中:
<template>
  <div id="app">
    <!-- 标签上的属性如果不做特殊处理默认传递给组件的是字符串(string类型), 改v-bind可以解决 -->
    <swiper :url="m3" :num="1" :isLike="false"></swiper>
  </div>
</template>

在swiper.vue中:
<script>
export default {
  name: 'Swiper',
  props:  {
    url:{
        type: String,
        default:''
    },
    num:{
        type:Number
    },
    isLike:{
        type:Boolean
    }  
  },
  setup(props){
    console.log(typeof(props.num))//打印属性类型:number
    if (props.isLike) {
        console.log(typeof(props.isLike))//打印属性类型:boolean
    }
  }
}
</script>

3-1、vue的单向数据流特性

值类型和引用类型
如果是一个number类型的数据A, 父组件把数据A传到子组件后, A的值是不能改的, 因为被vue设置成了只读类型.

什么是vue的单向数据流?
vue要求值可以在父组件里面被更新,如果在子组件里更新值, 默认情况下是不允许的.vue这么做是因为: 如果一个值A传到子组件,子组件又将值传入子子组件, A会贯穿多个组件,那么如果中间某个组件改变了A的值,其他组件也会受影响,会造成很多调试问题

在swiper.vue中:
<script>
export default {
  name: 'Swiper',
  props:  {
    url:{
        type: String,
        default:''
    },
    num:{
        type:Number
    },
    obj:{
        type:Object// js对象
    }  
  },
  setup(props){
    //   子组件是不能修改父组件的
    // props.num = props.num + 1
    // props.obj = {}
    console.log(props.num)
    console.log(props.obj)
  }
}
</script>

3-2、父组件改变导致子组件的变化

在父组件改变num的值, 使用响应对象
在App.vue中:
<template>
  <div id="app">
    <swiper :url="m3" :num="a" :obj="{naem:'777'}"></swiper>
    <button @click="onClick">click</button>
  </div>
</template>

<script>
import Swiper from '@/components/swiper.vue'
import m3 from '@/assets/home/logo.png'
import { ref } from 'vue'
let a = ref(1)
export default {
  name: 'App',
  components: {
    Swiper
  },
  setup(){
    function onClick() {
      a.value++
      console.log(a.value)
    }
    return {
      m3,
      a,
      onClick 
    }
  }
}
</script>

在swiper.vue中:
<template>
    <img class="size" :src="url" >
    <span>{{num}}</span>
</template>
<script>
export default {
  name: 'Swiper',
  props:  {
    url:{
        type: String,
        default:''
    },
    num:{
        type:Number
    },
    obj:{
        type:Object// js对象
    }  
  },
  setup(props){
  }
}
</script>

3-3、引用类型的修改导致父组件异常

证明一句话, 不能反向的通过子组件改变父组件
在App.vue中:
<template>
  <div id="app">
    <img v-if="obj.name == '777'" src="./assets/home/logo.png">
    <swiper :url="m3" :num="a" :obj="obj"></swiper>
    <button @click="onClick">click</button>
  </div>
</template>

<script>
import Swiper from '@/components/swiper.vue'
import m3 from '@/assets/home/logo.png'
import { ref } from 'vue'

let a = ref(1)
const obj ={
  name: '777'
}
export default {
  name: 'App',
  components: {
    Swiper
  },
  setup(){
    function onClick() {
      a.value++
      console.log(a.value)
    }
    return {
      m3,
      a,
      onClick,
      obj 
    }
  }
}
</script>
在swiper.vue中:
<script>
export default {
  name: 'Swiper',
  props:  {
    url:{
        type: String,
        default:''
    },
    num:{
        type:Number
    },
    obj:{
        type:Object
    }  
  },
  setup(props){
    // props.obj = {}//值类型: 改变obj. 报错
    // props.obj.name = '666'//引用类型: 改变obj内部的属性. 报错
    console.log(props.num)
    console.log(props.obj)
  }
}
</script>

第四章 vue-router与vuex

1-1 自定义组件监听原生事件

子组件如何把数据传递到父组件:
最常用的方法: 子组件通过自定义事件传递数据到父组件中

1-2 自定义组件的自定义事件与传参 (最常用)

在swiper.vue中:
<template>
    <!-- @click.native触发原生的onClick事件 -->
    <img @click="onImgClick" class="size" :src="url" >
    <span>{{num}}</span>
</template>

<script>
const testNum = 'a'
export default {
  name: 'Swiper',
  props:  {
    url:{
        type: String,
        default:''
    },
    num:{
        type: Number
    }  
  },
  setup(props,context){
    function onImgClick() {
        //sp1.创建自定义事件: 参数一:自定义事件名; 参数二:自定义事件参数
        context.emit('sub-event', testNum)
    }
    return {
        onImgClick
    }
  }
}
</script>

在App.vue中:
<template>
  <div id="app">
    <img src="./assets/home/logo.png">
    <!-- sp2.响应子组件的自定义事件 -->
    <swiper @sub-event="onTestClick" :url="m3" ></swiper>
  </div>
</template>

<script>
import Swiper from '@/components/swiper.vue'
import m3 from '@/assets/home/logo.png'

export default {
  name: 'App',
  components: {
    Swiper
  },
  setup(){
    // sp3.接收子组件自定义事件传递过来的参数
    function onTestClick(event) {
      console.log(event)
    }
    return {
      m3,
      onTestClick
    }
  }
}
</script>

1-4 初识Vue3.0的Router

​ 可参照链接操作: https://segmentfault.com/a/1190000022684511

终端操作:
$ vue create vue-new #创建项目
$ 选择 "Manually select features" #手动选择
$ 空格勾选 "Babel"、"Router"、"Vuex"、"Linter/Formatter"、"CSS Pre-processors"(#CSS预处理器,可以不勾选)
$ n (不使用history router)
$ 选择"Sass/SCSS (with node-sass)" #如果之前勾选了CSS预处理器,会来到这里

1-5 自己定义一个detail页面路由

​ 目标任务: 当我们在子组件swiper.vue中,点击后触发onImgClick(),跳转detail.vue页面

在 src/views/ 目录下,新建detail.vue页面:
<template>
  <div class="about">
    <h1>商品详情页面</h1>
  </div>
</template> 
<script>
export default{
  name: 'Detail', // sp1.定义路由名称
  components:{
    // 这里导入子组件
  }
}
</script>
    
在src/router/index.js文件中, 定义路由:
import Home from '../views/Home.vue'
import Detail from '../views/detail.vue' // sp2.导入Detail
const routes = [
    {//方式一: 使用import的方式导入(推荐)
        path: '/',
    name: 'Home',
    component: Home
    },
  {//方式二: 使用了webpack的方式进行导入的
        path: '/about',
    name: 'About',
    component: () =>import('../views/About.vue')
    },
  {//sp3.添加Detail路由
    path: '/detail',
    name: 'Detail',
    component: Detail
  }
]

2-1 router-view与router-link

在App.vue中:
<template>
  <div id="app">
    <!-- router-link类似于原生html的a标签,用于链接跳转(用的比较少) -->
    <!-- to表示组件要跳转的路由路径 -->
    <router-link to="/detail">Detail</router-link> 
    <!-- router-view 显示某个路由它所对应的页面(核心) -->
    <!-- 如果没有指定, 路由默认是一个/, 显示的是/所指向的内容 -->
    <router-view></router-view>
  </div>
</template>

2-2 vue-router编程思想

本质上都是组件, 只是做了规范约定如下: 
    通常views文件夹下放的组件是要关联router的, 但是components文件夹下放的组件是不会关联rooter的.
    components: 里面的组件主要是为了复用, 通常会被多个views复用
    views: 里面的组件主要为了被当做页面上的显示区域, 通常是不会复用的

2-3 vue-router编程思想--入口组件的规范

​ 页面的变化用路由切换, 尽量不用条件渲染.

任务目标: 精简App.vue, 把之前的swiper组件迁移到新页面home.vue中,方便后面路由跳转.
在App.vue中:
<template>
  <div id="app">
      <!-- <router-view></router-view> -->
      <Home></Home>
  </div>
</template>

<script>
  import Home from '@/views/home'
  export default{
    name: 'App',
    components:{
      Home
    }
  }
</script>

在 views/ 下创建home.vue, 在home.vue中:
<template>
  <div class="Home">
    <img src="@/assets/home/logo.png">
    <swiper @sub-event="onTestClick" :url="m3" ></swiper>
    <router-link to="/detail">Detail</router-link>
    <router-view></router-view>  
  </div>
</template> 

<script>
import Swiper from '@/components/swiper.vue'
import m3 from '@/assets/home/m1.png'

export default {
  name: 'Home',
  components: {
    Swiper
  },
  setup(){
    function onTestClick(event) {
      console.log(event)
    }
    return {
      m3,
      onTestClick
    }
  }
}
</script>

2-4 vue-router编程思想--router-view监听事件

任务目标: 通过冒泡将事件一步步传递到App.vue, 执行路由展示

在swiper.vue中:
<template>
    <img @click="onImgClick" class="size" :src="url" >
    <span>{{num}}</span>
</template>

<script>
const testNum = 'a'
export default {
  name: 'Swiper',
  props:  {
    url:{
        type: String,
        default:''
    },
    num:{
        type: Number
    }  
  },
  setup(props,context){
    function onImgClick() {
        context.emit('sub-event', testNum)//sp1.触发一个自定义事件
    }
    return {
        onImgClick
    }
  }
}
</script>

在home.vue中:
<template>
  <div class="home">
    <swiper @sub-event="onSwiperClick" :url="m3" ></swiper>
  </div>
</template> 

<script>
import Swiper from '@/components/swiper.vue'
import m3 from '@/assets/home/logo.png'

export default {
  name: 'Home',
  components: {
    Swiper
  },
  setup(props,context){
    function onSwiperClick(event) {
        context.emit('change-view',event)//sp2.触发一个自定义事件
        console.log(event)
    }
    return {
      m3,
      onSwiperClick
    }
  }
}
</script>

在App.vue中:
<template>
  <div id="app">
      <router-view @change-view="onChangeView"></router-view>
  </div>
</template>

<script>
  import Home from '@/views/home'
  export default{
    name: 'App',
    components:{
      Home
    },
    setup(){
      function onChangeView() {
        alert()
      }
      return {
        onChangeView
      }
    }
  }
</script>

2-5 vue-router编程思想--组件的切换优先考虑路由而不是条件渲染

任务目标: 实现简单的页面跳转
<template>
  <div id="app">
      <router-view @change-view="onChangeView"></router-view>
  </div>
</template>

<script>
  import Home from '@/views/home'
  import router from '@/router'//sp1.导入router
  export default{
    name: 'App',
    components:{
      Home
    },
    setup(){
      function onChangeView() {
        // sp2.跳转其他页面
        // router.push({
        //   path:'/detail'
        // })
        router.push('/detail')//简写
      }
      return {
        onChangeView
      }
    }
  }
</script>

/*在vue中不管是组件还是页面都推荐用路由的方式进行切换,而不是条件渲染*/

2-6 默认情况下不能跨父组件通信

在Vue中默认情况下不能跨父组件进行通信

3-1 嵌套路由

如果说一个组件是另一个组件的子组件, 那么它通常要显示在它的父组件view中. 比如SubDetail被配置成了Detail的子组件, 那么SubDetail要想被显示出来需要在Detail中的view里写上<router-view/>,那么就会把SubDetail默认显示出来了.

在index.js中:
import { createRouter, createWebHashHistory } from 'vue-router'
import SubDetail from '../views/sub-detail.vue'

const routes = [
  {
    path: '/detail',
    name: 'Detail',
    component: Detail,
    //嵌套子路由
    children:[
      {
        path:'sub',//路径先当于/detail/sub
        name: 'SubDetail',
        component: SubDetail
      }
    ]
  },
]
const router = createRouter({
  history: createWebHashHistory(),
  routes
})
export default router

在detail.vue中:
<template>
  <div class="detail">
    <h1>商品详情页面</h1>
    <router-view></router-view>
  </div>
</template> 

3-3 多页面跳转change-view

任务目标: 通过App.vue动态切换任意页面

在App.vue中:
<template>
  <div id="app">
      <router-view @change-view="onChangeView"></router-view>
  </div>
</template>

<script>
  import Home from '@/views/home'
  import router from '@/router'
  export default{
    name: 'App',
    components:{
      Home
    },
    setup(){
      // sp2.动态传递参数跳转任意页面,切换view.
      function onChangeView(event) {
        router.push({
          path: event
        })
      }
      return {
        onChangeView
      }
    }
  }
</script>

在home.vue中:
<script>
import Swiper from '@/components/swiper.vue'
import m3 from '@/assets/home/logo.png'

export default {
  name: 'Home',
  components: {
    Swiper
  },
  setup(props,context){
    function onSwiperClick(event) {
        context.emit('change-view','/detail')// sp1.传入路由路径参数
        console.log(event)
    }
    return {
      m3,
      onSwiperClick
    }
  }
}
</script>

3-4 Vue3中如何获取路由参数

目标任务: 传递并获取路由参数.
在index.js中:
const routes = [
  {
    path: '/detail/:id',//sp1.一定要传递固定参数id
    name: 'Detail',
    component: Detail
  },
]

在home.vue中:
<script>
import Swiper from '@/components/swiper.vue'
import m3 from '@/assets/home/logo.png'

export default {
  name: 'Home',
  components: {
    Swiper
  },
  setup(props,context){
    function onSwiperClick(event) {
        context.emit('change-view','/detail/3')//sp2.传递id参数3
        console.log(event)
    }
    return {
      m3,
      onSwiperClick
    }
  }
}
</script>

在detail.vue中:
<script>
import router from '@/router'
export default {
  name:'Detail',
  components:{},
  setup(props, context){
    console.log(router.currentRoute.vaule.params.id)//sp3.获取传递过来的路由参数
    function onClick() {
      context.emit('change-View','/detail/sub')
    }
    return {
      onClick
    }
  }
}
</script>

3-5 Vue3中的路由名称与统一跳转页面逻辑

任务目标: 通过路由name传参

在index.js中:
const routes = [
  {
    path: '/detail/:id',//一定要传递固定参数id
    name: 'Detail',
    component: Detail
  },
  {
    path:'/detail/sub',
    name: 'SubDetail',
    component: SubDetail
  },
]

在home.vue中:
<script>
import Swiper from '@/components/swiper.vue'
import m3 from '@/assets/home/logo.png'

export default {
  name: 'Home',
  components: {
    Swiper
  },
  setup(props,context){
    function onSwiperClick(event) {
        context.emit('change-view',{
            name:'Detail',
            params:{
                id:3
            }
        })//sp1.传递参数改为对象形式: 通过name进行路由跳转,格式按照router.push()要求格式
        console.log(event)
    }
    return {
      m3,
      onSwiperClick
    }
  }
}
</script>

在App.vue中:
<script>
  import Home from '@/views/home'
  import router from '@/router'
  export default{
    name: 'App',
    components:{
      Home
    },
    setup(){
      // 动态跳转任意页面,切换view
      function onChangeView(event) {
        // router.push({
        //   name:event.name,
        //   params:{
        //     id: event.params.id
        //   }
        // })
        router.push(event)//sp3.简写, 可以简写是因为传递参数时是按照router.push()要求格式的参数传递的
      }
      return {
        onChangeView
      }
    }
  }
</script>

4-1 为什么需要Vuex全局状态管理

组件都有一定的独立性, 需要事件传参. 另一个传参的方法Vuex,是一种可以存储全局变量的机制.
Vuex全局变量与html的全局变量的区别:
    1.Vuex全局变量天生是响应式的; 2.Vue对全局状态的管理很多的操作是有记录性质的,不能直接修改, 要通过方法的方式去操作它(因为Vue需要追踪记录这些变化修改).

4-2 定义Vuex的全局变量

最终目标任务: 通过Vuex解决路由跳转的问题, 比如home.vue跳转detail.vue页面.
目标任务: 通过Vuex定义与获取全局变量

在src/store/index.js中:
export default createStore({
  state: {//sp1.定义全局变量
    url:'SubDetail'
  },
  mutations: {
  },
  actions: {
  },
  modules: {
  }
})

在home.vue中:
<script>
import Swiper from '@/components/swiper.vue'
import m3 from '@/assets/home/logo.png'
import store from '@/store/index' //sp2.导入store

export default {
  name: 'Home',
  components: {
    Swiper
  },
  setup(props,context){
    function onSwiperClick(event) {
        alert(store.state.url) //sp3.获得全局变量的值
    }
    return {
      m3,
      onSwiperClick
    }
  }
}
</script>

4-3 Vuex改变全局状态变量并传参的3种方式

最终目标任务: 通过Vuex解决路由跳转的问题, 比如home.vue跳转detail.vue页面.
目标任务: 通过Vuex调用方法修改全局变量的值

在src/store/index.js中:
export default createStore({
  // 定义全局变量
  state: {
    url:''
  },
  // 定义函数
  mutations: {
    // 通过方法的方式来改变全局变量的值
    // 对应传参方式一、三:
    // state就是上面的state,vue自动传入,不用我们传.
    // url要改变的全局变量的值,需要我们传入
    changeView(state, url){
        state.url = url
    }

    // 对应传参方式二:
    // changeView(state, payload){
    //   state.url = payload.url
    // }
  },
  actions: {
  },
  modules: {
  }
})

在home.vue中:
<script>
import Swiper from '@/components/swiper.vue'
import m3 from '@/assets/home/logo.png'
import store from '@/store/index' //导入store

export default {
  name: 'Home',
  components: {
    Swiper
  },
  setup(props,context){
    function onSwiperClick(event) {
        // 调用方法修改全局变量的值
        // (推荐)传参方式一: (适合传一个参数的场景)
        //参数一:传入要调用mutations下的函数名 参数二:传入要设置的新url值
        store.commit('changeView', 'SubDetail')
        console.log(store.state.url)

        // 传参方式二:(适合传多个参数的场景)
        store.commit({
            type: 'changeView',
            url: 'SubDetail'
        })
        console.log(store.state.url)
    
        // (推荐)传参方式三:(传一个或多个参数的场景)
        store.commit('changeView',{
            url: 'SubDetail'
        })
        console.log(store.state.url)
    }
    return {
      m3,
      onSwiperClick
    }
  }
}
</script>

4-4 计算属性和监听器在Vuex状态改变时的应用

目标任务: 通过Vuex解决路由跳转的问题, 比如home.vue跳转detail.vue页面.
// 之前是通过多个事件函数回调传递到App.vue触发路由跳转事件, 现在去除了事件回调传递, 留下一个响应对象(Vuex全局变量),就在App.vue通过watch监听全局变量(响应式对象)的变化来触发路由跳转事件.

在App.vue中:
<template>
  <div id="app">
      <router-view ></router-view>
      <!-- 显示全局变量url的值 -->
      {{store.state.url}}
        <!-- 同上 -->
        {{changedUrl}}
  </div>
</template>

<script>
  import Home from '@/views/home'
  import router from '@/router'
  import store from '@/store/index'

  import {
    computed,
    watch
  } from 'vue'

  export default{
    name: 'App',
    components:{
      Home
    },
    setup(){
      const changedUrl = computed(() => store.state.url)
      // 直接监听store.state.url不能成功,试着用计算属性监听一下,再监听计算属性返回的响应对象
      watch(changedUrl, (newVal, oldVal) => {
        // alert(newVal)
        // 为什么只有点击第一次,才会触发alert()?
        // 因为第一次时是空字符串, 第二次改变了全局变量的值,也就是只有一次改变.后面相同的值是不会触发监听事件的.
        // 那么计算属性不会触发,所以watch函数也不会被触发
        router.push({
          name: newVal
        })
      })
      return {
        store, //store要被显示出来,需要return一下store
        changedUrl
      }
    }
  }
</script>

4-5 利用全局状态管理变量进行路由切换

目标任务: 全局状态管理的总结

利用全局状态管理变量进行路由切换逻辑:
    在跳转路由的时候,在跳转的地方,调用 store.commit('changeView',{url: 'SubDetail'}) 方法改变url, 然后在App.vue中进行监听跳转路由操作.

什么时候需要用计算属性和watch?
如果只是想显示全局变量的值, 那么只需要计算属性就可以了;
如果要做监听这个值的改变, 并且做路由跳转等操作,就要用watch.

精简利用全局状态管理变量进行路由切换代码:

在src/store/index.js中:
export default createStore({
  state: {
    routerParams:{} //定义对象变量
  },
  mutations: {
    changeView(state, payload){
        state.routerParams = payload.routerParams
    }
  },
  actions: {
  },
  modules: {
  }
})

home.vue跳转detail.vue逻辑,在home.vue中: 
<script>
import Swiper from '@/components/swiper.vue'
import m3 from '@/assets/home/logo.png'
import store from '@/store/index' //导入store

export default {
  name: 'Home',
  components: {
    Swiper
  },
  setup(props,context){
    function onSwiperClick(event) {
        // 调用方法修改全局变量的值
        store.commit('changeView',{
            routerParams: {
                name: 'Detail',
                params: {
                    id: 3
                }
            }
        })
        console.log(store.state.routerParams)
    }
    return {
      m3,
      onSwiperClick
    }
  }
}
</script>

在App.vue中:
<script>
  import Home from '@/views/home'
  import router from '@/router'
  import store from '@/store/index'

  import {
    computed,
    watch
  } from 'vue'

  export default{
    name: 'App',
    components:{
      Home
    },
    setup(){
      const changedUrl = computed(() => store.state.routerParams)
      watch(changedUrl, (newVal, oldVal) => {
        router.push(newVal)
      })
    }
  }
</script>

同理detail.vue跳转sub-detail.vue逻辑, 在detail.vue中:
<script>
import router from '@/router'
import store from '@/store/index'
export default {
  name:'Detail',
  components:{},
  setup(props, context){
    console.log(router.currentRoute.vaule.params.id)//获取传递过来的路由参数
    function onClick() {
      store.commit('changeView', {
        routerParams: {
          name: 'SubDetail' 
        }
      })
    }
    return {
      onClick
    }
  }
}
</script>

第五章 动态组件与Vuex全局状态管理

1-1 动态组件(1)总结Vue中几种切换视图组件的方案

目标任务:
1.动态组件 
2.如何使用keep alive保存组件状态? keep alive是Vue内置的标签, 只需要使用keep alive包裹上动态组件就可以记录保存组件状态了

关于组件和视图的切换: 
    ①利用路由router-view方式进行切换,用于切换大的模块视图 ②利用条件渲染(v-if)进行切换, 用于小幅度的切换 ③显示或隐藏 ④动态组件, 用于小幅度的显示或隐藏
    

1-2 动态组件(2)初识动态组件

什么是动态组件?动态组件就是多个组件根据条件只显示一个组件.

在home.vue中:
<template>
  <div class="home">
    <!-- 动态组件的基本用法 -->
    <!-- 使用vue内置的component标签,通过is属性决定显示 keep 还是 static 组件-->
    <!-- <component v-bind:is="currentComponent"/> -->
    <component :is="currentComponent"/>
    <!-- <keep/>
    <static/> -->
  </div>
</template> 

<script>
import Swiper from '@/components/swiper.vue'
import Keep from '@/components/keep'
import Static from '@/components/static'

let currentComponent = 'keep' //定义一个动态属性并设置初始值
export default {
  name: 'Home',
  components: {
    Keep,
    Static
  },
  setup(props,context){
    return {
      currentComponent
    }
  }
}
</script>

1-3 使用keep-alive缓存组件状态

在home.vue中:
<template>
  <div class="home">
    <button @click="onChangeComponent('keep')">点击切换到keep组件</button>
    <button @click="onChangeComponent('static')">点击切换到Static组件</button>
    <!-- keep-alive 保持组件数据,使组件切换时数据不丢失 -->
    <!-- keep-alive 原理: vue会自动帮我们缓存一些动态组件,比如当前缓存的是keep组件,等切换回来时不会再重新创建, 而是从缓存中获取keep组件,所以组件上的信息是被保留下来的 -->
    <keep-alive>
      <component :is="currentComponent"/>
    </keep-alive>
    <!-- 动态控制哪些组件需要被缓存,哪些不需要被缓存数据?比如想缓存static, 不想缓存keep -->
    <!-- 如果include属性中只填写Static, 那么keep组件数据将不会被缓存 -->
    <!-- 多个值可以用逗号,也可以用数组,还可以用正则进行批量的添加一类组件 -->
    <keep-alive include="Static,Keep">
      <component :is="currentComponent"/>
    </keep-alive>
    <!-- 排除哪些组件不缓存, 用exclude, 比如下面代码表示只有Keep组件不缓存 -->
    <!-- exclude属性值的大小写根据其组件export default的name完全匹配才生效 -->
    <!-- vue做缓存的时候,就是根据组件的name索引和寻找的,所以组件没有name是不影响使用的,但是不会被keep-alive缓存,建议每个组件给它一个name -->
    <keep-alive exclude="Keep">
      <component :is="currentComponent"/>
    </keep-alive>

    <!-- <keep/>
    <static/> -->
  </div>
</template> 

<script>
import store from '@/store/index' //导入store
import Keep from '@/components/keep'
import Static from '@/components/static'
import { ref } from 'vue'

const currentComponent = ref('keep')
export default {
  name: 'Home',
  components: {
    Keep,
    Static
  },
  setup(props,context){
    function onChangeComponent(event) {
      console.log(event)
      currentComponent.value = event
    }
    return {
      currentComponent,
      onChangeComponent
    }
  }
}
</script>

第六章 CMS电商管理系统前端搭建

1-1 实战LinCMSVue介绍

组件库 + CMS框架
LinCMSVue -> 3.0 机制 ✅
ElementUI -> 3.0 
Antd Vue

1-2 实战的准备工作

sparrow: 四阶段Lin CMS Vue源码
在 https://github.com/TaleLin/lin-cms-vue 下载0.1.0-sleeve版本到本地

1-3 下载LinCMSVue并前后端运行联调

$ cd ~/apps/c-sleeve/lin-cms-vue-0.1.0-sleeve
$ npm install
$ npm run serve #启动项目

# 前端服务器启动完毕了, 前端服务器用来下载一些vue的文件.下面启动的就是前端服务器的地址.前端服务器是由node.js启动的,所以需要node.js环境.
App running at:
- Local:  http://localhost:8080/
- Network: http://192.168.0.103:8080/

1-4 权限、角色与分组的关系

# CMS最重要的是权限.

# 用户、分组、权限的关系:
    用户不能直接拥有权限, 用户属于某一个分组, 分组有多个权限.
    分配权限不是用户分配权限, 是分组分配权限.

1-5 字段级别的细粒度权限探讨

# 权限的实质: API
    放到springboot里就是Controller.
    1个权限 = 1个Controller = 1个API

# 权限粒度问题: 更多的时候指查询
    比如用户A可以看到Banner的名称,但不能看到Banner的描述, 用户B不能看到Banner的名称,但可以看到Banner的描述.
    方案一: 可能要写多个Controller,即权限和Controller挂钩
        ①权限--不返回描述  A
        ②权限--不返回名称  B
    方案二: 权限不和Controller挂钩, 而是和数据库字段挂钩, 这样操作复杂度很高; 
    也可以选择使用动态SQL, 根据拥有的权限动态的拼接SQL查询语句:
    select name ... from banner A
    select description ... from banner B
    但动态SQL只适合单表查询, 很多时候数据来自多个表, 所以很多时候SQL用join, 这样操作就复杂了
    方案三: 在vo层做一个权限拦截层, vo这一层它的数据是来源多张表的,它和数据库的单表是没关系的. vo是视图, 它的作用是综合多个表的数据把他们简化成一个vo对象.
    比如对某一个order来说,数据可能来自多个表,那么多个表的数据会汇总成一个order的vo对象,在这里做权限的过滤. 不会在数据库这层做权限的拼接.
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,080评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,422评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,630评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,554评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,662评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,856评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,014评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,752评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,212评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,541评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,687评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,347评论 4 331
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,973评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,777评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,006评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,406评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,576评论 2 349

推荐阅读更多精彩内容