08-首页

学习内容

  • 类目Icon功能
  • 底部猜你喜欢
  • 上拉加载更多
  • 下拉刷新
  • 尾部视图处理
  • 自定义顶部标签导航栏

根据网络数据结构,定义数据模型

//header icon 模型
export interface HeaderItemIconModel 
{
    title: string,
    coverPath: string,
    darkCoverPath: string,
    properties: HeaderItemIconUrlModel,
}
//header Icon 模型 的 properties 模型
export interface HeaderItemIconUrlModel
{
    uri: string,
}

类目Icon组件封装

  • src/pages/Home/ 创建 icon.tsx
interface IProps {
    iconList: HeaderItemIconModel[],
}
class Icon extends React.Component<IProps> {
    renderItem = ({ item }: { item: HeaderItemIconModel }, parallaxProps?: AdditionalParallaxProps) => {
        return (
            <View style={styles.item}>
                <Image source={{uri: item.coverPath}} style={styles.itemImage}/>
                <Text style={styles.itemTitle} numberOfLines={1}>
                    {item.title}
                </Text>
            </View>
        );
    }
    render() {
        const { iconList } = this.props;
        if (iconList != null && iconList.length > 0) {
            return (
                <View style={styles.container}>
                    {/* <Text>{JSON.stringify(iconList)}</Text> */}
                    <FlatList
                        data={iconList}
                        renderItem={this.renderItem}
                        numColumns={5}
                    />
                </View>
            );
        }
        else {
            return null;
        }
    }
}

const styles = StyleSheet.create({
    container: {
        // backgroundColor: '#fff',
        borderRadius: 8,
        marginTop: 5,
        margin: 16,
    },
    item: {
        flex : 1,
        marginHorizontal: 5,
        marginVertical: 6,
    },
    itemImage: {
        width: '100%',
        height: 70,
        borderRadius: 50,
        marginBottom: 5,
    },
    itemTitle: {
        textAlign: 'center',
        fontSize: 14,
        color: '#333333',
        marginBottom: 5,
    }
});
export default Icon;
  • src/pages/Home/Home.tsx 调用
render() {
        ...
        var icons: HeaderItemIconModel[] = [];
        header.forEach(element => {
            ...
            else if (element.item.moduleType == 'square' && element.item.list.length > 0) {
                //广告Icon
                icons = element.item.list;
            }
        });
        return (
            <View>
                ...
                <Icon iconList={icons}/>
            </View>
        );
}

ps: 当然样式上实现了,但是点击事件都没有做

  • 封装点击事件组件,在src/components/Touchable.tsx
import React from "react";
import { TouchableOpacity, TouchableOpacityProps } from "react-native";
const Touchable: React.FC<TouchableOpacityProps>= (props) => (
    <TouchableOpacity activeOpacity={0.8} {...props} />
)
export default Touchable;
  • 重新封装类目Icon的 item
return (
    <Touchable
        style={styles.item}
        onPress={() => {
              Alert.alert(item.properties.uri);
        }}
    >
         ...
    </Touchable>
);

底部猜你喜欢模块

  • 封装猜你喜欢模块
    src/pages/Home/Guess.tsx
interface IProps {
    data: HomeBodyItemModel;
}

class GuessItem extends React.Component<IProps> {
    render() {
        const { data } = this.props;
        // console.log("_________:", data.intro);
        return (
            <Touchable
                style={styles.container}
                onPress={() => {
                    Alert.alert(data.title);
                }}
            >
                <Image source={{ uri: data.coverPath }} style={styles.image} />
                <View>
                    <Text style={styles.title}>{data.title}</Text>
                    <Text style={styles.subTitle} numberOfLines={1}>{data.intro}</Text>
                    <View style={styles.playContainer}>
                        <IconBofang2 color='#999999' />
                        <Text style={styles.playNum}>{numberChange(data.playsCounts)}</Text>
                    </View>
                </View>
                <IconClose color='#999999' style={styles.close} />
            </Touchable>
        );
    }
}

