• React 实现列表拖动效果 当我们想在 React实现一个列表拖动的效果的时候,有很多的第三方库(React dnd)可以借鉴,但是学习第三方库也是一个成本,或者拖动本身并不复杂,只需要第三方库的某一个 api 。这样情况...

    React 实现列表拖动效果

    当我们想在 React 中实现一个列表拖动的效果的时候,有很多的第三方库(React dnd)可以借鉴,但是学习第三方库也是一个成本,或者拖动本身并不复杂,只需要第三方库的某一个 api 。这样情况下,我们可以自己实现一个。
    在这里插入图片描述
    组件源码
    完成项目 欢迎 star
    效果预览用户名:admin;密码:admin;

    1. 使用 React 的鼠标事件

    React 鼠标事件

    在这里我们只会用到:onMouseDownonMouseMoveonMouseUp 这三个鼠标事件

    定义组件的 state

     state = {
        list: data, // 列表的数据
        dragging: false, // 是否开始拖`动`
        draggingIndex: -1, // 拖动元素的下标
        startPageY: 0, // 开始拖动的 Y 轴坐标
        offsetPageY: 0 // 拖动元素的位移
      }
    

    2. 判断拖放的开始和结束

    onMouseDown开始、onMouseUp结束

    1. 给列表的每一项添加一个onMouseDown事件监听
    <List.Item 
        onMouseDown={(e) => this.dragging(e, index)}
        >
      {item}
    </List.Item>
    
    1. 当鼠标按下的时候我们初始化组件的状态。
      // 点击的时候记录 Y 轴的位置 
    dragging = (e, index) => {
      this.setState({
        dragging: true, 
        draggingIndex: index, 
        currentPageY: e.pageY, // 只需要纵向移动 
        startPageY: e.pageY,
      })
    }
    
    1. 给点击的元素添加样式,让我们知道要拖动谁。
    <List.Item 
        onMouseDown={(e) => this.dragging(e, index)}
       	style={this.getDraggingStyle(index)}
        >
      {item}
    </List.Item>
    
    // 移动动画
    getDraggingStyle = (index) => {
      if (index !== this.state.draggingIndex) return
      return {
        backgorundColor: '#eee',
        transform: `translate(10px,${this.state.offsetPageY}px)`, // 下面会介绍
        opacity: 0.5
      }
    }
    

    3. 实现拖放元素的位移效果

    效果:拖放的元素视觉效果上要脱离列表本身,在列表上下进行移动。

    如何实现:

    1. 需要对onMouseMove进行监听。

      1. 在哪里进行监听 ?列表本身?

        首先不能在列表本身进行监听,也不能在父容器监听,因为鼠标移动的范围是屏幕的大小(暂时这么设定),那么只有在 document 上进行监听吗 ?

        其实我们可以设置一个遮罩层,在它上面进行onMouseMoveonMouseUp的事件监听。

        <List
          dataSource={this.state.list}
          renderItem={(item, index) => (
            <List.Item onMouseDown={(e) => this.dragging(e, index)} key={item}
              style={this.getDraggingStyle(index)}
              >
              {item}
            </List.Item>
          )}
          />
        {/* 用一个遮罩监听事件,也可以监听整个 document */}
        {
          this.state.dragging && (
            <div
              style={maskStyle}
              onMouseUp={e => this.onMouseUp(e)}
              onMouseMove={e => this.onMouseMove(e)}
              >
            </div>
          )
        }
        
        const maskStyle = {
          position: 'fixed',
          left: 0,
          right: 0,
          top: 0,
          bottom: 0,
          backgorund: 'rgba(0,0,0,0.5)'
        }
        
    2. 需要记录 onMouseMove 移动的轨迹(offset),列表跟随鼠标进行移动。

      1. 需要记录从起点移动到终点的距离(offset),记录移动元素的下标。
      2. 根据移动距离是否大于行高判断是向上移动,还是向下移动。
      3. 移动的过程更新 offsetPageY 然后借助 CSS3 动画进行移动。
    // 移动的轨迹
    onMouseMove = (e) => {
      let offset = e.pageY - this.state.startPageY
      const draggingIndex = this.state.draggingIndex
      if (offset > lineHeight && draggingIndex < this.state.list.length) {
        //  向下移动
        offset -= lineHeight
      } else if (offset < -lineHeight && draggingIndex > 0) {
        // 向上移动
        offset += lineHeight
      }
      // item 移动的距离
      this.setState({ offsetPageY: offset })
    }
    

    4. 更新拖放后的数据

    移动的过程中更新列表的数据

    // 重新计算数组,插入一个,删除一个。不断地插入删除(每隔一行)。
    const move = (arr = [], startIndex, toIndex) => {
      arr = arr.slice()
      arr.splice(toIndex, 0, arr.splice(startIndex, 1))
      return arr;
    }
    

    更新数据

    onMouseMove = (e) => {
      let offset = e.pageY - this.state.startPageY
      const draggingIndex = this.state.draggingIndex
      if (offset > lineHeight && draggingIndex < this.state.list.length) {
        //  向下移动
        offset -= lineHeight
         // 按照移动的方向进行数据交换
        this.setState({
          list: move(this.state.list, draggingIndex, draggingIndex + 1),
          draggingIndex: draggingIndex + 1,
          startPageY: this.state.startPageY + lineHeight
        })
      } else if (offset < -lineHeight && draggingIndex > 0) {
        // 向上移动
        offset += lineHeight
        this.setState({
          list: move(this.state.list, draggingIndex, draggingIndex - 1),
          draggingIndex: draggingIndex - 1,
          startPageY: this.state.startPageY - lineHeight
        })
      }
    

    向上移动 draggingIndex-1 ,向下移动 draggingIndex+1

    每移动一行,数据更新一次,0 移动到 2 的位置,需要先移动到 1 ,然后从 1 的位置开始重新计算位移,然后由 1 的位置移动到 2 。以此类推。类似冒泡排序。

    5. 结束拖放

    // 松开鼠标的时候,重新初始化 startPageY、draggingIndex,
    onMouseUp = (e) => {
      this.setState({
        dragging: false,// 移除遮罩
        startPageY: 0, 
        draggingIndex: -1
      })
    }
    

    这只是一个很简单场景实现(列表的简单排序),如需功能强大的拖动还是需要第三方库来实现。

    展开全文
  • 设置了 key 以后,页面更新时,React 会根据这个 key,只更新或重新渲染更改的组件,没有更改的组件则直接移动,而不是将它们从 DOM Tree 中移除然后再将它们重新创建,实现了复用,提高了渲染效率。 KeyReact ...

    数组和迭代器循环出的 DOM 元素要设置唯一的,固定不变的 key 值。

    设置了 key 以后,页面更新时,React 会根据这个 key,只更新或重新渲染更改的组件,没有更改的组件则直接移动,而不是将它们从 DOM Tree 中移除然后再将它们重新创建,实现了复用,提高了渲染效率。

    Key 是 React 识别 DOM 元素的唯一标识,不真实存在,也不能被 Props 读取。React 根据 Key 来决定是销毁重建组件还是更新组件。

    不建议使用 index 作为 key 值,有时候会出现意想不到的结果。

    来看个使用 index 作为 key 的反模式例子:

    export default class Home extends React.Component {
        constructor(props) {
            super(props);
    
            this.state = {
                arr: ['a','b','c']
            }
        }
    
        reverse = () => {
            this.setState({
                arr: this.state.arr.reverse()
            })
        }
    
        render() {
            return (
                <div>
                    <ul>
                        {
                            this.state.arr.map((item, idx) => {
                                return <li key={idx}>{item}<input /></li>
                            })
                        }
                    </ul>
                    <button onClick={this.reverse}>reverse</button>
                </div>
            )
        }
    }

    点击了 reverse 按钮后变成了这样

    显然这个效果不是我们想要的。

    出现这个情况,是因为 React 是以 key 来唯一标识组件的,当发现 update 前和 update 后 key 值没有变化,react 就会认为 update 前后是同一个组件,进而只会对内部的属性进行修改。拿上述例子分析,react 会作出这样的判断:

    • 检查 key 值,发现都是0,判定为同一个组件;
    • 检查 item 部分,发现有变化,重新渲染这部分;
    • 检查 input 部分,发现不依赖 state 和 props,所以不进行重新渲染;

    优化方法:

    用 item 作为 key 值。

     

     

    展开全文
  • React实现tab切换效果

    2018-11-23 15:06:38
    之前写过JQ实现tab切换效果,业务需要,用React实现tab切换,记录一下 ... this.state = { // 全部数据 addressJobs: [ { address: '北京', jobs: ['北京职位1', '北京职位2', '北京职位3', '北京职位4', '...

    之前写过JQ实现tab切换效果,业务需要,用React实现tab切换,记录一下

    ...
    this.state = {
      // 全部数据
      addressJobs: [
        {
          address: '北京',
          jobs: ['北京职位1', '北京职位2', '北京职位3', '北京职位4', '北京职位5', '北京职位6', '北京职位7', '北京职位8', '北京职位9']
        },
        {
          address: '上海',
          jobs: ['上海职位1', '上海职位2', '上海职位3', '上海职位4', '上海职位5', '上海职位6', '上海职位7', '上海职位8', '上海职位9']
        },
        {
          address: '广州',
          jobs: ['广州职位1', '广州职位2', '广州职位3', '广州职位4', '广州职位5', '广州职位6', '广州职位7', '广州职位8', '广州职位9']
        },
        {
          address: '其他',
          jobs: ['其他职位1', '其他职位2', '其他职位3', '其他职位4', '其他职位5', '其他职位6', '其他职位7', '其他职位8', '其他职位9']
        },
      ],
      jobssss: [],// 单独取出jobs渲染职位
    }
    .....
    // 初始化的时候选中第一个
    componentDidMount() {
      document.getElementsByClassName('adress-item')[0].className = 'adress-item active';
      this.setState({
        jobssss: this.state.addressJobs[0].jobs
      })
    }
    // tab切换
    adressNavClick = (index) => {
      document.getElementsByClassName('adress-item')[index].className = 'adress-item active';
      this.state.addressJobs.forEach((el,item) => {
        if (item === index) {
          this.setState({
            jobssss: el.jobs
          })
        } else {
          document.getElementsByClassName('adress-item')[item].className = 'adress-item';
        }
      })
    }
    ......
    {/* dom结构 */}
    <div className='recruit-content'>
      <ul className='adress-nav'>
        {this.state.addressJobs ? this.state.addressJobs.map((el, index) => {
          return (<li className='adress-item' key={index} onClick={() => {this.adressNavClick(index)}}>{el.address}</li>)
        }) : ''}
      </ul>
      <div className='jobs-wrapper'>
        <ul className='jobs-list clearfloat'>
          {this.state.jobssss ? this.state.jobssss.map((el, index) => {
            return (<li className='jobs-item' key={index}>{el}</li>)
          }) : ''}
        </ul>
      </div>
    </div>
    ......

    展开全文
  • react实现任务添加功能效果图如下: 首先我们如1课创建一个文件夹在文件夹中安装react环境需要的配置文件 npm init -y npm i babel-standalone -D npm i react react-dom -D 安装配置文件教程链接:...

    react实现任务添加功能效果图如下:

    在这里插入图片描述

    首先我们如1课创建一个文件夹在文件夹中安装react环境需要的配置文件

    npm init -y
    npm i babel-standalone -D
    npm i react react-dom -D

    安装配置文件教程链接:https://blog.csdn.net/qq_41614928/article/details/93771657
    安装完成后我们开始编写下面代码

    实现代码如下:

    <!DOCTYPE html>
    <html lang="en">
    
    <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <meta http-equiv="X-UA-Compatible" content="ie=edge">
      <title>qq聊天框</title>
      <style>
        * {margin: 0; padding: 0;}
        #app {
          width: 500px;
          border: 1px solid #000;
          text-align: center;
        }
        h2 {
          background-color: #dee1e6;
          cursor: pointer;
        }
        ul li {
          background-color: #f1f2f3;
          margin-bottom: 1px;
          padding: 10px 10px;
          text-align: left;
        }
      </style>
    </head>
    
    <body>
      <!-- 所有的页面代码都是放在这里面 -->
      <div id="app"></div>
      <!-- 用于解析jsx的代码 babel引用应在前面 -->
      <script src="./node_modules/babel-standalone/babel.js"></script>
      <!-- 引用react模块,用于构建用户界面的 JavaScript 库 UI库 -->
      <script src="./node_modules/react/umd/react.development.js"></script>
      <!-- 操作VM DOM 加载顺序必须先引入react再引入react-dom-->
      <script src='./node_modules/react-dom/umd/react-dom.development.js'></script>
      
      <!-- 引入script类型为babel样式 这样可以解析里面的jsx代码 -->
      <script type='text/babel'>
    
        class App extends React.Component{
          constructor(){
            super();
            this.state = {
              list:[
                {
                  title:'vue',
                  chacked:true
                },
                {
                  title:'augular',
                  chacked:false
                },
                {
                  title:'javaScript',
                  chacked:true
                },
                {
                  title:'typeScript',
                  chacked:false
                }
              ]
            }
          }
          //添加事件
          addData=(e)=>{
            if(this.refs.title.value && e.keyCode===13 ){
              let list = this.state.list;
              let value = this.refs.title.value;
              list.unshift({
                title:value,
                chacked:false
              });
              //将更改后的数据渲染到页面上
              this.setState({
                list:list
              })
              this.refs.title.value = '';
            }
          };
          //勾选事件
          checkboxChange=(index)=>{
            let list = this.state.list;
            list[index].chacked = !list[index].chacked
            //将更改后的数据渲染到页面上
            this.setState({
              list:list
            })
          };
          //删除事件
          removeData=(index)=>{
            let list = this.state.list;
            list.splice(index,1)
            //将更改后的数据渲染到页面上 es6写法两边相同直接List
            this.setState({
              list
            })
          }
          render(){
            return(
              <div>
                请输入你需要添加的课程:
                <input type='text' ref='title' onKeyUp={this.addData} />
                <h2>你还没学习的课程</h2>
                <ul>{
                  this.state.list.map((item, index)=>{
                    if(!item.chacked){
                      return(
                        <li key={index}>
                          <input type='checkbox' defaultChecked={item.chacked} onClick={this.checkboxChange.bind(this,index)} />
                          {item.title}----
                          <button onClick={this.removeData.bind(this,index)}>删除</button>
                        </li>
                      )
                    }
                  })
                }</ul>
                <h2>你学习过的课程</h2>
                <ul>{
                  this.state.list.map((item, index)=>{
                    if(item.chacked){
                      return(
                        <li key={index}>
                          <input type='checkbox' defaultChecked={item.chacked} onClick={this.checkboxChange.bind(this,index)} />
                          {item.title}----
                          <button onClick={this.removeData.bind(this,index)}>删除</button>
                        </li>
                      )
                    }
                  })
                }</ul>
              </div>
            )
          }
        }
    
        ReactDOM.render(
          <App />,
          document.getElementById('app')
        )
      </script>
    </body>
    </html>
    
    展开全文
  • React 组件的key

    2018-07-06 14:57:48
    转载:https://blog.csdn.net/code_for_free/article/details/50514259一,概要在同系列的上一篇博客React修炼之路(一)里,我第一次接触组件key这个概念,因为不理解key的用处及不清楚React组件的更新机制而遇到...

    转载:https://blog.csdn.net/code_for_free/article/details/50514259

    一,概要

    在同系列的上一篇博客React修炼之路(一)里,我第一次接触组件key这个概念,因为不理解key的用处及不清楚React组件的更新机制而遇到父级组件调用了render函数而render内的子组件内容不更新的问题。

    React修炼之路(一)里我在子组件里使用了componentWillReceiveProps方法,实现了内容更新,具体内容参见上一篇博客。

    最近又看了一些资料,同时实践得更多,对这个问题有了新的理解,下面简单说说。

    二,学习资料

    惯例,先上资料:

    核心内容: React是根据组件上设定的key属性来生成该组件唯一的标识,只有key改变了,React才会更新组件,否则重用该组件。如果想要更新组件内容,请保证每次的key都不一样。

    建议大家先看第二个文档:民间文档:进一步阐述和论证,该文档有例子演示也有相应的解决方法。解决方法的核心还是这句话:如果想要更新组件内容,请保证当前组件的key与上一状态组件的key不同。

    三,代码示例

    React修炼之路(一)里面实现组件更新的方法就不说了,这里说说上面文档给我的启发。首先要明确自己的业务需求。

    • 你的组件不是动态组件(不需要动态更新),例如你一个列表只做展示,第一次render出来后就不会改变里面的内容,那么你不需要指定key(不指定key时,React也会生成一个默认的标识),或者将index作为key,只要key不重复即可。

    • 你的组件是动态组件(需要动态更新),例如你一个列表做展示时,能向列表添加或删除元素,此时组件是需要刷新的,刷新的依据就是key,对于相同的key,React会采取和上一次刷新同样的方式。这个时候,如果你采取index作为key,那么假设你在列表顶部添加了元素,那么新的元素的key就是index=0,和上一次刷新时旧顶部元素的key相同,此时React将认为这是这个新元素就是就顶部元素,所以就按照顶部元素的内容刷新该元素,导致新元素的内容就是以前的内容,这个和我的预期不一致,我们预期是顶部元素显示新的内容。亲自试一试

    • 我希望自己讲得足够清楚,要是不够清楚的话,千万要试一试,不过大概实际开发都是遇到问题再去深入学习的吧,实践越多,遇到的问题越多,就能学到越多。

    那么现在我们都知道了,想要组件动态刷新,只要每次刷新都赋予一个新的独一无二的key即可,具体做法有很多种,民间文档:进一步阐述和论证,上面提到的文档后面就给出了几种方法,我没试过,大家感兴趣可以去试试。

    我的方法,之前查阅资料想了解随机数的生产原理时,看到这么一句话:

    逝者如斯夫,时间就是一个随机数。

    当前时间

    很有哲理味道的一句话,所以我就采取了时间作为key,那么每次生成的key都是当前时间,是独一无二的,能确保React的动态刷新。代码如下:

    上一篇博客的代码

    render: function(){
        var that = this;
        return(
            <ul>
            {
              this.state.programs.map(function(program,index) {
                return <li key={index} className="channel-program">
                <ChannelProgram program_index={index} day={that.props.day} change={that.props.change} bill={program}/>
                </li>;
              })
            }
            </ul>
        );
      }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    新的代码,区别在于将 li 标签的key由index换成当前时间

    render: function(){
        var that = this;
        return(
            <ul>
            {
              this.state.programs.map(function(program,index) {
                return <li key={new Date().getTime()} className="channel-program">
                <ChannelProgram program_index={index} day={that.props.day} change={that.props.change} bill={program}/>
                </li>;
              })
            }
            </ul>
        );
      }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    乍一看是没问题的,完美解决问题,但是一运行,结果,你猜。不卖关子了,直接上代码,正确做法是这样的。

    key={new Date().getTime() + index}
    • 1

    1月14日更新,+new Date() 等同于 new Date().getTime()

    key={+new Date() + index}
    • 1

    哈哈,new Date().getTime()返回了一个精确到毫秒的时间,也就是说,如果是毫秒内的,这个返回值是一样的,也就是key一样,这是React不允许的,直接报错。而恰好你的服务器就是那么快,每个li的生成耗时是毫秒内的,此处的解决方法就是在该基础上index,这样生成的key才是独一无二的。

    对比之前方法的优势所在

    虽然以前写了下面这个函数能实现动态更新,但是有两个弊端:

    componentWillReceiveProps: function(nextProps,nextState) {
        this.setState({
          start_time: nextProps.start_time,
          end_time: nextProps.end_time,
          title: nextProps.title
        });
      }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 一,倒不是代码量的问题,即使以后this.setState说不定会持续增多,但最主要的还是代码维护的问题,若采用了以前的方法,每次state的增减都要去维护componentWillReceiveProps这个方法。

    • 二,以前的方法每次都会更新所有的组件,而有时候你不需要更新所有的组件。例如,在增加一项的时候,你并不需要更新其余项,此时你只需要给新的一样设置新的独一无二的key,而其余项key不变即能实现你的业务逻辑。举个例子吧。

    render: function(){
        var that = this;
        return(
              {
                this.state.items.map(function(word) {
                  return <Object item={word}
                    key={word.id + ":" + word.order + ":" + (word.color || "")} />;
                })
              }
        );
      }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    核心内容:

    key={word.id + ":" + word.order + ":" + (word.color || "")}
    • 1

    可见,只有id,order,color有改变的时候key才会改变,这大概就是key的意义所在吧。


    展开全文
  • 最近在项目实践中,发现react中可以利用key实现子组件的重新渲染,具体场景如下:需要切换下拉菜单时,对应的榜单也要替换成该轮的数据 具体代码如下 render() { let {round, sid} = this.state; //round表示选中...
  • react实现todolist 增删改查 实现需求:todolist实现增删改查; 实现思路:react组件划分。因为用的是localstorage封装了数据库各个组件之间数据库不能同步,所以设计组件的思路是在父组件里进行增删改查后的渲染...
  • react实现轮播图。

    2019-12-18 16:57:34
    import React from 'react'; import PropTypes from 'prop-types'; import '../static/css/banner.css'; export default class Banner extends React.Component { //=>设置属性的默认值和规则 static ...
  • Reactkey详解

    2019-06-20 11:11:13
    Reactkey详解 react.js 一个例子 有这样的一个场景如下图所示,有一组动态数量的input,可以增加和删除和重新排序,数组元素生成的组件用index作为key的值,例如下图生成的ui展示: ... 上面例子中的input组件渲染...
  • 我们在开发react列表组件时,如果遍历项中缺少key,那么就会出现一个警告,a key should be provided for list items,就是当创建一个元素时,必须包含一个特殊的key属性。那么为什么要加这个key,其作用又是什么呢?...
  • react实现登录注册

    2019-07-08 21:14:40
    登录注册页面都分为上下两个部分,其中上部是logo,下部分是... logo import React from 'react'import logoImg from './job.png'import './logo.css' class Logo extends React.Component{ render(){ return ( ...
  • React实现的日历

    2019-01-06 15:54:31
    使用React实现的JS日历 class Calendar extends Dialog { constructor(props) { super(props); this.state.url = null; this.state.date = null; this.state.today = null; ...
  • React实现聊天机器人

    2016-09-12 12:31:52
    React实现了一个聊天机器人,结合使用fetch工具对网络数据进行读取,这些数据由图灵API提供。
  • react实现QQ聊天框效果如下: 首先我们如1课创建一个文件夹在文件夹中安装react环境需要的配置文件 npm init -y npm i babel-standalone -D npm i react react-dom -D 安装配置文件教程链接:...
  • react实现仿微信聊天输入框 实现原理: 利用H5提供的元素可编辑属性 contentEditable 使普通的html元素具有和输入框一样的编辑功能 其他实现方式(较复杂): 使用textarea作为输入框 准备一个与textarea等宽的...
  • React实现购物车功能

    2019-04-12 06:23:18
    这个简单的demo,功能是这样的:最左侧是列表,点击列表项可查看详情,中间是详情,详情中有按钮,点击可加入...使用create-react-app创建.代码如下: index.js: import React from 'react'; import ReactDOM from 'react-...
  • 最近在看react , jsx的语法一开始看起来确实不习惯,但是确实比较灵活 运行效果:   代码: import React from 'react'; // import ShadowDOM from 'react-shadow'; import './index.css'; let timeId = ...
  • 本文纯粹为了练习一下react,因此我会在react环境下实现这么一个小组件:它假设了菜单数据来自于网络请求,并且仅实现无限分级菜单的核心功能(父子关系,展开与收起),至于样式则不是关注的重点。 分析&设计 ...
  • React实现简单易用Toast

    2019-06-10 11:11:06
    很多项目中需要实现app中常见的提示效果Toast。这个效果看似简单,实现起来也容易,为了方便,将它封装成npm组件,方便后续使用。 这里也是抛砖引玉,可以查看项目地址一起学习react-comment-toast 使用方法 import ...
  • 使用React实现TodoList实现增删改查 TodoList编写思路 React --》 组件 --》 3大属性(props,state,refs) TODOList,数据增删改查的操作 思路: 1、UI界面怎么实现;UI(User Interface) 2、功能代码实现: (1...
1 2 3 4 5 ... 20
收藏数 22,000
精华内容 8,800
热门标签