taro图片上传组件(单张)兼容h5和weapp

uploadfileComponent

// 上传佐证组件代码
import Taro, { Component } from '@tarojs/taro'
import { connect } from '@tarojs/redux'

import { View, Input, Image, Text } from '@tarojs/components'
import { AtIcon, AtProgress } from 'taro-ui'
import _ from 'lodash'

import PropTypes from 'prop-types'
import { getUploadProofUrl } from '../../utils/utils'

import './index.scss'

@connect(({ loading }) => ({
  loading: loading.effects['global/uploadProof']
}))
export default class GtUploadFile extends Component {
  static propTypes = {
    existingFiles: PropTypes.array,//已经存在的文件
    uploadFileType: PropTypes.string,//上传文件类型控制,通过uploadFileType控制多个类型以逗号隔开,示例:uploadFileType:"image/gif,image/jp2"
    maxCount: PropTypes.number,//最大上传图片数
    multiple: PropTypes.bool,//是否支持多选
    onUploadFile: PropTypes.func, // 点击上传按钮时触发的事件
    onConfirom: PropTypes.func, // 点击删除按钮时触发的事件
    onRemoveImage: PropTypes.func, // 点击删除按钮时触发的事件
    tempFile: PropTypes.bool,//是文件为true 图片为false
    recTypeId: PropTypes.number,
    recId: PropTypes.number,
    subRecTypeId: PropTypes.number,
    subRecId: PropTypes.number,
  }
  static defaultProps = {
    existingFiles: [],
    uploadFileType: 'image/*',
    maxCount: 1, //最大上传图片数
    multiple: true, //是否支持多选
    onUploadFile: null, //获取上传的
    onConfirom: null, //获取删除的
    onRemoveImage: null, //删除上传的
    tempFile: false,
    recTypeId: 0,
    recId: 0,
    subRecTypeId: 0,
    subRecId: 0,

  }
  constructor(props) {
    super(props)
    this.state = {
      uploadTypeIsImage: true,//上传文件类型是否是上传图片
      viewFilesData: [],//图片文件,在view上面显示
      showFileProgress: false,//是否显示文件上传进度条
      fileProgress: 0,//文件上传进度条进度
      hiddenUploadBtn: false,//隐藏上传按钮
      isRemovFile: false
    }
  }
  componentWillMount() {
    const { uploadFileType } = this.props
    let uploadType = _.startsWith('image/*', uploadFileType, 0)
    if (uploadType) {
      this.setState({
        uploadTypeIsImage: true
      })
    } else {
      this.setState({
        uploadTypeIsImage: false
      })
    }
  }
  componentWillReceiveProps(nextProps) {
    //如果值是异步获取
    const { existingFiles } = nextProps
    const { viewFilesData, isRemovFile } = this.state
    if (!viewFilesData.length && !isRemovFile) {
      this.setState({
        viewFilesData: existingFiles,
        hiddenUploadBtn: true//隐藏上传按钮
      })
    } else {
      return
    }
  }

  //确定
  handConfirm() {
    this.props.onConfirom()
  }

  //获取服务器和本地用户的信息
  getIncidentalInfo() {
    const rootUrl = getUploadProofUrl('/file/upload') // 服务器地址
    const token = Taro.getStorageSync('token')  // 图片上传需要单独写入token
    const locale = Taro.getStorageSync('locale')  // 图片上传需要单独写入token
    const userLoginName = Taro.getStorageSync('userLoginName')  // 图片上传需要单独写入token
    return {
      rootUrl, token, locale, userLoginName
    }
  }

