Browse Source

商城前端项目

yingp 8 years ago
commit
ed2752356e
100 changed files with 3661 additions and 0 deletions
  1. 13 0
      .editorconfig
  2. 16 0
      .eslintrc.js
  3. 13 0
      .gitignore
  4. 22 0
      README.md
  5. 24 0
      app.html
  6. 8 0
      assets/README.md
  7. 46 0
      assets/font/iconfont.css
  8. BIN
      assets/font/iconfont.eot
  9. 60 0
      assets/font/iconfont.svg
  10. BIN
      assets/font/iconfont.ttf
  11. BIN
      assets/font/iconfont.woff
  12. 16 0
      assets/scss/app.scss
  13. 252 0
      assets/scss/common.scss
  14. 423 0
      assets/scss/mixins.scss
  15. 118 0
      assets/scss/reset.scss
  16. 64 0
      assets/scss/variables.scss
  17. 6 0
      components/README.md
  18. 26 0
      components/common/vue-empty/empty.vue
  19. 7 0
      components/common/vue-empty/index.js
  20. 7 0
      components/common/vue-loading/index.js
  21. 132 0
      components/common/vue-loading/loading.vue
  22. 160 0
      components/default/Footer.vue
  23. 186 0
      components/default/Header.vue
  24. 11 0
      components/default/LeaveMessage.vue
  25. 97 0
      components/default/RightBar.vue
  26. 5 0
      components/default/index.js
  27. 87 0
      components/home/Advert.vue
  28. 79 0
      components/home/Carousel.vue
  29. 181 0
      components/home/KindCategory.vue
  30. 75 0
      components/home/Nav.vue
  31. 132 0
      components/home/News.vue
  32. 66 0
      components/home/Partner.vue
  33. 186 0
      components/home/Search.vue
  34. 150 0
      components/home/floor/Floor.vue
  35. 83 0
      components/home/floor/FloorBar.vue
  36. 32 0
      components/home/floor/FloorList.vue
  37. 8 0
      components/home/index.js
  38. 64 0
      components/main/Header.vue
  39. 75 0
      components/main/Nav.vue
  40. 191 0
      components/main/Search.vue
  41. 65 0
      components/main/count/Box.vue
  42. 67 0
      components/main/count/Item.vue
  43. 4 0
      components/main/index.js
  44. 0 0
      components/news/index.js
  45. 0 0
      components/product/index.js
  46. 8 0
      layouts/README.md
  47. 23 0
      layouts/default.vue
  48. 38 0
      layouts/error.vue
  49. 28 0
      layouts/main.vue
  50. 9 0
      middleware/README.md
  51. 12 0
      middleware/check-auth.js
  52. 90 0
      nuxt.config.js
  53. 36 0
      package.json
  54. 7 0
      pages/README.md
  55. 40 0
      pages/index.vue
  56. 0 0
      pages/news/_id.vue
  57. 0 0
      pages/news/index.vue
  58. 0 0
      pages/product/brand/_code.vue
  59. 0 0
      pages/product/brand/index.vue
  60. 8 0
      plugins/README.md
  61. 21 0
      plugins/axios.js
  62. 4 0
      plugins/swiper.js
  63. 5 0
      plugins/vue-empty.js
  64. 4 0
      plugins/vue-loading.js
  65. 60 0
      server.js
  66. 11 0
      static/README.md
  67. BIN
      static/favicon.ico
  68. BIN
      static/images/adverts/1.jpg
  69. BIN
      static/images/adverts/10.jpg
  70. BIN
      static/images/adverts/11.jpg
  71. BIN
      static/images/adverts/2.jpg
  72. BIN
      static/images/adverts/3.jpg
  73. BIN
      static/images/adverts/4.jpg
  74. BIN
      static/images/adverts/5.jpg
  75. BIN
      static/images/adverts/6.jpg
  76. BIN
      static/images/adverts/7.jpg
  77. BIN
      static/images/adverts/8.jpg
  78. BIN
      static/images/adverts/9.jpg
  79. BIN
      static/images/credit/1.jpg
  80. BIN
      static/images/credit/2.jpg
  81. BIN
      static/images/credit/3.jpg
  82. BIN
      static/images/credit/4.jpg
  83. BIN
      static/images/credit/5.jpg
  84. BIN
      static/images/credit/credit01.jpg
  85. BIN
      static/images/credit/credit02.jpg
  86. BIN
      static/images/credit/credit03.jpg
  87. BIN
      static/images/credit/credit04.jpg
  88. BIN
      static/images/credit/credit05.jpg
  89. BIN
      static/images/logo/uas.png
  90. BIN
      static/images/logo/uas_mall.png
  91. BIN
      static/images/logo_uas.png
  92. BIN
      static/images/news/news-bg.jpg
  93. BIN
      static/images/partners/1.jpg
  94. BIN
      static/images/partners/2.jpg
  95. BIN
      static/images/partners/3.jpg
  96. BIN
      static/images/partners/4.jpg
  97. BIN
      static/images/partners/5.jpg
  98. BIN
      static/images/partners/6.jpg
  99. BIN
      static/images/partners/7.jpg
  100. BIN
      static/images/qrcode/mall.png

+ 13 - 0
.editorconfig

@@ -0,0 +1,13 @@
+# editorconfig.org
+root = true
+
+[*]
+indent_size = 2
+indent_style = space
+end_of_line = lf
+charset = utf-8
+trim_trailing_whitespace = true
+insert_final_newline = true
+
+[*.md]
+trim_trailing_whitespace = false

+ 16 - 0
.eslintrc.js

@@ -0,0 +1,16 @@
+module.exports = {
+  root: true,
+  parser: 'babel-eslint',
+  env: {
+    browser: true,
+    node: true
+  },
+  extends: 'standard',
+  // required to lint *.vue files
+  plugins: [
+    'html'
+  ],
+  // add your custom rules here
+  rules: {},
+  globals: {}
+}

+ 13 - 0
.gitignore

@@ -0,0 +1,13 @@
+# dependencies
+node_modules
+
+# logs
+npm-debug.log
+
+# Nuxt build
+.nuxt
+
+# Nuxt generate
+dist
+
+.idea

+ 22 - 0
README.md

