Authored by ccbikai

Merge branch 'feature/home' into develop

  1 +/**
  2 + * sub app shopping
  3 + * @author: jinhu.dong<jinhu.dong@yoho.cn>
  4 + * @date: 2016/07/04
  5 + */
  6 +
  7 +var express = require('express');
  8 +var app = express();
  9 +
  10 +app.on('mount', function(parent) {
  11 + delete parent.locals.settings; // 不继承父 App 的设置
  12 + Object.assign(app.locals, parent.locals);
  13 +});
  14 +
  15 +// router
  16 +app.use(require('./router'));
  17 +
  18 +module.exports = app;
  1 +/**
  2 + * router of sub app shopping
  3 + * @author: jinhu.dong<jinhu.dong@yoho.cn>
  4 + * @date: 2016/07/04
  5 + */
  6 +
  7 +'use strict';
  8 +
  9 +const router = require('express').Router(); // eslint-disable-line
  10 +
  11 +var multipart = require('connect-multiparty');
  12 +var multipartMiddleware = multipart();
  13 +
  14 +const uploadApi = require('./upload/upload.js');
  15 +
  16 +router.post('/upload/image', multipartMiddleware, uploadApi.uploadImg);
  17 +
  18 +module.exports = router;
  1 +/**
  2 + * 上传接口
  3 + * @author: kai.bi<kai.bi@yoho.cn>
  4 + * @date: 2016/07/22
  5 + */
  6 +
  7 +'use strict';
  8 +
  9 +const request = require('request-promise');
  10 +const fs = require('fs');
  11 +const _ = require('lodash');
  12 +
  13 +const uploadImg = (req, res, next) => {
  14 + let files = req.files && req.files.filename || [];
  15 +
  16 + if (!_.isArray(files)) {
  17 + files = [files];
  18 + }
  19 +
  20 + req.body.files = [];
  21 + files.forEach(file => {
  22 + req.body.files.push(fs.createReadStream(file.path));
  23 + req.body.files.push(file.name);
  24 + });
  25 +
  26 + request({
  27 + method: 'post',
  28 + url: 'http://upload.static.yohobuy.com',
  29 + formData: {
  30 + fileData: req.body.files,
  31 + project: req.body.bucket || 'goodsimg'
  32 + },
  33 + json: true
  34 + }).then(function(result) {
  35 + res.json(result);
  36 + }).catch(next);
  37 +};
  38 +
  39 +module.exports = {
  40 + uploadImg
  41 +};