  //触发选择文件的事件
  handChooesFile() {
    if (process.env.TARO_ENV === 'weapp') {
      //微信环境下触发微信小程序原生API的事件
      const { uploadTypeIsImage } = this.state
      if (uploadTypeIsImage) {
        this.weappHandUpFiles()
      } else {
        this.uploadMessageFile()
      }
    } else if (process.env.TARO_ENV === 'h5') {
      //h5环境下直接触发input框的点击事件
      let uploadInput = document.getElementById('uploadInput')
      uploadInput.click();
      //点击以后会触发input框上面的onChange事件
    }
  }
  /** -----h5端的处理----- */
  //input框的onChange事件
  handSelectFiles(e) {
    let files = e.target.files
    this.handUploadFilesFun(files)
  }
  //文件循环遍历处理
  handUploadFilesFun(files) {
    let that = this
    const { uploadTypeIsImage } = this.state
    let tipsText = uploadTypeIsImage ? '张图片' : '个文件'
    let fileNamesData = []
    let imagesSrcData = [] //转化为blob格式在浏览器上显示缓存的图片
    let uploadfilemaxsize = 10 * 1024 * 1024 //大小的上限
    let uploadData = [] //确定按钮时获取的值
    if (!files.length) {
      return
    } else {
      for (let index = 0; index < files.length; index++) {
        let filesItem = files[index]
        if (filesItem.size > uploadfilemaxsize) {
          let uploadfilemsg = '上传文件大小超过系统规定上限(10M),请重新选择图片'
          Taro.showToast({ title: uploadfilemsg, icon: 'success', duration: 2000 })
          return
        } else {

          this.uploadFileItem(filesItem, index, (data) => {
            if (!data) {
              return
            } else {
              if (data.success) {
                if (uploadTypeIsImage) {
                  //上传图片就添加URL
                  let filesSrcItem = URL.createObjectURL(filesItem);
                  imagesSrcData.push(filesSrcItem)
                } else {
                  //上传文件就添加filename
                  let fileName = filesItem.name
                  fileNamesData.push(fileName)
                }
                let showViewFilesData = uploadTypeIsImage ? imagesSrcData : fileNamesData
                that.setState({
                  viewFilesData: showViewFilesData
                });
                Taro.showToast({ title: `第${index + 1}${tipsText}上传成功`, icon: 'success', duration: 2000 })
                uploadData.push(data.data[0])
                if (index === files.length - 1) {
                  that.props.onUploadFile(uploadData)
                  return
                }
              } else {
                Taro.showToast({ title: `第${index + 1}${tipsText}上传失败`, icon: 'error', duration: 2000 })
                that.setState({
                  hiddenUploadBtn: false
                })
                console.log(data, 'error')
                return
              }

            }
          })
        }
      }

    }

  }
  //单个文件(图片)上传服务器
  uploadFileItem(filesItem, index, callback) {
    let that = this
    const { rootUrl, token, locale, userLoginName } = this.getIncidentalInfo()
    const { maxCount, tempFile, recTypeId, recId, subRecTypeId, subRecId } = this.props
    if (index > maxCount) {
      return
    } else {
      let formData = new FormData();
      let xhr = new XMLHttpRequest();
      xhr.open('POST', rootUrl, true);  // 第三个参数为async?,异步/同步
      formData.append(filesItem.name, filesItem);
      //把请求相关参数放入formData中
      formData.append('tempFile', tempFile);
      formData.append('recTypeId', recTypeId);
      formData.append('recId', recId);
      formData.append('subRecTypeId', subRecTypeId);
      formData.append('subRecId', subRecId);

      xhr.setRequestHeader("userToken", token);
      xhr.setRequestHeader("userLoginName", userLoginName);
      xhr.setRequestHeader("userLanguage", locale);

      that.setState({
        showFileProgress: true
      })
      //监听请求的进度并在回调中传入进度参数
      xhr.upload.addEventListener('progress', (e) => {
        if (e.lengthComputable) {
          let progress = Math.round((e.loaded / e.total) * 100);
          that.setState({
            fileProgress: progress
          })
          if (progress === 100) {
            setTimeout(function () {
              that.setState({
                hiddenUploadBtn: true,
                showFileProgress: false
              })
            }, 30)
          }
        }
      }, false);  // 第三个参数为useCapture?,是否使用事件捕获/冒泡
      //监听readyState的变化,完成时回调后端返回的response
      xhr.addEventListener('readystatechange', (e) => {
        let response = e.currentTarget.response ? JSON.parse(e.currentTarget.response) : null;
        if (e.currentTarget.readyState === 4) {
          callback(response);
          xhr.upload.removeEventListener('progress', (event) => {
            if (event.lengthComputable) {
              let progress = Math.round((event.loaded / event.total) * 100);
              that.setState({
                fileProgress: progress
              })
              if (progress === 100) {
                that.setState({
                  showFileProgress: false
                })
              }
            }
          }, false)
        } else {
          console.log('upload loading ... ')
        }
      }, false);
      xhr.send(formData);

    }
  }