@@ -0,0 +1,22 @@
+# mall-web-ssr
+
+> mall web project
+
+## Build Setup
+
+``` bash
+# install dependencies
+$ npm install # Or yarn install
+
+# serve with hot reload at localhost:3000
+$ npm run dev
+
+# build for production and launch server
+$ npm run build
+$ npm start
+
+# generate static project
+$ npm run generate
+```
+
+For detailed explanation on how things work, checkout the [Nuxt.js docs](https://github.com/nuxt/nuxt.js).

+ 24 - 0
app.html

@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<html {{ HTML_ATTRS }}>
+<head>
+  {{ HEAD }}
+  <script>
+    (function (w, d) {
+      if (/(MSIE)|(Trident)/.test(w.navigator.userAgent)) {
+        var head = d.getElementsByTagName('head')[0]
+        var appendScript = function (src) {
+          var node = d.createElement('script')
+          node.src = src
+          head.appendChild(node)
+        }
+        // ployfill for all ie
+        appendScript('https://cdn.bootcss.com/html5shiv/r29/html5.min.js')
+        appendScript('https://cdn.bootcss.com/js-polyfills/0.1.33/polyfill.min.js')
+      }
+    })(window, document)
+  </script>
+</head>
+<body {{ BODY_ATTRS }}>
+{{ APP }}
+</body>
+</html>

+ 8 - 0
assets/README.md

@@ -0,0 +1,8 @@
+# ASSETS
+
+This directory contains your un-compiled assets such as LESS, SASS, or JavaScript.
+
+More information about the usage of this directory in the documentation:
+https://nuxtjs.org/guide/assets#webpacked
+
+**This directory is not required, you can delete it if you don't want to use it.**

+ 46 - 0
assets/font/iconfont.css

@@ -0,0 +1,46 @@
+@font-face {font-family: "iconfont";
+  src: url('iconfont.eot?t=1499494715499'); /* IE9*/
+  src: url('iconfont.eot?t=1499494715499#iefix') format('embedded-opentype'), /* IE6-IE8 */
+  url('iconfont.woff?t=1499494715499') format('woff'), /* chrome, firefox */
+  url('iconfont.ttf?t=1499494715499') format('truetype'), /* chrome, firefox, opera, Safari, Android, iOS 4.2+*/
+  url('iconfont.svg?t=1499494715499#iconfont') format('svg'); /* iOS 4.1- */
+}
+
+.iconfont {
+  font-family:"iconfont" !important;
+  font-size:14px;
+  font-style:normal;
+  -webkit-font-smoothing: antialiased;
+  -moz-osx-font-smoothing: grayscale;
+}
+
+.icon-sm {
+  font-size: 12px;
+}
+
+.icon-lg {
+  font-size: 18px;
+}
+
+.icon-xlg {
+  font-size: 20px;
+}
+
+.iconfont.pull-right {
+  margin-left: .3em;
+}
+
+.icon-shopping-cart:before { content: "\e600"; }
+
+.icon-zuji:before { content: "\e604"; }
+
+.icon-kefu:before { content: "\e6f9"; }
+
+.icon-arrow-up:before { content: "\e64a"; }
+
+.icon-arrow-left:before { content: "\e601"; }
+
+.icon-liuyan:before { content: "\e6b6"; }
+
+.icon-arrow-right:before { content: "\e621"; }
+

BIN
assets/font/iconfont.eot


+ 60 - 0
assets/font/iconfont.svg

@@ -0,0 +1,60 @@
+<?xml version="1.0" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" >
+<svg xmlns="http://www.w3.org/2000/svg">
+<metadata>
+Created by FontForge 20120731 at Sat Jul  8 14:18:35 2017
+ By admin
+</metadata>
+<defs>
+<font id="iconfont" horiz-adv-x="1024" >
+  <font-face 
+    font-family="iconfont"
+    font-weight="500"
+    font-stretch="normal"
+    units-per-em="1024"
+    panose-1="2 0 6 3 0 0 0 0 0 0"
+    ascent="896"
+    descent="-128"
+    x-height="792"
+    bbox="8 -123.308 1018.54 896"
+    underline-thickness="0"
+    underline-position="0"
+    unicode-range="U+0078-E6F9"
+  />
+<missing-glyph 
+ />
+    <glyph glyph-name=".notdef" 
+ />
+    <glyph glyph-name=".notdef" 
+ />
+    <glyph glyph-name=".null" horiz-adv-x="0" 
+ />
+    <glyph glyph-name="nonmarkingreturn" horiz-adv-x="341" 
+ />
+    <glyph glyph-name="x" unicode="x" horiz-adv-x="1001" 
+d="M281 543q-27 -1 -53 -1h-83q-18 0 -36.5 -6t-32.5 -18.5t-23 -32t-9 -45.5v-76h912v41q0 16 -0.5 30t-0.5 18q0 13 -5 29t-17 29.5t-31.5 22.5t-49.5 9h-133v-97h-438v97zM955 310v-52q0 -23 0.5 -52t0.5 -58t-10.5 -47.5t-26 -30t-33 -16t-31.5 -4.5q-14 -1 -29.5 -0.5
+t-29.5 0.5h-32l-45 128h-439l-44 -128h-29h-34q-20 0 -45 1q-25 0 -41 9.5t-25.5 23t-13.5 29.5t-4 30v167h911zM163 247q-12 0 -21 -8.5t-9 -21.5t9 -21.5t21 -8.5q13 0 22 8.5t9 21.5t-9 21.5t-22 8.5zM316 123q-8 -26 -14 -48q-5 -19 -10.5 -37t-7.5 -25t-3 -15t1 -14.5
+t9.5 -10.5t21.5 -4h37h67h81h80h64h36q23 0 34 12t2 38q-5 13 -9.5 30.5t-9.5 34.5q-5 19 -11 39h-368zM336 498v228q0 11 2.5 23t10 21.5t20.5 15.5t34 6h188q31 0 51.5 -14.5t20.5 -52.5v-227h-327z" />
+    <glyph glyph-name="shopping-cart" unicode="&#xe600;" 
+d="M347 113q20 0 37 -7.5t30.5 -20t20.5 -30t7 -37t-7 -37t-20.5 -30t-30.5 -20.5t-37 -8t-37 8t-30.5 20.5t-20.5 30t-7 37t7 37t20.5 30t30.5 20t37 7.5zM773.5 111q19.5 0 37 -7.5t30.5 -20t20.5 -30t7.5 -37t-7.5 -37t-20.5 -30t-30.5 -20.5t-37 -8t-37 8t-30.5 20.5
+t-20.5 30t-7.5 37t7.5 37t20.5 30t30.5 20t37 7.5zM945 692q29 0 45 -7.5t22.5 -19t6 -24t-3.5 -22t-13 -38t-22 -63t-24 -68t-18 -53.5q-13 -41 -33.5 -56.5t-50.5 -15.5h-35h-66h-87h-96h-254l16 -93h516q49 0 49 -42q0 -20 -9.5 -35t-38.5 -15h-49h-95h-118h-120h-98h-57
+q-20 0 -34 9.5t-23 24t-15 32t-9 33.5q-1 6 -5.5 29.5t-11 58.5t-15 79t-16.5 88q-19 103 -44 230h-77q-15 0 -25 7.5t-17 18.5t-9.5 23t-2.5 23q0 20 14 33.5t37 13.5h23h20h26h35q20 0 32.5 -6.5t19.5 -15.5t10 -19t5 -18t4.5 -23t4.5 -30q3 -18 6 -39h700z" />
+    <glyph glyph-name="zuji" unicode="&#xe604;" 
+d="M839 701q-20 0 -34.5 14.5t-14.5 34.5t14.5 34.5t34.5 14.5t34.5 -14.5t14.5 -34.5t-14.5 -34.5t-34.5 -14.5zM714 772q-19 0 -33 13.5t-14 33t14 33t33 13.5t32.5 -13.5t13.5 -33t-13.5 -33t-32.5 -13.5zM580.5 793q-21.5 0 -36.5 15t-15 36.5t15 36.5t36.5 15t36.5 -15
+t15 -36.5t-15 -36.5t-36.5 -15zM443.5 779q-24.5 0 -41.5 17t-17 41.5t17 41.5t41.5 17t41.5 -17t17 -41.5t-17 -41.5t-41.5 -17zM241 667q-43 0 -74 31t-31 74t31 73.5t74 30.5t73.5 -30.5t30.5 -73.5t-30.5 -74t-73.5 -31zM592 715q10 0 20 -2q88 -14 140.5 -58t80.5 -114
+q22 -53 23 -115q3 -124 -10.5 -219.5t-42 -159t-68 -103t-91.5 -58.5q-59 -22 -114 8q-115 66 -75 209q3 14 4 26q2 27 2 47q0 48 -32 88q-50 62 -82 123q-36 68 -10 141q33 89 93 137.5t162 49.5z" />
+    <glyph glyph-name="kefu" unicode="&#xe6f9;" 
+d="M151 32q22 50 63 89q32 31 78 55q3 2 6 -1q46 -41 105 -62q40 -14 83 -17q73 -6 141 20q54 21 96 59q3 3 7 1q44 -23 76 -53q62 -57 83 -142q3 -13 6 -42q1 -10 -6.5 -18t-18.5 -8h-717q-11 0 -18.5 8t-6.5 19q6 51 23 92zM511 159q-122 5 -185 102q-27 42 -31 95
+q-6 77 40 140t122 81q112 27 198 -48q59 -51 69 -127q16 -119 -78 -196q-44 -35 -103 -44q-2 0 -32 -3v0zM253 486h7v-178q0 -7 -6 -12t-13 -4q-5 1 -10 3q-42 13 -61 51q-14 29 -7.5 61t30.5 53q2 2 8 6t8 6q3 2 4 5q41 107 142 164q52 30 112 38q111 14 205 -41
+q96 -56 135 -160q2 -5 6 -7q45 -27 47.5 -78.5t-39.5 -82.5q-12 -8 -40 -15q-8 -2 -14 3t-6 13v175h7q-31 69 -100.5 116t-157.5 47q-90 0 -158.5 -47.5t-98.5 -115.5v0zM253 486z" />
+    <glyph glyph-name="arrow-up" unicode="&#xe64a;" 
+d="M512 496l-292 -292q-9 -9 -22 -9t-22.5 9.5t-9.5 22.5t10 22l314 314q9 9 22 9t22 -9l315 -314q9 -9 9 -22t-9 -22.5t-22.5 -9.5t-22.5 9z" />
+    <glyph glyph-name="arrow-left" unicode="&#xe601;" 
+d="M643 722q8 0 13 -5.5t5 -13.5t-5 -13l-291 -291l311 -310q5 -5 5 -13t-5.5 -13.5t-13 -5.5t-13.5 6l-336 336l317 317q5 6 13 6z" />
+    <glyph glyph-name="liuyan" unicode="&#xe6b6;" 
+d="M131 765h584q28 0 47.5 -19.5t19.5 -47.5v-373q0 -27 -19.5 -47t-47.5 -20h-303l-170 -153v153h-111q-28 0 -47.5 20t-19.5 47v373q0 28 19.5 47.5t47.5 19.5zM831 292v326h62q28 0 47.5 -18.5t19.5 -46.5v-326q0 -27 -19 -43t-48 -16h-111v-153l-168 153h-129
+q-67 0 -67 45h327q29 0 57.5 28.5t28.5 50.5zM831 292z" />
+    <glyph glyph-name="arrow-right" unicode="&#xe621;" 
+d="M343 728l364 -343l-364 -345l-23 23l340 322l-340 320z" />
+  </font>
+</defs></svg>

BIN
assets/font/iconfont.ttf


BIN
assets/font/iconfont.woff


+ 16 - 0
assets/scss/app.scss

@@ -0,0 +1,16 @@
+@charset "UTF-8";
+
+// variables
+@import "variables.scss";
+
+// mixins
+@import "mixins.scss";
+
+// reset style
+@import "reset.scss";
+
+// Iconfont
+@import "../font/iconfont.css";
+
+// Common style
+@import "common.scss";

+ 252 - 0
assets/scss/common.scss

@@ -0,0 +1,252 @@
+// scroll
+::-webkit-scrollbar {
+  width: .5rem;
+  height: .5rem;
+  background: hsla(0, 0%, 100%, 0.6);
+}
+
+::-webkit-scrollbar-track {
+  border-radius: 0;
+}
+
+::-webkit-scrollbar-thumb {
+  border-radius: 0;
+  background-color: rgba(95,95,95,.4);
+  transition: background-color .15s;
+
+  &:hover {
+    background-color: rgba(95,95,95, .7);
+  }
+}
+// common style
+.clearfix {
+  &:before, &:after {
+    display: table;
+    content: " ";
+  }
+  &:after {
+    clear: both;
+  }
+}
+.list-unstyled {
+  padding-left: 0;
+  list-style: none;
+}
+
+.list-inline li {
+  display: inline-block;
+  padding-left: 5px;
+  padding-right: 5px;
+}
+
+.hide {
+  opacity: 0;
+  visibility: hidden;
+  pointer-events: none;
+}
+
+.pull-left {
+  float: left;
+}
+
+.pull-right {
+  float: right;
+}
+
+.container {
+  width: $container-width;
+  margin: 0 auto;
+}
+
+.dl-horizontal dt {
+  float: left;
+  width: 160px;
+  overflow: hidden;
+  clear: left;
+  text-align: right;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+}
+
+.dl-horizontal dd {
+  display: block;
+  margin-left: 180px;
+
+  &:before, &:after {
+    display: table;
+    content: " ";
+  }
+  &:after {
+    clear: both;
+  }
+}
+.dropdown {
+  position: relative;
+
+  .dropdown-toggle {
+    cursor: pointer;
+  }
+  .dropdown-menu {
+    position: absolute;
+    left: 0;
+    top: 100%;
+    min-width: 100%;
+    display: none;
+    background-color: $white;
+    z-index: 100;
+  }
+  &:hover {
+    .dropdown-menu {
+      display: block;
+    }
+  }
+}
+// button
+.btn {
+  display: inline-block;
+  height: 36px;
+  line-height: 1;
+  padding: 3px 12px;
+  margin: 0;
+  border: 1px solid transparent;
+  text-align: center;
+  white-space: nowrap;
+  vertical-align: middle;
+  -ms-touch-action: manipulation;
+  touch-action: manipulation;
+  cursor: pointer;
+  -webkit-user-select: none;
+  -moz-user-select: none;
+  -ms-user-select: none;
+
+  &.btn-default {
+    color: $text;
+    background-color: $white;
+    border-color: $dividers;
+  }
+  &.btn-primary {
+    color: $white;
+    background-color: $primary;
+    border-color: $primary;
+  }
+}
+// form
+.form-control {
+  -webkit-appearance: none;
+  -moz-appearance: none;
+  appearance: none;
+  background-color: $white;
+  background-image: none;
+  border: $border;
+  color: $text;
+  display: inline-block;
+  font-size: inherit;
+  height: 36px;
+  line-height: 1;
+  outline: none;
+  padding: 3px 10px;
+  width: 100%;
+
+  &.input-primary {
+    border-color: $primary;
+  }
+}
+
+.input-group {
+  position: relative;
+  display: inline-table;
+  width: 100%;
+  border-collapse: separate;
+
+  .form-control, .input-group-btn {
+    display: table-cell;
+  }
+
+  .input-group-addon, .input-group-btn {
+    position: relative;
+    width: 1px;
+    white-space: nowrap;
+    vertical-align: middle;
+  }
+
+  .form-control {
+    position: relative;
+    z-index: 2;
+    float: left;
+    width: 100%;
+  }
+
+  .input-group-btn {
+    .btn {
+      position: relative;
+    }
+  }
+}
+
+// vue animate
+.slide-down-enter-active, .slide-down-leave-active {
+  transition: all .4s cubic-bezier(0, 1.2, 1, 0.5);
+  opacity: .7;
+  transform: translate3d(0, 4em, 0);
+}
+.slide-down-enter, .slide-down-leave-active {
+  opacity: .3;
+  transform: translate3d(0, 4em, 0);
+}
+
+.slide-left-enter-active, .slide-left-leave-active {
+  transition: all .2s cubic-bezier(0, 1.2, 1, 0.5);
+  opacity: .5;
+  transform: translate3d(2em, 0, 0);
+}
+
+.slide-left-enter, .slide-left-leave-active {
+  opacity: .3;
+  transform: translate3d(2em, 0, 0);
+}
+
+.slide-right-enter-active, .slide-right-leave-active {
+  transition: all .4s cubic-bezier(0, 1.2, 1, 0.5);
+  opacity: .5;
+  transform: translate3d(5em, 0, 0);
+}
+.slide-right-enter, .slide-right-leave-active {
+  opacity: .3;
+  transform: translate3d(5em, 0, 0);
+}
+
+.fade-enter-active, .fade-leave-active {
+  transition: opacity .25s
+}
+.fade-enter, .fade-leave-active {
+  opacity: 0
+}
+.fade-move {
+  transition: transform .25s;
+}
+
+.page-enter-active, .page-leave-active {
+  transition: opacity .25s
+}
+.page-enter, .page-leave-active {
+  opacity: 0
+}
+
+.module-enter-active, .module-leave-active {
+  transition: opacity .25s
+}
+.module-enter, .module-leave-active {
+  opacity: 0
+}
+
+.aside-enter-active {
+  transition: opacity .25s cubic-bezier(1, -1.17, 1, -1.17);
+}
+.aside-leave-active {
+  transition: opacity 0s
+}
+.aside-enter, .aside-leave-active {
+  opacity: 0
+}
+
+

+ 423 - 0
assets/scss/mixins.scss

@@ -0,0 +1,423 @@
+/* -------------------------------------------------------------
+  Sass CSS3 Mixins! The Cross-Browser CSS3 Sass Library
+  By: Matthieu Aussaguel, http://www.mynameismatthieu.com, @matthieu_tweets
+
+  List of CSS3 Sass Mixins File to be @imported and @included as you need
+
+  The purpose of this library is to facilitate the use of CSS3 on different browsers avoiding HARD TO READ and NEVER
+  ENDING css files
+
+  note: All CSS3 Properties are being supported by Safari 5
+  more info: http://www.findmebyip.com/litmus/#css3-properties
+
+------------------------------------------------------------- */
+
+
+////
+/// @author Matthieu Aussaguel
+/// @group sass-css3-mixins
+////
+
+
+/// Adds a browser prefix to the property
+/// @param {*} $property Property
+/// @param {*} $value Value
+
+@mixin clamp($lines: 2) {
+  display: -webkit-box;
+  overflow: hidden;
+  -webkit-line-clamp: $lines;
+  -webkit-box-orient: vertical;
+}
+
+@mixin css3-prefix($property, $value) {
+  -webkit-#{$property}: #{$value};
+   -khtml-#{$property}: #{$value};
+     -moz-#{$property}: #{$value};
+      -ms-#{$property}: #{$value};
+       -o-#{$property}: #{$value};
+          #{$property}: #{$value};
+}
+
+
+/// Background Gradient
+/// @param {Color} $startColor [#3C3C3C] - Start Color
+/// @param {Color} $endColor [#999999] - End Color
+
+@mixin background-gradient($startColor: #3C3C3C, $endColor: #999999) {
+    background-color: $startColor;
+    background-image: -webkit-gradient(linear, left top, left bottom, from($startColor), to($endColor));
+    background-image: -webkit-linear-gradient(top, $startColor, $endColor);
+    background-image:    -moz-linear-gradient(top, $startColor, $endColor);
+    background-image:     -ms-linear-gradient(top, $startColor, $endColor);
+    background-image:      -o-linear-gradient(top, $startColor, $endColor);
+    background-image:         linear-gradient(top, $startColor, $endColor);
+    filter:            progid:DXImageTransform.Microsoft.gradient(startColorStr='#{$startColor}', endColorStr='#{$endColor}');
+}
+
+
+/// Background Horizontal
+/// @param {Color} $startColor [#3C3C3C] - Start Color
+/// @param {Color} $endColor [#999999] - End Color
+
+@mixin background-horizontal($startColor: #3C3C3C, $endColor: #999999) {
+    background-color: $startColor;
+    background-image: -webkit-gradient(linear, left top, right top, from($startColor), to($endColor));
+    background-image: -webkit-linear-gradient(left, $startColor, $endColor);
+    background-image:    -moz-linear-gradient(left, $startColor, $endColor);
+    background-image:     -ms-linear-gradient(left, $startColor, $endColor);
+    background-image:      -o-linear-gradient(left, $startColor, $endColor);
+    background-image:         linear-gradient(left, $startColor, $endColor);
+    filter:            progid:DXImageTransform.Microsoft.gradient(startColorStr='#{$startColor}', endColorStr='#{$endColor}', gradientType='1');
+}
+
+
+/// Background Radial
+/// @param {Color} $startColor [#3C3C3C] - Start Color
+/// @param {Percentage} $startPos [0%] - Start position
+/// @param {Color} $endColor [#999999] - End Color
+/// @param {Percentage} $endPos [100%] - End position
+
+@mixin background-radial($startColor: #FFFFFF, $startPos: 0%, $endColor: #000000, $endPos:100%) {
+    background: -moz-radial-gradient(center, ellipse cover, $startColor $startPos, $endColor $endPos);
+    background: -webkit-gradient(radial, center center, 0px, center center, 100%, color-stop($startPos,$startColor), color-stop($endPos,$endColor));
+    background: -webkit-radial-gradient(center, ellipse cover, $startColor $startPos,$endColor $endPos);
+    background: -o-radial-gradient(center, ellipse cover, $startColor $startPos,$endColor $endPos);
+    background: -ms-radial-gradient(center, ellipse cover, $startColor $startPos,$endColor $endPos);
+    background: radial-gradient(ellipse at center, $startColor $startPos,$endColor $endPos);
+}
+
+
+/// Background Size
+/// @param {Size} $width [100%] - Width
+/// @param {Size} $width [$width] - Height
+/// @require {mixin} css3-prefix
+
+@mixin background-size($width: 100%, $height: $width) {
+  @if type-of($width) == 'number' and $height != null {
+    @include css3-prefix('background-size', $width $height);
+  } @else {
+    @include css3-prefix('background-size', $width);
+  }
+}
+
+
+/// Background Color Opacity
+/// @param {Color} $color [100%] - Color
+/// @param {Double} $opacity [0.85] - Opacity
+
+@mixin background-opacity($color: #000, $opacity: 0.85) {
+  background: $color;
+  background: rgba($color, $opacity);
+}
+
+
+/// Border Radius
+/// @param {Size} $radius [5px] - Radius
+/// @require {mixin} css3-prefix
+
+@mixin border-radius($radius: 5px) {
+    @include css3-prefix('border-radius', $radius);
+}
+
+
+/// Border Radius Separate
+/// @param {Size} $topLeftRadius [5px] - Top Left
+/// @param {Size} $topRightRadius [5px] - Top Right
+/// @param {Size} $bottomLeftRadius [5px] - Bottom Left
+/// @param {Size} $bottomRightRadius [5px] - Bottom Right
+
+@mixin border-radius-separate($topLeftRadius: 5px, $topRightRadius: 5px, $bottomLeftRadius: 5px, $bottomRightRadius: 5px) {
+  -webkit-border-top-left-radius:     $topLeftRadius;
+  -webkit-border-top-right-radius:    $topRightRadius;
+  -webkit-border-bottom-right-radius: $bottomRightRadius;
+  -webkit-border-bottom-left-radius:  $bottomLeftRadius;
+
+  -moz-border-radius-topleft:     $topLeftRadius;
+  -moz-border-radius-topright:    $topRightRadius;
+  -moz-border-radius-bottomright: $bottomRightRadius;
+  -moz-border-radius-bottomleft:  $bottomLeftRadius;
+
+  border-top-left-radius:     $topLeftRadius;
+  border-top-right-radius:    $topRightRadius;
+  border-bottom-right-radius: $bottomRightRadius;
+  border-bottom-left-radius:  $bottomLeftRadius;
+}
+
+
+/// Box
+/// @param {*} $orient [horizontal] - Orientation
+/// @param {*} $pack [center] - Pack
+/// @param {*} $align [center] - Align
+/// @require {mixin} css3-prefix
+
+@mixin box($orient: horizontal, $pack: center, $align: center) {
+  display: -webkit-box;
+  display: -moz-box;
+  display: box;
+
+  @include css3-prefix('box-orient', $orient);
+  @include css3-prefix('box-pack', $pack);
+  @include css3-prefix('box-align', $align);
+}
+
+
+/// Box RGBA
+/// @param {Integer} $r [60] - Red
+/// @param {Integer} $g [3] - Green
+/// @param {Integer} $b [12] - Blue
+/// @param {Double} $opacity [0.23] - Opacity
+/// @param {Color} $color [#3C3C3C] - Color
+
+@mixin box-rgba($r: 60, $g: 3, $b: 12, $opacity: 0.23, $color: #3C3C3C) {
+  background-color: transparent;
+  background-color: rgba($r, $g, $b, $opacity);
+            filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#{$color}',endColorstr='#{$color}');
+            zoom:   1;
+}
+
+
+/// Box Shadow
+/// @param {Size} $x [2px] - X
+/// @param {Size} $y [2px] - Y
+/// @param {Size} $blur [5px] - Blur
+/// @param {Color} $color [rgba(0,0,0,.4)] - Color
+/// @param {Boolean} $inset - Inset
+
+@mixin box-shadow($x: 2px, $y: 2px, $blur: 5px, $color: rgba(0,0,0,.4), $inset: "") {
+  @if ($inset != "") {
+    @include css3-prefix('box-shadow', $inset $x $y $blur $color);
+  } @else {
+    @include css3-prefix('box-shadow', $x $y $blur $color);
+  }
+}
+
+
+/// Box Sizing
+/// @param {*} $type [border-box] - Type
+/// @require {mixin} css3-prefix
+
+@mixin box-sizing($type: border-box) {
+  @include css3-prefix('box-sizing', $type);
+}
+
+
+/// Columns
+/// @param {Integer} $count [3] - Count
+/// @param {Integer} $gap [10] - Gap
+/// @require {mixin} css3-prefix
+
+@mixin columns($count: 3, $gap: 10) {
+  @include css3-prefix('column-count', $count);
+  @include css3-prefix('column-gap', $gap);
+}
+
+
+/// Double Borders
+/// @param {Color} $colorOne [#3C3C3C] - Color One
+/// @param {Color} $colorTwo [#999999] - Color Two
+/// @param {Size} $radius [0] - Radius
+/// @require {mixin} css3-prefix
+/// @require {mixin} border-radius
+
+@mixin double-borders($colorOne: #3C3C3C, $colorTwo: #999999, $radius: 0) {
+  border: 1px solid $colorOne;
+
+  @include css3-prefix('box-shadow', 0 0 0 1px $colorTwo);
+
+  @include border-radius( $radius );
+}
+
+
+/// Flex
+/// @param {Integer} $value [1] - Value
+/// @require {mixin} css3-prefix
+
+@mixin flex($value: 1) {
+  @include css3-prefix('box-flex', $value);
+}
+
+
+/// Flip
+/// @param {Double} $scaleX [-1] - ScaleX
+/// @require {mixin} css3-prefix
+
+@mixin flip($scaleX: -1) {
+  @include css3-prefix('transform', scaleX($scaleX));
+  filter:            FlipH;
+  -ms-filter:        "FlipH";
+}
+
+
+/// Font Face
+/// @param {Font} $fontFamily [myFont] - Font Family
+/// @param {String} $eotFileSrc ['myFont.eot'] - Eot File Source
+/// @param {String} $woffFileSrc ['myFont.woff'] - Woff File Source
+/// @param {String} $ttfFileSrc ['myFont.ttf'] - Ttf File Source
+/// @param {String} $svgFileSrc ['myFont.svg'] - Svg File Source
+
+@mixin font-face($fontFamily: myFont, $eotFileSrc: 'myFont.eot', $woffFileSrc: 'myFont.woff', $ttfFileSrc: 'myFont.ttf', $svgFileSrc: 'myFont.svg', $svgFontID: '#myFont') {
+  font-family: $fontFamily;
+  src: url($eotFileSrc)  format('eot'),
+       url($woffFileSrc) format('woff'),
+       url($ttfFileSrc)  format('truetype'),
+       url($svgFileSrc + $svgFontID) format('svg');
+}
+
+
+/// Opacity
+/// @param {Double} $opacity [0.5] - Opacity
+/// @require {mixin} css3-prefix
+
+@mixin opacity($opacity: 0.5) {
+    $opacityMultiplied: ($opacity * 100);
+
+    filter:         alpha(opacity=$opacityMultiplied);
+    -ms-filter:     "progid:DXImageTransform.Microsoft.Alpha(Opacity=" + $opacityMultiplied + ")";
+    @include css3-prefix('opacity', $opacity);
+}
+
+
+/// Outline Radius
+/// @param {Size} $radius [5px] - Radius
+/// @require {mixin} css3-prefix
+
+@mixin outline-radius($radius: 5px) {
+  @include css3-prefix('outline-radius', $radius);
+}
+
+
+/// Resize
+/// @param {*} $directoin [both] - Direction
+/// @require {mixin} css3-prefix
+
+@mixin resize($direction: both) {
+  @include css3-prefix('resize', $direction);
+}
+
+
+/// Rotate
+///
+/// CSS Matrix Rotation Calculator http://www.boogdesign.com/examples/transforms/matrix-calculator.html
+/// @param {Double} $deg [0] - Degree
+/// @param {Double} $m11 [0] - M11
+/// @param {Double} $m12 [0] - M12
+/// @param {Double} $m21 [0] - M21
+/// @param {Double} $m22 [0] - M22
+/// @require {mixin} css3-prefix
+
+@mixin rotate($deg: 0, $m11: 0, $m12: 0, $m21: 0, $m22: 0) {
+  @include css3-prefix('transform', rotate($deg + deg));
+  filter: progid:DXImageTransform.Microsoft.Matrix(
+       M11=#{$m11}, M12=#{$m12}, M21=#{$m21}, M22=#{$m22}, sizingMethod='auto expand');
+    zoom: 1;
+}
+
+
+/// Text Shadow
+/// @param {Size} $x [2px] - X
+/// @param {Size} $y [2px] - Y
+/// @param {Size} $blur [2px] - Blur
+/// @param {Color} $color [rgba(0,0,0,.4)] - Color
+
+@mixin text-shadow($x: 2px, $y: 2px, $blur: 5px, $color: rgba(0,0,0,.4)) {
+    text-shadow: $x $y $blur $color;
+}
+
+
+/// Transform
+/// @param {List} $params - Params
+/// @require {mixin} css3-prefix
+
+@mixin transform($params) {
+  @include css3-prefix('transform', $params);
+}
+
+
+/// Transform-Origin
+/// @param {List} $params - Params
+/// @require {mixin} css3-prefix
+
+@mixin transform-origin($params) {
+  @include css3-prefix('transform-origin', $params);
+}
+
+
+// Transform-Style
+/// @param {List} $params - Params
+/// @require {mixin} css3-prefix
+
+@mixin transform-style($style: preserve-3d) {
+  @include css3-prefix('transform-style', $style);
+}
+
+/// Transition
+/// @param {List} $properties - Properties
+/// @require {mixin} css3-prefix
+
+@mixin transition($properties...) {
+
+  @if length($properties) >= 1 {
+    @include css3-prefix('transition', $properties);
+  }
+
+  @else {
+    @include css3-prefix('transition',  "all 0.2s ease-in-out 0s");
+  }
+}
+
+
+/// Triple Borders
+/// @param {Color} $colorOne [#3C3C3C] - Color One
+/// @param {Color} $colorTwo [#999999] - Color Two
+/// @param {Color} $colorThree [#000000] - Color Three
+/// @param {Size} $radius [0] - Radius
+/// @require {mixin} border-radius
+/// @require {mixin} css3-prefix
+
+@mixin triple-borders($colorOne: #3C3C3C, $colorTwo: #999999, $colorThree: #000000, $radius: 0) {
+    border: 1px solid $colorOne;
+
+    @include border-radius($radius);
+
+    @include css3-prefix('box-shadow', "0 0 0 1px #{$colorTwo}, 0 0 0 2px #{$colorThree}");
+}
+
+
+/// Keyframes
+/// @param {*} $animation-name - Animation name
+/// @content [Animation css]
+
+@mixin keyframes($animation-name) {
+  @-webkit-keyframes #{$animation-name} {
+    @content;
+  }
+  @-moz-keyframes #{$animation-name} {
+    @content;
+  }
+  @-ms-keyframes #{$animation-name} {
+    @content;
+  }
+  @-o-keyframes #{$animation-name} {
+    @content;
+  }
+  @keyframes #{$animation-name} {
+    @content;
+  }
+}
+
+
+/// Animation
+/// @param {*} $str - name duration timing-function delay iteration-count direction fill-mode play-state ([http://www.w3schools.com/cssref/css3_pr_animation.asp](http://www.w3schools.com/cssref/css3_pr_animation.asp))
+/// @require {mixin} css3-prefix
+
+@mixin animation($str) {
+  @include css3-prefix('animation', $str);
+}
+
+
+@mixin text-overflow() {
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+}

