vue3.0 学习使用记录

vue

在最近的一次项目中,前端js框架里使用了vue。vue的编程思想比较新颖,用起来也感觉高效便捷。由于个人原因,做完这个项目之后可能就接触不到前端的内容了,所以记录这个项目中对vue的学习和理解。对自己的要求是可以不记得具体的技术,但是要记住这个框架的思想。

vue简介

vue是一个JavaScript框架,最大的特点是响应式
响应式原理:意思就是在改变数据的时候,视图会跟着更新

对比jquery来说,jquery是通过语法操作html中的标签,从而改变标签的属性,值等。而vue是需要改变的html标签绑定一个变量,当变量发生变化时html中的标签的值也发生变化。

vue3.0安装

1. 安装node

sudo tar xf node-v14.16.1-linux-x64.tar.xz -C /opt

cd /opt/node-v14.16.1-linux-x64/bin

sudo ln -s /opt/node-v14.16.1-linux-x64/bin/node /usr/local/bin/node

sudo ln -s /opt/node-v14.16.1-linux-x64/bin/npm /usr/local/bin/npm

# 验证版本

node -v

npm -v

2. 装淘宝镜像

npm install -g cnpm --registry=https://registry.npm.taobao.org

sudo ln -s /opt/node-v14.16.1-linux-x64/bin/cnpm /usr/local/bin/cnpm

cnpm -v

3. vue环境安装

cnpm -g vue @vue/cli

sudo ln -s /opt/node-v14.16.1-linux-x64/bin/vue /usr/local/bin/vue

vue -V

vue3.0要求4.5以上的版本

4. 创建新项目

vue create todolist

启动项目

访问页面

vue 项目架构

一个新建的vue项目架构如下:

assets: 存放静态资源、图标、图片

components:存放一般组件,公共组件,通用组件

router:配置路由。vue中实现页面跳转需要定义的路由

sotre: 配置状态管理

views:vue 主要业务实现的位置

App.vue:页面的入口文件,通常是入口页面。上面的首页就是该文件中的。

组件

组件是vue功能的集合。可以把每一个.vue文件看成一个组件,包括App.vue也是一个组件,一个关于主页面的组件。组件的结构由三部分组成:

  1. template
  2. script
  3. style

<template>

</template>

<script>

</script>

<style scoped lang="scss">

</style>

组件结构

一个组件包括很多内容,大体上可以分为:

  1. defineComponent 引入

  2. 子组件引入(可选)
  3. 组件定义

  • 组件名称
  • 接收父组件的数据(可选)
  • 定义子组件(可选)
  • setup 定义变量和函数

setup中定义变量和函数,最后都要通过return返回,只有return中返回的变量才能被页面上响应。

//------------------------数据的展示------------------------

<template>

</template>

//--------------------------数据的定义和返回---------------------

<script>

// 所有的组件都需要从vue中定义

import {defineComponent} from 'vue'

import NavHeader from '@/components/navHeader/NavHeader'

// 调用defineComponent中的组件

export default defineComponent({

name: 'Home', // 组件名称

// 接收父组件的数据

props: {

},

// 定义子组件

components:{

},

setup(props, ctx) {

// 数字

let num = ref(10)

return {

num

}

}

})

</script>

<style scoped lang='scss'>

</style>

数据结构

vue中常见的数据结构包括:

  • 数字
  • 字符串
  • 字典
  • 数组

ref 用来定义响应式变量,只可以定义单个变量
reactive 用来打包定义变量,可以定义多个变量

数据定义

//------------------------数据的展示------------------------

<template>

<div>

<nav-header></nav-header>

<nav-main></nav-main>

<nav-footer></nav-footer>

{{ data.num }}

{{ data.name }}

{{ data.arr.slice(0,2) }}

{{ data.obj }}

</div>

</template>

//--------------------------数据的定义和返回---------------------

<script>

// 所有的组件都需要从vue中定义

import {defineComponent, ref, reactive} from 'vue'

// 调用defineComponent中的组件

export default defineComponent({

name: 'Home', // 组件名称

// 接收父组件的数据

props: {

},

// 定义子组件

components:{

NavHeader,

NavMain,

NavFooter

},

setup(props, ctx) {

// 数字

let num = ref(10)

// 字符串

let name = ref('jack')

// 数组

let arr = ref(['a', 'b', 'c', 'd'])

// 字典

let obj = ref({age: 20})

// reactive 方法

let data = reactive({

name: 'jack',

age: 20,

ojb: {

price: 20

},

arr: ['a', 'b', 'c', 'd']

})

return {

data

}

}

})