const styles = StyleSheet.create({
    container: {
        flexDirection: 'row',
        marginLeft: 15,
        marginRight: 15,
        marginBottom: 10,
        // backgroundColor: '#ffffff',
    },
    image: {
        width: 80,
        height: 80,
        borderRadius: 12,
        backgroundColor: '#dedede',
    },
    title: {
        marginTop: 10,
        marginLeft: 10,
        fontSize: 16,
        fontWeight: 'bold',
    },
    subTitle: {
        marginLeft: 10,
        marginTop: 10,
        marginRight: 5,
        width: viewPortWidth - 165,
    },
    playContainer: {
        flexDirection: 'row',
        marginLeft: 6,
        // backgroundColor: 'red',
        marginTop: 10,
    },
    playNum: {
        marginLeft: 2,
        color: '#999999',
    },
    close: {
        marginTop: 10,
    }
});
export default GuessItem;
  • 调用封装的猜你喜欢模块
    src/pages/Home/Home.tsx
renderItem = ({ item }: { item: HomeBodyModel }, parallaxProps?: AdditionalParallaxProps) => {
        return (
            <GuessItem data={item.item} />
        );
}

全屏滚动

  • 组件抽取
  get header() {
        const { header, body } = this.props;
        var banner: HeaerItemBannerModel[] = [];
        var icons: HeaderItemIconModel[] = [];

        header.forEach(element => {

            if (element.item.moduleType == 'focus' && element.item.list.length > 0) {
                //banner广告
                element.item.list.forEach((ele: HeaerItemBanner) => {
                    banner = ele.data;
                });
            }
            else if (element.item.moduleType == 'square' && element.item.list.length > 0) {
                //广告Icon
                icons = element.item.list;
            }

        });

        return (
            <View>
                <Banner banner={banner} />
                <Icon iconList={icons} />
                {/* <GuessTitle guessData={body} /> */}
            </View>
        );
    }

    get footer() {
        return (
            <View>
                <Text>加载中...</Text>
            </View>
        );
    }

    get empty() {
        return (
            <View>
                <Text style={styles.textStyle}>正在加载中...</Text>
            </View>
        );
    }

  • 将 猜你喜欢前面的模块整合到FlatList组件的头部
        return (

            <FlatList
                ListHeaderComponent={this.header}
                ListFooterComponent={this.footer}
                numColumns={1}
                ListEmptyComponent={this.empty}

                data={body} renderItem={this.renderItem}
            >

            </FlatList>
        );
image.png

上拉加载更多

修改src/pages/Home/Home.tsx

  • FlatList设置属性onEndReached
onEndReached={this.onEndReached}
onEndReachedThreshold={0.2}  //比例:距离内容底部多远开始掉接口
  • 加载更多方法实现
//加载更多
    onEndReached = () => {
        console.log("加载更多");
        const { dispatch, loading, hasMore } = this.props
        if (loading || !hasMore) return;
        dispatch({
            type: "home/fetchGuess",
            payload: {
                loadMore: true,
            },
        });
    }

修改src/models/http.ts,添加接口调用

      *fetchGuess({ callback, payload }, { call, put, select }) {
            const home = yield select((state: RootState) => state.home);

            let page = 1;
            if (payload && payload.loadMore) {
                page = home.pagenation.current + 1;
            }

            const { data, pagenation } = yield call(axios.get, GUESS_PAGE_URL, {
                params: {
                    page,
                }
            });

            let newData = data;
            if (payload && payload.loadMore) {
                newData = home.body.concat(newData);
            }
            console.log("___________数据个数:", newData.length);
            yield put({
                type: 'setState',
                payload: {
                    body: newData,
                    pagenation: {
                        current: pagenation.current,
                        total: pagenation.total,
                        hasMore: newData.length < pagenation.total,
                    }
                }
            });
            if (typeof callback == 'function') {
                callback();
            }
        },
    },

下拉刷新

修改src/pages/Home/Home.tsx

  • FlatList设置属性onRefresh 和 refreshing

interface IState {
    refreshing: boolean;
}

/**
 * 首页类
 */
class Home extends React.Component<IProps, IState> {

    state = {
        refreshing: false,
    };
    ...
    //下拉加载更多
    onRefresh = () => {
        console.log("下拉刷新");
        //1.修改刷新状态为 true
        this.setState({
            refreshing: true,
        });

        //2.获取数据
        const { dispatch } = this.props;
        dispatch({
            type: "home/fetchHome",
            //3.修改刷新状态为false
            callback: () => {
                this.setState({
                    refreshing: false,
                })
            }
        });
    }
    ...
   
    render() {
        const { body } = this.props;
        const { refreshing } = this.state;
        return (
            <FlatList
               ...
                onRefresh={this.onRefresh}
                refreshing={refreshing}
                ...
            >
            </FlatList>
        );
    }
}