+ 118 - 0
assets/scss/reset.scss

@@ -0,0 +1,118 @@
+* {
+  -webkit-box-sizing: border-box;
+  -moz-box-sizing: border-box;
+  box-sizing: border-box;
+}
+
+body {
+  margin: 0;
+  padding: 0;
+  color: $text;
+  font-family: $font-family;
+  font-size: $font-size;
+  line-height: $line-height;
+  -webkit-font-smoothing: antialiased;
+}
+
+article,aside,dialog,footer,header,section,footer,nav,figure,menu {
+  display: block
+}
+
+h1,
+h2,
+h3,
+h4,
+h5,
+h6 {
+  color: inherit;
+  font-family: inherit;
+  line-height: inherit;
+}
+
+h1 {
+  font-size: $font-size-h1;
+  font-weight: 500;
+  margin: $lg-pad 0;
+}
+
+h2 {
+  font-size: $font-size-h2;
+  font-weight: 500;
+  margin: $lg-pad 0;
+}
+
+h3 {
+  font-size: $font-size-h3;
+  font-weight: 500;
+  margin: $md-pad 0;
+}
+
+h4 {
+  font-size: $font-size-h4;
+  font-weight: 600;
+  margin: $md-pad 0;
+}
+
+h5 {
+  font-size: $font-size-h5;
+  font-weight: 600;
+  margin: $md-pad 0;
+}
+
+h6 {
+  color: $secondary;
+  font-size: $font-size-h6;
+  font-weight: 600;
+  margin: $md-pad 0;
+}
+
+dl {
+  margin-bottom: $md-pad;
+}
+
+dd {
+  margin-left: $xlg-pad;
+}
+
+ul,
+ol {
+  margin: 0;
+  padding: 0;
+  vertical-align: baseline;
+  list-style: none;
+  -webkit-padding-start: 0;
+  -webkit-margin-before: 0;
+  -webkit-margin-after: 0;
+}
+
+main,
+header,
+footer,
+article,
+section,
+aside,
+details,
+summary {
+  margin: 0 auto;
+  width: 100%;
+}
+
+a {
+  cursor: pointer;
+  text-decoration: none;
+  color: $text;
+  &:hover,&:active {
+    text-decoration: none;
+    outline: 0;
+    color: $red;
+  }
+}
+
+img {
+  outline: 0;
+  border: none;
+}
+
+p {
+  margin: 0 0 10px;
+}

+ 64 - 0
assets/scss/variables.scss

@@ -0,0 +1,64 @@
+// container
+$container-width: 1190px;
+$body-bg: #fff;
+$grey-bg: #f7f7f7;
+$dark-bg: rgba(197,197,197,0.4);
+$module-bg: rgba(255,255,255,0.6);
+$module-hover-bg: $dark-bg;
+
+//Other
+$radius: 2px;
+
+//Basic stylings
+$br: 4px;
+$xs-pad: 4px;
+$sm-pad: 8px;
+$pad: 1em;
+$md-pad: 1.2em;
+$lg-pad: 1.5em;
+$xlg-pad: 3em;
+$trans: .3s;
+
+//Breakpoints
+$small-breakpoint: 400px;
+$large-breakpoint: $container-width;
+
+//Colors
+$primary: #5078CB;
+$accent: #1162a4;
+$red: #f44336;
+$yellow: #ffeb3b;
+$dark: #999;
+$grey: rgb(230, 230, 230);
+$grey-light: #efefef;
+$white: #fff;
+$black: #000;
+$black-light: #474443;
+
+//Text
+$text: $black-light;
+$secondary: rgba(0, 0, 0, .54);
+$disabled: rgba(0, 0, 0, .38);
+$dividers: #c9c9c9;
+
+//Links
+$link-color: $text;
+$link-hover-color: darken($link-color, 20%);
+
+//Font
+$font-family: "Microsoft YaHei",'微软雅黑',serif;
+
+//Typography
+$font-size-small: 12px;
+$font-size: 14px;
+$font-size-large: 1.2rem;
+$font-size-h1: 2rem;
+$font-size-h2: 1.75rem;
+$font-size-h3: 1.5rem;
+$font-size-h4: 1.2rem;
+$font-size-h5: 1rem;
+$font-size-h6: $font-size-small;
+
+$line-height: 1.2;
+
+$border: 1px solid $dividers;

