├── src ├── pages │ ├── home │ │ ├── style.scss │ │ └── index.js │ ├── about │ │ ├── style.scss │ │ └── index.js │ └── index.js ├── assets │ └── images │ │ ├── base.jpg │ │ └── favicon.ico ├── components │ ├── index.js │ ├── Header │ │ ├── style.scss │ │ └── index.js │ └── Page │ │ ├── index.js │ │ └── page.scss ├── app │ ├── constants │ │ ├── TodoFilters.js │ │ └── ActionTypes.js │ ├── reducers │ │ ├── index.js │ │ ├── home.js │ │ └── todos.js │ ├── actions │ │ ├── home.js │ │ └── index.js │ └── store │ │ └── configureStore.js ├── utils │ ├── connect.js │ ├── cache.js │ ├── lru.js │ ├── validator.js │ ├── device.js │ └── canvas2img.js └── scss │ ├── reset.scss │ └── normalize.scss ├── .gitignore ├── dist ├── assets │ └── images │ │ └── base-4bee5605.jpg ├── index.html └── js │ ├── manifest-c1371331.js │ └── about-68c021d2.js ├── README.md ├── mock.js ├── package.json ├── template.html └── v2rx.config.js /src/pages/home/style.scss: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | build 3 | dist 4 | tmp 5 | reports 6 | -------------------------------------------------------------------------------- /src/assets/images/base.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stephenzhao/zhunkaozheng/HEAD/src/assets/images/base.jpg -------------------------------------------------------------------------------- /src/assets/images/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stephenzhao/zhunkaozheng/HEAD/src/assets/images/favicon.ico -------------------------------------------------------------------------------- /dist/assets/images/base-4bee5605.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stephenzhao/zhunkaozheng/HEAD/dist/assets/images/base-4bee5605.jpg -------------------------------------------------------------------------------- /src/components/index.js: -------------------------------------------------------------------------------- 1 | import Header from './Header' 2 | import Page from './Page' 3 | 4 | export default { 5 | Header, 6 | Page 7 | } 8 | -------------------------------------------------------------------------------- /src/app/constants/TodoFilters.js: -------------------------------------------------------------------------------- 1 | export const SHOW_ALL = 'SHOW_ALL' 2 | export const SHOW_COMPLETED = 'SHOW_COMPLETED' 3 | export const SHOW_ACTIVE = 'SHOW_ACTIVE' 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | #### how to start 4 | 5 | ``` 6 | clone repo.. 7 | npm install -g v2rx 8 | v2rx start 9 | ``` 10 | 11 | just follow the instruction 12 | 13 | more details at [v2rx doc](https://github.com/stephenzhao/v2rx) 14 | -------------------------------------------------------------------------------- /src/app/constants/ActionTypes.js: -------------------------------------------------------------------------------- 1 | export const ADD_TODO = 'ADD_TODO' 2 | export const DELETE_TODO = 'DELETE_TODO' 3 | export const EDIT_TODO = 'EDIT_TODO' 4 | export const COMPLETE_TODO = 'COMPLETE_TODO' 5 | export const COMPLETE_ALL = 'COMPLETE_ALL' 6 | export const CLEAR_COMPLETED = 'CLEAR_COMPLETED' 7 | -------------------------------------------------------------------------------- /src/app/reducers/index.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux' 2 | import { routerReducer } from 'react-router-redux' 3 | import todos from './todos' 4 | 5 | const rootReducer = combineReducers({ 6 | todos, 7 | routing: routerReducer, 8 | 9 | }) 10 | 11 | export default rootReducer 12 | -------------------------------------------------------------------------------- /src/pages/about/style.scss: -------------------------------------------------------------------------------- 1 | .about { 2 | padding: 10px; 3 | 4 | .desc { 5 | border: 1px solid #efefef; 6 | padding: 10px; 7 | background-color: #efefef; 8 | margin-bottom: 10px; 9 | } 10 | 11 | .link { 12 | float: right; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/components/Header/style.scss: -------------------------------------------------------------------------------- 1 | $active-color: #286090; 2 | 3 | .nav { 4 | display: inline-block; 5 | padding: 10px; 6 | 7 | .nav-item { 8 | display: inline; 9 | margin-left: 10px; 10 | 11 | a { 12 | padding: 3px 10px; 13 | } 14 | } 15 | 16 | .nav-item.active a { 17 | background-color: $active-color; 18 | color: white; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/app/reducers/home.js: -------------------------------------------------------------------------------- 1 | import cache from 'utils/cache' 2 | import * as types from '../constants/ActionTypes' 3 | 4 | const initialState = { 5 | movies: [], 6 | } 7 | 8 | export default function root(state = initialState, action) { 9 | switch (action.type) { 10 | case types.RECEIVE_MOVIES: 11 | { 12 | return Object.assign({}, state, { 13 | movies: action.movies 14 | }) 15 | } 16 | 17 | default: 18 | return state 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /mock.js: -------------------------------------------------------------------------------- 1 | module.exports = [{ 2 | path: /\/apis/, 3 | method: 'get', 4 | data: function(options) { 5 | return [{ // response data 6 | id: 1, 7 | first: '@FIRST', 8 | }, { 9 | id: 2, 10 | first: '@FIRST', 11 | }, { 12 | id: 3, 13 | first: '@FIRST', 14 | }] 15 | } 16 | }, { 17 | path: /\/api/, 18 | method: 'get', 19 | data: { 20 | 'list|1-10': [{ 21 | 'id|+1': 1 22 | }] 23 | } 24 | }, , { 25 | path: '/movie', 26 | proxy: 'http://mapi.ffan.com', 27 | }] 28 | -------------------------------------------------------------------------------- /src/utils/connect.js: -------------------------------------------------------------------------------- 1 | import { connect } from 'react-redux' 2 | import { bindActionCreators } from 'redux' 3 | import * as actions from 'app/actions' 4 | 5 | function mapStateToProps(state) { 6 | return state 7 | } 8 | 9 | function mapDispatchToProps(dispatch) { 10 | return { 11 | actions: bindActionCreators(actions, dispatch) 12 | } 13 | } 14 | 15 | export default (state) => { 16 | return (target) => { 17 | // target.prototype.setTitle = (title) => { 18 | // document.title = title 19 | // } 20 | return connect(mapStateToProps, mapDispatchToProps)(target) 21 | } 22 | } -------------------------------------------------------------------------------- /src/app/actions/home.js: -------------------------------------------------------------------------------- 1 | import reqwest from 'reqwest' 2 | import cache from 'utils/cache' 3 | import * as types from '../constants/ActionTypes' 4 | 5 | export function fetchMovies() { 6 | return (dispatch, getState) => { 7 | const url = `${API}/movie/v1/movies?status=1&cityId=110100&limit=80&offset=0&__uni_source=1.3` 8 | 9 | return reqwest(url) 10 | .then(resp => { 11 | dispatch(receiveMovies(resp.data.datas)) 12 | return resp 13 | }) 14 | } 15 | } 16 | 17 | function receiveMovies(movies) { 18 | return { 19 | type: types.RECEIVE_MOVIES, 20 | movies, 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/pages/about/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | // only import in Header 3 | import { Link } from 'react-router' 4 | import styles from './style' 5 | 6 | export default class About extends React.Component { 7 | 8 | render() { 9 | return ( 10 |
11 |
12 | Demo powered by v2rx 13 |
14 | 15 | 20 |
21 | ) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/components/Page/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | import React from 'react'; 4 | import './page.scss'; 5 | 6 | export default class Page extends React.Component { 7 | render() { 8 | const {title, subTitle, spacing, className, children} = this.props; 9 | 10 | return ( 11 |
12 |
13 |

{title}

14 |

{subTitle}

15 |
16 |
17 | {children} 18 |
19 |
20 | ); 21 | } 22 | }; -------------------------------------------------------------------------------- /src/app/actions/index.js: -------------------------------------------------------------------------------- 1 | import * as types from '../constants/ActionTypes' 2 | 3 | export function addTodo(text) { 4 | return { 5 | type: types.ADD_TODO, 6 | text 7 | } 8 | } 9 | 10 | export function deleteTodo(id) { 11 | return { 12 | type: types.DELETE_TODO, 13 | id 14 | } 15 | } 16 | 17 | export function editTodo(id, text) { 18 | return { 19 | type: types.EDIT_TODO, 20 | id, 21 | text 22 | } 23 | } 24 | 25 | export function completeTodo(id) { 26 | return { 27 | type: types.COMPLETE_TODO, 28 | id 29 | } 30 | } 31 | 32 | export function completeAll() { 33 | return { 34 | type: types.COMPLETE_ALL 35 | } 36 | } 37 | 38 | export function clearCompleted() { 39 | return { 40 | type: types.CLEAR_COMPLETED 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pepper", 3 | "version": "0.0.1", 4 | "description": "An front end solution with react", 5 | "main": "app.js", 6 | "author": "Damon", 7 | "license": "ISC", 8 | "scripts": { 9 | "start": "pepper start" 10 | }, 11 | "dependencies": { 12 | "classnames": "^1.2.0", 13 | "history": "^2.0.1", 14 | "js-cookie": "^2.1.0", 15 | "object-assign": "^4.0.1", 16 | "react": "^0.14.1", 17 | "react-dom": "^0.14.1", 18 | "react-redux": "^4.4.1", 19 | "react-router": "^2.0.0", 20 | "react-router-redux": "^4.0.1", 21 | "react-weui": "^0.4.0", 22 | "redux": "^3.3.1", 23 | "redux-logger": "^2.6.1", 24 | "redux-router": "^0.1.0", 25 | "redux-thunk": "^1.0.3", 26 | "reqwest": "^2.0.5", 27 | "store": "^1.3.20", 28 | "weui": "^0.4.2" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/app/store/configureStore.js: -------------------------------------------------------------------------------- 1 | import { createStore, applyMiddleware } from 'redux' 2 | import thunk from 'redux-thunk' 3 | import reducer from '../reducers' 4 | import { routerReducer, routerMiddleware } from 'react-router-redux' 5 | 6 | let middlewares = [thunk] 7 | let MODE = process.env.MODE 8 | 9 | if (MODE !== 'release') { 10 | let createLogger = require('redux-logger') 11 | const logger = createLogger({ 12 | level: 'info', 13 | logger: console, 14 | collapsed: true 15 | }) 16 | 17 | middlewares = [...middlewares, logger] 18 | } 19 | 20 | module.exports = function configureStore(history, initialState) { 21 | middlewares = [...middlewares, routerMiddleware(history)] 22 | const createStoreWithMiddleware = applyMiddleware(...middlewares)(createStore) 23 | return createStoreWithMiddleware(reducer, initialState) 24 | } 25 | -------------------------------------------------------------------------------- /dist/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 高考准考证 14 | 15 | 16 | 17 |
18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /src/pages/index.js: -------------------------------------------------------------------------------- 1 | import ReactDom from 'react-dom' 2 | import { Router, Route, Link, IndexRoute, useRouterHistory } from 'react-router' 3 | import { createHashHistory } from 'history' 4 | import { connect, Provider } from 'react-redux' 5 | import { syncHistoryWithStore } from 'react-router-redux' 6 | 7 | import objectAssign from 'object-assign' 8 | Object.assign = objectAssign 9 | 10 | import configureStore from 'app/store/configureStore' 11 | 12 | import Home from 'react-proxy?name=home!./home' 13 | import About from 'react-proxy?name=about!./about' 14 | 15 | const routes = (history) => ( 16 | 17 | 18 | 19 | 20 | ) 21 | 22 | const appHistory = useRouterHistory(createHashHistory)({ queryKey: false }) 23 | const store = configureStore(appHistory) 24 | const history = syncHistoryWithStore(appHistory, store) 25 | 26 | ReactDom.render( 27 | 28 | { routes(history) } 29 | , document.getElementById('app') 30 | ) 31 | -------------------------------------------------------------------------------- /template.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | {% if (o.htmlWebpackPlugin.files.favicon) { %} 11 | 12 | {% } %} 13 | {% for (var css in o.htmlWebpackPlugin.files.css) { %} 14 | 15 | {% } %} 16 | {% if(o.chunkManifest) { %} 17 | 18 | {% } %} 19 | {%=o.htmlWebpackPlugin.options.title || '高考准考证'%} 20 | 21 | 22 | 23 |
24 |
25 | {% for (var chunk in o.htmlWebpackPlugin.files.chunks) { %} 26 | 27 | {% } %} 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /src/app/reducers/todos.js: -------------------------------------------------------------------------------- 1 | import { ADD_TODO, DELETE_TODO, EDIT_TODO, COMPLETE_TODO, COMPLETE_ALL, CLEAR_COMPLETED } from '../constants/ActionTypes' 2 | 3 | const initialState = [{ 4 | text: 'Learn about actions', 5 | completed: false, 6 | id: 0 7 | }] 8 | 9 | export default function reducer(state = initialState, action) { 10 | switch (action.type) { 11 | case ADD_TODO: 12 | return [{ 13 | id: state.reduce((maxId, todo) => Math.max(todo.id, maxId), -1) + 1, 14 | completed: false, 15 | text: action.text 16 | }, 17 | ...state 18 | ] 19 | 20 | case DELETE_TODO: 21 | return state.filter(todo => 22 | todo.id !== action.id 23 | ) 24 | 25 | case EDIT_TODO: 26 | return state.map(todo => 27 | todo.id === action.id ? 28 | Object.assign({}, todo, { text: action.text }) : 29 | todo 30 | ) 31 | 32 | case COMPLETE_TODO: 33 | return state.map(todo => 34 | todo.id === action.id ? 35 | Object.assign({}, todo, { completed: !todo.completed }) : 36 | todo 37 | ) 38 | 39 | case COMPLETE_ALL: 40 | const areAllMarked = state.every(todo => todo.completed) 41 | return state.map(todo => Object.assign({}, todo, { 42 | completed: !areAllMarked 43 | })) 44 | 45 | case CLEAR_COMPLETED: 46 | return state.filter(todo => todo.completed === false) 47 | 48 | default: 49 | return state 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/components/Page/page.scss: -------------------------------------------------------------------------------- 1 | html, body, .container { 2 | height: 100%; 3 | } 4 | 5 | body { 6 | background-color: #FBF9FE; 7 | overflow-x: hidden; 8 | } 9 | 10 | .container{ 11 | overflow: hidden; 12 | } 13 | 14 | .page { 15 | position: absolute; 16 | top: 0; 17 | right: 0; 18 | bottom: 0; 19 | left: 0; 20 | background-color: #FBF9FE; 21 | .hd { 22 | padding: 2em 0; 23 | .title { 24 | text-align: center; 25 | font-size: 34px; 26 | color: #3CC51F; 27 | font-weight: 400; 28 | margin: 0 15%; 29 | } 30 | .sub_title { 31 | text-align: center; 32 | color: #888; 33 | font-size: 14px; 34 | } 35 | } 36 | .bd { 37 | &.spacing { 38 | padding: 0 15px; 39 | } 40 | } 41 | 42 | &.cell { 43 | .bd { 44 | padding-bottom: 30px; 45 | } 46 | } 47 | 48 | &.home { 49 | &.page-enter { 50 | z-index: 0; 51 | opacity: 1; 52 | transform: translate3d(0, 0, 0); 53 | &.page-enter-active { 54 | 55 | } 56 | } 57 | &.page-leave { 58 | &.page-leave-active { 59 | opacity: 1; 60 | transform: translate3d(0, 0, 0); 61 | } 62 | } 63 | } 64 | 65 | &.panel { 66 | .bd{ 67 | padding-bottom:20px; 68 | } 69 | } 70 | } 71 | 72 | .page-enter { 73 | z-index: 1024; 74 | opacity: 0.01; 75 | transform: translate3d(100%, 0, 0); 76 | transition: all .2s ease; 77 | &.page-enter-active { 78 | opacity: 1; 79 | transform: translate3d(0, 0, 0); 80 | } 81 | } 82 | 83 | .page-leave { 84 | opacity: 1; 85 | transform: translate3d(0, 0, 0); 86 | transition: all .2s ease; 87 | &.page-leave-active { 88 | opacity: 0.01; 89 | transform: translate3d(100%, 0, 0); 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/components/Header/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Header Component 3 | * 4 | * const menus = [{text: '', link: ''}] 5 | * const onClick = (e) => {} 6 | *
7 | */ 8 | 9 | import classnames from 'classnames' 10 | import { Link } from 'react-router' 11 | 12 | import styles from './style' 13 | 14 | export default class Header extends React.Component { 15 | 16 | // props constrants 17 | static propTypes = { 18 | className: React.PropTypes.string, 19 | menus:React.PropTypes.arrayOf(React.PropTypes.shape({ 20 | text: React.PropTypes.string, 21 | link: React.PropTypes.string 22 | })), 23 | onClick: React.PropTypes.func 24 | } 25 | 26 | constructor() { 27 | super(); 28 | } 29 | 30 | // default state definition 31 | state = { 32 | activeIndex: 0 33 | } 34 | 35 | onMenuClick(index, e) { 36 | this.props.onClick && this.props.onClick(index, e); 37 | this.setState({ 38 | activeIndex: index 39 | }); 40 | } 41 | 42 | render() { 43 | // current active index 44 | const activeIndex = this.state.activeIndex; 45 | 46 | // combine classname with active 47 | const menuClass = (index) => { 48 | return classnames(styles['nav-item'], { [styles['active']]: index === activeIndex }) 49 | } 50 | 51 | const menus = this.props.menus; 52 | 53 | return
54 | 65 |
66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/scss/reset.scss: -------------------------------------------------------------------------------- 1 | @charset "UTF-8"; 2 | 3 | /* reset */ 4 | html, body, div, span, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre, abbr, address, cite, code, del, dfn, em, img, ins, kbd, q, samp, small, strong, sub, sup, var, b, i, dl, dt, dd, ol, ul, li, fieldset, form, label, legend, table, caption, tbody, tfoot, thead, tr, th, td, article, aside, canvas, details, figcaption, figure, footer, header, hgroup, menu, nav, section, summary, time, mark, audio, video { 5 | margin: 0; 6 | padding: 0; 7 | outline: 0; 8 | border: 0; 9 | background: transparent; 10 | vertical-align: baseline; 11 | font-style: inherit; 12 | font-size: 100%; } 13 | 14 | html { 15 | font-family: sans-serif; 16 | -ms-text-size-adjust: 100%; 17 | -webkit-text-size-adjust: 100%; } 18 | 19 | body { 20 | line-height: 1; } 21 | 22 | li { 23 | list-style: none; } 24 | 25 | a:active { 26 | outline: 0; } 27 | 28 | button[disabled], html input[disabled] { 29 | cursor: default; } 30 | 31 | img { 32 | vertical-align: middle; } 33 | 34 | button, input, select, textarea { 35 | margin: 0; 36 | font-size: 100%; 37 | font-family: inherit; } 38 | 39 | button, input { 40 | line-height: normal; } 41 | 42 | button, select { 43 | text-transform: none; } 44 | 45 | button, html input[type="button"], input[type="reset"], input[type="submit"] { 46 | cursor: pointer; 47 | -webkit-appearance: button; } 48 | 49 | input[type="search"] { 50 | -webkit-box-sizing: content-box; 51 | -moz-box-sizing: content-box; 52 | box-sizing: content-box; 53 | -webkit-appearance: textfield; } 54 | 55 | input[type="search"]::-webkit-search-cancel-button, input[type="search"]::-webkit-search-decoration { 56 | -webkit-appearance: none; } 57 | 58 | audio:not([controls]) { 59 | display: none; 60 | height: 0; } 61 | 62 | [hidden], template { 63 | display: none; } 64 | 65 | dfn { 66 | font-style: italic; } 67 | 68 | code, kbd, pre, samp { 69 | font-size: 1em; 70 | font-family: monospace, serif; } 71 | 72 | blockquote, q { 73 | quotes: none; } 74 | 75 | blockquote:before, blockquote:after, q:before, q:after { 76 | content: ''; 77 | content: none; } 78 | 79 | table { 80 | border-spacing: 0; 81 | border-collapse: collapse; } 82 | 83 | 84 | // // font 85 | 86 | // @font-face { 87 | // font-family: 'digital'; 88 | // src: 89 | // url('../assets/fonts/iconfont/ds-webfont.woff2') format('woff2'), 90 | // url('../assets/fonts/iconfont/ds-webfont.woff') format('woff'), 91 | // url('../assets/fonts/iconfont/ds-webfont.ttf') format('truetype'); 92 | // font-weight: normal; 93 | // font-style: normal; 94 | 95 | // } 96 | -------------------------------------------------------------------------------- /src/utils/cache.js: -------------------------------------------------------------------------------- 1 | import store from 'store' 2 | import LRU from './lru' 3 | import cookie from 'js-cookie' 4 | 5 | let __session__ = sessionStorage 6 | let __memory__ = new LRU(); 7 | 8 | const TYPES = ['memory', 'session', 'store', 'cookie']; 9 | const DEFAULT_CACHE = TYPES[1] 10 | 11 | const cache = { 12 | set: (key, value, type = DEFAULT_CACHE) => { 13 | !(type in TYPES) && (type = DEFAULT_CACHE); 14 | cache[type].set(key, value); 15 | return cache[type]; 16 | }, 17 | get: (key, type = DEFAULT_CACHE) => { 18 | !(type in TYPES) && (type = DEFAULT_CACHE); 19 | return cache[type].get(key); 20 | }, 21 | clear: (type = DEFAULT_CACHE) => { 22 | !(type in TYPES) && (type = DEFAULT_CACHE); 23 | cache[type].clear(); 24 | return cache[type]; 25 | }, 26 | remove: (key, type = DEFAULT_CACHE) => { 27 | cache[type].remove(key); 28 | return cache; 29 | }, 30 | session: { 31 | set: (key, value) => { 32 | __session__.setItem(key, JSON.stringify(value)); 33 | return cache.session; 34 | }, 35 | get: (key) => { 36 | let value = __session__.getItem(key); 37 | return key && JSON.parse(value); 38 | }, 39 | remove: (key) => { 40 | __session__.removeItem(key); 41 | return cache.session; 42 | }, 43 | clear: () => { 44 | __session__.clear(); 45 | return cache.session; 46 | } 47 | }, 48 | memory: { 49 | set: (key, value) => { 50 | __memory__[key] = value; 51 | return cache.memory; 52 | }, 53 | get: (key) => { 54 | return __memory__[key]; 55 | }, 56 | remove: (key) => { 57 | delete __memory__[key]; 58 | return cache.memory; 59 | }, 60 | clear: () => { 61 | __memory__ = new LRU(); 62 | return cache.memory; 63 | } 64 | }, 65 | store: store.enabled ? store : cookie, 66 | cookie: { 67 | // expire Day 68 | set: (key, value, expires = 7, path: '') => { 69 | cookie.set(key, value); 70 | return cache.cookie; 71 | }, 72 | get: (key) => { 73 | return cookie.get(key); 74 | }, 75 | remove: (key) => { 76 | cookie.remove(key); 77 | return cache.cookie; 78 | }, 79 | clear: () => { 80 | cookie.remove('isNew'); 81 | cookie.remove('PHPSESSIS'); 82 | cookie.remove('sUin'); 83 | cookie.remove('userId'); 84 | return cache.cookie; 85 | } 86 | } 87 | } 88 | 89 | if(!store.enabled) { 90 | alert('为了更好的使用体验,请关闭隐私模式'); 91 | cache.store = cache.cookie; 92 | cache.session = cache.memory; 93 | } 94 | 95 | 96 | export default cache 97 | -------------------------------------------------------------------------------- /v2rx.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | // debug host 3 | "host": "0.0.0.0", 4 | 5 | // debug port 6 | "port": "9527", 7 | 8 | // pepper src entry, also inner webpack entry, default to `src/pages/index.js` 9 | "base": "src", 10 | 11 | // target build dir 12 | "build": "dist", 13 | 14 | // CDN domain, or just leave it blank 15 | "static": { 16 | "start" : "", // here use relative path 17 | "test" : "", 18 | "pre" : "//static.v2rx.com/",// here use CDN domain 19 | "release" : "//static.v2rx.com/" // here use CDN domain 20 | }, 21 | 22 | // globals 23 | "globals": { // 配置全局变量,这里配置了 static_api 和 api 两个全局变量 24 | // config `mock.js` for CROS solution 25 | "static_api": { 26 | "start" : "", // local api base entry 27 | "test" : "//m.v2rx.com", 28 | "pre" : "//m.v2rx.com", // online api base entry 29 | "release" : "//m.v2rx.com" 30 | }, 31 | "api": { 32 | "start" : "", 33 | "test" : "", 34 | "pre" : "//api.v2rx.com", 35 | "release" : "//api.v2rx.com" 36 | } 37 | }, 38 | 39 | // third patry libs to bundle 40 | "vendor": ["react", "react-dom"], 41 | 42 | // dir alias, could use globally, despite of CWD 43 | "alias": { 44 | "scss" : "scss", 45 | "components" : "components", 46 | "utils" : "utils", 47 | "assets" : "assets", 48 | "app" : "app" 49 | 50 | }, 51 | 52 | // source map options 53 | "devtool": "source-map", 54 | 55 | // switch for CSS Modules 56 | "css_modules": false, 57 | 58 | // switch for eslint 59 | "eslint": false, 60 | 61 | // template settings 62 | "template": { 63 | "title" : "高考准考证", // inner template document title 64 | "keywords" : "", // inner template meta keywords 65 | "description" : "", // inner template meta description 66 | "viewport" : "", // inner template meta viewport 67 | "favicon" : "", // website favicon path 68 | "path" : "template.html" // custom template path, omit it if your desire to use inner template 69 | }, 70 | 71 | // custom default page dir 72 | "pages": "pages", 73 | 74 | // custom default component dir 75 | "components": "components", 76 | 77 | // custom default scss dir 78 | "scss": "scss", 79 | 80 | 81 | // switch template ES mode, ['es5' or 'es6'] 82 | "esmode": "es6", 83 | 84 | // switch for transfering assets dir to dist when build 85 | "transfer_assets": false, 86 | 87 | // limit image size for use base64, (smaller use base64, larger use url) 88 | "base64_image_limit": 10240 // 10k 89 | } 90 | -------------------------------------------------------------------------------- /src/utils/lru.js: -------------------------------------------------------------------------------- 1 | function LRU (opts) { 2 | if (!(this instanceof LRU)) return new LRU(opts) 3 | if (typeof opts === 'number') opts = {max: opts} 4 | if (!opts) opts = {} 5 | this.cache = {} 6 | this.head = this.tail = null 7 | this.length = 0 8 | this.max = opts.max || 1000 9 | this.maxAge = opts.maxAge || 0 10 | } 11 | 12 | LRU.prototype.remove = function (key) { 13 | if (typeof key !== 'string') key = '' + key 14 | if (!this.cache.hasOwnProperty(key)) return 15 | 16 | let element = this.cache[key] 17 | delete this.cache[key] 18 | this._unlink(key, element.prev, element.next) 19 | return element.value 20 | } 21 | 22 | LRU.prototype._unlink = function (key, prev, next) { 23 | this.length-- 24 | 25 | if (this.length === 0) { 26 | this.head = this.tail = null 27 | } else { 28 | if (this.head === key) { 29 | this.head = prev 30 | this.cache[this.head].next = null 31 | } else if (this.tail === key) { 32 | this.tail = next 33 | this.cache[this.tail].prev = null 34 | } else { 35 | this.cache[prev].next = next 36 | this.cache[next].prev = prev 37 | } 38 | } 39 | } 40 | 41 | LRU.prototype.peek = function (key) { 42 | return this.cache.hasOwnProperty(key) ? this.cache[key].value : null 43 | } 44 | 45 | LRU.prototype.set = function (key, value) { 46 | if (typeof key !== 'string') key = '' + key 47 | 48 | let element 49 | 50 | if (this.cache.hasOwnProperty(key)) { 51 | element = this.cache[key] 52 | element.value = value 53 | if (this.maxAge) element.modified = Date.now() 54 | 55 | // If it's already the head, there's nothing more to do: 56 | if (key === this.head) return value 57 | this._unlink(key, element.prev, element.next) 58 | } else { 59 | element = {value: value, modified: 0, next: null, prev: null} 60 | if (this.maxAge) element.modified = Date.now() 61 | this.cache[key] = element 62 | 63 | // Eviction is only possible if the key didn't already exist: 64 | if (this.length === this.max) this.evict() 65 | } 66 | 67 | this.length++ 68 | element.next = null 69 | element.prev = this.head 70 | 71 | if (this.head) this.cache[this.head].next = key 72 | this.head = key 73 | 74 | if (!this.tail) this.tail = key 75 | return value 76 | } 77 | 78 | LRU.prototype.get = function (key) { 79 | if (typeof key !== 'string') key = '' + key 80 | if (!this.cache.hasOwnProperty(key)) return 81 | 82 | let element = this.cache[key] 83 | 84 | if (this.maxAge && (Date.now() - element.modified) > this.maxAge) { 85 | this.remove(key) 86 | return 87 | } 88 | 89 | if (this.head !== key) { 90 | if (key === this.tail) { 91 | this.tail = element.next 92 | this.cache[this.tail].prev = null 93 | } else { 94 | // Set prev.next -> element.next: 95 | this.cache[element.prev].next = element.next 96 | } 97 | 98 | // Set element.next.prev -> element.prev: 99 | this.cache[element.next].prev = element.prev 100 | 101 | // Element is the new head 102 | this.cache[this.head].next = key 103 | element.prev = this.head 104 | element.next = null 105 | this.head = key 106 | } 107 | 108 | return element.value 109 | } 110 | 111 | LRU.prototype.evict = function () { 112 | if (!this.tail) return 113 | let key = this.tail 114 | let value = this.remove(this.tail) 115 | } 116 | 117 | export default LRU 118 | -------------------------------------------------------------------------------- /src/utils/validator.js: -------------------------------------------------------------------------------- 1 | /** 2 | var validator = new Validator({ 3 | username: [ 4 | Validator.required('请输入您的用户名'), 5 | ] 6 | }); 7 | 8 | validator.validate(formData) 9 | */ 10 | 11 | class Validator { 12 | constructor (fields) { 13 | this.fields = fields; 14 | } 15 | 16 | validate (value) { 17 | var fields = this.fields; 18 | var keys = Object.keys(fields); 19 | 20 | return new Promise(function (res, rej) { 21 | var key, rules, rule; 22 | for (var i = 0; i < keys.length; i++) { 23 | key = keys[i]; 24 | rules = fields[key]; 25 | 26 | if (!Array.isArray(rules)) { 27 | rules = [rules]; 28 | } 29 | 30 | for (var j = 0; j < rules.length; j++) { 31 | rule = rules[j]; 32 | try { 33 | rule(value[key]); 34 | } catch (ex) { 35 | rej(ex); 36 | return; 37 | } 38 | } 39 | } 40 | 41 | res(value); 42 | }); 43 | } 44 | } 45 | 46 | export default Validator; 47 | 48 | export function required (msg) { 49 | return function (value) { 50 | var error = new Error(msg); 51 | 52 | if (Array.isArray(value) && value.length) { 53 | return; 54 | } 55 | 56 | if (typeof value === 'undefined' || value === null) { 57 | throw error; 58 | } 59 | }; 60 | } 61 | 62 | export function maxLength (maxLength, msg) { 63 | return function (value) { 64 | if (!value || value.length > maxLength) { 65 | throw new Error(msg); 66 | } 67 | }; 68 | } 69 | 70 | export function minLength (minLength, msg) { 71 | return function (value) { 72 | if (!value || value.length < minLength) { 73 | throw new Error(msg); 74 | } 75 | }; 76 | } 77 | 78 | export function max (max, msg) { 79 | return function (value) { 80 | if (value > max) { 81 | throw new Error(msg); 82 | } 83 | }; 84 | } 85 | 86 | export function min (min, msg) { 87 | return function (value) { 88 | if (value < min) { 89 | throw new Error(msg); 90 | } 91 | }; 92 | } 93 | 94 | export function regexp (re, msg) { 95 | return function (value) { 96 | if (!re.test(value)) { 97 | throw new Error(msg); 98 | } 99 | }; 100 | } 101 | 102 | export function chineseCitizenIdNumber (msg) { 103 | return function (value) { 104 | var error = new Error(msg); 105 | if (!value || value.length !== 18) { 106 | throw error; 107 | } 108 | }; 109 | } 110 | 111 | export function format (format, msg) { 112 | var re = null; 113 | 114 | if (format === 'email') { 115 | re = /^[_a-z0-9\-]+(\.[_a-z0-9\-]+)*@[a-z0-9\-]+(\.[a-z0-9\-]+)*(\.[a-z]+)$/; 116 | } else if (format === 'phone') { 117 | re = /^1[34578]\d{9}$/; 118 | } else if (format === 'number') { 119 | re = /^[+-]?[0-9]*$/; 120 | } else if (format === 'chinese_english') { 121 | re = /^[\u4E00-\u9FA5\uF900-\uFA2Da-zA-Z]+$/; 122 | } 123 | if (!re) { 124 | throw new Error('UNKNOW_FORMAT_ERROR'); 125 | } 126 | 127 | return regexp(re, msg); 128 | } -------------------------------------------------------------------------------- /dist/js/manifest-c1371331.js: -------------------------------------------------------------------------------- 1 | /******/ (function(modules) { // webpackBootstrap 2 | /******/ // install a JSONP callback for chunk loading 3 | /******/ var parentJsonpFunction = window["webpackJsonp"]; 4 | /******/ window["webpackJsonp"] = function webpackJsonpCallback(chunkIds, moreModules) { 5 | /******/ // add "moreModules" to the modules object, 6 | /******/ // then flag all "chunkIds" as loaded and fire callback 7 | /******/ var moduleId, chunkId, i = 0, callbacks = []; 8 | /******/ for(;i < chunkIds.length; i++) { 9 | /******/ chunkId = chunkIds[i]; 10 | /******/ if(installedChunks[chunkId]) 11 | /******/ callbacks.push.apply(callbacks, installedChunks[chunkId]); 12 | /******/ installedChunks[chunkId] = 0; 13 | /******/ } 14 | /******/ for(moduleId in moreModules) { 15 | /******/ modules[moduleId] = moreModules[moduleId]; 16 | /******/ } 17 | /******/ if(parentJsonpFunction) parentJsonpFunction(chunkIds, moreModules); 18 | /******/ while(callbacks.length) 19 | /******/ callbacks.shift().call(null, __webpack_require__); 20 | /******/ if(moreModules[0]) { 21 | /******/ installedModules[0] = 0; 22 | /******/ return __webpack_require__(0); 23 | /******/ } 24 | /******/ }; 25 | 26 | /******/ // The module cache 27 | /******/ var installedModules = {}; 28 | 29 | /******/ // object to store loaded and loading chunks 30 | /******/ // "0" means "already loaded" 31 | /******/ // Array means "loading", array contains callbacks 32 | /******/ var installedChunks = { 33 | /******/ 4:0 34 | /******/ }; 35 | 36 | /******/ // The require function 37 | /******/ function __webpack_require__(moduleId) { 38 | 39 | /******/ // Check if module is in cache 40 | /******/ if(installedModules[moduleId]) 41 | /******/ return installedModules[moduleId].exports; 42 | 43 | /******/ // Create a new module (and put it into the cache) 44 | /******/ var module = installedModules[moduleId] = { 45 | /******/ exports: {}, 46 | /******/ id: moduleId, 47 | /******/ loaded: false 48 | /******/ }; 49 | 50 | /******/ // Execute the module function 51 | /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); 52 | 53 | /******/ // Flag the module as loaded 54 | /******/ module.loaded = true; 55 | 56 | /******/ // Return the exports of the module 57 | /******/ return module.exports; 58 | /******/ } 59 | 60 | /******/ // This file contains only the entry chunk. 61 | /******/ // The chunk loading function for additional chunks 62 | /******/ __webpack_require__.e = function requireEnsure(chunkId, callback) { 63 | /******/ // "0" is the signal for "already loaded" 64 | /******/ if(installedChunks[chunkId] === 0) 65 | /******/ return callback.call(null, __webpack_require__); 66 | 67 | /******/ // an array means "currently loading". 68 | /******/ if(installedChunks[chunkId] !== undefined) { 69 | /******/ installedChunks[chunkId].push(callback); 70 | /******/ } else { 71 | /******/ // start chunk loading 72 | /******/ installedChunks[chunkId] = [callback]; 73 | /******/ var head = document.getElementsByTagName('head')[0]; 74 | /******/ var script = document.createElement('script'); 75 | /******/ script.type = 'text/javascript'; 76 | /******/ script.charset = 'utf-8'; 77 | /******/ script.async = true; 78 | 79 | /******/ script.src = __webpack_require__.p + "js/" + ({"0":"shared","1":"home","2":"about","3":"vendor"}[chunkId]||chunkId) + "-" + {"0":"86b256a7","1":"8000d244","2":"68c021d2","3":"fe6beca7"}[chunkId] + ".js"; 80 | /******/ head.appendChild(script); 81 | /******/ } 82 | /******/ }; 83 | 84 | /******/ // expose the modules object (__webpack_modules__) 85 | /******/ __webpack_require__.m = modules; 86 | 87 | /******/ // expose the module cache 88 | /******/ __webpack_require__.c = installedModules; 89 | 90 | /******/ // __webpack_public_path__ 91 | /******/ __webpack_require__.p = ""; 92 | /******/ }) 93 | /************************************************************************/ 94 | /******/ ([]); -------------------------------------------------------------------------------- /src/utils/device.js: -------------------------------------------------------------------------------- 1 | let userAgent = (window.navigator && navigator.userAgent) || ""; 2 | 3 | function detect(pattern) { 4 | return function () { 5 | return (pattern).test(userAgent); 6 | }; 7 | } 8 | 9 | export default { 10 | /** 11 | * Return true if the browser is Chrome or compatible. 12 | * 13 | * @method isChrome 14 | */ 15 | isChrome: detect(/webkit\W.*(chrome|chromium)\W/i), 16 | 17 | /** 18 | * Return true if the browser is Firefox. 19 | * 20 | * @method isFirefox 21 | */ 22 | isFirefox: detect(/mozilla.*\Wfirefox\W/i), 23 | 24 | /** 25 | * Return true if the browser is using the Gecko engine. 26 | * 27 | * This is probably a better way to identify Firefox and other browsers 28 | * that use XulRunner. 29 | * 30 | * @method isGecko 31 | */ 32 | isGecko: detect(/mozilla(?!.*webkit).*\Wgecko\W/i), 33 | 34 | /** 35 | * Return true if the browser is Internet Explorer. 36 | * 37 | * @method isIE 38 | */ 39 | isIE: function () { 40 | if (navigator.appName === "Microsoft Internet Explorer") { 41 | return true; 42 | } else if (detect(/\bTrident\b/)) { 43 | return true; 44 | } else { 45 | return false; 46 | } 47 | }, 48 | 49 | 50 | /** 51 | * Return true if the browser is running on Kindle. 52 | * 53 | * @method isKindle 54 | */ 55 | isKindle: detect(/\W(kindle|silk)\W/i), 56 | 57 | /** 58 | * Return true if the browser is running on a mobile device. 59 | * 60 | * @method isMobile 61 | */ 62 | isMobile: detect(/(iphone|ipod|((?:android)?.*?mobile)|blackberry|nokia)/i), 63 | 64 | /** 65 | * Return true if we are running on Opera. 66 | * 67 | * @method isOpera 68 | */ 69 | isOpera: detect(/opera.*\Wpresto\W|OPR/i), 70 | 71 | /** 72 | * Return true if the browser is Safari. 73 | * 74 | * @method isSafari 75 | */ 76 | isSafari: detect(/webkit\W(?!.*chrome).*safari\W/i), 77 | 78 | /** 79 | * Return true if the browser is running on a tablet. 80 | * 81 | * One way to distinguish Android mobiles from tablets is that the 82 | * mobiles contain the string "mobile" in their UserAgent string. 83 | * If the word "Android" isn't followed by "mobile" then its a 84 | * tablet. 85 | * 86 | * @method isTablet 87 | */ 88 | isTablet: detect(/(ipad|android(?!.*mobile)|tablet)/i), 89 | 90 | /** 91 | * Return true if the browser is running on a TV! 92 | * 93 | * @method isTV 94 | */ 95 | isTV: detect(/googletv|sonydtv/i), 96 | 97 | /** 98 | * Return true if the browser is running on a WebKit browser. 99 | * 100 | * @method isWebKit 101 | */ 102 | isWebKit: detect(/webkit\W/i), 103 | 104 | /** 105 | * Return true if the browser is running on an Android browser. 106 | * 107 | * @method isAndroid 108 | */ 109 | isAndroid: detect(/android/i), 110 | 111 | /** 112 | * Return true if the browser is running on any iOS device. 113 | * 114 | * @method isIOS 115 | */ 116 | isIOS: detect(/(ipad|iphone|ipod)/i), 117 | 118 | /** 119 | * Return true if the browser is running on an iPad. 120 | * 121 | * @method isIPad 122 | */ 123 | isIPad: detect(/ipad/i), 124 | 125 | /** 126 | * Return true if the browser is running in Wechat. 127 | * 128 | * @method isIPad 129 | */ 130 | isWechat: detect(/MicroMessenger/i), 131 | 132 | /** 133 | * Return true if the browser is running on an iPhone. 134 | * 135 | * @method isIPhone 136 | */ 137 | isIPhone: detect(/iphone/i), 138 | 139 | /** 140 | * Return true if the browser is running on an iPod touch. 141 | * 142 | * @method isIPod 143 | */ 144 | isIPod: detect(/ipod/i), 145 | 146 | /** 147 | * Return the complete UserAgent string verbatim. 148 | * 149 | * @method whoami 150 | */ 151 | whoami: function () { 152 | return userAgent; 153 | } 154 | }; 155 | -------------------------------------------------------------------------------- /src/scss/normalize.scss: -------------------------------------------------------------------------------- 1 | // Based on normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css 2 | // This normalize aims to reduce the number of rules and focus on Chrome. 3 | 4 | // 5 | // 1. Normalize vertical alignment of `progress` in Chrome. 6 | // 7 | 8 | audio, 9 | canvas, 10 | progress, 11 | video { 12 | vertical-align: baseline; // 1 13 | } 14 | 15 | // 16 | // Prevent modern browsers from displaying `audio` without controls. 17 | // 18 | 19 | audio:not([controls]) { 20 | display: none; 21 | } 22 | 23 | // Links 24 | // ========================================================================== 25 | 26 | // 27 | // Improve readability of focused elements when they are also in an 28 | // active/hover state. 29 | // 30 | 31 | a:active, 32 | a:hover { 33 | outline: 0; 34 | } 35 | 36 | // Text-level semantics 37 | // ========================================================================== 38 | 39 | // 40 | // Address styling not present in IE 8/9/10/11, Safari, and Chrome. 41 | // 42 | 43 | abbr[title] { 44 | border-bottom: 1px dotted; 45 | } 46 | 47 | // 48 | // Address style set to `bolder` in Chrome. 49 | // 50 | 51 | b, 52 | strong { 53 | font-weight: bold; 54 | } 55 | 56 | // 57 | // Address styling not present in Chrome. 58 | // 59 | 60 | dfn { 61 | font-style: italic; 62 | } 63 | 64 | // 65 | // Address variable `h1` font-size and margin within `section` and `article` 66 | // contexts in Chrome. 67 | // 68 | 69 | h1 { 70 | font-size: 2em; 71 | margin: 0.67em 0; 72 | } 73 | 74 | // 75 | // Address inconsistent and variable font size. 76 | // 77 | 78 | small { 79 | font-size: 80%; 80 | } 81 | 82 | // 83 | // Prevent `sub` and `sup` affecting `line-height`. 84 | // 85 | 86 | sub, 87 | sup { 88 | font-size: 75%; 89 | line-height: 0; 90 | position: relative; 91 | vertical-align: baseline; 92 | } 93 | 94 | sup { 95 | top: -0.5em; 96 | } 97 | 98 | sub { 99 | bottom: -0.25em; 100 | } 101 | 102 | // Grouping content 103 | // ========================================================================== 104 | 105 | // 106 | // Contain overflow. 107 | // 108 | 109 | pre { 110 | overflow: auto; 111 | } 112 | 113 | // 114 | // Address odd `em`-unit font size rendering. 115 | // 116 | 117 | code, 118 | kbd, 119 | pre, 120 | samp { 121 | font-family: monospace, monospace; 122 | font-size: 1em; 123 | } 124 | 125 | // Forms 126 | // ========================================================================== 127 | 128 | // 129 | // Known limitation: by default, Chrome allows very limited 130 | // styling of `select`, unless a `border` property is set. 131 | // 132 | 133 | // 134 | // 1. Correct color not being inherited. 135 | // Known issue: affects color of disabled elements. 136 | // 2. Correct font properties not being inherited. 137 | // 3. Resets margin 138 | // 139 | 140 | button, 141 | input, 142 | optgroup, 143 | select, 144 | textarea { 145 | color: inherit; // 1 146 | font: inherit; // 2 147 | margin: 0; // 3 148 | } 149 | 150 | // 151 | // Fix the cursor style for Chrome's increment/decrement buttons. For certain 152 | // `font-size` values of the `input`, it causes the cursor style of the 153 | // decrement button to change from `default` to `text`. 154 | // 155 | 156 | input[type="number"]::-webkit-inner-spin-button, 157 | input[type="number"]::-webkit-outer-spin-button { 158 | height: auto; 159 | } 160 | 161 | // 162 | // 1. Address `appearance` set to `searchfield` in Chrome. 163 | // 2. Address `box-sizing` set to `border-box` in Chrome. 164 | // 165 | 166 | input[type="search"] { 167 | -webkit-appearance: textfield; // 1 168 | box-sizing: content-box; // 2 169 | } 170 | 171 | // 172 | // Remove inner padding and search cancel button in Chrome on OS X. 173 | // 174 | 175 | input[type="search"]::-webkit-search-cancel-button, 176 | input[type="search"]::-webkit-search-decoration { 177 | -webkit-appearance: none; 178 | } 179 | 180 | // 181 | // Define consistent border, margin, and padding. 182 | // 183 | 184 | fieldset { 185 | border: 1px solid #c0c0c0; 186 | margin: 0 2px; 187 | padding: 0.35em 0.625em 0.75em; 188 | } 189 | 190 | // 191 | // 1. Correct `color` not being inherited in IE 8/9/10/11. 192 | // 2. Remove padding so people aren't caught out if they zero out fieldsets. 193 | // 194 | 195 | legend { 196 | border: 0; // 1 197 | padding: 0; // 2 198 | } 199 | 200 | // Tables 201 | // ========================================================================== 202 | 203 | // 204 | // Remove most spacing between table cells. 205 | // 206 | 207 | table { 208 | border-collapse: collapse; 209 | border-spacing: 0; 210 | } 211 | 212 | td, 213 | th { 214 | padding: 0; 215 | } 216 | -------------------------------------------------------------------------------- /src/pages/home/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import WeUI from 'react-weui'; 4 | import canvas2Image from 'utils/canvas2img' 5 | import 'weui'; 6 | import Page from 'components/Page'; 7 | import baseImg from 'assets/images/base.jpg'; 8 | const { 9 | ButtonArea, 10 | Button, 11 | Cells, 12 | CellsTitle, 13 | CellsTips, 14 | Cell, 15 | CellHeader, 16 | CellBody, 17 | CellFooter, 18 | Form, 19 | FormCell, 20 | Icon, 21 | Input, 22 | Label, 23 | TextArea, 24 | Switch, 25 | Radio, 26 | Checkbox, 27 | Select, 28 | Uploader 29 | } = WeUI; 30 | 31 | 32 | class App extends React.Component { 33 | state = { 34 | show: false, 35 | timer: null, 36 | ctx: null, 37 | src:null, 38 | formData:{ 39 | gender: '男', 40 | name: '拾文', 41 | location: '腾冲市第一中学' 42 | } 43 | }; 44 | 45 | componentDidMount() { 46 | this.show(); 47 | window.form = this.refs.form; 48 | } 49 | 50 | render() { 51 | return ( 52 | 53 |
{ 55 | e.preventDefault(); 56 | this.submit(e); 57 | }}> 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 |
68 | 性别 69 |
70 | 71 | 72 | 73 | {this.changeFn(e)}} name="gender" value="1" defaultChecked/> 74 | 75 | 76 | 77 | 78 | 79 | {this.changeFn(e)}} name="gender" value="2"/> 80 | 81 | 82 |
83 |
84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 |
93 | 94 | 95 | 96 | 97 | 98 | 99 |
100 | 102 | 106 | 107 |
108 | 109 |
110 | ); 111 | } 112 | drawImg(ctx) { 113 | var img = new Image(); 114 | img.onload = function () { 115 | ctx.scale(0.6, 0.6); 116 | ctx.drawImage(img, 0, 0); 117 | } 118 | img.src = baseImg; 119 | } 120 | show() { 121 | var canvas = this.refs.canvas; 122 | var ctx = canvas.getContext('2d'); 123 | this.drawImg(ctx); 124 | this.setState({ctx: ctx}) 125 | } 126 | changeFn(e){ 127 | var formData = this.state.formData; 128 | var gender = ['', '男', '女']; 129 | formData['gender'] = gender[e.currentTarget.value]; 130 | this.setState({formData: formData}); 131 | } 132 | handleChange(event){ 133 | var formData = this.state.formData; 134 | formData[event.target.name] = event.target.value; 135 | this.setState({formData: formData}); 136 | } 137 | submit(e){ 138 | this.addText(this.state.ctx, this.state.formData); 139 | this.saveImg(); 140 | } 141 | saveImg(){ 142 | var w = 484; 143 | var h = 640; 144 | var img = canvas2Image.convertToJPEG(this.refs.canvas, w, h); 145 | this.setState({src: img.src}); 146 | } 147 | addText(ctx, formData) { 148 | var room = Math.ceil(Math.random() * 40); 149 | var name = formData.name; 150 | var gender = formData.gender; 151 | var location = formData.location; 152 | var seat = Math.ceil(Math.random() * 60); 153 | this.setState({show: true}); 154 | if (ctx) { 155 | ctx.font="28px 宋体"; 156 | ctx.fillStyle="#000"; 157 | ctx.fillText(name,410,415); 158 | ctx.fillText(gender,640,415); 159 | ctx.fillText(location,410,485); 160 | ctx.fillText(room,210,795); 161 | ctx.fillText(seat,210,870); 162 | } 163 | } 164 | } 165 | 166 | export default App; -------------------------------------------------------------------------------- /src/utils/canvas2img.js: -------------------------------------------------------------------------------- 1 | /** 2 | * covert canvas to image 3 | * and save the image file 4 | */ 5 | 6 | var Canvas2Image = function () { 7 | 8 | // check if support sth. 9 | var $support = function () { 10 | var canvas = document.createElement('canvas'), 11 | ctx = canvas.getContext('2d'); 12 | 13 | return { 14 | canvas: !!ctx, 15 | imageData: !!ctx.getImageData, 16 | dataURL: !!canvas.toDataURL, 17 | btoa: !!window.btoa 18 | }; 19 | }(); 20 | 21 | var downloadMime = 'image/octet-stream'; 22 | 23 | function scaleCanvas (canvas, width, height) { 24 | var w = canvas.width, 25 | h = canvas.height; 26 | if (width == undefined) { 27 | width = w; 28 | } 29 | if (height == undefined) { 30 | height = h; 31 | } 32 | 33 | var retCanvas = document.createElement('canvas'); 34 | var retCtx = retCanvas.getContext('2d'); 35 | retCanvas.width = width; 36 | retCanvas.height = height; 37 | retCtx.drawImage(canvas, 0, 0, w, h, 0, 0, width, height); 38 | return retCanvas; 39 | } 40 | 41 | function getDataURL (canvas, type, width, height) { 42 | canvas = scaleCanvas(canvas, width, height); 43 | return canvas.toDataURL(type); 44 | } 45 | 46 | function saveFile (strData) { 47 | document.location.href = strData; 48 | } 49 | 50 | function genImage(strData) { 51 | var img = document.createElement('img'); 52 | img.src = strData; 53 | return img; 54 | } 55 | function fixType (type) { 56 | type = type.toLowerCase().replace(/jpg/i, 'jpeg'); 57 | var r = type.match(/png|jpeg|bmp|gif/)[0]; 58 | return 'image/' + r; 59 | } 60 | function encodeData (data) { 61 | if (!window.btoa) { throw 'btoa undefined' } 62 | var str = ''; 63 | if (typeof data == 'string') { 64 | str = data; 65 | } else { 66 | for (var i = 0; i < data.length; i ++) { 67 | str += String.fromCharCode(data[i]); 68 | } 69 | } 70 | 71 | return btoa(str); 72 | } 73 | function getImageData (canvas) { 74 | var w = canvas.width, 75 | h = canvas.height; 76 | return canvas.getContext('2d').getImageData(0, 0, w, h); 77 | } 78 | function makeURI (strData, type) { 79 | return 'data:' + type + ';base64,' + strData; 80 | } 81 | 82 | 83 | /** 84 | * create bitmap image 85 | * 按照规则生成图片响应头和响应体 86 | */ 87 | var genBitmapImage = function (oData) { 88 | 89 | // 90 | // BITMAPFILEHEADER: http://msdn.microsoft.com/en-us/library/windows/desktop/dd183374(v=vs.85).aspx 91 | // BITMAPINFOHEADER: http://msdn.microsoft.com/en-us/library/dd183376.aspx 92 | // 93 | 94 | var biWidth = oData.width; 95 | var biHeight = oData.height; 96 | var biSizeImage = biWidth * biHeight * 3; 97 | var bfSize = biSizeImage + 54; // total header size = 54 bytes 98 | 99 | // 100 | // typedef struct tagBITMAPFILEHEADER { 101 | // WORD bfType; 102 | // DWORD bfSize; 103 | // WORD bfReserved1; 104 | // WORD bfReserved2; 105 | // DWORD bfOffBits; 106 | // } BITMAPFILEHEADER; 107 | // 108 | var BITMAPFILEHEADER = [ 109 | // WORD bfType -- The file type signature; must be "BM" 110 | 0x42, 0x4D, 111 | // DWORD bfSize -- The size, in bytes, of the bitmap file 112 | bfSize & 0xff, bfSize >> 8 & 0xff, bfSize >> 16 & 0xff, bfSize >> 24 & 0xff, 113 | // WORD bfReserved1 -- Reserved; must be zero 114 | 0, 0, 115 | // WORD bfReserved2 -- Reserved; must be zero 116 | 0, 0, 117 | // DWORD bfOffBits -- The offset, in bytes, from the beginning of the BITMAPFILEHEADER structure to the bitmap bits. 118 | 54, 0, 0, 0 119 | ]; 120 | 121 | // 122 | // typedef struct tagBITMAPINFOHEADER { 123 | // DWORD biSize; 124 | // LONG biWidth; 125 | // LONG biHeight; 126 | // WORD biPlanes; 127 | // WORD biBitCount; 128 | // DWORD biCompression; 129 | // DWORD biSizeImage; 130 | // LONG biXPelsPerMeter; 131 | // LONG biYPelsPerMeter; 132 | // DWORD biClrUsed; 133 | // DWORD biClrImportant; 134 | // } BITMAPINFOHEADER, *PBITMAPINFOHEADER; 135 | // 136 | var BITMAPINFOHEADER = [ 137 | // DWORD biSize -- The number of bytes required by the structure 138 | 40, 0, 0, 0, 139 | // LONG biWidth -- The width of the bitmap, in pixels 140 | biWidth & 0xff, biWidth >> 8 & 0xff, biWidth >> 16 & 0xff, biWidth >> 24 & 0xff, 141 | // LONG biHeight -- The height of the bitmap, in pixels 142 | biHeight & 0xff, biHeight >> 8 & 0xff, biHeight >> 16 & 0xff, biHeight >> 24 & 0xff, 143 | // WORD biPlanes -- The number of planes for the target device. This value must be set to 1 144 | 1, 0, 145 | // WORD biBitCount -- The number of bits-per-pixel, 24 bits-per-pixel -- the bitmap 146 | // has a maximum of 2^24 colors (16777216, Truecolor) 147 | 24, 0, 148 | // DWORD biCompression -- The type of compression, BI_RGB (code 0) -- uncompressed 149 | 0, 0, 0, 0, 150 | // DWORD biSizeImage -- The size, in bytes, of the image. This may be set to zero for BI_RGB bitmaps 151 | biSizeImage & 0xff, biSizeImage >> 8 & 0xff, biSizeImage >> 16 & 0xff, biSizeImage >> 24 & 0xff, 152 | // LONG biXPelsPerMeter, unused 153 | 0,0,0,0, 154 | // LONG biYPelsPerMeter, unused 155 | 0,0,0,0, 156 | // DWORD biClrUsed, the number of color indexes of palette, unused 157 | 0,0,0,0, 158 | // DWORD biClrImportant, unused 159 | 0,0,0,0 160 | ]; 161 | 162 | var iPadding = (4 - ((biWidth * 3) % 4)) % 4; 163 | 164 | var aImgData = oData.data; 165 | 166 | var strPixelData = ''; 167 | var biWidth4 = biWidth<<2; 168 | var y = biHeight; 169 | var fromCharCode = String.fromCharCode; 170 | 171 | do { 172 | var iOffsetY = biWidth4*(y-1); 173 | var strPixelRow = ''; 174 | for (var x = 0; x < biWidth; x++) { 175 | var iOffsetX = x<<2; 176 | strPixelRow += fromCharCode(aImgData[iOffsetY+iOffsetX+2]) + 177 | fromCharCode(aImgData[iOffsetY+iOffsetX+1]) + 178 | fromCharCode(aImgData[iOffsetY+iOffsetX]); 179 | } 180 | 181 | for (var c = 0; c < iPadding; c++) { 182 | strPixelRow += String.fromCharCode(0); 183 | } 184 | 185 | strPixelData += strPixelRow; 186 | } while (--y); 187 | 188 | var strEncoded = encodeData(BITMAPFILEHEADER.concat(BITMAPINFOHEADER)) + encodeData(strPixelData); 189 | 190 | return strEncoded; 191 | }; 192 | 193 | /** 194 | * saveAsImage 195 | * @param canvasElement 196 | * @param {String} image type 197 | * @param {Number} [optional] png width 198 | * @param {Number} [optional] png height 199 | */ 200 | var saveAsImage = function (canvas, width, height, type) { 201 | if ($support.canvas && $support.dataURL) { 202 | if (typeof canvas == "string") { canvas = document.getElementById(canvas); } 203 | if (type == undefined) { type = 'png'; } 204 | type = fixType(type); 205 | if (/bmp/.test(type)) { 206 | var data = getImageData(scaleCanvas(canvas, width, height)); 207 | var strData = genBitmapImage(data); 208 | saveFile(makeURI(strData, downloadMime)); 209 | } else { 210 | var strData = getDataURL(canvas, type, width, height); 211 | saveFile(strData.replace(type, downloadMime)); 212 | } 213 | } 214 | }; 215 | 216 | var convertToImage = function (canvas, width, height, type) { 217 | if ($support.canvas && $support.dataURL) { 218 | if (typeof canvas == "string") { canvas = document.getElementById(canvas); } 219 | if (type == undefined) { type = 'png'; } 220 | type = fixType(type); 221 | 222 | if (/bmp/.test(type)) { 223 | var data = getImageData(scaleCanvas(canvas, width, height)); 224 | var strData = genBitmapImage(data); 225 | return genImage(makeURI(strData, 'image/bmp')); 226 | } else { 227 | var strData = getDataURL(canvas, type, width, height); 228 | return genImage(strData); 229 | } 230 | } 231 | }; 232 | 233 | 234 | 235 | return { 236 | saveAsImage: saveAsImage, 237 | saveAsPNG: function (canvas, width, height) { 238 | return saveAsImage(canvas, width, height, 'png'); 239 | }, 240 | saveAsJPEG: function (canvas, width, height) { 241 | return saveAsImage(canvas, width, height, 'jpeg'); 242 | }, 243 | saveAsGIF: function (canvas, width, height) { 244 | return saveAsImage(canvas, width, height, 'gif'); 245 | }, 246 | saveAsBMP: function (canvas, width, height) { 247 | return saveAsImage(canvas, width, height, 'bmp'); 248 | }, 249 | 250 | convertToImage: convertToImage, 251 | convertToPNG: function (canvas, width, height) { 252 | return convertToImage(canvas, width, height, 'png'); 253 | }, 254 | convertToJPEG: function (canvas, width, height) { 255 | return convertToImage(canvas, width, height, 'jpeg'); 256 | }, 257 | convertToGIF: function (canvas, width, height) { 258 | return convertToImage(canvas, width, height, 'gif'); 259 | }, 260 | convertToBMP: function (canvas, width, height) { 261 | return convertToImage(canvas, width, height, 'bmp'); 262 | } 263 | }; 264 | 265 | }(); 266 | export default Canvas2Image; -------------------------------------------------------------------------------- /dist/js/about-68c021d2.js: -------------------------------------------------------------------------------- 1 | webpackJsonp([2,4],{ 2 | 3 | /***/ 333: 4 | /***/ function(module, exports) { 5 | 6 | /* 7 | MIT License http://www.opensource.org/licenses/mit-license.php 8 | Author Tobias Koppers @sokra 9 | */ 10 | // css base code, injected by the css-loader 11 | module.exports = function() { 12 | var list = []; 13 | 14 | // return the list of modules as css string 15 | list.toString = function toString() { 16 | var result = []; 17 | for(var i = 0; i < this.length; i++) { 18 | var item = this[i]; 19 | if(item[2]) { 20 | result.push("@media " + item[2] + "{" + item[1] + "}"); 21 | } else { 22 | result.push(item[1]); 23 | } 24 | } 25 | return result.join(""); 26 | }; 27 | 28 | // import a list of modules into the list 29 | list.i = function(modules, mediaQuery) { 30 | if(typeof modules === "string") 31 | modules = [[null, modules, ""]]; 32 | var alreadyImportedModules = {}; 33 | for(var i = 0; i < this.length; i++) { 34 | var id = this[i][0]; 35 | if(typeof id === "number") 36 | alreadyImportedModules[id] = true; 37 | } 38 | for(i = 0; i < modules.length; i++) { 39 | var item = modules[i]; 40 | // skip already imported module 41 | // this implementation is not 100% perfect for weird media query combinations 42 | // when a module is imported multiple times with different media queries. 43 | // I hope this will never occur (Hey this way we have smaller bundles) 44 | if(typeof item[0] !== "number" || !alreadyImportedModules[item[0]]) { 45 | if(mediaQuery && !item[2]) { 46 | item[2] = mediaQuery; 47 | } else if(mediaQuery) { 48 | item[2] = "(" + item[2] + ") and (" + mediaQuery + ")"; 49 | } 50 | list.push(item); 51 | } 52 | } 53 | }; 54 | return list; 55 | }; 56 | 57 | 58 | /***/ }, 59 | 60 | /***/ 334: 61 | /***/ function(module, exports, __webpack_require__) { 62 | 63 | /* 64 | MIT License http://www.opensource.org/licenses/mit-license.php 65 | Author Tobias Koppers @sokra 66 | */ 67 | var stylesInDom = {}, 68 | memoize = function(fn) { 69 | var memo; 70 | return function () { 71 | if (typeof memo === "undefined") memo = fn.apply(this, arguments); 72 | return memo; 73 | }; 74 | }, 75 | isOldIE = memoize(function() { 76 | return /msie [6-9]\b/.test(window.navigator.userAgent.toLowerCase()); 77 | }), 78 | getHeadElement = memoize(function () { 79 | return document.head || document.getElementsByTagName("head")[0]; 80 | }), 81 | singletonElement = null, 82 | singletonCounter = 0, 83 | styleElementsInsertedAtTop = []; 84 | 85 | module.exports = function(list, options) { 86 | if(false) { 87 | if(typeof document !== "object") throw new Error("The style-loader cannot be used in a non-browser environment"); 88 | } 89 | 90 | options = options || {}; 91 | // Force single-tag solution on IE6-9, which has a hard limit on the # of