  /** -----weapp端的处理---- */
  //图片
  weappHandUpFiles() {
    let that = this
    const { maxCount, tempFile, recTypeId, recId, subRecTypeId, subRecId } = this.props
    const { rootUrl, token, locale, userLoginName } = this.getIncidentalInfo()
    let imagesSrcData = [] //在浏览器上显示缓存的图片
    let imagesUploadData = [] //确定按钮时获取的值
    Taro.chooseImage({
      count: maxCount,
      sizeType: ['original', 'compressed'], // 可以指定是原图还是压缩图,默认二者都有
      sourceType: ['album', 'camera'], // 可以指定来源是相册还是相机,默认二者都有
      success: (res) => {
        // 返回选定照片的本地文件路径列表,tempFilePath可以作为img标签的src属性显示图片
        let tempFilePaths = res.tempFilePaths
        for (let i = 0; i < tempFilePaths.length; i++) {
          if (i > maxCount) {
            return
          } else {
            let tempFileItem = tempFilePaths[i]
            imagesSrcData.push(tempFileItem)
            const uploadTask = Taro.uploadFile({
              url: rootUrl, //里面填写你的上传图片服务器API接口的路径 
              filePath: tempFileItem,//要上传文件资源的路径 String类型 
              name: 'gantIbom',//按个人情况修改,文件对应的 key,开发者在服务器端通过这个 key 可以获取到文件二进制内容,(后台接口规定的关于图片的请求参数)
              header: {
                "Content-Type": "multipart/form-data",//记得设置
                "userToken": token,
                "userLoginName": userLoginName,
                "userLanguage": locale,
              },
              formData: {
                //和服务器约定的token, 一般也可以放在header中
                'tempFile': tempFile,
                'recTypeId': recTypeId,
                'recId': recId,
                'subRecTypeId': subRecTypeId,
                'subRecId': subRecId,
              },
              success: (data) => {
                let result = JSON.parse(data.data)
                if (result.success) {

                  Taro.showToast({ title: `第${i + 1}张图片上传成功`, icon: 'success', duration: 2000 })
                  imagesUploadData.push(result.data)
                  if (i === tempFilePaths.length - 1) {
                    this.props.onUploadFile(imagesUploadData)
                  }
                } else {
                  Taro.showToast({ title: `第${i + 1}张图片上传失败`, icon: 'error', duration: 2000 })
                  return
                }
              },
              fail: (err) => {
                console.log(err)
              }
            })
            uploadTask.progress((progress) => {
              this.setState({
                fileProgress: progress.progress
              })
              if (progress.progress === 100) {
                that.setState({
                  hiddenUploadBtn: true
                })
                setTimeout(function () {
                  that.setState({
                    showFileProgress: false
                  })
                }, 600)
              }
            })
          }
        }
        this.setState({
          viewFilesData: imagesSrcData
        });

      },
      fail: (err) => {
        console.log(err)
      }
    })
  }
  //文件
  uploadMessageFile() {
    let that = this
    const { tempFile, recTypeId, recId, subRecTypeId, subRecId } = this.props
    const { rootUrl, token, locale, userLoginName } = this.getIncidentalInfo()
    let fileNamesData = []
    let fileUploadData = []
    Taro.chooseMessageFile({
      count: 1,
      type: 'file',
      success: (res) => {
        // 返回选定的本地文件路径列表
        let tempFiles = res.tempFiles
        for (let i = 0; i < tempFiles.length; i++) {
          if (!tempFiles.length) {
            return
          } else {
            let tempFileItem = tempFiles[i]
            let fileName = tempFileItem.name
            // let fileItemName = fileName.substring(0,fileName.lastIndexOf(".")) 
            fileNamesData.push(fileName)
            this.setState({
              showFileProgress: true
            })
            const uploadTask = Taro.uploadFile({
              url: rootUrl, //里面填写你的上传图片服务器API接口的路径 
              filePath: tempFileItem.path,//要上传文件资源的路径 String类型 
              name: 'gantIbom',//按个人情况修改,文件对应的 key,开发者在服务器端通过这个 key 可以获取到文件二进制内容,(后台接口规定的关于图片的请求参数)
              header: {
                "Content-Type": "multipart/form-data",//记得设置
                "userToken": token,
                "userLoginName": userLoginName,
                "userLanguage": locale,
              },
              formData: {
                //和服务器约定的token, 一般也可以放在header中
                'tempFile': tempFile,
                'recTypeId': recTypeId,
                'recId': recId,
                'subRecTypeId': subRecTypeId,
                'subRecId': subRecId,
              },
              success: (data) => {
                let result = JSON.parse(data.data)
                if (result.success) {
                  Taro.showToast({ title: `第${i + 1}个文件上传成功`, icon: 'success', duration: 2000 })
                  fileUploadData.push(result.data)
                  if (i === tempFiles.length) {
                    this.props.onUploadFile(fileUploadData)
                  }
                } else {
                  Taro.showToast({ title: `第${i + 1}个文件上传失败`, icon: 'error', duration: 2000 })
                  return
                }
              },
              fail: (err) => {
                console.log(err)
              }
            })
            uploadTask.progress((progress) => {
              this.setState({
                fileProgress: progress.progress
              })
              if (progress.progress === 100) {
                setTimeout(function () {
                  that.setState({
                    showFileProgress: false,
                    hiddenUploadBtn: true
                  })
                }, 30)
              }
            })
          }
        }
        this.setState({
          viewFilesData: fileNamesData
        });
      },
      fail: (err) => {
        console.log(`文件上传失败,查看错误信息:${err}`)
      }
    })
  }