+ 6 - 0
components/README.md

@@ -0,0 +1,6 @@
+# COMPONENTS
+
+The components directory contains your Vue.js Components.
+Nuxt.js doesn't supercharge these components.
+
+**This directory is not required, you can delete it if you don't want to use it.**

+ 26 - 0
components/common/vue-empty/empty.vue

@@ -0,0 +1,26 @@
+<template>
+  <div class="empty-box">
+    <slot>没有数据</slot>
+  </div>
+</template>
+
+<script>
+  export default {
+    name: 'vue-empty'
+  }
+</script>
+
+<style lang="scss">
+  @import '~assets/scss/mixins';
+  @import '~assets/scss/variables';
+  .empty-box {
+    position: relative;
+    display: flex;
+    width: 100%;
+    height: 100%;
+    min-height: 5em;
+    text-align: center;
+    justify-content: center;
+    align-items: center;
+  }
+</style>

+ 7 - 0
components/common/vue-empty/index.js

@@ -0,0 +1,7 @@
+import EmptyComponent from './empty.vue'
+
+export default {
+  install (Vue) {
+    Vue.component('empty-box', EmptyComponent)
+  }
+}

+ 7 - 0
components/common/vue-loading/index.js

@@ -0,0 +1,7 @@
+import LoadingComponent from './loading.vue'
+
+export default {
+  install (Vue) {
+    Vue.component('loading-box', LoadingComponent)
+  }
+}

+ 132 - 0
components/common/vue-loading/loading.vue

@@ -0,0 +1,132 @@
+<template>
+  <div class="spinner-box" v-show="loading">
+    <div class="spinner-inner" :style="{color: color}">
+      <div class="la-ball-beat">
+        <div :style="spinnerStyle"></div>
+        <div :style="spinnerStyle"></div>
+        <div :style="spinnerStyle"></div>
+      </div>
+      <slot></slot>
+    </div>
+  </div>
+</template>
+
+<script>
+  export default {
+    name: 'Loader',
+    props: {
+      loading: {
+        type: Boolean,
+        default: true
+      },
+      color: {
+        type: String,
+        default: 'rgba(197, 197, 197, 0.4)'
+      },
+      height: {
+        type: String,
+        default: '15px'
+      },
+      width: {
+        type: String,
+        default: '15px'
+      },
+      margin: {
+        type: String,
+        default: '5px'
+      }
+    },
+    data () {
+      return {
+        spinnerStyle: {
+          backgroundColor: this.color,
+          height: this.height,
+          width: this.width,
+          margin: this.margin
+        }
+      }
+    }
+  }
+</script>
+
+<style lang="scss" scoped>
+  @import '~assets/scss/mixins';
+  @import '~assets/scss/variables';
+  .spinner-box {
+    position: relative;
+    width: 100%;
+    min-height: 50px;
+    height: 100%;
+    > .spinner-inner {
+      width: 80px;
+      height: 30px;
+      position: absolute;
+      top: 50%;
+      left: 50%;
+      margin-left: -40px;
+      margin-top: -15px;
+      text-align: center;
+      > .la-ball-beat {
+        display: block;
+        position: relative;
+        box-sizing: border-box;
+        font-size: 0;
+        color: #fff;
+        width: 80px;
+        height: 30px;
+        > div {
+          position: relative;
+          box-sizing: border-box;
+          display: inline-block;
+          float: none;
+          border: none;
+          width: 15px;
+          height: 15px;
+          margin: 5px;
+          background-color: $module-hover-bg;
+          @include css3-prefix(animation, ball-beat 0.7s -0.15s infinite linear);
+          &:nth-child(2n-1) {
+            @include css3-prefix(animation-delay, -.5s);
+          }
+        }
+        &.la-sm {
+          width: 26px;
+          height: 8px;
+          > div {
+            width: 8px;
+            height: 8px;
+            margin: 3px;
+          }
+        }
+        &.la-2x {
+          width: 108px;
+          height: 36px;
+          > div {
+            width: 20px;
+            height: 20px;
+            margin: 8px;
+          }
+        }
+        &.la-3x {
+          width: 162px;
+          height: 54px;
+          > div {
+            width: 30px;
+            height: 30px;
+            margin: 12px
+          }
+        }
+      }
+    }
+  }
+  @include keyframes(ball-beat) {
+    50% {
+      opacity: .2;
+      @include css3-prefix(transform, scale(0.75));
+    }
+    100% {
+      opacity: 1;
+      @include css3-prefix(transform, scale(1));
+    }
+  }
+</style>

+ 160 - 0
components/default/Footer.vue

@@ -0,0 +1,160 @@
+<template>
+  <footer class="footer">
+    <div class="footer-container container">
+      <div class="footer-guide">
+        <div class="item">
+          <h5>用户指南</h5>
+          <ul class="list-unstyled">
+            <li><a href="/help#/issue/50" target="_blank">服务条款</a></li>
+            <li><a href="/help#/issue/16" target="_blank">买卖条例</a></li>
+            <li><a href="/help#/issue/51" target="_blank">代收代付协议</a></li>
+          </ul>
+        </div>
+        <div class="item">
+          <h5>关于我们</h5>
+          <ul class="list-unstyled">
+            <li><a href="/help#/issue/1" target="_blank">公司简介</a></li>
+            <li><a href="/help#/issue/28" target="_blank">公司地址</a></li>
+            <li><a href="/help#/issue/1" target="_blank">联系我们</a></li>
+          </ul>
+        </div>
+        <div class="item">
+          <h5>更多服务</h5>
+          <ul class="list-unstyled">
+            <li><a href="http://www.usoftchina.com/usoft/"  target="_blank">优软科技</a></li>
+            <li><a href="http://www.ubtob.com" target="_blank">优软云</a></li>
+          </ul>
+        </div>
+        <div class="item">
+          <h5>商城公众号</h5>
+          <img src="/images/qrcode/mall.png" />
+        </div>
+          <div class="item">
+          <h5>科技公众号</h5>
+          <img src="/images/qrcode/uas.png" />
+        </div>
+      </div>
+      <ul class="footer-link list-unstyled">
+        <span>友情链接:</span>
+        <li v-for="(link, index) in links" class="footer-link-item">
+          <span v-if="index!=0" class="separation">|</span>
+          <a :href="link.url" target="_blank" :title="link.title">{{ link.title }}</a>
+        </li>
+      </ul>
+      <ul class="footer-notice list-unstyled">
+        <li>客服电话:400-830-1818</li>
+        <li>公司地址:深圳市南山区英唐大厦6楼</li>
+        <li>©2016 深圳市优软科技有限公司 粤ICP备15112126号-3</li>
+      </ul>
+      <div class="footer-credit">
+        <a href="javascript:void(0)"><img src="/images/credit/1.jpg" /></a>
+        <a href="javascript:void(0)"><img src="/images/credit/2.jpg" /></a>
+        <a href="javascript:void(0)"><img src="/images/credit/3.jpg" /></a>
+        <a href="javascript:void(0)"><img src="/images/credit/4.jpg" /></a>
+        <a href="javascript:void(0)"><img src="/images/credit/5.jpg" /></a>
+      </div>
+    </div>
+  </footer>
+</template>
+<script>
+  export default {
+    name: 'footer',
+    data () {
+      return {
+        links: [{
+          url: 'http://www.worldshine.net',
+          title: '深圳华商龙'
+        }, {
+          url: 'http://www.yitoa.com',
+          title: '深圳市英唐智能科技'
+        }, {
+          url: 'http://www.usoftchina.com/usoft',
+          title: '深圳市优软科技'
+        }, {
+          url: 'http://www.fantem.com',
+          title: '丰唐物联技术(深圳)'
+        }, {
+          url: 'http://www.hiways.com',
+          title: '深圳市海威思科技'
+        }, {
+          url: 'http://www.huashangweitai.com',
+          title: '深圳市华商维泰显示科技'
+        }, {
+          url: 'http://www.ufct.com.cn',
+          title: '联合创泰科技'
+        }]
+      }
+    }
+  }
+</script>
+<style lang="scss" scoped>
+  @import '~assets/scss/variables';
+  .footer {
+    font-size: $font-size-small;
+    background-color: $grey-bg;
+
+    .footer-container {
+      position: relative;
+      text-align: center;
+      text-transform: uppercase;
+
+      a {
+        color: $text;
+        &:hover {
+          color: $red;
+        }
+      }
+
+      .footer-guide {
+        margin-bottom: $pad;
+
+        > .item {
+          display: inline-block;
+          width: 16%;
+          text-align: center;
+          vertical-align: top;
+
+          > h5 {
+            font-weight: bold;
+          }
+
+          > ul > li {
+            padding: $xs-pad;
+          }
+        }
+      }
+
+      .footer-link {
+        margin-bottom: $pad;
+        color: $dark;
+
+        .footer-link-item {
+          display: inline-block;
+          margin-left: $pad;
+
+          .separation {
+            margin-right: $pad;
+          }
+        }
+      }
+
+      .footer-notice {
+        border-top: $border;
+        padding: $lg-pad 0;
+
+        > li {
+          display: inline-block;
+          padding: 0 2em;
+        }
+      }
+
+      .footer-credit {
+        padding-bottom: $xlg-pad;
+
+        > a {
+          margin: 0 $sm-pad;
+        }
+      }
+    }
+  }
+</style>

+ 186 - 0
components/default/Header.vue

@@ -0,0 +1,186 @@
+<template>
+  <header class="header">
+    <nav class="navbar">
+      <div class="navbar-container container">
+        <div class="navbar-header">
+          <a href="http://www.ubtob.com" class="item navbar-link">
+            <img src="/images/logo/uas.png" class="navbar-logo">
+            <span class="navbar-slogan">进入优软云</span>
+          </a>
+        </div>
+        <div class="navbar-right">
+          <template v-if="user.logged">
+            <div class="item-wrap dropdown">
+              <div class="item dropdown-toggle">
+                欢迎您,{{ user.data.userName }}&nbsp;<a @click="logout()">[退出]</a>
+              </div>
+              <ul class="dropdown-menu">
+                <li class="menu-item-first">
+                  <span>{{ enterprise.enName }}</span>
+                  <a class="pull-right" @click="toggleEnterprises()" v-if="user.data.enterprises.length > 1">
+                    {{ showEnterprises ? '取消' : '切换' }}
+                  </a>
+                </li>
+                <li class="menu-item"
+                    v-for="en in user.data.enterprises"
+                    v-if="showEnterprises && en.enName!=enterprise.enName">
+                  <a @click="switchEnterprise(en)">{{ en.enName }}</a>
+                </li>
+              </ul>
+            </div>
+            <nuxt-link class="item" :to="'/'">商城首页</nuxt-link>
+            <nuxt-link class="item" to="/user">买家中心</nuxt-link>
+            <nuxt-link class="item" to="/vendor">卖家中心</nuxt-link>
+          </template>
+          <template v-else>
+            <a class="item" @click="onLoginClick()">登录</a>
+            <a class="item" href="http://account.ubtob.com/sso/register">注册</a>
+            <nuxt-link class="item" :to="'/'">商城首页</nuxt-link>
+          </template>
+          <nuxt-link class="item" to="/help">帮助中心</nuxt-link>
+        </div>
+      </div>
+    </nav>
+  </header>
+</template>
+<script>
+  export default {
+    name: 'header',
+    data () {
+      return {
+        showEnterprises: false
+      }
+    },
+    computed: {
+      user () {
+        return this.$store.state.option.user
+      },
+      enterprise () {
+        let ens = this.user.data.enterprises
+        if (ens && ens.length) {
+          return ens.find(item => item.current) || ens[0]
+        }
+        return {}
+      }
+    },
+    methods: {
+      logout () {
+        this.$store.dispatch('logout')
+      },
+      onLoginClick () {
+        this.$http.get('/login/page').then(response => {
+          // 跳转登录
+          window.location.href = response.data.content
+        })
+        // TODO 待Account Center改版
+      },
+      toggleEnterprises () {
+        this.showEnterprises = !this.showEnterprises
+      },
+      // 切换当前企业
+      switchEnterprise (en) {
+        this.toggleEnterprises()
+        this.$http.get(`authentication/${en.enUU}`).then(() => {
+          this.$store.dispatch('loadUserInfo')
+        })
+      }
+    }
+  }
+</script>
+<style lang="scss" scoped>
+  @import '~assets/scss/mixins';
+  @import '~assets/scss/variables';
+
+  $nav-height: 36px;
+
+  .header {
+    height: $nav-height;
+
+    .navbar {
+      width: 100%;
+      height: 100%;
+      font-size: $font-size-small;
+      background-color: $black-light;
+
+      .navbar-container {
+
+        .item-wrap {
+          display: inline-block;
+        }
+
+        .item {
+          color: $grey;
+          display: inline-block;
+          height: $nav-height;
+          line-height: $nav-height;
+        }
+
+        a {
+          color: $grey;
+        }
+
+        .navbar-header {
+          float: left;
+
+          .navbar-logo {
+            margin-bottom: -2px;
+          }
+
+          .navbar-slogan {
+            margin-left: $sm-pad;
+          }
+
+        }
+
+        .navbar-right {
+          float: right;
+
+          .item {
+            padding: 0 $pad;
+          }
+
+          .dropdown {
+
+            .dropdown-toggle {
+              line-height: $nav-height;
+
+              a:hover {
+                color: $red !important;
+              }
+            }
+
+            .dropdown-menu {
+              width: 220px;
+              margin-left: -1px;
+              border: $border;
+              border-top: none;
+              padding: 1em;
+
+              .menu-item-first {
+                margin-bottom: 10px;
+              }
+
+              .menu-item {
+                line-height: 30px;
+                a {
+                  color: $accent;
+                }
+              }
+            }
+
+            &:hover {
+              background-color: $white;
+
+              .dropdown-toggle {
+                color: $text;
+              }
+              a {
+                color: $text
+              }
+            }
+          }
+        }
+      }
+    }
+  }
+</style>

+ 11 - 0
components/default/LeaveMessage.vue

@@ -0,0 +1,11 @@
+<template>
+  <div class="leave-message-box"></div>
+</template>
+<script>
+  export default {
+    name: 'leave-message'
+  }
+</script>
+<style>
+
+</style>

+ 97 - 0
components/default/RightBar.vue