底部视图处理

  • 处理加载更多 和 到底了
    get footer() {
        const { body, hasMore, loading } = this.props;
        if (!hasMore) {
            return (
                <View style={styles.footerView}>
                    <Text style={styles.footerStyle}>--- 我是有底线的 ---</Text>
                </View>
            );
        }
        if (loading && hasMore && body.length > 0) {
            return (
                <View style={styles.footerView}>
                    <Text style={styles.footerLoadingStyle}>正在加载中...</Text>
                </View>
            );
        }
    }

赋值到 FlatList 的 ListFooterComponent

导航栏顶部自定义标签

首页沉浸式头部

  • 将头部导航隐藏,修改BottomTabs.tsx

        componentDidMount() {
            this.setOptions();
        }
    
        componentDidUpdate() {
            this.setOptions();
        }
    
        setOptions = () => {
            const { navigation, route } = this.props;
    
            //使用route.state的时候:官方提示用getFocusedRouteNameFromRoute
            const routeName = getFocusedRouteNameFromRoute(route) ?? 'HomeTabs';
    
            if (routeName === 'HomeTabs') {
                navigation.setOptions({
                    //头部透明
                    headerTransparent: true,
                    headerTitle: '',
                });
            }
            else {
                navigation.setOptions({
                    //展示头部样式
                    headerTransparent: false,
                    headerTitle: getHeaderTitle(routeName),
                });
            }
        }
    

封装顶部top导航栏

  • 新增pages/views/TopTabBarWrapper.tsx

    import { MaterialTopTabBar, MaterialTopTabBarProps } from "@react-navigation/material-top-tabs";
    import React from "react";
    import { View } from "react-native";
    
    interface IProps extends MaterialTopTabBarProps {
    }
    
    class TopTabBarWrapper extends React.Component<IProps> {
        render() {
            const { props } = this;
            return (
                <View>
                    <MaterialTopTabBar {...props} />
                </View>
            );
        }
    }
    
    export default TopTabBarWrapper;
    
  • iPhone X 之后的 全面屏 状态栏高度 计算插件 react-native-iphone-x-helper堆栈式导航器依赖了这个插件。查看node_modules里面是否已经依赖。

    • 如果未依赖,就安装一下

      yarn add react-native-iphone-x-helper
      
    • 如果依赖,直接使用

      //...
      import { getStatusBarHeight } from 'react-native-iphone-x-helper';
      
      class TopTabBarWrapper extends React.Component<IProps> {
          render() {
              //...
              return (
                  <View style={styles.container}>
                     //...
                  </View>
              );
          }
      }
      
      const styles = StyleSheet.create({
          container: {
              back
              paddingTop: getStatusBarHeight(),
          }
      });
      
      export default TopTabBarWrapper;
      

导航栏布局

  • 背景色修改 6.x 版本导航栏背景色修改放到了screenOptionsMaterialTopTabBar没有更多的属性设置

    //修改导航栏上的背景色
    tabBarStyle: {
      backgroundColor: 'transparent',
    },
    
  • 分类,搜索框(语音,搜索按钮),历史记录

    class TopTabBarWrapper extends React.Component<IProps> {
        render() {
            const { props } = this;
            return (
                <View style={styles.container}>
                    <View style={styles.topTabBarView}>
                        <View style={styles.tabbar}>
                            <MaterialTopTabBar {...props} />
                        </View>
                        <Touchable style={styles.categoryBtn}>
                            <IconFenlei color='#ff6600' style={styles.categoryImage} />
                        </Touchable>
                    </View>
                    <View style={styles.bottom}>
                        <Touchable style={styles.searchBtn}>
                            <Text style={styles.searchText}>搜索按钮</Text>
                            <Touchable style={styles.yuyinBtn}>
                                <IconYuyin color='#555555'/>
                            </Touchable>
                            <View style={styles.line}></View>
                            <Touchable style={styles.searchIconBtn}>
                                <IconSousuo color='#ff5500'/>
                            </Touchable>
                        </Touchable>
                        <Touchable style={styles.historyBtn}>
                            <IconLishi color='#999999' />
                        </Touchable>
                    </View>
                </View>
            );
        }
    }
    

