├── 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 |
16 | -
17 | Back Home
18 |
19 |
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 |
55 | {
56 | menus.map((menu, index) => {
57 | return -
58 |
59 | { menu.text }
60 |
61 |
62 | })
63 | }
64 |
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 |
68 | 性别
69 |
83 |
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