@@ -0,0 +1,97 @@
+<template>
+  <div class="right-bar">
+    <ul class="right-bar-center">
+      <li class="right-bar-item">
+        <nuxt-link to="/user/cart">
+          <i class="iconfont icon-shopping-cart icon-xlg"></i>
+        </nuxt-link>
+
+      </li>
+      <li class="right-bar-item">
+        <a @click="onLeaveMessageClick()">
+          <i class="iconfont icon-liuyan icon-xlg"></i>
+        </a>
+      </li>
+      <li class="right-bar-item contact-menu">
+        <a href="http://wpa.qq.com/msgrd?v=3&uin=3432892085&site=www.ubtoc.com&menu=yes" target="_blank">
+          <i class="iconfont icon-kefu icon-xlg"></i>
+        </a>
+      </li>
+    </ul>
+    <ul class="right-bar-bottom">
+      <li class="right-bar-item">
+        <nuxt-link to="/user/browsingHistory">
+          <i class="iconfont icon-zuji icon-xlg"></i>
+        </nuxt-link>
+      </li>
+      <li class="right-bar-item">
+        <a @click="toTop()">
+          <i class="iconfont icon-arrow-up icon-xlg"></i>
+        </a>
+      </li>
+    </ul>
+  </div>
+</template>
+<script>
+  import { scrollTo } from '~utils/scroll'
+
+  export default {
+    name: 'right-bar',
+    methods: {
+      toTop () {
+        scrollTo('body', 300)
+      },
+      toBottom () {
+        scrollTo(window.scrollY + window.innerHeight, 300)
+      },
+      // 打开留言板
+      onLeaveMessageClick () {
+
+      }
+    }
+  }
+</script>
+<style lang="scss" scoped>
+  .right-bar {
+    position: fixed;
+    z-index: 1000;
+    right: 0;
+    top: 0;
+    width: 36px;
+    height: 100%;
+
+    .right-bar-center {
+      position: absolute;
+      top: 50%;
+      transform: translateY(-50%);
+      list-style: none;
+      padding: 0;
+      width: 100%;
+    }
+
+    .right-bar-bottom {
+      position: absolute;
+      right: 0;
+      bottom: 20px;
+      width: 36px;
+    }
+
+    .right-bar-item {
+      a {
+        position: relative;
+        display: block;
+        padding: 5px 0;
+        width: 100%;
+        color: #fff;
+        background-color: #5078CB;
+        text-align: center;
+        -webkit-transition: background-color ease .5s;
+        -moz-transition: background-color ease 0.5s;
+        -ms-transition: background-color ease 0.5s;
+        -o-transition: background-color ease 0.5s;
+        transition: background-color ease 0.5s;
+        z-index: 20;
+      }
+    }
+  }
+</style>

+ 5 - 0
components/default/index.js

@@ -0,0 +1,5 @@
+import Header from './Header.vue'
+import Footer from './Footer.vue'
+import RightBar from './RightBar.vue'
+
+export { Header, Footer, RightBar }

+ 87 - 0
components/home/Advert.vue

@@ -0,0 +1,87 @@
+<template>
+  <div class="advert-slide">
+    <div class="swiper-container container" v-swiper:swiper="swiperOption">
+      <div class="swiper-wrapper">
+        <div class="swiper-slide" v-for="(advert, index) in adverts" :key="index">
+          <a :href="advert.url">
+            <img :src="advert.img" target="_blank">
+          </a>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+<script>
+  export default {
+    name: 'advert',
+    data () {
+      return {
+        swiperOption: {
+          autoplay: 2500,
+          slidesPerView: 7,
+          spaceBetween: 20
+        },
+        adverts: [{
+          url: 'http://www.usoftmall.com/store/worldshine#/home',
+          img: '/images/adverts/1.jpg'
+        }, {
+          url: 'http://www.usoftmall.com/store/adtracon#/home',
+          img: '/images/adverts/2.jpg'
+        }, {
+          url: 'http://www.usoftmall.com/store/compa#/home',
+          img: '/images/adverts/3.jpg'
+        }, {
+          url: 'http://www.usoftmall.com/store/corestaff#/home',
+          img: '/images/adverts/4.jpg'
+        }, {
+          url: 'http://www.usoftmall.com/store/chipled#/home',
+          img: '/images/adverts/5.jpg'
+        }, {
+          url: 'http://www.usoftmall.com/store/opd#/home',
+          img: '/images/adverts/6.jpg'
+        }, {
+          url: 'http://www.usoftmall.com/store/jrxy#/home',
+          img: '/images/adverts/7.jpg'
+        }, {
+          url: 'http://www.usoftmall.com/store/hbt#/home',
+          img: '/images/adverts/8.jpg'
+        }, {
+          url: 'http://www.usoftmall.com/store/yjycoin#/home',
+          img: '/images/adverts/9.jpg'
+        }, {
+          url: 'http://www.usoftmall.com/store/d3567635164a4811a5ba9e9adbcdcc87#/home',
+          img: '/images/adverts/10.jpg'
+        }, {
+          url: 'http://www.usoftmall.com/store/winsen#/home',
+          img: '/images/adverts/11.jpg'
+        }]
+      }
+    }
+  }
+</script>
+<style lang="scss" scoped>
+  @import '~assets/scss/variables';
+
+  .advert-slide {
+    text-align: center;
+    margin-bottom: $xlg-pad;
+
+    .swiper-container {
+      height: 100px;
+      background-color: $grey-bg;
+      overflow: hidden;
+
+      .swiper-wrapper {
+        padding: 5px 0;
+
+        .swiper-slide {
+
+          img {
+            width: 161px;
+            height: 90px;
+          }
+        }
+      }
+    }
+  }
+</style>

+ 79 - 0
components/home/Carousel.vue

@@ -0,0 +1,79 @@
+<template>
+  <div class="carousel" :style="{backgroundColor: activeColor}">
+    <div class="container">
+      <slot></slot>
+      <div class="carousel-container">
+        <div v-swiper:mySwiper="swiperOption">
+          <div class="swiper-wrapper">
+            <div class="swiper-slide" v-for="banner in banners.data">
+              <a :href="banner.hrefUrl" target="_blank">
+                <img :src="banner.pictureUrl"/>
+              </a>
+            </div>
+          </div>
+          <div class="swiper-pagination swiper-pagination-bullets"></div>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+<script>
+  export default {
+    name: 'carousel',
+    data () {
+      return {
+        activeSlide: 0,
+        swiperOption: {
+          autoplay: 6000,
+          pagination: '.swiper-pagination',
+          paginationClickable: true,
+          mousewheelControl: true,
+          effect: 'fade',
+          lazyLoading: true,
+          onTransitionStart: (swiper) => {
+            // 不要通过vue刷新dom,会导致pagination无法刷新
+            // this.activeSlide = swiper.activeIndex
+            document.querySelector('.carousel').style.backgroundColor =
+              this.banners.data[swiper.activeIndex].metadata['background-color']
+          }
+        }
+      }
+    },
+    computed: {
+      banners () {
+        return this.$store.state.carousel.banners
+      },
+      activeColor () {
+        return this.banners.data[this.activeSlide].metadata['background-color']
+      }
+    }
+  }
+</script>
+<style lang="scss" scoped>
+  @import '~assets/scss/variables';
+
+  $carousel_width: 990px;
+  $carousel_height: 477px;
+
+  .carousel {
+    transition: background-color .3s;
+    position: relative;
+    margin-bottom: $lg-pad;
+
+    .carousel-container {
+      width: $carousel_width;
+      height: $carousel_height;
+      margin-left: 200px;
+      overflow: hidden;
+
+      .swiper-wrapper {
+        .swiper-slide {
+          img {
+            display: block;
+            height: $carousel_height;
+          }
+        }
+      }
+    }
+  }
+</style>

+ 181 - 0
components/home/KindCategory.vue

@@ -0,0 +1,181 @@
+<template>
+  <div class="kind-category" @mouseleave="hideChildrenLayout()">
+    <ul class="kind-main list-unstyled">
+      <li v-for="kind in kindsToShow" class="kind-main-item" :class="{active: kind.id==activeKindId}"
+        @mouseenter="showChildrenLayout(kind)">
+        <nuxt-link :to="`/product/kind/${kind.id}`">
+          <span>{{ kind.nameCn }}</span>
+          <i class="iconfont icon-arrow-right icon-sm pull-right"></i>
+        </nuxt-link>
+      </li>
+      <li class="kind-main-item item-more">
+        <nuxt-link to="/product/kind">
+          <span>查看更多器件分类</span>
+        </nuxt-link>
+      </li>
+    </ul>
+    <!-- 子类目 -->
+    <transition name="fade" mode="out-in">
+      <div v-if="activeKindId" class="kind-children">
+        <div v-for="(kind, index) in kindsToShow" :key="index"
+             v-if="kind.id==activeKindId" class="kind-children-layout">
+          <empty-box v-if="!kind.fetching && kind.children && !kind.children.length"></empty-box>
+          <loading-box v-else-if="kind.fetching" color="rgba(80, 120, 203, 0.9)"></loading-box>
+          <div class="kind-children-item-wrap" v-else>
+            <dl class="kind-children-item dl-horizontal" v-for="c in kind.children">
+              <dt>
+                <nuxt-link :to="`/product/kind/${c.id}`">
+                  <span>{{ c.nameCn }}</span><i class="pull-right iconfont icon-arrow-right icon-sm"></i>
+                </nuxt-link>
+              </dt>
+              <dd>
+                <ul class="list-unstyled list-inline">
+                  <li v-for="h in c.children">
+                    <nuxt-link :to="`/product/kind/${h.id}`">
+                      <span>{{ h.nameCn }}</span>
+                    </nuxt-link>
+                  </li>
+                </ul>
+              </dd>
+            </dl>
+          </div>
+        </div>
+      </div>
+    </transition>
+  </div>
+</template>
+<script>
+  export default {
+    name: 'kind-category',
+    data () {
+      return {
+        activeKindId: 0
+      }
+    },
+    methods: {
+      showChildrenLayout (kind) {
+        if (!kind.leaf) {
+          this.activeKindId = kind.id
+          if (!kind.children) {
+            this.$emit('loadchild', kind.id)
+          }
+        }
+      },
+      hideChildrenLayout () {
+        this.activeKindId = null
+      }
+    },
+    computed: {
+      kinds () {
+        return this.$store.state.product.kinds
+      },
+      kindsToShow () {
+        // 只显示前13个根类目
+        return this.kinds.data.slice(0, 13)
+      }
+    }
+  }
+</script>
+<style lang="scss" scoped>
+  @import '~assets/scss/variables';
+
+  .kind-category {
+    position: absolute;
+    width: 200px;
+    height: 477px;
+
+    .kind-main {
+      margin: 0;
+      padding: 0;
+      z-index: 10;
+
+      .kind-main-item {
+        height: 34.1px;
+        line-height: 34.1px;
+        padding: 0 20px;
+        background: rgba(80, 120, 203, 0.9);
+        display: block;
+
+        > a {
+          color: #fff;
+
+          > .iconfont {
+            opacity: 0;
+          }
+        }
+
+        &.item-more {
+          background-color: #4c66bb;
+          font-weight: bold;
+          font-size: 12px;
+        }
+
+        &.active,&:hover {
+          background-color: #a0b2eb;
+
+          > a {
+            font-weight: bold;
+
+            > .iconfont {
+              opacity: 1;
+            }
+          }
+        }
+      }
+    }
+
+    .kind-children {
+      position: absolute;
+      top: 0;
+      left: 200px;
+      z-index: 9;
+      box-shadow: 1px 0 3px #666;
+
+      .kind-children-layout {
+        background-color: #fff;
+        width: 990px;
+        height: 477px;
+        font-size: 12px;
+        overflow-y: auto;
+
+        .kind-children-item-wrap {
+          height: 100%;
+          width: 100%;
+
+          .kind-children-item {
+            margin-bottom: $md-pad;
+
+            dt {
+              span {
+                width: 90%;
+                display: inline-block;
+                font-weight: bold;
+                overflow: hidden;
+                clear: left;
+                text-align: right;
+                text-overflow: ellipsis;
+                white-space: nowrap;
+              }
+            }
+
+            dd {
+              ul {
+                margin: 0;
+                padding: 0;
+
+                li {
+                  border-left: $border;
+                  margin-bottom: $pad;
+
+                  span {
+                    margin: 0 $sm-pad;
+                  }
+                }
+              }
+            }
+          }
+        }
+      }
+    }
+  }
+</style>

+ 75 - 0
components/home/Nav.vue

@@ -0,0 +1,75 @@
+<template>
+  <nav class="nav-list">
+    <div class="container">
+      <nuxt-link to="/product" class="item item-first">
+        <div>器件选型</div>
+      </nuxt-link>
+      <nuxt-link :to="'/'" class="item" exact>
+        <span>首&nbsp;&nbsp;页</span>
+      </nuxt-link>
+      <nuxt-link to="/product" class="item">
+        <span>品牌中心</span>
+      </nuxt-link>
+      <nuxt-link to="/providers" class="item">
+        <span>原厂专区</span>
+      </nuxt-link>
+      <nuxt-link to="/providers" class="item">
+        <span>代理经销</span>
+      </nuxt-link>
+      <nuxt-link to="/product" class="item">
+        <span>热卖推荐</span>
+      </nuxt-link>
+      <nuxt-link to="/product" class="item">
+        <span>库存寄售</span>
+      </nuxt-link>
+      <nuxt-link to="/news" class="item">
+        <span>优软快讯</span>
+      </nuxt-link>
+    </div>
+  </nav>
+</template>
+<script>
+  export default {
+    name: 'nav'
+  }
+</script>
+<style lang="scss" scoped>
+  @import '~assets/scss/variables';
+  $nav-height: 40px;
+
+  .nav-list {
+    background-color: rgb(244, 248, 255);
+    height: $nav-height;
+
+    .item {
+      display: inline-block;
+      height: $nav-height;
+      line-height: $nav-height;
+      margin: 0 15px;
+      color: $black-light;
+
+      > span {
+        padding: 5px 2px;
+      }
+
+      &.nuxt-link-active, &:hover {
+        > span {
+          color: #5078cb;
+          border-bottom: #5078cb 3px solid;
+          font-weight: bold;
+        }
+      }
+
+      &.item-first {
+        width: 200px;
+        margin: 0;
+        background-color: rgb(33, 71, 151);
+        font-size: 14px;
+        font-weight: bold;
+        text-align: center;
+        color: #fff;
+        cursor: pointer;
+      }
+    }
+  }
+</style>

+ 132 - 0
components/home/News.vue