</script>

<style scoped lang='scss'>

</style>

在实际的编程过程中,似乎不需要用关键字refreactive。如在未来网络学院的开发过程中,一个vue文件定义的相应式变量如下:

 return {

tableConfig: {

config: tableConfigBase(),

tableData: [],

selectedRows: [],

},

createModal: {

visible: false

},

createForm: {

dialog_title: '添加讲师',

name: '',

title: '',

profile: '',

img: [],

},

searchForm: {

keywords: ''

},

createRules: {

name: [

{ required: true, message: '请输入名称', trigger: 'change' },

{ min: 2, max: 20, message: '长度在 2~20 个字符内', trigger: 'change' },

{validator: nameUniqueAcq, trigger: 'blur'},

{validator: urlAcq, trigger: 'blur'},

],

title: [

{ required: true, message:'请输入头衔', trigger: 'change' },

{ min: 1, max: 500, message: '长度在 2~500 个字符内', trigger: 'change' },

],

profile: [

{ required: false, message: '长度在 200 个字符内', trigger: 'change' },

],

img: [

{ required: true, message: '请上传图片', trigger: 'change'}

],

},

pager:{},

disabledDel: 'disabled',

disableDis: 'disabled',

disableEn: 'disabled',

}

vue基础语法

指令

Vue.js的指令是以v-开头的,它们作用于HTML元素,指令提供了一些特殊的特性,将指令绑定在元素上时,指令会为绑定的目标元素添加一些特殊的行为,我们可以将指令看作特殊的HTML特性(attribute)。

作用

指令的作用是当表达式的值改变时,相应地将某些行为应用到 DOM 上。

v-if

v-if可以实现条件渲染,Vue会根据表达式的值的真假条件来渲染元素。

<a v-if="ok">yes</a>

如果属性值ok为true,则显示。否则,不会渲染这个元素。

v-else

v-else是搭配v-if使用的,它必须紧跟在v-if或者v-else-if后面,否则不起作用。

<div>ok的值为:{{ ok }}</div>

<div v-if='ok'>如果值为true显示</div>

<div v-else>如果值为false显示</div>

v-for

用v-for指令根据遍历数组来进行渲染

两种渲染方式

<div v-for="(item,index) in items"></div>   //使用in,index是一个可选参数,表示当前项的索引

<div v-for="item of items"></div> //使用of

 let list = ref([11,23,34,45,56])

<div v-for='(index, item) in list'>

<span>{{ item }}--{{ index }}</span>

</div>

v-model

这个指令用于在表单上创建双向数据绑定。限制在<input><select><textarea>components中使用

 <input v-model='input_value'  @keydown.enter="enter"/>

let input_value = ref('')

let enter = () => {

console.log(input_value.value)

}

![image_1f5i6uiup1irig8hr8pdd4fr2q.png-21.3kB][15]

<1> lazy 默认情况下,v-model同步输入框的值和数据。可以通过这个修饰符,转变为在change事件再同步。

<input v-model.lazy="msg">

<2> number 自动将用户的输入值转化为数值类型

<input v-model.number="msg">

<3> trim:自动过滤用户输入的首尾空格

<input v-model.trim="msg">

v-on

v-on主要用来监听dom事件,以便执行一些代码块。表达式可以是一个方法名。

简写为:@

<button @click='change_ok'>点击按钮</button>

let change_ok = () => {

console.log('00000')

ok.value = !ok.value

}

<!-- 阻止单击事件继续传播 -->

<a v-on:click.stop="doThis"></a>

<!-- 提交事件不再重载页面 -->

<form v-on:submit.prevent="onSubmit"></form>

<!-- 修饰符可以串联 -->

<a v-on:click.stop.prevent="doThat"></a>

<!-- 只有修饰符 -->

<form v-on:submit.prevent></form>

<!-- 添加事件监听器时使用事件捕获模式 -->

<!-- 即元素自身触发的事件先在此处处理,然后才交由内部元素进行处理 -->

<div v-on:click.capture="doThis">...</div>

<!-- 只当在 event.target 是当前元素自身时触发处理函数 -->

