├── reactjs-flex-order-practice
├── .babelrc
├── .eslintrc
├── .gitignore
├── db
│ ├── category.json
│ ├── item.json
│ └── tables.json
├── index.html
├── index_bundle.js
├── package.json
├── readme.md
├── src
│ ├── actions
│ │ └── OrderActions.js
│ ├── components
│ │ ├── OrderContent.jsx
│ │ ├── OrderContentDetail.jsx
│ │ ├── OrderContentOperation.jsx
│ │ ├── OrderContentSubmit.jsx
│ │ ├── OrderContentTotal.jsx
│ │ ├── OrderMenu.jsx
│ │ ├── OrderMenuCategoryList.jsx
│ │ ├── OrderMenuItemList.jsx
│ │ └── OrderMenuPager.jsx
│ ├── constants
│ │ ├── ActionTypes.js
│ │ └── OrderConstants.js
│ ├── dispatcher
│ │ └── AppDispatcher.js
│ ├── index.html
│ ├── index.jsx
│ ├── stores
│ │ └── OrderStore.js
│ └── styles
│ │ ├── components
│ │ ├── datetime.scss
│ │ ├── input.scss
│ │ └── loading.scss
│ │ ├── entry.scss
│ │ ├── layout
│ │ ├── app.scss
│ │ └── grid.scss
│ │ ├── pages
│ │ └── Order.scss
│ │ └── variables
│ │ ├── button.scss
│ │ └── font.scss
└── webpack.config.js
├── reactjs-order-practice
├── .babelrc
├── .eslintrc
├── .gitignore
├── db
│ ├── category.json
│ ├── item.json
│ └── tables.json
├── index.html
├── index_bundle.js
├── package.json
├── readme.md
├── src
│ ├── actions
│ │ └── OrderActions.js
│ ├── components
│ │ ├── OrderContent.jsx
│ │ ├── OrderContentDetail.jsx
│ │ ├── OrderContentOperation.jsx
│ │ ├── OrderContentSubmit.jsx
│ │ ├── OrderContentTotal.jsx
│ │ ├── OrderMenu.jsx
│ │ ├── OrderMenuCategoryList.jsx
│ │ ├── OrderMenuItemList.jsx
│ │ └── OrderMenuPager.jsx
│ ├── constants
│ │ └── OrderConstants.js
│ ├── index.html
│ ├── index.jsx
│ └── styles
│ │ ├── components
│ │ ├── datetime.scss
│ │ ├── input.scss
│ │ └── loading.scss
│ │ ├── entry.scss
│ │ ├── layout
│ │ ├── app.scss
│ │ └── grid.scss
│ │ ├── pages
│ │ └── Order.scss
│ │ └── variables
│ │ ├── button.scss
│ │ └── font.scss
└── webpack.config.js
├── reactjs-php-isomorphic-example
├── .gitignore
├── README.md
├── build
│ └── .gitignore
├── index.php
├── package.json
├── src
│ ├── App.js
│ ├── Heading.js
│ └── Table.js
└── webpack.config.js
├── reactjs-redux-order-practice
├── .babelrc
├── .eslintrc
├── .gitignore
├── db
│ ├── category.json
│ ├── item.json
│ └── tables.json
├── index.html
├── index_bundle.js
├── package.json
├── readme.md
├── src
│ ├── actions
│ │ ├── data.js
│ │ └── ui
│ │ │ └── Order
│ │ │ ├── categories.js
│ │ │ ├── items.js
│ │ │ ├── order.js
│ │ │ └── pages.js
│ ├── components
│ │ └── Order
│ │ │ ├── OrderContent.jsx
│ │ │ ├── OrderContentDetail.jsx
│ │ │ ├── OrderContentSubmit.jsx
│ │ │ ├── OrderMenu.jsx
│ │ │ ├── OrderMenuCategory.jsx
│ │ │ ├── OrderMenuItem.jsx
│ │ │ └── OrderMenuPager.jsx
│ ├── constants
│ │ ├── ActionTypes.js
│ │ └── OrderConstants.js
│ ├── containers
│ │ ├── App.jsx
│ │ └── Order
│ │ │ ├── OrderContent.js
│ │ │ ├── OrderContentDetail.js
│ │ │ ├── OrderContentSubmit.js
│ │ │ ├── OrderMenuCategory.js
│ │ │ ├── OrderMenuItem.js
│ │ │ └── OrderMenuPager.js
│ ├── helpers
│ │ └── index.js
│ ├── index.html
│ ├── index.jsx
│ ├── reducers
│ │ ├── Order
│ │ │ ├── categories.js
│ │ │ ├── items.js
│ │ │ ├── order.js
│ │ │ ├── pages.js
│ │ │ └── tables.js
│ │ └── index.js
│ ├── store
│ │ ├── configureStore.js
│ │ └── index.js
│ └── styles
│ │ ├── components
│ │ ├── datetime.scss
│ │ ├── input.scss
│ │ └── loading.scss
│ │ ├── entry.scss
│ │ ├── layout
│ │ ├── app.scss
│ │ └── grid.scss
│ │ ├── pages
│ │ └── Order.scss
│ │ └── variables
│ │ ├── button.scss
│ │ └── font.scss
└── webpack.config.js
├── reactjs-redux-todos-practice
├── .babelrc
├── .eslintrc
├── .gitignore
├── index.html
├── index_bundle.js
├── package.json
├── readme.md
├── src
│ ├── actions
│ │ └── index.js
│ ├── components
│ │ ├── AddTodo.jsx
│ │ ├── App.jsx
│ │ ├── Footer.jsx
│ │ ├── Link.jsx
│ │ ├── Todo.jsx
│ │ └── TodoList.jsx
│ ├── containers
│ │ ├── AddTodo.js
│ │ ├── FilterLink.js
│ │ └── VisibleTodoList.js
│ ├── index.html
│ ├── index.jsx
│ └── reducers
│ │ ├── index.js
│ │ ├── todos.js
│ │ └── visibilityFilter.js
└── webpack.config.js
├── readme.md
└── wPOS
├── .gitignore
├── bundle.js
├── db
├── categories.json
├── items.json
├── payments.json
└── tables.json
├── index.html
├── main.css
├── package.json
├── readme.md
└── src
├── App.jsx
├── components
├── ChoiceButton.jsx
├── InputField.jsx
├── Loading.jsx
├── MenuCategory.jsx
├── MenuCategoryItem.jsx
├── MenuItem.jsx
├── MenuItemNav.jsx
├── MenuRow.jsx
├── MenuRowItem.jsx
├── OrderDetails.jsx
├── OrderList.jsx
└── OrderSubmit.jsx
├── configs
└── API.js
├── entry.jsx
├── helpers
├── ArrayHelper.js
└── PaymentHelper.js
├── pages
├── Home.jsx
├── NotFound.jsx
└── Order.jsx
└── styles
├── components
└── loading.scss
├── entry.scss
├── layout
├── app.scss
└── grid.scss
├── pages
├── Home.scss
└── Order.scss
└── variables
├── button.scss
└── font.scss
/reactjs-flex-order-practice/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | "es2015",
4 | "react",
5 | ],
6 | "plugins": []
7 | }
8 |
--------------------------------------------------------------------------------
/reactjs-flex-order-practice/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "airbnb",
3 | "rules": {
4 | "no-multiple-empty-lines": [2, {"max": 2, "maxEOF": 2}]
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/reactjs-flex-order-practice/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 |
--------------------------------------------------------------------------------
/reactjs-flex-order-practice/db/category.json:
--------------------------------------------------------------------------------
1 | [{
2 | "id": 1,
3 | "name": "\u4e3b\u98df",
4 | "user": "rz",
5 | "is_active": 1,
6 | "created_at": "2016-06-09 20:51:39",
7 | "updated_at": "2016-06-09 20:51:39"
8 | }, {
9 | "id": 2,
10 | "name": "\u6e6f\u54c1",
11 | "user": "rz",
12 | "is_active": 1,
13 | "created_at": "2016-06-09 20:51:39",
14 | "updated_at": "2016-06-09 20:51:39"
15 | }, {
16 | "id": 3,
17 | "name": "\u5c0f\u9ede",
18 | "user": "rz",
19 | "is_active": 1,
20 | "created_at": "2016-06-09 20:51:39",
21 | "updated_at": "2016-06-09 20:51:39"
22 | }]
23 |
--------------------------------------------------------------------------------
/reactjs-flex-order-practice/db/tables.json:
--------------------------------------------------------------------------------
1 | [{
2 | "id": 1,
3 | "name": "一號桌"
4 | }, {
5 | "id": 2,
6 | "name": "二號桌"
7 | }, {
8 | "id": 3,
9 | "name": "三號桌"
10 | }, {
11 | "id": 4,
12 | "name": "四號桌"
13 | }, {
14 | "id": 5,
15 | "name": "五號桌"
16 | }, {
17 | "id": 6,
18 | "name": "六號桌"
19 | }]
20 |
--------------------------------------------------------------------------------
/reactjs-flex-order-practice/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | POS Order - ReactJS + Flux
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/reactjs-flex-order-practice/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "reactjs-flex-order-practice",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "start": "webpack-dev-server --colors --inline --content-base .",
8 | "prod": "webpack -p"
9 | },
10 | "author": "",
11 | "license": "ISC",
12 | "dependencies": {
13 | "events": "^1.1.1",
14 | "flux": "^3.0.0",
15 | "react": "^15.3.2",
16 | "react-dom": "^15.3.2"
17 | },
18 | "devDependencies": {
19 | "babel-core": "^6.16.0",
20 | "babel-eslint": "^7.0.0",
21 | "babel-loader": "^6.2.5",
22 | "babel-preset-es2015": "^6.16.0",
23 | "babel-preset-react": "^6.16.0",
24 | "css-loader": "^0.25.0",
25 | "eslint": "^3.6.1",
26 | "eslint-config-airbnb": "^12.0.0",
27 | "eslint-loader": "^1.5.0",
28 | "eslint-plugin-import": "^1.16.0",
29 | "eslint-plugin-jsx-a11y": "^2.2.2",
30 | "eslint-plugin-react": "^6.3.0",
31 | "html-webpack-plugin": "^2.22.0",
32 | "node-sass": "^3.10.1",
33 | "sass-loader": "^4.0.2",
34 | "style-loader": "^0.13.1",
35 | "webpack": "^1.13.2",
36 | "webpack-dev-server": "^1.16.1"
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/reactjs-flex-order-practice/readme.md:
--------------------------------------------------------------------------------
1 | # POS Order
2 |
3 | ## 概觀
4 | 練習以 ReactJS + Flux 實作 POS 點單介面
5 |
6 |
7 | ## 開發
8 | 1. 套件相依安裝 `npm install`
9 | 2. 運行服務 `npm start`
10 | 3. 開啟瀏覽器
11 |
12 | ```
13 | http://localhost:8008
14 | ```
15 |
16 | ## 產出 js & html
17 | 運行 `npm run prod`
18 |
19 | > 產出的 js & html file 會在根目錄下
20 |
21 |
22 | ## 參考
23 | * [從零開始學 ReactJS(ReactJS 101)](https://github.com/kdchang/reactjs101)
24 | * [24 小時,React 快速入門](https://github.com/shiningjason1989/react-quick-tutorial/)
--------------------------------------------------------------------------------
/reactjs-flex-order-practice/src/actions/OrderActions.js:
--------------------------------------------------------------------------------
1 | import AppDispatcher from '../dispatcher/AppDispatcher';
2 | import ActionTypes from '../constants/ActionTypes';
3 |
4 | const OrderActions = {
5 | /* Order 左側 Actions */
6 | loadMenuCategories() {
7 | const url = './db/category.json';
8 | fetch(url, {
9 | method: 'GET'
10 | }).then((response) => response.json()).then((records) => {
11 | AppDispatcher.dispatch({
12 | type: ActionTypes.LOAD_CATEGORIES_SUCCESS,
13 | data: records
14 | });
15 | });
16 | },
17 | loadMenuItems() {
18 | const url = './db/item.json';
19 | fetch(url, {
20 | method: 'GET'
21 | }).then((response) => response.json()).then((records) => {
22 | AppDispatcher.dispatch({
23 | type: ActionTypes.LOAD_ITEMS_SUCCESS,
24 | data: records
25 | });
26 | });
27 | },
28 | loadTables() {
29 | const url = './db/tables.json';
30 |
31 | fetch(url, {
32 | method: 'GET'
33 | }).then((response) => response.json()).then((records) => {
34 | AppDispatcher.dispatch({
35 | type: ActionTypes.LOAD_TABLES_SUCCESS,
36 | data: records
37 | });
38 | });
39 | },
40 | changeSelectedCategory(category_id) {
41 | AppDispatcher.dispatch({
42 | type: ActionTypes.CHANGE_SELECTED_CATEGORY,
43 | data: category_id
44 | });
45 | },
46 | getMenuPagesCount(items, itemsPerPageAmount) {
47 | AppDispatcher.dispatch({
48 | type: ActionTypes.GET_MENU_PAGES_COUNT,
49 | data: Math.ceil(items.length / itemsPerPageAmount)
50 | });
51 | },
52 | changeCurrentMenuPage(pageIdx) {
53 | AppDispatcher.dispatch({
54 | type: ActionTypes.CHANGE_CURRENT_MENU_PAGE,
55 | data: pageIdx
56 | });
57 | },
58 | addItemToOrder(item) {
59 | AppDispatcher.dispatch({
60 | type: ActionTypes.ADD_ITEM_TO_ORDER,
61 | data: item
62 | });
63 | },
64 | removeItemFromOder(item) {
65 | AppDispatcher.dispatch({
66 | type: ActionTypes.REMOVE_ITEM_FROM_ORDER,
67 | data: item
68 | });
69 | },
70 | addAdditionToOrder() {
71 | AppDispatcher.dispatch({
72 | type: ActionTypes.ADD_ADDITION_TO_ORDER,
73 | });
74 | },
75 | removeItemFromAddition(item) {
76 | AppDispatcher.dispatch({
77 | type: ActionTypes.REMOVE_ITEM_FROM_ADDITION,
78 | data: item
79 | });
80 | },
81 | updateAdditionItem(id, field, value) {
82 | AppDispatcher.dispatch({
83 | type: ActionTypes.UPADTE_ADDITION_ITEM,
84 | id: id,
85 | field: field,
86 | value: value,
87 | });
88 | },
89 | toggleToGoStatus() {
90 | AppDispatcher.dispatch({
91 | type: ActionTypes.TOGGLE_TOGO_STATUS,
92 | });
93 | },
94 | toggleWhenStatus() {
95 | AppDispatcher.dispatch({
96 | type: ActionTypes.TOGGLE_WHEN_STATUS,
97 | });
98 | },
99 | clearOrderAndAdditon() {
100 | AppDispatcher.dispatch({
101 | type: ActionTypes.CLEAR_ORDER_AND_ADDITION,
102 | });
103 | },
104 | };
105 |
106 | export default OrderActions;
107 |
--------------------------------------------------------------------------------
/reactjs-flex-order-practice/src/components/OrderContent.jsx:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react';
2 | import OrderConstants from '../constants/OrderConstants';
3 | import OrderContentDetail from './OrderContentDetail.jsx';
4 | import OrderContentOperation from './OrderContentOperation.jsx';
5 | import OrderContentSubmit from './OrderContentSubmit.jsx';
6 | import OrderContentTotal from './OrderContentTotal.jsx';
7 |
8 | class OrderContent extends Component {
9 | getOrderContentRowHeight() {
10 | const bodyHeight = document.body.clientHeight;
11 | const orderContentHeight = (bodyHeight - OrderConstants.contentDetailHeadingHeight - OrderConstants.contentOperationHeight);
12 | this.setState({
13 | orderContentStyle: {
14 | height: orderContentHeight + 'px'
15 | }
16 | });
17 | }
18 |
19 | constructor(props) {
20 | super(props);
21 | this.state = {
22 | orderContentStyle: {
23 | height: 'inherit'
24 | }
25 | };
26 | this.getOrderContentRowHeight = this.getOrderContentRowHeight.bind(this);
27 | }
28 |
29 | componentDidMount() {
30 | // item 動態列高
31 | this.getOrderContentRowHeight();
32 | window.addEventListener('resize', () => this.getOrderContentRowHeight());
33 | }
34 |
35 | componentWillUnmount() {
36 | window.removeEventListener('resize', () => this.getOrderContentRowHeight());
37 | }
38 |
39 | render() {
40 | return (
41 |
42 |
43 |
點單明細
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 | );
66 | }
67 | }
68 |
69 |
70 | export default OrderContent;
71 |
--------------------------------------------------------------------------------
/reactjs-flex-order-practice/src/components/OrderContentDetail.jsx:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react';
2 | import OrderActions from './../actions/OrderActions';
3 | import OrderStore from '../stores/OrderStore';
4 |
5 | class OrderItemFromMenu extends Component {
6 | handleClickItem() {
7 | OrderActions.removeItemFromOder(this.props.item);
8 | }
9 |
10 | constructor(props) {
11 | super(props);
12 | this.handleClickItem = this.handleClickItem.bind(this);
13 | }
14 |
15 | render() {
16 | return (
17 |
18 | {this.props.item.name} |
19 | {this.props.item.amount} |
20 | {this.props.item.price} |
21 | {this.props.item.subtotal} |
22 |
23 |
26 | |
27 |
28 | );
29 | }
30 |
31 | }
32 |
33 | class OrderItemFromAddition extends Component {
34 | handleChange(e) {
35 | // 取值
36 | const id = e.target.dataset.id;
37 | const field = e.target.dataset.field;
38 | const value = e.target.value;
39 |
40 | // 改變 state
41 | let _item = this.state.item;
42 | _item[field] = value;
43 | _item.subtotal = _item.amount * _item.price;
44 | this.setState({item: _item});
45 |
46 | // 改變 store 的 addition
47 | OrderActions.updateAdditionItem(id, field, value);
48 | }
49 |
50 | handleClickAddition(item) {
51 | OrderActions.removeItemFromAddition(item);
52 | }
53 |
54 | constructor(props) {
55 | super(props);
56 | this.state = {
57 | item: props.item
58 | };
59 | this.handleChange = this.handleChange.bind(this);
60 | }
61 |
62 | render() {
63 | return (
64 |
65 | |
66 | |
67 | |
68 | {this.state.item.subtotal} |
69 |
70 |
73 | |
74 |
75 | );
76 | }
77 | }
78 |
79 | class OrderContentDetail extends Component {
80 | constructor(props) {
81 | super(props);
82 | this.state = {
83 | order: [],
84 | addition: []
85 | };
86 | }
87 |
88 | componentDidMount() {
89 | OrderStore.addChangeListener(() => this.setState({order: OrderStore.getCurrentOrder(), addition: OrderStore.getCurrenrAddition()}));
90 | }
91 |
92 | componentWillUnmount() {
93 | OrderStore.removeChangeListener();
94 | }
95 |
96 | render() {
97 | return (
98 |
99 |
100 |
101 | 品項 |
102 | 數量 |
103 | 單價 |
104 | 小計 |
105 | |
106 |
107 |
108 |
109 | {this.state.order.map((record) => ())}
110 | {this.state.addition.map((record) => ())}
111 |
112 |
113 | );
114 | }
115 | }
116 |
117 | export default OrderContentDetail;
118 |
--------------------------------------------------------------------------------
/reactjs-flex-order-practice/src/components/OrderContentOperation.jsx:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react';
2 | import OrderActions from './../actions/OrderActions';
3 | import OrderStore from '../stores/OrderStore';
4 |
5 | class OrderAddAddition extends Component {
6 | handleClick() {
7 | OrderActions.addAdditionToOrder();
8 | }
9 |
10 | render() {
11 | return (
12 |
13 |
18 |
19 | );
20 | }
21 | }
22 |
23 | class OrderToGo extends Component {
24 | handleClick() {
25 | OrderActions.toggleToGoStatus();
26 | }
27 |
28 | constructor(props) {
29 | super(props);
30 | this.state = {
31 | isToGo: null
32 | };
33 | }
34 |
35 | componentDidMount() {
36 | OrderStore.addChangeListener(() => this.setState({isToGo: OrderStore.getToGoStatus()}));
37 | }
38 |
39 | componentWillUnmount() {
40 | OrderStore.removeChangeListener();
41 | }
42 |
43 | render() {
44 | const toGoIconClass = (this.state.isToGo)
45 | ? 'fa fa-hand-o-right'
46 | : 'fa fa-cutlery'; // 外帶狀態 icon
47 | const toGoButtonClass = ((this.state.isToGo)
48 | ? 'btn btn-danger'
49 | : 'btn btn-success') + ' btn-lg btn-block';
50 | return (
51 |
52 |
59 |
60 | );
61 | }
62 | }
63 |
64 | class OrderWhen extends Component {
65 | handleClick() {
66 | OrderActions.toggleWhenStatus();
67 | }
68 |
69 | constructor(props) {
70 | super(props);
71 | this.state = {
72 | when: null
73 | };
74 | }
75 |
76 | componentDidMount() {
77 | OrderStore.addChangeListener(() => this.setState({when: OrderStore.getwhenStatus()}));
78 | }
79 |
80 | componentWillUnmount() {
81 | OrderStore.removeChangeListener();
82 | }
83 |
84 | render() {
85 | const whenIconClass = (this.state.when === '中午')
86 | ? 'fa fa-sun-o'
87 | : 'fa fa-moon-o'; // 中午/晚上狀態 icon
88 | const whenButtonClass = ((this.state.when === '中午')
89 | ? 'btn btn-warning'
90 | : 'btn btn-primary') + ' btn-lg btn-block'; // 中午/晚上狀態 icon
91 | return (
92 |
93 |
98 |
99 | );
100 | }
101 | }
102 |
103 | class OrderTables extends Component {
104 | constructor(props) {
105 | super(props);
106 | this.state = {
107 | tables: [],
108 | isToGo: null
109 | };
110 | }
111 |
112 | componentDidMount() {
113 | OrderStore.addChangeListener(() => {
114 | this.setState({tables: OrderStore.getTables(), isToGo: OrderStore.getToGoStatus()})
115 | });
116 | }
117 |
118 | componentWillUnmount() {
119 | OrderStore.removeChangeListener();
120 | }
121 |
122 | render() {
123 | if (this.state.isToGo == true) {
124 | return (
125 |
126 |
129 |
130 | );
131 | }
132 | return (
133 |
134 |
141 |
142 | );
143 | }
144 | }
145 |
146 | const OrderContentOperation = () => (
147 |
148 |
149 |
150 |
151 |
152 |
153 | );
154 |
155 | export default OrderContentOperation;
156 |
--------------------------------------------------------------------------------
/reactjs-flex-order-practice/src/components/OrderContentSubmit.jsx:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react';
2 | import OrderActions from './../actions/OrderActions';
3 | import OrderStore from '../stores/OrderStore';
4 |
5 | class OrderSubmit extends Component {
6 | handleClick() {
7 | // dump order & addition records
8 | const data = {
9 | order: OrderStore.getCurrentOrder(),
10 | addition: OrderStore.getCurrenrAddition()
11 | };
12 | console.log('--- dump order & addition records ---');
13 | console.log(data);
14 |
15 | // clear order & addition records
16 | console.log('--- clear order & addition records ---');
17 | OrderActions.clearOrderAndAdditon();
18 |
19 | }
20 |
21 | constructor(props) {
22 | super(props);
23 | this.state = {
24 | order: []
25 | };
26 | }
27 |
28 | componentDidMount() {
29 | OrderStore.addChangeListener(() => this.setState({order: OrderStore.getCurrentOrder()}));
30 | }
31 |
32 | componentWillUnmount() {
33 | OrderStore.removeChangeListener();
34 | }
35 |
36 | render() {
37 | return (
38 |
43 | );
44 | }
45 | }
46 |
47 | export default OrderSubmit;
48 |
--------------------------------------------------------------------------------
/reactjs-flex-order-practice/src/components/OrderContentTotal.jsx:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react';
2 | import OrderStore from '../stores/OrderStore';
3 |
4 | class OrderContentTotal extends Component {
5 | constructor(props) {
6 | super(props);
7 | this.state = {
8 | total: null
9 | };
10 | }
11 |
12 | componentDidMount() {
13 | OrderStore.addChangeListener(() => this.setState({total: OrderStore.getTotal()}));
14 | }
15 |
16 | componentWillUnmount() {
17 | OrderStore.removeChangeListener();
18 | }
19 |
20 | render() {
21 | return (
22 |
23 |
24 | 總計
25 |
26 |
27 | {this.state.total}
28 |
29 |
30 | );
31 | }
32 | }
33 |
34 | export default OrderContentTotal;
35 |
--------------------------------------------------------------------------------
/reactjs-flex-order-practice/src/components/OrderMenu.jsx:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react';
2 | import OrderMenuCategoryList from './OrderMenuCategoryList.jsx'
3 | import OrderMenuItemList from './OrderMenuItemList.jsx'
4 | import OrderMenuPager from './OrderMenuPager.jsx'
5 |
6 | // stateless component
7 | const OrderMenu = () => (
8 |
9 |
10 |
11 |
12 |
13 | );
14 |
15 | export default OrderMenu;
16 |
--------------------------------------------------------------------------------
/reactjs-flex-order-practice/src/components/OrderMenuCategoryList.jsx:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react';
2 | import OrderActions from './../actions/OrderActions';
3 | import OrderStore from './../stores/OrderStore';
4 |
5 | // stateless component
6 | const OrderMenyCategoryListItem = (props) => {
7 | const itemClass = props.isSelected
8 | ? "active"
9 | : "";
10 | return (
11 |
12 |
13 | {props.name}
14 |
15 |
16 | )
17 | };
18 |
19 | class OrderMenuCategoryList extends Component {
20 | handleClick(i) {
21 | // 讓 click 的 category 變成 active
22 | this.setState({selectedCategory: i});
23 |
24 | // OrderStore 的 selectedCategory 也要一併更新
25 | OrderActions.changeSelectedCategory(i);
26 |
27 | // OrderStore 的 菜單分頁數 也要一併更新
28 | OrderActions.getMenuPagesCount(OrderStore.getItems(), 12);
29 | }
30 |
31 | constructor(props) {
32 | super(props);
33 | this.state = {
34 | categories: [], // 分類
35 | selectedCategory: null // 被選擇的分類
36 | };
37 | this.handleClick = this.handleClick.bind(this);
38 | }
39 |
40 | componentDidMount() {
41 | OrderStore.addChangeListener(() => {
42 | this.setState({categories: OrderStore.getCategories(), selectedCategory: OrderStore.getSelectCategoryId()})
43 | });
44 | }
45 |
46 | componentWillUnmount() {
47 | OrderStore.removeChangeListener();
48 | }
49 |
50 | render() {
51 | return (
52 |
53 |
54 | {this.state.categories.map((record) => ())}
55 |
56 |
57 | );
58 | }
59 | }
60 |
61 | export default OrderMenuCategoryList;
62 |
--------------------------------------------------------------------------------
/reactjs-flex-order-practice/src/components/OrderMenuItemList.jsx:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react';
2 | import OrderConstants from '../constants/OrderConstants';
3 | import OrderActions from './../actions/OrderActions';
4 | import OrderStore from '../stores/OrderStore';
5 |
6 | // stateless component
7 | const OrderMenuItem = (props) => {
8 | return (
9 |
10 |
11 |
{props.item.name}
12 |
13 |
14 | );
15 | };
16 |
17 | class OrderMenuItemList extends Component {
18 | handleClickItem(item) {
19 | OrderActions.addItemToOrder(item);
20 | }
21 |
22 | getItemRowHeight() {
23 | const bodyHeight = document.body.clientHeight;
24 | const rowHeight = (bodyHeight - OrderConstants.menuItemRowMarginHeight - OrderConstants.menuCategoryListHeight - OrderConstants.menuPagerHeight) / OrderConstants.menuItemListRows;
25 | this.setState({
26 | itemRowStyle: {
27 | height: rowHeight + 'px'
28 | }
29 | });
30 | }
31 |
32 | constructor(props) {
33 | super(props);
34 | this.state = {
35 | items: [],
36 | itemRowStyle: {
37 | height: 'inherit'
38 | }
39 | };
40 | this.getItemRowHeight = this.getItemRowHeight.bind(this);
41 | }
42 |
43 | componentDidMount() {
44 | OrderStore.addChangeListener(() => this.setState({items: OrderStore.getItems()}));
45 |
46 | // item 動態列高
47 | this.getItemRowHeight();
48 | window.addEventListener('resize', () => this.getItemRowHeight());
49 | }
50 |
51 | componentWillUnmount() {
52 | OrderStore.removeChangeListener();
53 | window.removeEventListener('resize', () => this.getItemRowHeight());
54 | }
55 |
56 | render() {
57 | return (
58 |
59 |
60 | {this.state.items.map((record) => ())}
61 |
62 |
63 | );
64 | }
65 | }
66 |
67 | export default OrderMenuItemList;
68 |
--------------------------------------------------------------------------------
/reactjs-flex-order-practice/src/components/OrderMenuPager.jsx:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react';
2 | import OrderActions from '../actions/OrderActions';
3 | import OrderStore from '../stores/OrderStore';
4 | // import OrderConstants from '../constants/OrderConstants';
5 |
6 | class OrderMenuPager extends Component {
7 | handleClick(i) {
8 | // 更新當前頁面 idx
9 | this.setState({currentMenuPage: i});
10 |
11 | // OrderStore 的 當前頁面 idx 也要一併更新
12 | OrderActions.changeCurrentMenuPage(i);
13 | }
14 |
15 | constructor(props) {
16 | super(props);
17 | this.state = {
18 | menuPagesCount: null,
19 | currentMenuPage: null
20 | };
21 | this.handleClick = this.handleClick.bind(this);
22 | }
23 |
24 | componentDidMount() {
25 | OrderStore.addChangeListener(() => {
26 | this.setState({menuPagesCount: OrderStore.getMenuPagesCount(), currentMenuPage: OrderStore.getCurrentMenuPage()})
27 | });
28 | }
29 |
30 | componentWillUnmount() {
31 | OrderStore.removeChangeListener();
32 | }
33 |
34 | render() {
35 | const hasNextPage = this.state.menuPagesCount - this.state.currentMenuPage > 1; // 是否還有下一頁
36 | const hasPrevPage = this.state.currentMenuPage - 1 > -1; // 是否還有上一頁
37 | const nextPage = hasNextPage
38 | ? this.state.currentMenuPage + 1
39 | : this.state.currentMenuPage;
40 | const prevPage = hasPrevPage
41 | ? this.state.currentMenuPage - 1
42 | : this.state.currentMenuPage;
43 |
44 | return (
45 |
46 |
47 |
48 |
53 |
54 |
55 |
60 |
61 |
62 |
63 | );
64 | }
65 | }
66 |
67 | export default OrderMenuPager;
68 |
--------------------------------------------------------------------------------
/reactjs-flex-order-practice/src/constants/ActionTypes.js:
--------------------------------------------------------------------------------
1 | const ActionTypes = {
2 | // Order 左側 Action
3 | CHANGE_EVENT: 'CHANGE_EVENT',
4 | LOAD_CATEGORIES_SUCCESS: 'LOAD_CATEGORIES_SUCCESS',
5 | LOAD_ITEMS_SUCCESS: 'LOAD_ITEMS_SUCCESS',
6 | LOAD_TABLES_SUCCESS: 'LOAD_TABLES_SUCCESS',
7 | CHANGE_SELECTED_CATEGORY: 'CHANGE_SELECTED_CATEGORY',
8 | GET_MENU_PAGES_COUNT: 'GET_MENU_PAGES_COUNT',
9 | CHANGE_CURRENT_MENU_PAGE: 'CHANGE_CURRENT_MENU_PAGE',
10 | ADD_ITEM_TO_ORDER: 'ADD_ITEM_TO_ORDER',
11 | // Order 右側 Action
12 | REMOVE_ITEM_FROM_ORDER: 'REMOVE_ITEM_FROM_ORDER',
13 | ADD_ADDITION_TO_ORDER: 'ADD_ADDITION_TO_ORDER',
14 | REMOVE_ITEM_FROM_ADDITION: 'REMOVE_ITEM_FROM_ADDITION',
15 | UPADTE_ADDITION_ITEM: 'UPADTE_ADDITION_ITEM',
16 | TOGGLE_TOGO_STATUS: 'TOGGLE_TOGO_STATUS',
17 | TOGGLE_WHEN_STATUS: 'TOGGLE_WHEN_STATUS',
18 | CLEAR_ORDER_AND_ADDITION: 'CLEAR_ORDER_AND_ADDITION',
19 | };
20 |
21 | export default ActionTypes;
22 |
--------------------------------------------------------------------------------
/reactjs-flex-order-practice/src/constants/OrderConstants.js:
--------------------------------------------------------------------------------
1 | const now = new Date();
2 | const nightStart = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 17, 30, 0, 0);
3 | const whenStatus = (now.getTime() - nightStart.getTime() < 0) ? '中午' : '晚上';
4 |
5 | const OrderConstants = {
6 | // parameters
7 | itemsPerPageAmount: 12, // 每頁菜單項目數
8 | menuItemListRows: 3, // 菜單列數
9 | menuItemRowMarginHeight: 30, // 菜單列與列之間 margin 高
10 | menuCategoryListHeight: 87, // 菜單分類 DOM 高
11 | menuPagerHeight: 62, // 菜單分頁 DOM 高
12 | contentDetailHeadingHeight: 72, // "點單明細" 高
13 | contentOperationHeight: 133, // order 操作區域高
14 | // store default value
15 | whenStatus: whenStatus // 菜單 中午/晚上的狀態
16 | };
17 |
18 | export default OrderConstants;
19 |
--------------------------------------------------------------------------------
/reactjs-flex-order-practice/src/dispatcher/AppDispatcher.js:
--------------------------------------------------------------------------------
1 | import {
2 | Dispatcher
3 | } from 'flux';
4 |
5 | class DispatcherClass extends Dispatcher {
6 | // handleAction(action) {
7 | // this.dispatch({
8 | // type: action.type,
9 | // payload: action.payload
10 | // });
11 | // }
12 | }
13 |
14 | const AppDispatcher = new DispatcherClass();
15 |
16 | export default AppDispatcher;
17 |
--------------------------------------------------------------------------------
/reactjs-flex-order-practice/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | POS Order - ReactJS + Flux
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/reactjs-flex-order-practice/src/index.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import OrderMenu from './components/OrderMenu.jsx';
4 | import OrderContent from './components/OrderContent.jsx';
5 | import OrderActions from './actions/OrderActions';
6 |
7 | require("!style!css!sass!./styles/entry.scss");
8 |
9 | class App extends React.Component {
10 | constructor(props) {
11 | super(props);
12 | this.state = {};
13 | }
14 |
15 | componentDidMount() {
16 | OrderActions.loadMenuCategories();
17 | OrderActions.loadMenuItems();
18 | OrderActions.loadTables();
19 | }
20 |
21 | render() {
22 | return (
23 |
24 |
25 |
26 |
27 | {/* 左側欄 */}
28 | {/* 右側欄 */}
29 |
30 |
31 |
32 |
33 | );
34 | }
35 | }
36 |
37 | ReactDOM.render(
38 | , document.getElementById('app'));
39 |
--------------------------------------------------------------------------------
/reactjs-flex-order-practice/src/styles/components/datetime.scss:
--------------------------------------------------------------------------------
1 | .datetime {
2 | .time {
3 | font-family: 'Roboto', sans-serif;
4 | font-size: 24px;
5 | vertical-align: middle;
6 | display: inline-block;
7 | }
8 | .date {
9 | font-family: 'Roboto', sans-serif;
10 | font-size: 16px;
11 | margin-top: 5px;
12 | vertical-align: middle;
13 | display: inline-block;
14 | }
15 | height: 50px; // nav menu bar 的高度
16 | text-align: center;
17 | font-size: 0;
18 | &::before {
19 | content: '';
20 | height: 100%;
21 | vertical-align: middle;
22 | display: inline-block;
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/reactjs-flex-order-practice/src/styles/components/input.scss:
--------------------------------------------------------------------------------
1 | //input[type="number"] {
2 | // width:3em;
3 | //}
4 |
--------------------------------------------------------------------------------
/reactjs-flex-order-practice/src/styles/components/loading.scss:
--------------------------------------------------------------------------------
1 | .sk-circle {
2 | margin: 100px auto;
3 | width: 40px;
4 | height: 40px;
5 | position: relative;
6 | }
7 | .sk-circle .sk-child {
8 | width: 100%;
9 | height: 100%;
10 | position: absolute;
11 | left: 0;
12 | top: 0;
13 | }
14 | .sk-circle .sk-child:before {
15 | content: '';
16 | display: block;
17 | margin: 0 auto;
18 | width: 15%;
19 | height: 15%;
20 | background-color: #333;
21 | border-radius: 100%;
22 | -webkit-animation: sk-circleBounceDelay 1.2s infinite ease-in-out both;
23 | animation: sk-circleBounceDelay 1.2s infinite ease-in-out both;
24 | }
25 | .sk-circle .sk-circle2 {
26 | -webkit-transform: rotate(30deg);
27 | -ms-transform: rotate(30deg);
28 | transform: rotate(30deg); }
29 | .sk-circle .sk-circle3 {
30 | -webkit-transform: rotate(60deg);
31 | -ms-transform: rotate(60deg);
32 | transform: rotate(60deg); }
33 | .sk-circle .sk-circle4 {
34 | -webkit-transform: rotate(90deg);
35 | -ms-transform: rotate(90deg);
36 | transform: rotate(90deg); }
37 | .sk-circle .sk-circle5 {
38 | -webkit-transform: rotate(120deg);
39 | -ms-transform: rotate(120deg);
40 | transform: rotate(120deg); }
41 | .sk-circle .sk-circle6 {
42 | -webkit-transform: rotate(150deg);
43 | -ms-transform: rotate(150deg);
44 | transform: rotate(150deg); }
45 | .sk-circle .sk-circle7 {
46 | -webkit-transform: rotate(180deg);
47 | -ms-transform: rotate(180deg);
48 | transform: rotate(180deg); }
49 | .sk-circle .sk-circle8 {
50 | -webkit-transform: rotate(210deg);
51 | -ms-transform: rotate(210deg);
52 | transform: rotate(210deg); }
53 | .sk-circle .sk-circle9 {
54 | -webkit-transform: rotate(240deg);
55 | -ms-transform: rotate(240deg);
56 | transform: rotate(240deg); }
57 | .sk-circle .sk-circle10 {
58 | -webkit-transform: rotate(270deg);
59 | -ms-transform: rotate(270deg);
60 | transform: rotate(270deg); }
61 | .sk-circle .sk-circle11 {
62 | -webkit-transform: rotate(300deg);
63 | -ms-transform: rotate(300deg);
64 | transform: rotate(300deg); }
65 | .sk-circle .sk-circle12 {
66 | -webkit-transform: rotate(330deg);
67 | -ms-transform: rotate(330deg);
68 | transform: rotate(330deg); }
69 | .sk-circle .sk-circle2:before {
70 | -webkit-animation-delay: -1.1s;
71 | animation-delay: -1.1s; }
72 | .sk-circle .sk-circle3:before {
73 | -webkit-animation-delay: -1s;
74 | animation-delay: -1s; }
75 | .sk-circle .sk-circle4:before {
76 | -webkit-animation-delay: -0.9s;
77 | animation-delay: -0.9s; }
78 | .sk-circle .sk-circle5:before {
79 | -webkit-animation-delay: -0.8s;
80 | animation-delay: -0.8s; }
81 | .sk-circle .sk-circle6:before {
82 | -webkit-animation-delay: -0.7s;
83 | animation-delay: -0.7s; }
84 | .sk-circle .sk-circle7:before {
85 | -webkit-animation-delay: -0.6s;
86 | animation-delay: -0.6s; }
87 | .sk-circle .sk-circle8:before {
88 | -webkit-animation-delay: -0.5s;
89 | animation-delay: -0.5s; }
90 | .sk-circle .sk-circle9:before {
91 | -webkit-animation-delay: -0.4s;
92 | animation-delay: -0.4s; }
93 | .sk-circle .sk-circle10:before {
94 | -webkit-animation-delay: -0.3s;
95 | animation-delay: -0.3s; }
96 | .sk-circle .sk-circle11:before {
97 | -webkit-animation-delay: -0.2s;
98 | animation-delay: -0.2s; }
99 | .sk-circle .sk-circle12:before {
100 | -webkit-animation-delay: -0.1s;
101 | animation-delay: -0.1s; }
102 |
103 | @-webkit-keyframes sk-circleBounceDelay {
104 | 0%, 80%, 100% {
105 | -webkit-transform: scale(0);
106 | transform: scale(0);
107 | } 40% {
108 | -webkit-transform: scale(1);
109 | transform: scale(1);
110 | }
111 | }
112 |
113 | @keyframes sk-circleBounceDelay {
114 | 0%, 80%, 100% {
115 | -webkit-transform: scale(0);
116 | transform: scale(0);
117 | } 40% {
118 | -webkit-transform: scale(1);
119 | transform: scale(1);
120 | }
121 | }
--------------------------------------------------------------------------------
/reactjs-flex-order-practice/src/styles/entry.scss:
--------------------------------------------------------------------------------
1 | // variables
2 | @import 'variables/font.scss';
3 | @import 'variables/button.scss';
4 |
5 | // layout
6 | @import 'layout/grid.scss';
7 | @import 'layout/app.scss';
8 |
9 | // Payment style
10 | @import 'pages/Order.scss';
11 |
12 | // input style
13 | @import 'components/input.scss';
14 |
15 | // loading style
16 | // ref:http://tobiasahlin.com/spinkit/
17 | @import 'components/loading';
18 |
19 | @import 'components/datetime.scss';
20 |
--------------------------------------------------------------------------------
/reactjs-flex-order-practice/src/styles/layout/app.scss:
--------------------------------------------------------------------------------
1 | html,
2 | body {
3 | height: 100%;
4 | /* The html and body elements cannot have any padding or margin. */
5 | }
6 |
7 |
8 | // 滿版 window 高度, ref:http://www.bootply.com/97103
9 | .center-container {
10 | height:100%;
11 | }
12 |
13 | .center-row {
14 | height:100%;
15 | background-color: #9ac7d5;
16 | background-size:cover;
17 | overflow-y: hidden;
18 | }
19 |
20 |
21 | #app{
22 | height: 100%;
23 | font-family: "微軟正黑體", "Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial;
24 | }
25 |
26 | /* Wrapper for page content to push down footer */
27 | #wrap {
28 | min-height: 100%;
29 | height: 100%;
30 | /* Negative indent footer by its height */
31 | margin: 0 auto 0 0;
32 | /* Pad bottom by footer height */
33 | padding: 0 0 0 0;
34 |
35 | > .center-container {
36 | padding: 0 0 0 0;
37 | margin:0;
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/reactjs-flex-order-practice/src/styles/layout/grid.scss:
--------------------------------------------------------------------------------
1 | // 複寫 col-* 的 padding & margin
2 | .nopadding {
3 | padding: 0 !important;
4 | margin: 0 !important;
5 | }
--------------------------------------------------------------------------------
/reactjs-flex-order-practice/src/styles/pages/Order.scss:
--------------------------------------------------------------------------------
1 | .order {
2 | .menu {
3 | position: relative;
4 | .item {
5 | margin: 0 15px;
6 | .box {
7 | .text {
8 | margin: 10px;
9 | font-size: $fontValue;
10 | vertical-align: middle;
11 | display: inline-block;
12 | }
13 | margin: 5px;
14 | cursor: pointer;
15 | box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19);
16 | background-color: #d9edf7;
17 | &:hover {
18 | background-color: #dff0d8;
19 | }
20 | &:active {
21 | box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.6), 0 6px 20px 0 rgba(0, 0, 0, 0.59);
22 | }
23 | // 區塊內容置中, height 已預先定義在 DOM 上
24 | text-align: center;
25 | font-size: 0;
26 | &::before {
27 | content: '';
28 | height: 100%;
29 | vertical-align: middle;
30 | display: inline-block;
31 | }
32 | }
33 | }
34 | .item-nav {
35 | position: absolute;
36 | bottom: 0;
37 | .btn {
38 | span {
39 | font-size: $fontTitle;
40 | }
41 | padding: 10px 0;
42 | }
43 | }
44 | }
45 | .content {
46 | // position: relative;
47 | .panel.center-container {
48 | position: relative;
49 | .panel-heading {
50 | font-size: $fontTitle;
51 | }
52 | .panel-body {
53 | // 該 div height 由 js 動態決定
54 | overflow-y: auto; // 依據 div height 作用自動滾輪
55 | .order-details {
56 | table {
57 | font-size: $tableFont;
58 | }
59 | table {
60 | tbody tr td,
61 | thead tr th {
62 | &:nth-child(1) {
63 | width: 45%;
64 | }
65 | &:nth-child(2),
66 | &:nth-child(3) {
67 | width: 20%;
68 | }
69 | &:nth-child(4) {
70 | width: 10%;
71 | }
72 | &:nth-child(5) {
73 | width: 5%;
74 | }
75 | }
76 | }
77 | }
78 | .order-submit {
79 | position: absolute;
80 | bottom: 0;
81 | width: 100%;
82 | padding: 0 15px;
83 | margin-left: -15px;
84 | margin-bottom: -20px;
85 | button[type=submit] {
86 | height: $orderButtonHeight * 2;
87 | font-size: $fontValue;
88 | }
89 | select[name=table] {
90 | height: $orderButtonHeight;
91 | }
92 | .total {
93 | > div {
94 | height: $orderButtonHeight;
95 | }
96 | .title {
97 | font-size: $fontValue;
98 | padding: 6px;
99 | color: #333;
100 | background-color: #f5f5f5;
101 | border-color: #ddd;
102 | border-bottom: 1px solid transparent;
103 | border-top-left-radius: 3px;
104 | border-top-right-radius: 3px;
105 | }
106 | .value {
107 | font-size: $fontValue;
108 | padding: 6px;
109 | }
110 | }
111 | }
112 | }
113 | }
114 | }
115 | }
116 |
--------------------------------------------------------------------------------
/reactjs-flex-order-practice/src/styles/variables/button.scss:
--------------------------------------------------------------------------------
1 | $orderButtonHeight: 55px;
2 |
--------------------------------------------------------------------------------
/reactjs-flex-order-practice/src/styles/variables/font.scss:
--------------------------------------------------------------------------------
1 | $fontTitle: 36px;
2 | $fontValue: 24px;
3 | $tableFont: 20px;
--------------------------------------------------------------------------------
/reactjs-flex-order-practice/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const HtmlWebpackPlugin = require('html-webpack-plugin');
3 |
4 | const HTMLWebpackPluginConfig = new HtmlWebpackPlugin({
5 | template: `${__dirname}/src/index.html`,
6 | filename: 'index.html',
7 | inject: 'body',
8 | });
9 |
10 | module.exports = {
11 | entry: [
12 | './src/index.jsx',
13 | ],
14 | output: {
15 | path: `${__dirname}`,
16 | filename: 'index_bundle.js',
17 | },
18 | module: {
19 | preLoaders: [{
20 | test: /\.jsx$|\.js$/,
21 | loader: 'eslint-loader',
22 | include: `${__dirname}/src`,
23 | exclude: /bundle\.js$/
24 | }],
25 | loaders: [{
26 | test: /\.js$|\.jsx$/,
27 | exclude: /node_modules/,
28 | loader: 'babel-loader',
29 | query: {
30 | presets: ['es2015', 'react'],
31 | },
32 | }, {
33 | test: /\.scss$/,
34 | loaders: ["style", "css", "sass"]
35 | }],
36 | },
37 | devServer: {
38 | inline: true,
39 | port: 8008,
40 | },
41 | plugins: [HTMLWebpackPluginConfig],
42 | };
43 |
--------------------------------------------------------------------------------
/reactjs-order-practice/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | "es2015",
4 | "react",
5 | ],
6 | "plugins": []
7 | }
8 |
--------------------------------------------------------------------------------
/reactjs-order-practice/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "airbnb",
3 | "rules": {
4 | "no-multiple-empty-lines": [2, {"max": 2, "maxEOF": 2}]
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/reactjs-order-practice/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 |
--------------------------------------------------------------------------------
/reactjs-order-practice/db/category.json:
--------------------------------------------------------------------------------
1 | [{
2 | "id": 1,
3 | "name": "\u4e3b\u98df",
4 | "user": "rz",
5 | "is_active": 1,
6 | "created_at": "2016-06-09 20:51:39",
7 | "updated_at": "2016-06-09 20:51:39"
8 | }, {
9 | "id": 2,
10 | "name": "\u6e6f\u54c1",
11 | "user": "rz",
12 | "is_active": 1,
13 | "created_at": "2016-06-09 20:51:39",
14 | "updated_at": "2016-06-09 20:51:39"
15 | }, {
16 | "id": 3,
17 | "name": "\u5c0f\u9ede",
18 | "user": "rz",
19 | "is_active": 1,
20 | "created_at": "2016-06-09 20:51:39",
21 | "updated_at": "2016-06-09 20:51:39"
22 | }]
23 |
--------------------------------------------------------------------------------
/reactjs-order-practice/db/tables.json:
--------------------------------------------------------------------------------
1 | [{
2 | "id": 1,
3 | "name": "一號桌"
4 | }, {
5 | "id": 2,
6 | "name": "二號桌"
7 | }, {
8 | "id": 3,
9 | "name": "三號桌"
10 | }, {
11 | "id": 4,
12 | "name": "四號桌"
13 | }, {
14 | "id": 5,
15 | "name": "五號桌"
16 | }, {
17 | "id": 6,
18 | "name": "六號桌"
19 | }]
20 |
--------------------------------------------------------------------------------
/reactjs-order-practice/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | POS Order - ReactJS + Flux
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/reactjs-order-practice/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "reactjs-order-practice",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "start": "webpack-dev-server --colors --inline --content-base .",
8 | "prod": "webpack -p"
9 | },
10 | "author": "",
11 | "license": "ISC",
12 | "dependencies": {
13 | "react": "^15.3.2",
14 | "react-dom": "^15.3.2"
15 | },
16 | "devDependencies": {
17 | "babel-core": "^6.16.0",
18 | "babel-eslint": "^7.0.0",
19 | "babel-loader": "^6.2.5",
20 | "babel-preset-es2015": "^6.16.0",
21 | "babel-preset-react": "^6.16.0",
22 | "css-loader": "^0.25.0",
23 | "eslint": "^3.6.1",
24 | "eslint-config-airbnb": "^12.0.0",
25 | "eslint-loader": "^1.5.0",
26 | "eslint-plugin-import": "^1.16.0",
27 | "eslint-plugin-jsx-a11y": "^2.2.2",
28 | "eslint-plugin-react": "^6.3.0",
29 | "html-webpack-plugin": "^2.22.0",
30 | "node-sass": "^3.10.1",
31 | "sass-loader": "^4.0.2",
32 | "style-loader": "^0.13.1",
33 | "webpack": "^1.13.2",
34 | "webpack-dev-server": "^1.16.1"
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/reactjs-order-practice/readme.md:
--------------------------------------------------------------------------------
1 | # POS Order
2 |
3 | ## 概觀
4 | 練習以 ReactJS 實作 POS 點單介面
5 |
6 |
7 | ## 開發
8 | 1. 套件相依安裝 `npm install`
9 | 2. 運行服務 `npm start`
10 | 3. 開啟瀏覽器
11 |
12 | ```
13 | http://localhost:8008
14 | ```
15 |
16 | ## 產出 js & html
17 | 運行 `npm run prod`
18 |
19 | > 產出的 js & html file 會在根目錄下
20 |
21 |
22 | ## 參考
23 | * [從零開始學 ReactJS(ReactJS 101)](https://github.com/kdchang/reactjs101)
24 | * [24 小時,React 快速入門](https://github.com/shiningjason1989/react-quick-tutorial/)
25 |
--------------------------------------------------------------------------------
/reactjs-order-practice/src/actions/OrderActions.js:
--------------------------------------------------------------------------------
1 | const OrderActions = {
2 | addItemToOrder(order, item) {
3 | let target = order.find((record) => record.id === item.id);
4 | if (target) {
5 | // 已在 order 內, 更新 obj 的 amount & subtotal
6 | target.amount += 1;
7 | target.subtotal = target.amount * target.price;
8 | } else {
9 | // 若不在 order 內, 產生新 obj
10 | order.push({
11 | id: item.id,
12 | name: item.name,
13 | amount: 1,
14 | price: item.price,
15 | subtotal: 1 * item.price
16 | });
17 | }
18 | return order;
19 | },
20 | removeItemFromOder(order, item) {
21 | let target = order.find((record) => record.id === item.id);
22 | if (target) {
23 | target.amount -= 1;
24 | target.subtotal = target.amount * target.price;
25 | }
26 | order = order.filter((record) => record.amount > 0);
27 | return order;
28 | },
29 | addAdditionToOrder(addition) {
30 | addition.push({
31 | id: new Date().getTime(),
32 | name: '',
33 | amount: 1,
34 | price: 0,
35 | subtotal: 0
36 | });
37 | return addition;
38 | },
39 | removeItemFromAddition(addition, item) {
40 | const additionIdx = addition.findIndex((record) => record.id === item.id);
41 | if (additionIdx !== -1) {
42 | addition.splice(additionIdx, 1);
43 | }
44 | return addition;
45 | },
46 | updateAdditionItem(addition, item) {
47 | let target = addition.find((record) => record.id === item.id);
48 | if (target) {
49 | target.name = item.name;
50 | target.amount = item.amount;
51 | target.price = item.price;
52 | target.subtotal = item.amount * item.price;
53 | }
54 | return addition;
55 | },
56 | updateTotal(order, addition) {
57 | let total = 0;
58 | order.map((el) => total += el.subtotal);
59 | addition.map((el) => total += el.subtotal);
60 | return total;
61 | },
62 | toggleWhenStatus(when) {
63 | return (when === '中午') ? '晚上' : '中午';
64 | },
65 | toggleIsToGoStatus(isToGo) {
66 | return !isToGo;
67 | }
68 | };
69 |
70 | export default OrderActions;
71 |
--------------------------------------------------------------------------------
/reactjs-order-practice/src/components/OrderContent.jsx:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react';
2 | import OrderConstants from '../constants/OrderConstants';
3 | import OrderContentDetail from './OrderContentDetail.jsx';
4 | import OrderContentOperation from './OrderContentOperation.jsx';
5 | import OrderContentSubmit from './OrderContentSubmit.jsx';
6 | import OrderContentTotal from './OrderContentTotal.jsx';
7 |
8 | class OrderContent extends Component {
9 | getOrderContentRowHeight() {
10 | const bodyHeight = document.body.clientHeight;
11 | const orderContentHeight = (bodyHeight - OrderConstants.contentDetailHeadingHeight - OrderConstants.contentOperationHeight);
12 | this.setState({
13 | orderContentStyle: {
14 | height: orderContentHeight + 'px'
15 | }
16 | });
17 | }
18 |
19 | constructor(props) {
20 | super(props);
21 | this.state = {
22 | orderContentStyle: {
23 | height: 'inherit'
24 | }
25 | };
26 | this.getOrderContentRowHeight = this.getOrderContentRowHeight.bind(this);
27 | }
28 |
29 | componentDidMount() {
30 | // item 動態列高
31 | this.getOrderContentRowHeight();
32 | window.addEventListener('resize', () => this.getOrderContentRowHeight());
33 | }
34 |
35 | componentWillUnmount() {
36 | window.removeEventListener('resize', () => this.getOrderContentRowHeight());
37 | }
38 |
39 | render() {
40 | return (
41 |
42 |
43 |
點單明細
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 | );
65 | }
66 | }
67 |
68 | export default OrderContent;
69 |
--------------------------------------------------------------------------------
/reactjs-order-practice/src/components/OrderContentDetail.jsx:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react';
2 | import OrderActions from './../actions/OrderActions';
3 |
4 | class OrderItemFromMenu extends Component {
5 | handleClickItem() {
6 | this.props.updateOrder(OrderActions.removeItemFromOder, this.props.item);
7 | }
8 |
9 | constructor(props) {
10 | super(props);
11 | this.handleClickItem = this.handleClickItem.bind(this);
12 | }
13 |
14 | render() {
15 | return (
16 |
17 | {this.props.item.name} |
18 | {this.props.item.amount} |
19 | {this.props.item.price} |
20 | {this.props.item.subtotal} |
21 |
22 |
25 | |
26 |
27 | );
28 | }
29 |
30 | }
31 |
32 | class OrderItemFromAddition extends Component {
33 | handleChange(e) {
34 | // 取值
35 | const id = e.target.dataset.id;
36 | const field = e.target.dataset.field;
37 | const value = e.target.value;
38 |
39 | // 改變 state
40 | let _item = this.state.item;
41 | _item[field] = value;
42 | _item.subtotal = _item.amount * _item.price;
43 | this.setState({item: _item});
44 |
45 | // 改變 store 的 addition
46 | this.props.updateAddition(OrderActions.updateAdditionItem, _item);
47 | }
48 |
49 | handleClickAddition(item) {
50 | this.props.updateAddition(OrderActions.removeItemFromAddition, item);
51 | }
52 |
53 | constructor(props) {
54 | super(props);
55 | this.state = {
56 | item: props.item
57 | };
58 | this.handleChange = this.handleChange.bind(this);
59 | this.handleClickAddition = this.handleClickAddition.bind(this);
60 | }
61 |
62 | render() {
63 | return (
64 |
65 | |
66 | |
67 | |
68 | {this.state.item.subtotal} |
69 |
70 |
73 | |
74 |
75 | );
76 | }
77 | }
78 |
79 | class OrderContentDetail extends Component {
80 | render() {
81 | return (
82 |
83 |
84 |
85 | 品項 |
86 | 數量 |
87 | 單價 |
88 | 小計 |
89 | |
90 |
91 |
92 |
93 | {this.props.order.map((record) => ())}
94 | {this.props.addition.map((record) => ())}
95 |
96 |
97 | );
98 | }
99 | }
100 |
101 | export default OrderContentDetail;
102 |
--------------------------------------------------------------------------------
/reactjs-order-practice/src/components/OrderContentOperation.jsx:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react';
2 | import OrderActions from './../actions/OrderActions';
3 |
4 | class OrderAddAddition extends Component {
5 | handleClick() {
6 | this.props.updateAddition(OrderActions.addAdditionToOrder);
7 | }
8 |
9 | constructor(props) {
10 | super(props);
11 | this.handleClick = this.handleClick.bind(this);
12 | }
13 |
14 | render() {
15 | return (
16 |
17 |
22 |
23 | );
24 | }
25 | }
26 |
27 | class OrderToGo extends Component {
28 | handleClick() {
29 | this.props.updateIsToGoStatus();
30 | }
31 |
32 | constructor(props) {
33 | super(props);
34 | this.state = {};
35 | this.handleClick = this.handleClick.bind(this);
36 | }
37 |
38 | render() {
39 | const toGoIconClass = (this.props.isToGo)
40 | ? 'fa fa-hand-o-right'
41 | : 'fa fa-cutlery'; // 外帶狀態 icon
42 | const toGoButtonClass = ((this.props.isToGo)
43 | ? 'btn btn-danger'
44 | : 'btn btn-success') + ' btn-lg btn-block';
45 | return (
46 |
47 |
54 |
55 | );
56 | }
57 | }
58 |
59 | class OrderWhen extends Component {
60 | handleClick() {
61 | this.props.updateWhenStatus();
62 | }
63 |
64 | constructor(props) {
65 | super(props);
66 | this.state = {};
67 | this.handleClick = this.handleClick.bind(this);
68 | }
69 |
70 | render() {
71 | const whenIconClass = (this.props.when === '中午')
72 | ? 'fa fa-sun-o'
73 | : 'fa fa-moon-o'; // 中午/晚上狀態 icon
74 | const whenButtonClass = ((this.props.when === '中午')
75 | ? 'btn btn-warning'
76 | : 'btn btn-primary') + ' btn-lg btn-block'; // 中午/晚上狀態 icon
77 | return (
78 |
79 |
84 |
85 | );
86 | }
87 | }
88 |
89 | class OrderTables extends Component {
90 | render() {
91 | if (this.props.isToGo == true) {
92 | return (
93 |
94 |
97 |
98 | );
99 | }
100 | return (
101 |
102 |
109 |
110 | );
111 | }
112 | }
113 |
114 | const OrderContentOperation = (props) => (
115 |
116 |
117 |
118 |
119 |
120 |
121 | );
122 |
123 | export default OrderContentOperation;
124 |
--------------------------------------------------------------------------------
/reactjs-order-practice/src/components/OrderContentSubmit.jsx:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react';
2 |
3 | class OrderSubmit extends Component {
4 | handleClick() {
5 | // dump order & addition records
6 | const data = {
7 | order: this.props.order,
8 | addition: this.props.addition
9 | };
10 | console.log('--- dump order & addition records ---');
11 | console.log(data);
12 |
13 | // clear order & addition records
14 | console.log('--- clear order & addition records ---');
15 | this.props.clearOrderAndAdditon();
16 |
17 | }
18 |
19 | constructor(props) {
20 | super(props);
21 | this.handleClick = this.handleClick.bind(this);
22 | }
23 |
24 | render() {
25 | return (
26 |
31 | );
32 | }
33 | }
34 |
35 | export default OrderSubmit;
36 |
--------------------------------------------------------------------------------
/reactjs-order-practice/src/components/OrderContentTotal.jsx:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react';
2 |
3 | const OrderContentTotal = (props) => (
4 |
5 |
6 | 總計
7 |
8 |
9 | {props.total}
10 |
11 |
12 | );
13 |
14 | export default OrderContentTotal;
15 |
--------------------------------------------------------------------------------
/reactjs-order-practice/src/components/OrderMenu.jsx:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react';
2 | import OrderConstants from '../constants/OrderConstants';
3 | import OrderMenuCategoryList from './OrderMenuCategoryList.jsx'
4 | import OrderMenuItemList from './OrderMenuItemList.jsx'
5 | import OrderMenuPager from './OrderMenuPager.jsx'
6 |
7 | class OrderMenu extends Component {
8 | updateMenu(categoryId, pageId) {
9 | const items = this.props.items.filter((record) => {
10 | return record.category_id == categoryId;
11 | }).filter((record, i) => {
12 | return OrderConstants.itemsPerPageAmount * pageId <= i && i < OrderConstants.itemsPerPageAmount * (1 + pageId);
13 | });
14 | this.setState({items: items, categoryId: categoryId});
15 | }
16 |
17 | constructor(props) {
18 | super(props);
19 | this.state = {
20 | items: [],
21 | categoryId: null,
22 | currentPage: null
23 | };
24 | this.updateMenu = this.updateMenu.bind(this);
25 | }
26 |
27 | componentWillReceiveProps(nextProps) {
28 | if (nextProps.categories.length > 0 && this.state.categoryId === null) {
29 | // set default category && menu page
30 | const categoryId = nextProps.categories[0].id
31 | const currentPage = 0;
32 | this.setState({selectedCategory: categoryId, currentPage: currentPage});
33 |
34 | // refresh menu item
35 | this.updateMenu(categoryId, currentPage);
36 | }
37 | }
38 |
39 | render() {
40 | return (
41 |
42 |
43 |
44 |
45 |
46 | );
47 | }
48 | }
49 |
50 | export default OrderMenu;
51 |
--------------------------------------------------------------------------------
/reactjs-order-practice/src/components/OrderMenuCategoryList.jsx:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react';
2 |
3 | // stateless component
4 | const OrderMenyCategoryListItem = (props) => {
5 | const itemClass = props.isSelected
6 | ? "active"
7 | : "";
8 | return (
9 |
10 |
11 | {props.name}
12 |
13 |
14 | )
15 | };
16 |
17 | class OrderMenuCategoryList extends Component {
18 | handleClick(categoryId) {
19 | // refresh menu item
20 | const currentPage = 0;
21 | this.props.updateMenu(categoryId, currentPage);
22 | }
23 |
24 | constructor(props) {
25 | super(props);
26 | this.handleClick = this.handleClick.bind(this);
27 | }
28 |
29 | render() {
30 | return (
31 |
32 |
33 | {this.props.categories.map((record) => ())}
34 |
35 |
36 | );
37 | }
38 | }
39 |
40 | export default OrderMenuCategoryList;
41 |
--------------------------------------------------------------------------------
/reactjs-order-practice/src/components/OrderMenuItemList.jsx:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react';
2 | import OrderConstants from '../constants/OrderConstants';
3 | import OrderActions from '../actions/OrderActions';
4 |
5 | class OrderMenuItem extends Component {
6 | handleClickItem(item) {
7 | this.props.updateOrder(OrderActions.addItemToOrder, item);
8 | }
9 |
10 | constructor(props) {
11 | super(props);
12 | this.handleClickItem = this.handleClickItem.bind(this);
13 | }
14 |
15 | render() {
16 | return (
17 |
18 |
19 |
{this.props.item.name}
20 |
21 |
22 | );
23 | }
24 | }
25 |
26 | class OrderMenuItemList extends Component {
27 | getItemRowHeight() {
28 | const bodyHeight = document.body.clientHeight;
29 | const rowHeight = (bodyHeight - OrderConstants.menuItemRowMarginHeight - OrderConstants.menuCategoryListHeight - OrderConstants.menuPagerHeight) / OrderConstants.menuItemListRows;
30 | this.setState({
31 | itemRowStyle: {
32 | height: rowHeight + 'px'
33 | }
34 | });
35 | }
36 |
37 | constructor(props) {
38 | super(props);
39 | this.state = {
40 | itemRowStyle: {
41 | height: 'inherit'
42 | }
43 | };
44 | this.getItemRowHeight = this.getItemRowHeight.bind(this);
45 | }
46 |
47 | componentDidMount() {
48 | // item 動態列高
49 | this.getItemRowHeight();
50 | window.addEventListener('resize', () => this.getItemRowHeight());
51 | }
52 |
53 | componentWillUnmount() {
54 | window.removeEventListener('resize', () => this.getItemRowHeight());
55 | }
56 |
57 | render() {
58 | return (
59 |
60 |
61 | {this.props.items.map((record) => ())}
62 |
63 |
64 | );
65 | }
66 | }
67 |
68 | export default OrderMenuItemList;
69 |
--------------------------------------------------------------------------------
/reactjs-order-practice/src/components/OrderMenuPager.jsx:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react';
2 | import OrderConstants from '../constants/OrderConstants';
3 |
4 | class OrderMenuPager extends Component {
5 | handleClick(pageId) {
6 | // 更新當前頁面 idx
7 | this.setState({currentMenuPage: pageId});
8 |
9 | // refresh menu item
10 | this.props.updateMenu(this.props.categoryId, pageId);
11 | }
12 |
13 | constructor(props) {
14 | super(props);
15 | this.state = {
16 | menuPagesCount: null,
17 | currentMenuPage: 0
18 | };
19 | this.handleClick = this.handleClick.bind(this);
20 | }
21 |
22 | componentWillReceiveProps(nextProps) {
23 | if (this.props.categoryId !== nextProps.categoryId && nextProps.categoryId !== null) {
24 | const menuPagesCount = Math.ceil(nextProps.items.filter((record) => {
25 | return record.category_id == nextProps.categoryId;
26 | }).length / OrderConstants.itemsPerPageAmount);
27 | this.setState({menuPagesCount: menuPagesCount});
28 | }
29 | }
30 |
31 | render() {
32 | const hasNextPage = this.state.menuPagesCount - this.state.currentMenuPage > 1; // 是否還有下一頁
33 | const hasPrevPage = this.state.currentMenuPage - 1 > -1; // 是否還有上一頁
34 | const nextPage = hasNextPage
35 | ? this.state.currentMenuPage + 1
36 | : this.state.currentMenuPage;
37 | const prevPage = hasPrevPage
38 | ? this.state.currentMenuPage - 1
39 | : this.state.currentMenuPage;
40 |
41 | return (
42 |
43 |
44 |
45 |
50 |
51 |
52 |
57 |
58 |
59 |
60 | );
61 | }
62 | }
63 |
64 | export default OrderMenuPager;
65 |
--------------------------------------------------------------------------------
/reactjs-order-practice/src/constants/OrderConstants.js:
--------------------------------------------------------------------------------
1 | const now = new Date();
2 | const nightStart = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 17, 30, 0, 0);
3 | const whenStatus = (now.getTime() - nightStart.getTime() < 0) ? '中午' : '晚上';
4 |
5 | const OrderConstants = {
6 | // parameters
7 | itemsPerPageAmount: 12, // 每頁菜單項目數
8 | menuItemListRows: 3, // 菜單列數
9 | menuItemRowMarginHeight: 30, // 菜單列與列之間 margin 高
10 | menuCategoryListHeight: 87, // 菜單分類 DOM 高
11 | menuPagerHeight: 62, // 菜單分頁 DOM 高
12 | contentDetailHeadingHeight: 72, // "點單明細" 高
13 | contentOperationHeight: 133, // order 操作區域高
14 | // store default value
15 | whenStatus: whenStatus // 菜單 中午/晚上的狀態
16 | };
17 |
18 | export default OrderConstants;
19 |
--------------------------------------------------------------------------------
/reactjs-order-practice/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | POS Order - ReactJS + Flux
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/reactjs-order-practice/src/index.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import OrderMenu from './components/OrderMenu.jsx';
4 | import OrderContent from './components/OrderContent.jsx';
5 | import OrderActions from './actions/OrderActions';
6 | import OrderConstants from './constants/OrderConstants';
7 |
8 | require("!style!css!sass!./styles/entry.scss");
9 |
10 | class App extends React.Component {
11 | updateOrder(cb, item) {
12 | const order = cb(this.state.order, item);
13 | const total = OrderActions.updateTotal(order, this.state.addition);
14 | this.setState({order: order, total: total});
15 | }
16 |
17 | updateAddition(cb, item) {
18 | const addition = cb(this.state.addition, item);
19 | const total = OrderActions.updateTotal(this.state.order, addition);
20 | this.setState({addition: addition, total: total});
21 | }
22 |
23 | updateIsToGoStatus() {
24 | this.setState({
25 | isToGo: OrderActions.toggleIsToGoStatus(this.state.isToGo)
26 | });
27 | }
28 |
29 | updateWhenStatus() {
30 | this.setState({
31 | when: OrderActions.toggleWhenStatus(this.state.when)
32 | });
33 | }
34 |
35 | clearOrderAndAdditon() {
36 | this.setState({
37 | order: [],
38 | addition: [],
39 | total: 0,
40 | });
41 | }
42 |
43 | constructor(props) {
44 | super(props);
45 | this.state = {
46 | categories: [], // for
47 | items: [], // for
48 | order: [], // for
49 | addition: [], // for
50 | tables: [], // for
51 | when: OrderConstants.whenStatus, // for
52 | isToGo: false, // for
53 | total: 0, // for
54 | };
55 | this.updateOrder = this.updateOrder.bind(this);
56 | this.updateAddition = this.updateAddition.bind(this);
57 | this.updateIsToGoStatus = this.updateIsToGoStatus.bind(this);
58 | this.updateWhenStatus = this.updateWhenStatus.bind(this);
59 | this.clearOrderAndAdditon = this.clearOrderAndAdditon.bind(this);
60 | }
61 |
62 | componentDidMount() {
63 | // load items
64 | fetch('./db/item.json', {method: 'GET'}).then((response) => response.json()).then((records) => {
65 | this.setState({items: records});
66 | });
67 |
68 | // load categories && set default category
69 | fetch('./db/category.json', {method: 'GET'}).then((response) => response.json()).then((records) => {
70 | this.setState({categories: records, selectedCategory: records[0].id});
71 | });
72 |
73 | // load tables
74 | fetch('./db/tables.json', {method: 'GET'}).then((response) => response.json()).then((records) => {
75 | this.setState({tables: records});
76 | });
77 | }
78 |
79 | render() {
80 | return (
81 |
82 |
83 |
84 |
85 | {/* 左側欄 */}
86 | {/* 右側欄 */}
87 |
88 |
89 |
90 |
91 | );
92 | }
93 | }
94 |
95 | ReactDOM.render(
96 | , document.getElementById('app'));
97 |
--------------------------------------------------------------------------------
/reactjs-order-practice/src/styles/components/datetime.scss:
--------------------------------------------------------------------------------
1 | .datetime {
2 | .time {
3 | font-family: 'Roboto', sans-serif;
4 | font-size: 24px;
5 | vertical-align: middle;
6 | display: inline-block;
7 | }
8 | .date {
9 | font-family: 'Roboto', sans-serif;
10 | font-size: 16px;
11 | margin-top: 5px;
12 | vertical-align: middle;
13 | display: inline-block;
14 | }
15 | height: 50px; // nav menu bar 的高度
16 | text-align: center;
17 | font-size: 0;
18 | &::before {
19 | content: '';
20 | height: 100%;
21 | vertical-align: middle;
22 | display: inline-block;
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/reactjs-order-practice/src/styles/components/input.scss:
--------------------------------------------------------------------------------
1 | //input[type="number"] {
2 | // width:3em;
3 | //}
4 |
--------------------------------------------------------------------------------
/reactjs-order-practice/src/styles/components/loading.scss:
--------------------------------------------------------------------------------
1 | .sk-circle {
2 | margin: 100px auto;
3 | width: 40px;
4 | height: 40px;
5 | position: relative;
6 | }
7 | .sk-circle .sk-child {
8 | width: 100%;
9 | height: 100%;
10 | position: absolute;
11 | left: 0;
12 | top: 0;
13 | }
14 | .sk-circle .sk-child:before {
15 | content: '';
16 | display: block;
17 | margin: 0 auto;
18 | width: 15%;
19 | height: 15%;
20 | background-color: #333;
21 | border-radius: 100%;
22 | -webkit-animation: sk-circleBounceDelay 1.2s infinite ease-in-out both;
23 | animation: sk-circleBounceDelay 1.2s infinite ease-in-out both;
24 | }
25 | .sk-circle .sk-circle2 {
26 | -webkit-transform: rotate(30deg);
27 | -ms-transform: rotate(30deg);
28 | transform: rotate(30deg); }
29 | .sk-circle .sk-circle3 {
30 | -webkit-transform: rotate(60deg);
31 | -ms-transform: rotate(60deg);
32 | transform: rotate(60deg); }
33 | .sk-circle .sk-circle4 {
34 | -webkit-transform: rotate(90deg);
35 | -ms-transform: rotate(90deg);
36 | transform: rotate(90deg); }
37 | .sk-circle .sk-circle5 {
38 | -webkit-transform: rotate(120deg);
39 | -ms-transform: rotate(120deg);
40 | transform: rotate(120deg); }
41 | .sk-circle .sk-circle6 {
42 | -webkit-transform: rotate(150deg);
43 | -ms-transform: rotate(150deg);
44 | transform: rotate(150deg); }
45 | .sk-circle .sk-circle7 {
46 | -webkit-transform: rotate(180deg);
47 | -ms-transform: rotate(180deg);
48 | transform: rotate(180deg); }
49 | .sk-circle .sk-circle8 {
50 | -webkit-transform: rotate(210deg);
51 | -ms-transform: rotate(210deg);
52 | transform: rotate(210deg); }
53 | .sk-circle .sk-circle9 {
54 | -webkit-transform: rotate(240deg);
55 | -ms-transform: rotate(240deg);
56 | transform: rotate(240deg); }
57 | .sk-circle .sk-circle10 {
58 | -webkit-transform: rotate(270deg);
59 | -ms-transform: rotate(270deg);
60 | transform: rotate(270deg); }
61 | .sk-circle .sk-circle11 {
62 | -webkit-transform: rotate(300deg);
63 | -ms-transform: rotate(300deg);
64 | transform: rotate(300deg); }
65 | .sk-circle .sk-circle12 {
66 | -webkit-transform: rotate(330deg);
67 | -ms-transform: rotate(330deg);
68 | transform: rotate(330deg); }
69 | .sk-circle .sk-circle2:before {
70 | -webkit-animation-delay: -1.1s;
71 | animation-delay: -1.1s; }
72 | .sk-circle .sk-circle3:before {
73 | -webkit-animation-delay: -1s;
74 | animation-delay: -1s; }
75 | .sk-circle .sk-circle4:before {
76 | -webkit-animation-delay: -0.9s;
77 | animation-delay: -0.9s; }
78 | .sk-circle .sk-circle5:before {
79 | -webkit-animation-delay: -0.8s;
80 | animation-delay: -0.8s; }
81 | .sk-circle .sk-circle6:before {
82 | -webkit-animation-delay: -0.7s;
83 | animation-delay: -0.7s; }
84 | .sk-circle .sk-circle7:before {
85 | -webkit-animation-delay: -0.6s;
86 | animation-delay: -0.6s; }
87 | .sk-circle .sk-circle8:before {
88 | -webkit-animation-delay: -0.5s;
89 | animation-delay: -0.5s; }
90 | .sk-circle .sk-circle9:before {
91 | -webkit-animation-delay: -0.4s;
92 | animation-delay: -0.4s; }
93 | .sk-circle .sk-circle10:before {
94 | -webkit-animation-delay: -0.3s;
95 | animation-delay: -0.3s; }
96 | .sk-circle .sk-circle11:before {
97 | -webkit-animation-delay: -0.2s;
98 | animation-delay: -0.2s; }
99 | .sk-circle .sk-circle12:before {
100 | -webkit-animation-delay: -0.1s;
101 | animation-delay: -0.1s; }
102 |
103 | @-webkit-keyframes sk-circleBounceDelay {
104 | 0%, 80%, 100% {
105 | -webkit-transform: scale(0);
106 | transform: scale(0);
107 | } 40% {
108 | -webkit-transform: scale(1);
109 | transform: scale(1);
110 | }
111 | }
112 |
113 | @keyframes sk-circleBounceDelay {
114 | 0%, 80%, 100% {
115 | -webkit-transform: scale(0);
116 | transform: scale(0);
117 | } 40% {
118 | -webkit-transform: scale(1);
119 | transform: scale(1);
120 | }
121 | }
--------------------------------------------------------------------------------
/reactjs-order-practice/src/styles/entry.scss:
--------------------------------------------------------------------------------
1 | // variables
2 | @import 'variables/font.scss';
3 | @import 'variables/button.scss';
4 |
5 | // layout
6 | @import 'layout/grid.scss';
7 | @import 'layout/app.scss';
8 |
9 | // Payment style
10 | @import 'pages/Order.scss';
11 |
12 | // input style
13 | @import 'components/input.scss';
14 |
15 | // loading style
16 | // ref:http://tobiasahlin.com/spinkit/
17 | @import 'components/loading';
18 |
19 | @import 'components/datetime.scss';
20 |
--------------------------------------------------------------------------------
/reactjs-order-practice/src/styles/layout/app.scss:
--------------------------------------------------------------------------------
1 | html,
2 | body {
3 | height: 100%;
4 | /* The html and body elements cannot have any padding or margin. */
5 | }
6 |
7 |
8 | // 滿版 window 高度, ref:http://www.bootply.com/97103
9 | .center-container {
10 | height:100%;
11 | }
12 |
13 | .center-row {
14 | height:100%;
15 | background-color: #9ac7d5;
16 | background-size:cover;
17 | overflow-y: hidden;
18 | }
19 |
20 |
21 | #app{
22 | height: 100%;
23 | font-family: "微軟正黑體", "Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial;
24 | }
25 |
26 | /* Wrapper for page content to push down footer */
27 | #wrap {
28 | min-height: 100%;
29 | height: 100%;
30 | /* Negative indent footer by its height */
31 | margin: 0 auto 0 0;
32 | /* Pad bottom by footer height */
33 | padding: 0 0 0 0;
34 |
35 | > .center-container {
36 | padding: 0 0 0 0;
37 | margin:0;
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/reactjs-order-practice/src/styles/layout/grid.scss:
--------------------------------------------------------------------------------
1 | // 複寫 col-* 的 padding & margin
2 | .nopadding {
3 | padding: 0 !important;
4 | margin: 0 !important;
5 | }
--------------------------------------------------------------------------------
/reactjs-order-practice/src/styles/pages/Order.scss:
--------------------------------------------------------------------------------
1 | .order {
2 | .menu {
3 | position: relative;
4 | .item {
5 | margin: 0 15px;
6 | .box {
7 | .text {
8 | margin: 10px;
9 | font-size: $fontValue;
10 | vertical-align: middle;
11 | display: inline-block;
12 | }
13 | margin: 5px;
14 | cursor: pointer;
15 | box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19);
16 | background-color: #d9edf7;
17 | &:hover {
18 | background-color: #dff0d8;
19 | }
20 | &:active {
21 | box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.6), 0 6px 20px 0 rgba(0, 0, 0, 0.59);
22 | }
23 | // 區塊內容置中, height 已預先定義在 DOM 上
24 | text-align: center;
25 | font-size: 0;
26 | &::before {
27 | content: '';
28 | height: 100%;
29 | vertical-align: middle;
30 | display: inline-block;
31 | }
32 | }
33 | }
34 | .item-nav {
35 | position: absolute;
36 | bottom: 0;
37 | .btn {
38 | span {
39 | font-size: $fontTitle;
40 | }
41 | padding: 10px 0;
42 | }
43 | }
44 | }
45 | .content {
46 | // position: relative;
47 | .panel.center-container {
48 | position: relative;
49 | .panel-heading {
50 | font-size: $fontTitle;
51 | }
52 | .panel-body {
53 | // 該 div height 由 js 動態決定
54 | overflow-y: auto; // 依據 div height 作用自動滾輪
55 | .order-details {
56 | table {
57 | font-size: $tableFont;
58 | }
59 | table {
60 | tbody tr td,
61 | thead tr th {
62 | &:nth-child(1) {
63 | width: 45%;
64 | }
65 | &:nth-child(2),
66 | &:nth-child(3) {
67 | width: 20%;
68 | }
69 | &:nth-child(4) {
70 | width: 10%;
71 | }
72 | &:nth-child(5) {
73 | width: 5%;
74 | }
75 | }
76 | }
77 | }
78 | .order-submit {
79 | position: absolute;
80 | bottom: 0;
81 | width: 100%;
82 | padding: 0 15px;
83 | margin-left: -15px;
84 | margin-bottom: -20px;
85 | button[type=submit] {
86 | height: $orderButtonHeight * 2;
87 | font-size: $fontValue;
88 | }
89 | select[name=table] {
90 | height: $orderButtonHeight;
91 | }
92 | .total {
93 | > div {
94 | height: $orderButtonHeight;
95 | }
96 | .title {
97 | font-size: $fontValue;
98 | padding: 6px;
99 | color: #333;
100 | background-color: #f5f5f5;
101 | border-color: #ddd;
102 | border-bottom: 1px solid transparent;
103 | border-top-left-radius: 3px;
104 | border-top-right-radius: 3px;
105 | }
106 | .value {
107 | font-size: $fontValue;
108 | padding: 6px;
109 | }
110 | }
111 | }
112 | }
113 | }
114 | }
115 | }
116 |
--------------------------------------------------------------------------------
/reactjs-order-practice/src/styles/variables/button.scss:
--------------------------------------------------------------------------------
1 | $orderButtonHeight: 55px;
2 |
--------------------------------------------------------------------------------
/reactjs-order-practice/src/styles/variables/font.scss:
--------------------------------------------------------------------------------
1 | $fontTitle: 36px;
2 | $fontValue: 24px;
3 | $tableFont: 20px;
--------------------------------------------------------------------------------
/reactjs-order-practice/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const HtmlWebpackPlugin = require('html-webpack-plugin');
3 |
4 | const HTMLWebpackPluginConfig = new HtmlWebpackPlugin({
5 | template: `${__dirname}/src/index.html`,
6 | filename: 'index.html',
7 | inject: 'body',
8 | });
9 |
10 | module.exports = {
11 | entry: [
12 | './src/index.jsx',
13 | ],
14 | output: {
15 | path: `${__dirname}`,
16 | filename: 'index_bundle.js',
17 | },
18 | module: {
19 | preLoaders: [{
20 | test: /\.jsx$|\.js$/,
21 | loader: 'eslint-loader',
22 | include: `${__dirname}/src`,
23 | exclude: /bundle\.js$/
24 | }],
25 | loaders: [{
26 | test: /\.js$|\.jsx$/,
27 | exclude: /node_modules/,
28 | loader: 'babel-loader',
29 | query: {
30 | presets: ['es2015', 'react'],
31 | },
32 | }, {
33 | test: /\.scss$/,
34 | loaders: ["style", "css", "sass"]
35 | }],
36 | },
37 | devServer: {
38 | inline: true,
39 | port: 8008,
40 | },
41 | plugins: [HTMLWebpackPluginConfig],
42 | };
43 |
--------------------------------------------------------------------------------
/reactjs-php-isomorphic-example/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
--------------------------------------------------------------------------------
/reactjs-php-isomorphic-example/README.md:
--------------------------------------------------------------------------------
1 | # react-php-isomorphic-example
2 |
3 | 部屬
4 | ---
5 | **安裝相依套件 & 打包 components**
6 | ```
7 | npm install
8 | npm run watch
9 | ```
10 | **運行 http server (via PHP)**
11 | ```
12 | php -S 0.0.0.0:8080 -t .
13 | ```
14 | `http://0.0.0.0:8080/`
15 |
16 | 環境
17 | ---
18 | * PHP 5.5.9
19 | * V8Js 0.1.3 (PHP extension)
20 |
21 | 參考
22 | ---
23 | * [v8js](https://github.com/phpv8/v8js)
24 | * [How To Install PHP-V8JS in Ubuntu 16.04](https://www.infinitastech.com/blog/php/how-to-install-php-v8js-in-ubuntu-16-04/)
25 | * Install in Windows (Download)
26 | * [PHP 5.5.9](http://windows.php.net/downloads/releases/archives/php-5.5.9-nts-Win32-VC11-x86.zip)
27 | * [V8Js 0.1.3](http://windows.php.net/downloads/pecl/snaps/v8js/0.1.3/php_v8js-0.1.3-5.5-nts-vc11-x86.zip)
28 | * Example: [easylearntutorial/react-js/server-rendering-php](https://github.com/formigone/easylearntutorial/tree/master/react-js/server-rendering-php)
--------------------------------------------------------------------------------
/reactjs-php-isomorphic-example/build/.gitignore:
--------------------------------------------------------------------------------
1 | *.js
--------------------------------------------------------------------------------
/reactjs-php-isomorphic-example/index.php:
--------------------------------------------------------------------------------
1 | [
5 | [1,2,3],
6 | [4,5,6],
7 | [7,8,9]
8 | ]
9 | ];
10 | $propsJson = json_encode($props);
11 | $react = [
12 | file_get_contents(__DIR__.'/node_modules/react/dist/react.min.js'),
13 | file_get_contents(__DIR__.'/node_modules/react-dom/dist/react-dom-server.min.js'),
14 | file_get_contents(__DIR__.'/build/app.js'),
15 | 'ReactDOMServer.renderToString(React.createElement(App, ' . $propsJson . '))'
16 | ];
17 |
18 | try {
19 | $reactStr = $v8->executeString(implode(PHP_EOL, $react));
20 | } catch (Exception $e) {
21 | echo '', $e->getMessage(), '
';
22 | echo '', $e->getTraceAsString(), '
';
23 | exit;
24 | }
25 |
26 | ?>
27 |
28 |
29 |
30 | React page
31 |
32 |
33 |
34 | = $reactStr; ?>
35 |
36 |
37 |
38 |
39 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/reactjs-php-isomorphic-example/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "reactjs-php-isomorphic-example",
3 | "version": "0.0.1",
4 | "scripts": {
5 | "watch": "webpack --progress --colors --watch"
6 | },
7 | "dependencies": {
8 | "react": "^15.3.2",
9 | "react-dom": "^15.3.2"
10 | },
11 | "devDependencies": {
12 | "babel-cli": "~6.18.0",
13 | "babel-core": "^6.17.0",
14 | "babel-loader": "~6.2.9",
15 | "babel-preset-es2015": "^6.16.0",
16 | "babel-preset-react": "^6.16.0",
17 | "webpack": "~1.14.0"
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/reactjs-php-isomorphic-example/src/App.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import Table from './Table'
3 | import Heading from './Heading'
4 |
5 | const App = ({data}) => (
6 |
7 |
8 |
9 |
10 | )
11 |
12 | global.App = App
13 |
--------------------------------------------------------------------------------
/reactjs-php-isomorphic-example/src/Heading.js:
--------------------------------------------------------------------------------
1 | const Heading = () => (Hello world!!
)
2 |
3 | export default Heading
4 |
--------------------------------------------------------------------------------
/reactjs-php-isomorphic-example/src/Table.js:
--------------------------------------------------------------------------------
1 | const Table = ({data}) => (
2 |
3 | {data.map((row, i) => (
4 |
5 | {row.map((cell, j) => (
6 | {cell} |
7 | ))}
8 |
9 | ))}
10 |
11 | )
12 |
13 | export default Table
14 |
--------------------------------------------------------------------------------
/reactjs-php-isomorphic-example/webpack.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | entry: "./src/App.js",
3 | output: {
4 | path: "./build",
5 | filename: "app.js"
6 | },
7 | module: {
8 | loaders: [{
9 | test: /\.js$|\.jsx$/,
10 | exclude: /node_modules/,
11 | loader: 'babel-loader',
12 | query: {
13 | presets: ['es2015', 'react'],
14 | },
15 | }]
16 | },
17 | externals: {
18 | react: 'React',
19 | 'react-dom': 'ReactDOM'
20 | },
21 | resolve: {
22 | extensions: ['', '.js', '.jsx']
23 | }
24 | };
25 |
--------------------------------------------------------------------------------
/reactjs-redux-order-practice/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | "es2015",
4 | "react",
5 | ],
6 | "plugins": []
7 | }
8 |
--------------------------------------------------------------------------------
/reactjs-redux-order-practice/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "airbnb",
3 | "rules": {
4 | "no-multiple-empty-lines": [2, {"max": 2, "maxEOF": 2}]
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/reactjs-redux-order-practice/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 |
--------------------------------------------------------------------------------
/reactjs-redux-order-practice/db/category.json:
--------------------------------------------------------------------------------
1 | [{
2 | "id": 1,
3 | "name": "\u4e3b\u98df",
4 | "user": "rz",
5 | "is_active": 1,
6 | "created_at": "2016-06-09 20:51:39",
7 | "updated_at": "2016-06-09 20:51:39"
8 | }, {
9 | "id": 2,
10 | "name": "\u6e6f\u54c1",
11 | "user": "rz",
12 | "is_active": 1,
13 | "created_at": "2016-06-09 20:51:39",
14 | "updated_at": "2016-06-09 20:51:39"
15 | }, {
16 | "id": 3,
17 | "name": "\u5c0f\u9ede",
18 | "user": "rz",
19 | "is_active": 1,
20 | "created_at": "2016-06-09 20:51:39",
21 | "updated_at": "2016-06-09 20:51:39"
22 | }]
23 |
--------------------------------------------------------------------------------
/reactjs-redux-order-practice/db/tables.json:
--------------------------------------------------------------------------------
1 | [{
2 | "id": 1,
3 | "name": "一號桌"
4 | }, {
5 | "id": 2,
6 | "name": "二號桌"
7 | }, {
8 | "id": 3,
9 | "name": "三號桌"
10 | }, {
11 | "id": 4,
12 | "name": "四號桌"
13 | }, {
14 | "id": 5,
15 | "name": "五號桌"
16 | }, {
17 | "id": 6,
18 | "name": "六號桌"
19 | }]
20 |
--------------------------------------------------------------------------------
/reactjs-redux-order-practice/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | POS Order - ReactJS + Redux
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/reactjs-redux-order-practice/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "reactjs-redux-practice",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "start": "webpack-dev-server --colors --inline --content-base .",
8 | "prod": "webpack -p"
9 | },
10 | "author": "",
11 | "license": "ISC",
12 | "dependencies": {
13 | "babel-polyfill": "^6.16.0",
14 | "isomorphic-fetch": "^2.2.1",
15 | "react": "^15.3.2",
16 | "react-dom": "^15.3.2",
17 | "react-redux": "^4.4.5",
18 | "redux": "^3.6.0",
19 | "redux-actions": "^0.12.0",
20 | "redux-immutable": "^3.0.8",
21 | "redux-logger": "^2.6.1",
22 | "redux-thunk": "^2.1.0"
23 | },
24 | "devDependencies": {
25 | "babel-core": "^6.17.0",
26 | "babel-eslint": "^7.0.0",
27 | "babel-loader": "^6.2.5",
28 | "babel-preset-es2015": "^6.16.0",
29 | "babel-preset-react": "^6.16.0",
30 | "css-loader": "^0.25.0",
31 | "eslint": "^3.7.1",
32 | "eslint-config-airbnb": "^12.0.0",
33 | "eslint-loader": "^1.5.0",
34 | "eslint-plugin-import": "^1.16.0",
35 | "eslint-plugin-jsx-a11y": "^2.2.3",
36 | "eslint-plugin-react": "^6.4.1",
37 | "html-webpack-plugin": "^2.22.0",
38 | "node-sass": "^3.10.1",
39 | "sass-loader": "^4.0.2",
40 | "style-loader": "^0.13.1",
41 | "webpack": "^1.13.2",
42 | "webpack-dev-server": "^1.16.2"
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/reactjs-redux-order-practice/readme.md:
--------------------------------------------------------------------------------
1 | # POS Order
2 |
3 | ## 概觀
4 | 練習以 ReactJS + Redux 實作 POS 點單介面
5 |
6 |
7 | ## 開發
8 | 1. 套件相依安裝 `npm install`
9 | 2. 運行服務 `npm start`
10 | 3. 開啟瀏覽器
11 |
12 | ```
13 | http://localhost:8008
14 | ```
15 |
16 | ## 產出 js & html
17 | 運行 `npm run prod`
18 |
19 | > 產出的 js & html file 會在根目錄下
20 |
21 |
22 | ## 參考
23 | * https://chentsulin.github.io/redux/index.html
--------------------------------------------------------------------------------
/reactjs-redux-order-practice/src/actions/data.js:
--------------------------------------------------------------------------------
1 | import fetch from 'isomorphic-fetch'
2 | import {
3 | FETCH_CATEGORY_DATA,
4 | FETCH_ITEM_DATA,
5 | FETCH_TABLE_DATA
6 | } from '../constants/ActionTypes'
7 |
8 | export function fetchCategoryData() {
9 | return dispatch => {
10 | return fetch('./db/category.json')
11 | .then(response => response.json())
12 | .then(json => dispatch({
13 | type: FETCH_CATEGORY_DATA,
14 | payload: json
15 | }))
16 | }
17 | }
18 |
19 | export function fetchItemData() {
20 | return dispatch => {
21 | return fetch('./db/item.json')
22 | .then(response => response.json())
23 | .then(json => dispatch({
24 | type: FETCH_ITEM_DATA,
25 | payload: json
26 | }))
27 | }
28 | }
29 |
30 | export function fetchTableData() {
31 | return dispatch => {
32 | return fetch('./db/tables.json')
33 | .then(response => response.json())
34 | .then(json => dispatch({
35 | type: FETCH_TABLE_DATA,
36 | payload: json
37 | }))
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/reactjs-redux-order-practice/src/actions/ui/Order/categories.js:
--------------------------------------------------------------------------------
1 | import {
2 | createAction
3 | } from 'redux-actions'
4 | import {
5 | CHANGE_CATEGORY
6 | } from '../../../constants/ActionTypes'
7 |
8 | export const changeCategoryBy = createAction(CHANGE_CATEGORY)
9 |
--------------------------------------------------------------------------------
/reactjs-redux-order-practice/src/actions/ui/Order/items.js:
--------------------------------------------------------------------------------
1 | import {
2 | createAction
3 | } from 'redux-actions'
4 | import {
5 | UPDATE_MENU_STYLE
6 | } from '../../../constants/ActionTypes'
7 |
8 | export const updateMenuStyle = createAction(UPDATE_MENU_STYLE)
9 |
--------------------------------------------------------------------------------
/reactjs-redux-order-practice/src/actions/ui/Order/order.js:
--------------------------------------------------------------------------------
1 | import {
2 | createAction
3 | } from 'redux-actions'
4 | import {
5 | ADD_ITEM_TO_ORDER,
6 | REMOVE_ITEM_FROM_ORDER,
7 | ADD_ADDITION_TO_ORDER,
8 | UPADTE_ADDITION,
9 | REMOVE_ADDITION_FROM_ORDER,
10 | UPDATE_ORDER_STYLE,
11 | TOGGLE_TOGO_STATUS,
12 | TOGGLE_WHEN_STATUS,
13 | SUBMIT_ORDER
14 | } from '../../../constants/ActionTypes'
15 |
16 | export const addItemToOrder = createAction(ADD_ITEM_TO_ORDER)
17 | export const removeItemFromOrder = createAction(REMOVE_ITEM_FROM_ORDER)
18 | export const addAdditionToOrder = createAction(ADD_ADDITION_TO_ORDER)
19 | export const updateAddition = createAction(UPADTE_ADDITION)
20 | export const removeAdditionFromOrder = createAction(REMOVE_ADDITION_FROM_ORDER)
21 | export const updateOrderStyle = createAction(UPDATE_ORDER_STYLE)
22 | export const toggleToGoStatus = createAction(TOGGLE_TOGO_STATUS)
23 | export const toggleWhenStatus = createAction(TOGGLE_WHEN_STATUS)
24 | export const sumbitOrder = createAction(SUBMIT_ORDER)
25 |
--------------------------------------------------------------------------------
/reactjs-redux-order-practice/src/actions/ui/Order/pages.js:
--------------------------------------------------------------------------------
1 | import {
2 | createAction
3 | } from 'redux-actions'
4 | import {
5 | CHANGE_MENU_PAGE,
6 | RESET_MENU_PAGE
7 | } from '../../../constants/ActionTypes'
8 |
9 | export const changeMenuPageBy = createAction(CHANGE_MENU_PAGE)
10 | export const resetMenuPage = createAction(RESET_MENU_PAGE)
11 |
--------------------------------------------------------------------------------
/reactjs-redux-order-practice/src/components/Order/OrderContent.jsx:
--------------------------------------------------------------------------------
1 | import React, {Component, PropTypes} from 'react'
2 | import {getOrderContentStyle} from '../../helpers'
3 | import OrderContentDetail from '../../containers/Order/OrderContentDetail'
4 | import OrderContentSubmit from '../../containers/Order/OrderContentSubmit'
5 |
6 | class OrderContent extends Component {
7 | componentDidMount() {
8 | // item 動態列高
9 | const {updateOrderStyle} = this.props
10 | updateOrderStyle(getOrderContentStyle())
11 | window.addEventListener('resize', () => updateOrderStyle(getOrderContentStyle()))
12 | }
13 |
14 | componentWillUnmount() {
15 | const {updateOrderStyle} = this.props
16 | window.removeEventListener('resize', () => updateOrderStyle(getOrderContentStyle()))
17 | }
18 |
19 | render() {
20 | const {orderContentStyle} = this.props
21 | return (
22 |
23 |
24 |
點單明細
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 | )
36 | }
37 | }
38 | OrderContent.propTypes = {
39 | orderContentStyle: PropTypes.object.isRequired,
40 | updateOrderStyle: PropTypes.func.isRequired
41 | }
42 |
43 | export default OrderContent
44 |
--------------------------------------------------------------------------------
/reactjs-redux-order-practice/src/components/Order/OrderContentDetail.jsx:
--------------------------------------------------------------------------------
1 | import React, {Component, PropTypes} from 'react'
2 |
3 | const OrderContentDetail = ({items, additions, removeItemFromOrder, removeAdditionFromOrder, updateAddition}) => (
4 |
43 | )
44 | OrderContentDetail.propTypes = {
45 | items: PropTypes.array.isRequired,
46 | additions: PropTypes.array.isRequired,
47 | removeItemFromOrder: PropTypes.func.isRequired,
48 | removeAdditionFromOrder: PropTypes.func.isRequired,
49 | updateAddition: PropTypes.func.isRequired
50 | }
51 |
52 | export default OrderContentDetail
53 |
--------------------------------------------------------------------------------
/reactjs-redux-order-practice/src/components/Order/OrderContentSubmit.jsx:
--------------------------------------------------------------------------------
1 | import React, {Component, PropTypes} from 'react'
2 |
3 | const OrderContentSubmit = ({
4 | order,
5 | isToGo,
6 | when,
7 | tables,
8 | total,
9 | addAdditionToOrder,
10 | toggleToGoStatus,
11 | toggleWhenStatus,
12 | sumbitOrder
13 | }) => (
14 |
15 |
16 |
17 | {/* 加點 */}
18 |
19 |
24 |
25 | {/* 外帶 */}
26 |
27 |
38 |
39 | {/* 時段 */}
40 |
41 |
50 |
51 | {/* 桌號 */}
52 |
53 |
66 |
67 |
68 |
69 |
70 | {/* 送單 */}
71 |
76 |
77 |
78 | {/* 總計 */}
79 |
80 |
81 | 總計
82 |
83 |
84 | {total}
85 |
86 |
87 |
88 |
89 | )
90 | OrderContentSubmit.propTypes = {
91 | order: PropTypes.array.isRequired,
92 | isToGo: PropTypes.bool.isRequired,
93 | when: PropTypes.string.isRequired,
94 | tables: PropTypes.array.isRequired,
95 | total: PropTypes.number.isRequired,
96 | addAdditionToOrder: PropTypes.func.isRequired,
97 | toggleToGoStatus: PropTypes.func.isRequired,
98 | toggleWhenStatus: PropTypes.func.isRequired,
99 | sumbitOrder: PropTypes.func.isRequired
100 | }
101 |
102 | export default OrderContentSubmit
103 |
--------------------------------------------------------------------------------
/reactjs-redux-order-practice/src/components/Order/OrderMenu.jsx:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react'
2 | import OrderMenuCategory from '../../containers/Order/OrderMenuCategory'
3 | import OrderMenuItem from '../../containers/Order/OrderMenuItem'
4 | import OrderMenuPager from '../../containers/Order/OrderMenuPager'
5 |
6 | const OrderMenu = () => (
7 |
8 |
9 |
10 |
11 |
12 | )
13 |
14 | export default OrderMenu
15 |
--------------------------------------------------------------------------------
/reactjs-redux-order-practice/src/components/Order/OrderMenuCategory.jsx:
--------------------------------------------------------------------------------
1 | import React, {PropTypes} from 'react'
2 |
3 | const OrderMenuCategory = ({categories, selectedId, changeCategoryBy, resetMenuPage}) => (
4 |
5 |
6 | {categories.map((category) => (
7 | - changeCategoryBy(category.id) && resetMenuPage()}>
10 |
11 |
{category.name}
12 |
13 |
14 | ))}
15 |
16 |
17 | )
18 | OrderMenuCategory.propTypes = {
19 | categories: PropTypes.array.isRequired,
20 | selectedId: PropTypes.number.isRequired,
21 | changeCategoryBy: PropTypes.func.isRequired,
22 | resetMenuPage: PropTypes.func.isRequired
23 | }
24 |
25 | export default OrderMenuCategory
26 |
--------------------------------------------------------------------------------
/reactjs-redux-order-practice/src/components/Order/OrderMenuItem.jsx:
--------------------------------------------------------------------------------
1 | import React, {Component, PropTypes} from 'react'
2 | import {getItemRowStyle} from '../../helpers'
3 |
4 | class OrderMenuItem extends Component {
5 | componentDidMount() {
6 | // item 動態列高
7 | const {updateMenuStyle} = this.props
8 | updateMenuStyle(getItemRowStyle())
9 | window.addEventListener('resize', () => updateMenuStyle(getItemRowStyle()))
10 | }
11 |
12 | componentWillUnmount() {
13 | const {updateMenuStyle} = this.props
14 | window.removeEventListener('resize', () => updateMenuStyle(getItemRowStyle()))
15 | }
16 |
17 | render() {
18 | const {items, itemRowStyle, addItemToOrder} = this.props
19 | return (
20 |
21 |
22 | {items.map((item) => (
23 |
24 |
addItemToOrder(item)}>
25 |
{item.name}
26 |
27 |
28 | ))}
29 |
30 |
31 | )
32 | }
33 | }
34 | OrderMenuItem.propTypes = {
35 | items: PropTypes.array.isRequired,
36 | itemRowStyle: PropTypes.object.isRequired,
37 | updateMenuStyle: PropTypes.func.isRequired
38 | }
39 |
40 | export default OrderMenuItem
41 |
--------------------------------------------------------------------------------
/reactjs-redux-order-practice/src/components/Order/OrderMenuPager.jsx:
--------------------------------------------------------------------------------
1 | import React, {PropTypes} from 'react'
2 |
3 | const OrderMenuPager = ({pagesCount, currentPage, changeMenuPageBy}) => {
4 | const hasNextPage = pagesCount - currentPage > 1 // 是否還有下一頁
5 | const hasPrevPage = currentPage - 1 > -1 // 是否還有上一頁
6 | const nextPage = hasNextPage
7 | ? currentPage + 1
8 | : currentPage
9 | const prevPage = hasPrevPage
10 | ? currentPage - 1
11 | : currentPage
12 |
13 | return (
14 |
15 |
16 |
17 |
22 |
23 |
24 |
29 |
30 |
31 |
32 | )
33 | }
34 | OrderMenuPager.propTypes = {
35 | pagesCount: PropTypes.number.isRequired,
36 | currentPage: PropTypes.number.isRequired,
37 | changeMenuPageBy: PropTypes.func.isRequired
38 | }
39 |
40 | export default OrderMenuPager
41 |
--------------------------------------------------------------------------------
/reactjs-redux-order-practice/src/constants/ActionTypes.js:
--------------------------------------------------------------------------------
1 | /* categories */
2 | export const FETCH_CATEGORY_DATA = 'FETCH_CATEGORY_DATA'
3 | export const CHANGE_CATEGORY = 'CHANGE_CATEGORY'
4 |
5 | /* items */
6 | export const FETCH_ITEM_DATA = 'FETCH_ITEM_DATA'
7 | export const UPDATE_MENU_STYLE = 'UPDATE_MENU_STYLE'
8 |
9 | /* order */
10 | export const ADD_ITEM_TO_ORDER = 'ADD_ITEM_TO_ORDER'
11 | export const REMOVE_ITEM_FROM_ORDER = 'REMOVE_ITEM_FROM_ORDER'
12 |
13 | export const ADD_ADDITION_TO_ORDER = 'ADD_ADDITION_TO_ORDER'
14 | export const REMOVE_ADDITION_FROM_ORDER = 'REMOVE_ADDITION_FROM_ORDER'
15 | export const UPADTE_ADDITION = 'UPADTE_ADDITION'
16 |
17 | export const UPDATE_ORDER_STYLE = 'UPDATE_ORDER_STYLE'
18 | export const TOGGLE_TOGO_STATUS = 'TOGGLE_TOGO_STATUS'
19 | export const TOGGLE_WHEN_STATUS = 'TOGGLE_WHEN_STATUS'
20 | export const SUBMIT_ORDER = 'SUBMIT_ORDER'
21 |
22 | /* pages */
23 | export const CHANGE_MENU_PAGE = 'CHANGE_MENU_PAGE'
24 | export const RESET_MENU_PAGE = 'RESET_MENU_PAGE'
25 |
26 | /* tables */
27 | export const FETCH_TABLE_DATA = 'FETCH_TABLE_DATA'
28 |
--------------------------------------------------------------------------------
/reactjs-redux-order-practice/src/constants/OrderConstants.js:
--------------------------------------------------------------------------------
1 | const now = new Date();
2 | const nightStart = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 17, 30, 0, 0);
3 | const whenStatus = (now.getTime() - nightStart.getTime() < 0) ? '中午' : '晚上';
4 |
5 | const OrderConstants = {
6 | // parameters
7 | itemsPerPageAmount: 12, // 每頁菜單項目數
8 | menuItemListRows: 3, // 菜單列數
9 | menuItemRowMarginHeight: 30, // 菜單列與列之間 margin 高
10 | menuCategoryListHeight: 87, // 菜單分類 DOM 高
11 | menuPagerHeight: 62, // 菜單分頁 DOM 高
12 | contentDetailHeadingHeight: 72, // "點單明細" 高
13 | contentOperationHeight: 133, // order 操作區域高
14 | // store default value
15 | whenStatus: whenStatus // 菜單 中午/晚上的狀態
16 | };
17 |
18 | export default OrderConstants;
19 |
--------------------------------------------------------------------------------
/reactjs-redux-order-practice/src/containers/App.jsx:
--------------------------------------------------------------------------------
1 | import React, {Component, PropTypes} from 'react'
2 | import {connect} from 'react-redux'
3 | import {fetchCategoryData, fetchItemData, fetchTableData} from '../actions/data'
4 | import OrderMenu from '../components/Order/OrderMenu.jsx'
5 | import OrderContent from '../containers/Order/OrderContent'
6 |
7 | class App extends Component {
8 | componentDidMount() {
9 | const {fetchCategoryData, fetchItemData, fetchTableData} = this.props
10 | fetchCategoryData()
11 | fetchItemData()
12 | fetchTableData()
13 | }
14 |
15 | render() {
16 | return (
17 |
18 |
19 |
20 |
21 | {/* 左側欄 */}
22 | {/* 右側欄 */}
23 |
24 |
25 |
26 |
27 |
28 | )
29 | }
30 | }
31 | App.propTypes = {
32 | fetchCategoryData: PropTypes.func.isRequired,
33 | fetchItemData: PropTypes.func.isRequired,
34 | fetchTableData: PropTypes.func.isRequired
35 | }
36 |
37 | function mapDispatchToProps(dispatch) {
38 | return {
39 | fetchCategoryData: () => dispatch(fetchCategoryData()),
40 | fetchItemData: () => dispatch(fetchItemData()),
41 | fetchTableData: () => dispatch(fetchTableData())
42 | }
43 | }
44 |
45 | export default connect(undefined, mapDispatchToProps)(App)
46 |
--------------------------------------------------------------------------------
/reactjs-redux-order-practice/src/containers/Order/OrderContent.js:
--------------------------------------------------------------------------------
1 | import {
2 | connect
3 | } from 'react-redux'
4 | import {updateOrderStyle} from '../../actions/ui/Order/order'
5 | import OrderContent from '../../components/Order/OrderContent.jsx'
6 |
7 | function mapStateToProps(state) {
8 | return {
9 | orderContentStyle: state.order.orderContentStyle
10 | }
11 | }
12 |
13 | function mapDispatchToProps(dispatch) {
14 | return {
15 | updateOrderStyle: (style) => dispatch(updateOrderStyle(style))
16 | }
17 | }
18 |
19 | export default connect(mapStateToProps,mapDispatchToProps)(OrderContent)
20 |
--------------------------------------------------------------------------------
/reactjs-redux-order-practice/src/containers/Order/OrderContentDetail.js:
--------------------------------------------------------------------------------
1 | import {
2 | connect
3 | } from 'react-redux'
4 | import {
5 | removeItemFromOrder,
6 | removeAdditionFromOrder,
7 | updateAddition
8 | } from '../../actions/ui/Order/order'
9 | import OrderContentDetail from '../../components/Order/OrderContentDetail.jsx'
10 |
11 | function mapStateToProps(state) {
12 | return {
13 | items: state.order.items,
14 | additions: state.order.additions
15 | }
16 | }
17 |
18 | function mapDispatchToProps(dispatch) {
19 | return {
20 | removeItemFromOrder: (item) => dispatch(removeItemFromOrder(item)),
21 | removeAdditionFromOrder: (addition) => dispatch(removeAdditionFromOrder(addition)),
22 | updateAddition: (addition) => dispatch(updateAddition(addition))
23 | }
24 | }
25 |
26 | export default connect(mapStateToProps, mapDispatchToProps)(OrderContentDetail)
27 |
--------------------------------------------------------------------------------
/reactjs-redux-order-practice/src/containers/Order/OrderContentSubmit.js:
--------------------------------------------------------------------------------
1 | import {
2 | connect
3 | } from 'react-redux'
4 | import {
5 | addAdditionToOrder,
6 | toggleToGoStatus,
7 | toggleWhenStatus,
8 | sumbitOrder
9 | } from '../../actions/ui/Order/order'
10 | import OrderContentSubmit from '../../components/Order/OrderContentSubmit.jsx'
11 |
12 | function mapStateToProps(state) {
13 | return {
14 | order: state.order.items,
15 | isToGo: state.order.isToGo,
16 | when: state.order.when,
17 | tables: state.tables.records,
18 | total: state.order.total
19 | }
20 | }
21 |
22 | function mapDispatchToProps(dispatch) {
23 | return {
24 | addAdditionToOrder: () => dispatch(addAdditionToOrder()),
25 | toggleToGoStatus: () => dispatch(toggleToGoStatus()),
26 | toggleWhenStatus: () => dispatch(toggleWhenStatus()),
27 | sumbitOrder: () => dispatch(sumbitOrder())
28 | }
29 | }
30 |
31 | export default connect(mapStateToProps,mapDispatchToProps)(OrderContentSubmit)
32 |
--------------------------------------------------------------------------------
/reactjs-redux-order-practice/src/containers/Order/OrderMenuCategory.js:
--------------------------------------------------------------------------------
1 | import {connect} from 'react-redux'
2 | import {changeCategoryBy} from '../../actions/ui/Order/categories'
3 | import {resetMenuPage} from '../../actions/ui/Order/pages'
4 | import OrderMenuCategory from '../../components/Order/OrderMenuCategory.jsx'
5 |
6 | function mapStateToProps(state) {
7 | return {categories: state.categories.records, selectedId: state.categories.selectedId}
8 | }
9 |
10 | function mapDispatchToProps(dispatch) {
11 | return {
12 | changeCategoryBy: (index) => dispatch(changeCategoryBy(index)),
13 | resetMenuPage: () => dispatch(resetMenuPage())
14 | }
15 | }
16 |
17 | export default connect(mapStateToProps, mapDispatchToProps)(OrderMenuCategory)
18 |
--------------------------------------------------------------------------------
/reactjs-redux-order-practice/src/containers/Order/OrderMenuItem.js:
--------------------------------------------------------------------------------
1 | import {connect} from 'react-redux'
2 | import {updateMenuStyle} from '../../actions/ui/Order/items'
3 | import {addItemToOrder} from '../../actions/ui/Order/order'
4 | import {filterItemsBy} from '../../helpers'
5 | import OrderMenuItem from '../../components/Order/OrderMenuItem.jsx'
6 |
7 | function mapStateToProps(state) {
8 | return {
9 | items: filterItemsBy(state.items.records, state.categories.selectedId, state.pages.currentPage),
10 | itemRowStyle: state.items.itemRowStyle
11 | }
12 | }
13 |
14 | function mapDispatchToProps(dispatch) {
15 | return {
16 | updateMenuStyle: (style) => dispatch(updateMenuStyle(style)),
17 | addItemToOrder: (item) => dispatch(addItemToOrder(item))
18 | }
19 | }
20 |
21 | export default connect(mapStateToProps, mapDispatchToProps)(OrderMenuItem)
22 |
--------------------------------------------------------------------------------
/reactjs-redux-order-practice/src/containers/Order/OrderMenuPager.js:
--------------------------------------------------------------------------------
1 | import {connect} from 'react-redux'
2 | import {changeMenuPageBy} from '../../actions/ui/Order/pages'
3 | import {countMenuPage} from '../../helpers'
4 | import OrderMenuPager from '../../components/Order/OrderMenuPager.jsx'
5 |
6 | function mapStateToProps(state) {
7 | return {
8 | pagesCount: countMenuPage(state.items.records, state.categories.selectedId),
9 | currentPage: state.pages.currentPage
10 | }
11 | }
12 |
13 | function mapDispatchToProps(dispatch) {
14 | return {
15 | changeMenuPageBy: (index) => dispatch(changeMenuPageBy(index))
16 | }
17 | }
18 |
19 | export default connect(mapStateToProps, mapDispatchToProps)(OrderMenuPager)
20 |
--------------------------------------------------------------------------------
/reactjs-redux-order-practice/src/helpers/index.js:
--------------------------------------------------------------------------------
1 | import OrderConstants from '../constants/OrderConstants'
2 |
3 | export function countMenuPage(items, categoryId) {
4 | const _items = items.filter((item) => item.category_id == categoryId)
5 | return Math.ceil(_items.length / OrderConstants.itemsPerPageAmount)
6 | }
7 |
8 | export function filterItemsBy(items, categoryId, pageId) {
9 | const _items = items.filter(item => item.category_id == categoryId)
10 | return _items.filter((record, i) => {
11 | return OrderConstants.itemsPerPageAmount * (pageId) <= i && i < OrderConstants.itemsPerPageAmount * (1 + pageId)
12 | })
13 | }
14 |
15 | export function getItemRowStyle() {
16 | const bodyHeight = document.body.clientHeight
17 | const rowHeight = (bodyHeight - OrderConstants.menuItemRowMarginHeight - OrderConstants.menuCategoryListHeight - OrderConstants.menuPagerHeight) / OrderConstants.menuItemListRows
18 | return {
19 | height: rowHeight + 'px'
20 | }
21 | }
22 |
23 | export function getOrderContentStyle() {
24 | const bodyHeight = document.body.clientHeight;
25 | const contentHeight = (bodyHeight - OrderConstants.contentDetailHeadingHeight - OrderConstants.contentOperationHeight);
26 | return {
27 | height: contentHeight + 'px'
28 | }
29 | }
30 |
31 | export function calculateTotalBy(items, additions) {
32 | let total = 0;
33 | items.map((record) => total += record.subtotal)
34 | additions.map((record) => total += record.subtotal)
35 | return total
36 | }
37 |
--------------------------------------------------------------------------------
/reactjs-redux-order-practice/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | POS Order - ReactJS + Redux
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/reactjs-redux-order-practice/src/index.jsx:
--------------------------------------------------------------------------------
1 | import 'babel-polyfill'
2 |
3 | import React from 'react'
4 | import {render} from 'react-dom'
5 | import {Provider} from 'react-redux'
6 | import configureStore from './store/configureStore'
7 | import App from './containers/App.jsx'
8 |
9 | require("!style!css!sass!./styles/entry.scss")
10 |
11 | const store = configureStore()
12 |
13 | render(
14 |
15 |
16 | , document.getElementById('app'))
17 |
--------------------------------------------------------------------------------
/reactjs-redux-order-practice/src/reducers/Order/categories.js:
--------------------------------------------------------------------------------
1 | import {
2 | handleActions
3 | } from 'redux-actions'
4 | import {
5 | FETCH_CATEGORY_DATA,
6 | CHANGE_CATEGORY
7 | } from '../../constants/ActionTypes'
8 |
9 | export default handleActions({
10 | FETCH_CATEGORY_DATA: (state, {
11 | payload
12 | }) => {
13 | return Object.assign({}, state, {
14 | isFetched: true,
15 | selectedId: payload[0].id,
16 | records: payload,
17 | receivedAt: Date.now()
18 | })
19 | },
20 | CHANGE_CATEGORY: (state, {
21 | payload
22 | }) => {
23 | return Object.assign({}, state, {
24 | selectedId: payload
25 | })
26 | }
27 | }, {
28 | isFetched: false,
29 | selectedId: -1,
30 | records: []
31 | })
32 |
--------------------------------------------------------------------------------
/reactjs-redux-order-practice/src/reducers/Order/items.js:
--------------------------------------------------------------------------------
1 | import {
2 | handleActions
3 | } from 'redux-actions'
4 | import {
5 | FETCH_ITEM_DATA,
6 | UPDATE_MENU_STYLE
7 | } from '../../constants/ActionTypes'
8 |
9 | export default handleActions({
10 | FETCH_ITEM_DATA: (state, {
11 | payload
12 | }) => {
13 | return Object.assign({}, state, {
14 | isFetched: true,
15 | records: payload,
16 | receivedAt: Date.now()
17 | })
18 | },
19 | UPDATE_MENU_STYLE: (state, {
20 | payload
21 | }) => {
22 | return Object.assign({}, state, {
23 | itemRowStyle: payload
24 | })
25 | }
26 | }, {
27 | isFetched: false,
28 | itemRowStyle: {
29 | height: 'inherit'
30 | },
31 | records: []
32 | })
33 |
--------------------------------------------------------------------------------
/reactjs-redux-order-practice/src/reducers/Order/order.js:
--------------------------------------------------------------------------------
1 | import {
2 | handleActions
3 | } from 'redux-actions'
4 | import {
5 | ADD_ITEM_TO_ORDER,
6 | REMOVE_ITEM_FROM_ORDER,
7 | ADD_ADDITION_TO_ORDER,
8 | REMOVE_ADDITION_FROM_ORDER,
9 | UPADTE_ADDITION,
10 | UPDATE_ORDER_STYLE,
11 | TOGGLE_TOGO_STATUS,
12 | TOGGLE_WHEN_STATUS,
13 | SUBMIT_ORDER
14 | } from '../../constants/ActionTypes'
15 | import {
16 | calculateTotalBy
17 | } from '../../helpers'
18 | import OrderConstants from '../../constants/OrderConstants'
19 |
20 | export default handleActions({
21 | ADD_ITEM_TO_ORDER: (state, {
22 | payload
23 | }) => {
24 | if (state.items.find((item) => item.id == payload.id)) {
25 | let _items = state.items.map(item => {
26 | if (item.id == payload.id) {
27 | return Object.assign({}, item, {
28 | amount: item.amount + 1,
29 | subtotal: (item.amount + 1) * item.price
30 | })
31 | }
32 | return item
33 | })
34 | return Object.assign({}, state, {
35 | items: _items,
36 | total: calculateTotalBy(_items, state.additions)
37 | })
38 | }
39 | let _items = [...state.items, {
40 | id: payload.id,
41 | name: payload.name,
42 | amount: 1,
43 | price: payload.price,
44 | subtotal: payload.price
45 | }]
46 | return Object.assign({}, state, {
47 | items: _items,
48 | total: calculateTotalBy(_items, state.additions)
49 | })
50 | },
51 | REMOVE_ITEM_FROM_ORDER: (state, {
52 | payload
53 | }) => {
54 | let _items = state.items.map(item => {
55 | if (item.id == payload.id) {
56 | return Object.assign({}, item, {
57 | amount: item.amount - 1,
58 | subtotal: (item.amount - 1) * item.price
59 | })
60 | }
61 | return item
62 | }).filter((item) => item.amount > 0)
63 | return Object.assign({}, state, {
64 | items: _items,
65 | total: calculateTotalBy(_items, state.additions)
66 | })
67 | },
68 | ADD_ADDITION_TO_ORDER: (state) => {
69 | return Object.assign({}, state, {
70 | additions: [...state.additions, {
71 | id: Date.now(),
72 | name: '',
73 | amount: 1,
74 | price: 0,
75 | subtotal: 0
76 | }]
77 | })
78 | },
79 | REMOVE_ADDITION_FROM_ORDER: (state, {
80 | payload
81 | }) => {
82 | let _additions = state.additions.filter((addition) => addition.id !== payload.id)
83 | return Object.assign({}, state, {
84 | additions: _additions,
85 | total: calculateTotalBy(state.items, _additions)
86 | })
87 | },
88 | UPADTE_ADDITION: (state, {
89 | payload
90 | }) => {
91 | let _additions = state.additions.map(addition => {
92 | if (addition.id == payload.id) {
93 | return Object.assign({}, addition, {
94 | id: payload.id,
95 | name: payload.name,
96 | amount: payload.amount,
97 | price: payload.price,
98 | subtotal: payload.amount * payload.price
99 | })
100 | }
101 | return addition
102 | })
103 | return Object.assign({}, state, {
104 | additions: _additions,
105 | total: calculateTotalBy(state.items, _additions)
106 | })
107 | },
108 | TOGGLE_TOGO_STATUS: (state) => {
109 | return Object.assign({}, state, {
110 | isToGo: !state.isToGo,
111 | })
112 | },
113 | TOGGLE_WHEN_STATUS: (state) => {
114 | return Object.assign({}, state, {
115 | when: (state.when === '中午') ? '晚上' : '中午',
116 | })
117 | },
118 | SUBMIT_ORDER: (state) => {
119 | console.log('--- clear order ---')
120 | return Object.assign({}, state, {
121 | items: [],
122 | additions: [],
123 | total: 0
124 | })
125 | },
126 | UPDATE_ORDER_STYLE: (state, {
127 | payload
128 | }) => {
129 | return Object.assign({}, state, {
130 | orderContentStyle: payload
131 | })
132 | },
133 | TOGGLE_WHEN_STATUS: (state) => {
134 | return Object.assign({}, state, {
135 | when: (state.when === '中午') ? '晚上' : '中午',
136 | })
137 | },
138 | TOGGLE_WHEN_STATUS: (state) => {
139 | return Object.assign({}, state, {
140 | when: (state.when === '中午') ? '晚上' : '中午',
141 | })
142 | },
143 | }, {
144 | orderContentStyle: {
145 | height: 'inherit'
146 | },
147 | items: [],
148 | additions: [],
149 | isToGo: false,
150 | when: OrderConstants.whenStatus,
151 | total: 0
152 | })
153 |
--------------------------------------------------------------------------------
/reactjs-redux-order-practice/src/reducers/Order/pages.js:
--------------------------------------------------------------------------------
1 | import {
2 | handleActions
3 | } from 'redux-actions'
4 | import {
5 | CHANGE_MENU_PAGE,
6 | RESET_MENU_PAGE
7 | } from '../../constants/ActionTypes'
8 |
9 | export default handleActions({
10 | CHANGE_MENU_PAGE: (state, {
11 | payload
12 | }) => {
13 | return Object.assign({}, state, {
14 | currentPage: payload,
15 | })
16 | },
17 | RESET_MENU_PAGE: (state) => {
18 | return Object.assign({}, state, {
19 | currentPage: 0,
20 | })
21 | }
22 | }, {
23 | currentPage: 0
24 | })
25 |
--------------------------------------------------------------------------------
/reactjs-redux-order-practice/src/reducers/Order/tables.js:
--------------------------------------------------------------------------------
1 | import {
2 | handleActions
3 | } from 'redux-actions'
4 | import {
5 | FETCH_TABLE_DATA
6 | } from '../../constants/ActionTypes'
7 |
8 | export default handleActions({
9 | FETCH_TABLE_DATA: (state, {
10 | payload
11 | }) => {
12 | return Object.assign({}, state, {
13 | isFetched: true,
14 | records: payload,
15 | receivedAt: Date.now()
16 | })
17 | }
18 | }, {
19 | isFetched: false,
20 | records: []
21 | })
22 |
--------------------------------------------------------------------------------
/reactjs-redux-order-practice/src/reducers/index.js:
--------------------------------------------------------------------------------
1 | import {
2 | combineReducers
3 | } from 'redux'
4 | import categories from './Order/categories'
5 | import items from './Order/items'
6 | import tables from './Order/tables'
7 | import pages from './Order/pages'
8 | import order from './Order/order'
9 |
10 | const rootReducer = combineReducers({
11 | categories,
12 | items,
13 | tables,
14 | pages,
15 | order
16 | })
17 |
18 | export default rootReducer
19 |
--------------------------------------------------------------------------------
/reactjs-redux-order-practice/src/store/configureStore.js:
--------------------------------------------------------------------------------
1 | import { createStore, applyMiddleware } from 'redux';
2 | import thunkMiddleware from 'redux-thunk'
3 | import createLogger from 'redux-logger'
4 | import rootReducer from '../reducers'
5 |
6 | const loggerMiddleware = createLogger()
7 |
8 | export default function configureStore(preloadedState) {
9 | return createStore(
10 | rootReducer,
11 | preloadedState,
12 | applyMiddleware(
13 | thunkMiddleware,
14 | loggerMiddleware
15 | )
16 | )
17 | }
18 |
--------------------------------------------------------------------------------
/reactjs-redux-order-practice/src/store/index.js:
--------------------------------------------------------------------------------
1 | export { default } from './configureStore';
--------------------------------------------------------------------------------
/reactjs-redux-order-practice/src/styles/components/datetime.scss:
--------------------------------------------------------------------------------
1 | .datetime {
2 | .time {
3 | font-family: 'Roboto', sans-serif;
4 | font-size: 24px;
5 | vertical-align: middle;
6 | display: inline-block;
7 | }
8 | .date {
9 | font-family: 'Roboto', sans-serif;
10 | font-size: 16px;
11 | margin-top: 5px;
12 | vertical-align: middle;
13 | display: inline-block;
14 | }
15 | height: 50px; // nav menu bar 的高度
16 | text-align: center;
17 | font-size: 0;
18 | &::before {
19 | content: '';
20 | height: 100%;
21 | vertical-align: middle;
22 | display: inline-block;
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/reactjs-redux-order-practice/src/styles/components/input.scss:
--------------------------------------------------------------------------------
1 | //input[type="number"] {
2 | // width:3em;
3 | //}
4 |
--------------------------------------------------------------------------------
/reactjs-redux-order-practice/src/styles/components/loading.scss:
--------------------------------------------------------------------------------
1 | .sk-circle {
2 | margin: 100px auto;
3 | width: 40px;
4 | height: 40px;
5 | position: relative;
6 | }
7 | .sk-circle .sk-child {
8 | width: 100%;
9 | height: 100%;
10 | position: absolute;
11 | left: 0;
12 | top: 0;
13 | }
14 | .sk-circle .sk-child:before {
15 | content: '';
16 | display: block;
17 | margin: 0 auto;
18 | width: 15%;
19 | height: 15%;
20 | background-color: #333;
21 | border-radius: 100%;
22 | -webkit-animation: sk-circleBounceDelay 1.2s infinite ease-in-out both;
23 | animation: sk-circleBounceDelay 1.2s infinite ease-in-out both;
24 | }
25 | .sk-circle .sk-circle2 {
26 | -webkit-transform: rotate(30deg);
27 | -ms-transform: rotate(30deg);
28 | transform: rotate(30deg); }
29 | .sk-circle .sk-circle3 {
30 | -webkit-transform: rotate(60deg);
31 | -ms-transform: rotate(60deg);
32 | transform: rotate(60deg); }
33 | .sk-circle .sk-circle4 {
34 | -webkit-transform: rotate(90deg);
35 | -ms-transform: rotate(90deg);
36 | transform: rotate(90deg); }
37 | .sk-circle .sk-circle5 {
38 | -webkit-transform: rotate(120deg);
39 | -ms-transform: rotate(120deg);
40 | transform: rotate(120deg); }
41 | .sk-circle .sk-circle6 {
42 | -webkit-transform: rotate(150deg);
43 | -ms-transform: rotate(150deg);
44 | transform: rotate(150deg); }
45 | .sk-circle .sk-circle7 {
46 | -webkit-transform: rotate(180deg);
47 | -ms-transform: rotate(180deg);
48 | transform: rotate(180deg); }
49 | .sk-circle .sk-circle8 {
50 | -webkit-transform: rotate(210deg);
51 | -ms-transform: rotate(210deg);
52 | transform: rotate(210deg); }
53 | .sk-circle .sk-circle9 {
54 | -webkit-transform: rotate(240deg);
55 | -ms-transform: rotate(240deg);
56 | transform: rotate(240deg); }
57 | .sk-circle .sk-circle10 {
58 | -webkit-transform: rotate(270deg);
59 | -ms-transform: rotate(270deg);
60 | transform: rotate(270deg); }
61 | .sk-circle .sk-circle11 {
62 | -webkit-transform: rotate(300deg);
63 | -ms-transform: rotate(300deg);
64 | transform: rotate(300deg); }
65 | .sk-circle .sk-circle12 {
66 | -webkit-transform: rotate(330deg);
67 | -ms-transform: rotate(330deg);
68 | transform: rotate(330deg); }
69 | .sk-circle .sk-circle2:before {
70 | -webkit-animation-delay: -1.1s;
71 | animation-delay: -1.1s; }
72 | .sk-circle .sk-circle3:before {
73 | -webkit-animation-delay: -1s;
74 | animation-delay: -1s; }
75 | .sk-circle .sk-circle4:before {
76 | -webkit-animation-delay: -0.9s;
77 | animation-delay: -0.9s; }
78 | .sk-circle .sk-circle5:before {
79 | -webkit-animation-delay: -0.8s;
80 | animation-delay: -0.8s; }
81 | .sk-circle .sk-circle6:before {
82 | -webkit-animation-delay: -0.7s;
83 | animation-delay: -0.7s; }
84 | .sk-circle .sk-circle7:before {
85 | -webkit-animation-delay: -0.6s;
86 | animation-delay: -0.6s; }
87 | .sk-circle .sk-circle8:before {
88 | -webkit-animation-delay: -0.5s;
89 | animation-delay: -0.5s; }
90 | .sk-circle .sk-circle9:before {
91 | -webkit-animation-delay: -0.4s;
92 | animation-delay: -0.4s; }
93 | .sk-circle .sk-circle10:before {
94 | -webkit-animation-delay: -0.3s;
95 | animation-delay: -0.3s; }
96 | .sk-circle .sk-circle11:before {
97 | -webkit-animation-delay: -0.2s;
98 | animation-delay: -0.2s; }
99 | .sk-circle .sk-circle12:before {
100 | -webkit-animation-delay: -0.1s;
101 | animation-delay: -0.1s; }
102 |
103 | @-webkit-keyframes sk-circleBounceDelay {
104 | 0%, 80%, 100% {
105 | -webkit-transform: scale(0);
106 | transform: scale(0);
107 | } 40% {
108 | -webkit-transform: scale(1);
109 | transform: scale(1);
110 | }
111 | }
112 |
113 | @keyframes sk-circleBounceDelay {
114 | 0%, 80%, 100% {
115 | -webkit-transform: scale(0);
116 | transform: scale(0);
117 | } 40% {
118 | -webkit-transform: scale(1);
119 | transform: scale(1);
120 | }
121 | }
--------------------------------------------------------------------------------
/reactjs-redux-order-practice/src/styles/entry.scss:
--------------------------------------------------------------------------------
1 | // variables
2 | @import 'variables/font.scss';
3 | @import 'variables/button.scss';
4 |
5 | // layout
6 | @import 'layout/grid.scss';
7 | @import 'layout/app.scss';
8 |
9 | // Payment style
10 | @import 'pages/Order.scss';
11 |
12 | // input style
13 | @import 'components/input.scss';
14 |
15 | // loading style
16 | // ref:http://tobiasahlin.com/spinkit/
17 | @import 'components/loading';
18 |
19 | @import 'components/datetime.scss';
20 |
--------------------------------------------------------------------------------
/reactjs-redux-order-practice/src/styles/layout/app.scss:
--------------------------------------------------------------------------------
1 | html,
2 | body {
3 | height: 100%;
4 | /* The html and body elements cannot have any padding or margin. */
5 | }
6 |
7 |
8 | // 滿版 window 高度, ref:http://www.bootply.com/97103
9 | .center-container {
10 | height:100%;
11 | }
12 |
13 | .center-row {
14 | height:100%;
15 | background-color: #9ac7d5;
16 | background-size:cover;
17 | overflow-y: hidden;
18 | }
19 |
20 |
21 | #app{
22 | height: 100%;
23 | font-family: "微軟正黑體", "Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial;
24 | }
25 |
26 | /* Wrapper for page content to push down footer */
27 | #wrap {
28 | min-height: 100%;
29 | height: 100%;
30 | /* Negative indent footer by its height */
31 | margin: 0 auto 0 0;
32 | /* Pad bottom by footer height */
33 | padding: 0 0 0 0;
34 |
35 | > .center-container {
36 | padding: 0 0 0 0;
37 | margin:0;
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/reactjs-redux-order-practice/src/styles/layout/grid.scss:
--------------------------------------------------------------------------------
1 | // 複寫 col-* 的 padding & margin
2 | .nopadding {
3 | padding: 0 !important;
4 | margin: 0 !important;
5 | }
--------------------------------------------------------------------------------
/reactjs-redux-order-practice/src/styles/pages/Order.scss:
--------------------------------------------------------------------------------
1 | .order {
2 | .menu {
3 | position: relative;
4 | .item {
5 | margin: 0 15px;
6 | .box {
7 | .text {
8 | margin: 10px;
9 | font-size: $fontValue;
10 | vertical-align: middle;
11 | display: inline-block;
12 | }
13 | margin: 5px;
14 | cursor: pointer;
15 | box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19);
16 | background-color: #d9edf7;
17 | &:hover {
18 | background-color: #dff0d8;
19 | }
20 | &:active {
21 | box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.6), 0 6px 20px 0 rgba(0, 0, 0, 0.59);
22 | }
23 | // 區塊內容置中, height 已預先定義在 DOM 上
24 | text-align: center;
25 | font-size: 0;
26 | &::before {
27 | content: '';
28 | height: 100%;
29 | vertical-align: middle;
30 | display: inline-block;
31 | }
32 | }
33 | }
34 | .item-nav {
35 | position: absolute;
36 | bottom: 0;
37 | .btn {
38 | span {
39 | font-size: $fontTitle;
40 | }
41 | padding: 10px 0;
42 | }
43 | }
44 | }
45 | .content {
46 | // position: relative;
47 | .panel.center-container {
48 | position: relative;
49 | .panel-heading {
50 | font-size: $fontTitle;
51 | }
52 | .panel-body {
53 | // 該 div height 由 js 動態決定
54 | overflow-y: auto; // 依據 div height 作用自動滾輪
55 | .order-details {
56 | table {
57 | font-size: $tableFont;
58 | }
59 | table {
60 | tbody tr td,
61 | thead tr th {
62 | &:nth-child(1) {
63 | width: 45%;
64 | }
65 | &:nth-child(2),
66 | &:nth-child(3) {
67 | width: 20%;
68 | }
69 | &:nth-child(4) {
70 | width: 10%;
71 | }
72 | &:nth-child(5) {
73 | width: 5%;
74 | }
75 | }
76 | }
77 | }
78 | .order-submit {
79 | position: absolute;
80 | bottom: 0;
81 | width: 100%;
82 | padding: 0 15px;
83 | margin-left: -15px;
84 | margin-bottom: -20px;
85 | button[type=submit] {
86 | height: $orderButtonHeight * 2;
87 | font-size: $fontValue;
88 | }
89 | select[name=table] {
90 | height: $orderButtonHeight;
91 | }
92 | .total {
93 | > div {
94 | height: $orderButtonHeight;
95 | }
96 | .title {
97 | font-size: $fontValue;
98 | padding: 6px;
99 | color: #333;
100 | background-color: #f5f5f5;
101 | border-color: #ddd;
102 | border-bottom: 1px solid transparent;
103 | border-top-left-radius: 3px;
104 | border-top-right-radius: 3px;
105 | }
106 | .value {
107 | font-size: $fontValue;
108 | padding: 6px;
109 | }
110 | }
111 | }
112 | }
113 | }
114 | }
115 | }
116 |
--------------------------------------------------------------------------------
/reactjs-redux-order-practice/src/styles/variables/button.scss:
--------------------------------------------------------------------------------
1 | $orderButtonHeight: 55px;
2 |
--------------------------------------------------------------------------------
/reactjs-redux-order-practice/src/styles/variables/font.scss:
--------------------------------------------------------------------------------
1 | $fontTitle: 36px;
2 | $fontValue: 24px;
3 | $tableFont: 20px;
--------------------------------------------------------------------------------
/reactjs-redux-order-practice/webpack.config.js:
--------------------------------------------------------------------------------
1 | // import 'babel-polyfill';
2 |
3 | const path = require('path');
4 | const HtmlWebpackPlugin = require('html-webpack-plugin');
5 |
6 | const HTMLWebpackPluginConfig = new HtmlWebpackPlugin({
7 | template: `${__dirname}/src/index.html`,
8 | filename: 'index.html',
9 | inject: 'body',
10 | });
11 |
12 | module.exports = {
13 | entry: [
14 | './src/index.jsx',
15 | ],
16 | output: {
17 | path: `${__dirname}`,
18 | filename: 'index_bundle.js',
19 | },
20 | module: {
21 | preLoaders: [{
22 | test: /\.jsx$|\.js$/,
23 | loader: 'eslint-loader',
24 | include: `${__dirname}/src`,
25 | exclude: /bundle\.js$/
26 | }],
27 | loaders: [{
28 | test: /\.js$|\.jsx$/,
29 | exclude: /node_modules/,
30 | loader: 'babel-loader',
31 | query: {
32 | presets: ['es2015', 'react'],
33 | },
34 | }, {
35 | test: /\.scss$/,
36 | loaders: ["style", "css", "sass"]
37 | }],
38 | },
39 | devServer: {
40 | inline: true,
41 | port: 8008,
42 | },
43 | plugins: [HTMLWebpackPluginConfig],
44 | };
45 |
--------------------------------------------------------------------------------
/reactjs-redux-todos-practice/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | "es2015",
4 | "react",
5 | ],
6 | "plugins": []
7 | }
8 |
--------------------------------------------------------------------------------
/reactjs-redux-todos-practice/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "airbnb",
3 | "rules": {
4 | "no-multiple-empty-lines": [2, {"max": 2, "maxEOF": 2}]
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/reactjs-redux-todos-practice/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 |
--------------------------------------------------------------------------------
/reactjs-redux-todos-practice/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Todo
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/reactjs-redux-todos-practice/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "reactjs-redux-todos-practice",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "start": "webpack-dev-server --colors --inline --content-base .",
8 | "prod": "webpack -p"
9 | },
10 | "author": "",
11 | "license": "ISC",
12 | "dependencies": {
13 | "react": "^15.3.2",
14 | "react-dom": "^15.3.2",
15 | "react-redux": "^4.4.5",
16 | "redux": "^3.6.0"
17 | },
18 | "devDependencies": {
19 | "babel-core": "^6.17.0",
20 | "babel-eslint": "^7.0.0",
21 | "babel-loader": "^6.2.5",
22 | "babel-preset-es2015": "^6.16.0",
23 | "babel-preset-react": "^6.16.0",
24 | "eslint": "^3.7.1",
25 | "eslint-config-airbnb": "^12.0.0",
26 | "eslint-loader": "^1.5.0",
27 | "eslint-plugin-import": "^1.16.0",
28 | "eslint-plugin-jsx-a11y": "^2.2.3",
29 | "eslint-plugin-react": "^6.4.1",
30 | "html-webpack-plugin": "^2.22.0",
31 | "webpack": "^1.13.2",
32 | "webpack-dev-server": "^1.16.2"
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/reactjs-redux-todos-practice/readme.md:
--------------------------------------------------------------------------------
1 | # Todos
2 |
3 | ## 概觀
4 | 練習以 ReactJS + Redux 實作 todos list
5 |
6 |
7 | ## 開發
8 | 1. 套件相依安裝 `npm install`
9 | 2. 運行服務 `npm start`
10 | 3. 開啟瀏覽器
11 |
12 | ```
13 | http://localhost:8008
14 | ```
15 |
16 | ## 產出 js & html
17 | 運行 `npm run prod`
18 |
19 | > 產出的 js & html file 會在根目錄下
20 |
21 |
22 | ## 參考
23 | * https://chentsulin.github.io/redux/index.html
--------------------------------------------------------------------------------
/reactjs-redux-todos-practice/src/actions/index.js:
--------------------------------------------------------------------------------
1 | let nextTodoId = 0
2 | export const addTodo = (text) => {
3 | return {
4 | type: 'ADD_TODO',
5 | id: nextTodoId++,
6 | text
7 | }
8 | }
9 |
10 | export const setVisibilityFilter = (filter) => {
11 | return {
12 | type: 'SET_VISIBILITY_FILTER',
13 | filter
14 | }
15 | }
16 |
17 | export const toggleTodo = (id) => {
18 | return {
19 | type: 'TOGGLE_TODO',
20 | id
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/reactjs-redux-todos-practice/src/components/AddTodo.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | const AddTodo = ({onAddTodo}) => {
4 | let input
5 |
6 | return (
7 |
8 |
23 |
24 | )
25 | }
26 |
27 | export default AddTodo
28 |
--------------------------------------------------------------------------------
/reactjs-redux-todos-practice/src/components/App.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import Footer from './Footer.jsx'
3 | import AddTodo from '../containers/AddTodo'
4 | import VisibleTodoList from '../containers/VisibleTodoList'
5 |
6 | const App = () => (
7 |
12 | )
13 |
14 | export default App
15 |
--------------------------------------------------------------------------------
/reactjs-redux-todos-practice/src/components/Footer.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import FilterLink from '../containers/FilterLink'
3 |
4 | const Footer = () => (
5 |
6 | Show:
7 | {" "}
8 |
9 | All
10 |
11 | {", "}
12 |
13 | Active
14 |
15 | {", "}
16 |
17 | Completed
18 |
19 |
20 | )
21 |
22 | export default Footer
23 |
--------------------------------------------------------------------------------
/reactjs-redux-todos-practice/src/components/Link.jsx:
--------------------------------------------------------------------------------
1 | import React, {PropTypes} from 'react'
2 |
3 | const Link = ({active, children, onClick}) => {
4 | if (active) {
5 | return {children}
6 | }
7 | return (
8 | {
9 | e.preventDefault();
10 | onClick();
11 | }}>{children}
12 | )
13 | }
14 |
15 | Link.propTypes = {
16 | active: PropTypes.bool.isRequired,
17 | children: PropTypes.node.isRequired,
18 | onClick: PropTypes.func.isRequired
19 | }
20 |
21 | export default Link
22 |
--------------------------------------------------------------------------------
/reactjs-redux-todos-practice/src/components/Todo.jsx:
--------------------------------------------------------------------------------
1 | import React, {PropTypes} from 'react'
2 |
3 | const Todo = ({onClick, completed, text}) => (
4 | {text}
9 | )
10 |
11 | Todo.propTypes = {
12 | onClick: PropTypes.func.isRequired,
13 | completed: PropTypes.bool.isRequired,
14 | text: PropTypes.string.isRequired,
15 | }
16 |
17 | export default Todo
18 |
--------------------------------------------------------------------------------
/reactjs-redux-todos-practice/src/components/TodoList.jsx:
--------------------------------------------------------------------------------
1 | import React, {PropTypes} from 'react'
2 | import Todo from './Todo.jsx'
3 |
4 | const TodoList = ({todos, onTodoClick}) => (
5 |
6 | {todos.map(todo => onTodoClick(todo.id)}/>)}
7 |
8 | )
9 |
10 | TodoList.propTypes = {
11 | todos: PropTypes.arrayOf(PropTypes.shape({id: PropTypes.number.isRequired, completed: PropTypes.bool.isRequired, text: PropTypes.string.isRequired}).isRequired).isRequired,
12 | onTodoClick: PropTypes.func.isRequired
13 | }
14 |
15 | export default TodoList
16 |
--------------------------------------------------------------------------------
/reactjs-redux-todos-practice/src/containers/AddTodo.js:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux'
2 | import {addTodo} from '../actions'
3 | import TodoList from '../components/AddTodo.jsx'
4 |
5 | const mapDispatchToProps = (dispatch) => {
6 | return {
7 | onAddTodo: (text) => {
8 | dispatch(addTodo(text))
9 | }
10 | }
11 | }
12 |
13 | const AddTodo = connect(
14 | undefined,
15 | mapDispatchToProps
16 | )(TodoList)
17 |
18 | export default AddTodo
19 | // import React from 'react'
20 | // import {connect} from 'react-redux'
21 | // import {addTodo} from '../actions'
22 | //
23 | // let AddTodo = ({dispatch}) => {
24 | // let input
25 | //
26 | // return (
27 | //
28 | //
41 | //
42 | // )
43 | // }
44 | // AddTodo = connect()(AddTodo)
45 | //
46 | // export default AddTodo
47 |
--------------------------------------------------------------------------------
/reactjs-redux-todos-practice/src/containers/FilterLink.js:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux'
2 | import { setVisibilityFilter } from '../actions'
3 | import Link from '../components/Link.jsx'
4 |
5 | const mapStateToProps = (state, ownProps) => {
6 | return {
7 | active: ownProps.filter === state.visibilityFilter
8 | }
9 | }
10 |
11 | const mapDispatchToProps = (dispatch, ownProps) => {
12 | return {
13 | onClick: () => {
14 | dispatch(setVisibilityFilter(ownProps.filter))
15 | }
16 | }
17 | }
18 |
19 | const FilterLink = connect(
20 | mapStateToProps,
21 | mapDispatchToProps
22 | )(Link)
23 |
24 | export default FilterLink
25 |
--------------------------------------------------------------------------------
/reactjs-redux-todos-practice/src/containers/VisibleTodoList.js:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux'
2 | import { toggleTodo } from '../actions'
3 | import TodoList from '../components/TodoList.jsx'
4 |
5 | const getVisibleTodos = (todos, filter) => {
6 | switch (filter) {
7 | case 'SHOW_ALL':
8 | return todos
9 | case 'SHOW_COMPLETED':
10 | return todos.filter(t => t.completed)
11 | case 'SHOW_ACTIVE':
12 | return todos.filter(t => !t.completed)
13 | }
14 | }
15 |
16 | const mapStateToProps = (state) => {
17 | return {
18 | todos: getVisibleTodos(state.todos, state.visibilityFilter)
19 | }
20 | }
21 |
22 | const mapDispatchToProps = (dispatch) => {
23 | return {
24 | onTodoClick: (id) => {
25 | dispatch(toggleTodo(id))
26 | }
27 | }
28 | }
29 |
30 | const VisibleTodoList = connect(
31 | mapStateToProps,
32 | mapDispatchToProps
33 | )(TodoList)
34 |
35 | export default VisibleTodoList
36 |
--------------------------------------------------------------------------------
/reactjs-redux-todos-practice/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Todo
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/reactjs-redux-todos-practice/src/index.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { render } from 'react-dom'
3 | import { Provider } from 'react-redux'
4 | import { createStore } from 'redux'
5 | import todoApp from './reducers'
6 | import App from './components/App.jsx'
7 |
8 | let store = createStore(todoApp)
9 |
10 | render(
11 |
12 |
13 | ,
14 | document.getElementById('app')
15 | )
16 |
--------------------------------------------------------------------------------
/reactjs-redux-todos-practice/src/reducers/index.js:
--------------------------------------------------------------------------------
1 | import {
2 | combineReducers
3 | } from 'redux'
4 | import todos from './todos'
5 | import visibilityFilter from './visibilityFilter'
6 |
7 | const todoApp = combineReducers({
8 | todos,
9 | visibilityFilter
10 | })
11 |
12 | export default todoApp
13 |
--------------------------------------------------------------------------------
/reactjs-redux-todos-practice/src/reducers/todos.js:
--------------------------------------------------------------------------------
1 | const todos = (state = [], action) => {
2 | switch (action.type) {
3 | case 'ADD_TODO':
4 | return [
5 | ...state, {
6 | id: action.id,
7 | text: action.text,
8 | completed: false
9 | }
10 | ]
11 | case 'TOGGLE_TODO':
12 | return state.map(todo => {
13 | if (todo.id !== action.id) {
14 | return todo
15 | }
16 |
17 | return Object.assign({}, todo, {
18 | completed: !todo.completed
19 | })
20 | })
21 | default:
22 | return state
23 | }
24 | }
25 |
26 | export default todos
27 |
--------------------------------------------------------------------------------
/reactjs-redux-todos-practice/src/reducers/visibilityFilter.js:
--------------------------------------------------------------------------------
1 | const visibilityFilter = (state = 'SHOW_ALL', action) => {
2 | switch (action.type) {
3 | case 'SET_VISIBILITY_FILTER':
4 | return action.filter
5 | default:
6 | return state
7 | }
8 | }
9 |
10 | export default visibilityFilter
11 |
--------------------------------------------------------------------------------
/reactjs-redux-todos-practice/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const HtmlWebpackPlugin = require('html-webpack-plugin');
3 |
4 | const HTMLWebpackPluginConfig = new HtmlWebpackPlugin({
5 | template: `${__dirname}/src/index.html`,
6 | filename: 'index.html',
7 | inject: 'body',
8 | });
9 |
10 | module.exports = {
11 | entry: [
12 | './src/index.jsx',
13 | ],
14 | output: {
15 | path: `${__dirname}`,
16 | filename: 'index_bundle.js',
17 | },
18 | module: {
19 | preLoaders: [{
20 | test: /\.jsx$|\.js$/,
21 | loader: 'eslint-loader',
22 | include: `${__dirname}/src`,
23 | exclude: /bundle\.js$/
24 | }],
25 | loaders: [{
26 | test: /\.js$|\.jsx$/,
27 | exclude: /node_modules/,
28 | loader: 'babel-loader',
29 | query: {
30 | presets: ['es2015', 'react'],
31 | },
32 | }],
33 | },
34 | devServer: {
35 | inline: true,
36 | port: 8008,
37 | },
38 | plugins: [HTMLWebpackPluginConfig],
39 | };
40 |
--------------------------------------------------------------------------------
/readme.md:
--------------------------------------------------------------------------------
1 | # react-demo
2 |
3 | 目錄 | 技術 | 備註 | DEMO
4 | --- | --- | --- | ---
5 | reactjs-redux-todos-practice | ReactJS + Redux | 依照 Redux 設計模式,實作 POS 點單 | [URL](https://rz12345.github.io/react-demo/reactjs-redux-order-practice/)
6 | reactjs-redux-todos-practice | ReactJS + Redux | 依照 Redux 設計模式,實作 Todos 待辦清單 | [URL](https://rz12345.github.io/react-demo/reactjs-redux-todos-practice/)
7 | reactjs-flex-order-practice | ReactJS + Flux | 依照 Flux 設計模式,實作 POS 點單 | [URL](https://rz12345.github.io/react-demo/reactjs-flex-order-practice/)
8 | reactjs-order-practice | ReactJS | 基於 wPOS 重構 | [URL](https://rz12345.github.io/react-demo/reactjs-order-practice/)
9 | wPOS | ReactJS | POS 點單 | [URL](https://rz12345.github.io/react-demo/wPOS/)
--------------------------------------------------------------------------------
/wPOS/.gitignore:
--------------------------------------------------------------------------------
1 | .idea
2 | bower_components
3 | node_modules
4 |
--------------------------------------------------------------------------------
/wPOS/db/categories.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "id":1,
4 | "name":"\u4e3b\u98df",
5 | "user":"rz",
6 | "is_active":1,
7 | "created_at":"2016-06-09 12:27:01",
8 | "updated_at":"2016-06-09 12:27:01"
9 | },
10 | {
11 | "id":2,
12 | "name":"\u6e6f\u54c1",
13 | "user":"rz",
14 | "is_active":1,
15 | "created_at":"2016-06-09 12:27:01",
16 | "updated_at":"2016-06-09 12:27:01"
17 | },
18 | {
19 | "id":3,
20 | "name":"\u5c0f\u9ede",
21 | "user":"rz",
22 | "is_active":1,
23 | "created_at":"2016-06-09 12:27:01",
24 | "updated_at":"2016-06-09 12:27:01"
25 | },
26 | {
27 | "id":4,
28 | "name":"\u5176\u4ed6",
29 | "user":"rz",
30 | "is_active":1,
31 | "created_at":"2016-06-09 12:27:01",
32 | "updated_at":"2016-06-09 12:27:01"
33 | }
34 | ]
35 |
--------------------------------------------------------------------------------
/wPOS/db/payments.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "id":2088,
4 | "order":{
5 | "orderFromMenu":{
6 | "1":"1",
7 | "9":"2"
8 | },
9 | "orderFromAddition":null
10 | },
11 | "summary":[
12 | {
13 | "id":1,
14 | "name":"\u85e5\u81b3\u6e05\u71c9\u725b\u8089\u9eb5(\u5c0f)",
15 | "category":"\u4e3b\u98df",
16 | "price":80,
17 | "amount":1,
18 | "subtotal":80,
19 | "from":"menu"
20 | },
21 | {
22 | "id":9,
23 | "name":"\u7d05\u71d2\u4e7e\u62cc\u725b\u8089\u9eb5(\u5c0f)",
24 | "category":"\u4e3b\u98df",
25 | "price":80,
26 | "amount":2,
27 | "subtotal":160,
28 | "from":"menu"
29 | }
30 | ],
31 | "total":240,
32 | "part":"\u4e2d\u5348",
33 | "is_paid":1,
34 | "is_to_go":0,
35 | "table":"二號桌",
36 | "user":"user",
37 | "deleted_at":null,
38 | "created_at":"2016-06-19 14:57:35",
39 | "updated_at":"2016-06-19 14:57:35"
40 | },
41 | {
42 | "id":2087,
43 | "order":{
44 | "orderFromMenu":{
45 | "1":"1",
46 | "3":"1",
47 | "4":"1",
48 | "12":"1"
49 | },
50 | "orderFromAddition":null
51 | },
52 | "summary":[
53 | {
54 | "id":1,
55 | "name":"\u85e5\u81b3\u6e05\u71c9\u725b\u8089\u9eb5(\u5c0f)",
56 | "category":"\u4e3b\u98df",
57 | "price":80,
58 | "amount":1,
59 | "subtotal":80,
60 | "from":"menu"
61 | },
62 | {
63 | "id":3,
64 | "name":"\u85e5\u81b3\u6e05\u71c9\u725b\u8089\u6e6f\u9eb5(\u5c0f)",
65 | "category":"\u4e3b\u98df",
66 | "price":50,
67 | "amount":1,
68 | "subtotal":50,
69 | "from":"menu"
70 | },
71 | {
72 | "id":4,
73 | "name":"\u85e5\u81b3\u6e05\u71c9\u725b\u8089\u6e6f\u9eb5(\u5927)",
74 | "category":"\u4e3b\u98df",
75 | "price":60,
76 | "amount":1,
77 | "subtotal":60,
78 | "from":"menu"
79 | },
80 | {
81 | "id":12,
82 | "name":"\u9999\u8fa3\u725b\u6c41\u4e7e\u62cc\u9eb5(\u5927)",
83 | "category":"\u4e3b\u98df",
84 | "price":55,
85 | "amount":1,
86 | "subtotal":55,
87 | "from":"menu"
88 | }
89 | ],
90 | "total":245,
91 | "part":"\u4e2d\u5348",
92 | "is_paid":1,
93 | "is_to_go":0,
94 | "table":"三號桌",
95 | "user":"user",
96 | "deleted_at":null,
97 | "created_at":"2016-06-19 14:57:32",
98 | "updated_at":"2016-06-19 14:57:32"
99 | },
100 | {
101 | "id":2086,
102 | "order":{
103 | "orderFromMenu":{
104 | "4":"1",
105 | "1":"1",
106 | "6":"1",
107 | "2":"1"
108 | },
109 | "orderFromAddition":null
110 | },
111 | "summary":[
112 | {
113 | "id":4,
114 | "name":"\u85e5\u81b3\u6e05\u71c9\u725b\u8089\u6e6f\u9eb5(\u5927)",
115 | "category":"\u4e3b\u98df",
116 | "price":60,
117 | "amount":1,
118 | "subtotal":60,
119 | "from":"menu"
120 | },
121 | {
122 | "id":1,
123 | "name":"\u85e5\u81b3\u6e05\u71c9\u725b\u8089\u9eb5(\u5c0f)",
124 | "category":"\u4e3b\u98df",
125 | "price":80,
126 | "amount":1,
127 | "subtotal":80,
128 | "from":"menu"
129 | },
130 | {
131 | "id":6,
132 | "name":"\u62db\u724c\u7d05\u71d2\u725b\u8089\u9eb5(\u5927)",
133 | "category":"\u4e3b\u98df",
134 | "price":100,
135 | "amount":1,
136 | "subtotal":100,
137 | "from":"menu"
138 | },
139 | {
140 | "id":2,
141 | "name":"\u85e5\u81b3\u6e05\u71c9\u725b\u8089\u9eb5(\u5927)",
142 | "category":"\u4e3b\u98df",
143 | "price":90,
144 | "amount":1,
145 | "subtotal":90,
146 | "from":"menu"
147 | }
148 | ],
149 | "total":330,
150 | "part":"\u4e2d\u5348",
151 | "is_paid":1,
152 | "is_to_go":0,
153 | "table":"五號桌",
154 | "user":"user",
155 | "deleted_at":null,
156 | "created_at":"2016-06-19 14:57:29",
157 | "updated_at":"2016-06-19 14:57:29"
158 | },
159 | {
160 | "id":2085,
161 | "order":{
162 | "orderFromMenu":{
163 | "10":"2"
164 | },
165 | "orderFromAddition":null
166 | },
167 | "summary":[
168 | {
169 | "id":10,
170 | "name":"\u7d05\u71d2\u4e7e\u62cc\u725b\u8089\u9eb5(\u5927)",
171 | "category":"\u4e3b\u98df",
172 | "price":90,
173 | "amount":2,
174 | "subtotal":180,
175 | "from":"menu"
176 | }
177 | ],
178 | "total":180,
179 | "part":"\u4e2d\u5348",
180 | "is_paid":1,
181 | "is_to_go":1,
182 | "table":null,
183 | "user":"user",
184 | "deleted_at":null,
185 | "created_at":"2016-06-19 14:57:05",
186 | "updated_at":"2016-06-19 14:57:05"
187 | }
188 | ]
189 |
--------------------------------------------------------------------------------
/wPOS/db/tables.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "name":"一號桌"
4 | },
5 | {
6 | "name":"二號桌"
7 | },
8 | {
9 | "name":"三號桌"
10 | },
11 | {
12 | "name":"四號桌"
13 | },
14 | {
15 | "name":"五號桌"
16 | },
17 | {
18 | "name":"六號桌"
19 | }
20 | ]
21 |
--------------------------------------------------------------------------------
/wPOS/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | POS
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/wPOS/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "wPOS",
3 | "version": "0.0.1",
4 | "description": "A web-based POS demo",
5 | "devDependencies": {
6 | "babelify": "^6.1.3",
7 | "browserify": "^10.2.4",
8 | "fetch": "^1.0.1",
9 | "node-sass": "^3.7.0",
10 | "nodemon": "^1.9.2",
11 | "npm-run-all": "^2.1.1",
12 | "react": "^15.1.0",
13 | "react-dom": "^15.1.0",
14 | "react-router": "^2.4.1",
15 | "uglify": "^0.1.5",
16 | "watchify": "^3.7.0"
17 | },
18 | "scripts": {
19 | "start": "npm run watch",
20 | "watch": "npm-run-all --parallel \"watch-*\"",
21 | "build-js": "browserify src/entry.jsx | uglifyjs -o ./bundle.js",
22 | "watch-js": "watchify src/entry.jsx -do ./bundle.js -v",
23 | "build-css": "node-sass --include-path src/styles src/styles/entry.scss ./main.css",
24 | "watch-css": "nodemon -e scss -x \"npm run build-css\""
25 | },
26 | "browserify": {
27 | "transform": [
28 | "babelify"
29 | ]
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/wPOS/readme.md:
--------------------------------------------------------------------------------
1 | # wPOS
2 |
3 | ## 概觀
4 |
5 | 練習以 ReactJS 實作 POS 點單介面
6 |
7 | DEMO:
8 | https://rz12345.github.io/react-demo/wPOS/
9 |
10 | ## 部屬
11 |
12 | ```
13 | npm install
14 | bower install
15 | ```
16 |
17 | ## 開發環境
18 |
19 | 開發時,透過腳本監控 css & js 變化,即時 compiled
20 | ```
21 | npm start
22 | ```
23 |
24 | ## 學習參考
25 | -| 敘述 | 網址
26 | --- | --- | ---
27 | ReactJS 基礎 | 24 小時,React 快速入門 | https://github.com/shiningjason1989/react-quick-tutorial/
28 | ReactJS 技巧 | list item click toggle class | http://jsfiddle.net/faria/3nodz94g/
29 |
--------------------------------------------------------------------------------
/wPOS/src/App.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Link } from 'react-router'
3 |
4 | class App extends React.Component {
5 | render() {
6 | return (
7 |
8 |
28 |
29 |
30 |
31 | {this.props.children}
32 |
33 |
34 |
35 |
36 | )
37 | }
38 | }
39 |
40 | module.exports = App;
41 |
--------------------------------------------------------------------------------
/wPOS/src/components/ChoiceButton.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | class ChoiceButton extends React.Component {
4 | render() {
5 | const {type, onClick, isSelected} = this.props;
6 | const classAttr = "btn btn-lg btn-block " + (isSelected
7 | ? "btn-primary"
8 | : "btn-default");
9 | return (
10 |
11 | );
12 | }
13 | }
14 | ChoiceButton.defaultProps = {
15 | isSelected: false
16 | };
17 | ChoiceButton.propTypes = {
18 | isSelected: React.PropTypes.bool.isRequired
19 | };
20 |
21 | module.exports = ChoiceButton;
22 |
--------------------------------------------------------------------------------
/wPOS/src/components/InputField.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | class InputField extends React.Component {
4 | constructor(props, context) {
5 | super(props, context);
6 | this.state = {value: props.value || ''};
7 | this.handleChange = this.handleChange.bind(this);
8 | }
9 |
10 | handleChange(e) {
11 | const {onUpdateAddition} = this.props;
12 | // 更新元件的 state value
13 | this.setState({value: e.target.value});
14 |
15 | // 一併更新父層的 addition
16 | if (onUpdateAddition) {
17 | onUpdateAddition(e.target.value);
18 | }
19 | }
20 |
21 | render() {
22 | return (
23 |
29 | );
30 | }
31 | }
32 |
33 | module.exports = InputField;
34 |
--------------------------------------------------------------------------------
/wPOS/src/components/Loading.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | class Loading extends React.Component {
4 | render(){
5 | return (
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 | );
21 | }
22 |
23 | }
24 |
25 | module.exports = Loading;
--------------------------------------------------------------------------------
/wPOS/src/components/MenuCategory.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | // jsx components
4 | const MenuCategoryItem = require('./MenuCategoryItem.jsx');
5 |
6 | class MenuCategory extends React.Component {
7 | handleClick(i, category) {
8 | const {onClickCategroy} = this.props;
9 | // 觸發切換分類 item 動作
10 | onClickCategroy && onClickCategroy(category);
11 |
12 | // 設定選擇的 category (項目變成 active 狀態)
13 | this.setState({
14 | selectedItem: i
15 | });
16 | }
17 |
18 | constructor(props, context) {
19 | super(props, context);
20 | this.state = {
21 | selectedItem: 0
22 | };
23 | }
24 |
25 | render() {
26 | const {categories} = this.props;
27 | return (
28 |
29 |
30 | {categories ? categories.map((category, i)=>
31 | ()
35 | ) : ''}
36 |
37 |
38 | );
39 | }
40 | }
41 |
42 | module.exports = MenuCategory;
43 |
--------------------------------------------------------------------------------
/wPOS/src/components/MenuCategoryItem.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | // list item click toggle class
4 | // example: http://jsfiddle.net/faria/3nodz94g/
5 |
6 | class MenuCategoryItem extends React.Component {
7 | render() {
8 | const {category,onClick,isSelected} = this.props;
9 | return (
10 |
13 | {category}
14 |
15 | );
16 | }
17 | }
18 | MenuCategoryItem.defaultProps = {
19 | isSelected: false
20 | };
21 | MenuCategoryItem.propTypes = {
22 | isSelected: React.PropTypes.bool.isRequired
23 | };
24 |
25 | module.exports = MenuCategoryItem;
26 |
--------------------------------------------------------------------------------
/wPOS/src/components/MenuItem.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | // jsx components
4 | const MenuRow = require('./MenuRow.jsx');
5 |
6 | // js helper
7 | const ArrayHelper = require('./../helpers/ArrayHelper');
8 |
9 | class MenuItem extends React.Component {
10 | render() {
11 | const {
12 | items,
13 | onClickItem,
14 | contentDivHeight
15 | } = this.props;
16 | const splitedItems = items ? ArrayHelper.splitArray2D(items, 4) : null;
17 | const categoriesHeight = 83;
18 | const itemsPageNavHeight = 101;
19 | const paddingHeight = 10;
20 | const rowHeight = contentDivHeight ? (contentDivHeight - categoriesHeight - itemsPageNavHeight - paddingHeight) / 3 : null;
21 | const menuItemsRowStyle = rowHeight
22 | ? {
23 | height: rowHeight + 'px'
24 | }
25 | : {
26 | height: 'inherit'
27 | };
28 |
29 |
30 | return (
31 |
32 | {splitedItems ? splitedItems.map((items, i)=>(
33 |
34 | )) : ''}
35 |
36 | );
37 | }
38 | }
39 |
40 | module.exports = MenuItem;
41 |
--------------------------------------------------------------------------------
/wPOS/src/components/MenuItemNav.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | class MenuItemNav extends React.Component {
4 | constructor(props, context) {
5 | super(props, context);
6 | this.state = {
7 | pagesCount: null,
8 | currentPage: null
9 | };
10 | }
11 |
12 | render() {
13 | const {pagesCount, currentPage,onClickNav} = this.props;
14 | const hasNextPage = pagesCount - currentPage > 1; // 是否還有下一頁
15 | const hasPrevPage = currentPage - 1 > -1; // 是否還有上一頁
16 | const nextPage = hasNextPage
17 | ? currentPage + 1
18 | : currentPage;
19 | const prevPage = hasPrevPage
20 | ? currentPage - 1
21 | : currentPage;
22 |
23 | return (
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 | );
35 | }
36 | }
37 |
38 | module.exports = MenuItemNav;
39 |
--------------------------------------------------------------------------------
/wPOS/src/components/MenuRow.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | // jsx components
4 | const MenuRowItem = require('./MenuRowItem.jsx');
5 |
6 | class MenuRow extends React.Component {
7 | render() {
8 | const {items,onClickItem,style} = this.props;
9 | return (
10 |
11 | {items.map((item, i)=> (
12 |
16 | ))}
17 |
18 | );
19 | }
20 | }
21 |
22 | module.exports = MenuRow;
23 |
--------------------------------------------------------------------------------
/wPOS/src/components/MenuRowItem.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | class MenuRowItem extends React.Component {
4 | render() {
5 | const {item,className,onClickItem} = this.props;
6 | return (
7 | onClickItem && onClickItem(item)}>
10 | {item.name}
11 |
12 | );
13 | }
14 | }
15 |
16 | module.exports = MenuRowItem;
17 |
--------------------------------------------------------------------------------
/wPOS/src/components/OrderDetails.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | // jsx components
4 | const InputField = require('./InputField.jsx');
5 |
6 | class OrderDetails extends React.Component {
7 | render() {
8 | const {
9 | order,
10 | addition,
11 | onClickItem,
12 | onClickDeleteAddition,
13 | onUpdateAddition,
14 | divHeight
15 | } = this.props;
16 |
17 | // 選單內的點單項目
18 | const OrderElements = order
19 | ? order.map((el) => (
20 |
21 | {el.name} |
22 | {el.amount} |
23 | {el.price} |
24 | {el.subtotal} |
25 |
26 |
27 |
30 | |
31 |
32 | ))
33 | : '';
34 |
35 | // 加點項目
36 | const AdditionElements = addition
37 | ? addition.map((el) => (
38 |
39 | onUpdateAddition(el.id, 'name', value)}/> |
41 | onUpdateAddition(el.id, 'amount', value)}/> |
43 | onUpdateAddition(el.id, 'price', value)}/> |
45 | {el.subtotal} |
46 |
47 |
48 |
49 |
52 | |
53 |
54 | ))
55 | : '';
56 |
57 | // orderDetails 動態高度
58 | const orderDetailsDivStyle = divHeight
59 | ? {
60 | height: divHeight + 'px'
61 | }
62 | : {
63 | height: 'inherit'
64 | };
65 |
66 | return (
67 |
68 |
69 |
70 |
71 | 品項 |
72 | 數量 |
73 | 單價 |
74 | 小計 |
75 |
76 | |
77 |
78 |
79 |
80 | {OrderElements}
81 | {AdditionElements}
82 |
83 |
84 |
85 | );
86 | }
87 | }
88 |
89 | module.exports = OrderDetails;
90 |
--------------------------------------------------------------------------------
/wPOS/src/components/OrderList.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ReactDOM from 'react-dom'
3 |
4 | // config
5 | const API = require('./../configs/API');
6 |
7 | // jsx components
8 | const OrderSubmit = require('./OrderSubmit.jsx');
9 | const OrderDetails = require('./OrderDetails.jsx');
10 |
11 | class OrderList extends React.Component {
12 | // 背景執行提交表單的動作
13 | handleSubmit(e) {
14 | e.preventDefault();
15 | const {clearOrder} = this.props;
16 | const {order, addition, total} = this.state;
17 | // 提交
18 | alert('觸發提交動作, 清除 order!');
19 | // const formNode = ReactDOM.findDOMNode(this);
20 | // fetch(API.post.payment, {
21 | // method: 'POST',
22 | // body: new FormData(formNode)
23 | // });
24 | // 呼叫父層清除 order 項目的方法
25 | clearOrder && clearOrder();
26 | }
27 |
28 | updateOrderSubmitHeight(height) {
29 | this.setState({
30 | orderSubmitHeight: height // 取得 orderSubmitHeight 高度
31 | });
32 | }
33 |
34 | constructor(props, context) {
35 | super(props, context);
36 | this.state = {
37 | OrderSubmitHeight: null
38 | };
39 | this.updateOrderSubmitHeight = this.updateOrderSubmitHeight.bind(this);
40 | this.handleSubmit = this.handleSubmit.bind(this);
41 | }
42 |
43 | render() {
44 | const {
45 | order,
46 | addition,
47 | total,
48 | onClickItem,
49 | onClickNewAddition,
50 | onClickDeleteAddition,
51 | onUpdateAddition,
52 | contentDivHeight,
53 | panelHeadingHeight
54 | } = this.props;
55 | const {orderSubmitHeight} = this.state;
56 |
57 | // 動態 OrderDetails 計算方法
58 | // 1. Order 接 contentDivHeight & panelHeadingHeight
59 | // 2. 當 OrderSubmit 生成後 , 產生 OrderSubmitHeight
60 | // 3. contentDivHeight 減去 panelHeadingHeight && OrderSubmitHeight && 兩個 rowHeight 來得到 OrderDetails 必要之高度
61 | const rowHeight = 51;
62 | const orderDetailsHeight = (contentDivHeight && panelHeadingHeight && orderSubmitHeight)
63 | ? contentDivHeight - panelHeadingHeight - orderSubmitHeight
64 | : null;
65 | const props = {
66 | OrderDetails: {
67 | order: order,
68 | addition: addition,
69 | onClickItem: onClickItem,
70 | onClickDeleteAddition: onClickDeleteAddition,
71 | onUpdateAddition: onUpdateAddition,
72 | divHeight: orderDetailsHeight
73 | },
74 | OrderSubmit: {
75 | order: order,
76 | total: total,
77 | onClickNewAddition: onClickNewAddition,
78 | updateOrderSubmitHeight: this.updateOrderSubmitHeight
79 | }
80 | };
81 |
82 | return (
83 |
87 | );
88 | }
89 | }
90 |
91 | module.exports = OrderList;
92 |
--------------------------------------------------------------------------------
/wPOS/src/configs/API.js:
--------------------------------------------------------------------------------
1 | const serviceURL = './db/';
2 | const API = {
3 | get:{
4 | item: serviceURL + '/items.json',
5 | category: serviceURL + '/categories.json',
6 | table: serviceURL + '/tables.json',
7 | payment: {
8 | today: serviceURL + '/payments.json'
9 | }
10 | }
11 | };
12 |
13 | module.exports = API;
14 |
--------------------------------------------------------------------------------
/wPOS/src/entry.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { render } from 'react-dom'
3 | import { Router, Route, hashHistory, IndexRoute } from 'react-router'
4 |
5 | const App = require('./App.jsx');
6 | const Order = require('./pages/Order.jsx');
7 | const Home = require('./pages/Home.jsx');
8 | const NotFound = require('./pages/NotFound.jsx');
9 |
10 | render((
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 | ), document.getElementById('app'));
20 |
--------------------------------------------------------------------------------
/wPOS/src/helpers/ArrayHelper.js:
--------------------------------------------------------------------------------
1 | class ArrayHelper {
2 | /**
3 | * 將陣列 haystack 依照 colNum 切成二維陣列
4 | *
5 | * @param haystack
6 | * @param colNum
7 | * @returns {Array}
8 | */
9 | static splitArray2D(haystack, colNum) {
10 | var result = [];
11 | var rowNum = Math.ceil(haystack.length / colNum); // 無條件進位
12 | for (var i = 0; i < rowNum; i++) {
13 | // 第一階 array
14 | result[i] = [];
15 | for (var j = 0; j < colNum; j++) {
16 | // 第二階 array
17 | var idx = (i * colNum) + j;
18 | if (haystack[idx])
19 | result[i].push(haystack[idx]);
20 | }
21 | }
22 | return result;
23 | };
24 | }
25 |
26 | module.exports = ArrayHelper;
27 |
--------------------------------------------------------------------------------
/wPOS/src/helpers/PaymentHelper.js:
--------------------------------------------------------------------------------
1 | class PaymentHelper {
2 | // 取得 payment 的摘要資訊
3 | static getPaymentSummary(payment){
4 | // 這邊只要品名跟數量就好
5 | const data = payment.map((item) => item.name + ': ' + item.amount);
6 | return data.join(', ');
7 | }
8 |
9 | // 傳回該分類的 items
10 | static getItemsByCategory(items, category) {
11 | return items.filter((item) => item.category === category);
12 | }
13 |
14 | // 傳回該分頁的 items
15 | static getItemsByPage(items, idx) {
16 | return items[idx];
17 | }
18 |
19 | // 新增項目至明細表中
20 | static addItemToOrder(order, item) {
21 | const target = order.find((el) => el.id === item.id);
22 | if (target) {
23 | // 已在 order 內, 更新 obj 的 amount & subtotal
24 | target.amount += 1;
25 | target.subtotal = target.amount * target.price;
26 | } else {
27 | // 若不在 order 內, 產生新 obj
28 | order.push({
29 | id: item.id,
30 | name: item.name,
31 | amount: 1,
32 | price: item.price,
33 | subtotal: 1 * item.price
34 | });
35 | }
36 | return order;
37 | }
38 |
39 | // 從明細表中移除項目
40 | static deleteItemFromOrder(order, item) {
41 | const target = order.find((el) => el.id === item.id);
42 | if (target) {
43 | target.amount -= 1;
44 | target.subtotal = target.amount * target.price;
45 | }
46 | return order.filter((el) => el.amount > 0);
47 | }
48 |
49 | // 新增加點項目
50 | static addAdditionToOrder(addition) {
51 | addition.push({
52 | id: new Date().getTime(),
53 | name: '',
54 | amount: 1,
55 | price: 0,
56 | subtotal: 0
57 | });
58 | return addition;
59 | }
60 |
61 | // 更新加點項目
62 | static updateAddition(addition, id, key, value) {
63 | const target = addition.find((el) => el.id === id);
64 | if (target) {
65 | target[key] = value;
66 | target.subtotal = target.amount * target.price;
67 | }
68 | return addition;
69 | }
70 |
71 | // 刪除加點項目
72 | static deleteAdditionFromOrder(addition, item) {
73 | const idx = addition.findIndex((el) => el.id === item.id);
74 | if (idx !== -1)
75 | addition.splice(idx, 1);
76 | return addition;
77 | }
78 | }
79 |
80 | module.exports = PaymentHelper;
81 |
--------------------------------------------------------------------------------
/wPOS/src/pages/NotFound.jsx:
--------------------------------------------------------------------------------
1 | const React = require('react');
2 | class NotFound extends React.Component {
3 | render() {
4 | return (
5 |
6 |
Not Found This Page
7 |
8 | );
9 | }
10 | }
11 | module.exports = NotFound;
12 |
--------------------------------------------------------------------------------
/wPOS/src/styles/components/loading.scss:
--------------------------------------------------------------------------------
1 | .sk-circle {
2 | margin: 100px auto;
3 | width: 40px;
4 | height: 40px;
5 | position: relative;
6 | }
7 | .sk-circle .sk-child {
8 | width: 100%;
9 | height: 100%;
10 | position: absolute;
11 | left: 0;
12 | top: 0;
13 | }
14 | .sk-circle .sk-child:before {
15 | content: '';
16 | display: block;
17 | margin: 0 auto;
18 | width: 15%;
19 | height: 15%;
20 | background-color: #333;
21 | border-radius: 100%;
22 | -webkit-animation: sk-circleBounceDelay 1.2s infinite ease-in-out both;
23 | animation: sk-circleBounceDelay 1.2s infinite ease-in-out both;
24 | }
25 | .sk-circle .sk-circle2 {
26 | -webkit-transform: rotate(30deg);
27 | -ms-transform: rotate(30deg);
28 | transform: rotate(30deg); }
29 | .sk-circle .sk-circle3 {
30 | -webkit-transform: rotate(60deg);
31 | -ms-transform: rotate(60deg);
32 | transform: rotate(60deg); }
33 | .sk-circle .sk-circle4 {
34 | -webkit-transform: rotate(90deg);
35 | -ms-transform: rotate(90deg);
36 | transform: rotate(90deg); }
37 | .sk-circle .sk-circle5 {
38 | -webkit-transform: rotate(120deg);
39 | -ms-transform: rotate(120deg);
40 | transform: rotate(120deg); }
41 | .sk-circle .sk-circle6 {
42 | -webkit-transform: rotate(150deg);
43 | -ms-transform: rotate(150deg);
44 | transform: rotate(150deg); }
45 | .sk-circle .sk-circle7 {
46 | -webkit-transform: rotate(180deg);
47 | -ms-transform: rotate(180deg);
48 | transform: rotate(180deg); }
49 | .sk-circle .sk-circle8 {
50 | -webkit-transform: rotate(210deg);
51 | -ms-transform: rotate(210deg);
52 | transform: rotate(210deg); }
53 | .sk-circle .sk-circle9 {
54 | -webkit-transform: rotate(240deg);
55 | -ms-transform: rotate(240deg);
56 | transform: rotate(240deg); }
57 | .sk-circle .sk-circle10 {
58 | -webkit-transform: rotate(270deg);
59 | -ms-transform: rotate(270deg);
60 | transform: rotate(270deg); }
61 | .sk-circle .sk-circle11 {
62 | -webkit-transform: rotate(300deg);
63 | -ms-transform: rotate(300deg);
64 | transform: rotate(300deg); }
65 | .sk-circle .sk-circle12 {
66 | -webkit-transform: rotate(330deg);
67 | -ms-transform: rotate(330deg);
68 | transform: rotate(330deg); }
69 | .sk-circle .sk-circle2:before {
70 | -webkit-animation-delay: -1.1s;
71 | animation-delay: -1.1s; }
72 | .sk-circle .sk-circle3:before {
73 | -webkit-animation-delay: -1s;
74 | animation-delay: -1s; }
75 | .sk-circle .sk-circle4:before {
76 | -webkit-animation-delay: -0.9s;
77 | animation-delay: -0.9s; }
78 | .sk-circle .sk-circle5:before {
79 | -webkit-animation-delay: -0.8s;
80 | animation-delay: -0.8s; }
81 | .sk-circle .sk-circle6:before {
82 | -webkit-animation-delay: -0.7s;
83 | animation-delay: -0.7s; }
84 | .sk-circle .sk-circle7:before {
85 | -webkit-animation-delay: -0.6s;
86 | animation-delay: -0.6s; }
87 | .sk-circle .sk-circle8:before {
88 | -webkit-animation-delay: -0.5s;
89 | animation-delay: -0.5s; }
90 | .sk-circle .sk-circle9:before {
91 | -webkit-animation-delay: -0.4s;
92 | animation-delay: -0.4s; }
93 | .sk-circle .sk-circle10:before {
94 | -webkit-animation-delay: -0.3s;
95 | animation-delay: -0.3s; }
96 | .sk-circle .sk-circle11:before {
97 | -webkit-animation-delay: -0.2s;
98 | animation-delay: -0.2s; }
99 | .sk-circle .sk-circle12:before {
100 | -webkit-animation-delay: -0.1s;
101 | animation-delay: -0.1s; }
102 |
103 | @-webkit-keyframes sk-circleBounceDelay {
104 | 0%, 80%, 100% {
105 | -webkit-transform: scale(0);
106 | transform: scale(0);
107 | } 40% {
108 | -webkit-transform: scale(1);
109 | transform: scale(1);
110 | }
111 | }
112 |
113 | @keyframes sk-circleBounceDelay {
114 | 0%, 80%, 100% {
115 | -webkit-transform: scale(0);
116 | transform: scale(0);
117 | } 40% {
118 | -webkit-transform: scale(1);
119 | transform: scale(1);
120 | }
121 | }
--------------------------------------------------------------------------------
/wPOS/src/styles/entry.scss:
--------------------------------------------------------------------------------
1 | // variables
2 | @import 'variables/font.scss';
3 | @import 'variables/button.scss';
4 |
5 | // layout
6 | @import 'layout/grid.scss';
7 | @import 'layout/app.scss';
8 | @import 'layout/grid.scss';
9 |
10 | // Payment style
11 | @import 'pages/Order';
12 | @import 'pages/Report.scss';
13 | @import 'pages/Home.scss';
14 |
15 | // loading style
16 | // ref:http://tobiasahlin.com/spinkit/
17 | @import 'components/loading';
18 |
--------------------------------------------------------------------------------
/wPOS/src/styles/layout/app.scss:
--------------------------------------------------------------------------------
1 | html,
2 | body {
3 | height: 100%;
4 | /* The html and body elements cannot have any padding or margin. */
5 | }
6 |
7 |
8 | // 滿版 window 高度, ref:http://www.bootply.com/97103
9 | .center-container {
10 | height:100%;
11 | }
12 |
13 | .center-row {
14 | height:100%;
15 | background-color: #9ac7d5;
16 | background-size:cover;
17 | overflow-y: hidden;
18 | }
19 |
20 |
21 | #app{
22 | height: 100%;
23 | font-family: "微軟正黑體", "Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial;
24 | }
25 |
26 | /* Wrapper for page content to push down footer */
27 | #wrap {
28 | min-height: 100%;
29 | height: 100%;
30 | /* Negative indent footer by its height */
31 | margin: 0 auto 0 0;
32 | /* Pad bottom by footer height */
33 | padding: 0 0 0 0;
34 |
35 | > .center-container {
36 | padding: 50px 0 0 0;
37 | margin:0;
38 | }
39 | }
--------------------------------------------------------------------------------
/wPOS/src/styles/layout/grid.scss:
--------------------------------------------------------------------------------
1 | // 複寫 col-* 的 padding & margin
2 | .nopadding {
3 | padding: 0 !important;
4 | margin: 0 !important;
5 | }
--------------------------------------------------------------------------------
/wPOS/src/styles/pages/Home.scss:
--------------------------------------------------------------------------------
1 | .home {
2 | .content {
3 | .home-details {
4 | // 該 div height 由 js 動態決定
5 | overflow-y: auto; // 依據 div height 作用自動滾輪
6 | }
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/wPOS/src/styles/pages/Order.scss:
--------------------------------------------------------------------------------
1 | .order {
2 | .menu {
3 | position: relative;
4 | .item {
5 | // 選單項目滑動
6 | .flex-container {
7 | display: flex;
8 | /* primary flex container */
9 | flex-direction: row;
10 | /* horizontal alignment of flex items (default value; can be omitted) */
11 | align-items: stretch;
12 | /* will apply equal heights to flex items (default value; can be omitted) */
13 | // height: 150px; // 高度由 js 動態決定
14 | .flex-item {
15 | display: flex;
16 | /* nested flex container */
17 | flex-direction: column;
18 | /* vertical alignment of flex items */
19 | justify-content: center;
20 | /* center flex items vertically */
21 | align-items: center;
22 | /* center flex items horizontally */
23 | flex: 1;
24 | box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19);
25 | margin: 5px;
26 | padding: 1em;
27 | font-size: $fontValue;
28 | text-align: center;
29 | }
30 | }
31 | }
32 | .item-nav {
33 | position: absolute;
34 | bottom: 0;
35 | .btn {
36 | span {
37 | font-size: $fontTitle;
38 | }
39 | padding: 10px 0;
40 | }
41 | }
42 | }
43 | .content {
44 | .panel.center-container {
45 |
46 | .panel-heading{
47 | font-size: $fontTitle;
48 | }
49 | .panel-body{
50 |
51 | form{
52 | //position: relative;
53 | .order-details {
54 | // 該 div height 由 js 動態決定
55 | overflow-y: auto; // 依據 div height 作用自動滾輪
56 |
57 | table{
58 | font-size: $tableFont;
59 | }
60 | table {
61 | tbody tr td,
62 | thead tr th {
63 | &:nth-child(1) {
64 | width: 45%;
65 | }
66 | &:nth-child(2),
67 | &:nth-child(3){
68 | width: 20%;
69 | }
70 | &:nth-child(4){
71 | width: 10%;
72 | }
73 | &:nth-child(5) {
74 | width: 5%;
75 | }
76 | }
77 | }
78 | }
79 | .order-submit {
80 | //margin-bottom: 15px;
81 | //position: absolute;
82 | //bottom: 0;
83 | //left: 0;
84 | button[type=submit] {
85 | height: $orderButtonHeight * 2;
86 | font-size: $fontValue;
87 | }
88 |
89 | select[name=table] {
90 | height: $orderButtonHeight;
91 | }
92 |
93 | .total{
94 | > div {
95 | height: $orderButtonHeight;
96 | }
97 | .title{
98 | font-size: $fontValue;
99 | padding: 6px;
100 | color: #333;
101 | background-color: #f5f5f5;
102 | border-color: #ddd;
103 | border-bottom: 1px solid transparent;
104 | border-top-left-radius: 3px;
105 | border-top-right-radius: 3px;
106 | }
107 | .value {
108 | font-size: $fontValue;
109 | padding: 6px;
110 | }
111 |
112 | }
113 | }
114 | }
115 | }
116 | }
117 | }
118 | }
119 |
--------------------------------------------------------------------------------
/wPOS/src/styles/variables/button.scss:
--------------------------------------------------------------------------------
1 | $orderButtonHeight: 46px;
--------------------------------------------------------------------------------
/wPOS/src/styles/variables/font.scss:
--------------------------------------------------------------------------------
1 | $fontTitle: 36px;
2 | $fontValue: 24px;
3 | $tableFont: 20px;
--------------------------------------------------------------------------------