  //文件上传缓存以后确定选中的文件上传服务器
  handConfirmFile() {
    this.props.onConfirom()
  }
  //删除文件
  handDeleteFile() {
    this.setState({
      hiddenUploadBtn: false,
      isRemovFile: true,
      viewFilesData: []
    }, () => {
      const { viewFilesData } = this.state
      this.props.onRemoveImage(viewFilesData)
    });
  }
  render() {
    const { uploadTypeIsImage, fileProgress, viewFilesData, showFileProgress, hiddenUploadBtn } = this.state
    console.log(hiddenUploadBtn, 'hiddenUploadBtn')
    const { uploadFileType } = this.props
    let showUploadFileView = null
    let uploadIcon = uploadTypeIsImage ? 'image' : 'upload'
    let uplpadIconSize = uploadTypeIsImage ? '40' : '18'
    let fileProgressClass = showFileProgress ? 'progress show-progress' : 'progress hidden-progress'
    showUploadFileView = (
      <View className='uploadfile-content'>
        {
          viewFilesData.map(fileVal => {
            return uploadTypeIsImage ? (
              <View className='img-item-warp'>
                <View className='img-warp'>
                  <Image className='img-item' src={fileVal} />
                </View>
                <View className='conforim-img' onClick={this.handConfirmFile.bind(this)}>确认</View>
                <View className='icon-btn-warp'>
                  <AtIcon className='icon-btn' onClick={this.handDeleteFile.bind(this)} value='close-circle' size='24' color='#6e6e6e' ></AtIcon>
                </View>
              </View>
            ) : (
                <View className='file-item-warp'>
                  <AtIcon className='icon-btn' value='file-generic' size='20' color='#336633'></AtIcon>
                  <Text className='uploadfile-name'>{fileVal}</Text>
                  <AtIcon className='close-icon-btn' onClick={this.handDeleteFile.bind(this)} value='close-circle' size='12' color='#6e6e6e'></AtIcon>
                </View>
              )
          })
        }
      </View>
    )
    return (
      <View className='uploadfile-components'>
        {showUploadFileView}
        <View className={hiddenUploadBtn ? 'btn-warp btn-warp-hidden' : 'btn-warp btn-warp-show'}>
          <View className={uploadTypeIsImage ? 'upload-img-btn' : 'upload-file-btn'} onClick={this.handChooesFile.bind(this)}>
            <AtIcon className='upload-icon' value={uploadIcon} size={uplpadIconSize} color='#fff'></AtIcon>
          </View>
        </View>
        <View className={fileProgressClass}>
          <AtProgress percent={fileProgress} />
        </View>
        <Input className='uploadfile-ipt' type='file' name='file' id='uploadInput' accept={uploadFileType} onChange={this.handSelectFiles.bind(this)} />
      </View>
    )
  }

}