@@ -0,0 +1,132 @@
+<template>
+  <div class="news">
+    <div class="news-container container">
+      <h2 class="title">
+        <nuxt-link to="/news">优软快讯</nuxt-link>
+      </h2>
+      <ul class="title-list">
+        <li v-for="n in news5" class="item">
+          <nuxt-link :to="`/news/${n.id}`">
+            {{ n.title }}
+          </nuxt-link>
+        </li>
+        <li class="item item-last"><nuxt-link to="/news">查看更多&gt;&gt;</nuxt-link></li>
+      </ul>
+      <ul class="thumbnail-list">
+        <li v-for="n in news3" class="item">
+          <nuxt-link :to="`/news/${n.id}`">
+            <img :src="n.thumbnail">
+            <span>{{ n.title }}</span>
+          </nuxt-link>
+        </li>
+      </ul>
+      <div class="clearfix"></div>
+    </div>
+  </div>
+</template>
+<script>
+  export default {
+    name: 'news',
+    computed: {
+      news () {
+        return this.$store.state.news.snapshot
+      },
+      news3 () {
+        return this.news.data.slice(0, 3)
+      },
+      news5 () {
+        return this.news.data.slice(0, 5)
+      }
+    }
+  }
+</script>
+<style lang="scss" scoped>
+  .news {
+    height: 280px;
+    background: url("/images/news/news-bg.jpg");
+
+    .news-container {
+
+      .title {
+        padding: 20px 0 15px 0;
+        margin: 0;
+      }
+
+      a {
+        color: #fff;
+
+        &:hover {
+          color: #feb900;
+        }
+      }
+
+      .title-list {
+        width: 450px;
+        float: left;
+
+        .item {
+          line-height: 25px;
+
+          a {
+            display: inline-block;
+            width: 100%;
+            text-overflow: ellipsis;
+            overflow: hidden;
+            white-space: nowrap;
+          }
+
+          &:last-child a, .item-last a{
+            color: #feb900;
+          }
+        }
+      }
+
+      .thumbnail-list {
+        float: right;
+
+        .item {
+          display: inline-block;
+          position: relative;
+          margin-left: 15px;
+          background: #fff;
+          overflow: hidden;
+          width: 220px;
+          height: 190px;
+
+          a {
+
+            img {
+              width: 220px;
+              height: 152px;
+            }
+
+            span {
+              position: absolute;
+              left: 0;
+              right: 0;
+              bottom: 0;
+              display: block;
+              line-height: 38px;
+              padding: 0 10px;
+              color: #666;
+              text-overflow: ellipsis;
+              overflow: hidden;
+              white-space: nowrap;
+            }
+
+            &:hover {
+              img {
+                transform: scale(1.15);
+              }
+              span {
+                line-height: 28px;
+                color: #feb900;
+              }
+            }
+          }
+        }
+      }
+    }
+
+  }
+</style>

+ 66 - 0
components/home/Partner.vue

@@ -0,0 +1,66 @@
+<template>
+  <div class="partner">
+    <div class="partner-container container">
+      <h3>合作案例</h3>
+      <ul class="list-unstyled">
+        <li v-for="p in partners">
+          <a :href="p.href" target="_blank">
+            <img :src="p.img"/>
+          </a>
+        </li>
+      </ul>
+    </div>
+  </div>
+</template>
+<script>
+  export default {
+    name: 'partner',
+    data () {
+      return {
+        partners: [{
+          href: 'http://www.fantem.com',
+          img: '/images/partners/1.jpg'
+        }, {
+          href: 'http://www.tidal.cn/docc/default.html',
+          img: '/images/partners/2.jpg'
+        }, {
+          href: 'http://www.huansujt.com',
+          img: '/images/partners/3.jpg'
+        }, {
+          href: 'http://www.madigi.net',
+          img: '/images/partners/4.jpg'
+        }, {
+          href: 'http://www.51cube.com/ch/Default.asp',
+          img: '/images/partners/5.jpg'
+        }, {
+          href: 'http://www.szsoling.com/home',
+          img: '/images/partners/6.jpg'
+        }, {
+          href: 'http://www.chiptrip.com.cn/china/cn/index.asp',
+          img: '/images/partners/7.jpg'
+        }]
+      }
+    }
+  }
+</script>
+<style lang="scss" scoped>
+  @import '~assets/scss/variables';
+
+  .partner {
+    text-align: center;
+    margin-bottom: $xlg-pad;
+
+    ul {
+      width: 1204px;
+      overflow: hidden;
+
+      li {
+        float: left;
+        width: 155px;
+        height: 61px;
+        margin-right: 15px;
+        border: $border;
+      }
+    }
+  }
+</style>

+ 186 - 0
components/home/Search.vue

@@ -0,0 +1,186 @@
+<template>
+  <div class="search-box">
+    <div class="input-group">
+      <input v-model="keyword" type="text" class="search-input form-control input-primary"
+             placeholder="型号/类目/品牌"
+             @focus.stop.prevent="onFocus()"
+             @blur.stop.prevent="onBlur()"
+             @keyup.40="onSelectChange(1)"
+             @keyup.38="onSelectChange(-1)"
+             @keyup.13="onSearch()"/>
+      <span class="input-group-btn" @click="onSearch()">
+        <button class="btn btn-primary search-btn" type="button">搜&nbsp;索</button>
+      </span>
+    </div>
+    <ul class="association" v-show="showAssociate">
+      <li v-for="(k, index) in similarKeywords.data" class="item"
+          :class="{'active': index==associate.activeIndex}"
+          @click.stop.prevent="onAssociateClick(k)">{{ k }}
+      </li>
+    </ul>
+    <div class="search-hot">
+      <ul class="list-untyled">
+        <li class="item item-first">热门搜索</li>
+        <li class="item" v-for="w in hotwords">
+          <nuxt-link :to="w.url" target="_blank">{{ w.name }}</nuxt-link>
+        </li>
+      </ul>
+    </div>
+  </div>
+</template>
+<script>
+  export default {
+    name: 'search-box',
+    data () {
+      return {
+        keyword: '',
+        associate: {
+          show: false,
+          activeIndex: null
+        }
+      }
+    },
+    computed: {
+      similarKeywords () {
+        return this.$store.state.search.keywords
+      },
+      showAssociate () {
+        return this.associate.show && this.similarKeywords.data && this.similarKeywords.data.length
+      }
+    },
+    props: {
+      hotwords: {
+        type: Array,
+        default () {
+          return [{
+            name: 'SCT2080KEC',
+            url: 'product/component/1100400300009990'
+          }, {
+            name: '电池组',
+            url: 'product/kinds/346'
+          }, {
+            name: 'Vishay',
+            url: 'product/brand/30327265e42a871be050007f01003d96'
+          }, {
+            name: 'Panasonic Battery',
+            url: 'product/brand/30327265e4e7871be050007f01003d96'
+          }]
+        }
+      }
+    },
+    watch: {
+      'keyword': {
+        handler (val, oldVal) {
+          let keywords = this.similarKeywords.data
+          if (!keywords || !keywords.length || this.associate.activeIndex === null || val !== keywords[this.associate.activeIndex]) {
+            this.onChange()
+          }
+        }
+      }
+    },
+    methods: {
+      onFocus () {
+        this.associate.show = true
+      },
+      onBlur () {
+        this.associate.show = false
+      },
+      onSelectChange (count) {
+        let keywords = this.similarKeywords.data
+        if (keywords && keywords.length) {
+          let index = this.associate.activeIndex
+          if (index === null) {
+            index = -1
+          }
+          index += count
+          if (index >= keywords.length) {
+            index = 0
+          } else if (index < 0) {
+            index = keywords.length - 1
+          }
+          this.associate.activeIndex = index
+          this.keyword = keywords[index]
+        }
+      },
+      onChange () {
+        this.associate.activeIndex = null
+        if (!this.keyword) {
+          this.associate.show = false
+          this.$store.dispatch('resetSearchKeywords')
+        } else {
+          this.searchKeywords()
+        }
+      },
+      searchKeywords () {
+        this.associate.show = true
+        this.$store.dispatch('searchKeywords', { keyword: this.keyword })
+      },
+      onSearch () {
+        if (this.keyword) {
+          this.associate.show = false
+          this.$store.dispatch('resetSearchKeywords')
+          this.$router.push({path: '/search?w=' + encodeURIComponent(this.keyword)})
+        }
+      },
+      onAssociateClick (word) {
+        this.keyword = word
+        this.onSearch()
+      }
+    }
+  }
+</script>
+<style lang="scss" scoped>
+  @import '~assets/scss/variables';
+
+  .search-box {
+    width: 470px;
+    height: 40px;
+    position: relative;
+
+    .search-input, .search-btn {
+      height: 40px;
+      border-width: 2px;
+    }
+
+    .search-btn {
+      font-size: 16px;
+      width: 78px;
+    }
+
+    .search-hot {
+      .item {
+        display: inline-block;
+        font-size: $font-size-small;
+        margin-right: $pad;
+
+        &.item-first {
+          color: $red;
+        }
+      }
+    }
+
+    .association {
+      position: absolute;
+      left: 0;
+      top: 100%;
+      right: 79px;
+      background: $white;
+      border: $border;
+      border-top-width: 0;
+      z-index: 21;
+
+      .item {
+        padding: 0 15px;
+        line-height: 30px;
+        cursor: pointer;
+
+        &.active {
+          background-color: $dark-bg;
+        }
+        &:hover {
+          background-color: $grey-bg;
+        }
+      }
+    }
+  }
+</style>

+ 150 - 0
components/home/floor/Floor.vue

@@ -0,0 +1,150 @@
+<template>
+  <div class="floor">
+    <h3>F{{ floor.floorNumber }}&nbsp;{{ floor.name }}</h3>
+    <ul class="list-unstyled clearfix" :style="{borderColor: floor.items[1].backGroundColor || '#d8d8d8'}">
+      <li v-for="(item, index) in floor.items" :key="index" class="floor-item" :class="item.size"
+          :style="{backgroundColor: item.backGroundColor || '#fff', borderColor: item.borderColor || floor.items[1].backGroundColor || '#d8d8d8'}">
+        <a :href="item.hrefUrl" target="_blank">
+          <img :src="item.pictureUrl" class="floor-item-img"/>
+          <div class="floor-content">
+            <p v-if="item.name" class="floor-item-name">{{ item.name }}</p>
+            <p v-if="item.body" v-html="item.body" class="floor-item-body"></p>
+          </div>
+        </a>
+      </li>
+    </ul>
+  </div>
+</template>
+<script>
+  export default {
+    name: 'floor',
+    props: {
+      floor: Object
+    }
+  }
+</script>
+<style lang="scss" scoped>
+  .floor {
+
+    ul {
+      border-bottom: 1px solid #d8d8d8;
+    }
+
+    .floor-item {
+      float: left;
+      position: relative;
+      overflow: hidden;
+
+      a {
+        display: block;
+        text-align: center;
+      }
+
+      .floor-item-img:hover {
+        transform: scale(1.1);
+      }
+
+      .floor-content {
+        position: absolute;
+        text-align: center;
+      }
+
+      &.medium,&.small {
+        border-top: 1px solid #d8d8d8;
+        border-right: 1px solid #d8d8d8;
+      }
+
+      &.large {
+        width: 360px;
+        height: 400px;
+
+        img {
+          width: 226px;
+          height: 226px;
+          margin-top: 40px;
+        }
+      }
+      &.medium {
+        width: 389px;
+        height: 199px;
+
+        img {
+          position: absolute;
+          bottom: 50px;
+          right: 52px;
+          width: 100px;
+          height: 100px;
+        }
+
+        .floor-content {
+          top: 0;
+          left: 0;
+          right: 0;
+          padding: 30px;
+        }
+
+        .floor-item-name {
+          color: #575757;
+          font-size: 24px;
+          margin-bottom: 30px;
+          text-align: left;
+          font-weight: 600;
+          overflow: hidden;
+          text-overflow: ellipsis;
+          display: -webkit-box;
+          -webkit-box-orient: vertical;
+          -webkit-line-clamp: 2;
+          width: 300px;
+          word-wrap: break-word;
+        }
+
+        .floor-item-body {
+          margin-right: 150px;
+          color: #575757;
+          font-size: 14px;
+          line-height: 20px;
+          text-align: left;
+        }
+      }
+      &.small {
+        width: 219px;
+        height: 199px;
+
+        img {
+          margin-top: 15px;
+          width: 70px;
+          height: 70px;
+        }
+
+        .floor-content {
+          top: 100px;
+          bottom: 0;
+          left: 0;
+          right: 0;
+          padding: 15px;
+        }
+
+        .floor-item-name {
+          color: #575757;
+          font-size: 16px;
+          font-weight: 600;
+          text-overflow: ellipsis;
+          overflow: hidden;
+          display: -webkit-box;
+          -webkit-box-orient: vertical;
+          -webkit-line-clamp: 2;
+          word-break: break-all;
+        }
+
+        .floor-item-body {
+          color: #575757;
+          font-size: 14px;
+          line-height: 18px;
+        }
+      }
+      &.tiny {
+        display: none;
+      }
+    }
+  }
+</style>

+ 83 - 0
components/home/floor/FloorBar.vue

@@ -0,0 +1,83 @@
+<template>
+  <ul class="floor-bar list-unstyled" :class="{hide: !visible}">
+    <li v-for="(floor, index) in floors.data" :key="index" class="floor-bar-item">
+      <a @click="jumpFloor(index)"
+         :style="{backgroundColor: index==activeFloor?floor.items[1].backGroundColor:'#b7dfff'}">
+        <span>F{{ floor.floorNumber }}</span><br/>
+        <span class="floor-item-name">{{ floor.name }}</span>
+      </a>
+    </li>
+  </ul>
+</template>
+<script>
+  import { scrollTo } from '~utils/scroll'
+
+  export default {
+    name: 'floor-bar',
+    props: {
+      floors: {
+        type: Object
+      }
+    },
+    data () {
+      return {
+        visible: false,
+        activeFloor: -1
+      }
+    },
+    methods: {
+      onScroll () {
+        let scrolled = document.documentElement.scrollTop || window.pageYOffset || document.body.scrollTop
+        let floors = document.querySelectorAll('.floor')
+        let barOffset = document.querySelector('.floor-bar').offsetTop
+        this.visible = (barOffset >= floors[0].offsetTop - scrolled &&
+          barOffset <= floors[floors.length - 1].offsetTop - scrolled + floors[floors.length - 1].clientHeight)
+        if (this.visible) {
+          for (let i = 0; i < floors.length; i++) {
+            if (barOffset >= floors[i].offsetTop - scrolled + 60) {
+              this.activeFloor = i
+            }
+          }
+        }
+      },
+      jumpFloor (index) {
+        if (this.visible) {
+          scrollTo(document.querySelectorAll('.floor').item(index), 300, { offset: -60 })
+        }
+      }
+    },
+    mounted: function () {
+      this.$nextTick(function () {
+        window.addEventListener('scroll', this.onScroll)
+      })
+    }
+  }
+</script>
+<style lang="scss" scoped>
+  .floor-bar {
+    position: fixed;
+    margin-left: -70px;
+    width: 60px;
+    bottom: 20%;
+
+    .floor-bar-item {
+      margin-bottom: 1px;
+      text-align: center;
+
+      a {
+        display: block;
+        width: 60px;
+        height: 45px;
+        padding-top: 5px;
+        background-color: #b7dfff;
+        color: #fff;
+        overflow: hidden;
+
+        .floor-item-name {
+          font-size: 12px;
+          display: inline-block;
+        }
+      }
+    }
+  }
+</style>