<!-- 即事件不是从内部元素触发的 -->

<div v-on:click.self="doThat">...</div>

<!-- 点击事件将只会触发一次 -->

<a v-on:click.once="doThis"></a>

<!-- 滚动事件的默认行为 (即滚动行为) 将会立即触发 -->

<!-- 而不会等待 `onScroll` 完成 -->

<!-- 这其中包含 `event.preventDefault()` 的情况 -->

<div v-on:scroll.passive="onScroll">...</div>

v-bind

v-bind用来动态的绑定一个或者多个特性。没有参数时,可以绑定到一个包含键值对的对象。常用于动态绑定class和style。以及href等。

简写为一个冒号

 <a v-bind:href='herf'>百度</a>

<img :src='img_url' alt='leishen'>

let img_url = ref('https://image-1300284638.cos.ap-nanjing.myqcloud.com/leishen.jpeg')

常用的v-bind标签

  • v-bind:style
  • v-bind:title
  • v-bind:src

v-html

双大括号会将数据解释为普通文本,而非 HTML 代码。为了输出真正的 HTML ,你需要使用 v-html 指令:

<div v-html="rawHtml"></div>

这个 div 的内容将会被替换成为属性值 rawHtml,直接作为 HTML——会忽略解析属性值中的数据绑定。

动作监听

动作监听的流程:

  1. 标签上定义监听函数 @click='监听函数名'

  2. setup中实现监听函数
  3. setup中return监听函数

<template>

<!-- <one></one> -->

<button @click='alert_action'>点击事件</button>

</template>

<script>

import {defineComponent, ref, reactive, computed} from 'vue'

import one from '@/components/one/One'

export default defineComponent({

name: 'Main', // 组件名称

components:{

one

},

setup() {

let alert_action = () => {

alert('你点击了我')

}

return {

alert_action

}

}

})

</script>

<style scoped lang="scss">

</style>

vue全局使用

引用组件

子组件可以定义在components中,而views中往往定义父组件。在父组件中使用子组件的流程为:

  1. 子组件定义

<template>

<div>这是子组件</div>

</template>

<script>

import {defineComponent, ref, reactive, computed} from 'vue'

export default defineComponent({

name: 'One', // 组件名称

setup() {

}

})

</script>

<style scoped lang="scss">

</style>

  1. 父组件定义

<template>

<one></one>

</template>

<script>

import {defineComponent, ref, reactive, computed} from 'vue'

// 从components中的one文件夹下的One.vue中引入

import one from '@/components/one/One'

export default defineComponent({

name: 'Main', // 组件名称

// 组件本地化

components:{

one

},

setup() {

}

})

</script>

<style scoped lang="scss">

</style>

父子组件中传递数据

  1. 父组件传递给子组件

在父组件使用的子组件中加入参数传递:num='num'

<template>

<one :num='num'></one>

</template>

<script>

import {defineComponent, ref, reactive, computed} from 'vue'

import one from '@/components/one/One'

export default defineComponent({

name: 'Main', // 组件名称

components:{

one

},

setup() {

let alert_action = () => {

alert('你点击了我')

}

let num = ref(100)

return {

alert_action,

num

}

}

})

</script>

<style scoped lang="scss">

</style>

子组件中props用来接收父组件的传参,定义好变量,然后直接使用

<template>

<div>这是子组件</div>

<div>父组件传递过来的是: {{ num }}</div>

</template>

<script>

import {defineComponent, ref, reactive, computed} from 'vue'

export default defineComponent({

name: 'One', // 组件名称

props: {

num:{

type: Number

}

},

setup(props) {

}

})

</script>

<style scoped lang="scss">

</style>

  1. 子组件传递给父组件

子组件传值给父组件叫事件分发。通过ctx.emit分发事件

  1. 子组件

<template>

<div>这是子组件</div>

<div>父组件传递过来的是: {{ num }}</div>

<button @click='send'>点击传递参数</button>

</template>

<script>

import {defineComponent, ref, reactive, computed} from 'vue'

export default defineComponent({

name: 'One', // 组件名称

props: {

num:{

type: Number

}

},

setup(props, ctx) {

let send = () => {

// 使用事件分发

ctx.emit("send", 200)

}

return {

send

}

}

})

</script>

<style scoped lang="scss">