@@ -40,13 +40,15 @@ const refund = { @@ -40,13 +40,15 @@ const refund = {
40 }).catch(next); 40 }).catch(next);
41 }, 41 },
42 saveLogistics(req, res, next) { 42 saveLogistics(req, res, next) {
43 - const company = req.body.company; 43 + const company_id = req.body.company_id;
  44 + const company_name = req.body.company_name;
44 const num = req.body.num; 45 const num = req.body.num;
45 46
  47 + // todo 调用保存物流信息接口
46 res.json({ 48 res.json({
47 code: 200 49 code: 200
48 }); 50 });
49 } 51 }
50 }; 52 };
51 53
52 -module.exports = refund; 54 +module.exports = refund;
1 <div class="logistics-page" id="logistics"> 1 <div class="logistics-page" id="logistics">
2 - <components :is="currentView" :company="company" keep-alive></components> 2 + <components :is="currentView" :company_id="company_id" :company_name="company_name" keep-alive></components>
3 </div> 3 </div>
@@ -15,8 +15,8 @@ module.exports = { @@ -15,8 +15,8 @@ module.exports = {
15 port: 6004, 15 port: 6004,
16 siteUrl: '//m.yohoblk.com', 16 siteUrl: '//m.yohoblk.com',
17 domains: { 17 domains: {
18 - api: 'http://api.yoho.cn/',  
19 - service: 'http://service.yoho.cn/' 18 + api: 'http://192.168.102.202:8080/gateway/',
  19 + service: 'http://192.168.102.202:8080/gateway/'
20 }, 20 },
21 subDomains: { 21 subDomains: {
22 host: '.m.yohoblk.com', 22 host: '.m.yohoblk.com',
@@ -7,6 +7,7 @@ @@ -7,6 +7,7 @@
7 7
8 module.exports = app => { 8 module.exports = app => {
9 app.use('/', require('./apps/channel')); 9 app.use('/', require('./apps/channel'));
  10 + app.use('/api', require('./apps/api'));
10 app.use('/product', require('./apps/product')); 11 app.use('/product', require('./apps/product'));
11 app.use('/home', require('./apps/home')); 12 app.use('/home', require('./apps/home'));
12 13
@@ -14,6 +15,4 @@ module.exports = app => { @@ -14,6 +15,4 @@ module.exports = app => {
14 if (app.locals.devEnv) { 15 if (app.locals.devEnv) {
15 app.use('/example', require('./apps/example')); 16 app.use('/example', require('./apps/example'));
16 } 17 }
17 -  
18 - app.use('/product', require('./apps/product'));  
19 }; 18 };
@@ -22,6 +22,7 @@ @@ -22,6 +22,7 @@
22 "bluebird": "^3.4.1", 22 "bluebird": "^3.4.1",
23 "body-parser": "^1.15.2", 23 "body-parser": "^1.15.2",
24 "connect-memcached": "^0.2.0", 24 "connect-memcached": "^0.2.0",
  25 + "connect-multiparty": "^2.0.0",
25 "cookie-parser": "^1.4.3", 26 "cookie-parser": "^1.4.3",
26 "express": "^4.14.0", 27 "express": "^4.14.0",
27 "express-handlebars": "^3.0.0", 28 "express-handlebars": "^3.0.0",
@@ -8,7 +8,8 @@ Vue.use(infiniteScroll); @@ -8,7 +8,8 @@ Vue.use(infiniteScroll);
8 new Vue({ 8 new Vue({
9 el: '#logistics', 9 el: '#logistics',
10 data: { 10 data: {
11 - company: '', 11 + company_id: '',
  12 + company_name: '',
12 currentView: 'logistics', 13 currentView: 'logistics',
13 }, 14 },
14 components: { 15 components: {
@@ -18,7 +19,8 @@ new Vue({ @@ -18,7 +19,8 @@ new Vue({
18 events: { 19 events: {
19 changeView: function(obj) { 20 changeView: function(obj) {
20 this.currentView = obj.view; 21 this.currentView = obj.view;
21 - this.company = obj.company; 22 + this.company_id = obj.company_id;
  23 + this.company_name = obj.company_name;
22 } 24 }
23 } 25 }
24 }); 26 });
1 .logistics-page { 1 .logistics-page {
2 width: 100%; 2 width: 100%;
3 - background: #f0f0f0;  
4 3
5 .edit-logistics-page { 4 .edit-logistics-page {
6 width: 100%; 5 width: 100%;
@@ -14,7 +13,6 @@ @@ -14,7 +13,6 @@
14 background: #fff; 13 background: #fff;
15 font-size: 30px; 14 font-size: 30px;
16 line-height: 88px; 15 line-height: 88px;
17 - border-bottom: 1px solid #e0e0e0;  
18 16
19 label { 17 label {
20 display: block; 18 display: block;
@@ -50,6 +48,7 @@ @@ -50,6 +48,7 @@
50 -webkit-appearance: none; 48 -webkit-appearance: none;
51 } 49 }
52 50
  51 +
53 .num { 52 .num {
54 width: 440px; 53 width: 440px;
55 text-align: right; 54 text-align: right;
@@ -80,26 +79,50 @@ @@ -80,26 +79,50 @@
80 79
81 .search-input { 80 .search-input {
82 position: relative; 81 position: relative;
83 - padding: 14px 22px;  
84 - background: #f8f8f8;  
85 -  
86 - .icon {  
87 - position: absolute;  
88 - font-size: 24px;  
89 - top: 26px;  
90 - left: 36px;  
91 - color: #b2b2b2;  
92 - } 82 + text-align: center;
  83 + padding: 16px 16px;
  84 + border-bottom: 1px solid #e6e6e6;
93 85
94 input { 86 input {
95 height: 56px; 87 height: 56px;
96 - width: 378px;  
97 - border-radius: 28px;  
98 - padding: 0 52px;  
99 - font-size: 24px;  
100 - background: #fff; 88 + width: 100%;
  89 + padding-left: 15px;
  90 + border-radius: 20px;
  91 + font-size: 36px;
  92 + color: #b0b0b0;
  93 + background: #eee;
101 border: none; 94 border: none;
102 } 95 }
  96 +
  97 + input::-webkit-input-placeholder { /* WebKit browsers */
  98 + text-align: center;
  99 + }
  100 + input:-ms-input-placeholder { /* Internet Explorer 10+ */
  101 + text-align: center;
  102 + }
  103 + }
  104 +
  105 + .company-data {
  106 + color: #000;
  107 + margin-left: 30px;
  108 +
  109 + .company-item {
  110 +
  111 + h2 {
  112 + height: 50px;
  113 + line-height: 50px;
  114 + font-size: 30px;
  115 + border-bottom: 1px solid #f3f3f3;
  116 + }
  117 +
  118 + span {
  119 + display: block;
  120 + height: 90px;
  121 + line-height: 90px;
  122 + font-size: 30px;
  123 + border-bottom: 1px solid #f3f3f3;
  124 + }
  125 + }
103 } 126 }
104 } 127 }
105 } 128 }
  1 +<template>
  2 + <div class="upload">
  3 + <form v-on:change="upload">
  4 + <label class="label-input icon" for="{{inputId}}">
  5 + <input id="{{inputId}}" type="file" name="filename">
  6 + </label>
  7 + </form>
  8 + </div>
  9 +</template>
  10 +
  11 +<script>
  12 + module.exports = {
  13 + props: ['imageList', 'bucket'],
  14 + data() {
  15 + return {
  16 + inputId: 'input-' + Math.floor(Math.random() * 999999999) // 尽可能保证 input ID 唯一
  17 + };
  18 + },
  19 + methods: {
  20 + upload(e) {
  21 + const formData = new FormData(e.target.closest('form'));
  22 +
  23 + formData.append('bucket', this.bucket || '');
  24 + $.ajax({
  25 + method: 'POST',
  26 + url: '/api/upload/image',
  27 + data: formData,
  28 + processData: false, // 告诉jQuery不要去处理发送的数据
  29 + contentType: false // 告诉jQuery不要去设置Content-Type请求头
  30 + }).then(res => {
  31 + e.target.value = '';
  32 +
  33 + if (res.code === 200) {
  34 + res.data.imagesList.forEach(imagesPath => {
  35 + this.imageList.push(imagesPath);
  36 + });
  37 + } else {
  38 + alert(res.message);
  39 + }
  40 + });
  41 + }
  42 + }
  43 + };
  44 +</script>
  45 +
  46 +<style>
  47 + /* 每个地方上传按钮可能不一样,使用的时候自己写样式 */
  48 +</style>
@@ -167,6 +167,7 @@ @@ -167,6 +167,7 @@
167 167
168 .refund { 168 .refund {
169 .return-amount { 169 .return-amount {
  170 + margin: 30px 0;
170 padding: 0 30px; 171 padding: 0 30px;
171 font-size: 32px; 172 font-size: 32px;
172 line-height: 90px; 173 line-height: 90px;
1 <template> 1 <template>
2 <div class="companylist-page"> 2 <div class="companylist-page">
3 <div class="search-input"> 3 <div class="search-input">
4 - <input class="buriedpoint icon" type="text" placeholder="&#xe608;搜索快递公司"> 4 + <input class="icon" type="text" placeholder="&#xe608; 搜索快递公司" v-model="inputname" @input="search">
  5 + </div>
  6 + <div class="company-data">
  7 + <div class="company-item" v-for="item in showData">
  8 + <h2>{{ $key }}</h2>
  9 + <span v-for="val in item" track-by="id" @click="select(val.id, val.company_name)">{{val.company_name}}</span>
  10 + </div>
5 </div> 11 </div>
6 - <ul class="search-associate"></ul>  
7 </div> 12 </div>
8 </template> 13 </template>
9 14
10 <script> 15 <script>
  16 + const $ = require('yoho-jquery');
  17 +
11 module.exports = { 18 module.exports = {
12 data() { 19 data() {
13 return { 20 return {
14 - companyData: {}, 21 + inputname: '',
  22 + data: {},
  23 + showData: {}
15 }; 24 };
16 }, 25 },
17 methods: { 26 methods: {
18 - submit: function(){  
19 - console.log(this.num); 27 + search: function() {
  28 + var inputname = this.inputname;
  29 + if (!inputname) {
  30 + this.showData = this.data;
  31 + return;
  32 + }
  33 +
  34 + var filter = {};
  35 + for (var k in this.data) {
  36 + this.data[k].forEach(function(d){
  37 + if (d.company_name.indexOf(inputname) > -1) {
  38 + if (!filter[k]) filter[k] = [];
  39 + filter[k].push(d);
  40 + }
  41 + })
  42 + }
  43 + this.showData = filter;
  44 + },
  45 + select: function(company_id, company_name) {
20 this.$dispatch('changeView', { 46 this.$dispatch('changeView', {
21 view: 'logistics', 47 view: 'logistics',
22 - company: "aaaf啊啊啊" 48 + company_id: company_id,
  49 + company_name: company_name
23 }); 50 });
  51 +
  52 + // 重置列表
  53 + this.inputname = '';
  54 + this.showData = this.data;
24 } 55 }
25 }, 56 },
26 activate: function(done) { 57 activate: function(done) {
  58 + let _this = this;
27 $.ajax({ 59 $.ajax({
28 url: '/home/refund/companylist' 60 url: '/home/refund/companylist'
29 }).then(function(res) { 61 }).then(function(res) {
@@ -31,9 +63,9 @@ @@ -31,9 +63,9 @@
31 res = {}; 63 res = {};
32 } 64 }
33 if (res.code === 200) { 65 if (res.code === 200) {
34 - 66 + _this.data = res.data;
  67 + _this.showData = res.data;
35 } 68 }
36 -  
37 done(); 69 done();
38 }).fail(function() { 70 }).fail(function() {
39 tip('网络错误'); 71 tip('网络错误');
@@ -2,12 +2,12 @@ @@ -2,12 +2,12 @@
2 <div class="edit-logistics-page"> 2 <div class="edit-logistics-page">
3 <form class="edit-logistics"> 3 <form class="edit-logistics">
4 <label @click="companylist"> 4 <label @click="companylist">
5 - 选择快递公司<input class="company-val" type="text" name="company" value="{{company}}" readonly> 5 + 选择快递公司<input class="company-val" type="text" value="{{company_name}}" readonly>
6 <span class="icon icon-right"></span> 6 <span class="icon icon-right"></span>
7 </label> 7 </label>
8 <label> 8 <label>
9 快递单号 9 快递单号
10 - <input class="num" type="number" name="num" v-model='num'> 10 + <input class="num" type="number" v-model='num'>
11 </label> 11 </label>
12 </form> 12 </form>
13 <div class="submit" @click="submit">确认</div> 13 <div class="submit" @click="submit">确认</div>
@@ -19,7 +19,7 @@ @@ -19,7 +19,7 @@
19 const tip = require('common/tip'); 19 const tip = require('common/tip');
20 20
21 module.exports = { 21 module.exports = {
22 - props: ['company'], 22 + props: ['company_id', 'company_name'],
23 data() { 23 data() {
24 return { 24 return {
25 num: '', 25 num: '',
@@ -32,11 +32,11 @@ @@ -32,11 +32,11 @@
32 }); 32 });
33 }, 33 },
34 submit: function(){ 34 submit: function(){
35 - if (!this.company) { 35 + if (!this.company_name) {
36 tip("请选择快递公司"); 36 tip("请选择快递公司");
37 return false; 37 return false;
38 } 38 }
39 - if (!/^[0-9]*$/.test(this.num)) { 39 + if (!this.num || !/^[0-9]*$/.test(this.num)) {
40 tip("请输入正确的快递单号"); 40 tip("请输入正确的快递单号");
41 return false; 41 return false;
42 } 42 }
@@ -45,8 +45,9 @@ @@ -45,8 +45,9 @@
45 method: 'POST', 45 method: 'POST',
46 url: '/home/save-logistics', 46 url: '/home/save-logistics',
47 data: { 47 data: {
48 - company: company,  
49 - num: num 48 + company_id: this.company_id,
  49 + company_name: this.company_name,
  50 + num: this.num
50 } 51 }
51 }).then(function(res) { 52 }).then(function(res) {
52 if ($.type(res) !== 'object') { 53 if ($.type(res) !== 'object') {
@@ -37,8 +37,7 @@ @@ -37,8 +37,7 @@
37 } 37 }
38 38
39 .product-list { 39 .product-list {
40 - margin-bottom: 30px;  
41 - background: #fff; 40 + margin-top: -4px;
42 border-top: 1px solid #eee; 41 border-top: 1px solid #eee;
43 border-bottom: 1px solid #eee; 42 border-bottom: 1px solid #eee;
44 } 43 }
@@ -42,6 +42,7 @@ @@ -42,6 +42,7 @@
42 height: 170px; 42 height: 170px;
43 font-size: 24px; 43 font-size: 24px;
44 line-height: 1.5; 44 line-height: 1.5;
  45 + background: #fff;
45 46
46 &:after { 47 &:after {
47 content: ""; 48 content: "";
@@ -51,6 +52,7 @@ @@ -51,6 +52,7 @@
51 width: 690px; 52 width: 690px;
52 height: 0; 53 height: 0;
53 border-bottom: 1px solid #eee; 54 border-bottom: 1px solid #eee;
  55 + z-index: 1;
54 } 56 }
55 57
56 .checkbox { 58 .checkbox {
@@ -12,10 +12,35 @@ @@ -12,10 +12,35 @@
12 <option v-for="reason in returnReason" v-bind:value="reason.id" selected="{{reason.id === product.reason.id}}">{{reason.name}}</option> 12 <option v-for="reason in returnReason" v-bind:value="reason.id" selected="{{reason.id === product.reason.id}}">{{reason.name}}</option>
13 </select> 13 </select>
14 </div> 14 </div>
  15 + <div class="remark">
  16 + <textarea v-model="product.remark" rows="3" max-length="100" placeholder="退货原因说明"></textarea>
  17 + </div>
  18 + <div class="image-list clearfix">
  19 + <div class="image-item" v-for="image in imageListForShow">
  20 + <span v-on:click="deleteImage(image.index)" class="icon icon-close"></span>
  21 + <img v-bind:src="image.path">
  22 + </div>
  23 + <upload v-show="imageListForShow.length < 4" class="image-item" v-bind:image-list="product.imageList"></upload>
  24 + </div>
15 </div> 25 </div>
16 </template> 26 </template>
17 27
18 <script> 28 <script>
  29 + const upload = require('component/tool/upload.vue');
  30 +
  31 + const getImgHost = function(url) {
  32 + let urlArr = url.split('/'),
  33 + num = urlArr[urlArr.length - 1].substr(1, 1),
  34 + domain = 'static.yhbimg.com/goodsimg';
  35 +
  36 + url = domain + url;
  37 + if (num === '1') {
  38 + return '//img11.' + url;
  39 + } else {
  40 + return '//img12.' + url;
  41 + }
  42 + };
  43 +
19 module.exports = { 44 module.exports = {
20 props: ['product', 'refundData'], 45 props: ['product', 'refundData'],
21 computed: { 46 computed: {
@@ -24,25 +49,54 @@ @@ -24,25 +49,54 @@
24 id: 0, 49 id: 0,
25 name: '请选择' 50 name: '请选择'
26 }].concat(this.refundData.returnReason); 51 }].concat(this.refundData.returnReason);
  52 + },
  53 + imageListForShow() {
  54 + const list = [];
  55 +
  56 + this.product.imageList.forEach((path, index) => {
  57 + list.push({
  58 + index: index,
  59 + path: getImgHost(path) + '?imageView2/2/w/160/h/160'
  60 + });
  61 + });
  62 +
  63 + return list;
27 } 64 }
28 }, 65 },
29 methods: { 66 methods: {
30 showTip() { 67 showTip() {
31 alert(this.refundData.specialNotice.remark); 68 alert(this.refundData.specialNotice.remark);
  69 + },
  70 + deleteImage(index) {
  71 + this.product.imageList.splice(index, 1);
32 } 72 }
  73 + },
  74 + components: {
  75 + upload
33 } 76 }
34 }; 77 };
35 </script> 78 </script>
36 79
37 <style> 80 <style>
38 .reason { 81 .reason {
39 - padding: 0 30px;  
40 font-size: 32px; 82 font-size: 32px;
41 line-height: 90px; 83 line-height: 90px;
  84 + background: #f6f6f6;
  85 +
  86 + &:after {
  87 + content: "";
  88 + display: block;
  89 + width: 100%;
  90 + height: 30px;
  91 + border-top: 1px solid #eee;
  92 + border-bottom: 1px solid #eee;
  93 + }
42 94
43 .tip { 95 .tip {
44 position: relative; 96 position: relative;
  97 + padding: 0 30px;
45 font-size: 26px; 98 font-size: 26px;
  99 + background: #fff;
46 100
47 .icon { 101 .icon {
48 margin-right: 5px; 102 margin-right: 5px;
@@ -61,8 +115,10 @@ @@ -61,8 +115,10 @@
61 115
62 .select-reason { 116 .select-reason {
63 position: relative; 117 position: relative;
  118 + padding: 0 30px;
64 width: 100%; 119 width: 100%;
65 height: 90px; 120 height: 90px;
  121 + background: #fff;
66 122
67 &:after { 123 &:after {
68 content: ""; 124 content: "";
@@ -83,5 +139,73 @@ @@ -83,5 +139,73 @@
83 color: #b0b0b0; 139 color: #b0b0b0;
84 } 140 }
85 } 141 }
  142 +
  143 + .remark {
  144 + margin-top: 20px;
  145 + padding: 0 30px;
  146 + background: #fff;
  147 + border-top: 1px solid #eee;
  148 +
  149 + textarea {
  150 + margin-top: 30px;
  151 + width: 100%;
  152 + font-size: 24px;
  153 + line-height: 40px;
  154 + resize: none;
  155 + }
  156 + }
  157 +
  158 + .image-list {
  159 + padding: 30px;
  160 + background: #fff;
  161 +
  162 + .image-item {
  163 + position: relative;
  164 + float: left;
  165 + margin-right: 20px;
  166 + width: 150px;
  167 + height: 150px;
  168 +
  169 + &:last-child {
  170 + margin-right: 0;
  171 + }
  172 +
  173 + .icon-close {
  174 + position: absolute;
  175 + right: -20px;
  176 + top: -20px;
  177 + font-size: 40px;
  178 + color: #fff;
  179 + background: #b0b0b0;
  180 + border-radius: 100%;
  181 + z-index: 2;
  182 + }
  183 +
  184 + img {
  185 + width: 100%;
  186 + height: 100%;
  187 + }
  188 + }
  189 +
  190 + .label-input {
  191 + position: relative;
  192 + width: 150px;
  193 + height: 150px;
  194 + font-size: 150px;
  195 +
  196 + &:before {
  197 + content: "\e604";
  198 + }
  199 +
  200 + input {
  201 + position: absolute;
  202 + left: 0;
  203 + top: 0;
  204 + width: 0;
  205 + height: 0;
  206 + opacity: 0;
  207 + }
  208 + }
  209 + }
86 } 210 }
87 </style> 211 </style>