raodaor-app
Third App Store
Document
https://reactnative.dev/
Getting Started
Upgrading
- recommend
npx expo install [package name]
have dependencie with auto version check. yarn add expo@46.0.0
- expo-cli 6.0.2
- build:ios [path] Superseded by eas build in eas-cli
- build:android [path] Superseded by eas build in eas-cli
- build:status [path] Superseded by eas build:list in eas-cli
- eject [path] Superseded by expo prebuild
- upload:android [path] Superseded by eas submit in eas-cli
- upload:ios [path] Superseded by eas submit in eas-cli
- client:ios [path] Superseded by Expo Dev Clients
yarn add react-native@0.69.4
http://file.camnpr.com/expo/react-native-sdk-46.0.0.tar.gz
yarn add http://file.camnpr.com/expo/react-native-sdk-45.0.0.tar.gz
yarn add https://github.com/expo/react-native/archive/sdk-45.0.0.tar.gz
yarn add react-native-web@0.18.7
- use
yarn
command
expo sdk versions are locked to a specific react-native version. you can't update the react-native version until we release a new sdk version that supports it. if you'd like to use hooks right now, you will need to run npm i -g react-native-cli and then react-native init.
bug review
npm run start
yarn add lark-cms@0.3.5-rn --registry http://npm.camnpr.com:4873/
Runs the app in development mode.
Open http://localhost:19002/ to view it in the browser.
The page will reload if you make edits.
Press w to browser vist.
npm run app
Builds the app for production to the build
folder.
start of expo 46 and expo-cli, managed is eas to build app.
browser Web
npm start
and Press w
APPEntry > package.json > main > index.js
(customize) OR node_modules/expo/AppEntry.js
(default)
npm run web:build
make web-build
folder. by use view demo.
eg: config website to web-build
folder before view http://localhost:2021/index.html
expo customize:web
change web index.html
of template.
data source
SaasPlatform background make public/data.js
file.
keystore
super-app-package-raodaor.keystore
password ? skyboxgogo123- Keystore alias > superAppRaodaor
usage: new keystore
Upgrading Expo SDK
🚨 With Expo SDK 46, we are migrating to a new suite of tooling that is versioned with the expo package. Use npx create-expo-app to initialize a new Expo project instead of expo init.
schema
首页 raodaor://homeDefault?key=3fo8nH32gJhRHt4EhNVH2G
知识乐园 raodaor://homeDefault?key=21QWxtWtq1kQHKzAgKnnuo
有话说 raodaor://homeDefault?key=iLebArvMiSLWrtfDQMFt9e
广告点点 raodaor://homeDefault?key=rRQhAHpv89Bhs3sYUNkGUp
私活有道 raodaor://homeDefault?key=pbKaFLs6DAwjEMHCBQQohi
定位地址 raodaor://addressDefault?key=xxx
拍照/录像 raodaor://cameraDefault?key=xxx
扫一扫 raodaor://cameraScanner?key=xxx
分类列表 raodaor://categoryDefault?key=xxx
购物车 raodaor://cartDefault?key=xxx
消息中心 raodaor://imList?key=xxx
聊天会话 raodaor://imChat?
文章列表 raodaor://articlesList?key=xxx
文章详情 raodaor://detailsArticle?key=xxx
产品列表 raodaor://productList?key=xxx
产品详情 raodaor://detailsProduct?key=xxx
发布文章 raodaor://publishArticle?key=xxx
发布产品 raodaor://publishProduct?key=xxx
浏览器 raodaor://webViewDefault?key=xxx
我的 raodaor://myDefault?key=xxx
设置 raodaor://mySetting?key=xxx
登录 raodaor://myLogin?key=xxx
用户中心 raodaor://myInfos?key=xxx
注册 raodaor://myRegister?key=xxx
纯净CMS页 raodaor://toolPureCmsView?key=xxx
热榜 raodaor://toolHotList?key=xxx
口算题 raodaor://toolVerbalexercise?key=xxx
404 raodaor://toolStatusCode404?key=xxx
全部订单 raodaor://listOrder?tab=4&key=xxx // 待付款、待收货、退换/售后、全部订单
附近好店 raodaor://activityNearStore?key=xxx
PLUS会员 raodaor://activityPlusVip?key=xxx
签到有礼 raodaor://activitySign?key=xxx
领券中心 raodaor://activityCouponCenter?key=xxx
table of contents
- src/pages
- home
首页
- default.tsx
默认页
- speed.tsx
极速版
- address
地址
- camera
照相机
- default.tsx
拍照/录像
- scanner.tsx
扫一扫
- im
消息
- list.tsx
消息列表页
- chat.tsx
消息会话页
- my
我的
- default.tsx
默认页
- login.tsx
登录页
- register.tsx
注册页
- infos.tsx
用户中心页
- setting.tsx
设置页
- cart
购物车
- category
分类
- list
列表页
- article.tsx
文章列表页
- product.tsx
产品列表页
- details
详情页
- article.tsx
文章详情页
- product.tsx
产品详情页
- publish
发布信息
- article.tsx
发布文章
- product.tsx
发布产品
- webview
APP内浏览器
document
NativeAPI.md
notice
FlatList
、SectionList
、VirtualizedList
、FlashList
当用List相关的组件时,为了性能,列表中当前不可见的元素,其实是获取不到它们的位置信息的(未渲染)
// simulate code
<FlatList>
// Single Item Save Position
<View onLayout={(event) => {
var {x, y, width, height} = event.nativeEvent.layout;
refLarkCmsObj.scrollTo({ x: x, y: y, animated: false });
// Error: y equal 0
console.log("View onLayout===>", event)
}}></View>
</FlatList>
// other remark
<VirtualizedList
ref={setRefCityList}
initialNumToRender={1}
onScrollToIndexFailed={null}
initialScrollIndex={9}
getItemLayout={(data, index) => {}}
keyExtractor={item => item}
renderItem={({item}) => renderItem(item)}
getItemCount={data => dataList.length}
getItem={renderGetItem}
data={dataList}
// onEndReached={loadMoreItems}
// onEndReachedThreshold={0.5} // 距离底部
/>
- name: 作为APP的名字,它将出现在 Expo Go 和 你手机屏幕上的 独立APP 的名字。
- slug: 用于发布时友好的APP名字。比如:配置:myAppName 将会添加到线上平台:expo.io/@project-owner/myAppName 项目上。
- (enum) - Defaults to unlisted. unlisted hides the project from search results. hidden restricts access to the project page to only the owner and other users that have been granted access. Valid values: public, unlisted, hidden.
- splash image size: 1242 x 2436 https://docs.expo.dev/tutorial/configuration/
Component Intro
- StatusBar: 定义状态栏,比如:顶部的带信号的状态栏。 可以通过: hidden={true} 来隐藏顶部状态栏。
backgroundColor="#f70f0f" 设置背景色。
- Expo 不能使用文件上传组件:
@types/rn-fetch-blob
Does rn-fetch-blob work with Expo? react-navigation
给每个页面 (Screen
) 传入了两个对象,可以通过 this.props.route
、this.props.navigation
调用。
expo publish
发布后,APP会自动更新(也可自己搭建更新服务器)
- TabView 组件 增加:
orientation = "horizontal" or "vertical"
方向配置。 - TODO:App的状态,前台运行 还是 后台运行 (备份代码)
import {
AppState,
} from "react-native";
// APP的准备状态
const [appIsReady, setAppIsReady] = useState(false);
// App的状态,前台运行 还是 后台运行
const appState = useRef(AppState.currentState);
const [appStateVisible, setAppStateVisible] = useState(appState.current);
/* App状态监测(可以告诉你应用程序是在前台还是后台,并在状态改变时通知你)*/
useEffect(() => {
// 【相当于】 componentDidMount() 组件第一次渲染完成, 【也相当于】componentDidUpdate()
AppState.addEventListener("change", _handleAppStateChange);
// 【相当于】componentWillUnmount() 在此处完成组件的卸载和数据的销毁
return () => {
AppState.removeEventListener("change", _handleAppStateChange);
}
}, [])// 传入第二个参数,表示,只在componentDidMount生命周期里执行,否则,setState也会触发 componentDidUpdate 造成死循环执行。
/* App 状态发生变化 */
const _handleAppStateChange = (nextAppState: any) => {
if (appState.current.match(/inactive|background/) && nextAppState === 'active') {
console.log('APP当前状态====> App has come to the foreground!');
}
appState.current = nextAppState;
setAppStateVisible(appState.current);
console.log('APP当前状态====> AppState', appState.current);
}
/*
* @Author: yinhongwei
* @Date: 2021-01-25 15:11:29
* @Last Modified by: yinhongwei
* @Last Modified time: 2022-08-21 16:34:09
* @Description: 文章详情页面 【参数:{title: "标题", itemId: 文章ID, }】
*/
import React, { useRef } from "react";
// import Constants from "expo-constants";
import { ActionSheetProvider, connectActionSheet, /* ActionSheetProps, */ } from "@expo/react-native-action-sheet";
import Toast from "react-native-toast-message";
import { StatusBar } from "expo-status-bar";
import { Audio } from "expo-av";
// import RNFetchBlob from "rn-fetch-blob";
import {
SafeAreaView,
View,
ScrollView,
RefreshControl,
FlatList,
StyleSheet,
TouchableOpacity,
Pressable,
Dimensions,
Animated,
PanResponder,
// Modal,
Text,
ImageBackground,
Image,
} from "react-native";
// 接口相关的
import * as PublicConstant from "../../utils/constant";
import { formatDataShort, extractResourceInfo, millisToMinutesAndSeconds } from "../../utils/common";
import { InterfaceRequest, FileFetchRequest } from "../../utils/api";
import ComponentsPageStatus from "../../component/pageStatus";
// 定义一个接口,目的是为后面的state提供类型,以便通过编译器的检查
interface stateDefined {
refreshing: boolean, // 刷新状态
pageStatus: string, // 页面状态 init(页面骨架图),loading,nodata,error
pageStatusText: string, // 页面状态提示语
articleData: any, // 文章详情内容
lastAudioFile: string, // 最新录制的音频文件地址
sound: any, // 音频播放对象
// soundInitStatus: any, // 音频状态
soundPositionMillis: number, // 音频时长的位置(毫秒)[收到变更为倒计时形式]
soundItemFlag: number, // 当前播放视频的flag标记
soundProgressMonitor: any, // 音频监听播放进度
recording: any, // 录音对象
recordingUri: string, // 录音后的地址文件
commentModal: boolean, // 评论列表浮层
CommentRefreshing: boolean, // 评论刷新状态
commentListData: Array<any>, // 评论列表数据
commentDataCount: number, // 总数据量
commentPageNumber: number, // 页码数
commentPageSize: number, // 每页个数
pageData: Array<any>, // 页面的配置数据
};
// type Props = ActionSheetProps;
// 这里的any用来定义props的类型,stateDefined接口用来定义this.state的类型
class DetailsArticle extends React.Component<any, stateDefined> {
// 定义拖动对象XY
public panObject = new Animated.ValueXY(); // useRef(new Animated.ValueXY()).current;
public panResponder = PanResponder.create({
onStartShouldSetPanResponder: () => true,
onPanResponderMove: Animated.event([
null,
{
dx: this.panObject.x,
dy: this.panObject.y,
}
]),
onPanResponderRelease: () => {
// Animated.spring(
// this.panObject,
// { toValue: 0 }
// ).start();
}
})
constructor(props:any) {
super(props)
this.state = {
refreshing: false,
// 页面状态
pageStatus: "",
pageStatusText: "",
articleData: {},
lastAudioFile: "",
sound: undefined,
// soundInitStatus: undefined,
soundPositionMillis: 0,
soundItemFlag: 0,
soundProgressMonitor: undefined,
recording: undefined,
recordingUri: "",
commentModal: false,
CommentRefreshing: false,
commentListData: [],
commentDataCount: 0,
commentPageNumber: 0, // 默认从0开始
commentPageSize: 10,
pageData: [],
}
}
/**
* 设置导航的标题
* @param {string} title 标题
* @param {string} message 分享的内容
*/
onSetPageTitle = (title: string, message: string) => {
this.props.navigation && this.props.navigation.setOptions({
title: title || "文章详情",
headerRight: () => (
<View style={{flexDirection: "row"}}>
<TouchableOpacity
onPress={ (route)=> {PublicConstant.EnvConstant.onChat(this.props.navigation, `✅【${title}】\r\n📝${message}`)} }>
<Image
style={{width: 30, height: 30}}
source={require("../../../assets/nav-chat.png")}
/>
</TouchableOpacity>
<TouchableOpacity
onPress={ (route)=> {PublicConstant.EnvConstant.onShare(title, `✅【${title}】\r\n📝${message}\r\n⬆️⬆️⬆️✏️${PublicConstant.defaultAppInfo.appName}`)} }>
<Image
style={{width: 30, height: 30, marginLeft: 10, marginRight: 15}}
source={require("../../../assets/nav-share.png")}
/>
</TouchableOpacity>
</View>
),
})
}
/* 初始化 */
UNSAFE_componentWillMount() {
this.setState({
pageStatus: "loading",
pageStatusText: "正在努力加载中..."
});
this.onFetchData(null)
}
/* 卸载处理 */
componentWillUnmount() {
const { sound, recording } = this.state;
if (sound) {
this.onStopSound()
}
if (recording) {
this.onStopRecording()
}
}
/* 获取文章内容 */
onFetchData = (callback: any) => {
let { params } = this.props.route || {};
// TODO: test
// params = params || {"title": "相思", "itemId": 2};
// this.onSetPageTitle(params && params["title"]);
let _this = this;
if (params && params["itemId"]) {
// 获取文章的详情
InterfaceRequest(
PublicConstant.EnvConstant.articles_detail,
{
body: {
id: params.itemId,
userKey: PublicConstant.EnvConstant.appUserInfo.key,
}
},
"POST",
(data: any)=>{
if (data.code === 200) {
_this.setState({
articleData: data.data
}, ()=>{
_this.setState({
pageStatus: "",
pageStatusText: ""
})
})
_this.onSetPageTitle(data.data.title, data.data.desc);
} else {
_this.setState({
pageStatus: "error",
pageStatusText: data.message || "服务异常,小主,请稍后~"
})
// Alert.alert(data.message || "服务异常,小主,请稍后~")
}
callback && callback();
},
(error: any)=>{
_this.setState({
pageStatus: "error",
pageStatusText: "服务太忙了,小主,请稍后"
})
callback && callback();
})
} else {
_this.setState({
pageStatus: "error",
pageStatusText: "没有指定内容,无法查询呢,请返回重试!"
})
}
}
/* 处理刷新 */
onRefresh = () => {
let _this = this;
this.setState({
refreshing: true,
pageStatus: "",
pageStatusText: ""
})
this.onFetchData(()=>{
_this.setState({
refreshing: false
})
})
};
/* 开始录音 */
onStartRecording = async () => {
// 登录检测
let isLogin = await PublicConstant.EnvConstant.checkLoginFunc(this.props.navigation);
if (isLogin) {
try {
// console.log("请求录音权限..");
await Audio.requestPermissionsAsync();
await Audio.setAudioModeAsync({
allowsRecordingIOS: true,
playsInSilentModeIOS: true,
});
// console.log("开始录音..");
const { recording } = await Audio.Recording.createAsync(
Audio.RECORDING_OPTIONS_PRESET_HIGH_QUALITY
);
this.setState({
recording: recording
})
// console.log("录音已经开始");
Toast.show({ type: "info", text1: "录音已经开始,请说话哟!", props: {width:300} });
} catch (err) {
// console.error("开始录音时失败", err);
Toast.show({ type: "error", text1: "录音发生异常!" });
}
}
};
/* 停止录音 */
onStopRecording = async () => {
// console.log("停止录音..");
await this.state.recording.stopAndUnloadAsync();
const uri = this.state.recording.getURI();
this.setState({
recording: undefined,
recordingUri: uri
})
Toast.show({ type: "info", text1: "录音已经停止,正在上传您的录音哟~", props: {width: 300} });
// 上传录音文件
let fileExtension = uri.substring(uri.lastIndexOf(".") + 1);
let _this = this;
FileFetchRequest(PublicConstant.EnvConstant.file_upload + "?type=audio", {
body: {
type: `audio/${fileExtension}`,
name: `article-audio-record.${fileExtension}`,
uri: uri,
}
}, (data: any)=>{
// console.log("=====>", data);
if (data.href) {
let { params } = this.props.route || {};
// TODO: test
params = params || {"title": "相思", "itemId": 2};
InterfaceRequest(PublicConstant.EnvConstant.articles_comment_addition, {
body: {
pid: params.itemId,
audio: data.href,
text: data.filename,
size: data.size
}
},"POST",
(res: any)=>{
if (res.code === 200) {
_this.setState({
lastAudioFile: data.href,
})
Toast.show({ type: "info", text1: "录音评价完成" });
// 显示评价窗口
_this.onViewComment();
} else {
Toast.show({ type: "error", text1: res.message || "服务异常,小主,请稍后~", props: {width: 290} });
}
},
(error: any)=>{
Toast.show({ type: "error", text1: "服务太忙了,小主,请稍~", props: {width: 290} });
})
} else {
Toast.show({ type: "error", text1: data.message || "录音文件上传失败~", });
}
}, (error: any)=>{
Toast.show({ type: "error", text1: "录音文件上传失败!", });
})
// console.log("录音停止,录音文件在:", uri);
};
/**
* 播放录音
* @param {string} uri 录音文件地址,优先使用 格式: 402553|http://micro-api.app.raodaor.com:9002/assets/uploads/POETRY34A33A1FC270325752EAF29C2B2022/0329/article-audio-record-1648488632763.m4a
* @param {number} itemid 记录的id
*/
onPlaySound = async (uri: string, itemid: number) => {
// 解析获取资源信息
let truthUri = extractResourceInfo(uri, "uri");
const { lastAudioFile } = this.state;
// console.log("加载音频文件");
const { sound } = await Audio.Sound.createAsync(
// { uri: "http://10.2.190.34:9002/assets/uploads/POETRY34A33A1FC270325752EAF29C2B2022/0211/article-audio-record-1644571314954.m4a"},
{ uri: truthUri || lastAudioFile || require("../../assets/audio/汽修师-宝宝巴士儿歌.m4a")},
{ shouldPlay: true }
);
// 监听音频对象播放进度
let soundProgressMonitor = setInterval(async ()=>{
let monitorProgress = await sound.getStatusAsync()
// "isPlaying": false,
// "positionMillis": 8034,
// 表示播放停止
if (monitorProgress["isPlaying"] === false) {
this.onStopSound()
} else {
this.setState({
// 总播放的时长 - 当前播放的位置时长
soundPositionMillis: monitorProgress["durationMillis"] - monitorProgress["positionMillis"]
})
}
// console.log("isPlaying===>positionMillis", monitorProgress["positionMillis"])
}, 1000);
// 获取音频状态
let soundInitStatus = await sound.getStatusAsync()
// 公共的播放器对象
this.setState({
sound: sound,
soundPositionMillis: soundInitStatus["durationMillis"],
soundItemFlag: itemid,
soundProgressMonitor: soundProgressMonitor
}, async() => {
// console.log("开始播放音频文件");
await this.state.sound.playAsync();
})
};
/* 停止播放 */
onStopSound = async () => {
// console.log("开始停止播放..");
const { sound, soundProgressMonitor } = this.state;
sound && await sound.stopAsync(); // replayAsync 重新播放, pauseAsync 暂停播放
sound && await sound.unloadAsync(); // 卸载
soundProgressMonitor && clearInterval(soundProgressMonitor); // 清除定时监听
this.setState({
sound: undefined,
soundPositionMillis: 0,
soundProgressMonitor: undefined,
})
};
/* 关闭评论弹层 */
onCloseComment = async () => {
this.onStopSound();
this.setState({
commentModal: false,
})
};
/**
* 去外网查询
* @param {object} query 查询参数
*/
onGoToQuery = (query: any) => {
this.props.navigation.navigate("webViewDefault", {
title: "查询 " + query,
uri: "https://www.baidu.com/s?wd=" + query
})
};
/* 展示评论浮层 */
onViewComment = () => {
this.setState({
commentModal: true
})
this.onRefreshComment();
}
/* 刷新评论列表 */
onRefreshComment = () => {
let _this = this;
this.setState({
CommentRefreshing: true,
})
this.onFetchDataComment(()=>{
_this.setState({
CommentRefreshing: false
})
})
};
/* 获取评论列表 */
onFetchDataComment = (callback: any) => {
let { params } = this.props.route || {};
// TODO: test
// params = params || {"title": "相思", "itemId": 2};
let _this = this;
if (params && params["itemId"]) {
// 获取评论列表
InterfaceRequest(
PublicConstant.EnvConstant.articles_comment_lists,
{
body: {
id: params.itemId,
userKey: PublicConstant.EnvConstant.appUserInfo.key,
}
},
"POST",
(data: any)=>{
// console.log("====评论列表====>", data)
if (data.code === 200) {
_this.setState({
commentListData: data.data.rows,
commentDataCount: data.data.count
})
} else {
Toast.show({ type: "error", text1: data.message || "获取失败,小主,请稍后~" });
}
callback && callback()
},
(error: any)=>{
callback && callback()
Toast.show({ type: "error", text1: "服务异常,小主,请稍后~", });
})
}
}
/**
* 列表滑动触底后触发的事件
* by https://docs.expo.dev/versions/v44.0.0/react-native/flatlist/#onendreached
*/
onEndReachedComment = (info: {distanceFromEnd: number}) => {
// console.log("触底了~, 距离有:", info.distanceFromEnd, this.state.commentPageNumber);
const { commentDataCount, commentListData, commentPageSize } = this.state;
if (!this.state.CommentRefreshing
&& commentListData.length >= commentPageSize
&& commentDataCount > commentListData.length)
{
this.setState({
commentPageNumber: this.state.commentPageNumber + 1
}, ()=>{
this.onFetchDataComment(null)
})
}
};
/* 跳转到个人用户 */
onJumpUserInfo = (item: any) => {
// this.onCloseComment();
this.props.navigation.navigate("myInfos", {
userKey: item.user_key,
});
};
/* 跳转到举报页面 */
onJumpReport = (item: any) => {
// this.onCloseComment();
this.props.navigation.navigate("report", {
userKey: item.user_key,
});
}
render() {
// 获取State变量
const {
refreshing, CommentRefreshing,
commentModal, commentListData, commentDataCount,
articleData,
pageStatus, pageStatusText,
recording, sound, soundPositionMillis, soundItemFlag,
} = this.state;
const _renderCommentItem = (obj: any) => {
let { item } = obj; // {index: 0, item: Object {}}
// console.log(item, "=====>item comment")
return <View key={`comment-list-${item.id}`} style={styles.commentItemWrap}>
{/* 评论人头像 */}
<TouchableOpacity
// style={styles.itemWrap}
onPress={ this.onJumpUserInfo.bind(null, item) }
>
<Image
style={styles.commentAvatar}
onError={(error)=>{}}
source={
{uri: item.avatar || PublicConstant.EnvConstant.defaultAvatar}
}
/>
</TouchableOpacity>
{/* 评论信息 */}
<View style={styles.commentInfo}>
{/* 评论的内容区 */}
<Text style={styles.nickTxt}>{item.nick || item.user_key}</Text>
<View>
{/* 目前只展示 评价文字和录音 */}
<Text style={styles.commentTxt}>{item.text}</Text>
{/* <Text>{item.img}</Text> */}
{/* <Text>{item.audio}</Text> */}
<TouchableOpacity style={styles.playWrapper} onPress={()=>{ sound ? this.onStopSound() : this.onPlaySound(item.audio, item.id) }}>
<Text style={styles.btnAudioTxt}>{(sound && soundItemFlag === item.id) ? "停止播放" : "播放录音"}</Text>
<Text style={styles.btnAudioTips}>{(sound && soundItemFlag === item.id) ? millisToMinutesAndSeconds(soundPositionMillis) : extractResourceInfo(item.audio, "size")}</Text>
</TouchableOpacity>
{/* <Text>{item.video}</Text> */}
{/* <Text>{item.file}</Text> */}
</View>
{/* 底部区域 */}
<View style={styles.commentBottom}>
<Text>{formatDataShort(item.time)}</Text>
{/* 快捷按钮(比如:举报) */}
<View style={styles.commentTool}>
<TouchableOpacity onPress={ this.onJumpReport.bind(null, item) }>
<Text>点赞</Text>
</TouchableOpacity>
<TouchableOpacity onPress={ this.onJumpReport.bind(null, item) }>
<Text>举报</Text>
</TouchableOpacity>
</View>
</View>
</View>
</View>
}
return (
// SafeAreaView 是IOS的组件
<SafeAreaView style={styles.container}>
<StatusBar
animated={true}
style="light"
backgroundColor="#f70f0f" />
{/* 页面状态 */}
<ComponentsPageStatus status={pageStatus} statusTxt={pageStatusText} callback={this.onRefresh} />
<ScrollView
refreshControl={<RefreshControl refreshing={refreshing} onRefresh={this.onRefresh} />} >
{/* 文章内容是占位符,上下左右应该还有其它内容,来自平台配置 */}
<Pressable onPress={this.onCloseComment}>
<Text
style={styles.articleContent}
// onLongPress={(e)=>{alert("长按了")}}
selectable={true}
selectionColor="#c4cdff">
{(articleData["content"] || "").replace(/<.*?>/g,"")}
</Text>
</Pressable>
</ScrollView>
{/* 按钮区域 */}
<View style={styles.moreToolWrap}>
<Pressable onPress={this.onViewComment}>
<Text style={styles.btnDefault}>用户评论</Text>
</Pressable>
<Pressable onPress={recording ? this.onStopRecording : this.onStartRecording}>
<Text style={styles.btnDefault}>{recording ? "停止录音" : "开始录音"}</Text>
</Pressable>
<Pressable onPress={this.onGoToQuery.bind(null, articleData["title"])}>
<Text style={styles.btnDefault}>百度查询</Text>
</Pressable>
</View>
{/* 悬浮按钮区域 */}
<Animated.View {...this.panResponder.panHandlers} style={[this.panObject.getLayout(), styles.floatWrap]}>
<Image style={styles.btnLightNight} source={require("../../assets/public/icon-night.png")} />
<Image style={styles.btnLightNight} source={require("../../assets/public/icon-light.png")} />
</Animated.View>
{/* 评论内容区域-浮层展示 */}
{/* <Modal
animationType="slide"
transparent={true}
visible={commentModal}
onRequestClose={this.onCloseComment}> */}
{
commentModal ?
<View style={styles.commentModalWrap}>
<View style={styles.modalHead}>
<Text style={styles.modalTitle}>{commentDataCount}条评论</Text>
<Pressable
style={styles.modalClose}
onPress={this.onCloseComment}>
<ImageBackground source={require("../../../assets/nav-cancel.png")} style={{width: 20, height: 20}}>
</ImageBackground>
</Pressable>
</View>
<FlatList
data={commentListData}
renderItem={_renderCommentItem}
keyExtractor={item => `${item.user_key}-${item.id}`}
// numColumns={2} // 要渲染多列,可以指定此项
refreshControl={
<RefreshControl
refreshing = {CommentRefreshing}
onRefresh = {this.onRefreshComment}
/>
}
onEndReachedThreshold={0.5}
onEndReached={this.onEndReachedComment}
/>
</View>
: null
}
{/* </Modal> */}
</SafeAreaView>
);
}
}
// 最上层的Layout
export default class AppContainer extends React.Component {
render() {
const ConnectedApp = connectActionSheet<object>(DetailsArticle);
return (
<ActionSheetProvider>
<ConnectedApp {...this.props} />
</ActionSheetProvider>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: "#ffffff"
// marginTop: Constants.statusBarHeight,
},
articleContent: {
lineHeight: 25,
margin: 10,
fontSize: 18,
},
// 更多按钮工具区域
moreToolWrap: {
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center",
height: 60,
paddingLeft: 30,
paddingRight: 30,
marginBottom: 20,
},
// 默认按钮样式
btnDefault: {
borderRadius: 10,
fontSize: 15,
color: "#ffffff",
backgroundColor: "#f70f0f",
paddingLeft: 10,
paddingRight: 10,
paddingTop: 5,
paddingBottom: 5,
},
// 评论列表
commentModalWrap: {
width: Dimensions.get("window").width,
height: Dimensions.get("window").height / 2 + 150,
backgroundColor: "#ffffff",
marginTop: Dimensions.get("window").height / 2 - 150,
position: "absolute",
left: 0,
bottom: 0,
zIndex: 99,
},
// 弹框的标题部分
modalHead: {
height: 50,
flexDirection: "row",
justifyContent: "center",
alignItems: "center",
// borderBottomColor: "#f3ecec",
// borderBottomWidth: 1,
borderTopLeftRadius: 20,
borderTopRightRadius: 20,
// 阴影效果
elevation: 1, // 该属性只支持>=android 5.0
shadowColor: "#b7b2b2", // IOS
shadowOffset: {width:0, height:0}, // IOS
shadowOpacity: 1, // IOS
shadowRadius: 1, // IOS
},
modalTitle: {
fontSize: 18,
fontWeight: "500"
},
modalClose: {
width: 50,
height: 50,
position: "absolute",
right: 10,
top: 0,
alignItems: "center",
justifyContent: "center"
},
// 评论相关
commentItemWrap: {
flexDirection: "row",
margin: 16,
},
commentAvatar: {
width: 32,
height: 32,
borderRadius: 32,
borderWidth: 1,
borderColor: "#eee"
},
commentInfo: {
marginLeft: 10,
width: Dimensions.get("window").width - 80
},
commentBottom: {
flexDirection: "row",
justifyContent: "space-between",
paddingBottom: 10,
borderBottomColor: "#f7f2f2",
borderBottomWidth: 1,
},
commentTool: {
flexDirection: "row",
width: 65,
justifyContent: "space-between"
},
// 昵称
nickTxt: {
fontSize: 15,
marginBottom: 5,
},
// 文本评价内容
commentTxt: {
fontSize: 14,
},
// 播放容器
playWrapper: {
flexDirection: "row",
alignItems: "center",
marginTop: 5,
marginBottom: 5,
},
// 按钮样式
btnAudioTxt: {
backgroundColor: "#f01d00cc",
fontSize: 14,
color: "#ffffff",
width: 75,
height: 26,
borderRadius: 26,
lineHeight: 26,
textAlign: "center",
},
btnAudioTips: {
fontSize: 12,
marginLeft: 5,
color: "#504e4e",
},
btnLightNight: {
width: 50,
height: 50,
},
// 浮动元素
floatWrap: {
position: "absolute",
right: 0,
top: 100,
}
});