</style>

父组件

<template>

// 父组件接收send事件,将其在send_father函数中处理

<one :num='num' @send='send_father'></one>

<div>父组件收到的子组件的传参: {{ recv_value }}</div>

</template>

<script>

import {defineComponent, ref, reactive, computed} from 'vue'

import one from '@/components/one/One'

export default defineComponent({

name: 'Main', // 组件名称

components:{

one

},

setup() {

let alert_action = () => {

alert('你点击了我')

}

let num = ref(100)

let recv_value = ref(0)

let send_father = (val) => {

recv_value.value = val

}

return {

alert_action,

num,

send_father,

recv_value

}

}

})

</script>

<style scoped lang="scss">

</style>

全局共享变量之状态管理

在store中的index.js中可以定义全局使用的变量和方法。全局使用指的是所有组件可以修改,共享使用。

使用这些变量或者函数的方法是:

import {useStore} from 'vuex'

setup(props, ctx) {

let store = useStore()

//

store.commit('addTodo',{

title: value.value,

complete: false

}

路由

在router文件夹下存放着vue的路由信息

路由就是页面的静态路由。

根路由:每一个vue项目都有一个根路由,该路由的主要作用是页面的默认路由。

 {

path: '/',

name: 'Start',

component: Start

},

path: 路由路径

name:路由的名字

component: 该路由对应的组件。该组件必须先引入进来,并且是只需要显示引入的。其他组件由于不需要立刻显示,所以使用懒加载,即不立即加载到vue项目中。

其他路由的加载

{

path: '/home',

name: 'Home',

component: () => import('../views/Home.vue')

},

路由的使用

import {useRouter} from 'vue-router'

 setup(props, ctx) {

// router是全局路由对象

let router = useRouter()

let start = () =>{

// 使用push的方式,跳转路由

router.push({

// 路由的目的地可以用name:模块

name:'Home',

// 也可以是path:路径。两者二选一

path: '/home'

})

}

}

路由的传参

路由的传参有两种方式,分别是

  1. query 类似于get,显示传参,地址栏可以看到参数,刷新保存参数
  2. params 类似于post,地址栏看不到参数,刷新不保存参数

query

 setup(props, ctx) {

// router是全局路由对象

let router = useRouter()

let start = () =>{

// 使用push的方式,跳转路由

router.push({

name:'Home',

query:{

name: name.value,

num: num.value,

}

})

}

}

使用

import {useRouter, useRoute} from 'vue-router'

// route是当前路由对象

let route = useRoute()

console.log(route.query)

有一点需要注意数字类型的参数会变成字符串类型

params

params传参只能用name来路由

let start = () =>{

router.push({

name:'Home',

params:

{

name: name.value,

num: num.value,

}

})

}

接收参数:

import {useRouter, useRoute} from 'vue-router'

// route是当前路由对象

let route = useRoute()

console.log(route.params)

插槽

定义

插槽就是子组件中的提供给父组件使用的一个占位符,用 表示,父组件可以在这个占位符中填充任何模板代码,如 HTML、组件等,填充的内容会替换子组件的标签。

https://www.cnblogs.com/mandy-dyf/p/11528505.html

vue2 和vue3的区别

建立数据 data

这里就是Vue2与Vue3 最大的区别 — Vue2使用选项类型API(Options API)对比Vue3合成型API(Composition API)

选项类型

旧的选项型API在代码里分割了不同的属性(properties):data,computed属性,methods
合成类型

新的合成型API能让我们用方法(function)来分割,相比于旧的API使用属性来分组,这样代码会更加简便和整洁。

vue2

export default {

props: {

title: String

},

data () {

return {

username: '',

password: ''

}

},

methods: {

login () {

// 登陆方法

}

}

}

Vue2 的选项型API是把methods分割到独立的属性区域的。我们可以直接在这个属性里面添加方法来处理各种前端逻辑。

vue3

export default {

props: {

title: String

},

setup () {

const state = reactive({

username: '',

password: ''

})

const login = () => {

// 登陆方法

}

return {

login,

state

}

}

}

Vue3 的合成型API里面的setup()方法也是可以用来操控methods的。创建声名方法其实和声名数据状态是一样的。— 我们需要先声名一个方法然后在setup()方法中返回(return), 这样我们的组件内就可以调用这个方法了。

项目代码结构

所有的vue代码都写在views当中

公共组件存放在components中

有关网络请求的axios都存放在network中

页面跳转路由存放在router中

vue在实际项目中的使用技巧

vue与后端交互

vue通过 axios 与后端交互。通过引入axios中的各种方法,完成网络请求

import { fetch, post, del, postForm } from '../base/axios'

const URL = {

listUrl: '/page/list/',

createUrl: '/page/create/',

delUrl: '/page/delete/',

disableUrl: '/page/disable/',

enableUrl: '/page/enable/',

pageStateChange: '/page/page_state_change/',

getFiles: '/page/get_files/'

}

export function pageList (data) {

return fetch(URL.listUrl, data)

}

export function pageCreate(form) {

return postForm(URL.createUrl, form)

}

export function pageDelete(page_id) {

return del(URL.delUrl + page_id + '/')

}

export function pageDisable(data) {

return fetch(URL.disableUrl, data)

}

export function pageEnable(data) {

return fetch(URL.enableUrl, data)

}

export function pageStateChange(data) {

return fetch(URL.pageStateChange, data)

}

export function pageGetFiles(page_id) {

return fetch(URL.getFiles + page_id + '/')

}

普通post请求:

export function post (url, data = {}) {

return new Promise((resolve, reject) => {

// var qs = require('querystring')

axios.create({

headers: {'X-CSRFToken': getCookie('csrftoken'), 'Content-Type': 'application/x-www-form-urlencoded'},

}).post(url, data).then(response => {

resolve(response.data)

}, err => {

reject(err)

})

})

}

form-data的请求

export function postForm (url, data= {}) {

return new Promise((resolve, reject) => {

axios.create({

withCredentials: true,

headers: {'X-CSRFToken': getCookie('csrftoken'), 'Content-Type': "multipart/form-data"},

}).post(url, data).then(response => {

resolve(response.data)

}, err => {

reject(err)

})

})

}

在vue文件中的网络请求为:

import { pageList, pageDelete, pageDisable, pageEnable, pageCreate, pageStateChange, pageGetFiles } from 'api/page/index'

pageList(data).then((res =>{

this.tableConfig.tableData = res.data;

this.pager.total = res.total

})).catch(err => {

this.$alert(`${ err }`, '提示', {

confirmButtonText: '确定',

});

});

vue 导入组件和方法

vue使用的过程中需要导入组件或者方法,方法如axios请求。导入的语法是:

  • 导入组件需要使用大括号
  • 导入方法需要用大括号包裹

 import { useRouter } from 'vue-router'

import { tableConfigBase } from 'utils/common.js'

import { pageList, pageDelete, pageDisable, pageEnable, pageCreate, pageStateChange, pageGetFiles } from 'api/page/index'

import { showConfirmMessageBox, showMessage } from 'api/base/message'

import UploadFile from 'components/common/UploadFile'

import Pagination from 'components/common/Pagination'

导入组件是从别的文件中导入组件,除需要使用大括号之外,还需要本地引入

 export default {

components: {

UploadFile,

Pagination

},

.....

}

导入方法指的是从js文件中能够导入一个函数。如 showConfirmMessageBox,就是:

export function showConfirmMessageBox (message) {

return ElMessageBox.confirm(message, '提示', {

confirmButtonText: '确定',

cancelButtonText: '取消',

type: 'warning',

center: true

})

}

vue属于js的一个库,理所当然可以使用js。想要引用一个js,需要js的函数export

vue异步变同步的方法

axios是异步请求,如果想要将异步变成同步,使用asyncawait两个关键字即可。
async 修饰 vue函数
await 修饰 axios请求方法

 async editRow(index, row){

this.createForm.name = row.name

this.createForm.desc = row.desc

this.createForm.page_url = row.page_url

this.createForm.edit = true

this.createForm.page_id = row.id

await pageGetFiles(row.id).then((res => {

if (res.result == 0) {

for (var i = 0; i < res.page_files.length; i++) {

this.createForm.file.push({'page_file_id': res.page_files[i].id, 'name': res.page_files[i].name})

}

}

}))

this.createModal.visible = true

},

vue中formdata的使用

formdata 用于有静态资源从vue传输到django中,如图片和文件等。使用如下:

创建: var data = new FormData()

添加:

    data.append('name', this.createForm.name)

data.append('desc', this.createForm.desc)

data.append('page_url', this.createForm.page_url)

data.append('edit', this.createForm.edit)

data.append('page_id', this.createForm.page_id)

特别注意,如果添加文件也是使用中格式
data.append('new_files', file.raw)

如果添加多个文件,那么直接用同一个key,会被打包成列表

取值:data.get(key)

如果value是一个数组,取值方法: data.getAll(key)

vue代码

 var data = new FormData();

data.append('name', this.createForm.name)

data.append('desc', this.createForm.desc)

data.append('page_url', this.createForm.page_url)

data.append('edit', this.createForm.edit)

data.append('page_id', this.createForm.page_id)

this.createForm.file.forEach((file) => {

if (file.raw) {

data.append('new_files', file.raw)

}else{

data.append('old_files', file.page_file_id)

}

})

export function postForm (url, data= {}) {

return new Promise((resolve, reject) => {

axios.create({

withCredentials: true,

headers: {'X-CSRFToken': getCookie('csrftoken'), 'Content-Type': "multipart/form-data"},

}).post(url, data).then(response => {

resolve(response.data)

}, err => {

reject(err)

})

})

}

export function pageCreate(form) {

return postForm(URL.createUrl, form)

}

pageCreate(data)

django的接收:

# 接收到formdata的出文件之外的数据

data = request.POST

# 接收文件,getlist是接收多个文件 get是接收单个文件

new_files = request.FILES.getlist('new_files')

vue校验

在input输入框中需要一些校验,以下面的demo为例

 <el-dialog title="添加页面" v-model="createModal.visible" width="800px" :before-close="closeDialog">

<div class="demo-dialog">

<el-form :model="createForm" :rules="createRules" ref="createForm" label-width="100px">

<el-form-item label="名称" prop="name">

<el-input v-model="createForm.name"></el-input>

</el-form-item>

<el-form-item label="描述" prop="desc">

<el-input v-model="createForm.desc"></el-input>

</el-form-item>

<el-form-item label="网址" prop="page_url">

<el-col>

<el-input v-model="createForm.page_url" >

<template #prepend>page/</template>

</el-input>

</el-col>

</el-form-item>

<el-form-item label="页面文件" prop="file">

<upload-file :allowed-type="['html']"

v-model:file-list="createForm.file"

:multiple="true">支持上传单个或多个html文件

</upload-file>

</el-form-item>

</el-form>

</div>

<template #footer>

<span class="dialog-footer">

<el-button @click="submitCancel">取 消</el-button>

<el-button type="primary" @click="submitForm('createForm')">确 定</el-button>

</span>

</template>

</el-dialog>

校验规则

createRules: {

position: [

{ required: true, message: '请选择位置', trigger: 'change' },

],

name: [

{ required: true, message: '请输入认证名称', trigger: 'change' },

{ min: 2, max: 50, message: '长度在 2~50 个字符内', trigger: 'change' },

],

desc: [

{ required: false, max: 200, message: '长度在 200 个字符内', trigger: 'change' },

],

page_url: [

{ required: true, message: '请填写链接', trigger: 'change' },

{ min: 1, max: 50, message: '长度在 1~50 个字符内', trigger: 'change' },

{validator: urlAcq, trigger: 'change'}

],

file: [

{ required: true, message: '请上传html文件', trigger: 'change' },

{validator: indexHtmlAcq, trigger: 'change'}

]

},

自定义校验 上传文件包含index.html

const indexHtmlAcq = (rule, files, callback) => {

let index_html_num = 0

files.forEach((file) =>{

if(file.name == 'index.html'){

index_html_num = index_html_num + 1

}

})

if(index_html_num == 0){

callback(new Error('必须添加一个index.html文件'))

}else if(index_html_num > 1){

callback(new Error('只能添加一个index.html文件'))

}

return callback()

}

自定义校验 输入框中只允许输入数字字母-_

      const urlAcq = (rule, str, callback) =>{

const reg =/^[-_a-zA-Z0-9]+$/;

if(!reg.test(str)){

callback(new Error('url中包含特殊字符'))

}

return callback()

}

清空校验的红色提示

this.$nextTick(() => {

this.$refs["createForm"].clearValidate()

})

以上是 vue3.0 学习使用记录 的全部内容, 来源链接: utcz.com/z/380302.html

回到顶部