+ 32 - 0
components/home/floor/FloorList.vue

@@ -0,0 +1,32 @@
+<template>
+  <div class="floor-list">
+    <div class="container">
+      <floor-bar :floors="floors"></floor-bar>
+      <floor v-for="(floor, index) in floors.data" :floor="floor" :key="index"></floor>
+    </div>
+  </div>
+</template>
+<script>
+  import Floor from './Floor.vue'
+  import FloorBar from './FloorBar.vue'
+
+  export default {
+    name: 'floor-list',
+    components: {
+      Floor,
+      FloorBar
+    },
+    computed: {
+      floors () {
+        return this.$store.state.floor.list
+      }
+    }
+  }
+</script>
+<style lang="scss" scoped>
+  @import '~assets/scss/variables';
+
+  .floor-list {
+    margin-bottom: $xlg-pad;
+  }
+</style>

+ 8 - 0
components/home/index.js

@@ -0,0 +1,8 @@
+import KindCategory from './KindCategory.vue'
+import Carousel from './Carousel.vue'
+import Advert from './Advert.vue'
+import FloorList from './floor/FloorList.vue'
+import Partner from './Partner.vue'
+import News from './News.vue'
+
+export { KindCategory, Carousel, Advert, FloorList, Partner, News }

+ 64 - 0
components/main/Header.vue

@@ -0,0 +1,64 @@
+<template>
+  <div class="header clearfix">
+    <div class="container">
+      <!--Logo商标-->
+      <div class="header-brand pull-left">
+        <nuxt-link :to="'/'">
+          <img src="/images/logo/uas_mall.png" class="mall-logo"/>
+          <div class="mall-slogan">
+            <p>For the World<br/>为世界电子产业创造价值</p>
+          </div>
+        </nuxt-link>
+      </div>
+      <!--搜索-->
+      <div class="header-search pull-left">
+        <search-box></search-box>
+      </div>
+      <!--统计-->
+      <div class="header-count pull-right">
+        <count-box></count-box>
+      </div>
+    </div>
+  </div>
+</template>
+<script>
+  import SearchBox from './Search.vue'
+  import CountBox from './count/Box.vue'
+
+  export default {
+    name: 'home-header',
+    components: {
+      SearchBox,
+      CountBox
+    }
+  }
+</script>
+<style lang="scss" scoped>
+  @import '~assets/scss/variables';
+
+  .header {
+    padding: 10px 0;
+
+    .header-brand {
+
+      .mall-logo {
+        float: left;
+      }
+
+      .mall-slogan {
+        line-height: 20px;
+        font-weight: bold;
+        color: $primary;
+        border-left: 1px solid $primary;
+        padding-left: 12px;
+        margin-left: 118px;
+        margin-top: 20px;
+      }
+    }
+
+    .header-search {
+      margin-top: 20px;
+      margin-left: 75px;
+    }
+  }
+</style>

+ 75 - 0
components/main/Nav.vue

@@ -0,0 +1,75 @@
+<template>
+  <nav class="nav-list">
+    <div class="container">
+      <nuxt-link to="/product" class="item item-first">
+        <div>器件选型</div>
+      </nuxt-link>
+      <nuxt-link :to="'/'" class="item" exact>
+        <span>首&nbsp;&nbsp;页</span>
+      </nuxt-link>
+      <nuxt-link to="/product" class="item">
+        <span>品牌中心</span>
+      </nuxt-link>
+      <nuxt-link to="/providers" class="item">
+        <span>原厂专区</span>
+      </nuxt-link>
+      <nuxt-link to="/providers" class="item">
+        <span>代理经销</span>
+      </nuxt-link>
+      <nuxt-link to="/product" class="item">
+        <span>热卖推荐</span>
+      </nuxt-link>
+      <nuxt-link to="/product" class="item">
+        <span>库存寄售</span>
+      </nuxt-link>
+      <nuxt-link to="/news" class="item">
+        <span>优软快讯</span>
+      </nuxt-link>
+    </div>
+  </nav>
+</template>
+<script>
+  export default {
+    name: 'nav'
+  }
+</script>
+<style lang="scss" scoped>
+  @import '~assets/scss/variables';
+  $nav-height: 40px;
+
+  .nav-list {
+    background-color: rgb(244, 248, 255);
+    height: $nav-height;
+
+    .item {
+      display: inline-block;
+      height: $nav-height;
+      line-height: $nav-height;
+      margin: 0 15px;
+      color: $black-light;
+
+      > span {
+        padding: 5px 2px;
+      }
+
+      &.nuxt-link-active, &:hover {
+        > span {
+          color: #5078cb;
+          border-bottom: #5078cb 3px solid;
+          font-weight: bold;
+        }
+      }
+
+      &.item-first {
+        width: 200px;
+        margin: 0;
+        background-color: rgb(33, 71, 151);
+        font-size: 14px;
+        font-weight: bold;
+        text-align: center;
+        color: #fff;
+        cursor: pointer;
+      }
+    }
+  }
+</style>

+ 191 - 0
components/main/Search.vue

@@ -0,0 +1,191 @@
+<template>
+  <div class="search-box">
+    <div class="input-group">
+      <input v-model="keyword" type="text" class="search-input form-control input-primary"
+             placeholder="型号/类目/品牌"
+             @focus.stop.prevent="onFocus()"
+             @blur.stop.prevent="onBlur()"
+             @keyup.40="onSelectChange(1)"
+             @keyup.38="onSelectChange(-1)"
+             @keyup.13="onSearch()"/>
+      <span class="input-group-btn" @click="onSearch()">
+        <button class="btn btn-primary search-btn" type="button">搜&nbsp;索</button>
+      </span>
+    </div>
+    <ul class="association" v-show="showAssociate"
+        @mouseenter="associate.focus=true" @mouseleave="associate.focus=false">
+      <li v-for="(k, index) in similarKeywords.data" :key="k" class="item"
+          :class="{'active': index==associate.activeIndex}"
+          @click.stop.prevent="onAssociateClick(k)">{{ k }}
+      </li>
+    </ul>
+    <div class="search-hot">
+      <ul class="list-untyled">
+        <li class="item item-first">热门搜索</li>
+        <li class="item" v-for="w in hotwords">
+          <nuxt-link :to="w.url" target="_blank">{{ w.name }}</nuxt-link>
+        </li>
+      </ul>
+    </div>
+  </div>
+</template>
+<script>
+  export default {
+    name: 'search-box',
+    data () {
+      return {
+        keyword: '',
+        associate: {
+          focus: false,
+          show: false,
+          activeIndex: null
+        }
+      }
+    },
+    computed: {
+      similarKeywords () {
+        return this.$store.state.search.keywords
+      },
+      showAssociate () {
+        return this.keyword && this.associate.show && this.similarKeywords.data && this.similarKeywords.data.length
+      }
+    },
+    props: {
+      hotwords: {
+        type: Array,
+        default () {
+          return [{
+            name: 'SCT2080KEC',
+            url: 'product/component/1100400300009990'
+          }, {
+            name: '电池组',
+            url: 'product/kinds/346'
+          }, {
+            name: 'Vishay',
+            url: 'product/brand/30327265e42a871be050007f01003d96'
+          }, {
+            name: 'Panasonic Battery',
+            url: 'product/brand/30327265e4e7871be050007f01003d96'
+          }]
+        }
+      }
+    },
+    watch: {
+      'keyword': {
+        handler (val, oldVal) {
+          let keywords = this.similarKeywords.data
+          if (!keywords || !keywords.length || this.associate.activeIndex === null || val !== keywords[this.associate.activeIndex]) {
+            this.onChange()
+          }
+        }
+      }
+    },
+    methods: {
+      onFocus () {
+        this.associate.show = true
+      },
+      onBlur () {
+        this.associate.show = this.associate.focus
+      },
+      onSelectChange (count) {
+        let keywords = this.similarKeywords.data
+        if (keywords && keywords.length) {
+          let index = this.associate.activeIndex
+          if (index === null) {
+            index = -1
+          }
+          index += count
+          if (index >= keywords.length) {
+            index = 0
+          } else if (index < 0) {
+            index = keywords.length - 1
+          }
+          this.associate.activeIndex = index
+          this.keyword = keywords[index]
+        }
+      },
+      onChange () {
+        this.associate.activeIndex = null
+        if (!this.keyword) {
+          this.associate.show = false
+          this.$store.dispatch('resetSearchKeywords')
+        } else {
+          this.searchKeywords()
+        }
+      },
+      searchKeywords () {
+        this.associate.show = true
+        this.$store.dispatch('searchKeywords', { keyword: this.keyword })
+      },
+      onSearch () {
+        if (this.keyword) {
+          this.associate.show = false
+          this.$store.dispatch('resetSearchKeywords')
+          this.$router.push({path: '/search?w=' + encodeURIComponent(this.keyword)})
+        }
+      },
+      onAssociateClick (word) {
+        this.keyword = word
+        this.onSearch()
+      }
+    },
+    created () {
+      this.$store.dispatch('resetSearchKeywords')
+    }
+  }
+</script>
+<style lang="scss" scoped>
+  @import '~assets/scss/variables';
+
+  .search-box {
+    width: 470px;
+    height: 40px;
+    position: relative;
+
+    .search-input, .search-btn {
+      height: 40px;
+      border-width: 2px;
+    }
+
+    .search-btn {
+      font-size: 16px;
+      width: 78px;
+    }
+
+    .search-hot {
+      .item {
+        display: inline-block;
+        font-size: $font-size-small;
+        margin-right: $pad;
+
+        &.item-first {
+          color: $red;
+        }
+      }
+    }
+
+    .association {
+      position: absolute;
+      left: 0;
+      top: 100%;
+      right: 79px;
+      background: $white;
+      border: $border;
+      border-top-width: 0;
+      z-index: 21;
+
+      .item {
+        padding: 0 15px;
+        line-height: 30px;
+        cursor: pointer;
+
+        &.active {
+          background-color: $dark-bg;
+        }
+        &:hover {
+          background-color: $grey-bg;
+        }
+      }
+    }
+  }
+</style>

+ 65 - 0
components/main/count/Box.vue

@@ -0,0 +1,65 @@
+<template>
+  <div class="count-box">
+    <div class="swiper-container" v-swiper:swiper="swiperOption">
+      <div class="swiper-wrapper">
+        <div class="swiper-slide" v-for="(c, index) in counts.data" :key="index">
+          <count-item class="item" :title="c.item" :value="c.count"></count-item>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+<script>
+  import CountItem from './Item.vue'
+
+  export default {
+    name: 'count-box',
+    components: {
+      CountItem
+    },
+    data () {
+      return {
+        swiperOption: {
+          autoplay: 5000,
+          speed: 500,
+          direction: 'vertical',
+          slidesPerView: 2,
+          slidesPerGroup: 2
+        }
+      }
+    },
+    computed: {
+      counts () {
+        return this.$store.state.product.counts
+      }
+    },
+    mounted () {
+      this.$nextTick(() => {
+        this.loadCounts()
+        // 刷新统计信息
+        setInterval(() => {
+          this.loadCounts()
+        }, 30000)
+      })
+    },
+    methods: {
+      loadCounts () {
+        this.$store.dispatch('loadProductCounts', { _status: 'actived' })
+      }
+    }
+  }
+</script>
+<style lang="scss" scoped>
+  .count-box {
+    position: relative;
+    top: 15px;
+    float: right;
+    width: 300px;
+    height: 60px;
+    overflow: hidden;
+
+    .swiper-container {
+      height: 100%;
+    }
+  }
+</style>

+ 67 - 0
components/main/count/Item.vue

@@ -0,0 +1,67 @@
+<template>
+  <div class="count-item">
+    <span class="title">{{ title }}</span>
+    <span v-for="num in nums" :class="num == ',' ? 'separator' : 'num'">{{ num }}</span>
+  </div>
+</template>
+<script>
+  export default {
+    name: 'count-item',
+    props: {
+      value: {
+        default: 0,
+        type: Number
+      },
+      title: {
+        type: String
+      }
+    },
+    methods: {
+      formatNumber (num) {
+        let re = /(\d+)(\d{3})/
+        num = (Array(10 - String(num).length).join(0) + num)
+        while (re.test(num)) {
+          num = num.replace(re, '$1,$2')
+        }
+        return num.split('')
+      }
+    },
+    computed: {
+      nums () {
+        return this.formatNumber(this.value)
+      }
+    }
+  }
+</script>
+<style lang="scss" scoped>
+  @import '~assets/scss/variables';
+
+  .count-item {
+    text-align: center;
+    line-height: 20px;
+
+    .title {
+      display: inline-block;
+      width: 60px;
+      float: left;
+      font-weight: bold;
+    }
+    .separator, .num {
+      display: inline-block;
+    }
+    .separator {
+      font-size: 12px;
+      color: $primary;
+      margin: 0 5px 0 -2px;
+    }
+    .num {
+      background: $primary;
+      width: 15px;
+      height: 20px;
+      margin-right: 10px;
+      text-align: center;
+      color: $white;
+      font-weight: bold;
+    }
+  }
+</style>

+ 4 - 0
components/main/index.js

@@ -0,0 +1,4 @@
+import MainHeader from './Header.vue'
+import MainNav from './Nav.vue'
+
+export { MainHeader, MainNav }

+ 0 - 0
components/news/index.js


+ 0 - 0
components/product/index.js


+ 8 - 0
layouts/README.md

@@ -0,0 +1,8 @@
+# LAYOUTS
+
+This directory contains your Application Layouts.
+
+More information about the usage of this directory in the documentation:
+https://nuxtjs.org/guide/views#layouts
+
+**This directory is not required, you can delete it if you don't want to use it.**

