Authored by 于良

品牌搜索 review by 孙凯

... ... @@ -20,6 +20,7 @@ import YH_SearchBar from '../../../common/components/YH_SearchBar';
import ChannelFliter from './ChannelFliter';
import AllBrandListCell from './AllBrandListCell';
import NewHotBannerListCell from './NewHotBannerListCell';
import BrandSearch from './BrandSearch';
export default class Brand extends Component {
... ... @@ -66,7 +67,14 @@ export default class Brand extends Component {
return (
<View style={styles.header}>
<YH_SearchBar/>
<TouchableOpacity
activeOpacity={1}
onPress={() => {
this.props.onPressSearch && this.props.onPressSearch();
}}
>
<YH_SearchBar editable={false} />
</TouchableOpacity>
{banner?<BrandSwiper resource={banner} onPressSlideItem={this.props.onPressSlideItem}/>:null}
{custom_brands?<BannerReourceList resource={custom_brands} onPressSlideItem={this.props.onPressSlideItem}/>:null}
{brandsText?<BrandFliter dataSource={brandsText} selectID={brandFliter} onPressFilter={this.props.onPressFilter}/>:null}
... ... @@ -97,6 +105,7 @@ export default class Brand extends Component {
render() {
let {
showSearch,
brandFliter,
channelFliter,
brandListForBoy,
... ... @@ -107,6 +116,7 @@ export default class Brand extends Component {
reourceForGirl,
reourceForKid,
reourceForLifeStyle,
search,
} = this.props;
let data;
... ... @@ -148,6 +158,7 @@ export default class Brand extends Component {
return (
<View style={styles.container}>
<ChannelFliter selectID={channelFliter} onChannelPressFliter={this.props.onChannelPressFliter}/>
<ListView
contentContainerStyle={contentContainerStyle}
enableEmptySections={true}
... ... @@ -156,6 +167,8 @@ export default class Brand extends Component {
renderSectionHeader={renderSectionHeader}
renderHeader={this.renderHeader}
/>
{showSearch ? <BrandSearch style={styles.search} data={search}/> : null}
</View>
);
}
... ... @@ -166,6 +179,7 @@ let {width, height} = Dimensions.get('window');
let styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: 'white',
},
contentContainerOne:{
... ... @@ -191,4 +205,8 @@ let styles = StyleSheet.create({
width,
backgroundColor: '#444',
},
search: {
},
});
... ...
'use strict';
import React, {Component} from 'react';
import ReactNative, {
View,
Text,
Image,
ListView,
StyleSheet,
Dimensions,
TouchableOpacity,
} from 'react-native';
import KeywordCell from './KeywordCell';
import YH_SearchBar from '../../../common/components/YH_SearchBar';
export default class SearchKeyword extends Component {
constructor(props) {
super(props);
this._renderRow = this._renderRow.bind(this);
this._renderSeparator = this._renderSeparator.bind(this);
this.dataSource = new ListView.DataSource({
rowHasChanged: (r1, r2) => !Immutable.is(r1, r2),
sectionHeaderHasChanged: (s1, s2) => !Immutable.is(s1, s2),
});
}
componentDidMount() {
this.searchBar && this.searchBar.focus();
}
_renderRow(rowData, sectionID, rowID) {
if (rowData.size == 0) {
return null;
}
let icon = rowID == 0 ? require('../../images/shijian.png') : require('../../images/huo.png');
let title = rowID == 0 ? '最近搜索' : '热门搜索';
let onPressAction = rowID == 0 ? this.props.onPressClearHistory : null;
return (
<KeywordCell
key={'row' + rowID}
icon={icon}
title={title}
list={rowData}
onPressAction={onPressAction}
onPressKeyword={this.props.onPressKeyword}
/>
);
}
_renderSeparator(sectionID, rowID, adjacentRowHighlighted) {
if (rowID == 0 && this.props.data.history.size > 0) {
return (
<View key={'sep' + rowID} style={styles.separator}>
<View style={styles.line}/>
</View>
);
}
return null;
}
render() {
let {history, hot, style} = this.props.data;
let list = [history, hot];
return (
<View style={[styles.container, style]}>
<YH_SearchBar
ref={(c) => {
this.searchBar = c;
}}
/>
<ListView
contentContainerStyle={styles.contentContainer}
enableEmptySections={true}
dataSource={this.dataSource.cloneWithRows(list)}
renderRow={this._renderRow}
renderSeparator={this._renderSeparator}
keyboardDismissMode={'on-drag'}
/>
</View>
);
}
}
let {width, height} = Dimensions.get('window');
let styles = StyleSheet.create({
container: {
flex: 1,
position: 'absolute',
top: 0,
left: 0,
width,
height,
backgroundColor: 'white',
},
contentContainer: {
},
separator: {
height: 15,
},
line: {
marginHorizontal: 30,
top: 10 ,
height: 1,
backgroundColor: '#e5e5e5',
},
});
... ...
'use strict';
import React from 'react';
import ReactNative from 'react-native';
const {
View,
Image,
Text,
ScrollView,
TouchableOpacity,
Dimensions,
StyleSheet,
} = ReactNative;
import KeywordHeader from './KeywordHeader';
import KeywordText from './KeywordText';
export default class KeywordCell extends React.Component {
constructor(props) {
super (props);
}
render() {
let {icon, title, list, onPressAction, onPressKeyword} = this.props;
return (
<View style={styles.container}>
<KeywordHeader
icon={icon}
title={title}
onPressAction={onPressAction}
/>
<View style={styles.textContainer}>
{list.toJS().map((item, i) => {
return <KeywordText key={i} keyword={item.keyword} onPressKeyword={onPressKeyword}/>;
})}
</View>
</View>
);
}
}
let styles = StyleSheet.create({
container: {
},
textContainer: {
marginHorizontal: 26,
flexDirection: 'row',
flexWrap: 'wrap',
},
});
... ...
'use strict';
import React from 'react';
import ReactNative from 'react-native';
const {
View,
Image,
Text,
TouchableOpacity,
Dimensions,
StyleSheet,
} = ReactNative;
export default class KeywordHeader extends React.Component {
constructor(props) {
super (props);
}
renderAction() {
if (this.props.onPressAction) {
return (
<TouchableOpacity style={styles.action} onPress={() => {
this.props.onPressAction && this.props.onPressAction();
}}>
<Image
style={styles.delete}
source={require('../../images/shanchu.png')}
resizeMode={'contain'}
/>
</TouchableOpacity>
);
}
return null;
}
render() {
let {icon, title, onPressAction} = this.props;
return (
<View style={styles.container}>
<Image
style={styles.icon}
source={icon}
resizeMode={'contain'}
/>
<Text style={styles.text}>{title}</Text>
{this.renderAction()}
</View>
);
}
}
let styles = StyleSheet.create({
container: {
flexDirection: 'row',
alignItems: 'center',
width: Dimensions.get('window').width,
height: 40,
},
icon: {
marginLeft: 15,
width: 13,
height: 17,
},
text: {
marginLeft: 10,
color: '#b0b0b0',
fontSize: 12,
},
action: {
position: 'absolute',
right: 10,
height: 40,
},
delete: {
width: 28,
height: 28,
top: 6,
},
});
... ...
'use strict';
import React from 'react';
import ReactNative from 'react-native';
const {
View,
Image,
Text,
TouchableHighlight,
Dimensions,
StyleSheet,
} = ReactNative;
export default class KeywordText extends React.Component {
constructor(props) {
super (props);
this.state = {
helight: false,
}
}
render() {
let {keyword, onPressKeyword} = this.props;
let textColor = this.state.helight ? {color: 'rgb(66, 66, 66)'} : {color: 'rgb(191, 191, 191)'};
return (
<TouchableHighlight
style={styles.container}
underlayColor={'rgb(255, 255, 255)'}
onPress={() => {
onPressKeyword && onPressKeyword(keyword);
}}
onPressIn={() => {
this.setState({
helight: !this.state.helight,
});
}}
onPressOut={() => {
this.setState({
helight: !this.state.helight,
});
}}
>
<Text style={[styles.text, textColor]}>{keyword}</Text>
</TouchableHighlight>
);
}
}
let styles = StyleSheet.create({
container: {
flexDirection: 'row',
alignItems: 'center',
alignSelf: 'flex-start',
height: 25,
backgroundColor: 'rgb(255, 255, 255)',
borderColor: 'rgb(191, 191, 191)',
borderRadius: 3,
borderWidth: 1,
margin: 4,
},
text: {
marginHorizontal: 10,
marginVertical: 6,
fontSize: 13,
},
});
... ...
... ... @@ -23,10 +23,6 @@ export default keyMirror({
LOAD_CACHED_INTEREST_LIST: null,
GET_COUPON_REQUEST: null,
GET_COUPON_SUCCESS: null,
GET_COUPON_FAILURE: null,
GET_BRAND_LIST_FOR_BOY_REQUEST: null,
GET_BRAND_LIST_FOR_BOY_SUCCESS: null,
GET_BRAND_LIST_FOR_BOY_FAILURE: null,
... ... @@ -62,9 +58,14 @@ export default keyMirror({
SET_BRAND_FILTER: null,
SET_CHANNEL_FILTER: null,
JUMP_WITH_URL: null,
BRAND_SHOW_SEARCH: null,
FETCH_BRAND_SEARCH_HISTORY: null,
INSERT_BRAND_SEARCH_HISTORY: null,
CLEAR_BRAND_SEARCH_HISTORY: null,
BRAND_HOT_KEYWORD_REQUEST: null,
BRAND_HOT_KEYWORD_SUCCESS: null,
BRAND_HOT_KEYWORD_FAILURE: null,
HIDE_SUCCESS_PROMPT: null,
HIDE_NET_ERROR_PROMPT: null,
JUMP_WITH_URL: null,
});
... ...
... ... @@ -50,23 +50,27 @@ class BrandContainer extends Component {
this._onPressSlideItem = this._onPressSlideItem.bind(this);
this._onPressFilter = this._onPressFilter.bind(this);
this._onChannelPressFliter = this._onChannelPressFliter.bind(this);
this._onPressSearch = this._onPressSearch.bind(this);
this._onPressClearHistory = this._onPressClearHistory.bind(this);
}
componentDidMount() {
this.props.actions.getBrandList(0);
this.props.actions.getBrandResource(0);
this.props.actions.searchHistory();
this.props.actions.hotKeyword();
}
componentWillUnmount() {
}
_onPressSlideItem(url){
_onPressSlideItem(url) {
console.log('aaa');
}
_onPressFilter(value){
_onPressFilter(value) {
this.props.actions.setBrandFilter(value);
}
... ... @@ -107,8 +111,17 @@ class BrandContainer extends Component {
this.props.actions.setChannelFilter(value);
}
_onPressSearch() {
this.props.actions.setShowSearch(!this.props.brand.showSearch);
}
_onPressClearHistory() {
this.props.actions.clearSearchHistory();
}
render() {
let {
showSearch,
channelFliter,
brandFliter,
brandListForBoy,
... ... @@ -119,8 +132,9 @@ class BrandContainer extends Component {
reourceForGirl,
reourceForKid,
reourceForLifeStyle,
search,
} = this.props.brand;
console.log(this.props.brand);
return (
<View style={styles.container}>
<Brand
... ... @@ -137,6 +151,10 @@ class BrandContainer extends Component {
onPressFilter= {this._onPressFilter}
onPressSlideItem= {this._onPressSlideItem}
onChannelPressFliter={this._onChannelPressFliter}
showSearch={showSearch}
search={search}
onPressSearch={this._onPressSearch}
onPressClearHistory={this._onPressClearHistory}
/>
</View>
);
... ...
... ... @@ -41,6 +41,13 @@ const {
GET_BRAND_RESOURCE_FOR_LIFESTYLE_SUCCESS,
GET_BRAND_RESOURCE_FOR_LIFESTYLE_FAILURE,
BRAND_SHOW_SEARCH,
FETCH_BRAND_SEARCH_HISTORY,
INSERT_BRAND_SEARCH_HISTORY,
CLEAR_BRAND_SEARCH_HISTORY,
BRAND_HOT_KEYWORD_REQUEST,
BRAND_HOT_KEYWORD_SUCCESS,
BRAND_HOT_KEYWORD_FAILURE,
} = require('../../constants/actionTypes').default;
... ... @@ -59,6 +66,13 @@ export function setChannelFilter(filter) {
};
}
export function setShowSearch(show) {
return {
type: BRAND_SHOW_SEARCH,
payload: show
};
}
export function getBrandListForBoyRequest() {
return {
type: GET_BRAND_LIST_FOR_BOY_REQUEST,
... ... @@ -331,3 +345,130 @@ export function parseResourceResources(json) {
brandsText,
};
}
export function searchHistory() {
return (dispatch, getState) => {
ReactNative.NativeModules.YH_ClassifyHelper.fetchSearchHistory()
.then(data => {
dispatch({
type: FETCH_BRAND_SEARCH_HISTORY,
payload: parseSearchHistory(data)
});
})
.catch(error => {
});
};
}
export function insertSearchHistory(keyword) {
return (dispatch, getState) => {
ReactNative.NativeModules.YH_ClassifyHelper.insertSearchHistory(keyword)
.then(data => {
dispatch({
type: INSERT_BRAND_SEARCH_HISTORY,
payload: parseSearchHistory(data)
});
})
.catch(error => {
});
};
}
export function clearSearchHistory() {
return (dispatch, getState) => {
ReactNative.NativeModules.YH_ClassifyHelper.clearSearchHistory()
.then(data => {
dispatch({
type: CLEAR_BRAND_SEARCH_HISTORY,
payload: parseSearchHistory(data)
});
})
.catch(error => {
});
};
}
function parseSearchHistory(data) {
let list = [];
data.map((item, i) => {
list.push({keyword: item});
});
return list;
}
export function hotKeywordRequest() {
return {
type: BRAND_HOT_KEYWORD_REQUEST,
};
}
export function hotKeywordSuccess(json) {
return {
type: BRAND_HOT_KEYWORD_SUCCESS,
payload: json
};
}
export function hotKeywordFailure(error) {
return {
type: BRAND_HOT_KEYWORD_FAILURE,
payload: error
};
}
export function hotKeyword(reload=false) {
return (dispatch, getState) => {
let {app, brand} = getState();
let {search} = brand;
let {isFetching} = search;
if (reload) {
// 强制刷新数据
} else {
if (isFetching) {
return;
}
}
let fetchHotKeyword = (uid) => {
dispatch(hotKeywordRequest());
return new BrandService(app.host).fetchHotKeyword(uid)
.then(json => {
let payload = parseHotKeyword(json);
dispatch(hotKeywordSuccess(payload));
})
.catch(error => {
dispatch(hotKeywordFailure(error));
});
}
ReactNative.NativeModules.YH_CommonHelper.uid()
.then(uid => {
fetchHotKeyword(uid);
})
.catch(error => {
fetchHotKeyword(0);
});
};
}
function parseHotKeyword(json) {
let list = [];
Array.isArray(json) && json.map((item, i) => {
let brandName = item.brandName ? item.brandName : '';
let keyword = brandName;
let brandDomain = item.brandDomain ? item.brandDomain : '';
let brandId = item.brandId ? item.brandId : '';
list.push({keyword, brandName, brandDomain, brandId});
});
return {
list,
};
}
\ No newline at end of file
... ...
... ... @@ -53,6 +53,13 @@ let InitialState = Record({
brandsText: List(),
hasSuccess: false,
})),
showSearch: false,
search: new (Record({
history: List(),
isFetching: false,
error: null,
hot: List(),
})),
});
export default InitialState;
... ...
... ... @@ -41,24 +41,33 @@ const {
GET_BRAND_RESOURCE_FOR_LIFESTYLE_SUCCESS,
GET_BRAND_RESOURCE_FOR_LIFESTYLE_FAILURE,
BRAND_SHOW_SEARCH,
FETCH_BRAND_SEARCH_HISTORY,
INSERT_BRAND_SEARCH_HISTORY,
CLEAR_BRAND_SEARCH_HISTORY,
BRAND_HOT_KEYWORD_REQUEST,
BRAND_HOT_KEYWORD_SUCCESS,
BRAND_HOT_KEYWORD_FAILURE,
} = require('../../constants/actionTypes').default;
const initialState = new InitialState;
export default function brandReducer(state=initialState, action) {
switch(action.type) {
case SET_TYPE: {
return state.set('type', action.payload);
}
case SET_BRAND_FILTER:{
case SET_BRAND_FILTER: {
return state.set('brandFliter', action.payload);
}
case SET_CHANNEL_FILTER:{
case SET_CHANNEL_FILTER: {
return state.set('channelFliter', action.payload);
}
case BRAND_SHOW_SEARCH: {
return state.set('showSearch', action.payload);
}
case GET_BRAND_LIST_FOR_BOY_REQUEST:
{
return state;
... ... @@ -232,6 +241,28 @@ export default function brandReducer(state=initialState, action) {
return state.setIn(['reourceForLifeStyle', 'hasSuccess'], false);
}
case FETCH_BRAND_SEARCH_HISTORY:
case INSERT_BRAND_SEARCH_HISTORY:
case CLEAR_BRAND_SEARCH_HISTORY: {
return state.setIn(['search', 'history'], Immutable.fromJS(action.payload));
}
case BRAND_HOT_KEYWORD_REQUEST: {
return state.setIn(['search', 'isFetching'], true)
.setIn(['search', 'error'], null);
}
case BRAND_HOT_KEYWORD_SUCCESS: {
let {list} = action.payload;
return state.setIn(['search', 'isFetching'], false)
.setIn(['search', 'error'], null)
.setIn(['search', 'hot'], Immutable.fromJS(list));
}
case BRAND_HOT_KEYWORD_FAILURE: {
return state.setIn(['search', 'isFetching'], false)
.setIn(['search', 'error'], action.payload);
}
}
return state;
... ...
... ... @@ -45,4 +45,20 @@ export default class BrandService {
});
}
async fetchHotKeyword(uid, sourcePage='iFP_SearchProList') {
return await this.api.get({
url: '',
body: {
method: 'app.search.hotBrands',
uid,
sourcePage,
}
})
.then((json) => {
return json;
})
.catch((error) => {
throw(error);
});
}
}
... ...