.uploadfile-components{
    width:100%;
    // height: 100vh;
    position: relative;
    padding: 20px;
    .progress{
        width: 94%;
        height: 42px;
        position: absolute;
        left: 3%;
        top:21px;
        z-index: 88;
    }
    .show-progress{
       display: block;
    }
    .hidden-progress{
        display: none;
    }
    .uploadfile-content{
        .img-item-warp{
            width: 100%;
            height: 100%;
            position: relative;
            overflow: hidden;
            .img-warp{
                display: flex;
                flex-direction: column;
                justify-content: center;
                overflow: hidden;
                width: 100vw;
                height: 100vw;
                .img-item{
                    width: 100%;
                    height: auto;
                    min-height: 100vw;
                }
            }
            .conforim-img{
                width:100%;
                height: 68px;
                line-height: 68px;
                font-size: 22px;
                text-align: center;
                background-color: #336633;
                color: #fff;
                border-radius: 8px;
                margin-top: 20px;
            }
            .icon-btn-warp{
                width: 42px;
                height: 42px;
                line-height: 42px;
                position: absolute;
                right: 8px;
                top: 8px;
                .icon-btn{
                    width: 42px;
                    height: 42px;
                    line-height: 42px;
                }
            }

        }
        .file-item-warp{
            width: 100%;
            height: 42px;
            line-height: 42px;
            display: flex;
            flex-direction: row;
            justify-content: flex-start;
            .icon-btn{
                height: 42px;
                line-height: 42px;
            }
            .uploadfile-name{
                height: 42px;
                line-height: 42px;
                font-size: 20px;
                color: #336633;
                margin-left: 22px;
                margin-right: 130px;
            }
            .close-icon-btn{
                flex: 1;
                width: 42px;
                height: 42px;
                line-height: 42px;
                text-align: right;
                margin-right: 20px;                
            }
        }
    }
    .btn-warp{
        display: flex;
        flex-direction: row;
        justify-content: flex-start;
        flex-wrap: wrap;
        &.btn-warp-hidden{
            display: none;
        }
        &.btn-warp-show{
            display: block;
        }
        .upload-img-btn{
            width: 220px;
            height: 220px;
            line-height: 220px;
            text-align: center;
            position: relative;
            background-color: #336633;
            border-radius: 14px;
            overflow: hidden;
            .upload-icon{
                width: 220px;
                height: 220px;
                display: block;
                line-height: 220px;
                font-size: 48px;
                color: #fff;
                text-align: center;
                position: absolute;
                left: 0;
                top: 0;
            }
        }
        .upload-file-btn{
            width: 120px;
            height: 52px;
            line-height: 38px;
            text-align: center;
            position: relative;
            background-color: #336633;
            border-radius: 14px;
            overflow: hidden;
            .upload-icon{
                display: block;
                width: 120px;
                height: 52px;
                line-height: 52px;
                font-size: 22px;
                color: #fff;
                text-align: center;
                position: absolute;
                left: 0;
                top: 0;
            }
        }
    }
    .uploadfile-ipt{
        position: absolute;
        top: -1220px;
        left: 0;
    }
}

组件的使用

import Taro, { Component } from '@tarojs/taro'
import { View } from '@tarojs/components'
import { PageView, GtUploadFile } from '@components'
import Avatar from '../../../static/images/default_avatar.png'

export default class UploadFileExample extends Component {

  state = {
    tempFile:true,
    recTypeId:0,
    recId:0,
    subRecTypeId:0,
    subRecId:0,
    currentFiles:['零件库名称.xsl']
  }
  onGetUpload = (files) => {
    console.log(files, `父组件获得的value值`)
  }

  //删除的时图片ID设置
  onRemoveImage = (data) => {
    console.log(data)
  }


  //确定按钮点击
  onConfirom = (data) => {
    console.log(data)
  }

  render() {
    //tempFile ,recTypeId , recId ,subRecTypeId , subRecId业务模块区分所需的参数 组件里面默认的是用来上传头像的
    const { tempFile ,recTypeId , recId ,subRecTypeId , subRecId , currentFiles } = this.state
    console.log(currentFiles,'currentFiles')
    return (
      <PageView navTitle='UploadImage组件使用示例'>
        <View style='padding:10px 0;'>
          <View style='padding:10px'>上传文件</View>
          <GtUploadFile
            tempFile={tempFile}
            recTypeId={recTypeId} 
            recId={recId}
            subRecTypeId={subRecTypeId}
            subRecId={subRecId}
            uploadFileType='*'
            onUploadFile={this.onGetUpload} //上传按钮的回调函数
            onConfirom={this.onConfirom}  //确定按钮的回调函数
            onRemoveImage={this.onRemoveImage}  //删除按钮的回调函数
            existingFiles={currentFiles}  //默认的文件反显
          ></GtUploadFile>
        </View>
      </PageView>
    )
  }
}

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,214评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,307评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,543评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,221评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,224评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,007评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,313评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,956评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,441评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,925评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,018评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,685评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,234评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,240评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,464评论 1 261
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,467评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,762评论 2 345

推荐阅读更多精彩内容