Repository: bailicangdu/vue2-elm Branch: master Commit: b85ff0220366 Files: 87 Total size: 507.2 KB Directory structure: gitextract_552txq2t/ ├── .babelrc ├── .editorconfig ├── .github/ │ └── workflows/ │ └── close_inactive_issue.yml ├── .gitignore ├── COPYING ├── README-en.md ├── README.md ├── build/ │ ├── build.js │ ├── dev-client.js │ ├── dev-server.js │ ├── utils.js │ ├── webpack.base.conf.js │ ├── webpack.dev.conf.js │ └── webpack.prod.conf.js ├── config/ │ └── index.js ├── index.html ├── package.json └── src/ ├── App.vue ├── components/ │ ├── common/ │ │ ├── alertTip.vue │ │ ├── buyCart.vue │ │ ├── computeTime.vue │ │ ├── loading.vue │ │ ├── mixin.js │ │ ├── ratingStar.vue │ │ ├── shoplist.vue │ │ └── svg.vue │ ├── footer/ │ │ └── footGuide.vue │ └── header/ │ └── head.vue ├── config/ │ ├── env.js │ ├── fetch.js │ ├── mUtils.js │ └── rem.js ├── main.js ├── page/ │ ├── balance/ │ │ ├── balance.vue │ │ └── children/ │ │ └── detail.vue │ ├── benefit/ │ │ ├── benefit.vue │ │ └── children/ │ │ ├── commend.vue │ │ ├── coupon.vue │ │ ├── exchange.vue │ │ ├── hbDescription.vue │ │ └── hbHistory.vue │ ├── city/ │ │ └── city.vue │ ├── confirmOrder/ │ │ ├── children/ │ │ │ ├── children/ │ │ │ │ ├── addAddress.vue │ │ │ │ └── children/ │ │ │ │ └── searchAddress.vue │ │ │ ├── chooseAddress.vue │ │ │ ├── invoice.vue │ │ │ ├── payment.vue │ │ │ ├── remark.vue │ │ │ └── userValidation.vue │ │ └── confirmOrder.vue │ ├── download/ │ │ └── download.vue │ ├── find/ │ │ └── find.vue │ ├── food/ │ │ └── food.vue │ ├── forget/ │ │ └── forget.vue │ ├── home/ │ │ └── home.vue │ ├── login/ │ │ └── login.vue │ ├── msite/ │ │ └── msite.vue │ ├── order/ │ │ ├── children/ │ │ │ └── orderDetail.vue │ │ └── order.vue │ ├── points/ │ │ ├── children/ │ │ │ └── detail.vue │ │ └── points.vue │ ├── profile/ │ │ ├── children/ │ │ │ ├── children/ │ │ │ │ ├── address.vue │ │ │ │ ├── children/ │ │ │ │ │ ├── add.vue │ │ │ │ │ └── children/ │ │ │ │ │ └── addDetail.vue │ │ │ │ └── setusername.vue │ │ │ └── info.vue │ │ └── profile.vue │ ├── search/ │ │ └── search.vue │ ├── service/ │ │ ├── children/ │ │ │ └── questionDetail.vue │ │ └── service.vue │ ├── shop/ │ │ ├── children/ │ │ │ ├── children/ │ │ │ │ └── shopSafe.vue │ │ │ ├── foodDetail.vue │ │ │ └── shopDetail.vue │ │ └── shop.vue │ └── vipcard/ │ ├── children/ │ │ ├── invoiceRecord.vue │ │ ├── useCart.vue │ │ └── vipDescription.vue │ └── vipcard.vue ├── router/ │ └── router.js ├── service/ │ └── getData.js ├── store/ │ ├── action.js │ ├── getters.js │ ├── index.js │ ├── mutation-types.js │ └── mutations.js └── style/ ├── common.scss └── mixin.scss ================================================ FILE CONTENTS ================================================ ================================================ FILE: .babelrc ================================================ { "presets": ["es2015", "stage-2"], "plugins": ["transform-runtime"], "comments": false, } ================================================ FILE: .editorconfig ================================================ root = true [*] charset = utf-8 indent_style = space indent_size = 2 end_of_line = lf insert_final_newline = true trim_trailing_whitespace = true ================================================ FILE: .github/workflows/close_inactive_issue.yml ================================================ name: Close Inactive Issue on: schedule: - cron: "0 0 * * *" jobs: close-issues: runs-on: ubuntu-latest steps: - name: close inactive issue uses: actions-cool/issues-helper@v2.2.1 with: actions: 'close-issues' labels: 'inactive' inactive-day: 30 body: | Since the issue was labeled with `inactive`, but no response in 30 days. This issue will be close. If you have any questions, you can comment and reply. 由于该 issue 被标记为不活跃,且 30 天未收到回应。现关闭 issue,若有任何问题,可评论回复。 ================================================ FILE: .gitignore ================================================ .DS_Store node_modules/ npm-debug.log package-lock.json # 忽略使用WebStorm开发时,生成的缓存文件夹 .idea # 忽略使用VSCode开发时,生成的缓存文件夹 .vscode tree.md *.idea elm/ ================================================ FILE: COPYING ================================================ GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc. 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Library General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Library General Public License instead of this License. ================================================ FILE: README-en.md ================================================ # Introduction [README in Chinese](README.md) When I began to learn to vue, search of some practical source code about vue, but most of them are simple demo and didn't help to explore the vue. The most of demo are front-end page, such as music player interaction not as complicated as expected.But in actual work, we often meet some project involving money, such as shopping cart page. This kind of project is complex, accompany by login, register ,user information and so on. It's difficult, no one has ever written a project like this in vue and commit to github. So I write it, hope I can help you. This functional project is practical but often boring, with no music player so gorgeous. For a long time, I think that Elm APP is a good material. First, it is complex that the open delivery platform is more complicated than the typical project. Second, you won't get bored seeing so much beautiful food. But why is the Elm APP, not Baidu App or Meituan App? The reason is simple, that Elm's layout is the most beautiful and the most comfortable in the three APP. There are 45 pages of this project, involving registration, login, merchandise display, shopping cart, order and so on. It is a complete process. The average company project is not that complicated, so if you can understand this project fully, I believe that you can able to do the most of single-page applications in the other company. Even if it's more complex, and it won't be much higher than this one. The project was done in the spare time, actually it was written years ago and over the years, so the spend time seem a bit long. The project spend more than two months from beginning to end. Now it has been completed, some performance optimizations are being performed to add detailed annotations. In addition, the project don't related elm video of imooc website, and that project only have one page. After watching the official documentation of vue, I wrote this project directly, and without reference to any code, so please don't mix them up. __Note: This project is purely personal. If you want to order, please choose the official elm client.__ ## Technology vue2 + vuex + vue-router + webpack + ES6/7 + fetch + sass + flex + svg ## Development Note:This project used a lot of properties such as ES6/7, so node 6.0.0+ is required. ``` git clone https://github.com/bailicangdu/vue2-elm.git   cd vue2-elm npm install npm run dev ``` ## More This project has the supporting background system. If you want to develop it, you can download the corresponding backend system.[backend system](https://github.com/bailicangdu/node-elm)。 Backend system's run command:npm run local . If you only do the front-end development, ignore this note. # Explain > If it helps you, you can click "Star" in the upper right corner to support,thank you. ^_^ > May be you can "follow" me, I will make more interstng projects. > Development environment: macOS 10.12.3 Chrome 56  nodejs 6.10.0 > Thanks for 辰妹子[@bailichen](https://github.com/bailichen), [@iceRao](https://github.com/raoenhui),to help complete the project,thank you🌹 > If you hava some question,you can post the question in Issues, and if you find some solution or some improvement,please pull request. 👍 > [communication group](https://gitter.im/vue2-elm/Lobby?utm_source=share-link&utm_medium=link&utm_campaign=share-link) > Recommend a open source project about react and redux。[address](https://github.com/bailicangdu/react-pxq) > Recommend a other demo about vue2 and vuex,it's simple and good for newbie.[address](https://github.com/bailicangdu/vue2-happyfri) ## Dscription of the data interface 🤔 ### 2017-05-30 For some reason,previous interfaces could not be used, it made the project failed to run.The new backend system is built by nodejs.[backend system address](https://github.com/bailicangdu/node-elm). It can kept consisent with the offical website. And provide corresponding [background management system](https://github.com/bailicangdu/vue2-manage) # Demo [click it](http://cangdu.org/elm/)(Preview with chrome phone mode please) ### The mobile can scan the qr code in the below # Features - [x] location function -- finished - [x] choose city -- finished - [x] search address-- finished - [x] display a list of merchant that near by the selected address -- finished - [x] find food or restaurant -- finished - [x] sorting and filter according to distance,sales,rating,special course,distribution and so on -- finished - [x] food list of restaurant -- finished - [x] cart function -- finished - [x] evaluation page of shop -- finished - [x] detail page about one food-- finished - [x] detail page about merchant -- finished - [x] login、register -- finished - [x] change password -- finished - [x] user center -- finished - [x] send message、voice verification -- finished - [x] order function -- finished ✨✨🎉🎉 - [x] order list -- finished - [x] order detail -- finished - [x] download App -- finished - [x] add、delete、change delivery address -- finished - [x] account info -- finished - [x] service center -- finished - [x] red packet -- finished - [x] upload avator -- finished - [ ] pay -- No money~~ # Summarize 1, It is not the official of elm, so it is necessary to open the agent, and must be opened on the PC. At most, you can order but not more, after the success of the order, you can check and pay the payment on your mobile phone. 2, In general, the page involves money that is complicated, especially like elm? An open platform, It has many types of merchants and foods, need some complex interaction between page and page. When I writing the cart and order pages, without API but it has a lot of 3, Vue is so bright, because of it's a lightweight framework good at the small and medium-sized project. Whem you want make a large single-page application, you can use vuex, the performance is still outstanding. In the treatment of the complex interaction logic page, vuex is necessary. So if you use vue and vuex, you can make large single-page projects. 4, Now, after make process of the login to register, homepage, search, businesses, shopping cart, order, order list, personal center and so on. I not only understanding the vue to a deeper level, and it's can help for me to control the large project in the future. Do a practical project has great improvement in myself. 5, Sometimes I doubt that to spend a few months make this project has meaning or not. At first I just wanted to do a small project, but I didn't think I could write more, but when I finished I believed it was worth it. 6, The project was already finished, and have 45 pages. # Ultimate Goal 1, Build a background system to simulate the delivery platform with node.js。[address](https://github.com/bailicangdu/node-elm) 2, Use react-native to write native APP of Android and IOS。[地址在这里](https://github.com/bailicangdu/React-Native-elm) 3、May be I will make a seller's version in the future. So my goal is to build a full ecosystem across the frontend , backend, IOS and Android. ...Waiting for me # Screenshot ### store list page ### store filter page ### food list and cart ### oder check page ### search page ### login page ### user center # Layout ``` . ├── build // webpack config file ├── config // package path ├── elm // online project file,can access if put them one the server ├── screenshots // screenshot ├── src // source directory │   ├── components // components │   │   ├── common // common components │   │   │   ├── alertTip.vue // popup components │   │   │   ├── buyCart.vue // cart components │   │   │   ├── computeTime.vue // countdown components │   │   │   ├── loading.vue // the animation component when page is initialized │   │   │   ├── mixin.js // mixinf components(inculde:directive-loading more,make picture address) │   │   │   ├── ratingStar.vue // the five rating star component about comment │   │   │   └── shoplist.vue // the resturant list common component of msite and shop page │   │   ├── footer │   │   │   └── footGuide.vue // footer common component │   │   └── header │   │   └── head.vue // header common component │   ├── config // basic configuration │   │   ├── env.js // environment switch configuration │   │   ├── fetch.js // git data │   │   ├── mUtils.js // common function about js │   │   └── rem.js // px transform rem │   ├── images // public picture │   ├── page │   │   ├── balance │   │   │   ├── balance.vue // balance page │   │   │   └── children │   │   │   └── detail.vue // balance detail page │   │   ├── benefit │   │   │   ├── benefit.vue // benefit pahe │   │   │   └── children │   │   │   ├── commend.vue // recommend prize │   │   │   ├── coupon.vue // coupon introduction │   │   │   ├── exchange.vue // exchange benefit │   │   │   ├── hbDescription.vue // benefit descripting │   │   │   └── hbHistory.vue // benefit's history │   │   ├── city │   │   │   └── city.vue // local city page │   │   ├── confirmOrder │   │   │   ├── children │   │   │   │   ├── children │   │   │   │   │   ├── addAddress.vue // add address page │   │   │   │   │   └── children │   │   │   │   │   └── searchAddress.vue // search address page │   │   │   │   ├── chooseAddress.vue // choose address page │   │   │   │   ├── invoice.vue // choose invoice page │   │   │   │   ├── payment.vue // pay page │   │   │   │   ├── remark.vue // order remark page │   │   │   │   └── userValidation.vue // user validation page │   │   │   └── confirmOrder.vue // confirm order page │   │   ├── download │   │   │   └── download.vue // downlad App │   │   ├── find │   │   │   └── find.vue // find page │   │   ├── food │   │   │   └── food.vue // food filter sort page │   │   ├── forget │   │   │   └── forget.vue // forget password, change password │   │   ├── home │   │   │   └── home.vue // index page │   │   ├── login │   │   │   └── login.vue // login and register page │   │   ├── msite │   │   │   └── msite.vue // merchant list page │   │   ├── order │   │   │   ├── children │   │   │   │   └── orderDetail.vue // order detail page │   │   │   └── order.vue // order list page │   │   ├── points │   │   │   ├── children │   │   │   │   └── detail.vue // points detail page │   │   │   └── points.vue // points page │   │   ├── profile │   │   │   ├── children │   │   │   │   ├── children │   │   │   │   │   ├── address.vue // address page │   │   │   │   │   └── children │   │   │   │   │   ├── add.vue // add address page │   │   │   │   │   └── children │   │   │   │   │   └── addDetail.vue // search address page │   │   │   │   ├── info.vue // account info page │   │   │   │   └── setusername.vue // reset user page │   │   │   └── profile.vue // user profile page │   │   ├── search │   │   │   └── search.vue // search page │   │   ├── service │   │   │   ├── children │   │   │   │   └── questionDetail.vue // question detail page │   │   │   └── service.vue // service center page │   │   ├── shop │   │   │   ├── children │   │   │   │   ├── children │   │   │   │   │   └── shopSafe.vue // shop check info page │   │   │   │   ├── foodDetail.vue // food detail page │   │   │   │   └── shopDetail.vue // shop detail page │   │   │   └── shop.vue // shop filter page │   │   └── vipcard │   │   ├── children │   │   │   ├── invoiceRecord.vue // invoice record page │   │   │   ├── useCart.vue // use card page │   │   │   └── vipDescription.vue // vip deacription page │   │   └── vipcard.vue // vip handling page │   ├── plugins // plugins │   ├── router │   │   └── router.js // router configuration │   ├── service // data interaction and unified deployment │   │   ├── getData.js // the unified deployment file for get data, and unified management of interfaces │   │   └── tempdata // temporary data for the development │   ├── store // Vuex status management │   │   ├── action.js // actions configuration │   │   ├── getters.js // getters configuration │   │   ├── index.js // use vuex,new store │   │   ├── modules // store modules │   │   ├── mutation-types.js // make const for muations │   │   └── mutations.js // mutations configuration │   └── style │   ├── common.scss // common css │   ├── mixin.scss // cs configuration file │   └── swiper.min.css │   ├── App.vue // entry page js file │   ├── main.js // the main js for loading components ├── favicon.ico // icon ├── index.html // entry page html file . 56 directories, 203 files ``` # License [GPL](https://github.com/bailicangdu/vue2-elm/blob/master/COPYING) ================================================ FILE: README.md ================================================ # 前言 [README in English](README-en.md) 初学vue时曾在网上搜索vue的实战项目源码,无奈大部分都是简单的demo,对于深究vue没有太大的帮助,剩下的一些大部分都是像音乐播放器之类的展示型项目,交互没有预期那么复杂。但我们实际在工作中,经常会遇到有购物车的项目,这类项目因为涉及到money,所以对逻辑严谨度要求高,页面之间交互复杂,又会伴随着登录、注册、用户信息等等,常常会让我们很头疼。既然还没人用vue写过这样的项目,那不如我来写,开源出来对能看到的人也会有帮助。 这种功能性的项目很实用但是往往也很枯燥,没有音乐播放器那么看起来绚丽,思来想去发现饿了么是一个不错的素材,一来它足够复杂,开放的外卖平台比一般的公司独有商店更加复杂。二来 见到那么多美食,大家也不会感觉到厌烦。 为啥是饿了么,而不是美团?原因很简单,饿了么的色调和布局是最漂亮的,看起来最舒服。 此项目大大小小共 45 个页面,涉及注册、登录、商品展示、购物车、下单等等,是一个完整的流程。一般公司即便是官网的单页面项目都没这么复杂,如果这个项目能驾驭的了,相信大部分公司的其他单页面应用也就不在话下,即便更复杂,也不会比这个高到哪里去。 因为利用业余时间来做,年前就开始写,又跨个年,周期有点长,项目从零布局到完成共用了2个多月的时间。 另外,这个项目和慕课网视频的那个饿了么没有任何关系,慕课网的项目只有一个页面,我在看完vue的官方文档后直接写了这个项目,没有参照任何人的代码,请大家不要混为一谈。 __注1:此项目纯属个人瞎搞,正常下单请选择饿了么官方客户端。__ __注2:项目预览地址和接口需要使用https访问哦!__ ## 技术栈 vue2 + vuex + vue-router + webpack + ES6/7 + fetch + sass + flex + svg ## 项目运行 #### 注意:由于涉及大量的 ES6/7 等新属性,node 需要 6.0 以上版本 ``` git clone https://github.com/bailicangdu/vue2-elm.git   cd vue2-elm npm install 或 yarn(推荐) npm run dev ``` ## 关于接口数据 此项目的所有接口数据都来源于配套的后台系统,[后台项目传送地址](https://github.com/bailicangdu/node-elm)。 如果想体验前后台同时开发,可以下载后台系统。 此时启动本项目的命令为:npm run local 而不是 npm run dev。 同时我们也提供了基于`element-ui`搭建的[后台管理页面](https://github.com/bailicangdu/vue2-manage) 如果只做前端开发,请忽略上面这几句话哟~ # 说明 > 如果对您有帮助,您可以点右上角 "Star" 支持一下 谢谢! ^_^ > 或者您可以 "follow" 一下,我会不断开源更多的有趣的项目 > 开发环境 macOS 10.12.3 Chrome 56  nodejs 6.10.0 > 特别感谢[@bailichen](https://github.com/bailichen), [@iceRao](https://github.com/raoenhui),在百忙之中抽出时间和我一起完成了这个项目,辛苦了🌹 > 如有问题请直接在 Issues 中提,或者您发现问题并有非常好的解决方案,欢迎 PR 👍 > [项目交流群](https://gitter.im/vue2-elm/Lobby?utm_source=share-link&utm_medium=link&utm_campaign=share-link) > 推荐一个 react + redux 开源项目,对react感兴趣的朋友赶紧去看看。[地址在这里](https://github.com/bailicangdu/react-pxq) > 另外一个 vue2 + vuex 的入门项目,比当前的项目简单很多,非常适合入门练习。[地址在这里](https://github.com/bailicangdu/vue2-happyfri) # 效果演示 [查看demo请戳这里](https://cangdu.org/elm/#/msite)(请用chrome手机模式预览) ### 移动端扫描下方二维码 # 目标功能 - [x] 定位功能 -- 完成 - [x] 选择城市 -- 完成 - [x] 搜索地址 -- 完成 - [x] 展示所选地址附近商家列表 -- 完成 - [x] 搜索美食,餐馆 -- 完成 - [x] 根据距离、销量、评分、特色菜、配送方式等进行排序和筛选 -- 完成 - [x] 餐馆食品列表页 -- 完成 - [x] 购物车功能 -- 完成 - [x] 店铺评价页面 -- 完成 - [x] 单个食品详情页面 -- 完成 - [x] 商家详情页 -- 完成 - [x] 登录、注册 -- 完成 - [x] 修改密码 -- 完成 - [x] 个人中心 -- 完成 - [x] 发送短信、语音验证 -- 完成 - [x] 下单功能 -- 完成 ✨✨🎉🎉 - [x] 订单列表 -- 完成 - [x] 订单详情 -- 完成 - [x] 下载App -- 完成 - [x] 添加、删除、修改收货地址 -- 完成 - [x] 帐户信息 -- 完成 - [x] 服务中心 -- 完成 - [x] 红包 -- 完成 - [x] 上传头像 -- 完成 - [ ] 付款 -- 臣妾做不到啊~~ # 总结 1、一般涉及到money的网页逻辑都比较复杂,尤其像饿了么这样一个开放的平台,商家和食品种类繁多,页面与页面之间交互复杂,在写到 购物车 和 下单 功能时众多的数据和逻辑一度让人很头疼,又没有设计和接口api文档,只能一步步摸索。 2、vue因其轻量级的框架在中小型项目中表现亮眼,在大型单页面应用中因为vuex的存在,表现依然出色,在处理复杂交互逻辑的时候,vuex的存在是不可或缺的。所以说利用 vue + vuex 完全可以去做大型的单页面项目。 3、项目写到现在,从 登录注册到、首页、搜索、商家列表、购物车、下单、订单列表、个人中心 一个流程走完之后、不但对vue的理解更深一层,而且对以后掌控大型项目的时候也有非常多的帮助,做一个实际的项目才能对自己有很大的提升。 4、曾一度怀疑,花几个月的时间做这样一个项目到底有没有意义,本来只是想做一个小项目练练手,没想到后来越写越多,不过坚持下来后我相信一切都是值得的。 5、项目已经完成,共45个页面。 # 最终目标 1、用node.js构建一个模拟外卖平台的后台系统。[地址在这里](https://github.com/bailicangdu/node-elm) 2、写出跨 Android 和 IOS 的原生APP版本。[地址在这里](https://github.com/bailicangdu/native-xdm) 3、如果时间来的及,会出一卖家版本。 所以我的目的是构建一个横跨前后端,移动IOS、Android的完整生态圈。 。。。敬请期待 # 部分截图 ### 商铺列表页 ### 商铺筛选页 ### 餐馆食品列表与购物车 ### 确认订单页 ### 搜索页 ### 登录页 ### 个人中心 # 项目布局 ``` . ├── build // webpack配置文件 ├── config // 项目打包路径 ├── elm // 上线项目文件,放在服务器即可正常访问 ├── screenshots // 项目截图 ├── src // 源码目录 │   ├── components // 组件 │   │   ├── common // 公共组件 │   │   │   ├── alertTip.vue // 弹出框组件 │   │   │   ├── buyCart.vue // 购物车组件 │   │   │   ├── computeTime.vue // 倒计时组件 │   │   │   ├── loading.vue // 页面初始化加载数据的动画组件 │   │   │   ├── mixin.js // 组件混合(包括:指令-下拉加载更多,处理图片地址) │   │   │   ├── ratingStar.vue // 评论的五颗星组件 │   │   │   └── shoplist.vue // msite和shop页面的餐馆列表公共组件 │   │   ├── footer │   │   │   └── footGuide.vue // 底部公共组件 │   │   └── header │   │   └── head.vue // 头部公共组件 │   ├── config // 基本配置 │   │   ├── env.js // 环境切换配置 │   │   ├── fetch.js // 获取数据 │   │   ├── mUtils.js // 常用的js方法 │   │   └── rem.js // px转换rem │   ├── images // 公共图片 │   ├── page │   │   ├── balance │   │   │   ├── balance.vue // 余额页 │   │   │   └── children │   │   │   └── detail.vue // 余额说明 │   │   ├── benefit │   │   │   ├── benefit.vue // 红包页 │   │   │   └── children │   │   │   ├── commend.vue // 推荐有奖 │   │   │   ├── coupon.vue // 代金券说明 │   │   │   ├── exchange.vue // 兑换红包 │   │   │   ├── hbDescription.vue // 红包说明 │   │   │   └── hbHistory.vue // 历史红包 │   │   ├── city │   │   │   └── city.vue // 当前城市页 │   │   ├── confirmOrder │   │   │   ├── children │   │   │   │   ├── children │   │   │   │   │   ├── addAddress.vue // 添加地址页 │   │   │   │   │   └── children │   │   │   │   │   └── searchAddress.vue // 搜索地址页 │   │   │   │   ├── chooseAddress.vue // 选择地址页 │   │   │   │   ├── invoice.vue // 选择发票页 │   │   │   │   ├── payment.vue // 付款页 │   │   │   │   ├── remark.vue // 订单备注页 │   │   │   │   └── userValidation.vue // 用户验证页 │   │   │   └── confirmOrder.vue // 确认订单页 │   │   ├── download │   │   │   └── download.vue // 下载App │   │   ├── find │   │   │   └── find.vue // 发现页 │   │   ├── food │   │   │   └── food.vue // 食品筛选排序页 │   │   ├── forget │   │   │   └── forget.vue // 忘记密码,修改密码页 │   │   ├── home │   │   │   └── home.vue // 首页 │   │   ├── login │   │   │   └── login.vue // 登录注册页 │   │   ├── msite │   │   │   └── msite.vue // 商铺列表页 │   │   ├── order │   │   │   ├── children │   │   │   │   └── orderDetail.vue // 订单详情页 │   │   │   └── order.vue // 订单列表页 │   │   ├── points │   │   │   ├── children │   │   │   │   └── detail.vue // 积分说明 │   │   │   └── points.vue // 积分页 │   │   ├── profile │   │   │   ├── children │   │   │   │   ├── children │   │   │   │   │   ├── address.vue // 地址 │   │   │   │   │   └── children │   │   │   │   │   ├── add.vue // 新增地址 │   │   │   │   │   └── children │   │   │   │   │   └── addDetail.vue // 搜索地址 │   │   │   │   ├── info.vue // 帐户信息 │   │   │   │   └── setusername.vue // 重置用户名 │   │   │   └── profile.vue // 个人中心 │   │   ├── search │   │   │   └── search.vue // 搜索页 │   │   ├── service │   │   │   ├── children │   │   │   │   └── questionDetail.vue // 问题详情 │   │   │   └── service.vue // 服务中心 │   │   ├── shop │   │   │   ├── children │   │   │   │   ├── children │   │   │   │   │   └── shopSafe.vue // 商铺认证信息页 │   │   │   │   ├── foodDetail.vue // 商铺信息页 │   │   │   │   └── shopDetail.vue // 单个商铺信息页 │   │   │   └── shop.vue // 商铺筛选页 │   │   └── vipcard │   │   ├── children │   │   │   ├── invoiceRecord.vue // 购买记录 │   │   │   ├── useCart.vue // 使用卡号购买 │   │   │   └── vipDescription.vue // 会员说明 │   │   └── vipcard.vue // 会员卡办理页 │   ├── plugins // 引用的插件 │   ├── router │   │   └── router.js // 路由配置 │   ├── service // 数据交互统一调配 │   │   ├── getData.js // 获取数据的统一调配文件,对接口进行统一管理 │   │   └── tempdata // 开发阶段的临时数据 │   ├── store // vuex的状态管理 │   │   ├── action.js // 配置actions │   │   ├── getters.js // 配置getters │   │   ├── index.js // 引用vuex,创建store │   │   ├── modules // store模块 │   │   ├── mutation-types.js // 定义常量muations名 │   │   └── mutations.js // 配置mutations │   └── style │   ├── common.scss // 公共样式文件 │   ├── mixin.scss // 样式配置文件 │   └── swiper.min.css │   ├── App.vue // 页面入口文件 │   ├── main.js // 程序入口文件,加载各种公共组件 ├── favicon.ico // 图标 ├── index.html // 入口html文件 . 56 directories, 203 files ``` # License [GPL](https://github.com/bailicangdu/vue2-elm/blob/master/COPYING) ================================================ FILE: build/build.js ================================================ // https://github.com/shelljs/shelljs require('shelljs/global') env.NODE_ENV = 'production' var path = require('path') var config = require('../config') var ora = require('ora') var webpack = require('webpack') var webpackConfig = require('./webpack.prod.conf') var spinner = ora('building for production...') spinner.start() var assetsPath = path.join(config.build.assetsRoot, config.build.assetsSubDirectory) rm('-rf', assetsPath) mkdir('-p', assetsPath) cp('-R', 'static/*', assetsPath) webpack(webpackConfig, function(err, stats) { spinner.stop() if (err) throw err process.stdout.write(stats.toString({ colors: true, modules: false, children: false, chunks: false, chunkModules: false }) + '\n') }) ================================================ FILE: build/dev-client.js ================================================ /* eslint-disable */ require('eventsource-polyfill') var hotClient = require('webpack-hot-middleware/client?noInfo=true&reload=true') hotClient.subscribe(function(event) { if (event.action === 'reload') { window.location.reload() } }) ================================================ FILE: build/dev-server.js ================================================ var config = require('../config') if (!process.env.NODE_ENV) process.env.NODE_ENV = JSON.parse(config.dev.env.NODE_ENV) var path = require('path') var express = require('express') var webpack = require('webpack') var opn = require('opn') var proxyMiddleware = require('http-proxy-middleware') var webpackConfig = require('./webpack.dev.conf') // default port where dev server listens for incoming traffic var port = process.env.PORT || config.dev.port // Define HTTP proxies to your custom API backend // https://github.com/chimurai/http-proxy-middleware var server = express() var compiler = webpack(webpackConfig) var devMiddleware = require('webpack-dev-middleware')(compiler, { publicPath: webpackConfig.output.publicPath, stats: { colors: true, chunks: false } }) var hotMiddleware = require('webpack-hot-middleware')(compiler) // force page reload when html-webpack-plugin template changes compiler.plugin('compilation', function(compilation) { compilation.plugin('html-webpack-plugin-after-emit', function(data, cb) { hotMiddleware.publish({ action: 'reload' }) cb() }) }) var context = config.dev.context switch(process.env.NODE_ENV){ case 'local': var proxypath = 'http://localhost:8001'; break; case 'online': var proxypath = 'https://elm.cangdu.org'; break; default: var proxypath = config.dev.proxypath; } var options = { target: proxypath, changeOrigin: true, } if (context.length) { server.use(proxyMiddleware(context, options)) } // handle fallback for HTML5 history API server.use(require('connect-history-api-fallback')()) // serve webpack bundle output server.use(devMiddleware) // enable hot-reload and state-preserving // compilation error display server.use(hotMiddleware) // serve pure static assets var staticPath = path.posix.join(config.dev.assetsPublicPath, config.dev.assetsSubDirectory) server.use(staticPath, express.static('./static')) module.exports = server.listen(port, function(err) { if (err) { console.log(err) return } var uri = 'http://localhost:' + port console.log('Listening at ' + uri + '\n') // when env is testing, don't need open it if (process.env.NODE_ENV !== 'testing') { opn(uri) } }) ================================================ FILE: build/utils.js ================================================ var path = require('path') var config = require('../config') var ExtractTextPlugin = require('extract-text-webpack-plugin') exports.assetsPath = function(_path) { var assetsSubDirectory = process.env.NODE_ENV === 'production' ? config.build.assetsSubDirectory : config.dev.assetsSubDirectory return path.posix.join(assetsSubDirectory, _path) } exports.cssLoaders = function(options) { options = options || {} // generate loader string to be used with extract text plugin function generateLoaders(loaders) { var sourceLoader = loaders.map(function(loader) { var extraParamChar if (/\?/.test(loader)) { loader = loader.replace(/\?/, '-loader?') extraParamChar = '&' } else { loader = loader + '-loader' extraParamChar = '?' } return loader + (options.sourceMap ? extraParamChar + 'sourceMap' : '') }).join('!') // (which is the case during production build) if (options.extract) { return ExtractTextPlugin.extract('vue-style-loader', sourceLoader) } else { return ['vue-style-loader', sourceLoader].join('!') } } // http://vuejs.github.io/vue-loader/en/configurations/extract-css.html return { css: generateLoaders(['css']), postcss: generateLoaders(['css']), less: generateLoaders(['css', 'less']), sass: generateLoaders(['css', 'sass?indentedSyntax']), scss: generateLoaders(['css', 'sass']), stylus: generateLoaders(['css', 'stylus']), styl: generateLoaders(['css', 'stylus']) } } // Generate loaders for standalone style files (outside of .vue) exports.styleLoaders = function(options) { var output = [] var loaders = exports.cssLoaders(options) for (var extension in loaders) { var loader = loaders[extension] output.push({ test: new RegExp('\\.' + extension + '$'), loader: loader }) } return output } ================================================ FILE: build/webpack.base.conf.js ================================================ var path = require('path') var config = require('../config') var utils = require('./utils') var projectRoot = path.resolve(__dirname, '../') var env = process.env.NODE_ENV // check env & config/index.js to decide weither to enable CSS Sourcemaps for the // various preprocessor loaders added to vue-loader at the end of this file var cssSourceMapDev = (env === 'development' && config.dev.cssSourceMap) var cssSourceMapProd = (env === 'production' && config.build.productionSourceMap) var useCssSourceMap = cssSourceMapDev || cssSourceMapProd module.exports = { entry: { app: './src/main.js' }, output: { path: config.build.assetsRoot, publicPath: process.env.NODE_ENV === 'production' ? config.build.assetsPublicPath : config.dev.assetsPublicPath, filename: '[name].js' }, resolve: { extensions: ['', '.js', '.vue', '.less', '.css', '.scss'], fallback: [path.join(__dirname, '../node_modules')], alias: { 'vue$': 'vue/dist/vue.common.js', 'src': path.resolve(__dirname, '../src'), 'assets': path.resolve(__dirname, '../src/assets'), 'components': path.resolve(__dirname, '../src/components') } }, resolveLoader: { fallback: [path.join(__dirname, '../node_modules')] }, module: { loaders: [{ test: /\.vue$/, loader: 'vue' }, { test: /\.js$/, loader: 'babel', include: projectRoot, exclude: /node_modules/ }, { test: /\.json$/, loader: 'json' }, { test: /\.(png|jpe?g|gif|svg)(\?.*)?$/, loader: 'url', query: { limit: 10000, name: utils.assetsPath('img/[name].[ext]') } }, { test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/, loader: 'url', query: { limit: 10000, name: utils.assetsPath('fonts/[name].[hash:7].[ext]') } }] }, vue: { loaders: utils.cssLoaders({ sourceMap: useCssSourceMap }), postcss: [ require('autoprefixer')({ browsers: [ 'Android >= 4.4', 'iOS >= 8' ] }) ] } } ================================================ FILE: build/webpack.dev.conf.js ================================================ var config = require('../config') var webpack = require('webpack') var merge = require('webpack-merge') var utils = require('./utils') var baseWebpackConfig = require('./webpack.base.conf') var HtmlWebpackPlugin = require('html-webpack-plugin') // add hot-reload related code to entry chunks Object.keys(baseWebpackConfig.entry).forEach(function(name) { baseWebpackConfig.entry[name] = ['./build/dev-client'].concat(baseWebpackConfig.entry[name]) }) module.exports = merge(baseWebpackConfig, { module: { loaders: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap }) }, // eval-source-map is faster for development devtool: '#eval-source-map', plugins: [ new webpack.DefinePlugin({ 'process.env': config.dev.env }), // https://github.com/glenjamin/webpack-hot-middleware#installation--usage new webpack.optimize.OccurenceOrderPlugin(), new webpack.HotModuleReplacementPlugin(), new webpack.NoErrorsPlugin(), // https://github.com/ampedandwired/html-webpack-plugin new HtmlWebpackPlugin({ filename: 'index.html', template: 'index.html', favicon: 'favicon.ico', inject: true }) ] }) ================================================ FILE: build/webpack.prod.conf.js ================================================ var path = require('path') var config = require('../config') var utils = require('./utils') var webpack = require('webpack') var merge = require('webpack-merge') var baseWebpackConfig = require('./webpack.base.conf') var ExtractTextPlugin = require('extract-text-webpack-plugin') var HtmlWebpackPlugin = require('html-webpack-plugin') var env = config.build.env var webpackConfig = merge(baseWebpackConfig, { module: { loaders: utils.styleLoaders({ sourceMap: config.build.productionSourceMap, extract: true }) }, //devtool: config.build.productionSourceMap ? '#source-map' : false, output: { path: config.build.assetsRoot, filename: utils.assetsPath('js/[name].js'), chunkFilename: utils.assetsPath('js/[name].[chunkhash].min.js') }, vue: { loaders: utils.cssLoaders({ sourceMap: config.build.productionSourceMap, extract: true }) }, plugins: [ // http://vuejs.github.io/vue-loader/en/workflow/production.html new webpack.DefinePlugin({ 'process.env': env }), new webpack.optimize.UglifyJsPlugin({ compress: { warnings: false } }), new webpack.optimize.OccurrenceOrderPlugin(), // extract css into its own file new ExtractTextPlugin(utils.assetsPath('css/[name].css')), // generate dist index.html with correct asset hash for caching. // you can customize output by editing /index.html // see https://github.com/ampedandwired/html-webpack-plugin new HtmlWebpackPlugin({ filename: config.build.index, template: 'index.html', inject: true, // minify: { // removeComments: true, // collapseWhitespace: true, // removeAttributeQuotes: true // // more options: // // https://github.com/kangax/html-minifier#options-quick-reference // }, // necessary to consistently work with multiple chunks via CommonsChunkPlugin chunksSortMode: 'dependency' }), // split vendor js into its own file new webpack.optimize.CommonsChunkPlugin({ name: 'vendor', minChunks: function(module, count) { // any required modules inside node_modules are extracted to vendor return ( module.resource && /\.js$/.test(module.resource) && module.resource.indexOf( path.join(__dirname, '../node_modules') ) === 0 ) } }), // extract webpack runtime and module manifest to its own file in order to // prevent vendor hash from being updated whenever app bundle is updated new webpack.optimize.CommonsChunkPlugin({ name: 'manifest', chunks: ['vendor'] }) ] }) if (config.build.productionGzip) { var CompressionWebpackPlugin = require('compression-webpack-plugin') webpackConfig.plugins.push( new CompressionWebpackPlugin({ asset: '[path].gz[query]', algorithm: 'gzip', test: new RegExp( '\\.(' + config.build.productionGzipExtensions.join('|') + ')$' ), threshold: 10240, minRatio: 0.8 }) ) } module.exports = webpackConfig ================================================ FILE: config/index.js ================================================ // see http://vuejs-templates.github.io/webpack for documentation. var path = require('path') module.exports = { build: { env: { NODE_ENV: '"production"' }, index: path.resolve(__dirname, '../elm/index.html'), assetsRoot: path.resolve(__dirname, '../elm'), assetsSubDirectory: 'static', assetsPublicPath: '/elm/', productionSourceMap: true, // Surge or Netlify already gzip all static assets for you. // Before setting to `true`, make sure to: // npm install --save-dev compression-webpack-plugin productionGzip: false, productionGzipExtensions: ['js', 'css'] }, dev: { env: { NODE_ENV: '"development"' }, port: 8000, assetsSubDirectory: 'static', assetsPublicPath: '/', context: [ //代理路径 '/shopping', '/ugc', '/v1', '/v2', '/v3', '/v4', '/bos', '/member', '/promotion', '/eus', '/payapi', '/img', ], proxypath: 'http://cangdu.org:8001', // CSS Sourcemaps off by default because relative paths are "buggy" // with this option, according to the CSS-Loader README // (https://github.com/webpack/css-loader#sourcemaps) // In our experience, they generally work as expected, // just be aware of this issue when enabling this option. cssSourceMap: false } } ================================================ FILE: index.html ================================================ elm
================================================ FILE: package.json ================================================ { "name": "elm", "version": "2.0.1", "description": "vue2-elm", "author": "cangdu <1264889788@qq.com>", "private": true, "license": "GPL", "scripts": { "dev": "cross-env NODE_ENV=online node build/dev-server.js", "local": "cross-env NODE_ENV=local node build/dev-server.js", "build": "node build/build.js" }, "dependencies": { "better-scroll": "1.15.2", "fastclick": "^1.0.6", "iscroll": "^5.2.0", "showdown": "^1.6.4", "vue": "^2.1.0", "vue-router": "^2.1.1", "vuex": "^2.0.0" }, "devDependencies": { "autoprefixer": "^6.4.0", "autoprefixer-loader": "^3.2.0", "babel-core": "^6.0.0", "babel-loader": "^6.0.0", "babel-plugin-transform-runtime": "^6.0.0", "babel-preset-es2015": "^6.0.0", "babel-preset-stage-2": "^6.0.0", "babel-register": "^6.0.0", "babel-runtime": "^6.23.0", "chalk": "^1.1.3", "connect-history-api-fallback": "^1.1.0", "cross-env": "^5.0.0", "css-loader": "^0.25.0", "eventsource-polyfill": "^0.9.6", "express": "^4.13.3", "extract-text-webpack-plugin": "^1.0.1", "file-loader": "^0.9.0", "function-bind": "^1.0.2", "html-webpack-plugin": "^2.8.1", "http-proxy-middleware": "^0.17.2", "json-loader": "^0.5.4", "less": "^2.7.1", "less-loader": "^2.2.3", "node-gyp": "^3.4.0", "node-sass": "^4.9.2", "opn": "^4.0.2", "ora": "^0.3.0", "sass": "^0.5.0", "sass-loader": "^4.1.1", "scss": "^0.2.4", "scss-loader": "0.0.1", "semver": "^5.3.0", "shelljs": "^0.7.4", "style-loader": "^0.13.1", "url-loader": "^0.5.7", "vue-loader": "^10.0.0", "vue-style-loader": "^1.0.0", "vue-template-compiler": "^2.1.0", "webpack": "^1.13.2", "webpack-dev-middleware": "^1.8.3", "webpack-dev-server": "^1.16.2", "webpack-hot-middleware": "^2.12.2", "webpack-merge": "^0.14.1" }, "engines": { "node": ">= 4.0.0", "npm": ">= 3.0.0" } } ================================================ FILE: src/App.vue ================================================ ================================================ FILE: src/components/common/alertTip.vue ================================================ ================================================ FILE: src/components/common/buyCart.vue ================================================ ================================================ FILE: src/components/common/computeTime.vue ================================================ ================================================ FILE: src/components/common/loading.vue ================================================ ================================================ FILE: src/components/common/mixin.js ================================================ import { getStyle } from '../../config/mUtils' import { imgBaseUrl, localapi, proapi } from '../../config/env' export const loadMore = { directives: { 'load-more': { bind: (el, binding) => { let windowHeight = window.screen.height; let height; let setTop; let paddingBottom; let marginBottom; let requestFram; let oldScrollTop; let scrollEl; let heightEl; let scrollType = el.attributes.type && el.attributes.type.value; let scrollReduce = 2; if (scrollType == 2) { scrollEl = el; heightEl = el.children[0]; } else { scrollEl = document.body; heightEl = el; } el.addEventListener('touchstart', () => { height = heightEl.clientHeight; if (scrollType == 2) { height = height } setTop = el.offsetTop; paddingBottom = getStyle(el, 'paddingBottom'); marginBottom = getStyle(el, 'marginBottom'); }, false) el.addEventListener('touchmove', () => { loadMore(); }, false) el.addEventListener('touchend', () => { oldScrollTop = scrollEl.scrollTop; moveEnd(); }, false) const moveEnd = () => { requestFram = requestAnimationFrame(() => { if (scrollEl.scrollTop != oldScrollTop) { oldScrollTop = scrollEl.scrollTop; moveEnd() } else { cancelAnimationFrame(requestFram); height = heightEl.clientHeight; loadMore(); } }) } const loadMore = () => { if (scrollEl.scrollTop + windowHeight >= height + setTop + paddingBottom + marginBottom - scrollReduce) { binding.value(); } } } } } }; export const getImgPath = { methods: { //传递过来的图片地址需要处理后才能正常使用 getImgPath(path) { let suffix; if (!path) { return '//elm.cangdu.org/img/default.jpg' } if (path.indexOf('jpeg') !== -1) { suffix = '.jpeg' } else { suffix = '.png' } let url = '/' + path.substr(0, 1) + '/' + path.substr(1, 2) + '/' + path.substr(3) + suffix; return 'https://fuss10.elemecdn.com' + url }, } } ================================================ FILE: src/components/common/ratingStar.vue ================================================ ================================================ FILE: src/components/common/shoplist.vue ================================================ ================================================ FILE: src/components/common/svg.vue ================================================ ================================================ FILE: src/components/footer/footGuide.vue ================================================ ================================================ FILE: src/components/header/head.vue ================================================ ================================================ FILE: src/config/env.js ================================================ /** * 配置编译环境和线上环境之间的切换 * * baseUrl: 域名地址 * routerMode: 路由模式 * imgBaseUrl: 图片所在域名地址 * */ let baseUrl = ''; let routerMode = 'hash'; let imgBaseUrl = ''; if (process.env.NODE_ENV == 'development') { imgBaseUrl = '/img/'; }else if(process.env.NODE_ENV == 'production'){ baseUrl = '//elm.cangdu.org'; imgBaseUrl = '//elm.cangdu.org/img/'; } export { baseUrl, routerMode, imgBaseUrl, } ================================================ FILE: src/config/fetch.js ================================================ import { baseUrl } from './env' export default async(url = '', data = {}, type = 'GET', method = 'fetch') => { type = type.toUpperCase(); url = baseUrl + url; if (type == 'GET') { let dataStr = ''; //数据拼接字符串 Object.keys(data).forEach(key => { dataStr += key + '=' + data[key] + '&'; }) if (dataStr !== '') { dataStr = dataStr.substr(0, dataStr.lastIndexOf('&')); url = url + '?' + dataStr; } } if (window.fetch && method == 'fetch') { let requestConfig = { credentials: 'include', method: type, headers: { 'Accept': 'application/json', 'Content-Type': 'application/json' }, mode: "cors", cache: "force-cache" } if (type == 'POST') { Object.defineProperty(requestConfig, 'body', { value: JSON.stringify(data) }) } try { const response = await fetch(url, requestConfig); const responseJson = await response.json(); return responseJson } catch (error) { throw new Error(error) } } else { return new Promise((resolve, reject) => { let requestObj; if (window.XMLHttpRequest) { requestObj = new XMLHttpRequest(); } else { requestObj = new ActiveXObject; } let sendData = ''; if (type == 'POST') { sendData = JSON.stringify(data); } requestObj.open(type, url, true); requestObj.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); requestObj.send(sendData); requestObj.onreadystatechange = () => { if (requestObj.readyState == 4) { if (requestObj.status == 200) { let obj = requestObj.response if (typeof obj !== 'object') { obj = JSON.parse(obj); } resolve(obj) } else { reject(requestObj) } } } }) } } ================================================ FILE: src/config/mUtils.js ================================================ /** * 存储localStorage */ export const setStore = (name, content) => { if (!name) return; if (typeof content !== 'string') { content = JSON.stringify(content); } window.localStorage.setItem(name, content); } /** * 获取localStorage */ export const getStore = name => { if (!name) return; return window.localStorage.getItem(name); } /** * 删除localStorage */ export const removeStore = name => { if (!name) return; window.localStorage.removeItem(name); } /** * 获取style样式 */ export const getStyle = (element, attr, NumberMode = 'int') => { let target; // scrollTop 获取方式不同,没有它不属于style,而且只有document.body才能用 if (attr === 'scrollTop') { target = element.scrollTop; }else if(element.currentStyle){ target = element.currentStyle[attr]; }else{ target = document.defaultView.getComputedStyle(element,null)[attr]; } //在获取 opactiy 时需要获取小数 parseFloat return NumberMode == 'float'? parseFloat(target) : parseInt(target); } /** * 页面到达底部,加载更多 */ export const loadMore = (element, callback) => { let windowHeight = window.screen.height; let height; let setTop; let paddingBottom; let marginBottom; let requestFram; let oldScrollTop; document.body.addEventListener('scroll',() => { loadMore(); }, false) //运动开始时获取元素 高度 和 offseTop, pading, margin element.addEventListener('touchstart',() => { height = element.offsetHeight; setTop = element.offsetTop; paddingBottom = getStyle(element,'paddingBottom'); marginBottom = getStyle(element,'marginBottom'); },{passive: true}) //运动过程中保持监听 scrollTop 的值判断是否到达底部 element.addEventListener('touchmove',() => { loadMore(); },{passive: true}) //运动结束时判断是否有惯性运动,惯性运动结束判断是非到达底部 element.addEventListener('touchend',() => { oldScrollTop = document.body.scrollTop; moveEnd(); },{passive: true}) const moveEnd = () => { requestFram = requestAnimationFrame(() => { if (document.body.scrollTop != oldScrollTop) { oldScrollTop = document.body.scrollTop; loadMore(); moveEnd(); }else{ cancelAnimationFrame(requestFram); //为了防止鼠标抬起时已经渲染好数据从而导致重获取数据,应该重新获取dom高度 height = element.offsetHeight; loadMore(); } }) } const loadMore = () => { if (document.body.scrollTop + windowHeight >= height + setTop + paddingBottom + marginBottom) { callback(); } } } /** * 显示返回顶部按钮,开始、结束、运动 三个过程中调用函数判断是否达到目标点 */ export const showBack = callback => { let requestFram; let oldScrollTop; document.addEventListener('scroll',() => { showBackFun(); }, false) document.addEventListener('touchstart',() => { showBackFun(); },{passive: true}) document.addEventListener('touchmove',() => { showBackFun(); },{passive: true}) document.addEventListener('touchend',() => { oldScrollTop = document.body.scrollTop; moveEnd(); },{passive: true}) const moveEnd = () => { requestFram = requestAnimationFrame(() => { if (document.body.scrollTop != oldScrollTop) { oldScrollTop = document.body.scrollTop; moveEnd(); }else{ cancelAnimationFrame(requestFram); } showBackFun(); }) } //判断是否达到目标点 const showBackFun = () => { if (document.body.scrollTop > 500) { callback(true); }else{ callback(false); } } } /** * 运动效果 * @param {HTMLElement} element 运动对象,必选 * @param {JSON} target 属性:目标值,必选 * @param {number} duration 运动时间,可选 * @param {string} mode 运动模式,可选 * @param {function} callback 可选,回调函数,链式动画 */ export const animate = (element, target, duration = 400, mode = 'ease-out', callback) => { clearInterval(element.timer); //判断不同参数的情况 if (duration instanceof Function) { callback = duration; duration = 400; }else if(duration instanceof String){ mode = duration; duration = 400; } //判断不同参数的情况 if (mode instanceof Function) { callback = mode; mode = 'ease-out'; } //获取dom样式 const attrStyle = attr => { if (attr === "opacity") { return Math.round(getStyle(element, attr, 'float') * 100); } else { return getStyle(element, attr); } } //根字体大小,需要从此将 rem 改成 px 进行运算 const rootSize = parseFloat(document.documentElement.style.fontSize); const unit = {}; const initState = {}; //获取目标属性单位和初始样式值 Object.keys(target).forEach(attr => { if (/[^\d^\.]+/gi.test(target[attr])) { unit[attr] = target[attr].match(/[^\d^\.]+/gi)[0] || 'px'; }else{ unit[attr] = 'px'; } initState[attr] = attrStyle(attr); }); //去掉传入的后缀单位 Object.keys(target).forEach(attr => { if (unit[attr] == 'rem') { target[attr] = Math.ceil(parseInt(target[attr])*rootSize); }else{ target[attr] = parseInt(target[attr]); } }); let flag = true; //假设所有运动到达终点 const remberSpeed = {};//记录上一个速度值,在ease-in模式下需要用到 element.timer = setInterval(() => { Object.keys(target).forEach(attr => { let iSpeed = 0; //步长 let status = false; //是否仍需运动 let iCurrent = attrStyle(attr) || 0; //当前元素属性址 let speedBase = 0; //目标点需要减去的基础值,三种运动状态的值都不同 let intervalTime; //将目标值分为多少步执行,数值越大,步长越小,运动时间越长 switch(mode){ case 'ease-out': speedBase = iCurrent; intervalTime = duration*5/400; break; case 'linear': speedBase = initState[attr]; intervalTime = duration*20/400; break; case 'ease-in': let oldspeed = remberSpeed[attr] || 0; iSpeed = oldspeed + (target[attr] - initState[attr])/duration; remberSpeed[attr] = iSpeed break; default: speedBase = iCurrent; intervalTime = duration*5/400; } if (mode !== 'ease-in') { iSpeed = (target[attr] - speedBase) / intervalTime; iSpeed = iSpeed > 0 ? Math.ceil(iSpeed) : Math.floor(iSpeed); } //判断是否达步长之内的误差距离,如果到达说明到达目标点 switch(mode){ case 'ease-out': status = iCurrent != target[attr]; break; case 'linear': status = Math.abs(Math.abs(iCurrent) - Math.abs(target[attr])) > Math.abs(iSpeed); break; case 'ease-in': status = Math.abs(Math.abs(iCurrent) - Math.abs(target[attr])) > Math.abs(iSpeed); break; default: status = iCurrent != target[attr]; } if (status) { flag = false; //opacity 和 scrollTop 需要特殊处理 if (attr === "opacity") { element.style.filter = "alpha(opacity:" + (iCurrent + iSpeed) + ")"; element.style.opacity = (iCurrent + iSpeed) / 100; } else if (attr === 'scrollTop') { element.scrollTop = iCurrent + iSpeed; }else{ element.style[attr] = iCurrent + iSpeed + 'px'; } } else { flag = true; } if (flag) { clearInterval(element.timer); if (callback) { callback(); } } }) }, 20); } ================================================ FILE: src/config/rem.js ================================================ (function(doc, win) { var docEl = doc.documentElement, resizeEvt = 'orientationchange' in window ? 'orientationchange' : 'resize', recalc = function() { var clientWidth = docEl.clientWidth; if (!clientWidth) return; docEl.style.fontSize = 20 * (clientWidth / 320) + 'px'; }; if (!doc.addEventListener) return; win.addEventListener(resizeEvt, recalc, false); doc.addEventListener('DOMContentLoaded', recalc, false); })(document, window); ================================================ FILE: src/main.js ================================================ import Vue from 'vue' import VueRouter from 'vue-router' import routes from './router/router' import store from './store/' import {routerMode} from './config/env' import './config/rem' Vue.use(VueRouter) const router = new VueRouter({ routes, mode: routerMode, strict: process.env.NODE_ENV !== 'production', scrollBehavior (to, from, savedPosition) { if (savedPosition) { return savedPosition } else { if (from.meta.keepAlive) { from.meta.savedPosition = document.body.scrollTop; } return { x: 0, y: to.meta.savedPosition || 0 } } } }) new Vue({ router, store, }).$mount('#app') ================================================ FILE: src/page/balance/balance.vue ================================================ ================================================ FILE: src/page/balance/children/detail.vue ================================================ ================================================ FILE: src/page/benefit/benefit.vue ================================================ ================================================ FILE: src/page/benefit/children/commend.vue ================================================ ================================================ FILE: src/page/benefit/children/coupon.vue ================================================ ================================================ FILE: src/page/benefit/children/exchange.vue ================================================ ================================================ FILE: src/page/benefit/children/hbDescription.vue ================================================ ================================================ FILE: src/page/benefit/children/hbHistory.vue ================================================ ================================================ FILE: src/page/city/city.vue ================================================ ================================================ FILE: src/page/confirmOrder/children/children/addAddress.vue ================================================ ================================================ FILE: src/page/confirmOrder/children/children/children/searchAddress.vue ================================================ ================================================ FILE: src/page/confirmOrder/children/chooseAddress.vue ================================================ ================================================ FILE: src/page/confirmOrder/children/invoice.vue ================================================ ================================================ FILE: src/page/confirmOrder/children/payment.vue ================================================ ================================================ FILE: src/page/confirmOrder/children/remark.vue ================================================ ================================================ FILE: src/page/confirmOrder/children/userValidation.vue ================================================ ================================================ FILE: src/page/confirmOrder/confirmOrder.vue ================================================ ================================================ FILE: src/page/download/download.vue ================================================ ================================================ FILE: src/page/find/find.vue ================================================ ================================================ FILE: src/page/food/food.vue ================================================ ================================================ FILE: src/page/forget/forget.vue ================================================ ================================================ FILE: src/page/home/home.vue ================================================ ================================================ FILE: src/page/login/login.vue ================================================ ================================================ FILE: src/page/msite/msite.vue ================================================ ================================================ FILE: src/page/order/children/orderDetail.vue ================================================ ================================================ FILE: src/page/order/order.vue ================================================ ================================================ FILE: src/page/points/children/detail.vue ================================================ ================================================ FILE: src/page/points/points.vue ================================================ ================================================ FILE: src/page/profile/children/children/address.vue ================================================ ================================================ FILE: src/page/profile/children/children/children/add.vue ================================================ ================================================ FILE: src/page/profile/children/children/children/children/addDetail.vue ================================================ ================================================ FILE: src/page/profile/children/children/setusername.vue ================================================ ================================================ FILE: src/page/profile/children/info.vue ================================================ ================================================ FILE: src/page/profile/profile.vue ================================================ ================================================ FILE: src/page/search/search.vue ================================================ ================================================ FILE: src/page/service/children/questionDetail.vue ================================================ ================================================ FILE: src/page/service/service.vue ================================================ ================================================ FILE: src/page/shop/children/children/shopSafe.vue ================================================ ================================================ FILE: src/page/shop/children/foodDetail.vue ================================================ ================================================ FILE: src/page/shop/children/shopDetail.vue ================================================ ================================================ FILE: src/page/shop/shop.vue ================================================ ================================================ FILE: src/page/vipcard/children/invoiceRecord.vue ================================================ ================================================ FILE: src/page/vipcard/children/useCart.vue ================================================ ================================================ FILE: src/page/vipcard/children/vipDescription.vue ================================================ ================================================ FILE: src/page/vipcard/vipcard.vue ================================================ ================================================ FILE: src/router/router.js ================================================ import App from '../App' const home = r => require.ensure([], () => r(require('../page/home/home')), 'home') const city = r => require.ensure([], () => r(require('../page/city/city')), 'city') const msite = r => require.ensure([], () => r(require('../page/msite/msite')), 'msite') const search = r => require.ensure([], () => r(require('../page/search/search')), 'search') const shop = r => require.ensure([], () => r(require('../page/shop/shop')), 'shop') const login = r => require.ensure([], () => r(require('../page/login/login')), 'login') const profile = r => require.ensure([], () => r(require('../page/profile/profile')), 'profile') const forget = r => require.ensure([], () => r(require('../page/forget/forget')), 'forget') const order = r => require.ensure([], () => r(require('../page/order/order')), 'order') const orderDetail = r => require.ensure([], () => r(require('../page/order/children/orderDetail')), 'orderDetail') const vipcard = r => require.ensure([], () => r(require('../page/vipcard/vipcard')), 'vipcard') const invoiceRecord = r => require.ensure([], () => r(require('../page/vipcard/children/invoiceRecord')), 'invoiceRecord') const useCart = r => require.ensure([], () => r(require('../page/vipcard/children/useCart')), 'useCart') const vipDescription = r => require.ensure([], () => r(require('../page/vipcard/children/vipDescription')), 'vipDescription') const food = r => require.ensure([], () => r(require('../page/food/food')), 'food') const confirmOrder = r => require.ensure([], () => r(require('../page/confirmOrder/confirmOrder')), 'confirmOrder') const remark = r => require.ensure([], () => r(require('../page/confirmOrder/children/remark')), 'remark') const payment = r => require.ensure([], () => r(require('../page/confirmOrder/children/payment')), 'payment') const userValidation = r => require.ensure([], () => r(require('../page/confirmOrder/children/userValidation')), 'userValidation') const invoice = r => require.ensure([], () => r(require('../page/confirmOrder/children/invoice')), 'invoice') const chooseAddress = r => require.ensure([], () => r(require('../page/confirmOrder/children/chooseAddress')), 'chooseAddress') const addAddress = r => require.ensure([], () => r(require('../page/confirmOrder/children/children/addAddress')), 'addAddress') const searchAddress = r => require.ensure([], () => r(require('../page/confirmOrder/children/children/children/searchAddress')), 'searchAddress') const foodDetail = r => require.ensure([], () => r(require('../page/shop/children/foodDetail')), 'foodDetail') const shopDetail = r => require.ensure([], () => r(require('../page/shop/children/shopDetail')), 'shopDetail') const shopSafe = r => require.ensure([], () => r(require('../page/shop/children/children/shopSafe')), 'shopSafe') const info = r => require.ensure([], () => r(require('../page/profile/children/info')), 'info') const setusername = r => require.ensure([], () => r(require('../page/profile/children/children/setusername')), 'setusername') const address = r => require.ensure([], () => r(require('../page/profile/children/children/address')), 'address') const add = r => require.ensure([], () => r(require('../page/profile/children/children/children/add')), 'add') const addDetail = r => require.ensure([], () => r(require('../page/profile/children/children/children/children/addDetail')), 'addDetail') const balance = r => require.ensure([], () => r(require('../page/balance/balance')), 'balance') const balanceDetail = r => require.ensure([], () => r(require('../page/balance/children/detail')), 'balanceDetail') const benefit = r => require.ensure([], () => r(require('../page/benefit/benefit')), 'benefit') const coupon = r => require.ensure([], () => r(require('../page/benefit/children/coupon')), 'coupon') const hbDescription = r => require.ensure([], () => r(require('../page/benefit/children/hbDescription')), 'hbDescription') const hbHistory = r => require.ensure([], () => r(require('../page/benefit/children/hbHistory')), 'hbHistory') const exchange = r => require.ensure([], () => r(require('../page/benefit/children/exchange')), 'exchange') const commend = r => require.ensure([], () => r(require('../page/benefit/children/commend')), 'commend') const points = r => require.ensure([], () => r(require('../page/points/points')), 'points') const pointsDetail = r => require.ensure([], () => r(require('../page/points/children/detail')), 'pointsDetail') const service = r => require.ensure([], () => r(require('../page/service/service')), 'service') const questionDetail = r => require.ensure([], () => r(require('../page/service/children/questionDetail')), 'questionDetail') const find = r => require.ensure([], () => r(require('../page/find/find')), 'find') const download = r => require.ensure([], () => r(require('../page/download/download')), 'download') export default [{ path: '/', component: App, //顶层路由,对应index.html children: [ //二级路由。对应App.vue //地址为空时跳转home页面 { path: '', redirect: '/home' }, //首页城市列表页 { path: '/home', component: home }, //当前选择城市页 { path: '/city/:cityid', component: city }, //所有商铺列表页 { path: '/msite', component: msite, meta: { keepAlive: true }, }, //特色商铺列表页 { path: '/food', component: food }, //搜索页 { path: '/search/:geohash', component: search }, //商铺详情页 { path: '/shop', component: shop, children: [{ path: 'foodDetail', //食品详情页 component: foodDetail, }, { path: 'shopDetail', //商铺详情页 component: shopDetail, children: [{ path: 'shopSafe', //商铺安全认证页 component: shopSafe, }, ] }] }, //确认订单页 { path: '/confirmOrder', component: confirmOrder, children: [{ path: 'remark', //订单备注 component: remark, }, { path: 'invoice', //发票抬头 component: invoice, }, { path: 'payment', //付款页面 component: payment, }, { path: 'userValidation', //用户验证 component: userValidation, }, { path: 'chooseAddress', //选择地址 component: chooseAddress, children: [{ path: 'addAddress', //添加地址 component: addAddress, children: [{ path: 'searchAddress', //搜索地址 component: searchAddress, }] }, ] }, ] }, //登录注册页 { path: '/login', component: login }, //个人信息页 { path: '/profile', component: profile, children: [{ path: 'info', //个人信息详情页 component: info, children: [{ path: 'setusername', component: setusername, },{ path: 'address', component: address, //编辑地址 children:[{ path:'add', component:add, children:[{ path:'addDetail', component:addDetail }] }] }] }, { path: 'service', //服务中心 component: service, },] }, //修改密码页 { path: '/forget', component: forget }, //订单列表页 { path: '/order', component: order, children: [{ path: 'orderDetail', //订单详情页 component: orderDetail, }, ] }, //vip卡页 { path: '/vipcard', component: vipcard, children: [{ path: 'invoiceRecord', //开发票 component: invoiceRecord, }, { path: 'useCart', //购买会员卡 component: useCart, }, { path: 'vipDescription', //会员说明 component: vipDescription, },] }, //发现页 { path: '/find', component: find }, //下载页 { path: '/download', component: download }, //服务中心 { path: '/service', component: service, children: [{ path: 'questionDetail', //订单详情页 component: questionDetail, }, ] }, //余额 { path: 'balance', component: balance, children: [{ path: 'detail', //余额说明 component: balanceDetail, }, ] }, //我的优惠页 { path: 'benefit', component: benefit, children: [{ path: 'coupon', //代金券说明 component: coupon, }, { path: 'hbDescription', //红包说明 component: hbDescription, }, { path: 'hbHistory', //历史红包 component: hbHistory, }, { path: 'exchange', //兑换红包 component: exchange, }, { path: 'commend', //推荐有奖 component: commend, },] }, //我的积分页 { path: 'points', component: points, children: [{ path: 'detail', //积分说明 component: pointsDetail, }, ] }, ] }] ================================================ FILE: src/service/getData.js ================================================ import fetch from '../config/fetch' import {getStore} from '../config/mUtils' /** * 获取首页默认地址 */ export const cityGuess = () => fetch('/v1/cities', { type: 'guess' }); /** * 获取首页热门城市 */ export const hotcity = () => fetch('/v1/cities', { type: 'hot' }); /** * 获取首页所有城市 */ export const groupcity = () => fetch('/v1/cities', { type: 'group' }); /** * 获取当前所在城市 */ export const currentcity = number => fetch('/v1/cities/' + number); /** * 获取搜索地址 */ export const searchplace = (cityid, value) => fetch('/v1/pois', { type: 'search', city_id: cityid, keyword: value }); /** * 获取msite页面地址信息 */ export const msiteAddress = geohash => fetch('/v2/pois/' + geohash); /** * 获取msite页面食品分类列表 */ export const msiteFoodTypes = geohash => fetch('/v2/index_entry', { geohash, group_type: '1', 'flags[]': 'F' }); /** * 获取msite商铺列表 */ export const shopList = (latitude, longitude, offset, restaurant_category_id = '', restaurant_category_ids = '', order_by = '', delivery_mode = '', support_ids = []) => { let supportStr = ''; support_ids.forEach(item => { if (item.status) { supportStr += '&support_ids[]=' + item.id; } }); let data = { latitude, longitude, offset, limit: '20', 'extras[]': 'activities', keyword: '', restaurant_category_id, 'restaurant_category_ids[]': restaurant_category_ids, order_by, 'delivery_mode[]': delivery_mode + supportStr }; return fetch('/shopping/restaurants', data); }; /** * 获取search页面搜索结果 */ export const searchRestaurant = (geohash, keyword) => fetch('/v4/restaurants', { 'extras[]': 'restaurant_activity', geohash, keyword, type: 'search' }); /** * 获取food页面的 category 种类列表 */ export const foodCategory = (latitude, longitude) => fetch('/shopping/v2/restaurant/category', { latitude, longitude }); /** * 获取food页面的配送方式 */ export const foodDelivery = (latitude, longitude) => fetch('/shopping/v1/restaurants/delivery_modes', { latitude, longitude, kw: '' }); /** * 获取food页面的商家属性活动列表 */ export const foodActivity = (latitude, longitude) => fetch('/shopping/v1/restaurants/activity_attributes', { latitude, longitude, kw: '' }); /** * 获取shop页面商铺详情 */ export const shopDetails = (shopid, latitude, longitude) => fetch('/shopping/restaurant/' + shopid, { latitude, longitude: longitude + '&extras[]=activities&extras[]=album&extras[]=license&extras[]=identification&extras[]=statistics' }); /** * 获取shop页面菜单列表 */ export const foodMenu = restaurant_id => fetch('/shopping/v2/menu', { restaurant_id }); /** * 获取商铺评价列表 */ export const getRatingList = (shopid, offset, tag_name = '') => fetch('/ugc/v2/restaurants/' + shopid + '/ratings', { has_content: true, offset, limit: 10, tag_name }); /** * 获取商铺评价分数 */ export const ratingScores = shopid => fetch('/ugc/v2/restaurants/' + shopid + '/ratings/scores'); /** * 获取商铺评价分类 */ export const ratingTags = shopid => fetch('/ugc/v2/restaurants/' + shopid + '/ratings/tags'); /** * 获取短信验证码 */ export const mobileCode = phone => fetch('/v4/mobile/verify_code/send', { mobile: phone, scene: 'login', type: 'sms' }, 'POST'); /** * 获取图片验证码 */ export const getcaptchas = () => fetch('/v1/captchas', {},'POST'); /** * 检测帐号是否存在 */ export const checkExsis = (checkNumber, type) => fetch('/v1/users/exists', { [type]: checkNumber, type }); /** * 发送帐号 */ export const sendMobile = (sendData, captcha_code, type, password) => fetch('/v1/mobile/verify_code/send', { action: "send", captcha_code, [type]: sendData, type: "sms", way: type, password, }, 'POST'); /** * 确认订单 */ export const checkout = (geohash, entities, shopid) => fetch('/v1/carts/checkout', { come_from: "web", geohash, entities, restaurant_id: shopid, }, 'POST'); /** * 获取快速备注列表 */ export const getRemark = (id, sig) => fetch('/v1/carts/' + id + '/remarks', { sig }); /** * 获取地址列表 */ export const getAddress = (id, sig) => fetch('/v1/carts/' + id + '/addresses', { sig }); /** * 搜索地址 */ export const searchNearby = keyword => fetch('/v1/pois', { type: 'nearby', keyword }); /** * 添加地址 */ export const postAddAddress = (userId, address, address_detail, geohash, name, phone, phone_bk, poi_type, sex, tag, tag_type) => fetch('/v1/users/' + userId + '/addresses', { address, address_detail, geohash, name, phone, phone_bk, poi_type, sex, tag, tag_type, }, 'POST'); /** * 下订单 */ export const placeOrders = (user_id, cart_id, address_id, description, entities, geohash, sig) => fetch('/v1/users/' + user_id + '/carts/' + cart_id + '/orders', { address_id, come_from: "mobile_web", deliver_time: "", description, entities, geohash, paymethod_id: 1, sig, }, 'POST'); /** * 重新发送订单验证码 */ export const rePostVerify = (cart_id, sig, type) => fetch('/v1/carts/' + cart_id + '/verify_code', { sig, type, }, 'POST'); /** * 下订单 */ export const validateOrders = ({ user_id, cart_id, address_id, description, entities, geohash, sig, validation_code, validation_token }) => fetch('/v1/users/' + user_id + '/carts/' + cart_id + '/orders', { address_id, come_from: "mobile_web", deliver_time: "", description, entities, geohash, paymethod_id: 1, sig, validation_code, validation_token, }, 'POST'); /** * 重新发送订单验证码 */ export const payRequest = (merchantOrderNo, userId) => fetch('/payapi/payment/queryOrder', { merchantId: 5, merchantOrderNo, source: 'MOBILE_WAP', userId, version: '1.0.0', }); /** * 获取服务中心信息 */ export const getService = () => fetch('/v3/profile/explain'); /** *兑换会员卡 */ export const vipCart = (id, number, password) => fetch('/member/v1/users/' + id + '/delivery_card/physical_card/bind',{ number, password }, 'POST') /** * 获取红包 */ export const getHongbaoNum = id => fetch('/promotion/v2/users/' + id + '/hongbaos?limit=20&offset=0'); /** * 获取过期红包 */ export const getExpired = id => fetch('/promotion/v2/users/' + id + '/expired_hongbaos?limit=20&offset=0'); /** * 兑换红包 */ export const exChangeHongbao = (id, exchange_code, captcha_code) => fetch('/v1/users/' + id + '/hongbao/exchange',{ exchange_code, captcha_code, }, 'POST'); /** * 获取用户信息 */ export const getUser = () => fetch('/v1/user', {user_id: getStore('user_id')}); /** * 手机号登录 */ var sendLogin = (code, mobile, validate_token) => fetch('/v1/login/app_mobile', { code, mobile, validate_token }, 'POST'); /** * 获取订单列表 */ export const getOrderList = (user_id, offset) => fetch('/bos/v2/users/' + user_id + '/orders', { limit: 10, offset, }); /** * 获取订单详情 */ export const getOrderDetail = (user_id, orderid) => fetch('/bos/v1/users/' + user_id + '/orders/' + orderid + '/snapshot'); /** *个人中心里编辑地址 */ export const getAddressList = (user_id) => fetch('/v1/users/'+user_id+'/addresses') /** *个人中心里搜索地址 */ export const getSearchAddress = (keyword) => fetch('v1/pois',{ keyword:keyword, type:'nearby' }) /** * 删除地址 */ export const deleteAddress = (userid, addressid) => fetch( '/v1/users/' + userid + '/addresses/' + addressid, {}, 'DELETE') /** * 账号密码登录 */ export const accountLogin = (username, password, captcha_code) => fetch('/v2/login', {username, password, captcha_code}, 'POST'); /** * 退出登录 */ export const signout = () => fetch('/v2/signout'); /** * 改密码 */ export const changePassword = (username, oldpassWord, newpassword, confirmpassword, captcha_code) => fetch('/v2/changepassword', {username, oldpassWord, newpassword, confirmpassword, captcha_code}, 'POST'); ================================================ FILE: src/store/action.js ================================================ import { getUser, getAddressList } from '../service/getData' import { GET_USERINFO, SAVE_ADDRESS } from './mutation-types.js' export default { async getUserInfo({ commit, state }) { let res = await getUser(); commit(GET_USERINFO, res) }, async saveAddress({ commit, state }) { if(state.removeAddress.length > 0) return; let addres = await getAddressList(state.userInfo.user_id); commit(SAVE_ADDRESS, addres); }, } ================================================ FILE: src/store/getters.js ================================================ export default { } ================================================ FILE: src/store/index.js ================================================ import Vue from 'vue' import Vuex from 'vuex' import mutations from './mutations' import actions from './action' import getters from './getters' Vue.use(Vuex) const state = { latitude: '', // 当前位置纬度 longitude: '', // 当前位置经度 cartList: {}, // 加入购物车的商品列表 shopDetail: null, //商家详情信息 userInfo: null, //用户信息 shopid: null,//商铺id remarkText: null,//可选备注内容 inputText: '',//输入备注内容 invoice: false,//开发票 newAddress: [], //确认订单页新的地址 searchAddress: null,//搜索并选择的地址 geohash: '31.22299,121.36025',//地址geohash值 choosedAddress: null,//选择地址 addressIndex: null,//选择地址的索引值 needValidation: null,//确认订单时是否需要验证 cartId: null, //购物车id sig: null,//购物车sig orderParam: null,//订单的参数 orderMessage: null, //订单返回的信息 orderDetail: null, //订单详情 login: true,//是否登录 imgPath:null,//头像地址 removeAddress:[],//移除地址 addAddress:'', //新增地址 question: null,//问题详情 cartPrice: null, //会员卡价格 } export default new Vuex.Store({ state, getters, actions, mutations, }) ================================================ FILE: src/store/mutation-types.js ================================================ export const RECORD_ADDRESS = 'RECORD_ADDRESS' export const ADD_CART = 'ADD_CART' export const REDUCE_CART = 'REDUCE_CART' export const INIT_BUYCART = 'INIT_BUYCART' export const CLEAR_CART = 'CLEAR_CART' export const RECORD_SHOPDETAIL = 'RECORD_SHOPDETAIL' export const RECORD_USERINFO = 'RECORD_USERINFO' export const GET_USERINFO = 'GET_USERINFO' export const CONFIRM_REMARK = 'CONFIRM_REMARK' export const CONFIRM_INVOICE = 'CONFIRM_INVOICE' export const CHOOSE_SEARCH_ADDRESS = 'CHOOSE_SEARCH_ADDRESS' export const SAVE_GEOHASH = 'SAVE_GEOHASH' export const CONFIRM_ADDRESS = 'CONFIRM_ADDRESS' export const CHOOSE_ADDRESS = 'CHOOSE_ADDRESS' export const NEED_VALIDATION = 'NEED_VALIDATION' export const SAVE_CART_ID_SIG = 'SAVE_CART_ID_SIG' export const SAVE_ORDER_PARAM = 'SAVE_ORDER_PARAM' export const CHANGE_ORDER_PARAM = 'CHANGE_ORDER_PARAM' export const ORDER_SUCCESS = 'ORDER_SUCCESS' export const SAVE_SHOPID = 'SAVE_SHOPID' export const SAVE_ORDER = 'SAVE_ORDER' export const OUT_LOGIN = 'OUT_LOGIN' export const RETSET_NAME = 'RETSET_NAME' export const SAVE_AVANDER = 'SAVE_AVANDER' export const SAVE_ADDDETAIL = 'SAVE_ADDDETAIL' export const SAVE_ADDRESS = 'SAVE_ADDRESS' export const SAVE_QUESTION = 'SAVE_QUESTION' export const ADD_ADDRESS = 'ADD_ADDRESS' export const BUY_CART = 'BUY_CART' ================================================ FILE: src/store/mutations.js ================================================ import { RECORD_ADDRESS, ADD_CART, REDUCE_CART, INIT_BUYCART, CLEAR_CART, RECORD_SHOPDETAIL, RECORD_USERINFO, GET_USERINFO, CONFIRM_REMARK, CONFIRM_INVOICE, CHOOSE_SEARCH_ADDRESS, SAVE_GEOHASH, CONFIRM_ADDRESS, CHOOSE_ADDRESS, NEED_VALIDATION, SAVE_CART_ID_SIG, SAVE_ORDER_PARAM, CHANGE_ORDER_PARAM, ORDER_SUCCESS, SAVE_SHOPID, SAVE_ORDER, OUT_LOGIN, RETSET_NAME, SAVE_AVANDER, SAVE_ADDRESS, SAVE_ADDDETAIL, SAVE_QUESTION, ADD_ADDRESS, BUY_CART, } from './mutation-types.js' import {setStore, getStore} from '../config/mUtils' import {localapi, proapi} from 'src/config/env' export default { // 记录当前经度纬度 [RECORD_ADDRESS](state, { latitude, longitude }) { state.latitude = latitude; state.longitude = longitude; }, [RECORD_SHOPDETAIL](state, detail) { state.shopDetail = detail; }, // 加入购物车 [ADD_CART](state, { shopid, category_id, item_id, food_id, name, price, specs, packing_fee, sku_id, stock }) { let cart = state.cartList; let shop = cart[shopid] = (cart[shopid] || {}); let category = shop[category_id] = (shop[category_id] || {}); let item = category[item_id] = (category[item_id] || {}); if (item[food_id]) { item[food_id]['num']++; } else { item[food_id] = { "num" : 1, "id" : food_id, "name" : name, "price" : price, "specs" : specs, "packing_fee" : packing_fee, "sku_id" : sku_id, "stock" : stock }; } state.cartList = {...cart}; //存入localStorage setStore('buyCart', state.cartList); }, // 移出购物车 [REDUCE_CART](state, { shopid, category_id, item_id, food_id, name, price, specs, }) { let cart = state.cartList; let shop = (cart[shopid] || {}); let category = (shop[category_id] || {}); let item = (category[item_id] || {}); if (item && item[food_id]) { if (item[food_id]['num'] > 0) { item[food_id]['num']--; state.cartList = {...cart}; //存入localStorage setStore('buyCart', state.cartList); } else { //商品数量为0,则清空当前商品的信息 item[food_id] = null; } } }, //网页初始化时从本地缓存获取购物车数据 [INIT_BUYCART](state) { let initCart = getStore('buyCart'); if (initCart) { state.cartList = JSON.parse(initCart); } }, //清空当前商品的购物车信息 [CLEAR_CART](state, shopid) { state.cartList[shopid] = null; state.cartList = {...state.cartList}; setStore('buyCart', state.cartList); }, // 记录用户信息 [RECORD_USERINFO](state, info) { state.userInfo = info; state.login = true; setStore('user_id', info.user_id); }, //获取用户信息存入vuex [GET_USERINFO](state, info) { if (state.userInfo && (state.userInfo.username !== info.username)) { return; }; if (!state.login) { return } if (!info.message) { state.userInfo = {...info}; } else { state.userInfo = null; } }, //修改用户名 [RETSET_NAME](state,username) { state.userInfo = Object.assign({}, state.userInfo,{username}) }, //保存商铺id [SAVE_SHOPID](state, shopid) { state.shopid = shopid; }, //记录订单页面用户选择的备注, 传递给订单确认页面 [CONFIRM_REMARK](state, { remarkText, inputText }) { state.remarkText = remarkText; state.inputText = inputText; }, //是否开发票 [CONFIRM_INVOICE](state, invoice) { state.invoice = invoice; }, //选择搜索的地址 [CHOOSE_SEARCH_ADDRESS](state, place) { state.searchAddress = place; }, //保存geohash [SAVE_GEOHASH](state, geohash) { state.geohash = geohash; }, //确认订单页添加新的的地址 [CONFIRM_ADDRESS](state, newAddress) { state.newAddress.push(newAddress); }, //选择的地址 [CHOOSE_ADDRESS](state, { address, index }) { state.choosedAddress = address; state.addressIndex = index; }, //保存下单需要验证的返回值 [NEED_VALIDATION](state, needValidation) { state.needValidation = needValidation; }, //保存下单后购物id 和 sig [SAVE_CART_ID_SIG](state, { cart_id, sig }) { state.cart_id = cart_id; state.sig = sig; }, //保存下单参数,用户验证页面调用 [SAVE_ORDER_PARAM](state, orderParam) { state.orderParam = orderParam; }, //修改下单参数 [CHANGE_ORDER_PARAM](state, newParam) { state.orderParam = Object.assign({}, state.orderParam, newParam); }, //下单成功,保存订单返回信息 [ORDER_SUCCESS](state, order) { state.cartPrice = null; state.orderMessage = order; }, //进入订单详情页前保存该订单信息 [SAVE_ORDER](state, orderDetail) { state.orderDetail = orderDetail; }, //退出登录 [OUT_LOGIN](state) { state.userInfo = {}; state.login = false; }, //保存图片 [SAVE_AVANDER](state, imgPath) { state.imgPath = imgPath; }, //删除地址列表 [SAVE_ADDRESS](state, newAdress) { state.removeAddress = newAdress }, //添加地址name [SAVE_ADDDETAIL](state, addAddress){ state.addAddress=addAddress; }, //保存所选问题标题和详情 [SAVE_QUESTION](state, question) { state.question = {...question}; }, //增加地址 [ADD_ADDRESS](state, obj) { state.removeAddress = [obj, ...state.removeAddress]; }, //会员卡价格纪录 [BUY_CART](state, price) { state.cartPrice = price; }, } ================================================ FILE: src/style/common.scss ================================================ body, div, span, header, footer, nav, section, aside, article, ul, dl, dt, dd, li, a, p, h1, h2, h3, h4,h5, h6, i, b, textarea, button, input, select, figure, figcaption, { padding: 0; margin: 0; list-style: none; font-style: normal; text-decoration: none; border: none; color: #333; font-weight: normal; font-family: "Microsoft Yahei"; box-sizing: border-box; -webkit-tap-highlight-color:transparent; -webkit-font-smoothing: antialiased; &:hover{ outline: none; } } /*定义滚动条高宽及背景 高宽分别对应横竖滚动条的尺寸*/ ::-webkit-scrollbar { width: 0px; height: 0px; background-color: #F5F5F5; } /*定义滚动条轨道 内阴影+圆角*/ ::-webkit-scrollbar-track { -webkit-box-shadow: inset 0 0 1px rgba(0,0,0,0); border-radius: 10px; background-color: #F5F5F5; } /*定义滑块 内阴影+圆角*/ ::-webkit-scrollbar-thumb { border-radius: 10px; -webkit-box-shadow: inset 0 0 6px rgba(0,0,0,.3); background-color: #555; } input[type="button"], input[type="submit"], input[type="search"], input[type="reset"] { -webkit-appearance: none; } textarea { -webkit-appearance: none;} html,body{ height: 100%; width: 100%; background-color: #F5F5F5; } .clear:after{ content: ''; display: block; clear: both; } .clear{ zoom:1; } .back_img{ background-repeat: no-repeat; background-size: 100% 100%; } .margin{ margin: 0 auto; } .left{ float: left; } .right{ float: right; } .hide{ display: none; } .show{ display: block; } .ellipsis{ overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .paddingTop{ padding-top: 1.95rem; } @keyframes backOpacity{ 0% { opacity: 1 } 25% { opacity: .5 } 50% { opacity: 1 } 75% { opacity: .5 } 100% { opacity: 1 } } .animation_opactiy{ animation: backOpacity 2s ease-in-out infinite; } ================================================ FILE: src/style/mixin.scss ================================================ $blue: #3190e8; $bc: #e4e4e4; $fc:#fff; // 背景图片地址和大小 @mixin bis($url) { background-image: url($url); background-repeat: no-repeat; background-size: 100% 100%; } @mixin borderRadius($radius) { -webkit-border-radius: $radius; -moz-border-radius: $radius; -ms-border-radius: $radius; -o-border-radius: $radius; border-radius: $radius; } //定位全屏 @mixin allcover{ position:absolute; top:0; right:0; } //定位上下左右居中 @mixin center { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); } //定位上下居中 @mixin ct { position: absolute; top: 50%; transform: translateY(-50%); } //定位左右居中 @mixin cl { position: absolute; left: 50%; transform: translateX(-50%); } //宽高 @mixin wh($width, $height){ width: $width; height: $height; } //字体大小、行高、字体 @mixin font($size, $line-height, $family: 'Microsoft YaHei') { font: #{$size}/#{$line-height} $family; } //字体大小,颜色 @mixin sc($size, $color){ font-size: $size; color: $color; } //flex 布局和 子元素 对其方式 @mixin fj($type: space-between){ display: flex; justify-content: $type; }