导航栏背景渐变

  • 安装插件

    yarn add react-native-linear-gradient
    
    # 需要原生支持
    ./pod.sh
    
  • 渐变色动画组件

    yarn add react-native-linear-animated-gradient-transition
    
  • 添加全局状态属性

    activeBannerIndex: number,  //当前轮播图的下标
    gradientVisible: boolean, // 渐变色组件是否显示的状态
    
  • 改造src/navigator/HomeTabs.tsx拿到 渐变组件的显示状态,来修改导航栏的样式

    import React from "react";
    import { createMaterialTopTabNavigator, MaterialTopTabBarProps } from "@react-navigation/material-top-tabs";
    import Home from "@/pages/Home/Home";
    import TopTabBarWrapper from "@/pages/views/TopTabBarWrapper";
    import { StyleSheet } from "react-native";
    import { RootState } from "../models";
    import { connect, ConnectedProps } from "react-redux";
    
    //声明变量,接收函数返回值
    const Tab = createMaterialTopTabNavigator();
    
    const mapStateToProps = ({home}: RootState) => {
        return {
            gradientVisible: home.gradientVisible,
        };
    };
      
    const connector = connect(mapStateToProps);
      
    type ModelState = ConnectedProps<typeof connector>;
      
    interface IProps extends ModelState {}
    
    class HomeTabs extends React.Component<IProps> {
    
        renderTabBar = (props: MaterialTopTabBarProps) => {
            return (
                <TopTabBarWrapper {...props} />
            );
        }
    
        render() {
            const { gradientVisible } = this.props;
            return (
                // {/* 'tabBarOptions' is deprecated. Migrate the options to 'screenOptions' instead. */}
                <Tab.Navigator
                    tabBar={this.renderTabBar}
                    screenOptions={{
                        //...
                        //tab底部横条样式
                        tabBarIndicatorStyle: {
                              //...
                            backgroundColor: gradientVisible ? '#fff' : '#f86442',
                        },
                        //...
                        tabBarActiveTintColor: gradientVisible ? '#fff' : '#f86442',
                    }}
                >
                //...
                </Tab.Navigator>
            );
        }
    }
    
    const styles = StyleSheet.create({
        sceneContainer: {
            backgroundColor: 'transparent',
        }
    });
    export default connector(HomeTabs);
    
  • 改造 banner广告组件src/pages/Home/Banner.tsx,更新banner轮播的当前索引值

    import { RootState } from "@/models/index";
    import { HeaerItemBannerModel } from "@/models/home";
    import { hp, viewPortWidth, wp } from "@/utils/index";
    import React from "react";
    import { Platform, StyleSheet, View } from "react-native";
    import SnapCarousel, { AdditionalParallaxProps, Pagination, ParallaxImage } from "react-native-snap-carousel";
    import { connect, ConnectedProps } from "react-redux";
    
    //...
    
    const mapStateToProps = ({ home }: RootState) => {
        return {
            activeBannerIndex: home.activeBannerIndex,
        };
    };
    
    const connector = connect(mapStateToProps);
    
    type ModelState = ConnectedProps<typeof connector>;
    
    interface IProps extends ModelState {
        banner: HeaerItemBannerModel[],
    }
    
    
    class Banner extends React.Component<IProps> {
          //更新 banner 轮播的索引值
        onSnapToItem = (index: number) => {
            const { dispatch } = this.props;
            dispatch({
                type: 'home/setState',
                payload: {
                    activeBannerIndex: index,
                },
            });
        }
          //...
        render() {
            //...
        }
    }
    
    // 定义样式
    const styles = StyleSheet.create({
        //...
    })
    
    export default connector(Banner);
    
  • 改造首页/src/pages/Home/Home.tsx, 监听FlatList的 onScroll,来计算出 FlatList滚动的偏移量,来计算是否需要展示渐变图层

    //...
    
    //拿到 models 中 home的 num 值
    const mapStateToProps = ({ home, loading }: RootState) => ({
        header: home.header,
        body: home.body,
        hasMore: home.pagenation.hasMore,
        loading: loading.effects['home/fetchGuess'],
        gradientVisible: home.gradientVisible, //渐变色图层显示状态
    });
    
    //获取到函数
    const connector = connect(mapStateToProps);
    
    type MadelState = ConnectedProps<typeof connector>;
    
    interface IProps extends MadelState {
        navigation: RootStackNavigation;
    }
    
    interface IState {
        refreshing: boolean;
    }
    
    /**
     * 首页类
     */
    class Home extends React.Component<IProps, IState> {
          //...
        onScroll = ({ nativeEvent }: NativeSyntheticEvent<NativeScrollEvent>) => {
            const offSetY = nativeEvent.contentOffset.y;
            // console.log("___________offSetY:", offSetY);
            // console.log("___________sideWidth:", sideHeight);
            let newGradientVisible = offSetY < sideHeight;
    
            const {dispatch, gradientVisible} = this.props;
            
            if (gradientVisible !== newGradientVisible) {
                dispatch({
                    type: "home/setState",
                    payload: {
                        gradientVisible: newGradientVisible,
                    },
                });
            }
        }
          //...
        render() {
            return (
    
                <FlatList
                    //...
                    onScroll={this.onScroll}
                >
                </FlatList>
            );
        }
    }
    
    const styles = StyleSheet.create({
        //...
    });
    
    // 导出首页类
    export default connector(Home);
    
  • 顶部自定义标签组件的优化/src/pages/views/TopTabBarWrapper.tsx,

    • 根据banner轮播的索引值获取渐变色数组,返回LinearAnimatedGradientTransition图层
    • 根据是否展示渐变色图层,修改顶部自定义标签子控件的眼神
    //...
    const mapStateToProps = ({ home }: RootState) => {
        return ({
              //索引值
            activeBannerIndex: home.activeBannerIndex,
              //渐变层是否展示的状态
            gradientVisible: home.gradientVisible,
            header: home.header,
        });
    }
    
    const connector = connect(mapStateToProps);
    
    type ModelState = ConnectedProps<typeof connector>;
    
    interface IProps extends MaterialTopTabBarProps, ModelState {
    }
    
    class TopTabBarWrapper extends React.Component<IProps> {
    
        get linearGradient() {
    
            const { activeBannerIndex, gradientVisible, header } = this.props;
    
            var banner: HeaerItemBannerModel[] = [];
    
            header.forEach(element => {
                if (element.item.moduleType == 'focus' && element.item.list.length > 0) {
                    //banner广告
                    element.item.list.forEach((ele: HeaerItemBanner) => {
                        banner = ele.data;
                    });
                }
            });
                  //根据banner轮播的索引值获取渐变色数组,返回LinearAnimatedGradientTransition图层
            var linearColors = banner[activeBannerIndex] ? banner[activeBannerIndex].colors : ['#fff', '#ff5500'];
    
            if (gradientVisible) {
                // console.log("_______:",activeBannerIndex);
                // console.log("________:来了",linearColors); 
                return (
                    <LinearAnimatedGradientTransition
                        colors={linearColors}
                        style={styles.grandient}
                    />
                );
            }
            return null;
        }
    
        render() {
            const { gradientVisible, ...restProps } = this.props;
            let textStyle = styles.searchText;
            let lineStyle = styles.line;
            let yuyinColor = '#555';
            let historyColor = '#999';
            let searchColor = '#ff5500';
            let categoryColor = searchColor;
              //根据是否展示渐变色图层,修改顶部自定义标签子控件的眼神
            if (gradientVisible) {
                textStyle = styles.whiteText;
                yuyinColor = historyColor = searchColor = categoryColor = '#fff';
                lineStyle = styles.whiteLine;
            }
            return (
                <View style={styles.container}>
                    {this.linearGradient}
                    <View style={styles.topTabBarView}>
                        <View style={styles.tabbar}>
                            <MaterialTopTabBar {...restProps} />
                        </View>
                        <Touchable style={styles.categoryBtn}>
                            <IconFenlei color={categoryColor} style={styles.categoryImage} />
                        </Touchable>
                    </View>
                    <View style={styles.bottom}>
                        <Touchable style={styles.searchBtn}>
                            <Text style={textStyle}>搜索按钮</Text>
                            <Touchable style={styles.yuyinBtn}>
                                <IconYuyin color={yuyinColor} />
                            </Touchable>
                            <View style={lineStyle}></View>
                            <Touchable style={styles.searchIconBtn}>
                                <IconSousuo color={searchColor} />
                            </Touchable>
                        </Touchable>
                        <Touchable style={styles.historyBtn}>
                            <IconLishi color={historyColor} />
                        </Touchable>
                    </View>
                </View>
            );
        }
    }
    
    const styles = StyleSheet.create({
        //...
    });
    
    export default connector(TopTabBarWrapper);
    

ps:一步一个脚印👣,up~

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

推荐阅读更多精彩内容