+ 23 - 0
layouts/default.vue

@@ -0,0 +1,23 @@
+<template>
+  <div id="app">
+    <header-view></header-view>
+    <nuxt/>
+    <footer-view></footer-view>
+    <right-bar></right-bar>
+  </div>
+</template>
+<script>
+  import { Header, Footer, RightBar } from '~components/default'
+
+  export default {
+    name: 'app',
+    components: {
+      HeaderView: Header,
+      FooterView: Footer,
+      RightBar
+    }
+  }
+</script>
+<style>
+
+</style>

+ 38 - 0
layouts/error.vue

@@ -0,0 +1,38 @@
+<template>
+  <div class="error">
+    <div class="error-content">
+      <h1 class="error-code">{{ error.statusCode }}</h1>
+      <div class="error-wrapper-message">
+        <h2 class="error-message">找不到页面</h2>
+      </div>
+      <p>
+        <nuxt-link class="error-link" to="/">返回首页</nuxt-link>
+      </p>
+    </div>
+  </div>
+</template>
+
+<script>
+  export default {
+    props: ['error']
+  }
+</script>
+
+<style lang="scss">
+  @import '~assets/scss/mixins';
+  @import '~assets/scss/variables';
+  .error {
+    position: relative;
+    background-color: $module-bg;
+    min-height: 10em;
+    width: 100%;
+    overflow: hidden;
+    text-align: center;
+    padding: 2em 0;
+    .error-content {
+      .error-code {
+        font-size: 6rem;
+      }
+    }
+  }
+</style>

+ 28 - 0
layouts/main.vue

@@ -0,0 +1,28 @@
+<template>
+  <div id="main">
+    <header-view></header-view>
+    <main-header></main-header>
+    <main-nav></main-nav>
+    <nuxt/>
+    <footer-view></footer-view>
+    <right-bar></right-bar>
+  </div>
+</template>
+<script>
+  import { Header, Footer, RightBar } from '~components/default'
+  import { MainHeader, MainNav } from '~components/main'
+
+  export default {
+    name: 'main',
+    components: {
+      HeaderView: Header,
+      FooterView: Footer,
+      RightBar,
+      MainHeader,
+      MainNav
+    }
+  }
+</script>
+<style>
+
+</style>

+ 9 - 0
middleware/README.md

@@ -0,0 +1,9 @@
+# MIDDLEWARE
+
+This directory contains your Application Middleware.
+The middleware lets you define custom function to be ran before rendering a page or a group of pages (layouts).
+
+More information about the usage of this directory in the documentation:
+https://nuxtjs.org/guide/routing#middleware
+
+**This directory is not required, you can delete it if you don't want to use it.**

+ 12 - 0
middleware/check-auth.js

@@ -0,0 +1,12 @@
+import { getAuthInfo } from '~/utils/client-auth'
+
+export default function ({ isServer, store, req }) {
+  // If nuxt generate, pass this middleware
+  if (isServer && !req) return
+  if (isServer) {
+    store.dispatch('loadUserInfo')
+  } else {
+    const loggedUser = getAuthInfo()
+    store.commit('option/REQUEST_USER_INFO_SUCCESS', loggedUser)
+  }
+}

+ 90 - 0
nuxt.config.js

@@ -0,0 +1,90 @@
+const path = require('path')
+const isProdMode = Object.is(process.env.NODE_ENV, 'production')
+const baseUrl = process.env.BASE_URL || (isProdMode ? 'http://www.usoftmall.com/' : 'http://192.168.253.60:9090/platform-b2c/')
+
+module.exports = {
+  router: {
+    middleware: 'check-auth'
+  },
+  /*
+  ** Headers of the page
+  */
+  head: {
+    title: '【优软商城】IC电子元器件现货采购交易平台商城',
+    meta: [
+      { charset: 'utf-8' },
+      { name: 'viewport', content: 'width=device-width, initial-scale=1' },
+      { hid: 'description', name: 'description', content: '优软商城(usoftmall.com)是中国领先的IC电子元器件现货采购交易网上商城,提供上千万种电子元器件现货采购交易,采购电子元器件就上优软商城!' }
+    ],
+    link: [
+      { rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' }
+    ]
+  },
+  /*
+  ** Customize the progress-bar color
+  */
+  loading: { color: '#3B8070' },
+  /*
+  ** Build configuration
+  */
+  build: {
+    /*
+    ** Run ESLINT on save
+    */
+    extend (config, { dev, isClient, isServer }) {
+      config.resolve.alias['~utils'] = path.join(__dirname, 'utils')
+      config.module.rules.push({
+        test: /\.scss$/,
+        loader: 'vue-style-loader!css-loader!sass-loader'
+      })
+      if (isClient) {
+        config.module.rules.push({
+          enforce: 'pre',
+          test: /\.(js|vue)$/,
+          loader: 'eslint-loader',
+          exclude: /(node_modules)/
+        })
+      }
+    },
+    vendor: [
+      'axios',
+      'swiper'
+    ],
+    babel: {
+      presets: ['es2015', 'stage-2'],
+      plugins: [
+        'transform-async-to-generator',
+        'transform-runtime'
+      ],
+      comments: true
+    },
+    postcss: [
+      require('autoprefixer')({
+        browsers: ['last 3 versions']
+      })
+    ]
+  },
+  css: [{
+    src: '~assets/scss/app.scss',
+    lang: 'scss'
+  }, {
+    src: 'swiper/dist/css/swiper.css'
+  }],
+  dev: !isProdMode,
+  env: {
+    baseUrl
+  },
+  plugins: [{
+    src: '~plugins/axios.js'
+  }, {
+    src: '~plugins/swiper.js',
+    ssr: false
+  }, {
+    src: '~plugins/vue-loading.js',
+    ssr: false
+  }, {
+    src: '~plugins/vue-empty.js',
+    ssr: false
+  }],
+  proxyTable: ['/api/**', '/search/**', '/user/**', '/login/**', '/logout/**']
+}

+ 36 - 0
package.json

@@ -0,0 +1,36 @@
+{
+  "name": "mall-web-ssr",
+  "version": "1.0.0",
+  "description": "mall web project",
+  "author": "yingp <yingp@usoftchina.com>",
+  "private": true,
+  "dependencies": {
+    "@nuxtjs/proxy": "^1.1.1",
+    "axios": "^0.15.3",
+    "bezier-easing": "^2.0.3",
+    "cross-env": "^3.1.4",
+    "express": "^4.14.1",
+    "nuxt": "0.10.6",
+    "vue-awesome-swiper": "^2.5.4"
+  },
+  "scripts": {
+    "dev": "nodemon --exec node server.js",
+    "build": "cross-env NODE_ENV=production nuxt build",
+    "start": "cross-env NODE_ENV=production node server.js",
+    "generate": "cross-env NODE_ENV=production nuxt generate",
+    "lint": "eslint --ext .js,.vue --ignore-path .gitignore .",
+    "precommit": "npm run lint"
+  },
+  "devDependencies": {
+    "babel-eslint": "^7.1.1",
+    "babel-preset-stage-2": "^6.24.1",
+    "eslint": "^3.15.0",
+    "eslint-config-standard": "^6.2.1",
+    "eslint-loader": "^1.6.1",
+    "eslint-plugin-html": "^2.0.0",
+    "eslint-plugin-promise": "^3.4.1",
+    "eslint-plugin-standard": "^2.0.1",
+    "node-sass": "^4.5.3",
+    "sass-loader": "^6.0.6"
+  }
+}

+ 7 - 0
pages/README.md

@@ -0,0 +1,7 @@
+# PAGES
+
+This directory contains your Application Views and Routes.
+The framework reads all the .vue files inside this directory and create the router of your application.
+
+More information about the usage of this directory in the documentation:
+https://nuxtjs.org/guide/routing

+ 40 - 0
pages/index.vue

@@ -0,0 +1,40 @@
+<template>
+  <div class="index">
+    <carousel>
+      <kind-category @loadchild="loadProductKinds"></kind-category>
+    </carousel>
+    <advert></advert>
+    <floor-list></floor-list>
+    <news></news>
+    <partner></partner>
+  </div>
+</template>
+<script>
+  import { KindCategory, Carousel, Advert, FloorList, Partner, News } from '~components/home'
+
+  export default {
+    name: 'index',
+    layout: 'main',
+    fetch ({ store }) {
+      return Promise.all([
+        store.dispatch('loadFloors'),
+        store.dispatch('loadBanners'),
+        store.dispatch('loadProductKinds', { id: 0 }),
+        store.dispatch('loadNewsSnapshot', { page: 1 })
+      ])
+    },
+    components: {
+      KindCategory,
+      Carousel,
+      Advert,
+      FloorList,
+      Partner,
+      News
+    },
+    methods: {
+      loadProductKinds (id) {
+        this.$store.dispatch('loadAllProductKinds', {id})
+      }
+    }
+  }
+</script>

+ 0 - 0
pages/news/_id.vue


+ 0 - 0
pages/news/index.vue


+ 0 - 0
pages/product/brand/_code.vue


+ 0 - 0
pages/product/brand/index.vue


+ 8 - 0
plugins/README.md

@@ -0,0 +1,8 @@
+# PLUGINS
+
+This directory contains your Javascript plugins that you want to run before instantiating the root vue.js application.
+
+More information about the usage of this directory in the documentation:
+https://nuxtjs.org/guide/plugins
+
+**This directory is not required, you can delete it if you don't want to use it.**

+ 21 - 0
plugins/axios.js

@@ -0,0 +1,21 @@
+import Vue from 'vue'
+import axios from 'axios'
+
+const service = axios.create({
+  baseURL: process.env.proxyUrl || process.env.baseUrl
+})
+
+service.interceptors.request.use(config => {
+  return config
+}, error => {
+  return Promise.reject(error)
+})
+
+service.interceptors.response.use(response => {
+  return response
+}, error => {
+  return Promise.reject(error)
+})
+
+Vue.prototype.$http = service
+export default service

+ 4 - 0
plugins/swiper.js

@@ -0,0 +1,4 @@
+import Vue from 'vue'
+import VueAwesomeSwiper from 'vue-awesome-swiper/ssr'
+
+Vue.use(VueAwesomeSwiper)

+ 5 - 0
plugins/vue-empty.js

@@ -0,0 +1,5 @@
+
+import Vue from 'vue'
+import VueEmpty from '~components/common/vue-empty'
+
+Vue.use(VueEmpty)

+ 4 - 0
plugins/vue-loading.js

@@ -0,0 +1,4 @@
+import Vue from 'vue'
+import VueLoading from '~components/common/vue-loading'
+
+Vue.use(VueLoading)

+ 60 - 0
server.js

@@ -0,0 +1,60 @@
+const Nuxt = require('nuxt')
+const app = require('express')()
+const proxy = require('http-proxy-middleware')
+const host = process.env.HOST || '127.0.0.1'
+const port = process.env.PORT || 3000
+process.noDeprecation = true
+
+app.set('port', port)
+
+// Import and Set Nuxt.js options
+let config = require('./nuxt.config.js')
+config.dev = !(process.env.NODE_ENV === 'production')
+
+// 请求代理,dev模式下使用,接口服务器如果支持跨域可去掉
+const proxyTable = config.proxyTable
+if (config.dev && proxyTable) {
+  // 本地代理支持localhost、127.0.0.1等不同地址跨域
+  app.use((req, res, next) => {
+    res.header('Access-Control-Allow-Origin', '*')
+    res.header('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE')
+    res.header('Access-Control-Allow-Headers', 'Content-Type')
+    res.header('Access-Control-Allow-Credentials', 'true')
+    next()
+  })
+  if (Array.isArray(proxyTable)) {
+    app.use(proxy(proxyTable, {
+      target: config.env.baseUrl,
+      changeOrigin: true
+    }))
+  } else {
+    Object.keys(proxyTable).forEach((context) => {
+      var options = proxyTable[context]
+      if (typeof options === 'string') {
+        options = { target: options }
+      }
+      app.use(proxy(context, options))
+    })
+  }
+  // axios use proxy url
+  config.env.proxyUrl = `http://${host}:${port}/`
+}
+
+// Init Nuxt.js
+const nuxt = new Nuxt(config)
+app.use(nuxt.render)
+
+// Build only in dev mode
+if (config.dev) {
+  nuxt.build()
+    .catch((error) => {
+      // eslint-disable-line no-console
+      console.error(error)
+      process.exit(1)
+    })
+}
+
+// Listen the server
+app.listen(port, host)
+// eslint-disable-line no-console
+console.log(`Nuxt.js SSR Server listening on ${host} : ${port}, at ${new Date().toLocaleString()}`)

+ 11 - 0
static/README.md

@@ -0,0 +1,11 @@
+# STATIC
+
+This directory contains your static files.
+Each file inside this directory is mapped to /.
+
+Example: /static/robots.txt is mapped as /robots.txt.
+
+More information about the usage of this directory in the documentation:
+https://nuxtjs.org/guide/assets#static
+
+**This directory is not required, you can delete it if you don't want to use it.**

BIN
static/favicon.ico


BIN
static/images/adverts/1.jpg


BIN
static/images/adverts/10.jpg


BIN
static/images/adverts/11.jpg


BIN
static/images/adverts/2.jpg


BIN
static/images/adverts/3.jpg


BIN
static/images/adverts/4.jpg


BIN
static/images/adverts/5.jpg


BIN
static/images/adverts/6.jpg


BIN
static/images/adverts/7.jpg


BIN
static/images/adverts/8.jpg


BIN
static/images/adverts/9.jpg


BIN
static/images/credit/1.jpg


BIN
static/images/credit/2.jpg


BIN
static/images/credit/3.jpg


BIN
static/images/credit/4.jpg


BIN
static/images/credit/5.jpg


BIN
static/images/credit/credit01.jpg


BIN
static/images/credit/credit02.jpg


BIN
static/images/credit/credit03.jpg


BIN
static/images/credit/credit04.jpg


BIN
static/images/credit/credit05.jpg


BIN
static/images/logo/uas.png


BIN
static/images/logo/uas_mall.png


BIN
static/images/logo_uas.png


BIN
static/images/news/news-bg.jpg


BIN
static/images/partners/1.jpg


BIN
static/images/partners/2.jpg


BIN
static/images/partners/3.jpg


BIN
static/images/partners/4.jpg


BIN
static/images/partners/5.jpg


BIN
static/images/partners/6.jpg


BIN
static/images/partners/7.jpg


BIN
static/images/qrcode/mall.png


Some files were not shown because too many files changed in this diff