├── .babelrc ├── .gitignore ├── README.md ├── index.html ├── package.json ├── src ├── App.jsx ├── assets │ ├── css │ │ └── style.css │ └── images │ │ ├── 360_logo.png │ │ ├── background.png │ │ ├── baidu_logo.png │ │ └── sougou_logo.png ├── components │ ├── logo-select.jsx │ └── search-input.jsx └── main.jsx └── webpack.config.js /.babelrc: -------------------------------------------------------------------------------- 1 | { "presets": [ "es2015","react"] } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | node_modules、 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 一个简易的搜索大全页面 2 | ---- 3 | 4 | [Vue版demo](http://github.com/lavyun/vue-demo-search) 5 | 6 | * [演示地址](http://lavyun.github.io/vue-demo-search) 7 | 8 | #### 所用知识 9 | - react组件与组件通信 10 | - 简单的webpack配置 11 | 12 | #### 如何运行 13 | 将本项目下载的本地. 14 | > 1. 需要node环境 15 | > 2. 输入命令npm install(最好使用cnpm淘宝镜像) 16 | > 3. 输入命令npm run dev 17 | > 4. 打开index.html 18 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | react-demo-search 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "learn-react", 3 | "version": "1.0.0", 4 | "description": "React is a JavaScript library for building user interfaces.", 5 | "main": "webpack.config.js", 6 | "directories": { 7 | "example": "examples" 8 | }, 9 | "scripts": { 10 | "test": "echo \"Error: no test specified\" && exit 1", 11 | "dev": "webpack --progress --watch" 12 | }, 13 | "author": "", 14 | "license": "ISC", 15 | "dependencies": { 16 | "jquery": "^3.1.1", 17 | "react": "^15.4.2", 18 | "react-dom": "^15.4.2", 19 | "webpack": "^2.2.0" 20 | }, 21 | "devDependencies": { 22 | "babel-core": "^6.23.1", 23 | "babel-loader": "^6.4.0", 24 | "babel-preset-es2015": "^6.22.0", 25 | "babel-preset-react": "^6.23.0", 26 | "css-loader": "^0.27.1", 27 | "file-loader": "^0.9.0", 28 | "style-loader": "^0.13.2", 29 | "url-loader": "^0.5.8" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/App.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { render } from 'react-dom' 3 | import SearchInput from './components/search-input' 4 | 5 | const App = React.createClass({ 6 | render(){ 7 | return ( 8 |
9 | 10 |
11 | ) 12 | } 13 | }); 14 | 15 | export default App; -------------------------------------------------------------------------------- /src/assets/css/style.css: -------------------------------------------------------------------------------- 1 | *{box-sizing: border-box;padding: 0;margin: 0;} 2 | ul,li{list-style: none} 3 | html { 4 | height: 100%; 5 | } 6 | 7 | body { 8 | 9 | height: 100%; 10 | background: url('../images/background.png') no-repeat center; 11 | background-size: 100% 100% 12 | } 13 | 14 | #app { 15 | color: #2c3e50; 16 | width: 700px; 17 | font-family: Source Sans Pro, Helvetica, sans-serif; 18 | text-align: center; 19 | position: absolute; 20 | top: 35%; 21 | left: 50%; 22 | margin-left: -350px; 23 | margin-top: -100px; 24 | } 25 | 26 | .search{width: 100%;} 27 | .search-panel{width: 100%;height: 50px;position: relative;} 28 | .search-input{width: 600px;height: 100%;font-size: 16px;padding: 0 30px 0 10px;display: block;float: left;} 29 | .search-btn{width: 100px;height: 100%;border: none;background-color: rgb(65, 185, 147);color: #fff;font-size: 18px;display: block;float: right} 30 | .search-clearinput{position: absolute;display: block;width: 20px;height: 20px;left: 575px;top: 15px;cursor: pointer;font-size: 18px;} 31 | .search-list{width: 600px;} 32 | .search-list li{text-align: left;background-color: #fff;padding: 5px 10px;cursor: pointer} 33 | .search-list .is-select{background-color: #e4e4e4;} 34 | 35 | .logo-panel{width: 100%;height: 140px;position: relative;margin-bottom: 20px;} 36 | .logo-display{width: 100%;height: 100%;text-align: center} 37 | .logo-select-arrow{ 38 | display: block; width: 0; 39 | height: 0; 40 | border: 10px solid #000; 41 | border-color: #000 transparent transparent transparent; 42 | position: absolute;;right: 130px;top: 65px; 43 | cursor: pointer; 44 | } 45 | .logo-list{position: absolute;top: 130px;width: 200px;left: 50%;z-index: 9;border: 1px solid #e4e4e4} 46 | .logo-list-item{background-color: #fff;border-bottom: 1px solid #e4e4e4} 47 | .logo-list-item:hover{background-color: #e4e4e4} 48 | .logo-list-item:last-of-type{border-bottom: 0} 49 | .logo-list-item img{width: 100%;} 50 | 51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /src/assets/images/360_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lavyun/react-demo-search/e30c13023cca420e74cd454611cfa5c6e47277d1/src/assets/images/360_logo.png -------------------------------------------------------------------------------- /src/assets/images/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lavyun/react-demo-search/e30c13023cca420e74cd454611cfa5c6e47277d1/src/assets/images/background.png -------------------------------------------------------------------------------- /src/assets/images/baidu_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lavyun/react-demo-search/e30c13023cca420e74cd454611cfa5c6e47277d1/src/assets/images/baidu_logo.png -------------------------------------------------------------------------------- /src/assets/images/sougou_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lavyun/react-demo-search/e30c13023cca420e74cd454611cfa5c6e47277d1/src/assets/images/sougou_logo.png -------------------------------------------------------------------------------- /src/components/logo-select.jsx: -------------------------------------------------------------------------------- 1 | import React,{Component} from 'react' 2 | import {render} from 'react-dom' 3 | 4 | class LogoSelect extends Component{ 5 | constructor(props){ 6 | super(props); 7 | this.state = { 8 | logos: [require('../assets/images/360_logo.png'),require('../assets/images/baidu_logo.png'),require('../assets/images/sougou_logo.png')], 9 | selectIndex: 0, 10 | showLogo: false 11 | } 12 | } 13 | 14 | //处理logo选择 15 | handleLogoSelect(ev){ 16 | var index = parseInt(ev.target.getAttribute('data-index')); 17 | this.setState({ 18 | selectIndex: index, 19 | showLogo: false 20 | },function(){ 21 | this.props.onLogoChange(index) 22 | }) 23 | } 24 | 25 | //显示logo列表 26 | showLogoList(){ 27 | this.setState({ 28 | showLogo: true 29 | }) 30 | } 31 | 32 | render() { 33 | var _state = this.state, 34 | logos = _state.logos; 35 | 36 | const Li= this.state.logos.map((logo, index)=>{ 37 | return ( 38 |
40 | 41 | 42 |
43 | ) 44 | }); 45 | 46 | return ( 47 |
48 |
49 | 50 |
51 | 52 |
53 | {Li} 54 |
55 |
56 | ) 57 | } 58 | } 59 | 60 | export default LogoSelect 61 | -------------------------------------------------------------------------------- /src/components/search-input.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import {render} from 'react-dom' 3 | import $ from 'jquery' 4 | 5 | import LogoSelect from './logo-select' 6 | 7 | class SearchInput extends Component { 8 | constructor(props) { 9 | super(props); 10 | this.state = { 11 | keyword: '', 12 | showList: [], 13 | listIndex: 0, 14 | searchSrcs: ['https://www.so.com/s?ie=utf-8&shb=1&src=360sou_newhome&q=', 'https://www.baidu.com/s?ie=utf-8&f=8&rsv_bp=0&rsv_idx=1&tn=baidu&wd=', 'https://www.sogou.com/web?query='], 15 | searchSrc: 'https://www.so.com/s?ie=utf-8&shb=1&src=360sou_newhome&q=' 16 | } 17 | } 18 | 19 | //处理输入 20 | handleInput(ev) { 21 | var _this = this; 22 | _this.setState({ 23 | keyword: ev.target.value 24 | }, function () { 25 | //jsonp获取数据 26 | $.ajax({ 27 | url: 'https://sug.so.360.cn/suggest?word=' + this.state.keyword + '&encodein=utf-8&encodeout=utf-8', 28 | type: 'get', 29 | dataType: "jsonp" 30 | }).done(function (data) { 31 | var list = data.s; 32 | _this.setState({ 33 | showList: list 34 | }) 35 | }) 36 | }) 37 | } 38 | 39 | //处理鼠标hover 40 | handleMouseSelect(ev) { 41 | var index = ev.target.getAttribute('data-index'); 42 | this.setState({ 43 | listIndex: parseInt(index) 44 | }) 45 | } 46 | 47 | //处理点击列表 48 | handleSelectClick(ev) { 49 | this.setState({ 50 | keyword: ev.target.innerText 51 | }, function () { 52 | this.refs.input.value = this.state.keyword; 53 | setTimeout(()=> { 54 | this.handleSearch(); 55 | }, 50); 56 | }) 57 | } 58 | 59 | //处理点击清除按钮 60 | handleClearClick() { 61 | this.setState({ 62 | keyword: '', 63 | showList: [] 64 | }); 65 | this.refs.input.value = ''; 66 | } 67 | 68 | //处理搜索 69 | handleSearch() { 70 | var _state = this.state; 71 | window.location.href = _state.searchSrc + _state.keyword; 72 | } 73 | 74 | //处理Enter键 75 | handleKeyEnter(ev) { 76 | var keyCode = ev.keyCode; 77 | switch (keyCode) { 78 | case 13: 79 | this.handleSearch(); 80 | break; 81 | case 38: 82 | this.selectUpAndDown(ev, keyCode); 83 | break; 84 | case 40: 85 | this.selectUpAndDown(ev, keyCode); 86 | break; 87 | 88 | } 89 | } 90 | 91 | //上下键选择列表项 92 | selectUpAndDown(ev, keycode) { 93 | ev.preventDefault(); 94 | var _state = this.state; 95 | 96 | var stateCb = function () { 97 | this.setState({ 98 | keyword: this.state.showList[this.state.listIndex] 99 | }, function () { 100 | this.refs.input.value = this.state.keyword; 101 | }); 102 | }; 103 | 104 | 105 | if (keycode === 38) { 106 | this.setState({ 107 | listIndex: _state.listIndex == 0 ? 9 : --_state.listIndex 108 | }, stateCb); 109 | } else if (keycode === 40) { 110 | this.setState({ 111 | listIndex: _state.listIndex == 9 ? 0 : ++_state.listIndex 112 | }, stateCb); 113 | } 114 | } 115 | 116 | //logo选择与父组件通信 117 | onLogoChange(index) { 118 | this.setState({ 119 | searchSrc: this.state.searchSrcs[index] 120 | }) 121 | } 122 | 123 | render() { 124 | var _this = this; 125 | var _state = this.state; 126 | 127 | const Li = _state.showList.map((value, index)=> 128 |
  • 133 | { value } 134 |
  • 135 | ); 136 | 137 | return ( 138 |
    139 | 140 | 141 | 142 |
    143 | 148 | × 149 | 150 |
    151 | 152 |
    153 |
      { 154 | Li 155 | }
    156 |
    157 | 158 |
    159 | ) 160 | } 161 | } 162 | 163 | export default SearchInput; 164 | -------------------------------------------------------------------------------- /src/main.jsx: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react' 2 | import { render } from 'react-dom' 3 | 4 | import './assets/css/style.css' 5 | 6 | import App from './App' 7 | 8 | var app = document.getElementById('app'); 9 | 10 | render(, app); -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | 3 | module.exports = { 4 | entry: './src/main.jsx', 5 | output: { 6 | path: path.join(__dirname, 'dist'), 7 | publicPath: './dist/', 8 | filename: 'main.bundle.js' 9 | }, 10 | resolve: { 11 | extensions: ['.js', '.jsx'] 12 | }, 13 | module: { 14 | rules: [{ 15 | test: /\.(js|jsx)$/, 16 | exclude: '/node_modules/', 17 | use: [{ 18 | loader: 'babel-loader', 19 | options: { 20 | presets: ['es2015'] 21 | } 22 | }] 23 | }, { 24 | test: /\.css/, 25 | use: [{ 26 | loader: 'style-loader' 27 | }, { 28 | loader: 'css-loader' 29 | }] 30 | }, { 31 | test: /\.(png|jpg)$/, 32 | use: [{ 33 | loader: 'url-loader', 34 | options: { 35 | limit: 10000, 36 | name: 'images/[name].[hash:7].[ext]' 37 | } 38 | }] 39 | }] 40 | } 41 | }; --------------------------------------------------------------------------------