├── .gitignore ├── .metadata ├── README.md ├── assets ├── data │ ├── discount.json │ ├── goods_179.json │ ├── goods_32.json │ ├── goods_33.json │ ├── goods_34.json │ ├── goods_35.json │ ├── goods_36.json │ ├── goods_37.json │ ├── goods_38.json │ ├── goods_39.json │ ├── goods_40.json │ ├── goods_43.json │ ├── goods_45.json │ ├── goods_46.json │ ├── goods_47.json │ ├── goods_48.json │ ├── goods_49.json │ ├── goods_50.json │ ├── goods_51.json │ ├── property.json │ └── sort.json ├── fonts │ └── DIN-Regular.otf └── images │ ├── empty.png │ ├── ic_add.png │ ├── ic_back_black.png │ ├── ic_clear.png │ ├── ic_reduce.png │ ├── none.png │ ├── order_cart.png │ ├── order_cart_empty.png │ ├── order_delete.png │ └── order_search.png ├── lib ├── common │ └── common.dart ├── entity │ ├── base_entity.dart │ ├── cart_goods_bean.dart │ ├── discount_packages.dart │ ├── goods.dart │ ├── property.dart │ └── sort.dart ├── entity_factory.dart ├── main.dart ├── provider │ ├── base_list_provider.dart │ └── theme_provider.dart ├── res │ ├── colors.dart │ ├── dimens.dart │ ├── gaps.dart │ ├── resources.dart │ └── styles.dart ├── ui │ ├── home_page.dart │ └── order │ │ ├── pages │ │ ├── discount_page.dart │ │ ├── goods_detail_page.dart │ │ ├── order_page.dart │ │ ├── sort_page.dart │ │ ├── sort_right_page.dart │ │ ├── sub_item_page.dart │ │ └── sub_list_page.dart │ │ ├── provider │ │ ├── ball_anim_provider.dart │ │ └── order_provider.dart │ │ └── widgets │ │ ├── goods_item.dart │ │ ├── goods_options.dart │ │ ├── goods_type_dialog.dart │ │ ├── shop_cart.dart │ │ ├── shop_cart_list.dart │ │ └── throw_ball_anim.dart ├── util │ ├── color_utils.dart │ ├── image_utils.dart │ ├── log_utils.dart │ ├── navigator_utils.dart │ ├── theme_utils.dart │ ├── toast.dart │ └── utils.dart └── widgets │ ├── 404.dart │ ├── label_text.dart │ ├── load_image.dart │ ├── md2_tab_indicator.dart │ ├── preferred_hero_img_bar.dart │ ├── progress_dialog.dart │ ├── search_bar.dart │ ├── state_layout.dart │ └── tool_bar.dart ├── order.iml ├── order_android.iml ├── pubspec.lock ├── pubspec.yaml └── test └── widget_test.dart /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .dart_tool/ 3 | 4 | .packages 5 | .pub/ 6 | 7 | .idea/ 8 | .vagrant/ 9 | .sconsign.dblite 10 | .svn/ 11 | 12 | *.swp 13 | profile 14 | 15 | DerivedData/ 16 | 17 | .generated/ 18 | 19 | *.pbxuser 20 | *.mode1v3 21 | *.mode2v3 22 | *.perspectivev3 23 | 24 | !default.pbxuser 25 | !default.mode1v3 26 | !default.mode2v3 27 | !default.perspectivev3 28 | 29 | xcuserdata 30 | 31 | *.moved-aside 32 | 33 | *.pyc 34 | *sync/ 35 | Icon? 36 | .tags* 37 | 38 | build/ 39 | .android/ 40 | .ios/ 41 | .flutter-plugins 42 | .flutter-plugins-dependencies 43 | 44 | # Symbolication related 45 | app.*.symbols 46 | 47 | # Obfuscation related 48 | app.*.map.json 49 | -------------------------------------------------------------------------------- /.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: f7a6a7906be96d2288f5d63a5a54c515a6e987fe 8 | channel: stable 9 | 10 | project_type: module 11 | -------------------------------------------------------------------------------- /assets/data/discount.json: -------------------------------------------------------------------------------- 1 | { 2 | "code": 200, 3 | "message": "success", 4 | "data": [ 5 | { 6 | "id": 33, 7 | "cid": "3300001", 8 | "name": "五一优惠套餐", 9 | "price": 99.0, 10 | "img": "https://p0.meituan.net/208.126/deal/972824cc8e129186c24682b12a4b45333304322.jpg", 11 | "type": 2, 12 | "description": null, 13 | "ableDiscount": 0, 14 | "ableReturn": 0, 15 | "isLowConsume": 1, 16 | "propertyIds": "", 17 | "propertyList": null, 18 | "detailsList": null 19 | }, 20 | { 21 | "id": 34, 22 | "cid": "3300001", 23 | "name": "哈啤一打", 24 | "price": 60.0, 25 | "img": "https://img10.360buyimg.com/n7/jfs/t1/31891/27/7984/329941/5c9b10d3Ebbf7644e/f1576fae49a1fada.jpg", 26 | "type": 2, 27 | "description": null, 28 | "ableDiscount": 0, 29 | "ableReturn": 0, 30 | "isLowConsume": 1, 31 | "propertyIds": "14,15", 32 | "propertyList": [ 33 | { 34 | "id": 14, 35 | "cid": "3300001", 36 | "propertyName": "冰", 37 | "parentId": 13 38 | }, 39 | { 40 | "id": 15, 41 | "cid": "3300001", 42 | "propertyName": "常温", 43 | "parentId": 13 44 | } 45 | ], 46 | "detailsList": null 47 | } 48 | ] 49 | } -------------------------------------------------------------------------------- /assets/data/goods_33.json: -------------------------------------------------------------------------------- 1 | { 2 | "code": 200, 3 | "message": "success", 4 | "data": [ 5 | { 6 | "id": 25, 7 | "cid": "3300001", 8 | "goodsName": "蓝带马爹利", 9 | "price": 2880.0, 10 | "selfPrice": 0.0, 11 | "unit": "瓶", 12 | "typeId": 33, 13 | "typeName": "白兰地", 14 | "printId": 6, 15 | "printName": "吧台出品", 16 | "propertyIds": "15", 17 | "img": "https://img14.360buyimg.com/n7/jfs/t1/39420/35/10769/106134/5d1ed05eE1c48cb2e/5953e250fcf0ee96.jpg", 18 | "description": null, 19 | "stock": 11059, 20 | "ableDiscount": 1, 21 | "ableReturn": 1, 22 | "isLowConsume": 1, 23 | "storeDay": 100, 24 | "storeMinCount": 0.1, 25 | "isWholeStore": 2, 26 | "delayDays": "100", 27 | "maxDelayCount": 100, 28 | "status": 1, 29 | "propertyList": [ 30 | { 31 | "id": 15, 32 | "cid": "3300001", 33 | "propertyName": "常温", 34 | "parentId": 13 35 | } 36 | ] 37 | }, 38 | { 39 | "id": 24, 40 | "cid": "3300001", 41 | "goodsName": "马爹利XO", 42 | "price": 2880.0, 43 | "selfPrice": 1800.0, 44 | "unit": "瓶", 45 | "typeId": 33, 46 | "typeName": "白兰地", 47 | "printId": 6, 48 | "printName": "吧台出品", 49 | "propertyIds": "15", 50 | "img": "https://img11.360buyimg.com/n7/jfs/t1/41980/10/12377/106772/5d5d0d0aEa1b68f4c/d4c954f14510327c.jpg", 51 | "description": null, 52 | "stock": 11110, 53 | "ableDiscount": 1, 54 | "ableReturn": 1, 55 | "isLowConsume": 1, 56 | "storeDay": 100, 57 | "storeMinCount": 0.1, 58 | "isWholeStore": 2, 59 | "delayDays": "100", 60 | "maxDelayCount": 100, 61 | "status": 1, 62 | "propertyList": [ 63 | { 64 | "id": 15, 65 | "cid": "3300001", 66 | "propertyName": "常温", 67 | "parentId": 13 68 | } 69 | ] 70 | }, 71 | { 72 | "id": 33, 73 | "cid": "3300001", 74 | "goodsName": "尚.马爹利至尊", 75 | "price": 36800.0, 76 | "selfPrice": null, 77 | "unit": "瓶", 78 | "typeId": 33, 79 | "typeName": "白兰地", 80 | "printId": 6, 81 | "printName": "吧台出品", 82 | "propertyIds": "", 83 | "img": "https://img13.360buyimg.com/n7/jfs/t18073/353/1264069463/286511/6e568b58/5ac2f35dN7683ac82.jpg", 84 | "description": null, 85 | "stock": 9990, 86 | "ableDiscount": 1, 87 | "ableReturn": 1, 88 | "isLowConsume": 1, 89 | "storeDay": 100, 90 | "storeMinCount": 0.1, 91 | "isWholeStore": 2, 92 | "delayDays": "100", 93 | "maxDelayCount": 10, 94 | "status": 1, 95 | "propertyList": null 96 | }, 97 | { 98 | "id": 36, 99 | "cid": "3300001", 100 | "goodsName": "名仕马爹利", 101 | "price": 1580.0, 102 | "selfPrice": null, 103 | "unit": "瓶", 104 | "typeId": 33, 105 | "typeName": "白兰地", 106 | "printId": 6, 107 | "printName": "吧台出品", 108 | "propertyIds": "", 109 | "img": "https://img14.360buyimg.com/n7/jfs/t1/116340/40/1541/164870/5e9980fdE5089a624/47002bd09f765d5a.jpg", 110 | "description": null, 111 | "stock": 9999, 112 | "ableDiscount": 1, 113 | "ableReturn": 1, 114 | "isLowConsume": 1, 115 | "storeDay": 100, 116 | "storeMinCount": 0.1, 117 | "isWholeStore": 2, 118 | "delayDays": "100", 119 | "maxDelayCount": 10, 120 | "status": 1, 121 | "propertyList": null 122 | }, 123 | { 124 | "id": 37, 125 | "cid": "3300001", 126 | "goodsName": "轩尼诗XO", 127 | "price": 3080.0, 128 | "selfPrice": null, 129 | "unit": "瓶", 130 | "typeId": 33, 131 | "typeName": "白兰地", 132 | "printId": 6, 133 | "printName": "吧台出品", 134 | "propertyIds": "", 135 | "img": "https://img10.360buyimg.com/n7/jfs/t1/80813/1/15326/97281/5dc96c48E40a4e15a/15b429898f1efeb6.jpg", 136 | "description": null, 137 | "stock": 9996, 138 | "ableDiscount": 1, 139 | "ableReturn": 1, 140 | "isLowConsume": 1, 141 | "storeDay": 100, 142 | "storeMinCount": 0.1, 143 | "isWholeStore": 2, 144 | "delayDays": "100", 145 | "maxDelayCount": 10, 146 | "status": 1, 147 | "propertyList": null 148 | }, 149 | { 150 | "id": 38, 151 | "cid": "3300001", 152 | "goodsName": "轩尼诗VSOP", 153 | "price": 1680.0, 154 | "selfPrice": null, 155 | "unit": "瓶", 156 | "typeId": 33, 157 | "typeName": "白兰地", 158 | "printId": 6, 159 | "printName": "吧台出品", 160 | "propertyIds": "", 161 | "img": "https://img10.360buyimg.com/n7/jfs/t1/125703/36/416/102296/5eb510a8Ed9c91402/4a621e3622e16dfd.jpg", 162 | "description": null, 163 | "stock": 9996, 164 | "ableDiscount": 1, 165 | "ableReturn": 1, 166 | "isLowConsume": 1, 167 | "storeDay": 100, 168 | "storeMinCount": 0.1, 169 | "isWholeStore": 2, 170 | "delayDays": "100", 171 | "maxDelayCount": 10, 172 | "status": 1, 173 | "propertyList": null 174 | }, 175 | { 176 | "id": 39, 177 | "cid": "3300001", 178 | "goodsName": "轩尼诗李察", 179 | "price": 36800.0, 180 | "selfPrice": null, 181 | "unit": "瓶", 182 | "typeId": 33, 183 | "typeName": "白兰地", 184 | "printId": 6, 185 | "printName": "吧台出品", 186 | "propertyIds": "", 187 | "img": "https://img12.360buyimg.com/n7/jfs/t18283/277/933818793/257221/522c717a/5ab36fbdNe752bf18.jpg", 188 | "description": null, 189 | "stock": 9998, 190 | "ableDiscount": 1, 191 | "ableReturn": 1, 192 | "isLowConsume": 1, 193 | "storeDay": 100, 194 | "storeMinCount": 0.1, 195 | "isWholeStore": 2, 196 | "delayDays": "100", 197 | "maxDelayCount": 10, 198 | "status": 1, 199 | "propertyList": null 200 | }, 201 | { 202 | "id": 40, 203 | "cid": "3300001", 204 | "goodsName": "人头马路易十三", 205 | "price": 36800.0, 206 | "selfPrice": null, 207 | "unit": "瓶", 208 | "typeId": 33, 209 | "typeName": "白兰地", 210 | "printId": 6, 211 | "printName": "吧台出品", 212 | "propertyIds": "", 213 | "img": "https://img12.360buyimg.com/n7/jfs/t1/25917/6/10594/85685/5c88621eEd27d0d83/8772f637e4edf2d2.jpg", 214 | "description": null, 215 | "stock": 9998, 216 | "ableDiscount": 1, 217 | "ableReturn": 1, 218 | "isLowConsume": 1, 219 | "storeDay": 100, 220 | "storeMinCount": 0.1, 221 | "isWholeStore": 2, 222 | "delayDays": "100", 223 | "maxDelayCount": 10, 224 | "status": 1, 225 | "propertyList": null 226 | }, 227 | { 228 | "id": 41, 229 | "cid": "3300001", 230 | "goodsName": "人头马XO", 231 | "price": 3080.0, 232 | "selfPrice": null, 233 | "unit": "瓶", 234 | "typeId": 33, 235 | "typeName": "白兰地", 236 | "printId": 6, 237 | "printName": "吧台出品", 238 | "propertyIds": "", 239 | "img": "https://img13.360buyimg.com/n7/jfs/t6202/188/1349717195/124428/d1591058/594fd7eaN9f5ab578.jpg", 240 | "description": null, 241 | "stock": 9998, 242 | "ableDiscount": 1, 243 | "ableReturn": 1, 244 | "isLowConsume": 1, 245 | "storeDay": 100, 246 | "storeMinCount": 0.1, 247 | "isWholeStore": 2, 248 | "delayDays": "100", 249 | "maxDelayCount": 10, 250 | "status": 1, 251 | "propertyList": null 252 | }, 253 | { 254 | "id": 42, 255 | "cid": "3300001", 256 | "goodsName": "人头马V.S.O.P", 257 | "price": 1080.0, 258 | "selfPrice": null, 259 | "unit": "瓶", 260 | "typeId": 33, 261 | "typeName": "白兰地", 262 | "printId": 6, 263 | "printName": "吧台出品", 264 | "propertyIds": "", 265 | "img": "https://img12.360buyimg.com/n7/jfs/t1/50389/36/7786/321540/5d561738Ede7970e2/81f0bb0f1d51264e.jpg", 266 | "description": null, 267 | "stock": 9998, 268 | "ableDiscount": 1, 269 | "ableReturn": 1, 270 | "isLowConsume": 1, 271 | "storeDay": 100, 272 | "storeMinCount": 0.1, 273 | "isWholeStore": 2, 274 | "delayDays": "100", 275 | "maxDelayCount": 10, 276 | "status": 1, 277 | "propertyList": null 278 | } 279 | ] 280 | } -------------------------------------------------------------------------------- /assets/data/goods_34.json: -------------------------------------------------------------------------------- 1 | { 2 | "code": 200, 3 | "message": "success", 4 | "data": [ 5 | { 6 | "id": 43, 7 | "cid": "3300001", 8 | "goodsName": "皇家礼炮21年", 9 | "price": 2880.0, 10 | "selfPrice": null, 11 | "unit": "瓶", 12 | "typeId": 34, 13 | "typeName": "威士忌", 14 | "printId": 6, 15 | "printName": "吧台出品", 16 | "propertyIds": "", 17 | "img": "https://img14.360buyimg.com/n7/jfs/t1/10295/33/9174/162398/5c3d5fe1E4ec6d88d/4c521b21d47bce53.jpg", 18 | "description": null, 19 | "stock": 9995, 20 | "ableDiscount": 1, 21 | "ableReturn": 1, 22 | "isLowConsume": 1, 23 | "storeDay": 100, 24 | "storeMinCount": 0.1, 25 | "isWholeStore": 2, 26 | "delayDays": "100", 27 | "maxDelayCount": 10, 28 | "status": 1, 29 | "propertyList": null 30 | }, 31 | { 32 | "id": 44, 33 | "cid": "3300001", 34 | "goodsName": "芝华士12年", 35 | "price": 980.0, 36 | "selfPrice": null, 37 | "unit": "瓶", 38 | "typeId": 34, 39 | "typeName": "威士忌", 40 | "printId": 6, 41 | "printName": "吧台出品", 42 | "propertyIds": "", 43 | "img": "https://img12.360buyimg.com/n7/jfs/t1/34568/39/5892/74753/5cc150feE541f6b36/fe55ceb438016af0.jpg", 44 | "description": null, 45 | "stock": 9997, 46 | "ableDiscount": 1, 47 | "ableReturn": 1, 48 | "isLowConsume": 1, 49 | "storeDay": 100, 50 | "storeMinCount": 0.1, 51 | "isWholeStore": 2, 52 | "delayDays": "100", 53 | "maxDelayCount": 10, 54 | "status": 1, 55 | "propertyList": null 56 | }, 57 | { 58 | "id": 45, 59 | "cid": "3300001", 60 | "goodsName": "芝华士18年", 61 | "price": 1580.0, 62 | "selfPrice": null, 63 | "unit": "瓶", 64 | "typeId": 34, 65 | "typeName": "威士忌", 66 | "printId": 6, 67 | "printName": "吧台出品", 68 | "propertyIds": "", 69 | "img": "https://img12.360buyimg.com/n7/jfs/t1/11007/35/3100/86831/5c1b19a5E58e36ded/16499940a40a4913.jpg", 70 | "description": null, 71 | "stock": 9998, 72 | "ableDiscount": 1, 73 | "ableReturn": 1, 74 | "isLowConsume": 1, 75 | "storeDay": 100, 76 | "storeMinCount": 0.1, 77 | "isWholeStore": 2, 78 | "delayDays": "100", 79 | "maxDelayCount": 10, 80 | "status": 1, 81 | "propertyList": null 82 | }, 83 | { 84 | "id": 46, 85 | "cid": "3300001", 86 | "goodsName": "尊尼获加蓝牌", 87 | "price": 3880.0, 88 | "selfPrice": null, 89 | "unit": "瓶", 90 | "typeId": 34, 91 | "typeName": "威士忌", 92 | "printId": 6, 93 | "printName": "吧台出品", 94 | "propertyIds": "", 95 | "img": "https://img10.360buyimg.com/n7/jfs/t19717/259/835790703/102674/c33adfdb/5aaa27bdNa8a7619a.jpg", 96 | "description": null, 97 | "stock": 9998, 98 | "ableDiscount": 1, 99 | "ableReturn": 1, 100 | "isLowConsume": 1, 101 | "storeDay": 100, 102 | "storeMinCount": 0.1, 103 | "isWholeStore": 2, 104 | "delayDays": "100", 105 | "maxDelayCount": 10, 106 | "status": 1, 107 | "propertyList": null 108 | }, 109 | { 110 | "id": 47, 111 | "cid": "3300001", 112 | "goodsName": "尊尼获加黑牌", 113 | "price": 980.0, 114 | "selfPrice": null, 115 | "unit": "瓶", 116 | "typeId": 34, 117 | "typeName": "威士忌", 118 | "printId": 6, 119 | "printName": "吧台出品", 120 | "propertyIds": "", 121 | "img": "https://img10.360buyimg.com/n7/jfs/t17407/188/2523751490/93916/55e41420/5af9628bNeb291a53.jpg", 122 | "description": null, 123 | "stock": 9998, 124 | "ableDiscount": 1, 125 | "ableReturn": 1, 126 | "isLowConsume": 1, 127 | "storeDay": 100, 128 | "storeMinCount": 0.1, 129 | "isWholeStore": 2, 130 | "delayDays": "100", 131 | "maxDelayCount": 10, 132 | "status": 1, 133 | "propertyList": null 134 | }, 135 | { 136 | "id": 48, 137 | "cid": "3300001", 138 | "goodsName": "杰克丹尼", 139 | "price": 780.0, 140 | "selfPrice": null, 141 | "unit": "瓶", 142 | "typeId": 34, 143 | "typeName": "威士忌", 144 | "printId": 6, 145 | "printName": "吧台出品", 146 | "propertyIds": "", 147 | "img": "https://img10.360buyimg.com/n7/g14/M03/05/1A/rBEhVlHnU9EIAAAAAAE2r4wpw1oAABI_gJYRrIAATbH261.jpg", 148 | "description": null, 149 | "stock": 9998, 150 | "ableDiscount": 1, 151 | "ableReturn": 1, 152 | "isLowConsume": 1, 153 | "storeDay": 100, 154 | "storeMinCount": 0.1, 155 | "isWholeStore": 2, 156 | "delayDays": "30", 157 | "maxDelayCount": 1, 158 | "status": 1, 159 | "propertyList": null 160 | }, 161 | { 162 | "id": 49, 163 | "cid": "3300001", 164 | "goodsName": "百龄坛12年", 165 | "price": 780.0, 166 | "selfPrice": null, 167 | "unit": "瓶", 168 | "typeId": 34, 169 | "typeName": "威士忌", 170 | "printId": 6, 171 | "printName": "吧台出品", 172 | "propertyIds": "", 173 | "img": "https://img12.360buyimg.com/n7/jfs/t1/27284/16/5400/109853/5c3d531eE385988c4/65a6f365a835bf9a.jpg", 174 | "description": null, 175 | "stock": 9999, 176 | "ableDiscount": 1, 177 | "ableReturn": 1, 178 | "isLowConsume": 1, 179 | "storeDay": 100, 180 | "storeMinCount": 0.1, 181 | "isWholeStore": 2, 182 | "delayDays": "30", 183 | "maxDelayCount": 1, 184 | "status": 1, 185 | "propertyList": null 186 | } 187 | ] 188 | } -------------------------------------------------------------------------------- /assets/data/goods_35.json: -------------------------------------------------------------------------------- 1 | { 2 | "code": 200, 3 | "message": "success", 4 | "data": [ 5 | { 6 | "id": 27, 7 | "cid": "3300001", 8 | "goodsName": "绝对伏特加", 9 | "price": 780.0, 10 | "selfPrice": 0.0, 11 | "unit": null, 12 | "typeId": 35, 13 | "typeName": "伏特加", 14 | "printId": 6, 15 | "printName": "吧台出品", 16 | "propertyIds": "", 17 | "img": "https://img12.360buyimg.com/n7/jfs/t28867/149/774124075/68040/9b167fb5/5bfdf497Nba73f75f.jpg", 18 | "description": null, 19 | "stock": 999993, 20 | "ableDiscount": 1, 21 | "ableReturn": 1, 22 | "isLowConsume": 1, 23 | "storeDay": 100, 24 | "storeMinCount": 0.1, 25 | "isWholeStore": 2, 26 | "delayDays": "100", 27 | "maxDelayCount": 10, 28 | "status": 1, 29 | "propertyList": null 30 | }, 31 | { 32 | "id": 51, 33 | "cid": "3300001", 34 | "goodsName": "皇冠伏特加", 35 | "price": 680.0, 36 | "selfPrice": null, 37 | "unit": "瓶", 38 | "typeId": 35, 39 | "typeName": "伏特加", 40 | "printId": 6, 41 | "printName": "吧台出品", 42 | "propertyIds": "", 43 | "img": "https://img11.360buyimg.com/n7/jfs/t1/101092/28/372/43279/5dac2a31Eebbda525/f1b8929d53bd3014.jpg", 44 | "description": null, 45 | "stock": 9999, 46 | "ableDiscount": 1, 47 | "ableReturn": 1, 48 | "isLowConsume": 1, 49 | "storeDay": 30, 50 | "storeMinCount": 1.0, 51 | "isWholeStore": 1, 52 | "delayDays": "15", 53 | "maxDelayCount": 1, 54 | "status": 1, 55 | "propertyList": null 56 | }, 57 | { 58 | "id": 52, 59 | "cid": "3300001", 60 | "goodsName": "瑞典伏特加", 61 | "price": 680.0, 62 | "selfPrice": null, 63 | "unit": "瓶", 64 | "typeId": 35, 65 | "typeName": "伏特加", 66 | "printId": 6, 67 | "printName": "吧台出品", 68 | "propertyIds": "", 69 | "img": "https://img13.360buyimg.com/n7/jfs/t1/6607/19/13905/52938/5c46bca8Ed60dde38/d47cb7b0f12cf9e7.jpg", 70 | "description": null, 71 | "stock": 9999, 72 | "ableDiscount": 1, 73 | "ableReturn": 1, 74 | "isLowConsume": 1, 75 | "storeDay": 30, 76 | "storeMinCount": 1.0, 77 | "isWholeStore": 1, 78 | "delayDays": "15", 79 | "maxDelayCount": 1, 80 | "status": 1, 81 | "propertyList": null 82 | } 83 | ] 84 | } -------------------------------------------------------------------------------- /assets/data/goods_36.json: -------------------------------------------------------------------------------- 1 | { 2 | "code": 200, 3 | "message": "success", 4 | "data": [ 5 | { 6 | "id": 54, 7 | "cid": "3300001", 8 | "goodsName": "百利甜酒", 9 | "price": 780.0, 10 | "selfPrice": null, 11 | "unit": "瓶", 12 | "typeId": 36, 13 | "typeName": "力乔酒", 14 | "printId": 6, 15 | "printName": "吧台出品", 16 | "propertyIds": "", 17 | "img": "https://img13.360buyimg.com/n7/jfs/t1/99787/24/7047/58488/5df8397fE3bc98886/4ee2896480163f35.jpg", 18 | "description": null, 19 | "stock": 9996, 20 | "ableDiscount": 1, 21 | "ableReturn": 1, 22 | "isLowConsume": 1, 23 | "storeDay": 100, 24 | "storeMinCount": 1.0, 25 | "isWholeStore": 2, 26 | "delayDays": "20", 27 | "maxDelayCount": 1, 28 | "status": 1, 29 | "propertyList": null 30 | } 31 | ] 32 | } -------------------------------------------------------------------------------- /assets/data/goods_37.json: -------------------------------------------------------------------------------- 1 | { 2 | "code": 200, 3 | "message": "success", 4 | "data": [ 5 | { 6 | "id": 30, 7 | "cid": "3300001", 8 | "goodsName": "酩悦香槟", 9 | "price": 1280.0, 10 | "selfPrice": 0.0, 11 | "unit": null, 12 | "typeId": 37, 13 | "typeName": "香槟", 14 | "printId": 6, 15 | "printName": "吧台出品", 16 | "propertyIds": "", 17 | "img": "https://img12.360buyimg.com/n7/jfs/t1/97836/39/18022/110286/5e8ffd0eE766b90a6/ea2c5c39665949a1.jpg", 18 | "description": null, 19 | "stock": 999, 20 | "ableDiscount": 1, 21 | "ableReturn": 1, 22 | "isLowConsume": 1, 23 | "storeDay": 100, 24 | "storeMinCount": 1.0, 25 | "isWholeStore": 1, 26 | "delayDays": "100", 27 | "maxDelayCount": 10, 28 | "status": 1, 29 | "propertyList": null 30 | }, 31 | { 32 | "id": 71, 33 | "cid": "3300001", 34 | "goodsName": "干露酒厂SBX莫斯卡托起泡葡萄酒", 35 | "price": 580.0, 36 | "selfPrice": null, 37 | "unit": "瓶", 38 | "typeId": 37, 39 | "typeName": "香槟", 40 | "printId": 6, 41 | "printName": "吧台出品", 42 | "propertyIds": "", 43 | "img": "https://img14.360buyimg.com/n7/jfs/t1/56914/1/11915/179487/5d8c83e6Efaf92faa/7f0b9642fae31a36.jpg", 44 | "description": null, 45 | "stock": 9999, 46 | "ableDiscount": 1, 47 | "ableReturn": 1, 48 | "isLowConsume": 1, 49 | "storeDay": 30, 50 | "storeMinCount": 1.0, 51 | "isWholeStore": 1, 52 | "delayDays": "10", 53 | "maxDelayCount": 1, 54 | "status": 1, 55 | "propertyList": null 56 | }, 57 | { 58 | "id": 72, 59 | "cid": "3300001", 60 | "goodsName": "威龙大香槟", 61 | "price": 380.0, 62 | "selfPrice": null, 63 | "unit": "瓶", 64 | "typeId": 37, 65 | "typeName": "香槟", 66 | "printId": 6, 67 | "printName": "吧台出品", 68 | "propertyIds": "", 69 | "img": "https://img14.360buyimg.com/n7/jfs/t18304/211/1124844369/237578/16c56c54/5abd1557N6ee4d553.jpg", 70 | "description": null, 71 | "stock": 9999, 72 | "ableDiscount": 1, 73 | "ableReturn": 1, 74 | "isLowConsume": 1, 75 | "storeDay": 30, 76 | "storeMinCount": 30.0, 77 | "isWholeStore": 1, 78 | "delayDays": "10", 79 | "maxDelayCount": 1, 80 | "status": 1, 81 | "propertyList": null 82 | }, 83 | { 84 | "id": 74, 85 | "cid": "3300001", 86 | "goodsName": "公司香槟", 87 | "price": 280.0, 88 | "selfPrice": null, 89 | "unit": "", 90 | "typeId": 37, 91 | "typeName": "香槟", 92 | "printId": 6, 93 | "printName": "吧台出品", 94 | "propertyIds": "", 95 | "img": "https://img14.360buyimg.com/n7/jfs/t1/103090/28/10138/124042/5e17d944E81f474dd/dc9e89a2085ab6b1.jpg", 96 | "description": null, 97 | "stock": 9999, 98 | "ableDiscount": 1, 99 | "ableReturn": 1, 100 | "isLowConsume": 1, 101 | "storeDay": 30, 102 | "storeMinCount": 1.0, 103 | "isWholeStore": 1, 104 | "delayDays": "10", 105 | "maxDelayCount": 1, 106 | "status": 1, 107 | "propertyList": null 108 | } 109 | ] 110 | } -------------------------------------------------------------------------------- /assets/data/goods_38.json: -------------------------------------------------------------------------------- 1 | { 2 | "code": 200, 3 | "message": "success", 4 | "data": [ 5 | { 6 | "id": 28, 7 | "cid": "3300001", 8 | "goodsName": "哥顿", 9 | "price": 500.0, 10 | "selfPrice": 0.0, 11 | "unit": null, 12 | "typeId": 38, 13 | "typeName": "金酒", 14 | "printId": 6, 15 | "printName": "吧台出品", 16 | "propertyIds": "", 17 | "img": "https://img14.360buyimg.com/n7/jfs/t1/7731/39/938/195087/5bcc2e8fE24f39ce4/987344d2f1898ac3.jpg", 18 | "description": null, 19 | "stock": 87, 20 | "ableDiscount": 1, 21 | "ableReturn": 1, 22 | "isLowConsume": 1, 23 | "storeDay": 100, 24 | "storeMinCount": 0.1, 25 | "isWholeStore": 2, 26 | "delayDays": "100", 27 | "maxDelayCount": 10, 28 | "status": 1, 29 | "propertyList": null 30 | } 31 | ] 32 | } -------------------------------------------------------------------------------- /assets/data/goods_39.json: -------------------------------------------------------------------------------- 1 | { 2 | "code": 200, 3 | "message": "success", 4 | "data": [ 5 | { 6 | "id": 32, 7 | "cid": "3300001", 8 | "goodsName": "百威", 9 | "price": 45.0, 10 | "selfPrice": 0.0, 11 | "unit": null, 12 | "typeId": 39, 13 | "typeName": "啤酒", 14 | "printId": 6, 15 | "printName": "吧台出品", 16 | "propertyIds": "", 17 | "img": "https://img12.360buyimg.com/n7/jfs/t1/92494/24/9645/150559/5e11adc2E75aedde2/aead3a282f46cad7.jpg", 18 | "description": null, 19 | "stock": 987, 20 | "ableDiscount": 1, 21 | "ableReturn": 1, 22 | "isLowConsume": 1, 23 | "storeDay": 7, 24 | "storeMinCount": 7.0, 25 | "isWholeStore": 1, 26 | "delayDays": "3", 27 | "maxDelayCount": 10, 28 | "status": 1, 29 | "propertyList": null 30 | }, 31 | { 32 | "id": 31, 33 | "cid": "3300001", 34 | "goodsName": "喜力", 35 | "price": 48.0, 36 | "selfPrice": 0.0, 37 | "unit": null, 38 | "typeId": 39, 39 | "typeName": "啤酒", 40 | "printId": 6, 41 | "printName": "吧台出品", 42 | "propertyIds": "", 43 | "img": "https://img11.360buyimg.com/n7/jfs/t1/100627/35/1475/189439/5dbfd5cfE59aa147b/ce9d29ec6f2444d1.jpg", 44 | "description": null, 45 | "stock": 9950, 46 | "ableDiscount": 1, 47 | "ableReturn": 1, 48 | "isLowConsume": 1, 49 | "storeDay": 7, 50 | "storeMinCount": 10.0, 51 | "isWholeStore": 1, 52 | "delayDays": "3", 53 | "maxDelayCount": 10, 54 | "status": 1, 55 | "propertyList": null 56 | }, 57 | { 58 | "id": 77, 59 | "cid": "3300001", 60 | "goodsName": "青岛纯生", 61 | "price": 35.0, 62 | "selfPrice": null, 63 | "unit": "", 64 | "typeId": 39, 65 | "typeName": "啤酒", 66 | "printId": 6, 67 | "printName": "吧台出品", 68 | "propertyIds": "", 69 | "img": "https://img10.360buyimg.com/n7/jfs/t1/90065/36/10932/431666/5e255af3E18fd3793/610898b78a3a52c2.jpg", 70 | "description": null, 71 | "stock": 9999, 72 | "ableDiscount": 1, 73 | "ableReturn": 1, 74 | "isLowConsume": 1, 75 | "storeDay": 7, 76 | "storeMinCount": 7.0, 77 | "isWholeStore": 1, 78 | "delayDays": "3", 79 | "maxDelayCount": 1, 80 | "status": 1, 81 | "propertyList": null 82 | }, 83 | { 84 | "id": 78, 85 | "cid": "3300001", 86 | "goodsName": "青岛", 87 | "price": 35.0, 88 | "selfPrice": null, 89 | "unit": "", 90 | "typeId": 39, 91 | "typeName": "啤酒", 92 | "printId": 6, 93 | "printName": "吧台出品", 94 | "propertyIds": "", 95 | "img": "https://img14.360buyimg.com/n7/jfs/t1/55320/29/602/122474/5ce74e38E4a212240/ed12b794e92653c5.jpg", 96 | "description": null, 97 | "stock": 9996, 98 | "ableDiscount": 1, 99 | "ableReturn": 1, 100 | "isLowConsume": 1, 101 | "storeDay": 7, 102 | "storeMinCount": 7.0, 103 | "isWholeStore": 1, 104 | "delayDays": "3", 105 | "maxDelayCount": 1, 106 | "status": 1, 107 | "propertyList": null 108 | }, 109 | { 110 | "id": 79, 111 | "cid": "3300001", 112 | "goodsName": "哈尔滨", 113 | "price": 30.0, 114 | "selfPrice": null, 115 | "unit": "", 116 | "typeId": 39, 117 | "typeName": "啤酒", 118 | "printId": 6, 119 | "printName": "吧台出品", 120 | "propertyIds": "", 121 | "img": "https://img13.360buyimg.com/n7/jfs/t1/35341/40/8060/182235/5cd531ebE38920bf0/b9cee24937294429.jpg", 122 | "description": null, 123 | "stock": 9997, 124 | "ableDiscount": 1, 125 | "ableReturn": 1, 126 | "isLowConsume": 1, 127 | "storeDay": 7, 128 | "storeMinCount": 7.0, 129 | "isWholeStore": 1, 130 | "delayDays": "3", 131 | "maxDelayCount": 1, 132 | "status": 1, 133 | "propertyList": null 134 | }, 135 | { 136 | "id": 80, 137 | "cid": "3300001", 138 | "goodsName": "百威纯生", 139 | "price": 35.0, 140 | "selfPrice": null, 141 | "unit": "", 142 | "typeId": 39, 143 | "typeName": "啤酒", 144 | "printId": 6, 145 | "printName": "吧台出品", 146 | "propertyIds": "", 147 | "img": "https://img13.360buyimg.com/n7/jfs/t1/109869/12/13474/711365/5e9fea76Eaeaeffa6/cd2565f52566b770.png", 148 | "description": null, 149 | "stock": 9997, 150 | "ableDiscount": 1, 151 | "ableReturn": 1, 152 | "isLowConsume": 1, 153 | "storeDay": 7, 154 | "storeMinCount": 7.0, 155 | "isWholeStore": 1, 156 | "delayDays": "3", 157 | "maxDelayCount": 1, 158 | "status": 1, 159 | "propertyList": null 160 | }, 161 | { 162 | "id": 81, 163 | "cid": "3300001", 164 | "goodsName": "千岛湖原生态", 165 | "price": 42.0, 166 | "selfPrice": null, 167 | "unit": "瓶", 168 | "typeId": 39, 169 | "typeName": "啤酒", 170 | "printId": 6, 171 | "printName": "吧台出品", 172 | "propertyIds": "", 173 | "img": "https://img10.360buyimg.com/n7/jfs/t1/15696/15/12789/376624/5c9b1445Eb6dcb44b/579b5601b2c62002.jpg", 174 | "description": null, 175 | "stock": 9998, 176 | "ableDiscount": 1, 177 | "ableReturn": 1, 178 | "isLowConsume": 1, 179 | "storeDay": 7, 180 | "storeMinCount": 7.0, 181 | "isWholeStore": 1, 182 | "delayDays": "3", 183 | "maxDelayCount": 1, 184 | "status": 1, 185 | "propertyList": null 186 | }, 187 | { 188 | "id": 82, 189 | "cid": "3300001", 190 | "goodsName": "千岛湖8°", 191 | "price": 39.0, 192 | "selfPrice": null, 193 | "unit": "瓶", 194 | "typeId": 39, 195 | "typeName": "啤酒", 196 | "printId": 6, 197 | "printName": "吧台出品", 198 | "propertyIds": "", 199 | "img": "https://img10.360buyimg.com/n7/jfs/t1/85506/6/16179/433986/5e786858E44a10fa2/804c5f17a97dd234.jpg", 200 | "description": null, 201 | "stock": 9997, 202 | "ableDiscount": 1, 203 | "ableReturn": 1, 204 | "isLowConsume": 1, 205 | "storeDay": 7, 206 | "storeMinCount": 7.0, 207 | "isWholeStore": 1, 208 | "delayDays": "3", 209 | "maxDelayCount": 1, 210 | "status": 1, 211 | "propertyList": null 212 | }, 213 | { 214 | "id": 83, 215 | "cid": "3300001", 216 | "goodsName": "科罗娜", 217 | "price": 39.0, 218 | "selfPrice": null, 219 | "unit": "", 220 | "typeId": 39, 221 | "typeName": "啤酒", 222 | "printId": 6, 223 | "printName": "吧台出品", 224 | "propertyIds": "", 225 | "img": "https://img14.360buyimg.com/n7/jfs/t1/92982/32/8107/287290/5e0180bfEa4f536dd/26a7adb5abe7ec3d.jpg", 226 | "description": null, 227 | "stock": 9997, 228 | "ableDiscount": 1, 229 | "ableReturn": 1, 230 | "isLowConsume": 1, 231 | "storeDay": 7, 232 | "storeMinCount": 7.0, 233 | "isWholeStore": 1, 234 | "delayDays": "3", 235 | "maxDelayCount": 1, 236 | "status": 1, 237 | "propertyList": null 238 | } 239 | ] 240 | } -------------------------------------------------------------------------------- /assets/data/goods_43.json: -------------------------------------------------------------------------------- 1 | { 2 | "code": 200, 3 | "message": "success", 4 | "data": [ 5 | { 6 | "id": 109, 7 | "cid": "3300001", 8 | "goodsName": "纸巾", 9 | "price": 20.0, 10 | "selfPrice": null, 11 | "unit": "包", 12 | "typeId": 43, 13 | "typeName": "其他", 14 | "printId": 6, 15 | "printName": "吧台出品", 16 | "propertyIds": "", 17 | "img": "https://img11.360buyimg.com/n7/jfs/t1/38336/33/6526/237049/5cd1359dEf76117bf/f117343a3b8ed84c.jpg", 18 | "description": null, 19 | "stock": 10000, 20 | "ableDiscount": 1, 21 | "ableReturn": 1, 22 | "isLowConsume": 1, 23 | "storeDay": null, 24 | "storeMinCount": null, 25 | "isWholeStore": null, 26 | "delayDays": null, 27 | "maxDelayCount": null, 28 | "status": 1, 29 | "propertyList": null 30 | }, 31 | { 32 | "id": 110, 33 | "cid": "3300001", 34 | "goodsName": "扑克", 35 | "price": 20.0, 36 | "selfPrice": null, 37 | "unit": "盒", 38 | "typeId": 43, 39 | "typeName": "其他", 40 | "printId": 6, 41 | "printName": "吧台出品", 42 | "propertyIds": "", 43 | "img": "https://img14.360buyimg.com/n7/jfs/t1/60073/30/213/300095/5ce6c84eEa756801b/42c5290fa2f4bf07.jpg", 44 | "description": null, 45 | "stock": 10000, 46 | "ableDiscount": 1, 47 | "ableReturn": 1, 48 | "isLowConsume": 1, 49 | "storeDay": null, 50 | "storeMinCount": null, 51 | "isWholeStore": null, 52 | "delayDays": null, 53 | "maxDelayCount": null, 54 | "status": 1, 55 | "propertyList": null 56 | }, 57 | { 58 | "id": 111, 59 | "cid": "3300001", 60 | "goodsName": "金嗓子喉宝", 61 | "price": 28.0, 62 | "selfPrice": null, 63 | "unit": "盒", 64 | "typeId": 43, 65 | "typeName": "其他", 66 | "printId": 6, 67 | "printName": "吧台出品", 68 | "propertyIds": "", 69 | "img": "https://img10.360buyimg.com/n7/jfs/t1/41774/5/6240/161380/5cfdbf9aEf297b528/13f87cc30254f1b1.jpg", 70 | "description": null, 71 | "stock": 10000, 72 | "ableDiscount": 1, 73 | "ableReturn": 1, 74 | "isLowConsume": 1, 75 | "storeDay": null, 76 | "storeMinCount": null, 77 | "isWholeStore": null, 78 | "delayDays": null, 79 | "maxDelayCount": null, 80 | "status": 1, 81 | "propertyList": null 82 | }, 83 | { 84 | "id": 112, 85 | "cid": "3300001", 86 | "goodsName": "王老吉润喉糖", 87 | "price": 28.0, 88 | "selfPrice": null, 89 | "unit": "盒", 90 | "typeId": 43, 91 | "typeName": "其他", 92 | "printId": 6, 93 | "printName": "吧台出品", 94 | "propertyIds": "", 95 | "img": "https://img12.360buyimg.com/n7/jfs/t19072/157/2369836710/200537/53e91647/5af01730N4bb77efa.jpg", 96 | "description": null, 97 | "stock": 10000, 98 | "ableDiscount": 1, 99 | "ableReturn": 1, 100 | "isLowConsume": 1, 101 | "storeDay": null, 102 | "storeMinCount": null, 103 | "isWholeStore": null, 104 | "delayDays": null, 105 | "maxDelayCount": null, 106 | "status": 1, 107 | "propertyList": null 108 | }, 109 | { 110 | "id": 113, 111 | "cid": "3300001", 112 | "goodsName": "嘉木遵生", 113 | "price": 28.0, 114 | "selfPrice": null, 115 | "unit": "盒", 116 | "typeId": 43, 117 | "typeName": "其他", 118 | "printId": 6, 119 | "printName": "吧台出品", 120 | "propertyIds": "", 121 | "img": "https://img11.360buyimg.com/n7/jfs/t1/108990/3/8929/363681/5e6e2a20Ef39cee9d/f47ff35ce3dd3d6e.jpg", 122 | "description": null, 123 | "stock": 10000, 124 | "ableDiscount": 1, 125 | "ableReturn": 1, 126 | "isLowConsume": 1, 127 | "storeDay": null, 128 | "storeMinCount": null, 129 | "isWholeStore": null, 130 | "delayDays": null, 131 | "maxDelayCount": null, 132 | "status": 1, 133 | "propertyList": null 134 | }, 135 | { 136 | "id": 114, 137 | "cid": "3300001", 138 | "goodsName": "海王金尊", 139 | "price": 28.0, 140 | "selfPrice": null, 141 | "unit": "盒", 142 | "typeId": 43, 143 | "typeName": "其他", 144 | "printId": 6, 145 | "printName": "吧台出品", 146 | "propertyIds": "", 147 | "img": "https://img11.360buyimg.com/n7/jfs/t1/49129/35/8907/154802/5d65cf91Ec6d6ade6/d1eca9457f38908b.jpg", 148 | "description": null, 149 | "stock": 10000, 150 | "ableDiscount": 1, 151 | "ableReturn": 1, 152 | "isLowConsume": 1, 153 | "storeDay": null, 154 | "storeMinCount": null, 155 | "isWholeStore": null, 156 | "delayDays": null, 157 | "maxDelayCount": null, 158 | "status": 1, 159 | "propertyList": null 160 | } 161 | ] 162 | } -------------------------------------------------------------------------------- /assets/data/goods_46.json: -------------------------------------------------------------------------------- 1 | { 2 | "code": 200, 3 | "message": "success", 4 | "data": [ 5 | { 6 | "id": 155, 7 | "cid": "3300001", 8 | "goodsName": "卤香鹅翅", 9 | "price": 58.0, 10 | "selfPrice": null, 11 | "unit": "份", 12 | "typeId": 46, 13 | "typeName": "卤水类", 14 | "printId": 3, 15 | "printName": "厨房", 16 | "propertyIds": "", 17 | "img": "https://ss2.bdstatic.com/70cFvnSh_Q1YnxGkpoWK1HF6hhy/it/u=2016802205,4156276963&fm=26&gp=0.jpg", 18 | "description": null, 19 | "stock": 10000, 20 | "ableDiscount": 1, 21 | "ableReturn": 1, 22 | "isLowConsume": 1, 23 | "storeDay": null, 24 | "storeMinCount": null, 25 | "isWholeStore": null, 26 | "delayDays": null, 27 | "maxDelayCount": null, 28 | "status": 1, 29 | "propertyList": null 30 | }, 31 | { 32 | "id": 156, 33 | "cid": "3300001", 34 | "goodsName": "卤香鹅掌", 35 | "price": 58.0, 36 | "selfPrice": null, 37 | "unit": "份", 38 | "typeId": 46, 39 | "typeName": "卤水类", 40 | "printId": 3, 41 | "printName": "厨房", 42 | "propertyIds": "", 43 | "img": "https://ss2.bdstatic.com/70cFvnSh_Q1YnxGkpoWK1HF6hhy/it/u=3199090039,3474998741&fm=26&gp=0.jpg", 44 | "description": null, 45 | "stock": 10000, 46 | "ableDiscount": 1, 47 | "ableReturn": 1, 48 | "isLowConsume": 1, 49 | "storeDay": null, 50 | "storeMinCount": null, 51 | "isWholeStore": null, 52 | "delayDays": null, 53 | "maxDelayCount": null, 54 | "status": 1, 55 | "propertyList": null 56 | }, 57 | { 58 | "id": 157, 59 | "cid": "3300001", 60 | "goodsName": "卤香鸭头", 61 | "price": 58.0, 62 | "selfPrice": null, 63 | "unit": "份", 64 | "typeId": 46, 65 | "typeName": "卤水类", 66 | "printId": 3, 67 | "printName": "厨房", 68 | "propertyIds": "", 69 | "img": "https://ss0.bdstatic.com/70cFvHSh_Q1YnxGkpoWK1HF6hhy/it/u=1002974511,2906142099&fm=26&gp=0.jpg", 70 | "description": null, 71 | "stock": 10000, 72 | "ableDiscount": 1, 73 | "ableReturn": 1, 74 | "isLowConsume": 1, 75 | "storeDay": null, 76 | "storeMinCount": null, 77 | "isWholeStore": null, 78 | "delayDays": null, 79 | "maxDelayCount": null, 80 | "status": 1, 81 | "propertyList": null 82 | }, 83 | { 84 | "id": 158, 85 | "cid": "3300001", 86 | "goodsName": "卤香鸭脆肚", 87 | "price": 58.0, 88 | "selfPrice": null, 89 | "unit": "份", 90 | "typeId": 46, 91 | "typeName": "卤水类", 92 | "printId": 3, 93 | "printName": "厨房", 94 | "propertyIds": "", 95 | "img": "https://ss2.bdstatic.com/70cFvnSh_Q1YnxGkpoWK1HF6hhy/it/u=846281623,642125161&fm=26&gp=0.jpg", 96 | "description": null, 97 | "stock": 10000, 98 | "ableDiscount": 1, 99 | "ableReturn": 1, 100 | "isLowConsume": 1, 101 | "storeDay": null, 102 | "storeMinCount": null, 103 | "isWholeStore": null, 104 | "delayDays": null, 105 | "maxDelayCount": null, 106 | "status": 1, 107 | "propertyList": null 108 | }, 109 | { 110 | "id": 159, 111 | "cid": "3300001", 112 | "goodsName": "卤香鸭脖", 113 | "price": 58.0, 114 | "selfPrice": null, 115 | "unit": "份", 116 | "typeId": 46, 117 | "typeName": "卤水类", 118 | "printId": 3, 119 | "printName": "厨房", 120 | "propertyIds": "", 121 | "img": "https://ss2.bdstatic.com/70cFvnSh_Q1YnxGkpoWK1HF6hhy/it/u=2275220535,231938459&fm=26&gp=0.jpg", 122 | "description": null, 123 | "stock": 10000, 124 | "ableDiscount": 1, 125 | "ableReturn": 1, 126 | "isLowConsume": 1, 127 | "storeDay": null, 128 | "storeMinCount": null, 129 | "isWholeStore": null, 130 | "delayDays": null, 131 | "maxDelayCount": null, 132 | "status": 1, 133 | "propertyList": null 134 | }, 135 | { 136 | "id": 160, 137 | "cid": "3300001", 138 | "goodsName": "卤香肚尖", 139 | "price": 58.0, 140 | "selfPrice": null, 141 | "unit": "份", 142 | "typeId": 46, 143 | "typeName": "卤水类", 144 | "printId": 3, 145 | "printName": "厨房", 146 | "propertyIds": "", 147 | "img": "https://ss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=3874132948,2913651691&fm=26&gp=0.jpg", 148 | "description": null, 149 | "stock": 10000, 150 | "ableDiscount": 1, 151 | "ableReturn": 1, 152 | "isLowConsume": 1, 153 | "storeDay": null, 154 | "storeMinCount": null, 155 | "isWholeStore": null, 156 | "delayDays": null, 157 | "maxDelayCount": null, 158 | "status": 1, 159 | "propertyList": null 160 | } 161 | ] 162 | } -------------------------------------------------------------------------------- /assets/data/goods_48.json: -------------------------------------------------------------------------------- 1 | { 2 | "code": 200, 3 | "message": "success", 4 | "data": [ 5 | { 6 | "id": 172, 7 | "cid": "3300001", 8 | "goodsName": "香烤鹌鹑", 9 | "price": 88.0, 10 | "selfPrice": null, 11 | "unit": "份", 12 | "typeId": 48, 13 | "typeName": "烧烤类", 14 | "printId": 3, 15 | "printName": "厨房", 16 | "propertyIds": "", 17 | "img": "https://ss0.bdstatic.com/70cFvHSh_Q1YnxGkpoWK1HF6hhy/it/u=86778724,1468900706&fm=26&gp=0.jpg", 18 | "description": null, 19 | "stock": 10000, 20 | "ableDiscount": 1, 21 | "ableReturn": 1, 22 | "isLowConsume": 1, 23 | "storeDay": null, 24 | "storeMinCount": null, 25 | "isWholeStore": null, 26 | "delayDays": null, 27 | "maxDelayCount": null, 28 | "status": 1, 29 | "propertyList": null 30 | }, 31 | { 32 | "id": 173, 33 | "cid": "3300001", 34 | "goodsName": "孜然羊肉", 35 | "price": 68.0, 36 | "selfPrice": null, 37 | "unit": "份", 38 | "typeId": 48, 39 | "typeName": "烧烤类", 40 | "printId": 3, 41 | "printName": "厨房", 42 | "propertyIds": "", 43 | "img": "https://ss2.bdstatic.com/70cFvnSh_Q1YnxGkpoWK1HF6hhy/it/u=1657491853,696224227&fm=26&gp=0.jpg", 44 | "description": null, 45 | "stock": 9997, 46 | "ableDiscount": 1, 47 | "ableReturn": 1, 48 | "isLowConsume": 1, 49 | "storeDay": null, 50 | "storeMinCount": null, 51 | "isWholeStore": null, 52 | "delayDays": null, 53 | "maxDelayCount": null, 54 | "status": 1, 55 | "propertyList": null 56 | }, 57 | { 58 | "id": 174, 59 | "cid": "3300001", 60 | "goodsName": "香烤鲳鱼", 61 | "price": 68.0, 62 | "selfPrice": null, 63 | "unit": "份", 64 | "typeId": 48, 65 | "typeName": "烧烤类", 66 | "printId": 3, 67 | "printName": "厨房", 68 | "propertyIds": "", 69 | "img": "https://ss0.bdstatic.com/70cFuHSh_Q1YnxGkpoWK1HF6hhy/it/u=1845450498,559743580&fm=26&gp=0.jpg", 70 | "description": null, 71 | "stock": 10000, 72 | "ableDiscount": 1, 73 | "ableReturn": 1, 74 | "isLowConsume": 1, 75 | "storeDay": null, 76 | "storeMinCount": null, 77 | "isWholeStore": null, 78 | "delayDays": null, 79 | "maxDelayCount": null, 80 | "status": 1, 81 | "propertyList": null 82 | }, 83 | { 84 | "id": 175, 85 | "cid": "3300001", 86 | "goodsName": "香烤脆骨", 87 | "price": 68.0, 88 | "selfPrice": null, 89 | "unit": "份", 90 | "typeId": 48, 91 | "typeName": "烧烤类", 92 | "printId": 3, 93 | "printName": "厨房", 94 | "propertyIds": "", 95 | "img": "https://ss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=4055438628,293814977&fm=26&gp=0.jpg", 96 | "description": null, 97 | "stock": 9998, 98 | "ableDiscount": 1, 99 | "ableReturn": 1, 100 | "isLowConsume": 1, 101 | "storeDay": null, 102 | "storeMinCount": null, 103 | "isWholeStore": null, 104 | "delayDays": null, 105 | "maxDelayCount": null, 106 | "status": 1, 107 | "propertyList": null 108 | }, 109 | { 110 | "id": 176, 111 | "cid": "3300001", 112 | "goodsName": "香烤鸡中翅", 113 | "price": 68.0, 114 | "selfPrice": null, 115 | "unit": "份", 116 | "typeId": 48, 117 | "typeName": "烧烤类", 118 | "printId": 3, 119 | "printName": "厨房", 120 | "propertyIds": "", 121 | "img": "https://ss1.bdstatic.com/70cFuXSh_Q1YnxGkpoWK1HF6hhy/it/u=3184894485,1999465710&fm=26&gp=0.jpg", 122 | "description": null, 123 | "stock": 10000, 124 | "ableDiscount": 1, 125 | "ableReturn": 1, 126 | "isLowConsume": 1, 127 | "storeDay": null, 128 | "storeMinCount": null, 129 | "isWholeStore": null, 130 | "delayDays": null, 131 | "maxDelayCount": null, 132 | "status": 1, 133 | "propertyList": null 134 | }, 135 | { 136 | "id": 177, 137 | "cid": "3300001", 138 | "goodsName": "碳烤明虾", 139 | "price": 68.0, 140 | "selfPrice": null, 141 | "unit": "份", 142 | "typeId": 48, 143 | "typeName": "烧烤类", 144 | "printId": 3, 145 | "printName": "厨房", 146 | "propertyIds": "", 147 | "img": "https://ss1.bdstatic.com/70cFuXSh_Q1YnxGkpoWK1HF6hhy/it/u=4140216399,3443487126&fm=26&gp=0.jpg", 148 | "description": null, 149 | "stock": 10000, 150 | "ableDiscount": 1, 151 | "ableReturn": 1, 152 | "isLowConsume": 1, 153 | "storeDay": null, 154 | "storeMinCount": null, 155 | "isWholeStore": null, 156 | "delayDays": null, 157 | "maxDelayCount": null, 158 | "status": 1, 159 | "propertyList": null 160 | }, 161 | { 162 | "id": 178, 163 | "cid": "3300001", 164 | "goodsName": "碳烤扇贝", 165 | "price": 68.0, 166 | "selfPrice": null, 167 | "unit": "份", 168 | "typeId": 48, 169 | "typeName": "烧烤类", 170 | "printId": 3, 171 | "printName": "厨房", 172 | "propertyIds": "", 173 | "img": "https://ss0.bdstatic.com/70cFvHSh_Q1YnxGkpoWK1HF6hhy/it/u=908642033,1493995651&fm=26&gp=0.jpg", 174 | "description": null, 175 | "stock": 10000, 176 | "ableDiscount": 1, 177 | "ableReturn": 1, 178 | "isLowConsume": 1, 179 | "storeDay": null, 180 | "storeMinCount": null, 181 | "isWholeStore": null, 182 | "delayDays": null, 183 | "maxDelayCount": null, 184 | "status": 1, 185 | "propertyList": null 186 | }, 187 | { 188 | "id": 179, 189 | "cid": "3300001", 190 | "goodsName": "碳烤鸭肫", 191 | "price": 58.0, 192 | "selfPrice": null, 193 | "unit": "份", 194 | "typeId": 48, 195 | "typeName": "烧烤类", 196 | "printId": 3, 197 | "printName": "厨房", 198 | "propertyIds": "", 199 | "img": "https://ss0.bdstatic.com/70cFvHSh_Q1YnxGkpoWK1HF6hhy/it/u=3259223203,3752220681&fm=26&gp=0.jpg", 200 | "description": null, 201 | "stock": 10000, 202 | "ableDiscount": 1, 203 | "ableReturn": 1, 204 | "isLowConsume": 1, 205 | "storeDay": null, 206 | "storeMinCount": null, 207 | "isWholeStore": null, 208 | "delayDays": null, 209 | "maxDelayCount": null, 210 | "status": 1, 211 | "propertyList": null 212 | }, 213 | { 214 | "id": 180, 215 | "cid": "3300001", 216 | "goodsName": "香烤鸡心", 217 | "price": 58.0, 218 | "selfPrice": null, 219 | "unit": "份", 220 | "typeId": 48, 221 | "typeName": "烧烤类", 222 | "printId": 3, 223 | "printName": "厨房", 224 | "propertyIds": "", 225 | "img": "https://ss2.bdstatic.com/70cFvnSh_Q1YnxGkpoWK1HF6hhy/it/u=1629638332,3092505062&fm=26&gp=0.jpg", 226 | "description": null, 227 | "stock": 10000, 228 | "ableDiscount": 1, 229 | "ableReturn": 1, 230 | "isLowConsume": 1, 231 | "storeDay": null, 232 | "storeMinCount": null, 233 | "isWholeStore": null, 234 | "delayDays": null, 235 | "maxDelayCount": null, 236 | "status": 1, 237 | "propertyList": null 238 | } 239 | ] 240 | } -------------------------------------------------------------------------------- /assets/data/goods_49.json: -------------------------------------------------------------------------------- 1 | { 2 | "code": 200, 3 | "message": "success", 4 | "data": [ 5 | { 6 | "id": 181, 7 | "cid": "3300001", 8 | "goodsName": "香炸鸡中翅", 9 | "price": 58.0, 10 | "selfPrice": null, 11 | "unit": "份", 12 | "typeId": 49, 13 | "typeName": "香炸类", 14 | "printId": 3, 15 | "printName": "厨房", 16 | "propertyIds": "", 17 | "img": "https://ss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=3674764904,1985037669&fm=26&gp=0.jpg", 18 | "description": null, 19 | "stock": 10000, 20 | "ableDiscount": 1, 21 | "ableReturn": 1, 22 | "isLowConsume": 1, 23 | "storeDay": null, 24 | "storeMinCount": null, 25 | "isWholeStore": null, 26 | "delayDays": null, 27 | "maxDelayCount": null, 28 | "status": 1, 29 | "propertyList": null 30 | }, 31 | { 32 | "id": 182, 33 | "cid": "3300001", 34 | "goodsName": "香炸鸡米花", 35 | "price": 58.0, 36 | "selfPrice": null, 37 | "unit": "份", 38 | "typeId": 49, 39 | "typeName": "香炸类", 40 | "printId": 3, 41 | "printName": "厨房", 42 | "propertyIds": "", 43 | "img": "https://ss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=1380010489,1197302652&fm=26&gp=0.jpg", 44 | "description": null, 45 | "stock": 10000, 46 | "ableDiscount": 1, 47 | "ableReturn": 1, 48 | "isLowConsume": 1, 49 | "storeDay": null, 50 | "storeMinCount": null, 51 | "isWholeStore": null, 52 | "delayDays": null, 53 | "maxDelayCount": null, 54 | "status": 1, 55 | "propertyList": null 56 | }, 57 | { 58 | "id": 183, 59 | "cid": "3300001", 60 | "goodsName": "骨肉相连", 61 | "price": 58.0, 62 | "selfPrice": null, 63 | "unit": "份", 64 | "typeId": 49, 65 | "typeName": "香炸类", 66 | "printId": 3, 67 | "printName": "厨房", 68 | "propertyIds": "", 69 | "img": "https://ss2.bdstatic.com/70cFvnSh_Q1YnxGkpoWK1HF6hhy/it/u=2476870951,1434284341&fm=26&gp=0.jpg", 70 | "description": null, 71 | "stock": 10000, 72 | "ableDiscount": 1, 73 | "ableReturn": 1, 74 | "isLowConsume": 1, 75 | "storeDay": null, 76 | "storeMinCount": null, 77 | "isWholeStore": null, 78 | "delayDays": null, 79 | "maxDelayCount": null, 80 | "status": 1, 81 | "propertyList": null 82 | }, 83 | { 84 | "id": 184, 85 | "cid": "3300001", 86 | "goodsName": "香辣香酥鱼", 87 | "price": 58.0, 88 | "selfPrice": null, 89 | "unit": "份", 90 | "typeId": 49, 91 | "typeName": "香炸类", 92 | "printId": 3, 93 | "printName": "厨房", 94 | "propertyIds": "", 95 | "img": "https://ss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=1270442078,1671778097&fm=26&gp=0.jpg", 96 | "description": null, 97 | "stock": 10000, 98 | "ableDiscount": 1, 99 | "ableReturn": 1, 100 | "isLowConsume": 1, 101 | "storeDay": null, 102 | "storeMinCount": null, 103 | "isWholeStore": null, 104 | "delayDays": null, 105 | "maxDelayCount": null, 106 | "status": 1, 107 | "propertyList": null 108 | }, 109 | { 110 | "id": 185, 111 | "cid": "3300001", 112 | "goodsName": "酥炸南瓜饼", 113 | "price": 38.0, 114 | "selfPrice": null, 115 | "unit": "份", 116 | "typeId": 49, 117 | "typeName": "香炸类", 118 | "printId": 3, 119 | "printName": "厨房", 120 | "propertyIds": "", 121 | "img": "https://ss1.bdstatic.com/70cFuXSh_Q1YnxGkpoWK1HF6hhy/it/u=183219120,175503629&fm=26&gp=0.jpg", 122 | "description": null, 123 | "stock": 10000, 124 | "ableDiscount": 1, 125 | "ableReturn": 1, 126 | "isLowConsume": 1, 127 | "storeDay": null, 128 | "storeMinCount": null, 129 | "isWholeStore": null, 130 | "delayDays": null, 131 | "maxDelayCount": null, 132 | "status": 1, 133 | "propertyList": null 134 | }, 135 | { 136 | "id": 186, 137 | "cid": "3300001", 138 | "goodsName": "美式炸薯条", 139 | "price": 38.0, 140 | "selfPrice": null, 141 | "unit": "份", 142 | "typeId": 49, 143 | "typeName": "香炸类", 144 | "printId": 3, 145 | "printName": "厨房", 146 | "propertyIds": "", 147 | "img": "https://ss0.bdstatic.com/70cFuHSh_Q1YnxGkpoWK1HF6hhy/it/u=3352550231,3035924991&fm=26&gp=0.jpg", 148 | "description": null, 149 | "stock": 10000, 150 | "ableDiscount": 1, 151 | "ableReturn": 1, 152 | "isLowConsume": 1, 153 | "storeDay": null, 154 | "storeMinCount": null, 155 | "isWholeStore": null, 156 | "delayDays": null, 157 | "maxDelayCount": null, 158 | "status": 1, 159 | "propertyList": null 160 | }, 161 | { 162 | "id": 187, 163 | "cid": "3300001", 164 | "goodsName": "中式炸春卷", 165 | "price": 38.0, 166 | "selfPrice": null, 167 | "unit": "份", 168 | "typeId": 49, 169 | "typeName": "香炸类", 170 | "printId": 3, 171 | "printName": "厨房", 172 | "propertyIds": "", 173 | "img": "https://ss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=4010074034,3583165755&fm=26&gp=0.jpg", 174 | "description": null, 175 | "stock": 10000, 176 | "ableDiscount": 1, 177 | "ableReturn": 1, 178 | "isLowConsume": 1, 179 | "storeDay": null, 180 | "storeMinCount": null, 181 | "isWholeStore": null, 182 | "delayDays": null, 183 | "maxDelayCount": null, 184 | "status": 1, 185 | "propertyList": null 186 | }, 187 | { 188 | "id": 188, 189 | "cid": "3300001", 190 | "goodsName": "香酥山药卷", 191 | "price": 38.0, 192 | "selfPrice": null, 193 | "unit": "份", 194 | "typeId": 49, 195 | "typeName": "香炸类", 196 | "printId": 3, 197 | "printName": "厨房", 198 | "propertyIds": "", 199 | "img": "https://ss1.bdstatic.com/70cFuXSh_Q1YnxGkpoWK1HF6hhy/it/u=3918757022,3691860583&fm=26&gp=0.jpg", 200 | "description": null, 201 | "stock": 10000, 202 | "ableDiscount": 1, 203 | "ableReturn": 1, 204 | "isLowConsume": 1, 205 | "storeDay": null, 206 | "storeMinCount": null, 207 | "isWholeStore": null, 208 | "delayDays": null, 209 | "maxDelayCount": null, 210 | "status": 1, 211 | "propertyList": null 212 | }, 213 | { 214 | "id": 189, 215 | "cid": "3300001", 216 | "goodsName": "香炸/苔菜花生米", 217 | "price": 38.0, 218 | "selfPrice": null, 219 | "unit": "份", 220 | "typeId": 49, 221 | "typeName": "香炸类", 222 | "printId": 3, 223 | "printName": "厨房", 224 | "propertyIds": "", 225 | "img": "https://ss3.bdstatic.com/70cFv8Sh_Q1YnxGkpoWK1HF6hhy/it/u=317887384,1020721397&fm=26&gp=0.jpg", 226 | "description": null, 227 | "stock": 10000, 228 | "ableDiscount": 1, 229 | "ableReturn": 1, 230 | "isLowConsume": 1, 231 | "storeDay": null, 232 | "storeMinCount": null, 233 | "isWholeStore": null, 234 | "delayDays": null, 235 | "maxDelayCount": null, 236 | "status": 1, 237 | "propertyList": null 238 | } 239 | ] 240 | } -------------------------------------------------------------------------------- /assets/data/goods_50.json: -------------------------------------------------------------------------------- 1 | { 2 | "code": 200, 3 | "message": "success", 4 | "data": [ 5 | { 6 | "id": 96, 7 | "cid": "3300001", 8 | "goodsName": "普洱茶", 9 | "price": 38.0, 10 | "selfPrice": null, 11 | "unit": "杯", 12 | "typeId": 50, 13 | "typeName": "热饮", 14 | "printId": 6, 15 | "printName": "吧台出品", 16 | "propertyIds": "", 17 | "img": "https://img14.360buyimg.com/n7/jfs/t11038/319/2953476513/143235/5bbc9dd1/5cdcca06N14f9800a.jpg", 18 | "description": null, 19 | "stock": 9995, 20 | "ableDiscount": 1, 21 | "ableReturn": 1, 22 | "isLowConsume": 1, 23 | "storeDay": null, 24 | "storeMinCount": null, 25 | "isWholeStore": null, 26 | "delayDays": null, 27 | "maxDelayCount": null, 28 | "status": 1, 29 | "propertyList": null 30 | }, 31 | { 32 | "id": 97, 33 | "cid": "3300001", 34 | "goodsName": "西洋参茶", 35 | "price": 38.0, 36 | "selfPrice": null, 37 | "unit": "杯", 38 | "typeId": 50, 39 | "typeName": "热饮", 40 | "printId": 6, 41 | "printName": "吧台出品", 42 | "propertyIds": "", 43 | "img": "https://img14.360buyimg.com/n7/jfs/t1/22033/18/5882/352594/5c452feeE27ae0887/c6d1ddcb74dfe00b.jpg", 44 | "description": null, 45 | "stock": 9999, 46 | "ableDiscount": 1, 47 | "ableReturn": 1, 48 | "isLowConsume": 1, 49 | "storeDay": null, 50 | "storeMinCount": null, 51 | "isWholeStore": null, 52 | "delayDays": null, 53 | "maxDelayCount": null, 54 | "status": 1, 55 | "propertyList": null 56 | }, 57 | { 58 | "id": 98, 59 | "cid": "3300001", 60 | "goodsName": "铁观音", 61 | "price": 38.0, 62 | "selfPrice": null, 63 | "unit": "杯", 64 | "typeId": 50, 65 | "typeName": "热饮", 66 | "printId": 6, 67 | "printName": "吧台出品", 68 | "propertyIds": "", 69 | "img": "https://img13.360buyimg.com/n7/jfs/t1/104656/7/13887/331542/5e61a9efE51c12aad/944352813b87c42f.jpg", 70 | "description": null, 71 | "stock": 9999, 72 | "ableDiscount": 1, 73 | "ableReturn": 1, 74 | "isLowConsume": 1, 75 | "storeDay": null, 76 | "storeMinCount": null, 77 | "isWholeStore": null, 78 | "delayDays": null, 79 | "maxDelayCount": null, 80 | "status": 1, 81 | "propertyList": null 82 | }, 83 | { 84 | "id": 99, 85 | "cid": "3300001", 86 | "goodsName": "菊花茶", 87 | "price": 35.0, 88 | "selfPrice": null, 89 | "unit": "杯", 90 | "typeId": 50, 91 | "typeName": "热饮", 92 | "printId": 6, 93 | "printName": "吧台出品", 94 | "propertyIds": "", 95 | "img": "https://img14.360buyimg.com/n7/jfs/t1/115021/17/6495/167687/5eba864cE115f7888/b7b8983bea760bfe.jpg", 96 | "description": null, 97 | "stock": 9997, 98 | "ableDiscount": 1, 99 | "ableReturn": 1, 100 | "isLowConsume": 1, 101 | "storeDay": null, 102 | "storeMinCount": null, 103 | "isWholeStore": null, 104 | "delayDays": null, 105 | "maxDelayCount": null, 106 | "status": 1, 107 | "propertyList": null 108 | }, 109 | { 110 | "id": 100, 111 | "cid": "3300001", 112 | "goodsName": "西湖龙井", 113 | "price": 35.0, 114 | "selfPrice": null, 115 | "unit": "杯", 116 | "typeId": 50, 117 | "typeName": "热饮", 118 | "printId": 6, 119 | "printName": "吧台出品", 120 | "propertyIds": "", 121 | "img": "https://img13.360buyimg.com/n7/jfs/t1/108548/14/14526/314568/5ea69cc0Ec7fc4634/12fba44e52714269.jpg", 122 | "description": null, 123 | "stock": 9999, 124 | "ableDiscount": 1, 125 | "ableReturn": 1, 126 | "isLowConsume": 1, 127 | "storeDay": null, 128 | "storeMinCount": null, 129 | "isWholeStore": null, 130 | "delayDays": null, 131 | "maxDelayCount": null, 132 | "status": 1, 133 | "propertyList": null 134 | }, 135 | { 136 | "id": 101, 137 | "cid": "3300001", 138 | "goodsName": "冷/热纯牛奶", 139 | "price": 35.0, 140 | "selfPrice": null, 141 | "unit": "杯", 142 | "typeId": 50, 143 | "typeName": "热饮", 144 | "printId": 6, 145 | "printName": "吧台出品", 146 | "propertyIds": "", 147 | "img": "https://img11.360buyimg.com/n7/jfs/t1/101321/17/5422/150607/5dee09dbE37f60fc2/e9b9f4e25ab1d31e.jpg", 148 | "description": null, 149 | "stock": 9997, 150 | "ableDiscount": 1, 151 | "ableReturn": 1, 152 | "isLowConsume": 1, 153 | "storeDay": null, 154 | "storeMinCount": null, 155 | "isWholeStore": null, 156 | "delayDays": null, 157 | "maxDelayCount": null, 158 | "status": 1, 159 | "propertyList": null 160 | }, 161 | { 162 | "id": 102, 163 | "cid": "3300001", 164 | "goodsName": "冷/热柠檬茶", 165 | "price": 30.0, 166 | "selfPrice": null, 167 | "unit": "杯", 168 | "typeId": 50, 169 | "typeName": "热饮", 170 | "printId": 6, 171 | "printName": "吧台出品", 172 | "propertyIds": "", 173 | "img": "https://img12.360buyimg.com/n7/jfs/t1/12074/33/13864/212796/5ca17746Ecd7af6e9/727c4ed671be8e48.jpg", 174 | "description": null, 175 | "stock": 9995, 176 | "ableDiscount": 1, 177 | "ableReturn": 1, 178 | "isLowConsume": 1, 179 | "storeDay": null, 180 | "storeMinCount": null, 181 | "isWholeStore": null, 182 | "delayDays": null, 183 | "maxDelayCount": null, 184 | "status": 1, 185 | "propertyList": null 186 | }, 187 | { 188 | "id": 103, 189 | "cid": "3300001", 190 | "goodsName": "蜂蜜水", 191 | "price": 30.0, 192 | "selfPrice": null, 193 | "unit": "杯", 194 | "typeId": 50, 195 | "typeName": "热饮", 196 | "printId": 6, 197 | "printName": "吧台出品", 198 | "propertyIds": "", 199 | "img": "https://img12.360buyimg.com/n7/jfs/t16393/265/2158045124/507205/cd840034/5a98f45aN69a65784.jpg", 200 | "description": null, 201 | "stock": 9993, 202 | "ableDiscount": 1, 203 | "ableReturn": 1, 204 | "isLowConsume": 1, 205 | "storeDay": null, 206 | "storeMinCount": null, 207 | "isWholeStore": null, 208 | "delayDays": null, 209 | "maxDelayCount": null, 210 | "status": 1, 211 | "propertyList": null 212 | }, 213 | { 214 | "id": 104, 215 | "cid": "3300001", 216 | "goodsName": "立顿红茶", 217 | "price": 30.0, 218 | "selfPrice": null, 219 | "unit": "杯", 220 | "typeId": 50, 221 | "typeName": "热饮", 222 | "printId": 6, 223 | "printName": "吧台出品", 224 | "propertyIds": "", 225 | "img": "https://img13.360buyimg.com/n7/jfs/t21349/50/2689120826/298046/ef97c3f0/5b5ff205Nfcfe9108.jpg", 226 | "description": null, 227 | "stock": 9999, 228 | "ableDiscount": 1, 229 | "ableReturn": 1, 230 | "isLowConsume": 1, 231 | "storeDay": null, 232 | "storeMinCount": null, 233 | "isWholeStore": null, 234 | "delayDays": null, 235 | "maxDelayCount": null, 236 | "status": 1, 237 | "propertyList": null 238 | } 239 | ] 240 | } -------------------------------------------------------------------------------- /assets/data/property.json: -------------------------------------------------------------------------------- 1 | { 2 | "code": 200, 3 | "message": "success", 4 | "data": [ 5 | { 6 | "id": 2, 7 | "cid": "3300001", 8 | "propertyName": "甜度", 9 | "parentId": -1 10 | }, 11 | { 12 | "id": 3, 13 | "cid": "3300001", 14 | "propertyName": "辣度", 15 | "parentId": -1 16 | }, 17 | { 18 | "id": 13, 19 | "cid": "3300001", 20 | "propertyName": "温度", 21 | "parentId": -1 22 | }, 23 | { 24 | "id": 18, 25 | "cid": "3300001", 26 | "propertyName": "份量", 27 | "parentId": -1 28 | } 29 | ] 30 | } -------------------------------------------------------------------------------- /assets/data/sort.json: -------------------------------------------------------------------------------- 1 | { 2 | "code": 200, 3 | "message": "success", 4 | "data": [ 5 | { 6 | "id": 10, 7 | "cid": "3300001", 8 | "typeCode": null, 9 | "typeName": "啤酒", 10 | "parentId": 4, 11 | "isWine": null, 12 | "status": 1 13 | }, 14 | { 15 | "id": 11, 16 | "cid": "3300001", 17 | "typeCode": null, 18 | "typeName": "白酒", 19 | "parentId": 4, 20 | "isWine": null, 21 | "status": 1 22 | }, 23 | { 24 | "id": 13, 25 | "cid": "3300001", 26 | "typeCode": null, 27 | "typeName": "红酒", 28 | "parentId": 4, 29 | "isWine": null, 30 | "status": 1 31 | }, 32 | { 33 | "id": 24, 34 | "cid": "3300001", 35 | "typeCode": "mifan", 36 | "typeName": "米饭类", 37 | "parentId": 23, 38 | "isWine": null, 39 | "status": 1 40 | }, 41 | { 42 | "id": 25, 43 | "cid": "3300001", 44 | "typeCode": "mian", 45 | "typeName": "面食", 46 | "parentId": 23, 47 | "isWine": null, 48 | "status": 1 49 | }, 50 | { 51 | "id": 26, 52 | "cid": "3300001", 53 | "typeCode": "other", 54 | "typeName": "其他", 55 | "parentId": 23, 56 | "isWine": null, 57 | "status": 1 58 | }, 59 | { 60 | "id": 28, 61 | "cid": "3300001", 62 | "typeCode": "hun", 63 | "typeName": "荤菜", 64 | "parentId": 27, 65 | "isWine": null, 66 | "status": 1 67 | }, 68 | { 69 | "id": 29, 70 | "cid": "3300001", 71 | "typeCode": "su", 72 | "typeName": "素菜", 73 | "parentId": 27, 74 | "isWine": null, 75 | "status": 1 76 | }, 77 | { 78 | "id": 30, 79 | "cid": "3300001", 80 | "typeCode": "haixian", 81 | "typeName": "海鲜类", 82 | "parentId": 27, 83 | "isWine": null, 84 | "status": 1 85 | }, 86 | { 87 | "id": 31, 88 | "cid": "3300001", 89 | "typeCode": "YJ", 90 | "typeName": "洋酒", 91 | "parentId": -1, 92 | "isWine": 1, 93 | "status": 1 94 | }, 95 | { 96 | "id": 32, 97 | "cid": "3300001", 98 | "typeCode": "HJ", 99 | "typeName": "葡萄酒", 100 | "parentId": -1, 101 | "isWine": 1, 102 | "status": 1 103 | }, 104 | { 105 | "id": 33, 106 | "cid": "3300001", 107 | "typeCode": "BLD", 108 | "typeName": "白兰地", 109 | "parentId": 31, 110 | "isWine": 1, 111 | "status": 1 112 | }, 113 | { 114 | "id": 34, 115 | "cid": "3300001", 116 | "typeCode": "WSJ", 117 | "typeName": "威士忌", 118 | "parentId": 31, 119 | "isWine": 1, 120 | "status": 1 121 | }, 122 | { 123 | "id": 35, 124 | "cid": "3300001", 125 | "typeCode": "FTJ", 126 | "typeName": "伏特加", 127 | "parentId": 31, 128 | "isWine": 1, 129 | "status": 1 130 | }, 131 | { 132 | "id": 36, 133 | "cid": "3300001", 134 | "typeCode": "QLJ", 135 | "typeName": "力乔酒", 136 | "parentId": 31, 137 | "isWine": 1, 138 | "status": 1 139 | }, 140 | { 141 | "id": 37, 142 | "cid": "3300001", 143 | "typeCode": "XB", 144 | "typeName": "香槟", 145 | "parentId": 31, 146 | "isWine": 1, 147 | "status": 1 148 | }, 149 | { 150 | "id": 38, 151 | "cid": "3300001", 152 | "typeCode": "JJ", 153 | "typeName": "金酒", 154 | "parentId": 31, 155 | "isWine": 1, 156 | "status": 1 157 | }, 158 | { 159 | "id": 39, 160 | "cid": "3300001", 161 | "typeCode": "PJ", 162 | "typeName": "啤酒", 163 | "parentId": -1, 164 | "isWine": 1, 165 | "status": 1 166 | }, 167 | { 168 | "id": 40, 169 | "cid": "3300001", 170 | "typeCode": "SGL", 171 | "typeName": "水果类", 172 | "parentId": -1, 173 | "isWine": 0, 174 | "status": 1 175 | }, 176 | { 177 | "id": 41, 178 | "cid": "3300001", 179 | "typeCode": "CF", 180 | "typeName": "厨房", 181 | "parentId": -1, 182 | "isWine": 0, 183 | "status": 1 184 | }, 185 | { 186 | "id": 42, 187 | "cid": "3300001", 188 | "typeCode": "YP", 189 | "typeName": "饮品", 190 | "parentId": -1, 191 | "isWine": 0, 192 | "status": 1 193 | }, 194 | { 195 | "id": 43, 196 | "cid": "3300001", 197 | "typeCode": "QT", 198 | "typeName": "其他", 199 | "parentId": -1, 200 | "isWine": 0, 201 | "status": 1 202 | }, 203 | { 204 | "id": 45, 205 | "cid": "3300001", 206 | "typeCode": "XCL", 207 | "typeName": "小吃类", 208 | "parentId": 41, 209 | "isWine": 0, 210 | "status": 1 211 | }, 212 | { 213 | "id": 46, 214 | "cid": "3300001", 215 | "typeCode": "LSL", 216 | "typeName": "卤水类", 217 | "parentId": 41, 218 | "isWine": 0, 219 | "status": 1 220 | }, 221 | { 222 | "id": 47, 223 | "cid": "3300001", 224 | "typeCode": "ZSL", 225 | "typeName": "主食类", 226 | "parentId": 41, 227 | "isWine": 0, 228 | "status": 1 229 | }, 230 | { 231 | "id": 48, 232 | "cid": "3300001", 233 | "typeCode": "SKL", 234 | "typeName": "烧烤类", 235 | "parentId": 41, 236 | "isWine": 0, 237 | "status": 1 238 | }, 239 | { 240 | "id": 49, 241 | "cid": "3300001", 242 | "typeCode": "XZL", 243 | "typeName": "香炸类", 244 | "parentId": 41, 245 | "isWine": 0, 246 | "status": 1 247 | }, 248 | { 249 | "id": 50, 250 | "cid": "3300001", 251 | "typeCode": "RY", 252 | "typeName": "热饮", 253 | "parentId": 42, 254 | "isWine": 0, 255 | "status": 1 256 | }, 257 | { 258 | "id": 51, 259 | "cid": "3300001", 260 | "typeCode": "RY", 261 | "typeName": "软饮", 262 | "parentId": 42, 263 | "isWine": 0, 264 | "status": 1 265 | }, 266 | { 267 | "id": 179, 268 | "cid": "3300001", 269 | "typeCode": "XLL", 270 | "typeName": "香辣类", 271 | "parentId": 41, 272 | "isWine": 0, 273 | "status": 1 274 | } 275 | ] 276 | } -------------------------------------------------------------------------------- /assets/fonts/DIN-Regular.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weibindev/flutter_order/846c27efe25ba8da181cf240c2d8511e914b7f2d/assets/fonts/DIN-Regular.otf -------------------------------------------------------------------------------- /assets/images/empty.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weibindev/flutter_order/846c27efe25ba8da181cf240c2d8511e914b7f2d/assets/images/empty.png -------------------------------------------------------------------------------- /assets/images/ic_add.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weibindev/flutter_order/846c27efe25ba8da181cf240c2d8511e914b7f2d/assets/images/ic_add.png -------------------------------------------------------------------------------- /assets/images/ic_back_black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weibindev/flutter_order/846c27efe25ba8da181cf240c2d8511e914b7f2d/assets/images/ic_back_black.png -------------------------------------------------------------------------------- /assets/images/ic_clear.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weibindev/flutter_order/846c27efe25ba8da181cf240c2d8511e914b7f2d/assets/images/ic_clear.png -------------------------------------------------------------------------------- /assets/images/ic_reduce.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weibindev/flutter_order/846c27efe25ba8da181cf240c2d8511e914b7f2d/assets/images/ic_reduce.png -------------------------------------------------------------------------------- /assets/images/none.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weibindev/flutter_order/846c27efe25ba8da181cf240c2d8511e914b7f2d/assets/images/none.png -------------------------------------------------------------------------------- /assets/images/order_cart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weibindev/flutter_order/846c27efe25ba8da181cf240c2d8511e914b7f2d/assets/images/order_cart.png -------------------------------------------------------------------------------- /assets/images/order_cart_empty.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weibindev/flutter_order/846c27efe25ba8da181cf240c2d8511e914b7f2d/assets/images/order_cart_empty.png -------------------------------------------------------------------------------- /assets/images/order_delete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weibindev/flutter_order/846c27efe25ba8da181cf240c2d8511e914b7f2d/assets/images/order_delete.png -------------------------------------------------------------------------------- /assets/images/order_search.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weibindev/flutter_order/846c27efe25ba8da181cf240c2d8511e914b7f2d/assets/images/order_search.png -------------------------------------------------------------------------------- /lib/common/common.dart: -------------------------------------------------------------------------------- 1 | class Constant { 2 | /// debug开关,上线需要关闭 3 | /// App运行在Release环境时,inProduction为true;当App运行在Debug和Profile环境时,inProduction为false 4 | static const bool inProduction = 5 | const bool.fromEnvironment('dart.vm.product'); 6 | 7 | static bool isTest = false; 8 | 9 | static const String baseUrl = 'baseUrl'; 10 | 11 | static const String data = 'data'; 12 | static const String message = 'message'; 13 | static const String code = 'code'; 14 | 15 | static const String theme = 'AppTheme'; 16 | 17 | static const String cartGoods = 'cartGoods'; 18 | static const String sortGoods = 'sortGoods'; 19 | 20 | //商品属性 21 | static const String property = 'property'; 22 | 23 | static bool isShowShopList = false; 24 | } 25 | -------------------------------------------------------------------------------- /lib/entity/base_entity.dart: -------------------------------------------------------------------------------- 1 | import 'package:order/common/common.dart'; 2 | 3 | import '../entity_factory.dart'; 4 | 5 | class BaseEntity{ 6 | 7 | int code; 8 | String message; 9 | T data; 10 | List listData = []; 11 | 12 | BaseEntity(this.code, this.message, this.data); 13 | 14 | BaseEntity.fromJson(Map json) { 15 | code = json[Constant.code]; 16 | message = json[Constant.message]; 17 | if (json.containsKey(Constant.data)) { 18 | if (json[Constant.data] is List) { 19 | (json[Constant.data] as List).forEach((item) { 20 | listData.add(_generateOBJ(item)); 21 | }); 22 | } else { 23 | data = _generateOBJ(json[Constant.data]); 24 | } 25 | } 26 | } 27 | 28 | S _generateOBJ(json) { 29 | if (S.toString() == 'String') { 30 | return json.toString() as S; 31 | } else if (T.toString() == 'Map') { 32 | return json as S; 33 | } else { 34 | return EntityFactory.generateOBJ(json); 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /lib/entity/cart_goods_bean.dart: -------------------------------------------------------------------------------- 1 | import 'package:order/entity/property.dart'; 2 | 3 | ///需要添加到购物车的实体类(覆写hashcode) 4 | class CartGoodsBean { 5 | int id; 6 | String img; 7 | String name; 8 | String description; 9 | double price; 10 | int type; 11 | String properties = ''; 12 | List propertyList; 13 | 14 | CartGoodsBean({this.id, 15 | this.img, 16 | this.name, 17 | this.description, 18 | this.type, 19 | this.price, 20 | this.properties, 21 | this.propertyList}); 22 | 23 | factory CartGoodsBean.fromJson(Map json) { 24 | return CartGoodsBean( 25 | id: json['id'], 26 | img: json['img'], 27 | name: json['name'], 28 | description: json['description'], 29 | price: json['price'], 30 | type: json['type'], 31 | properties: json['properties'], 32 | propertyList: json['propertyList'] != null 33 | ? (json['propertyList'] as List) 34 | .map((i) => Property.fromJson(i)) 35 | .toList() 36 | : null 37 | ); 38 | } 39 | 40 | Map toJson() { 41 | final Map data = new Map(); 42 | data['id'] = this.id; 43 | data['img'] = this.img; 44 | data['name'] = this.name; 45 | data['description'] = this.description; 46 | data['price'] = this.price; 47 | data['type'] = this.type; 48 | data['properties'] = this.properties; 49 | if (this.propertyList != null) { 50 | data['propertyList'] = this.propertyList.map((v) => v.toJson()).toList(); 51 | } 52 | return data; 53 | } 54 | 55 | @override 56 | bool operator ==(Object other) => 57 | identical(this, other) || 58 | other is CartGoodsBean && 59 | runtimeType == other.runtimeType && 60 | id == other.id && 61 | img == other.img && 62 | name == other.name && 63 | description == other.description && 64 | price == other.price && 65 | type == other.type && 66 | properties == other.properties ; 67 | 68 | @override 69 | int get hashCode => 70 | id.hashCode ^ 71 | img.hashCode ^ 72 | name.hashCode ^ 73 | description.hashCode ^ 74 | price.hashCode ^ 75 | type.hashCode ^ 76 | properties.hashCode ; 77 | } 78 | -------------------------------------------------------------------------------- /lib/entity/discount_packages.dart: -------------------------------------------------------------------------------- 1 | import 'property.dart'; 2 | 3 | class DiscountPackages { 4 | int ableDiscount; 5 | int ableReturn; 6 | String cid; 7 | String description; 8 | int id; 9 | String img; 10 | int isLowConsume; 11 | String modifier; 12 | String name; 13 | double price; 14 | String propertyIds; 15 | List propertyList; 16 | int type; 17 | 18 | DiscountPackages( 19 | {this.ableDiscount, 20 | this.ableReturn, 21 | this.cid, 22 | this.description, 23 | this.id, 24 | this.img, 25 | this.isLowConsume, 26 | this.modifier, 27 | this.name, 28 | this.price, 29 | this.propertyIds, 30 | this.propertyList, 31 | this.type}); 32 | 33 | factory DiscountPackages.fromJson(Map json) { 34 | return DiscountPackages( 35 | ableDiscount: json['ableDiscount'], 36 | ableReturn: json['ableReturn'], 37 | cid: json['cid'], 38 | description: json['description'] != null ? json['description'] : '', 39 | id: json['id'], 40 | img: json['img'], 41 | isLowConsume: json['isLowConsume'], 42 | modifier: json['modifier'], 43 | name: json['name'], 44 | price: json['price'], 45 | propertyIds: json['propertyIds'], 46 | propertyList: json['propertyList'] != null 47 | ? (json['propertyList'] as List) 48 | .map((i) => Property.fromJson(i)) 49 | .toList() 50 | : null, 51 | type: json['type'], 52 | ); 53 | } 54 | 55 | Map toJson() { 56 | final Map data = new Map(); 57 | data['ableDiscount'] = this.ableDiscount; 58 | data['ableReturn'] = this.ableReturn; 59 | data['cid'] = this.cid; 60 | data['id'] = this.id; 61 | data['img'] = this.img; 62 | data['isLowConsume'] = this.isLowConsume; 63 | data['modifier'] = this.modifier; 64 | data['name'] = this.name; 65 | data['price'] = this.price; 66 | data['propertyIds'] = this.propertyIds; 67 | data['type'] = this.type; 68 | if (this.description != null) { 69 | data['description'] = this.description; 70 | } 71 | if (this.propertyList != null) { 72 | data['propertyList'] = this.propertyList.map((v) => v.toJson()).toList(); 73 | } 74 | return data; 75 | } 76 | } -------------------------------------------------------------------------------- /lib/entity/goods.dart: -------------------------------------------------------------------------------- 1 | import 'property.dart'; 2 | 3 | class GoodsBean { 4 | int ableDiscount; 5 | int ableReturn; 6 | String cid; 7 | String delayDays; 8 | String description; 9 | String goodsName; 10 | int id; 11 | String img; 12 | int isLowConsume; 13 | double storeMinCount; //起存数量 14 | int isWholeStore; //整存或零存(1整存,2零存) 15 | int maxDelayCount; 16 | double price; 17 | int printId; 18 | String printName; 19 | String propertyIds; 20 | double selfPrice; 21 | int status; 22 | int stock; 23 | int typeId; 24 | String typeName; 25 | String unit; 26 | List propertyList; 27 | 28 | GoodsBean( 29 | {this.ableDiscount, 30 | this.ableReturn, 31 | this.cid, 32 | this.delayDays, 33 | this.description, 34 | this.goodsName, 35 | this.id, 36 | this.img, 37 | this.isLowConsume, 38 | this.storeMinCount, 39 | this.isWholeStore, 40 | this.maxDelayCount, 41 | this.price, 42 | this.printId, 43 | this.printName, 44 | this.propertyIds, 45 | this.selfPrice, 46 | this.status, 47 | this.stock, 48 | this.typeId, 49 | this.typeName, 50 | this.unit, 51 | this.propertyList}); 52 | 53 | factory GoodsBean.fromJson(Map json) { 54 | return GoodsBean( 55 | ableDiscount: json['ableDiscount'], 56 | ableReturn: json['ableReturn'], 57 | cid: json['cid'], 58 | delayDays: json['delayDays'] != null ? json['delayDays'] : '', 59 | description: json['description'] != null ? json['description'] : '', 60 | goodsName: json['goodsName'], 61 | id: json['id'], 62 | img: json['img'], 63 | isLowConsume: json['isLowConsume'], 64 | storeMinCount: 65 | json['storeMinCount'] != null ? json['storeMinCount'] : 0.0, 66 | isWholeStore: json['isWholeStore'] != null ? json['isWholeStore'] : -1, 67 | maxDelayCount: json['maxDelayCount'] != null ? json['maxDelayCount'] : -1, 68 | price: json['price'], 69 | printId: json['printId'], 70 | printName: json['printName'], 71 | propertyIds: json['propertyIds'], 72 | selfPrice: json['selfPrice'], 73 | status: json['status'], 74 | stock: json['stock'], 75 | typeId: json['typeId'], 76 | typeName: json['typeName'], 77 | unit: json['unit'], 78 | propertyList: json['propertyList'] != null 79 | ? (json['propertyList'] as List) 80 | .map((i) => Property.fromJson(i)) 81 | .toList() 82 | : null, 83 | ); 84 | } 85 | 86 | Map toJson() { 87 | final Map data = new Map(); 88 | data['ableDiscount'] = this.ableDiscount; 89 | data['ableReturn'] = this.ableReturn; 90 | data['cid'] = this.cid; 91 | data['goodsName'] = this.goodsName; 92 | data['id'] = this.id; 93 | data['img'] = this.img; 94 | data['isLowConsume'] = this.isLowConsume; 95 | data['price'] = this.price; 96 | data['printId'] = this.printId; 97 | data['printName'] = this.printName; 98 | data['propertyIds'] = this.propertyIds; 99 | data['selfPrice'] = this.selfPrice; 100 | data['status'] = this.status; 101 | data['stock'] = this.stock; 102 | data['typeId'] = this.typeId; 103 | data['typeName'] = this.typeName; 104 | data['unit'] = this.unit; 105 | if (this.delayDays != null) { 106 | data['delayDays'] = this.delayDays; 107 | } 108 | if (this.description != null) { 109 | data['description'] = this.description; 110 | } 111 | if (this.storeMinCount != null) { 112 | data['storeMinCount'] = this.storeMinCount; 113 | } 114 | if (this.isWholeStore != null) { 115 | data['isWholeStore'] = this.isWholeStore; 116 | } 117 | if (this.maxDelayCount != null) { 118 | data['maxDelayCount'] = this.maxDelayCount; 119 | } 120 | if (this.propertyList != null) { 121 | data['propertyList'] = this.propertyList.map((v) => v.toJson()).toList(); 122 | } 123 | return data; 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /lib/entity/property.dart: -------------------------------------------------------------------------------- 1 | class Property { 2 | int id; 3 | String cid; 4 | String propertyName; 5 | int parentId; 6 | 7 | Property({this.id, this.cid, this.propertyName, this.parentId}); 8 | 9 | factory Property.fromJson(Map json) { 10 | return Property( 11 | id: json['id'], 12 | cid: json['cid'], 13 | propertyName: json['propertyName'], 14 | parentId: json['parentId'], 15 | ); 16 | } 17 | 18 | Map toJson() { 19 | final Map data = new Map(); 20 | data['cid'] = this.cid; 21 | data['id'] = this.id; 22 | data['propertyName'] = this.propertyName; 23 | data['parentId'] = this.parentId; 24 | return data; 25 | } 26 | } -------------------------------------------------------------------------------- /lib/entity/sort.dart: -------------------------------------------------------------------------------- 1 | class Sort { 2 | String cid; 3 | int id; 4 | int parentId; 5 | int status; 6 | int isWine; //是否酒类 0:否,1:是 7 | String typeCode; 8 | String typeName; 9 | 10 | Sort( 11 | {this.cid, 12 | this.id, 13 | this.parentId, 14 | this.status, 15 | this.isWine, 16 | this.typeCode, 17 | this.typeName}); 18 | 19 | factory Sort.fromJson(Map json) { 20 | return Sort( 21 | cid: json['cid'], 22 | id: json['id'], 23 | parentId: json['parentId'], 24 | status: json['status'], 25 | isWine: json['isWine'], 26 | typeCode: json['typeCode'], 27 | typeName: json['typeName'], 28 | ); 29 | } 30 | 31 | Map toJson() { 32 | final Map data = new Map(); 33 | data['cid'] = this.cid; 34 | data['id'] = this.id; 35 | data['parentId'] = this.parentId; 36 | data['status'] = this.status; 37 | data['isWine'] = this.isWine; 38 | data['typeCode'] = this.typeCode; 39 | data['typeName'] = this.typeName; 40 | return data; 41 | } 42 | } -------------------------------------------------------------------------------- /lib/entity_factory.dart: -------------------------------------------------------------------------------- 1 | import 'entity/goods.dart'; 2 | import 'entity/property.dart'; 3 | import 'entity/sort.dart'; 4 | import 'entity/discount_packages.dart'; 5 | 6 | class EntityFactory { 7 | static T generateOBJ(json) { 8 | if (1 == 0) { 9 | return null; 10 | } else if (T.toString() == 'GoodsBean') { 11 | return GoodsBean.fromJson(json) as T; 12 | } else if (T.toString() == 'Property') { 13 | return Property.fromJson(json) as T; 14 | } else if (T.toString() == 'Sort') { 15 | return Sort.fromJson(json) as T; 16 | } else if (T.toString() == 'DiscountPackages') { 17 | return DiscountPackages.fromJson(json) as T; 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:flustars/flustars.dart'; 4 | import 'package:flutter/material.dart'; 5 | import 'package:flutter/services.dart'; 6 | import 'package:oktoast/oktoast.dart'; 7 | import 'package:order/provider/theme_provider.dart'; 8 | import 'package:order/ui/home_page.dart'; 9 | import 'package:provider/provider.dart'; 10 | 11 | import 'util/log_utils.dart'; 12 | 13 | void main() async { 14 | WidgetsFlutterBinding.ensureInitialized(); 15 | 16 | await SpUtil.getInstance(); 17 | 18 | //设定设计图尺寸 19 | setDesignWHD(375.0, 667.0); 20 | 21 | runApp(MyApp()); 22 | //透明状态栏 23 | if (Platform.isAndroid) { 24 | SystemUiOverlayStyle systemUiOverlayStyle = 25 | SystemUiOverlayStyle(statusBarColor: Colors.transparent); 26 | SystemChrome.setSystemUIOverlayStyle(systemUiOverlayStyle); 27 | } 28 | } 29 | 30 | class MyApp extends StatefulWidget { 31 | @override 32 | _MyAppState createState() => _MyAppState(); 33 | } 34 | 35 | class _MyAppState extends State { 36 | @override 37 | void initState() { 38 | super.initState(); 39 | Log.init(); 40 | } 41 | 42 | @override 43 | Widget build(BuildContext context) { 44 | return OKToast( 45 | child: ChangeNotifierProvider( 46 | create: (_) => ThemeProvider(), 47 | child: Consumer( 48 | builder: (_, provider, __) { 49 | return MaterialApp( 50 | title: '我要点东西', 51 | theme: provider.getTheme(), 52 | darkTheme: provider.getTheme(isDarkMode: true), 53 | themeMode: provider.getThemeMode(), 54 | home: HomePage(), 55 | ); 56 | }, 57 | ), 58 | ), 59 | ); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /lib/provider/base_list_provider.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:order/widgets/state_layout.dart'; 3 | 4 | class BaseListProvider extends ChangeNotifier { 5 | 6 | final List _list = []; 7 | List get list => _list; 8 | 9 | StateType _stateType = StateType.loading; 10 | bool _hasMore = true; 11 | 12 | StateType get stateType => _stateType; 13 | bool get hasMore => _hasMore; 14 | 15 | void setStateTypeNotNotify(StateType stateType) { 16 | _stateType = stateType; 17 | } 18 | 19 | void setStateType(StateType stateType) { 20 | _stateType = stateType; 21 | notifyListeners(); 22 | } 23 | 24 | void setHasMore(bool hasMore) { 25 | _hasMore = hasMore; 26 | } 27 | 28 | void add(T data) { 29 | _list.add(data); 30 | notifyListeners(); 31 | } 32 | 33 | void addAll(List data) { 34 | _list.addAll(data); 35 | notifyListeners(); 36 | } 37 | 38 | void insert(int i, T data) { 39 | _list.insert(i, data); 40 | notifyListeners(); 41 | } 42 | 43 | void insertAll(int i, List data) { 44 | _list.insertAll(i, data); 45 | notifyListeners(); 46 | } 47 | 48 | void remove(T data) { 49 | _list.remove(data); 50 | notifyListeners(); 51 | } 52 | 53 | void removeAt(int i) { 54 | _list.removeAt(i); 55 | notifyListeners(); 56 | } 57 | 58 | void clear() { 59 | _list.clear(); 60 | notifyListeners(); 61 | } 62 | 63 | void refresh() { 64 | notifyListeners(); 65 | } 66 | } -------------------------------------------------------------------------------- /lib/provider/theme_provider.dart: -------------------------------------------------------------------------------- 1 | import 'package:flustars/flustars.dart'; 2 | import 'package:flutter/cupertino.dart'; 3 | import 'package:flutter/material.dart'; 4 | import 'package:order/common/common.dart'; 5 | import 'package:order/res/resources.dart'; 6 | 7 | class ThemeProvider extends ChangeNotifier { 8 | static const Map themes = { 9 | ThemeMode.dark: 'Dark', 10 | ThemeMode.light: 'Light', 11 | ThemeMode.system: 'System' 12 | }; 13 | 14 | void syncTheme() { 15 | String theme = SpUtil.getString(Constant.theme); 16 | if (theme.isNotEmpty && theme != themes[ThemeMode.system]) { 17 | notifyListeners(); 18 | } 19 | } 20 | 21 | void setTheme(ThemeMode themeMode) { 22 | SpUtil.putString(Constant.theme, themes[themeMode]); 23 | notifyListeners(); 24 | } 25 | 26 | ThemeMode getThemeMode() { 27 | String theme = SpUtil.getString(Constant.theme); 28 | switch (theme) { 29 | case 'Dark': 30 | return ThemeMode.dark; 31 | case 'Light': 32 | return ThemeMode.light; 33 | default: 34 | return ThemeMode.system; 35 | } 36 | } 37 | 38 | getTheme({bool isDarkMode:false}){ 39 | return ThemeData( 40 | errorColor: isDarkMode ? Colours.dark_red : Colours.red, 41 | brightness: isDarkMode ? Brightness.dark : Brightness.light, 42 | primaryColor: isDarkMode ? Colours.dark_app_main : Colours.app_main, 43 | accentColor: isDarkMode ? Colours.dark_app_main : Colours.app_main, 44 | // Tab指示器颜色 45 | indicatorColor: isDarkMode ? Colours.dark_app_main : Colours.app_main, 46 | // 页面背景色 47 | scaffoldBackgroundColor: isDarkMode ? Colours.dark_bg_color : Colors.white, 48 | // 主要用于Material背景色 49 | canvasColor: isDarkMode ? Colours.dark_material_bg : Colors.white, 50 | // 文字选择色(输入框复制粘贴菜单) 51 | textSelectionColor: Colours.app_main.withAlpha(70), 52 | textSelectionHandleColor: Colours.app_main, 53 | textTheme: TextTheme( 54 | // TextField输入文字颜色 55 | subhead: isDarkMode ? TextStyles.textDark : TextStyles.text, 56 | // Text文字样式 57 | body1: isDarkMode ? TextStyles.textDark : TextStyles.text, 58 | subtitle: isDarkMode ? TextStyles.textDarkGray12 : TextStyles.textGray12, 59 | ), 60 | inputDecorationTheme: InputDecorationTheme( 61 | hintStyle: isDarkMode ? TextStyles.textHint14 : TextStyles.textDarkGray14, 62 | ), 63 | appBarTheme: AppBarTheme( 64 | elevation: 0.0, 65 | color: isDarkMode ? Colours.dark_bg_color : Colors.white, 66 | brightness: isDarkMode ? Brightness.dark : Brightness.light, 67 | ), 68 | dividerTheme: DividerThemeData( 69 | color: isDarkMode ? Colours.dark_line : Colours.line, 70 | space: 0.6, 71 | thickness: 0.6 72 | ), 73 | cupertinoOverrideTheme: CupertinoThemeData( 74 | brightness: isDarkMode ? Brightness.dark : Brightness.light, 75 | ) 76 | ); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /lib/res/colors.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class Colours { 4 | static const Color app_main = Color(0xFF4688FA); 5 | static const Color dark_app_main = Color(0xFF3F7AE0); 6 | 7 | static const Color bg_color = Color(0xfff1f1f1); 8 | static const Color dark_bg_color = Color(0xFF18191A); 9 | 10 | static const Color material_bg = Color(0xFFFFFFFF); 11 | static const Color dark_material_bg = Color(0xFF303233); 12 | 13 | static const Color text = Color(0xFF333333); 14 | static const Color dark_text = Color(0xFFB8B8B8); 15 | 16 | static const Color black_text = Color(0xFF000000); 17 | 18 | static const Color text_gray = Color(0xFF999999); 19 | static const Color dark_text_gray = Color(0xFF666666); 20 | 21 | static const Color text_gray_c = Color(0xFFcccccc); 22 | static const Color dark_button_text = Color(0xFFF2F2F2); 23 | 24 | static const Color bg_gray = Color(0xFFF6F6F6); 25 | static const Color dark_bg_gray = Color(0xFF1F1F1F); 26 | 27 | static const Color line = Color(0xFFEEEEEE); 28 | static const Color dark_line = Color(0xFF3A3C3D); 29 | 30 | static const Color red = Color(0xFFFF4759); 31 | static const Color dark_red = Color(0xFFE03E4E); 32 | 33 | static const Color text_disabled = Color(0xFFD4E2FA); 34 | static const Color dark_text_disabled = Color(0xFFCEDBF2); 35 | 36 | static const Color button_disabled = Color(0xFF96BBFA); 37 | static const Color dark_button_disabled = Color(0xFF83A5E0); 38 | 39 | static const Color unselected_item_color = Color(0xffbfbfbf); 40 | static const Color dark_unselected_item_color = Color(0xFF4D4D4D); 41 | 42 | static const Color bg_gray_ = Color(0xFFFAFAFA); 43 | static const Color dark_bg_gray_ = Color(0xFF242526); 44 | 45 | static const Color green_ordered = Color(0xFF36D98D); 46 | static const Color line_gray = Color(0xFFF1F1F1); 47 | static const Color order_red_bg = Color(0xFFFE492E); 48 | static const Color yellow_shallow = Color(0xFFFFF6E9); 49 | static const Color yellow = Color(0xFFFFBD67); 50 | static const Color button_start_blue = Color(0xFF50B5FF); 51 | static const Color button_blue = Color(0xFF2567FE); 52 | static const Color button_border_blue = Color(0xFF3D7DE9); 53 | static const Color button_border_red = Color(0xFFE93D3D); 54 | static const Color button_start_red = Color(0xFFFF9692); 55 | } 56 | -------------------------------------------------------------------------------- /lib/res/dimens.dart: -------------------------------------------------------------------------------- 1 | class Dimens { 2 | static const double font_sp10 = 10.0; 3 | static const double font_sp12 = 12.0; 4 | static const double font_sp14 = 14.0; 5 | static const double font_sp15 = 15.0; 6 | static const double font_sp16 = 16.0; 7 | static const double font_sp18 = 18.0; 8 | 9 | static const double gap_dp4 = 4; 10 | static const double gap_dp5 = 5; 11 | static const double gap_dp8 = 8; 12 | static const double gap_dp10 = 10; 13 | static const double gap_dp12 = 12; 14 | static const double gap_dp15 = 15; 15 | static const double gap_dp16 = 16; 16 | static const double gap_dp24 = 24; 17 | static const double gap_dp32 = 32; 18 | static const double gap_dp50 = 50; 19 | } 20 | -------------------------------------------------------------------------------- /lib/res/gaps.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter/widgets.dart'; 3 | 4 | import '../util/theme_utils.dart'; 5 | import 'colors.dart'; 6 | import 'colors.dart'; 7 | import 'dimens.dart'; 8 | 9 | /// 间隔 10 | class Gaps { 11 | /// 水平间隔 12 | static const Widget hGap4 = const SizedBox(width: Dimens.gap_dp4); 13 | static const Widget hGap5 = const SizedBox(width: Dimens.gap_dp5); 14 | static const Widget hGap8 = const SizedBox(width: Dimens.gap_dp8); 15 | static const Widget hGap10 = const SizedBox(width: Dimens.gap_dp10); 16 | static const Widget hGap12 = const SizedBox(width: Dimens.gap_dp12); 17 | static const Widget hGap15 = const SizedBox(width: Dimens.gap_dp15); 18 | static const Widget hGap16 = const SizedBox(width: Dimens.gap_dp16); 19 | static const Widget hGap32 = const SizedBox(width: Dimens.gap_dp32); 20 | 21 | /// 垂直间隔 22 | static const Widget vGap1 = const SizedBox(height: 1.0); 23 | static const Widget vGap4 = const SizedBox(height: 4.0); 24 | static const Widget vGap5 = const SizedBox(height: Dimens.gap_dp5); 25 | static const Widget vGap8 = const SizedBox(height: 8.0); 26 | static const Widget vGap10 = const SizedBox(height: Dimens.gap_dp10); 27 | static const Widget vGap20 = const SizedBox(height: 20.0); 28 | static const Widget vGap12 = const SizedBox(height: 12.0); 29 | static const Widget vGap15 = const SizedBox(height: Dimens.gap_dp15); 30 | static const Widget vGap16 = const SizedBox(height: Dimens.gap_dp16); 31 | static const Widget vGap24 = const SizedBox(height: Dimens.gap_dp24); 32 | static const Widget vGap32 = const SizedBox(height: Dimens.gap_dp32); 33 | static const Widget vGap50 = const SizedBox(height: Dimens.gap_dp50); 34 | 35 | // static Widget line = const SizedBox( 36 | // height: 0.6, 37 | // width: double.infinity, 38 | // child: const DecoratedBox(decoration: BoxDecoration(color: Colours.line)), 39 | // ); 40 | 41 | static Widget line = const Divider(); 42 | 43 | static Widget vLine = const SizedBox( 44 | width: 0.6, 45 | height: 24.0, 46 | child: const VerticalDivider(), 47 | ); 48 | 49 | static const Widget empty = const SizedBox.shrink(); 50 | 51 | static Widget vLine10(BuildContext context) { 52 | return SizedBox( 53 | height: 10.0, 54 | width: double.infinity, 55 | child: DecoratedBox( 56 | decoration: BoxDecoration( 57 | color: ThemeUtils.isDark(context) 58 | ? Colours.dark_material_bg 59 | : Colours.bg_gray_)), 60 | ); 61 | } 62 | 63 | //慎用-需要父控件占满高度 64 | static Widget hLine10(BuildContext context) { 65 | return SizedBox( 66 | height: double.infinity, 67 | width: 10.0, 68 | child: DecoratedBox( 69 | decoration: BoxDecoration( 70 | color: ThemeUtils.isDark(context) 71 | ? Colours.dark_material_bg 72 | : Colours.bg_gray_)), 73 | ); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /lib/res/resources.dart: -------------------------------------------------------------------------------- 1 | export 'colors.dart'; 2 | export 'dimens.dart'; 3 | export 'styles.dart'; 4 | export 'gaps.dart'; 5 | 6 | import 'package:flutter/widgets.dart'; 7 | import 'package:order/widgets/load_image.dart'; 8 | 9 | class Images{ 10 | static const Widget arrowRight = const LoadAssetImage('ic_arrow_right', height: 16.0, width: 16.0); 11 | 12 | } -------------------------------------------------------------------------------- /lib/res/styles.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter/widgets.dart'; 3 | 4 | import 'colors.dart'; 5 | import 'dimens.dart'; 6 | 7 | class TextStyles { 8 | 9 | static const TextStyle textSize12 = const TextStyle( 10 | color: Colours.text, 11 | fontSize: Dimens.font_sp12, 12 | ); 13 | static const TextStyle textSize16 = const TextStyle( 14 | color: Colours.text, 15 | fontSize: Dimens.font_sp16, 16 | ); 17 | static const TextStyle textBold14 = const TextStyle( 18 | fontSize: Dimens.font_sp14, 19 | fontWeight: FontWeight.bold 20 | ); 21 | static const TextStyle textBold16 = const TextStyle( 22 | fontSize: Dimens.font_sp16, 23 | fontWeight: FontWeight.bold 24 | ); 25 | static const TextStyle textBold18 = const TextStyle( 26 | fontSize: Dimens.font_sp18, 27 | fontWeight: FontWeight.bold 28 | ); 29 | 30 | static const TextStyle textWhite18 = const TextStyle( 31 | fontSize: Dimens.font_sp18, 32 | color: Colors.white 33 | ); 34 | 35 | static const TextStyle textBold24 = const TextStyle( 36 | fontSize: 24.0, 37 | fontWeight: FontWeight.bold 38 | ); 39 | static const TextStyle textBold26 = const TextStyle( 40 | fontSize: 26.0, 41 | fontWeight: FontWeight.bold 42 | ); 43 | 44 | static const TextStyle textGray14 = const TextStyle( 45 | fontSize: Dimens.font_sp14, 46 | color: Colours.text_gray, 47 | ); 48 | static const TextStyle textDarkGray14 = const TextStyle( 49 | fontSize: Dimens.font_sp14, 50 | color: Colours.dark_text_gray, 51 | ); 52 | 53 | static const TextStyle textBlack12 = const TextStyle( 54 | fontSize: Dimens.font_sp12, 55 | color: Colours.black_text, 56 | ); 57 | 58 | static const TextStyle textBlack14 = const TextStyle( 59 | fontSize: Dimens.font_sp14, 60 | ); 61 | 62 | static const TextStyle textWhite14 = const TextStyle( 63 | fontSize: Dimens.font_sp14, 64 | color: Colors.white, 65 | ); 66 | 67 | static const TextStyle text = const TextStyle( 68 | fontSize: Dimens.font_sp14, 69 | // https://github.com/flutter/flutter/issues/40248 70 | textBaseline: TextBaseline.alphabetic 71 | ); 72 | static const TextStyle textDark = const TextStyle( 73 | fontSize: Dimens.font_sp14, 74 | color: Colours.dark_text, 75 | textBaseline: TextBaseline.alphabetic 76 | ); 77 | 78 | static const TextStyle textGray10 = const TextStyle( 79 | fontSize: Dimens.font_sp10, 80 | color: Colours.text_gray, 81 | fontWeight: FontWeight.normal 82 | ); 83 | 84 | static const TextStyle textGray12 = const TextStyle( 85 | fontSize: Dimens.font_sp12, 86 | color: Colours.text_gray, 87 | fontWeight: FontWeight.normal 88 | ); 89 | static const TextStyle textDarkGray12 = const TextStyle( 90 | fontSize: Dimens.font_sp12, 91 | color: Colours.dark_text_gray, 92 | fontWeight: FontWeight.normal 93 | ); 94 | 95 | static const TextStyle textHint14 = const TextStyle( 96 | fontSize: Dimens.font_sp14, 97 | color: Colours.dark_unselected_item_color 98 | ); 99 | } 100 | -------------------------------------------------------------------------------- /lib/ui/home_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flustars/flustars.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:order/common/common.dart'; 4 | import 'package:order/res/gaps.dart'; 5 | import 'package:order/res/resources.dart'; 6 | import 'package:order/ui/order/pages/order_page.dart'; 7 | import 'package:order/widgets/label_text.dart'; 8 | import 'package:order/widgets/load_image.dart'; 9 | import 'package:order/widgets/tool_bar.dart'; 10 | 11 | class HomePage extends StatefulWidget { 12 | @override 13 | _HomePageState createState() => _HomePageState(); 14 | } 15 | 16 | class _HomePageState extends State { 17 | var _goodsItem = 0; 18 | 19 | @override 20 | void initState() { 21 | super.initState(); 22 | List goodsList = SpUtil.getObjectList(Constant.cartGoods); 23 | if (goodsList != null && goodsList.isNotEmpty) { 24 | _goodsItem = goodsList.length; 25 | } 26 | // _goodsItem = SpUtil.getObjectList(Constant.cartGoods).length; 27 | } 28 | 29 | @override 30 | Widget build(BuildContext context) { 31 | return Scaffold( 32 | appBar: Toolbar( 33 | centerTitle: '大爷,来点东西玩玩啊', 34 | ), 35 | body: Column( 36 | children: [ 37 | Card( 38 | margin: const EdgeInsets.all(15), 39 | shape: 40 | RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)), 41 | child: InkWell( 42 | child: Padding( 43 | padding: 44 | const EdgeInsets.symmetric(vertical: 15, horizontal: 10), 45 | child: Row( 46 | crossAxisAlignment: CrossAxisAlignment.start, 47 | children: [ 48 | Stack( 49 | children: [ 50 | LoadImage( 51 | 'https://img.meituan.net/msmerchant/77830c7aeb655a5e29bc3905ff7a648c2028276.jpg', 52 | width: 120.0, 53 | height: 80.0, 54 | fit: BoxFit.cover, 55 | ), 56 | Positioned( 57 | right: 0, 58 | bottom: 0, 59 | child: Visibility( 60 | visible: _goodsItem != 0, 61 | child: LabelText( 62 | shape: BoxShape.circle, 63 | backgroundColor: Colors.red, 64 | text: '$_goodsItem', 65 | padding: const EdgeInsets.all(5), 66 | ), 67 | ), 68 | ) 69 | ], 70 | ), 71 | Gaps.hGap10, 72 | Column( 73 | crossAxisAlignment: CrossAxisAlignment.start, 74 | children: [ 75 | Text('客官里面请', style: TextStyles.textBold18), 76 | Gaps.vGap10, 77 | Text('好酒好菜应有尽有', style: TextStyles.text), 78 | ], 79 | ) 80 | ], 81 | ), 82 | ), 83 | onTap: () { 84 | Navigator.of(context) 85 | .push(MaterialPageRoute(builder: (context) => OrderPage())) 86 | .then((value) { 87 | setState(() { 88 | if (value != null) { 89 | _goodsItem = value; 90 | } 91 | }); 92 | }); 93 | }, 94 | ), 95 | ) 96 | ], 97 | ), 98 | ); 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /lib/ui/order/pages/discount_page.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:flutter/services.dart'; 5 | import 'package:provider/provider.dart'; 6 | import 'package:order/provider/base_list_provider.dart'; 7 | import 'package:order/entity/base_entity.dart'; 8 | import 'package:order/entity/cart_goods_bean.dart'; 9 | import 'package:order/entity/discount_packages.dart'; 10 | import 'package:order/ui/order/widgets/goods_item.dart'; 11 | import 'package:order/widgets/state_layout.dart'; 12 | 13 | class DiscountPage extends StatefulWidget { 14 | @override 15 | _DiscountPageState createState() => _DiscountPageState(); 16 | } 17 | 18 | class _DiscountPageState extends State 19 | with AutomaticKeepAliveClientMixin { 20 | BaseListProvider provider = BaseListProvider(); 21 | 22 | @override 23 | void initState() { 24 | super.initState(); 25 | provider.setStateType(StateType.loading); 26 | getDiscountPackages(); 27 | } 28 | 29 | void getDiscountPackages() { 30 | 31 | try { 32 | rootBundle.loadString('assets/data/discount.json').then((value) { 33 | BaseEntity result = 34 | BaseEntity.fromJson(jsonDecode(value)); 35 | 36 | if (result != null) { 37 | if (result.listData.isEmpty || result.listData == null) { 38 | provider.setStateType(StateType.empty); 39 | } else { 40 | provider.addAll(result.listData); 41 | } 42 | } else { 43 | provider.setStateType(StateType.empty); 44 | } 45 | }); 46 | } catch (e) { 47 | print(e); 48 | provider.setStateType(StateType.error); 49 | } 50 | } 51 | 52 | @override 53 | Widget build(BuildContext context) { 54 | super.build(context); 55 | return ChangeNotifierProvider>( 56 | create: (_) => provider, 57 | child: Consumer>( 58 | builder: (context, provider, child) { 59 | return provider.list.isEmpty 60 | ? StateLayout(type: provider.stateType) 61 | : ListView.builder( 62 | shrinkWrap: true, 63 | padding: const EdgeInsets.only(bottom: 49), 64 | physics: BouncingScrollPhysics(), 65 | itemCount: provider.list.length, 66 | itemBuilder: (context, index) { 67 | DiscountPackages discountPackages = provider.list[index]; 68 | CartGoodsBean cartGoodsBean = CartGoodsBean( 69 | id: discountPackages.id, 70 | img: discountPackages.img, 71 | name: discountPackages.name, 72 | description: discountPackages.description, 73 | price: discountPackages.price, 74 | type: 2, 75 | propertyList: discountPackages.propertyList); 76 | return GoodsItem(data: cartGoodsBean); 77 | }); 78 | }, 79 | ), 80 | ); 81 | } 82 | 83 | @override 84 | bool get wantKeepAlive => true; 85 | } 86 | -------------------------------------------------------------------------------- /lib/ui/order/pages/goods_detail_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:order/ui/order/widgets/goods_options.dart'; 3 | import 'package:order/widgets/preferred_hero_img_bar.dart'; 4 | import '../../../entity/cart_goods_bean.dart'; 5 | import '../../../res/resources.dart'; 6 | import '../../../util/utils.dart'; 7 | 8 | ///商品详情页 9 | class GoodsDetailsPage extends StatefulWidget { 10 | final CartGoodsBean data; 11 | 12 | GoodsDetailsPage({Key key, this.data}) : super(key: key); 13 | 14 | @override 15 | _GoodsDetailsPageState createState() => _GoodsDetailsPageState(); 16 | } 17 | 18 | class _GoodsDetailsPageState extends State { 19 | @override 20 | Widget build(BuildContext context) { 21 | return Scaffold( 22 | ///封装的appBar 23 | appBar: PreferredHeroImgBar( 24 | tag: widget.data, imageUrl: '${widget.data.img}'), 25 | body: Padding( 26 | padding: const EdgeInsets.all(12.0), 27 | child: Column( 28 | crossAxisAlignment: CrossAxisAlignment.start, 29 | children: [ 30 | Text( 31 | widget.data.name, 32 | style: TextStyles.textBold24, 33 | overflow: TextOverflow.ellipsis, 34 | maxLines: 1, 35 | ), 36 | const SizedBox(height: 2.5), 37 | Text( 38 | '月销 100', 39 | style: TextStyles.textGray14, 40 | ), 41 | const SizedBox(height: 2.5), 42 | Text('${widget.data.description}', 43 | style: TextStyles.textGray14, 44 | overflow: TextOverflow.ellipsis, 45 | maxLines: 1), 46 | Row( 47 | crossAxisAlignment: CrossAxisAlignment.end, 48 | children: [ 49 | RichText( 50 | text: TextSpan(children: [ 51 | TextSpan( 52 | text: '¥', 53 | style: TextStyle( 54 | fontSize: Dimens.font_sp16, 55 | color: Colors.red)), 56 | TextSpan( 57 | text: 58 | '${Utils.formatPriceWithoutSymbol(widget.data.price.toString())}', 59 | style: TextStyle( 60 | fontSize: 21, 61 | color: Colors.red, 62 | fontFamily: 'DINRegular')), 63 | ]), 64 | ), 65 | Spacer(), 66 | //商品的加减操作 67 | GoodsOptions(data: widget.data) 68 | ], 69 | ), 70 | ], 71 | ), 72 | ), 73 | ); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /lib/ui/order/pages/order_page.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | import 'package:flustars/flustars.dart'; 4 | import 'package:flutter/material.dart'; 5 | import 'package:flutter/services.dart'; 6 | import 'package:order/common/common.dart'; 7 | import 'package:order/entity/base_entity.dart'; 8 | import 'package:order/entity/property.dart'; 9 | import 'package:order/entity/sort.dart'; 10 | import 'package:order/res/resources.dart'; 11 | import 'package:order/ui/order/pages/sort_page.dart'; 12 | import 'package:order/ui/order/pages/sort_right_page.dart'; 13 | import 'package:order/ui/order/provider/ball_anim_provider.dart'; 14 | import 'package:order/ui/order/provider/order_provider.dart'; 15 | import 'package:order/ui/order/widgets/shop_cart.dart'; 16 | import 'package:order/ui/order/widgets/shop_cart_list.dart'; 17 | import 'package:order/ui/order/widgets/throw_ball_anim.dart'; 18 | import 'package:order/widgets/search_bar.dart'; 19 | import 'package:provider/provider.dart'; 20 | 21 | import '../../../util/navigator_utils.dart'; 22 | 23 | class OrderPage extends StatefulWidget { 24 | @override 25 | _OrderPageState createState() => _OrderPageState(); 26 | } 27 | 28 | class _OrderPageState extends State { 29 | PageController _pageController = PageController(); 30 | 31 | ///管理点单功能Navigator的key 32 | GlobalKey navigatorKey = GlobalKey(); 33 | 34 | ///点单功能的provider实现了商品的增减,缓存,购物车列表回调等 35 | OrderProvider provider = OrderProvider(); 36 | 37 | bool _isShow = false; 38 | 39 | ///购物车不为空的情况下,点击购物车图标会触发的回调 40 | Future showShopCarListCallback() async { 41 | _isShow = !_isShow; 42 | if (_isShow) { 43 | // 显示 44 | await ShopCartList.start(navigatorKey.currentState); 45 | // 已经关闭 46 | _isShow = false; 47 | } else { 48 | // 关闭 49 | navigatorKey.currentState.pop(); 50 | } 51 | } 52 | 53 | @override 54 | void initState() { 55 | super.initState(); 56 | //初始化栏目分类数据以及缓存商品属性 57 | getSortList(); 58 | } 59 | 60 | void getSortList() { 61 | rootBundle.loadString('assets/data/sort.json').then((value) { 62 | BaseEntity result = BaseEntity.fromJson(jsonDecode(value)); 63 | 64 | //删选出一级菜单 65 | List sortList = 66 | result.listData.where((element) => element.parentId == -1).toList(); 67 | 68 | //增加一个名为菜单的一级菜单 69 | sortList.insert(0, Sort(typeName: '套餐', id: -1)); 70 | 71 | //删除出二级菜单 72 | List subSortList = 73 | result.listData.where((element) => element.parentId != -1).toList(); 74 | 75 | provider.setSortList(sortList); 76 | 77 | provider.setSubSortList(subSortList); 78 | }).then((value) { 79 | getPropertyList(); 80 | }); 81 | } 82 | 83 | void getPropertyList() { 84 | rootBundle.loadString('assets/data/property.json').then((value) { 85 | //将商品属性存储下来 86 | BaseEntity result = BaseEntity.fromJson(jsonDecode(value)); 87 | if (result.listData != null && result.listData.isNotEmpty) { 88 | SpUtil.putObjectList(Constant.property, result.listData); 89 | } 90 | }); 91 | } 92 | 93 | @override 94 | Widget build(BuildContext context) { 95 | //多Provider的使用 96 | return MultiProvider( 97 | providers: [ 98 | ChangeNotifierProvider(create: (_) { 99 | provider.showShopCarListCallback = showShopCarListCallback; 100 | return provider; 101 | }), 102 | ChangeNotifierProvider( 103 | create: (_) => BallAnimProvider(), 104 | ) 105 | ], 106 | child: WillPopScope( 107 | onWillPop: () { 108 | //监听系统返回键,先对自定义Navigator里的路由做出栈处理,最后关闭OrderPage 109 | navigatorKey.currentState.maybePop().then((value) { 110 | if (!value) { 111 | NavigatorUtils.goBackWithParams( 112 | context, provider.cartGoodsList.length); 113 | } 114 | }); 115 | return Future.value(false); 116 | }, 117 | child: Stack( 118 | children: [ 119 | Navigator( 120 | key: navigatorKey, 121 | //自定Navigator使用不了Hero的解决方案 122 | observers: [HeroController()], 123 | onGenerateRoute: (settings) { 124 | if (settings.name == '/') { 125 | return PageRouteBuilder( 126 | opaque: false, 127 | pageBuilder: 128 | (childContext, animation, secondaryAnimation) => 129 | _buildContent(childContext), 130 | transitionsBuilder: 131 | (context, animation, secondaryAnimation, child) => 132 | FadeTransition(opacity: animation, child: child), 133 | transitionDuration: Duration(milliseconds: 300), 134 | ); 135 | } 136 | return null; 137 | }, 138 | ), 139 | Positioned( 140 | bottom: 0, 141 | right: 0, 142 | left: 0, 143 | child: ShopCart(), 144 | ), 145 | //添加商品进购物车的小球动画 146 | ThrowBallAnim(), 147 | ], 148 | ), 149 | ), 150 | ); 151 | } 152 | 153 | //构建内容层 154 | Widget _buildContent(BuildContext childContext) { 155 | return Scaffold( 156 | //弹出键盘不拉起底部布局 157 | resizeToAvoidBottomInset: false, 158 | appBar: SearchBar( 159 | hintText: '请输入关键字', 160 | isSearch: false, 161 | onBack: () => Navigator.maybePop(context)), 162 | body: Column( 163 | crossAxisAlignment: CrossAxisAlignment.start, 164 | children: [ 165 | Gaps.vLine10(context), 166 | //占满剩余高度 167 | Expanded( 168 | child: Row( 169 | mainAxisAlignment: MainAxisAlignment.start, 170 | crossAxisAlignment: CrossAxisAlignment.start, 171 | children: [ 172 | Expanded( 173 | flex: 59, 174 | child: SingleChildScrollView( 175 | physics: BouncingScrollPhysics(), 176 | child: Padding( 177 | padding: EdgeInsets.only( 178 | bottom: 80 + MediaQuery.of(context).padding.bottom), 179 | child: 180 | Consumer(builder: (_, provider, __) { 181 | return SortPage( 182 | itemHeight: 43.0, 183 | items: 184 | provider.sortList.map((e) => e.typeName).toList(), 185 | sortTaped: (index) { 186 | _pageController.jumpToPage(index); 187 | }, 188 | ); 189 | }), 190 | ), 191 | ), 192 | ), 193 | Gaps.hLine10(context), 194 | Expanded( 195 | flex: 306, 196 | child: Consumer( 197 | builder: (_, provider, __) { 198 | return PageView.builder( 199 | controller: _pageController, 200 | physics: NeverScrollableScrollPhysics(), 201 | itemCount: provider.sortList.length, 202 | scrollDirection: Axis.vertical, 203 | itemBuilder: (_, index) { 204 | int parentId = provider.sortList[index].id; 205 | List list = provider.subSortMap[parentId]; 206 | return SortRightPage( 207 | key: Key('SortRightPage$parentId'), 208 | parentId: parentId, 209 | data: list); 210 | }); 211 | }, 212 | ), 213 | ) 214 | ], 215 | ), 216 | ) 217 | ], 218 | ), 219 | ); 220 | } 221 | } 222 | -------------------------------------------------------------------------------- /lib/ui/order/pages/sort_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/foundation.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:order/res/resources.dart'; 4 | import 'package:order/util/color_utils.dart'; 5 | 6 | import '../../../res/colors.dart'; 7 | import '../../../util/theme_utils.dart'; 8 | import '../../../util/theme_utils.dart'; 9 | import '../../../util/theme_utils.dart'; 10 | 11 | class SortPage extends StatefulWidget { 12 | final List items; 13 | final double itemHeight; 14 | final ValueChanged sortTaped; 15 | 16 | SortPage({Key key, this.items, this.itemHeight, this.sortTaped}) 17 | : super(key: key); 18 | 19 | @override 20 | SortPageState createState() => SortPageState(); 21 | } 22 | 23 | class SortPageState extends State 24 | with SingleTickerProviderStateMixin { 25 | Animation animation; 26 | AnimationController controller; 27 | int currentItemIndex = 0; 28 | 29 | @override 30 | Widget build(BuildContext context) { 31 | int len = widget.items.length; 32 | List widgets = List.generate(len, (i) { 33 | return InkWell( 34 | onTap: () { 35 | if (currentItemIndex == i) return; 36 | _sortTaped(i); 37 | }, 38 | child: Container( 39 | height: widget.itemHeight, 40 | alignment: Alignment.center, 41 | child: Text( 42 | widget.items[i], 43 | style: TextStyle( 44 | fontSize: 45 | currentItemIndex == i ? Dimens.font_sp16 : Dimens.font_sp14, 46 | color: currentItemIndex == i 47 | ? ColorUtils.hexToColor('#2567FE') 48 | : null), 49 | ), 50 | ), 51 | ); 52 | }); 53 | return Stack( 54 | children: [ 55 | Positioned( 56 | width: 75, 57 | height: widget.itemHeight, 58 | top: animation.value, 59 | child: Stack( 60 | children: [ 61 | Container( 62 | color: ThemeUtils.isDark(context) 63 | ? Colours.dark_material_bg 64 | : Colours.bg_gray_), 65 | Positioned( 66 | top: 12.5, 67 | child: Container( 68 | width: 3, 69 | height: 20, 70 | color: ColorUtils.hexToColor('#2567FE'), 71 | ), 72 | ) 73 | ], 74 | ), 75 | ), 76 | Column(children: widgets) 77 | ], 78 | ); 79 | } 80 | 81 | _sortTaped(int index) { 82 | widget.sortTaped(index); 83 | moveToTap(index); 84 | } 85 | 86 | moveToTap(int index) { 87 | double begin = currentItemIndex * widget.itemHeight; 88 | double end = index * widget.itemHeight; 89 | animation = Tween(begin: begin, end: end) 90 | .animate(CurvedAnimation(parent: controller, curve: Curves.linear)); 91 | controller.addStatusListener((status) { 92 | if (AnimationStatus.completed == status) { 93 | currentItemIndex = index; 94 | } 95 | }); 96 | 97 | controller.forward(from: 0); 98 | } 99 | 100 | @override 101 | void initState() { 102 | controller = 103 | AnimationController(duration: Duration(microseconds: 150), vsync: this); 104 | animation = Tween(begin: 0.0, end: 0.0).animate(controller); 105 | controller.addListener(() { 106 | if (mounted) { 107 | setState(() {}); 108 | } 109 | }); 110 | controller.forward(); 111 | super.initState(); 112 | } 113 | 114 | @override 115 | void dispose() { 116 | controller.dispose(); 117 | super.dispose(); 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /lib/ui/order/pages/sort_right_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:order/entity/sort.dart'; 3 | import 'package:order/ui/order/pages/discount_page.dart'; 4 | import 'package:order/ui/order/pages/sub_item_page.dart'; 5 | import 'package:order/ui/order/pages/sub_list_page.dart'; 6 | 7 | class SortRightPage extends StatefulWidget { 8 | final int parentId; 9 | final List data; 10 | 11 | SortRightPage( 12 | {Key key, 13 | this.parentId, 14 | this.data}) 15 | : super(key: key); 16 | 17 | @override 18 | _SortRightPageState createState() => _SortRightPageState(); 19 | } 20 | 21 | class _SortRightPageState extends State 22 | with AutomaticKeepAliveClientMixin { 23 | @override 24 | Widget build(BuildContext context) { 25 | super.build(context); 26 | if (widget.data == null || widget.data.isEmpty) { 27 | if (widget.parentId == -1) { 28 | //套餐Page 29 | return DiscountPage(); 30 | } else { 31 | //商品列表 32 | return SubItemPage( 33 | key: Key('subItem${widget.parentId}'), 34 | id: widget.parentId 35 | ); 36 | } 37 | } else { 38 | //二级分类 39 | return SubListPage( 40 | key: Key('subList${widget.parentId}'), 41 | data: widget.data 42 | ); 43 | } 44 | } 45 | 46 | @override 47 | bool get wantKeepAlive => true; 48 | } 49 | -------------------------------------------------------------------------------- /lib/ui/order/pages/sub_item_page.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:flutter/services.dart'; 5 | import 'package:order/entity/base_entity.dart'; 6 | import 'package:order/entity/cart_goods_bean.dart'; 7 | import 'package:order/entity/goods.dart'; 8 | import 'package:order/provider/base_list_provider.dart'; 9 | import 'package:order/ui/order/widgets/goods_item.dart'; 10 | import 'package:order/widgets/state_layout.dart'; 11 | import 'package:provider/provider.dart'; 12 | 13 | class SubItemPage extends StatefulWidget { 14 | final int id; 15 | 16 | SubItemPage({Key key, this.id}) : super(key: key); 17 | 18 | @override 19 | _SubItemPageState createState() => _SubItemPageState(); 20 | } 21 | 22 | class _SubItemPageState extends State 23 | with AutomaticKeepAliveClientMixin { 24 | BaseListProvider provider = BaseListProvider(); 25 | 26 | @override 27 | void initState() { 28 | super.initState(); 29 | provider.setStateType(StateType.loading); 30 | getGoodsList(widget.id); 31 | } 32 | 33 | void getGoodsList(int typeId) { 34 | try { 35 | rootBundle.loadString('assets/data/goods_$typeId.json').then((value) { 36 | BaseEntity result = BaseEntity.fromJson(jsonDecode(value)); 37 | 38 | if (result != null) { 39 | if (result.listData.isEmpty || result.listData == null) { 40 | provider.setStateType(StateType.empty); 41 | } else { 42 | provider.addAll(result.listData); 43 | } 44 | } else { 45 | provider.setStateType(StateType.empty); 46 | } 47 | }); 48 | } catch (e) { 49 | print(e); 50 | provider.setStateType(StateType.error); 51 | } 52 | } 53 | 54 | @override 55 | Widget build(BuildContext context) { 56 | super.build(context); 57 | return ChangeNotifierProvider>( 58 | create: (_) => provider, 59 | child: Consumer>( 60 | builder: (context, provider, child) { 61 | return provider.list.isEmpty 62 | ? StateLayout(type: provider.stateType) 63 | : ListView.builder( 64 | shrinkWrap: true, 65 | padding: EdgeInsets.only( 66 | bottom: 59 + MediaQuery.of(context).padding.bottom), 67 | physics: BouncingScrollPhysics(), 68 | itemCount: provider.list.length, 69 | itemBuilder: (context, index) { 70 | GoodsBean goodsBean = provider.list[index]; 71 | CartGoodsBean cartGoodsBean = CartGoodsBean( 72 | id: goodsBean.id, 73 | img: goodsBean.img, 74 | name: goodsBean.goodsName, 75 | description: goodsBean.description, 76 | price: goodsBean.price, 77 | type: 1, 78 | propertyList: goodsBean.propertyList); 79 | return GoodsItem(data: cartGoodsBean); 80 | }); 81 | }, 82 | ), 83 | ); 84 | } 85 | 86 | @override 87 | bool get wantKeepAlive => true; 88 | } 89 | -------------------------------------------------------------------------------- /lib/ui/order/pages/sub_list_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:order/entity/sort.dart'; 3 | import 'package:order/res/resources.dart'; 4 | import 'package:order/ui/order/pages/sub_item_page.dart'; 5 | import 'package:order/util/color_utils.dart'; 6 | import 'package:order/widgets/md2_tab_indicator.dart'; 7 | 8 | class SubListPage extends StatefulWidget { 9 | final List data; 10 | 11 | SubListPage({Key key, this.data}) 12 | : super(key: key); 13 | 14 | @override 15 | _SubListPageState createState() => _SubListPageState(); 16 | } 17 | 18 | class _SubListPageState extends State 19 | with TickerProviderStateMixin, AutomaticKeepAliveClientMixin { 20 | TabController _tabController; 21 | PageController _pageController; 22 | 23 | @override 24 | void initState() { 25 | super.initState(); 26 | _tabController = 27 | TabController(initialIndex: 0, length: widget.data.length, vsync: this); 28 | _pageController = PageController(initialPage: 0, keepPage: true); 29 | } 30 | 31 | @override 32 | Widget build(BuildContext context) { 33 | super.build(context); 34 | return Column( 35 | crossAxisAlignment: CrossAxisAlignment.start, 36 | children: [ 37 | TabBar( 38 | controller: _tabController, 39 | labelStyle: TextStyle( 40 | fontSize: Dimens.font_sp14, fontWeight: FontWeight.w700), 41 | labelColor: ColorUtils.hexToColor('#2567FE'), 42 | unselectedLabelColor: Colours.text_gray, 43 | isScrollable: true, 44 | indicator: MD2Indicator( 45 | indicatorHeight: 3, 46 | indicatorColor: ColorUtils.hexToColor('#2567FE'), 47 | indicatorSize: MD2IndicatorSize.normal), 48 | tabs: widget.data.map((e) => Tab(text: e.typeName)).toList(), 49 | onTap: (index) { 50 | if (!mounted) { 51 | return; 52 | } 53 | _pageController.jumpToPage(index); 54 | }, 55 | ), 56 | Expanded( 57 | child: PageView.builder( 58 | physics: BouncingScrollPhysics(), 59 | itemCount: widget.data.length, 60 | controller: _pageController, 61 | onPageChanged: (index) { 62 | _tabController.animateTo(index); 63 | }, 64 | itemBuilder: (_, index) { 65 | //商品列表 66 | return SubItemPage( 67 | key: Key('subItem${widget.data[index].id}'), 68 | id: widget.data[index].id 69 | ); 70 | }), 71 | ) 72 | ], 73 | ); 74 | } 75 | 76 | @override 77 | bool get wantKeepAlive => true; 78 | 79 | @override 80 | void dispose() { 81 | _tabController?.dispose(); 82 | _pageController?.dispose(); 83 | super.dispose(); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /lib/ui/order/provider/ball_anim_provider.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class BallAnimProvider extends ChangeNotifier { 4 | //能显示的小球的总个数 5 | static int ballCount = 10; 6 | 7 | Size _ballSize = Size.zero; 8 | 9 | set ballSize(Size ballSize) { 10 | this._ballSize = ballSize; 11 | } 12 | 13 | Size get ballSize => _ballSize; 14 | 15 | // 小球位置 16 | Offset _ballPosition = Offset.zero; 17 | 18 | set ballPosition(Offset ballPosition) { 19 | this._ballPosition = ballPosition; 20 | } 21 | 22 | Offset get ballPosition => _ballPosition; 23 | 24 | // 购物车大小 25 | Size _shopCarSize = Size.zero; 26 | 27 | set shopCarSize(Size shopCarSize) { 28 | this._shopCarSize = shopCarSize; 29 | } 30 | 31 | Size get shopCarSize => _shopCarSize; 32 | 33 | // 购物车位置 34 | Offset _shopCarPosition = Offset.zero; 35 | 36 | set shopCarPosition(Offset shopCarPosition) { 37 | this._shopCarPosition = shopCarPosition; 38 | } 39 | 40 | Offset get shopCarPosition => _shopCarPosition; 41 | 42 | // 指定动画的回调 43 | VoidCallback _notifyStartAnim = () {}; 44 | 45 | set notifyStartAnim(VoidCallback voidCallback) { 46 | this._notifyStartAnim = voidCallback; 47 | } 48 | 49 | VoidCallback get notifyStartAnim => _notifyStartAnim; 50 | } 51 | -------------------------------------------------------------------------------- /lib/ui/order/provider/order_provider.dart: -------------------------------------------------------------------------------- 1 | import 'package:flustars/flustars.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:order/common/common.dart'; 4 | import 'package:order/entity/cart_goods_bean.dart'; 5 | import 'package:order/entity/sort.dart'; 6 | import 'package:order/util/utils.dart'; 7 | 8 | ///点单的状态管理 9 | class OrderProvider extends ChangeNotifier { 10 | ///左侧菜单分类 11 | final List _sortList = []; 12 | 13 | List get sortList => _sortList; 14 | 15 | ///填充左侧菜单和缓存数据 16 | void setSortList(List data) { 17 | _sortList.addAll(data); 18 | 19 | List cartGoods = 20 | SpUtil.getObjList(Constant.cartGoods, (v) => CartGoodsBean.fromJson(v)); 21 | if (cartGoods != null) { 22 | _cartGoodsList.addAll(cartGoods); 23 | } 24 | 25 | List sortGoods = 26 | SpUtil.getObjList(Constant.sortGoods, (v) => CartGoodsBean.fromJson(v)); 27 | 28 | if (sortGoods != null) { 29 | _cartGoodsSort.addAll(sortGoods); 30 | } 31 | 32 | //两个都不为空的情况下,有缓存数据,取出对应的商品数量索引 33 | if (_cartGoodsList.isNotEmpty && _cartGoodsSort.isNotEmpty) { 34 | _cartGoodsSort.forEach((value) { 35 | if (value.properties != null && value.properties.isNotEmpty) { 36 | List goodsWithProMap = _cartGoodsList 37 | .where((goods) => 38 | _buildGoodsPropertyKey(goods) == 39 | _buildGoodsPropertyKey(value)) 40 | .toList(); 41 | if (goodsWithProMap != null) { 42 | _cartGoodsWithPropertyMap[_buildGoodsPropertyKey(value)] = 43 | goodsWithProMap; 44 | } 45 | } else { 46 | List goodsMap = 47 | _cartGoodsList.where((goods) => _buildGoodsKey(goods) == _buildGoodsKey(value)).toList(); 48 | if (goodsMap != null) { 49 | _cartGoodsMap[_buildGoodsKey(value)] = goodsMap; 50 | } 51 | } 52 | }); 53 | } 54 | 55 | notifyListeners(); 56 | } 57 | 58 | ///右侧二级菜单map 59 | final Map> _subSortMap = Map(); 60 | 61 | Map> get subSortMap => _subSortMap; 62 | 63 | ///设置右侧tab的map数据 64 | void setSubSortList(List data) { 65 | for (var value in data) { 66 | if (_subSortMap.containsKey(value.parentId)) { 67 | _subSortMap[value.parentId].add(value); 68 | } else { 69 | List list = []; 70 | list.add(value); 71 | _subSortMap[value.parentId] = list; 72 | } 73 | } 74 | notifyListeners(); 75 | } 76 | 77 | ///添加到购物车的商品 78 | final List _cartGoodsList = []; 79 | 80 | List get cartGoodsList => _cartGoodsList; 81 | 82 | ///维护购物车中的商品分类 83 | final List _cartGoodsSort = []; 84 | 85 | List get cartGoodsSort => _cartGoodsSort; 86 | 87 | ///维护商品规格的map 88 | //没有商品属性 89 | final Map> _cartGoodsMap = Map(); 90 | 91 | //有商品属性 92 | final Map> _cartGoodsWithPropertyMap = Map(); 93 | 94 | ///添加商品 95 | void addCartGoods(CartGoodsBean goods) { 96 | CartGoodsBean cartGoodsBean = CartGoodsBean( 97 | id: goods.id, 98 | img: goods.img, 99 | name: goods.name, 100 | description: goods.description, 101 | price: goods.price, 102 | type: goods.type, 103 | properties: goods.properties, 104 | propertyList: goods.propertyList); 105 | _cartGoodsList.add(cartGoodsBean); 106 | if (goods.properties != null && goods.properties.isNotEmpty) { 107 | if (_cartGoodsWithPropertyMap 108 | .containsKey(_buildGoodsPropertyKey(goods))) { 109 | _cartGoodsWithPropertyMap[_buildGoodsPropertyKey(goods)] 110 | .add(cartGoodsBean); 111 | } else { 112 | List list = []; 113 | list.add(cartGoodsBean); 114 | _cartGoodsWithPropertyMap[_buildGoodsPropertyKey(goods)] = list; 115 | _cartGoodsSort.add(cartGoodsBean); 116 | } 117 | } else { 118 | if (_cartGoodsMap.containsKey(_buildGoodsKey(goods))) { 119 | _cartGoodsMap[_buildGoodsKey(goods)].add(cartGoodsBean); 120 | } else { 121 | List list = []; 122 | list.add(cartGoodsBean); 123 | _cartGoodsMap[_buildGoodsKey(goods)] = list; 124 | _cartGoodsSort.add(cartGoodsBean); 125 | } 126 | } 127 | _save(); 128 | notifyListeners(); 129 | } 130 | 131 | ///从购物车中移除商品 132 | void removeCartGoods(CartGoodsBean goods) { 133 | _cartGoodsList.remove(goods); 134 | if (goods.properties != null && goods.properties.isNotEmpty) { 135 | if (_cartGoodsWithPropertyMap 136 | .containsKey(_buildGoodsPropertyKey(goods))) { 137 | _cartGoodsWithPropertyMap[_buildGoodsPropertyKey(goods)].remove(goods); 138 | if (_cartGoodsWithPropertyMap[_buildGoodsPropertyKey(goods)] == null || 139 | _cartGoodsWithPropertyMap[_buildGoodsPropertyKey(goods)].isEmpty) { 140 | _cartGoodsSort.remove(goods); 141 | _cartGoodsWithPropertyMap.remove(_buildGoodsPropertyKey(goods)); 142 | } 143 | } 144 | } else { 145 | if (_cartGoodsMap.containsKey(_buildGoodsKey(goods))) { 146 | _cartGoodsMap[_buildGoodsKey(goods)].remove(goods); 147 | if (_cartGoodsMap[_buildGoodsKey(goods)] == null || 148 | _cartGoodsMap[_buildGoodsKey(goods)].isEmpty) { 149 | _cartGoodsSort.remove(goods); 150 | _cartGoodsMap.remove(_buildGoodsKey(goods)); 151 | } 152 | } 153 | } 154 | _save(); 155 | notifyListeners(); 156 | } 157 | 158 | ///清空购物车 159 | void removeAllCartGoods() { 160 | _removeAll(); 161 | _cartGoodsList.clear(); 162 | _cartGoodsSort.clear(); 163 | _cartGoodsWithPropertyMap.clear(); 164 | _cartGoodsMap.clear(); 165 | notifyListeners(); 166 | } 167 | 168 | ///获得购物车中的商品 169 | List findCartGoods(CartGoodsBean goodsBean) { 170 | if (_cartGoodsMap.containsKey(_buildGoodsKey(goodsBean))) { 171 | return _cartGoodsMap[_buildGoodsKey(goodsBean)]; 172 | } else { 173 | return _cartGoodsList.where((goods) => _buildGoodsKey(goods) == _buildGoodsKey(goodsBean)).toList(); 174 | } 175 | } 176 | 177 | ///获得购物车中带属性的商品 178 | List findCartGoodsWithProperty(CartGoodsBean goodsBean) { 179 | if (_cartGoodsWithPropertyMap.containsKey(_buildGoodsPropertyKey(goodsBean))) { 180 | return _cartGoodsWithPropertyMap[_buildGoodsPropertyKey(goodsBean)]; 181 | } else { 182 | return List(); 183 | } 184 | } 185 | 186 | ///获得购物车的商品总价 187 | String getCartGoodsPrice() { 188 | if (_cartGoodsList.isEmpty) { 189 | return '未选购任何商品'; 190 | } else { 191 | double totalPrice = 0.0; 192 | for (var value in _cartGoodsList) { 193 | totalPrice += value.price; 194 | } 195 | return Utils.formatPrice(totalPrice.toString()); 196 | } 197 | } 198 | 199 | GestureTapCallback _showShopCarListCallback = () {}; 200 | 201 | set showShopCarListCallback(GestureTapCallback voidCallback) { 202 | this._showShopCarListCallback = voidCallback; 203 | } 204 | 205 | void clickShopCarButton() { 206 | _showShopCarListCallback(); 207 | } 208 | 209 | ///对数据进行持久化存储 210 | void _save() { 211 | SpUtil.putObjectList(Constant.cartGoods, _cartGoodsList); 212 | SpUtil.putObjectList(Constant.sortGoods, _cartGoodsSort); 213 | } 214 | 215 | ///删除持久化数据 216 | void _removeAll() { 217 | SpUtil.remove(Constant.cartGoods); 218 | SpUtil.remove(Constant.sortGoods); 219 | } 220 | 221 | ///构建没有属性的key,避免套餐和商品id重复 222 | String _buildGoodsKey(CartGoodsBean goods) { 223 | return 'type:${goods.type},id:${goods.id}'; 224 | } 225 | 226 | ///构建有属性的key,避免套餐和商品id重复 227 | String _buildGoodsPropertyKey(CartGoodsBean goods) { 228 | return 'type:${goods.type},id:${goods.id},properties:${goods.properties}'; 229 | } 230 | } 231 | -------------------------------------------------------------------------------- /lib/ui/order/widgets/goods_item.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:order/entity/cart_goods_bean.dart'; 3 | import 'package:order/res/resources.dart'; 4 | import 'package:order/ui/order/pages/goods_detail_page.dart'; 5 | import 'package:order/ui/order/widgets/goods_options.dart'; 6 | import 'package:order/util/utils.dart'; 7 | import 'package:order/widgets/load_image.dart'; 8 | 9 | /// 商品Item组件 10 | class GoodsItem extends StatefulWidget { 11 | final CartGoodsBean data; 12 | 13 | GoodsItem({Key key, this.data}) : super(key: key); 14 | 15 | @override 16 | _GoodsItemState createState() => _GoodsItemState(); 17 | } 18 | 19 | class _GoodsItemState extends State { 20 | @override 21 | Widget build(BuildContext context) { 22 | return InkWell( 23 | child: Padding( 24 | padding: const EdgeInsets.fromLTRB(15.0, 12.5, 0.0, 12.5), 25 | child: Row( 26 | crossAxisAlignment: CrossAxisAlignment.start, 27 | children: [ 28 | ClipRRect( 29 | borderRadius: BorderRadius.circular(4), 30 | child: Hero( 31 | tag: widget.data, 32 | child: LoadImage( 33 | '${widget.data.img}', 34 | width: 81.0, 35 | height: 81.0, 36 | fit: BoxFit.fitHeight, 37 | ), 38 | ), 39 | ), 40 | Gaps.hGap10, 41 | Expanded( 42 | child: Column( 43 | crossAxisAlignment: CrossAxisAlignment.start, 44 | children: [ 45 | Text( 46 | widget.data.name, 47 | style: TextStyles.textBold14, 48 | overflow: TextOverflow.ellipsis, 49 | maxLines: 1, 50 | ), 51 | const SizedBox(height: 2.5), 52 | Text( 53 | '月销 100', 54 | style: TextStyles.textGray10, 55 | ), 56 | const SizedBox(height: 2.5), 57 | Text('${widget.data.description}', 58 | style: TextStyles.textGray10, 59 | overflow: TextOverflow.ellipsis, 60 | maxLines: 1), 61 | Row( 62 | crossAxisAlignment: CrossAxisAlignment.end, 63 | children: [ 64 | RichText( 65 | text: TextSpan(children: [ 66 | TextSpan( 67 | text: '¥', 68 | style: TextStyle( 69 | fontSize: Dimens.font_sp12, 70 | color: Colors.red)), 71 | TextSpan( 72 | text: 73 | '${Utils.formatPriceWithoutSymbol(widget.data.price.toString())}', 74 | style: TextStyle( 75 | fontSize: 17, 76 | color: Colors.red, 77 | fontFamily: 'DINRegular')), 78 | ]), 79 | ), 80 | Spacer(), 81 | //商品的加减操作 82 | GoodsOptions(data: widget.data) 83 | ], 84 | ) 85 | ], 86 | ), 87 | ) 88 | ], 89 | ), 90 | ), 91 | onTap: () { 92 | Navigator.of(context).push(MaterialPageRoute( 93 | builder: (context) => GoodsDetailsPage(data: widget.data))); 94 | }, 95 | ); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /lib/ui/order/widgets/goods_options.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:provider/provider.dart'; 3 | 4 | import '../../../entity/cart_goods_bean.dart'; 5 | import '../../../res/resources.dart'; 6 | import '../../../util/color_utils.dart'; 7 | import '../../../util/utils.dart'; 8 | import '../../../widgets/label_text.dart'; 9 | import '../../../widgets/load_image.dart'; 10 | import '../provider/ball_anim_provider.dart'; 11 | import '../provider/order_provider.dart'; 12 | import 'goods_type_dialog.dart'; 13 | 14 | class GoodsOptions extends StatefulWidget { 15 | final CartGoodsBean data; 16 | 17 | GoodsOptions({Key key, this.data}) : super(key: key); 18 | 19 | @override 20 | _GoodsOptionsState createState() => _GoodsOptionsState(); 21 | } 22 | 23 | class _GoodsOptionsState extends State { 24 | GlobalKey _addWidgetKey = GlobalKey(); 25 | 26 | @override 27 | Widget build(BuildContext context) { 28 | OrderProvider provider = Provider.of(context, listen: true); 29 | 30 | List cartGoods = provider.findCartGoods(widget.data); 31 | 32 | if (widget.data.propertyList != null && widget.data.propertyList.isNotEmpty) { 33 | return Container( 34 | width: 100.0, 35 | height: 33.5, 36 | child: Stack( 37 | children: [ 38 | Positioned( 39 | bottom: 0.0, 40 | right: 15.0, 41 | child: LabelText( 42 | padding: const EdgeInsets.fromLTRB(12.5, 4, 12.5, 4), 43 | radius: 12.5, 44 | text: '选规格', 45 | backgroundColor: ColorUtils.hexToColor('#2567FE'), 46 | onPressed: () { 47 | FocusScope.of(context).unfocus(); 48 | showElasticDialog( 49 | context: context, 50 | builder: (_) { 51 | return GoodsTypeDialog(widget.data, context); 52 | }); 53 | }, 54 | ), 55 | ), 56 | Positioned( 57 | top: 0.0, 58 | right: 6.0, 59 | child: Visibility( 60 | visible: cartGoods.isNotEmpty, 61 | child: LabelText( 62 | key: Key('goods_count'), 63 | shape: BoxShape.circle, 64 | backgroundColor: Colors.red, 65 | text: '${cartGoods.length}', 66 | padding: const EdgeInsets.fromLTRB(6, 2, 6, 2), 67 | ), 68 | ), 69 | ) 70 | ], 71 | ), 72 | ); 73 | } else { 74 | return Row( 75 | children: [ 76 | Visibility( 77 | visible: cartGoods.isNotEmpty, 78 | child: GestureDetector( 79 | onTap: () => provider.removeCartGoods(widget.data), 80 | child: Padding( 81 | padding: const EdgeInsets.fromLTRB(15, 5, 7, 5), 82 | child: LoadAssetImage( 83 | 'ic_reduce', 84 | width: 21.85, 85 | height: 21.85, 86 | ), 87 | ), 88 | ), 89 | ), 90 | Visibility( 91 | visible: cartGoods.isNotEmpty, 92 | child: Container( 93 | width: 20, 94 | alignment: Alignment.center, 95 | child: Text('${cartGoods.length}', style: TextStyles.textBlack14), 96 | ), 97 | ), 98 | GestureDetector( 99 | onTap: () { 100 | final BallAnimProvider ballAnimProvider = 101 | Provider.of(context, listen: false); 102 | 103 | // 获取Widget树中真正的盒子对象 104 | RenderBox renderBox = 105 | _addWidgetKey.currentContext.findRenderObject(); 106 | // 小球大小保存到全局 107 | ballAnimProvider.ballSize = Size(16, 16); 108 | // 小球的在屏幕的位置保存到全局 109 | ballAnimProvider.ballPosition = 110 | renderBox.localToGlobal(Offset.zero); 111 | // 回调这个启动动画的函数 112 | ballAnimProvider.notifyStartAnim(); 113 | 114 | provider.addCartGoods(widget.data); 115 | }, 116 | child: Padding( 117 | padding: const EdgeInsets.fromLTRB(7, 5, 15, 5), 118 | child: LoadAssetImage( 119 | 'ic_add', 120 | key: _addWidgetKey, 121 | width: 21.85, 122 | height: 21.85, 123 | ), 124 | ), 125 | ) 126 | ], 127 | ); 128 | } 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /lib/ui/order/widgets/shop_cart.dart: -------------------------------------------------------------------------------- 1 | import 'dart:ui'; 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:oktoast/oktoast.dart'; 5 | import 'package:order/res/resources.dart'; 6 | import 'package:order/ui/order/provider/ball_anim_provider.dart'; 7 | import 'package:order/ui/order/provider/order_provider.dart'; 8 | import 'package:order/util/color_utils.dart'; 9 | import 'package:order/widgets/label_text.dart'; 10 | import 'package:order/widgets/load_image.dart'; 11 | import 'package:provider/provider.dart'; 12 | import '../../../common/common.dart'; 13 | import '../../../util/navigator_utils.dart'; 14 | import '../../../util/theme_utils.dart'; 15 | 16 | ///底部购物车组件 17 | class ShopCart extends StatefulWidget { 18 | @override 19 | ShopCartState createState() => ShopCartState(); 20 | } 21 | 22 | class ShopCartState extends State with WidgetsBindingObserver { 23 | GlobalKey _shopCarImageKey = GlobalKey(); 24 | 25 | @override 26 | Widget build(BuildContext context) { 27 | return Material( 28 | color: Colors.transparent, 29 | child: Padding( 30 | padding: const EdgeInsets.symmetric(horizontal: 10), 31 | child: SizedBox( 32 | height: 80 + MediaQuery.of(context).padding.bottom, 33 | child: Consumer(builder: (context, provider, child) { 34 | return Stack( 35 | children: [ 36 | Positioned( 37 | bottom: 0.0, 38 | left: 0.0, 39 | right: 0.0, 40 | child: Column( 41 | children: [ 42 | ClipRRect( 43 | borderRadius: BorderRadius.circular(24), 44 | child: ClipRect( 45 | //如果不设置ClipRect的话,效果将扩散至全屏 46 | child: BackdropFilter( 47 | filter: ImageFilter.blur(sigmaX: 10, sigmaY: 10), 48 | child: Container( 49 | height: 49.0, 50 | width: double.infinity, 51 | color: ColorUtils.hexToColor('#2D3339') 52 | .withOpacity(0.65), 53 | child: Row( 54 | mainAxisAlignment: MainAxisAlignment.start, 55 | crossAxisAlignment: CrossAxisAlignment.center, 56 | children: [ 57 | const SizedBox(width: 53.0), 58 | Expanded( 59 | key: Key('cart_amount'), 60 | child: Text(provider.getCartGoodsPrice(), 61 | style: TextStyles.textWhite14), 62 | ), 63 | InkWell( 64 | onTap: () { 65 | if (provider.cartGoodsList.isEmpty) { 66 | showToast('请先选择商品加入购物车'); 67 | return; 68 | } 69 | provider.removeAllCartGoods(); 70 | if (Constant.isShowShopList) { 71 | provider.clickShopCarButton(); 72 | } 73 | NavigatorUtils.goBackWithParams( 74 | context, 0); 75 | }, 76 | child: Container( 77 | width: 94.5, 78 | height: double.infinity, 79 | color: Colors.red, 80 | alignment: Alignment.center, 81 | child: Text('下单', 82 | style: TextStyles.textWhite14), 83 | ), 84 | ) 85 | ], 86 | ), 87 | ), 88 | ), 89 | ), 90 | ), 91 | Container( 92 | height: 10 + MediaQuery.of(context).padding.bottom, 93 | color: ThemeUtils.getBackgroundColor(context), 94 | ), 95 | ], 96 | ), 97 | ), 98 | Positioned( 99 | top: 0.0, 100 | left: 6.5, 101 | child: GestureDetector( 102 | key: _shopCarImageKey, 103 | child: LoadAssetImage( 104 | provider.cartGoodsList.isNotEmpty 105 | ? 'order_cart' 106 | : 'order_cart_empty', 107 | width: 45.0, 108 | height: 45.0, 109 | ), 110 | onTap: () => provider.cartGoodsList.isNotEmpty 111 | ? provider.clickShopCarButton() 112 | : null, 113 | ), 114 | ), 115 | Positioned( 116 | top: 0.0, 117 | left: 38, 118 | child: Visibility( 119 | visible: provider.cartGoodsList.isNotEmpty, 120 | child: LabelText( 121 | key: Key('cart_count'), 122 | shape: BoxShape.circle, 123 | backgroundColor: Colors.red, 124 | text: '${provider.cartGoodsList.length}', 125 | padding: const EdgeInsets.fromLTRB(6, 2, 6, 2), 126 | ), 127 | ), 128 | ) 129 | ], 130 | ); 131 | }), 132 | ), 133 | ), 134 | ); 135 | } 136 | 137 | void initShopCarPosition() { 138 | final ballAnimProvider = 139 | Provider.of(context, listen: false); 140 | RenderBox renderBox = _shopCarImageKey.currentContext.findRenderObject(); 141 | // 购物车大小存储到全局 142 | ballAnimProvider.shopCarSize = renderBox.size; 143 | // 购物车在屏幕的位置存储到全局 144 | ballAnimProvider.shopCarPosition = renderBox.localToGlobal(Offset.zero); 145 | } 146 | 147 | void _onAfterRendering(Duration timeStamp) { 148 | // 绘制完成的第一帧调用 并且只调用一次 149 | initShopCarPosition(); 150 | } 151 | 152 | @override 153 | void didChangeDependencies() { 154 | // 无需调用移出方法,因为当回调执行后,里边的List会被自动清空 155 | WidgetsBinding.instance.addPostFrameCallback(_onAfterRendering); 156 | super.didChangeDependencies(); 157 | } 158 | 159 | @override 160 | void didUpdateWidget(ShopCart oldWidget) { 161 | WidgetsBinding.instance.addPostFrameCallback(_onAfterRendering); 162 | super.didUpdateWidget(oldWidget); 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /lib/ui/order/widgets/throw_ball_anim.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:order/ui/order/provider/ball_anim_provider.dart'; 3 | import 'package:provider/provider.dart'; 4 | 5 | //加入购物车抛物线动画 6 | class ThrowBallAnim extends StatefulWidget { 7 | @override 8 | _ThrowBallAnimState createState() => _ThrowBallAnimState(); 9 | } 10 | 11 | class _ThrowBallAnimState extends State { 12 | List> _ballAnimKeys = []; 13 | 14 | void _startThrowBallAnim() { 15 | final ballAnimProvider = Provider.of(context,listen: false); 16 | //获取小球大小 17 | final ballSize = ballAnimProvider.ballSize; 18 | //计算小球的开始位置 19 | final startPosition = ballAnimProvider.ballPosition; 20 | // 计算小球的结束位置 21 | final endPosition = Offset( 22 | ballAnimProvider.shopCarPosition.dx + 23 | (ballAnimProvider.shopCarSize.width - ballSize.width) / 2.0, 24 | ballAnimProvider.shopCarPosition.dy + 25 | (ballAnimProvider.shopCarSize.height - ballSize.height) / 2.0, 26 | ); 27 | 28 | for (var value in _ballAnimKeys) { 29 | final _ballAnimState = value.currentState; 30 | if (_ballAnimState._isHide) { 31 | // 查找一个没有在执行动画的小球,然后启动动画 32 | _ballAnimState.startAnim(ballSize, startPosition, endPosition); 33 | // 中断循环 34 | break; 35 | } 36 | } 37 | } 38 | 39 | @override 40 | void initState() { 41 | super.initState(); 42 | for (var i = 0; i < BallAnimProvider.ballCount; i++) { 43 | _ballAnimKeys.add(GlobalKey()); 44 | } 45 | } 46 | 47 | @override 48 | Widget build(BuildContext context) { 49 | final ballAnimProvider = Provider.of(context,listen: false); 50 | //赋值回调 51 | ballAnimProvider.notifyStartAnim = _startThrowBallAnim; 52 | return IgnorePointer( 53 | child: Stack( 54 | children: _ballAnimKeys.map((key) => _BallAnim(key: key)).toList(), 55 | ), 56 | ); 57 | } 58 | } 59 | 60 | class _BallAnim extends StatefulWidget { 61 | _BallAnim({Key key}) : super(key: key); 62 | 63 | @override 64 | __BallAnimState createState() => __BallAnimState(); 65 | } 66 | 67 | class __BallAnimState extends State<_BallAnim> with TickerProviderStateMixin { 68 | bool _isHide; 69 | Size _ballSize; 70 | Offset _ballOffset; 71 | 72 | AnimationController _animationController; 73 | Animation _animationOffset; 74 | 75 | @override 76 | void initState() { 77 | super.initState(); 78 | //初始化 79 | _isHide = true; 80 | _ballSize = Size.zero; 81 | _ballOffset = Offset.zero; 82 | 83 | //动画控制器 84 | _animationController = AnimationController( 85 | vsync: this, duration: const Duration(milliseconds: 400)); 86 | 87 | _animationController.addStatusListener((status) { 88 | if (status == AnimationStatus.completed) { 89 | //动画执行结束,就立即隐藏小球 90 | setState(() { 91 | _isHide = true; 92 | }); 93 | } 94 | }); 95 | } 96 | 97 | void startAnim(Size ballSize, Offset startPosition, Offset endPosition) { 98 | //创建动画执行轨迹 99 | _animationOffset = ThrowTween(begin: startPosition, end: endPosition) 100 | .animate(_animationController) 101 | ..addListener(() { 102 | setState(() { 103 | //拿到中间计算值,刷新界面 104 | _ballOffset = _animationOffset.value; 105 | }); 106 | }); 107 | 108 | //启动动画前 将小球显示出来 109 | setState(() { 110 | _isHide = false; 111 | _ballSize = ballSize; 112 | _ballOffset = startPosition; 113 | }); 114 | 115 | //然后执行动画 116 | _animationController.reset(); 117 | _animationController.forward(); 118 | } 119 | 120 | @override 121 | Widget build(BuildContext context) { 122 | return Positioned( 123 | left: _ballOffset.dx, 124 | top: _ballOffset.dy, 125 | width: _ballSize.width, 126 | height: _ballSize.height, 127 | child: Offstage( 128 | offstage: _isHide, 129 | child: ClipOval( 130 | child: Container( 131 | color: Colors.red, 132 | ), 133 | ), 134 | ), 135 | ); 136 | } 137 | } 138 | 139 | ///抛物线方程表达式 y=ax^2+bx+c 140 | class ThrowTween extends Animatable { 141 | //开始点 142 | final Offset begin; 143 | 144 | //结束点 145 | final Offset end; 146 | 147 | double a = 0.0; 148 | double b = 0.0; 149 | double c = 0.0; 150 | 151 | ThrowTween({this.begin, this.end}) { 152 | Offset center = Offset(begin.dx - 5, begin.dy - 5); 153 | // 已知的三个点 154 | final x1 = begin.dx; 155 | final y1 = begin.dy; 156 | final x2 = center.dx; 157 | final y2 = center.dy; 158 | final x3 = end.dx; 159 | final y3 = end.dy; 160 | // 中间值 161 | final temp1 = x2 * x2 - x3 * x3; 162 | final temp2 = x1 * x1 - x2 * x2; 163 | // 计算抛物线系数 164 | b = (temp1 * (y1 - y2) - temp2 * (y2 - y3)) / 165 | (temp1 * (x1 - x2) - (temp2) * (x2 - x3)); 166 | a = (y1 - y2 - b * (x1 - x2)) / temp2; 167 | c = y1 - a * x1 * x1 - b * x1; 168 | } 169 | 170 | double _computed(double x) => a * x * x + b * x + c; 171 | 172 | @override 173 | Offset transform(double t) { 174 | // x是一次函数 175 | final dx = begin.dx + (end.dx - begin.dx) * t; 176 | final dy = _computed(dx); 177 | return Offset(dx, dy); 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /lib/util/color_utils.dart: -------------------------------------------------------------------------------- 1 | import 'dart:ui'; 2 | 3 | class ColorUtils{ 4 | static Color hexToColor(String s) { 5 | // 如果传入的十六进制颜色值不符合要求,返回默认值 6 | if (s == null || s.length != 7 || int.tryParse(s.substring(1, 7), radix: 16) == null) { 7 | s = '#999999'; 8 | } 9 | 10 | return new Color(int.parse(s.substring(1, 7), radix: 16) + 0xFF000000); 11 | } 12 | } -------------------------------------------------------------------------------- /lib/util/image_utils.dart: -------------------------------------------------------------------------------- 1 | import 'package:cached_network_image/cached_network_image.dart'; 2 | import 'package:common_utils/common_utils.dart'; 3 | import 'package:flutter/material.dart'; 4 | 5 | import 'log_utils.dart'; 6 | 7 | class ImageUtils { 8 | static ImageProvider getAssetImage(String name, {String format: 'png'}) { 9 | return AssetImage(getImgPath(name, format: format)); 10 | } 11 | 12 | static String getImgPath(String name, {String format: 'png'}) { 13 | return 'assets/images/$name.$format'; 14 | } 15 | 16 | static ImageProvider getImageProvider(String imageUrl, 17 | {String holderImg: 'none'}) { 18 | if (TextUtil.isEmpty(imageUrl)) { 19 | return AssetImage(getImgPath(holderImg)); 20 | } 21 | return CachedNetworkImageProvider(imageUrl, 22 | errorListener: () => Log.e("图片加载失败!")); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /lib/util/log_utils.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert' as convert; 2 | import 'package:common_utils/common_utils.dart'; 3 | import 'package:order/common/common.dart'; 4 | 5 | /// 输出Log工具类 6 | class Log{ 7 | 8 | static const String tag = 'DEER-LOG'; 9 | 10 | static init() { 11 | LogUtil.debuggable = !Constant.inProduction; 12 | } 13 | 14 | static d(String msg, {tag: tag}) { 15 | if (!Constant.inProduction) { 16 | LogUtil.v(msg, tag: tag); 17 | } 18 | } 19 | 20 | static e(String msg, {tag: tag}) { 21 | if (!Constant.inProduction) { 22 | LogUtil.e(msg, tag: tag); 23 | } 24 | } 25 | 26 | static json(String msg, {tag: tag}) { 27 | if (!Constant.inProduction) { 28 | var data = convert.json.decode(msg); 29 | if (data is Map) { 30 | _printMap(data); 31 | } else if (data is List) { 32 | _printList(data); 33 | } else 34 | LogUtil.v(msg, tag: tag); 35 | } 36 | } 37 | 38 | // https://github.com/Milad-Akarie/pretty_dio_logger 39 | static void _printMap(Map data, {tag: tag, int tabs = 1, bool isListItem = false, bool isLast = false}) { 40 | final bool isRoot = tabs == 1; 41 | final initialIndent = _indent(tabs); 42 | tabs++; 43 | 44 | if (isRoot || isListItem) LogUtil.v('$initialIndent{', tag: tag); 45 | 46 | data.keys.toList().asMap().forEach((index, key) { 47 | final isLast = index == data.length - 1; 48 | var value = data[key]; 49 | if (value is String) value = '\"$value\"'; 50 | if (value is Map) { 51 | if (value.length == 0) 52 | LogUtil.v('${_indent(tabs)} "$key": $value${!isLast ? ',' : ''}', tag: tag); 53 | else { 54 | LogUtil.v('${_indent(tabs)} "$key": {', tag: tag); 55 | _printMap(value, tabs: tabs); 56 | } 57 | } else if (value is List) { 58 | if (value.length == 0) { 59 | LogUtil.v('${_indent(tabs)} "$key": ${value.toString()}', tag: tag); 60 | } else { 61 | LogUtil.v('${_indent(tabs)} "$key": [', tag: tag); 62 | _printList(value, tabs: tabs); 63 | LogUtil.v('${_indent(tabs)} ]${isLast ? '' : ','}', tag: tag); 64 | } 65 | } else { 66 | final msg = value.toString().replaceAll('\n', ''); 67 | LogUtil.v('${_indent(tabs)} "$key": $msg${!isLast ? ',' : ''}', tag: tag); 68 | } 69 | }); 70 | 71 | LogUtil.v('$initialIndent}${isListItem && !isLast ? ',' : ''}', tag: tag); 72 | } 73 | 74 | static void _printList(List list, {tag: tag, int tabs = 1}) { 75 | list.asMap().forEach((i, e) { 76 | final isLast = i == list.length - 1; 77 | if (e is Map) { 78 | if (e.length == 0) 79 | LogUtil.v('${_indent(tabs)} $e${!isLast ? ',' : ''}', tag: tag); 80 | else 81 | _printMap(e, tabs: tabs + 1, isListItem: true, isLast: isLast); 82 | } else 83 | LogUtil.v('${_indent(tabs + 2)} $e${isLast ? '' : ','}', tag: tag); 84 | }); 85 | } 86 | 87 | static String _indent([int tabCount = 1]) => ' ' * tabCount; 88 | } -------------------------------------------------------------------------------- /lib/util/navigator_utils.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | 3 | //导航工具类 4 | class NavigatorUtils { 5 | 6 | 7 | /// 返回 8 | static void goBack(BuildContext context) { 9 | FocusScope.of(context).unfocus(); 10 | Navigator.pop(context); 11 | } 12 | 13 | /// 带参数返回 14 | static void goBackWithParams(BuildContext context, result) { 15 | FocusScope.of(context).unfocus(); 16 | Navigator.pop(context, result); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /lib/util/theme_utils.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:order/res/resources.dart'; 4 | 5 | import '../res/colors.dart'; 6 | import '../res/colors.dart'; 7 | 8 | class ThemeUtils { 9 | static bool isDark(BuildContext context) { 10 | return Theme.of(context).brightness == Brightness.dark; 11 | } 12 | 13 | static Color getDarkColor(BuildContext context, Color darkColor) { 14 | return isDark(context) ? darkColor : null; 15 | } 16 | 17 | static Color getIconColor(BuildContext context) { 18 | return isDark(context) ? Colours.dark_text : null; 19 | } 20 | 21 | static Color getTitleColor(BuildContext context) { 22 | return isDark(context) ? Colors.white : Colours.text; 23 | } 24 | 25 | static Color getTextColor(BuildContext context){ 26 | return isDark(context) ? Colors.white :Colours.black_text; 27 | } 28 | 29 | static Color getBackgroundColor(BuildContext context) { 30 | return Theme.of(context).scaffoldBackgroundColor; 31 | } 32 | 33 | static Color getDialogBackgroundColor(BuildContext context) { 34 | return Theme.of(context).canvasColor; 35 | } 36 | 37 | static Color getStickyHeaderColor(BuildContext context) { 38 | return isDark(context) ? Colours.dark_bg_gray_ : Colours.bg_gray_; 39 | } 40 | 41 | static Color getDialogTextFieldColor(BuildContext context) { 42 | return isDark(context) ? Colours.dark_bg_gray_ : Colours.bg_gray; 43 | } 44 | 45 | static Color getKeyboardActionsColor(BuildContext context) { 46 | return isDark(context) ? Colours.dark_bg_color : Colors.grey[200]; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /lib/util/toast.dart: -------------------------------------------------------------------------------- 1 | import 'package:oktoast/oktoast.dart'; 2 | 3 | /// Toast工具类 4 | class Toast { 5 | static show(String msg, {duration = 2000}) { 6 | if (msg == null) { 7 | return; 8 | } 9 | showToast( 10 | msg, 11 | duration: Duration(milliseconds: duration), 12 | dismissOtherToast: true 13 | ); 14 | } 15 | 16 | static cancelToast() { 17 | dismissAllToast(); 18 | } 19 | } -------------------------------------------------------------------------------- /lib/util/utils.dart: -------------------------------------------------------------------------------- 1 | import 'package:common_utils/common_utils.dart'; 2 | import 'package:flutter/material.dart'; 3 | class Utils { 4 | 5 | static String formatPrice(String price, {format: MoneyFormat.END_INTEGER}) { 6 | return MoneyUtil.changeYWithUnit( 7 | NumUtil.getDoubleByValueStr(price), MoneyUnit.YUAN, 8 | format: format); 9 | } 10 | 11 | static String formatPriceWithoutSymbol(String price,{format: MoneyFormat.END_INTEGER}){ 12 | return MoneyUtil.changeYWithUnit( 13 | NumUtil.getDoubleByValueStr(price), MoneyUnit.YUAN, 14 | format: format).replaceAll('¥', ''); 15 | } 16 | 17 | 18 | } 19 | 20 | ///默认dialog背景色为半透明黑色,这里修改源码为透明 21 | Future showTransparentDialog({ 22 | @required BuildContext context, 23 | bool barrierDismissible=true, 24 | WidgetBuilder builder, 25 | }){ 26 | final ThemeData theme = Theme.of(context, shadowThemeOnly: true); 27 | return showGeneralDialog( 28 | context: context, 29 | pageBuilder: (BuildContext buildContext, Animation animation, Animation secondaryAnimation) { 30 | final Widget pageChild = Builder(builder: builder); 31 | return SafeArea( 32 | child: Builder( 33 | builder: (BuildContext context) { 34 | return theme != null 35 | ? Theme(data: theme, child: pageChild) 36 | : pageChild; 37 | } 38 | ), 39 | ); 40 | }, 41 | barrierDismissible: barrierDismissible, 42 | barrierLabel: MaterialLocalizations.of(context).modalBarrierDismissLabel, 43 | barrierColor: const Color(0x00FFFFFF), 44 | transitionDuration: const Duration(milliseconds: 150), 45 | transitionBuilder: _buildMaterialDialogTransitions, 46 | ); 47 | } 48 | 49 | Widget _buildMaterialDialogTransitions( 50 | BuildContext context, 51 | Animation animation, 52 | Animation secondaryAnimation, 53 | Widget child) { 54 | return FadeTransition( 55 | opacity: CurvedAnimation( 56 | parent: animation, 57 | curve: Curves.easeOut, 58 | ), 59 | child: child, 60 | ); 61 | } 62 | 63 | Future showElasticDialog({ 64 | @required BuildContext context, 65 | bool barrierDismissible = true, 66 | WidgetBuilder builder, 67 | }) { 68 | final ThemeData theme = Theme.of(context, shadowThemeOnly: true); 69 | return showGeneralDialog( 70 | context: context, 71 | pageBuilder: (BuildContext buildContext, Animation animation, 72 | Animation secondaryAnimation) { 73 | final Widget pageChild = Builder(builder: builder); 74 | return SafeArea( 75 | child: Builder( 76 | builder: (BuildContext context) { 77 | return theme != null 78 | ? Theme(data: theme, child: pageChild) 79 | : pageChild; 80 | }, 81 | ), 82 | ); 83 | }, 84 | barrierDismissible: barrierDismissible, 85 | barrierLabel: MaterialLocalizations.of(context).modalBarrierDismissLabel, 86 | barrierColor: Colors.black54, 87 | transitionDuration: const Duration(milliseconds: 550), 88 | transitionBuilder: _buildDialogTransitions, 89 | ); 90 | } 91 | 92 | Widget _buildDialogTransitions( 93 | BuildContext context, 94 | Animation animation, 95 | Animation secondaryAnimation, 96 | Widget child) { 97 | return FadeTransition( 98 | opacity: CurvedAnimation( 99 | parent: animation, 100 | curve: Curves.easeOut, 101 | ), 102 | child: SlideTransition( 103 | position: Tween( 104 | begin: const Offset(0.0, 0.3), 105 | end: Offset.zero, 106 | ).animate(CurvedAnimation( 107 | parent: animation, 108 | curve: animation.status != AnimationStatus.forward 109 | ? Curves.easeOutBack 110 | : ElasticInOutCurve(0.85), 111 | )), 112 | child: child, 113 | ), 114 | ); 115 | } 116 | -------------------------------------------------------------------------------- /lib/widgets/404.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:order/widgets/state_layout.dart'; 3 | import 'package:order/widgets/tool_bar.dart'; 4 | 5 | class WidgetNotFound extends StatelessWidget { 6 | @override 7 | Widget build(BuildContext context) { 8 | return const Scaffold( 9 | appBar: Toolbar( 10 | centerTitle: '页面不存在', 11 | ), 12 | body: StateLayout( 13 | type: StateType.network, 14 | hintText: '页面不存在', 15 | ), 16 | ); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /lib/widgets/label_text.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:order/res/resources.dart'; 3 | 4 | class LabelText extends StatelessWidget { 5 | final EdgeInsets padding; 6 | final GestureTapCallback onPressed; 7 | final String text; 8 | final Color backgroundColor; 9 | final double radius; 10 | final Color textColor; 11 | final double textSize; 12 | final BoxShape shape; 13 | 14 | LabelText({ 15 | Key key, 16 | @required this.padding, 17 | @required this.text, 18 | @required this.backgroundColor, 19 | this.radius = 4.0, 20 | this.textColor = Colors.white, 21 | this.textSize = Dimens.font_sp12, 22 | this.shape = BoxShape.rectangle, 23 | this.onPressed, 24 | }) : super(key: key); 25 | 26 | @override 27 | Widget build(BuildContext context) { 28 | return InkWell( 29 | borderRadius: BorderRadius.circular(radius), 30 | onTap: onPressed, 31 | child: DecoratedBox( 32 | decoration: BoxDecoration( 33 | shape: shape, 34 | color: backgroundColor, 35 | borderRadius: shape == BoxShape.circle 36 | ? null 37 | : BorderRadius.circular(radius)), 38 | child: Padding( 39 | padding: padding, 40 | child: Text( 41 | text, 42 | style: TextStyle(color: textColor, fontSize: textSize), 43 | ), 44 | ), 45 | ), 46 | ); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /lib/widgets/load_image.dart: -------------------------------------------------------------------------------- 1 | import 'package:cached_network_image/cached_network_image.dart'; 2 | import 'package:common_utils/common_utils.dart'; 3 | import 'package:flutter/material.dart'; 4 | import 'package:order/util/image_utils.dart'; 5 | 6 | ///图片加载(支持本地与网络图片) 7 | class LoadImage extends StatelessWidget { 8 | final String image; 9 | final double width; 10 | final double height; 11 | final BoxFit fit; 12 | final String format; 13 | final String holderImg; 14 | 15 | const LoadImage(this.image, 16 | {Key key, 17 | this.width, 18 | this.height, 19 | this.fit = BoxFit.cover, 20 | this.format: 'png', 21 | this.holderImg: 'none'}) 22 | : super(key: key); 23 | 24 | @override 25 | Widget build(BuildContext context) { 26 | if (TextUtil.isEmpty(image) || image == 'null') { 27 | return LoadAssetImage(holderImg, 28 | height: height, width: width, fit: fit, format: format); 29 | } else { 30 | if (image.startsWith('http')) { 31 | return CachedNetworkImage( 32 | imageUrl: image, 33 | placeholder: (context, url) => 34 | LoadAssetImage(holderImg, height: height, width: width, fit: fit), 35 | errorWidget: (context, url, error) => 36 | LoadAssetImage(holderImg, height: height, width: width, fit: fit), 37 | width: width, 38 | height: height, 39 | fit: fit, 40 | ); 41 | } else { 42 | return LoadAssetImage(image, 43 | height: height, width: width, fit: fit, format: format); 44 | } 45 | } 46 | } 47 | } 48 | 49 | ///加载本地资源图片 50 | class LoadAssetImage extends StatelessWidget { 51 | const LoadAssetImage(this.image, 52 | {Key key, 53 | this.width, 54 | this.height, 55 | this.fit, 56 | this.format: 'png', 57 | this.color}) 58 | : super(key: key); 59 | 60 | final String image; 61 | final double width; 62 | final double height; 63 | final BoxFit fit; 64 | final String format; 65 | final Color color; 66 | 67 | @override 68 | Widget build(BuildContext context) { 69 | return Image.asset( 70 | ImageUtils.getImgPath(image, format: format), 71 | height: height, 72 | width: width, 73 | fit: fit, 74 | color: color, 75 | 76 | ///忽略图片语义 77 | excludeFromSemantics: true, 78 | ); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /lib/widgets/md2_tab_indicator.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | enum MD2IndicatorSize { 4 | tiny, 5 | normal, 6 | full, 7 | } 8 | 9 | /// 自定义indicator 10 | class MD2Indicator extends Decoration { 11 | final double indicatorHeight; 12 | final Color indicatorColor; 13 | final MD2IndicatorSize indicatorSize; 14 | 15 | const MD2Indicator( 16 | {@required this.indicatorHeight, 17 | @required this.indicatorColor, 18 | @required this.indicatorSize}); 19 | 20 | @override 21 | BoxPainter createBoxPainter([onChanged]) { 22 | return _MD2Painter(this, onChanged); 23 | } 24 | 25 | } 26 | 27 | class _MD2Painter extends BoxPainter { 28 | final MD2Indicator decoration; 29 | 30 | _MD2Painter(this.decoration, VoidCallback onChanged) 31 | : assert(decoration != null), 32 | super(onChanged); 33 | 34 | @override 35 | void paint(Canvas canvas, Offset offset, ImageConfiguration configuration) { 36 | assert(configuration != null); 37 | assert(configuration.size != null); 38 | 39 | Rect rect; 40 | if (decoration.indicatorSize == MD2IndicatorSize.full) { 41 | rect = Offset(offset.dx, 42 | (configuration.size.height - decoration.indicatorHeight ?? 3)) & 43 | Size(configuration.size.width, decoration.indicatorHeight ?? 3); 44 | } else if (decoration.indicatorSize == MD2IndicatorSize.normal) { 45 | rect = Offset(offset.dx + 12, 46 | (configuration.size.height - decoration.indicatorHeight ?? 3)) & 47 | Size(configuration.size.width - 24, decoration.indicatorHeight ?? 3); 48 | } else if (decoration.indicatorSize == MD2IndicatorSize.tiny) { 49 | rect = Offset( 50 | offset.dx + configuration.size.width / 2 - 8, 51 | (configuration.size.height - decoration.indicatorHeight - 5 ?? 52 | 3)) & 53 | Size(12, decoration.indicatorHeight ?? 3); 54 | } 55 | 56 | final Paint paint = Paint(); 57 | paint.color = decoration.indicatorColor ?? Color(0xff1967d2); 58 | paint.style = PaintingStyle.fill; 59 | canvas.drawRRect( 60 | RRect.fromRectAndCorners(rect, 61 | topRight: Radius.circular(8), 62 | topLeft: Radius.circular(8), 63 | bottomRight: Radius.circular(8), 64 | bottomLeft: Radius.circular(8)), 65 | paint); 66 | } 67 | 68 | } -------------------------------------------------------------------------------- /lib/widgets/preferred_hero_img_bar.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter/services.dart'; 3 | 4 | import '../util/theme_utils.dart'; 5 | import 'load_image.dart'; 6 | 7 | class PreferredHeroImgBar extends StatelessWidget 8 | implements PreferredSizeWidget { 9 | final Object tag; 10 | final String imageUrl; 11 | 12 | PreferredHeroImgBar({Key key, @required this.tag, @required this.imageUrl}) 13 | : super(key: key); 14 | 15 | @override 16 | Widget build(BuildContext context) { 17 | return Stack( 18 | children: [ 19 | Hero( 20 | tag: tag, 21 | child: LoadImage( 22 | imageUrl, 23 | width: double.infinity, 24 | height: 300, 25 | fit: BoxFit.cover, 26 | ), 27 | ), 28 | SafeArea( 29 | child: Semantics( 30 | namesRoute: true, 31 | header: true, 32 | child: IconButton( 33 | onPressed: () { 34 | FocusScope.of(context).unfocus(); 35 | Navigator.maybePop(context).then((value) { 36 | if (!value) { 37 | SystemNavigator.pop(); 38 | } 39 | }); 40 | }, 41 | tooltip: 'Back', 42 | icon: Image.asset( 43 | 'assets/images/ic_back_black.png', 44 | width: 25, 45 | height: 25, 46 | color: ThemeUtils.getIconColor(context), 47 | ), 48 | ), 49 | ), 50 | ) 51 | ], 52 | ); 53 | } 54 | 55 | @override 56 | Size get preferredSize => Size.fromHeight(375.0); 57 | } -------------------------------------------------------------------------------- /lib/widgets/progress_dialog.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:order/res/resources.dart'; 4 | 5 | ///加载中的弹框 6 | class ProgressDialog extends Dialog { 7 | const ProgressDialog({ 8 | Key key, 9 | this.hintText: '', 10 | }) : super(key: key); 11 | 12 | final String hintText; 13 | 14 | @override 15 | Widget build(BuildContext context) { 16 | return Material( 17 | type: MaterialType.transparency, 18 | child: Center( 19 | child: Container( 20 | height: 88.0, 21 | width: 120.0, 22 | decoration: ShapeDecoration( 23 | color: const Color(0xFF3A3A3A), 24 | shape: RoundedRectangleBorder( 25 | borderRadius: BorderRadius.all(Radius.circular(8.0)))), 26 | child: Column( 27 | mainAxisAlignment: MainAxisAlignment.center, 28 | children: [ 29 | Theme( 30 | data: ThemeData( 31 | cupertinoOverrideTheme: CupertinoThemeData( 32 | brightness: Brightness.dark // 局部指定夜间模式,加载圈颜色会设置为白色 33 | )), 34 | child: const CupertinoActivityIndicator(radius: 14.0), 35 | ), 36 | Gaps.vGap8, 37 | Text( 38 | hintText, 39 | style: const TextStyle(color: Colors.white), 40 | ) 41 | ], 42 | ), 43 | ), 44 | ), 45 | ); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /lib/widgets/search_bar.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter/scheduler.dart'; 3 | import 'package:flutter/services.dart'; 4 | import 'package:order/res/resources.dart'; 5 | import 'package:order/util/color_utils.dart'; 6 | import 'package:order/util/theme_utils.dart'; 7 | 8 | import 'load_image.dart'; 9 | 10 | ///搜索页的AppBar 11 | class SearchBar extends StatefulWidget implements PreferredSizeWidget { 12 | const SearchBar( 13 | {Key key, 14 | this.hintText: '', 15 | this.backImg: 'assets/images/ic_back_black.png', 16 | this.isSearch = true, 17 | this.onTouch, 18 | this.onSearch, 19 | this.onPressed, 20 | this.actionName = '', 21 | this.actionColor, 22 | this.isAutoFocus = false, 23 | this.isAutoSearch = true, 24 | this.onBack}) 25 | : super(key: key); 26 | 27 | final String backImg; 28 | final String hintText; 29 | final Function(String) onSearch; 30 | final VoidCallback onPressed; 31 | final VoidCallback onBack; 32 | final VoidCallback onTouch; 33 | final bool isSearch; 34 | final String actionName; 35 | final Color actionColor; 36 | final bool isAutoFocus; //是否自动获取焦点 37 | final bool isAutoSearch; //是否自动搜索 38 | 39 | @override 40 | _SearchBarState createState() => _SearchBarState(); 41 | 42 | @override 43 | Size get preferredSize => Size.fromHeight(44.0); 44 | } 45 | 46 | class _SearchBarState extends State { 47 | TextEditingController _controller = TextEditingController(); 48 | bool _hasText = false; 49 | 50 | @override 51 | void initState() { 52 | super.initState(); 53 | //监听输入改变 54 | _controller.addListener(_search); 55 | } 56 | 57 | void _search() { 58 | String searchText = _controller.text; 59 | setState(() { 60 | if (searchText.isEmpty) { 61 | _hasText = false; 62 | widget.onSearch(''); 63 | } else { 64 | _hasText = true; 65 | widget.onSearch(searchText); 66 | } 67 | }); 68 | } 69 | 70 | @override 71 | Widget build(BuildContext context) { 72 | bool isDark = ThemeUtils.isDark(context); 73 | SystemUiOverlayStyle overlayStyle = 74 | isDark ? SystemUiOverlayStyle.light : SystemUiOverlayStyle.dark; 75 | Color iconColor = isDark ? Colours.dark_text_gray : Colours.text_gray_c; 76 | return AnnotatedRegion( 77 | value: overlayStyle, 78 | child: Material( 79 | color: ThemeUtils.getBackgroundColor(context), 80 | child: SafeArea( 81 | child: Container( 82 | child: Row( 83 | children: [ 84 | Semantics( 85 | label: '返回', 86 | child: SizedBox( 87 | width: 30.0, 88 | height: 44.0, 89 | child: GestureDetector( 90 | onTap: widget.onBack == null 91 | ? () { 92 | FocusScope.of(context).unfocus(); 93 | Navigator.maybePop(context); 94 | } 95 | : widget.onBack, 96 | child: Padding( 97 | key: const Key('search_back'), 98 | padding: const EdgeInsets.fromLTRB(2.5, 9.5, 2.5, 9.5), 99 | child: Image.asset( 100 | widget.backImg, 101 | color: isDark ? Colours.dark_text : Colours.text, 102 | ), 103 | ), 104 | ), 105 | ), 106 | ), 107 | Expanded( 108 | child: widget.isSearch 109 | ? _buildSearch(isDark, iconColor) 110 | : _buildTouch(isDark, iconColor), 111 | ), 112 | Gaps.hGap10, 113 | Visibility( 114 | visible: widget.actionName.isNotEmpty, 115 | child: Theme( 116 | data: Theme.of(context).copyWith( 117 | buttonTheme: ButtonThemeData( 118 | padding: const EdgeInsets.symmetric(horizontal: 0.0), 119 | height: 44.0, 120 | minWidth: 42.0, 121 | materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, 122 | ), 123 | ), 124 | child: FlatButton( 125 | textColor: 126 | isDark ? Colours.dark_button_text : Colors.white, 127 | color: Colors.transparent, 128 | onPressed: () { 129 | FocusScope.of(context).unfocus(); 130 | widget.onPressed(); 131 | }, 132 | child: Text('${widget.actionName}', 133 | style: TextStyle( 134 | fontSize: Dimens.font_sp14, 135 | color: widget.actionColor ?? 136 | ColorUtils.hexToColor('#2567FE'))), 137 | ), 138 | ), 139 | ), 140 | Gaps.hGap10, 141 | ], 142 | ), 143 | ), 144 | ), 145 | ), 146 | ); 147 | } 148 | 149 | Widget _buildSearch(bool isDark, Color iconColor) { 150 | return Container( 151 | height: 32.0, 152 | decoration: BoxDecoration( 153 | color: isDark ? Colours.dark_material_bg : Colours.bg_gray, 154 | borderRadius: BorderRadius.circular(2.0), 155 | ), 156 | child: TextField( 157 | key: const Key('search_text_field'), 158 | autofocus: widget.isAutoFocus, 159 | controller: _controller, 160 | maxLines: 1, 161 | textInputAction: TextInputAction.search, 162 | onSubmitted: (val) { 163 | FocusScope.of(context).unfocus(); 164 | //点击软键盘的动作按钮时的回调 165 | if (widget.isAutoSearch) { 166 | widget.onSearch(val); 167 | } else { 168 | widget.onPressed(); 169 | } 170 | }, 171 | decoration: InputDecoration( 172 | contentPadding: const EdgeInsets.only( 173 | top: 0.0, left: -13.5, right: -16.0, bottom: 14.0), 174 | border: InputBorder.none, 175 | icon: Padding( 176 | padding: const EdgeInsets.only(top: 7.5, bottom: 7.5, left: 4.5), 177 | child: LoadAssetImage( 178 | 'order_search', 179 | color: iconColor, 180 | ), 181 | ), 182 | hintText: widget.hintText, 183 | hintStyle: 184 | TextStyle(fontSize: Dimens.font_sp12, color: Colours.text_gray), 185 | suffixIcon: Opacity( 186 | opacity: _hasText ? 1 : 0, 187 | child: GestureDetector( 188 | child: Semantics( 189 | label: '清空', 190 | child: Padding( 191 | padding: const EdgeInsets.only( 192 | left: 16.0, top: 8.0, bottom: 8.0), 193 | child: 194 | LoadAssetImage('order_delete', color: iconColor), 195 | ), 196 | ), 197 | onTap: () { 198 | /// https://github.com/flutter/flutter/issues/36324 199 | SchedulerBinding.instance.addPostFrameCallback((_) { 200 | _controller.text = ''; 201 | widget.onSearch(''); 202 | }); 203 | }, 204 | ), 205 | )), 206 | ), 207 | ); 208 | } 209 | 210 | Widget _buildTouch(bool isDark, Color iconColor) { 211 | return GestureDetector( 212 | onTap: widget.onTouch, 213 | child: Container( 214 | height: 32.0, 215 | decoration: BoxDecoration( 216 | color: isDark ? Colours.dark_material_bg : Colours.bg_gray, 217 | borderRadius: BorderRadius.circular(2.0), 218 | ), 219 | child: Row( 220 | crossAxisAlignment: CrossAxisAlignment.center, 221 | children: [ 222 | Gaps.hGap5, 223 | LoadAssetImage( 224 | 'order_search', 225 | color: iconColor, 226 | width: 17, 227 | height: 17, 228 | ), 229 | Text(widget.hintText, 230 | style: TextStyle( 231 | fontSize: Dimens.font_sp12, color: Colours.text_gray)) 232 | ], 233 | ), 234 | ), 235 | ); 236 | } 237 | 238 | @override 239 | void dispose() { 240 | _controller?.dispose(); 241 | super.dispose(); 242 | } 243 | } 244 | -------------------------------------------------------------------------------- /lib/widgets/state_layout.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:order/res/resources.dart'; 4 | import 'package:order/util/image_utils.dart'; 5 | import 'package:order/util/theme_utils.dart'; 6 | 7 | /// design/9暂无状态页面/index.html#artboard3 8 | class StateLayout extends StatefulWidget{ 9 | 10 | const StateLayout({ 11 | Key key, 12 | @required this.type, 13 | this.hintText 14 | }):super(key: key); 15 | 16 | final StateType type; 17 | final String hintText; 18 | 19 | @override 20 | _StateLayoutState createState() => _StateLayoutState(); 21 | } 22 | 23 | class _StateLayoutState extends State{ 24 | 25 | String _img; 26 | String _hintText; 27 | 28 | @override 29 | Widget build(BuildContext context) { 30 | switch(widget.type){ 31 | case StateType.error: 32 | _img = 'empty'; 33 | _hintText = '请求失败,请稍后重试'; 34 | break; 35 | case StateType.network: 36 | _img = 'empty'; 37 | _hintText = '无网络连接'; 38 | break; 39 | case StateType.loading: 40 | _img = ''; 41 | _hintText = ''; 42 | break; 43 | case StateType.empty: 44 | _img = 'empty'; 45 | _hintText = '暂无数据'; 46 | break; 47 | case StateType.none: 48 | _img = ''; 49 | _hintText = ''; 50 | break; 51 | } 52 | return Container( 53 | width: double.infinity, 54 | child: Column( 55 | crossAxisAlignment: CrossAxisAlignment.center, 56 | mainAxisAlignment: MainAxisAlignment.center, 57 | children: [ 58 | widget.type == StateType.loading ? const CupertinoActivityIndicator(radius: 16.0) : 59 | (widget.type == StateType.none ? Gaps.empty : 60 | Opacity( 61 | opacity: ThemeUtils.isDark(context) ? 0.5 : 1, 62 | child: Container( 63 | height: 120.0, 64 | width: 120.0, 65 | decoration: BoxDecoration( 66 | image: DecorationImage( 67 | image: ImageUtils.getAssetImage('state/$_img'), 68 | ), 69 | ), 70 | )) 71 | ), 72 | Gaps.vGap16, 73 | Text( 74 | widget.hintText ?? _hintText, 75 | style: Theme.of(context).textTheme.subtitle.copyWith(fontSize: Dimens.font_sp14), 76 | ), 77 | Gaps.vGap50, 78 | ], 79 | ), 80 | ); 81 | } 82 | 83 | } 84 | 85 | enum StateType{ 86 | /// 无网络 87 | network, 88 | ///请求失败 89 | error, 90 | /// 加载中 91 | loading, 92 | /// 暂无数据 93 | empty, 94 | ///空布局 95 | none, 96 | } -------------------------------------------------------------------------------- /lib/widgets/tool_bar.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter/services.dart'; 3 | import 'package:order/res/resources.dart'; 4 | import 'package:order/util/color_utils.dart'; 5 | import 'package:order/util/theme_utils.dart'; 6 | 7 | import '../util/theme_utils.dart'; 8 | 9 | ///自定义AppBar 10 | class Toolbar extends StatelessWidget implements PreferredSizeWidget { 11 | const Toolbar( 12 | {Key key, 13 | this.backgroundColor, 14 | this.titleColor, 15 | this.title: '', 16 | this.centerTitle: '', 17 | this.actionName: '', 18 | this.backImg: 'assets/images/ic_back_black.png', 19 | this.onPressed, 20 | this.onBack, 21 | this.isBack: true}) 22 | : super(key: key); 23 | 24 | final Color backgroundColor; 25 | final Color titleColor; 26 | final String title; 27 | final String centerTitle; 28 | final String backImg; 29 | final String actionName; 30 | final VoidCallback onPressed; 31 | final VoidCallback onBack; 32 | final bool isBack; 33 | 34 | @override 35 | Widget build(BuildContext context) { 36 | Color _backgroundColor; 37 | Color _titleColor; 38 | 39 | if (backgroundColor == null) { 40 | _backgroundColor = ThemeUtils.getBackgroundColor(context); 41 | } else { 42 | _backgroundColor = backgroundColor; 43 | } 44 | 45 | if (titleColor == null) { 46 | _titleColor = ThemeUtils.getTitleColor(context); 47 | } else { 48 | _titleColor = titleColor; 49 | } 50 | 51 | SystemUiOverlayStyle _overlayStyle = 52 | ThemeData.estimateBrightnessForColor(_backgroundColor) == 53 | Brightness.dark 54 | ? SystemUiOverlayStyle.light 55 | : SystemUiOverlayStyle.dark; 56 | 57 | var back = isBack 58 | ? onBack == null 59 | ? IconButton( 60 | onPressed: () { 61 | FocusScope.of(context).unfocus(); 62 | Navigator.maybePop(context).then((value) { 63 | if (!value) { 64 | SystemNavigator.pop(); 65 | } 66 | }); 67 | }, 68 | tooltip: 'Back', 69 | icon: Image.asset( 70 | backImg, 71 | width: 25, 72 | height: 25, 73 | color: ThemeUtils.getIconColor(context), 74 | ), 75 | ) 76 | : IconButton( 77 | onPressed: onBack, 78 | tooltip: 'Back', 79 | icon: Image.asset( 80 | backImg, 81 | width: 25, 82 | height: 25, 83 | color: ThemeUtils.getIconColor(context), 84 | ), 85 | ) 86 | : Gaps.empty; 87 | 88 | var action = actionName.isNotEmpty 89 | ? Positioned( 90 | right: 0.0, 91 | child: Theme( 92 | data: Theme.of(context).copyWith( 93 | buttonTheme: ButtonThemeData( 94 | padding: const EdgeInsets.symmetric(horizontal: 16.0), 95 | minWidth: 60.0, 96 | )), 97 | child: FlatButton( 98 | child: Text(actionName, 99 | key: const Key('actionName'), 100 | style: TextStyle(fontSize: Dimens.font_sp14)), 101 | textColor: ThemeUtils.isDark(context) 102 | ? Colours.dark_text 103 | : ColorUtils.hexToColor('#2567FE'), 104 | highlightColor: Colors.transparent, 105 | onPressed: onPressed, 106 | ), 107 | ), 108 | ) 109 | : Gaps.empty; 110 | 111 | return AnnotatedRegion( 112 | value: _overlayStyle, 113 | child: Material( 114 | color: _backgroundColor, 115 | child: SafeArea( 116 | child: Stack( 117 | alignment: Alignment.centerLeft, 118 | children: [ 119 | Semantics( 120 | namesRoute: true, 121 | header: true, 122 | child: Container( 123 | alignment: centerTitle.isEmpty 124 | ? Alignment.centerLeft 125 | : Alignment.center, 126 | width: double.infinity, 127 | child: Text( 128 | title.isEmpty ? centerTitle : title, 129 | style: TextStyle( 130 | color: _titleColor, fontSize: Dimens.font_sp18), 131 | ), 132 | margin: const EdgeInsets.symmetric(horizontal: 44.0), 133 | ), 134 | ), 135 | back, 136 | action 137 | ], 138 | ), 139 | ), 140 | ), 141 | ); 142 | } 143 | 144 | @override 145 | Size get preferredSize => Size.fromHeight(44.0); 146 | } 147 | -------------------------------------------------------------------------------- /order.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /order_android.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: order 2 | description: 用Flutter实现商品点单功能 3 | 4 | # The following defines the version and build number for your application. 5 | # A version number is three numbers separated by dots, like 1.2.43 6 | # followed by an optional build number separated by a +. 7 | # Both the version and the builder number may be overridden in flutter 8 | # build by specifying --build-name and --build-number, respectively. 9 | # In Android, build-name is used as versionName while build-number used as versionCode. 10 | # Read more about Android versioning at https://developer.android.com/studio/publish/versioning 11 | # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. 12 | # Read more about iOS versioning at 13 | # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html 14 | # 15 | # This version is used _only_ for the Runner app, which is used if you just do 16 | # a `flutter run` or a `flutter make-host-app-editable`. It has no impact 17 | # on any other native host app that you embed your Flutter project into. 18 | version: 1.0.0+1 19 | 20 | environment: 21 | sdk: ">=2.1.0 <3.0.0" 22 | 23 | dependencies: 24 | flutter: 25 | sdk: flutter 26 | 27 | # Toast插件 https://github.com/OpenFlutter/flutter_oktoast 28 | oktoast: ^2.3.1+1 29 | # Dart 常用工具类库 https://github.com/Sky24n/common_utils 30 | common_utils: ^1.1.3 31 | # Flutter 常用工具类库 https://github.com/Sky24n/flustars 32 | flustars: 0.2.6+1 33 | # 手势识别 https://github.com/aleksanderwozniak/simple_gesture_detector 34 | simple_gesture_detector: ^0.1.4 35 | # 图片缓存 https://github.com/renefloor/flutter_cached_network_image 36 | cached_network_image: ^2.0.0 37 | # 格式化String https://github.com/Naddiseo/dart-sprintf 38 | sprintf: ^4.0.2 39 | # 状态管理 https://github.com/rrousselGit/provider 40 | provider: ^4.0.4 41 | # The following adds the Cupertino Icons font to your application. 42 | # Use with the CupertinoIcons class for iOS style icons. 43 | cupertino_icons: ^0.1.3 44 | 45 | dev_dependencies: 46 | flutter_test: 47 | sdk: flutter 48 | 49 | # For information on the generic Dart part of this file, see the 50 | # following page: https://dart.dev/tools/pub/pubspec 51 | 52 | flutter: 53 | # The following line ensures that the Material Icons font is 54 | # included with your application, so that you can use the icons in 55 | # the material Icons class. 56 | uses-material-design: true 57 | 58 | # To add Flutter specific assets to your application, add an assets section, 59 | # like this: 60 | assets: 61 | - assets/data/ 62 | - assets/images/ 63 | 64 | # An image asset can refer to one or more resolution-specific "variants", see 65 | # https://flutter.dev/assets-and-images/#resolution-aware. 66 | 67 | # For details regarding adding assets from package dependencies, see 68 | # https://flutter.dev/assets-and-images/#from-packages 69 | 70 | # To add Flutter specific custom fonts to your application, add a fonts 71 | # section here, in this "flutter" section. Each entry in this list should 72 | # have a "family" key with the font family name, and a "fonts" key with a 73 | # list giving the asset and other descriptors for the font. For 74 | # example: 75 | fonts: 76 | - family: DINRegular 77 | fonts: 78 | - asset: assets/fonts/DIN-Regular.otf 79 | # 80 | # For details regarding fonts from package dependencies, 81 | # see https://flutter.dev/custom-fonts/#from-packages 82 | 83 | 84 | # This section identifies your Flutter project as a module meant for 85 | # embedding in a native host app. These identifiers should _not_ ordinarily 86 | # be changed after generation - they are used to ensure that the tooling can 87 | # maintain consistency when adding or modifying assets and plugins. 88 | # They also do not have any bearing on your native host application's 89 | # identifiers, which may be completely independent or the same as these. 90 | module: 91 | androidX: true 92 | androidPackage: com.wbb.order 93 | iosBundleIdentifier: com.wbb.order 94 | -------------------------------------------------------------------------------- /test/widget_test.dart: -------------------------------------------------------------------------------- 1 | // This is a basic Flutter widget test. 2 | // 3 | // To perform an interaction with a widget in your test, use the WidgetTester 4 | // utility that Flutter provides. For example, you can send tap and scroll 5 | // gestures. You can also use WidgetTester to find child widgets in the widget 6 | // tree, read text, and verify that the values of widget properties are correct. 7 | 8 | import 'package:flutter/material.dart'; 9 | import 'package:flutter_test/flutter_test.dart'; 10 | 11 | import 'package:order/main.dart'; 12 | 13 | void main() { 14 | testWidgets('Counter increments smoke test', (WidgetTester tester) async { 15 | // Build our app and trigger a frame. 16 | await tester.pumpWidget(MyApp()); 17 | 18 | // Verify that our counter starts at 0. 19 | expect(find.text('0'), findsOneWidget); 20 | expect(find.text('1'), findsNothing); 21 | 22 | // Tap the '+' icon and trigger a frame. 23 | await tester.tap(find.byIcon(Icons.add)); 24 | await tester.pump(); 25 | 26 | // Verify that our counter has incremented. 27 | expect(find.text('0'), findsNothing); 28 | expect(find.text('1'), findsOneWidget); 29 | }); 30 | } 31 | --------------------------------------------------------------------------------