Authored by ccbikai

Merge branch 'feature/home' into develop

/**
* sub app shopping
* @author: jinhu.dong<jinhu.dong@yoho.cn>
* @date: 2016/07/04
*/
var express = require('express');
var app = express();
app.on('mount', function(parent) {
delete parent.locals.settings; // 不继承父 App 的设置
Object.assign(app.locals, parent.locals);
});
// router
app.use(require('./router'));
module.exports = app;
... ...
/**
* router of sub app shopping
* @author: jinhu.dong<jinhu.dong@yoho.cn>
* @date: 2016/07/04
*/
'use strict';
const router = require('express').Router(); // eslint-disable-line
var multipart = require('connect-multiparty');
var multipartMiddleware = multipart();
const uploadApi = require('./upload/upload.js');
router.post('/upload/image', multipartMiddleware, uploadApi.uploadImg);
module.exports = router;
... ...
/**
* 上传接口
* @author: kai.bi<kai.bi@yoho.cn>
* @date: 2016/07/22
*/
'use strict';
const request = require('request-promise');
const fs = require('fs');
const _ = require('lodash');
const uploadImg = (req, res, next) => {
let files = req.files && req.files.filename || [];
if (!_.isArray(files)) {
files = [files];
}
req.body.files = [];
files.forEach(file => {
req.body.files.push(fs.createReadStream(file.path));
req.body.files.push(file.name);
});
request({
method: 'post',
url: 'http://upload.static.yohobuy.com',
formData: {
fileData: req.body.files,
project: req.body.bucket || 'goodsimg'
},
json: true
}).then(function(result) {
res.json(result);
}).catch(next);
};
module.exports = {
uploadImg
};
... ...
... ... @@ -40,13 +40,15 @@ const refund = {
}).catch(next);
},
saveLogistics(req, res, next) {
const company = req.body.company;
const company_id = req.body.company_id;
const company_name = req.body.company_name;
const num = req.body.num;
// todo 调用保存物流信息接口
res.json({
code: 200
});
}
};
module.exports = refund;
module.exports = refund;
\ No newline at end of file
... ...
<div class="logistics-page" id="logistics">
<components :is="currentView" :company="company" keep-alive></components>
<components :is="currentView" :company_id="company_id" :company_name="company_name" keep-alive></components>
</div>
\ No newline at end of file
... ...
... ... @@ -15,8 +15,8 @@ module.exports = {
port: 6004,
siteUrl: '//m.yohoblk.com',
domains: {
api: 'http://api.yoho.cn/',
service: 'http://service.yoho.cn/'
api: 'http://192.168.102.202:8080/gateway/',
service: 'http://192.168.102.202:8080/gateway/'
},
subDomains: {
host: '.m.yohoblk.com',
... ...
... ... @@ -7,6 +7,7 @@
module.exports = app => {
app.use('/', require('./apps/channel'));
app.use('/api', require('./apps/api'));
app.use('/product', require('./apps/product'));
app.use('/home', require('./apps/home'));
... ... @@ -14,6 +15,4 @@ module.exports = app => {
if (app.locals.devEnv) {
app.use('/example', require('./apps/example'));
}
app.use('/product', require('./apps/product'));
};
... ...
... ... @@ -22,6 +22,7 @@
"bluebird": "^3.4.1",
"body-parser": "^1.15.2",
"connect-memcached": "^0.2.0",
"connect-multiparty": "^2.0.0",
"cookie-parser": "^1.4.3",
"express": "^4.14.0",
"express-handlebars": "^3.0.0",
... ...
... ... @@ -8,7 +8,8 @@ Vue.use(infiniteScroll);
new Vue({
el: '#logistics',
data: {
company: '',
company_id: '',
company_name: '',
currentView: 'logistics',
},
components: {
... ... @@ -18,7 +19,8 @@ new Vue({
events: {
changeView: function(obj) {
this.currentView = obj.view;
this.company = obj.company;
this.company_id = obj.company_id;
this.company_name = obj.company_name;
}
}
});
\ No newline at end of file
... ...
.logistics-page {
width: 100%;
background: #f0f0f0;
.edit-logistics-page {
width: 100%;
... ... @@ -14,7 +13,6 @@
background: #fff;
font-size: 30px;
line-height: 88px;
border-bottom: 1px solid #e0e0e0;
label {
display: block;
... ... @@ -50,6 +48,7 @@
-webkit-appearance: none;
}
.num {
width: 440px;
text-align: right;
... ... @@ -80,26 +79,50 @@
.search-input {
position: relative;
padding: 14px 22px;
background: #f8f8f8;
.icon {
position: absolute;
font-size: 24px;
top: 26px;
left: 36px;
color: #b2b2b2;
}
text-align: center;
padding: 16px 16px;
border-bottom: 1px solid #e6e6e6;
input {
height: 56px;
width: 378px;
border-radius: 28px;
padding: 0 52px;
font-size: 24px;
background: #fff;
width: 100%;
padding-left: 15px;
border-radius: 20px;
font-size: 36px;
color: #b0b0b0;
background: #eee;
border: none;
}
input::-webkit-input-placeholder { /* WebKit browsers */
text-align: center;
}
input:-ms-input-placeholder { /* Internet Explorer 10+ */
text-align: center;
}
}
.company-data {
color: #000;
margin-left: 30px;
.company-item {
h2 {
height: 50px;
line-height: 50px;
font-size: 30px;
border-bottom: 1px solid #f3f3f3;
}
span {
display: block;
height: 90px;
line-height: 90px;
font-size: 30px;
border-bottom: 1px solid #f3f3f3;
}
}
}
}
}
... ...
<template>
<div class="upload">
<form v-on:change="upload">
<label class="label-input icon" for="{{inputId}}">
<input id="{{inputId}}" type="file" name="filename">
</label>
</form>
</div>
</template>
<script>
module.exports = {
props: ['imageList', 'bucket'],
data() {
return {
inputId: 'input-' + Math.floor(Math.random() * 999999999) // 尽可能保证 input ID 唯一
};
},
methods: {
upload(e) {
const formData = new FormData(e.target.closest('form'));
formData.append('bucket', this.bucket || '');
$.ajax({
method: 'POST',
url: '/api/upload/image',
data: formData,
processData: false, // 告诉jQuery不要去处理发送的数据
contentType: false // 告诉jQuery不要去设置Content-Type请求头
}).then(res => {
e.target.value = '';
if (res.code === 200) {
res.data.imagesList.forEach(imagesPath => {
this.imageList.push(imagesPath);
});
} else {
alert(res.message);
}
});
}
}
};
</script>
<style>
/* 每个地方上传按钮可能不一样,使用的时候自己写样式 */
</style>
... ...
... ... @@ -167,6 +167,7 @@
.refund {
.return-amount {
margin: 30px 0;
padding: 0 30px;
font-size: 32px;
line-height: 90px;
... ...
<template>
<div class="companylist-page">
<div class="search-input">
<input class="buriedpoint icon" type="text" placeholder="&#xe608;搜索快递公司">
<input class="icon" type="text" placeholder="&#xe608; 搜索快递公司" v-model="inputname" @input="search">
</div>
<div class="company-data">
<div class="company-item" v-for="item in showData">
<h2>{{ $key }}</h2>
<span v-for="val in item" track-by="id" @click="select(val.id, val.company_name)">{{val.company_name}}</span>
</div>
</div>
<ul class="search-associate"></ul>
</div>
</template>
<script>
const $ = require('yoho-jquery');
module.exports = {
data() {
return {
companyData: {},
inputname: '',
data: {},
showData: {}
};
},
methods: {
submit: function(){
console.log(this.num);
search: function() {
var inputname = this.inputname;
if (!inputname) {
this.showData = this.data;
return;
}
var filter = {};
for (var k in this.data) {
this.data[k].forEach(function(d){
if (d.company_name.indexOf(inputname) > -1) {
if (!filter[k]) filter[k] = [];
filter[k].push(d);
}
})
}
this.showData = filter;
},
select: function(company_id, company_name) {
this.$dispatch('changeView', {
view: 'logistics',
company: "aaaf啊啊啊"
company_id: company_id,
company_name: company_name
});
// 重置列表
this.inputname = '';
this.showData = this.data;
}
},
activate: function(done) {
let _this = this;
$.ajax({
url: '/home/refund/companylist'
}).then(function(res) {
... ... @@ -31,9 +63,9 @@
res = {};
}
if (res.code === 200) {
_this.data = res.data;
_this.showData = res.data;
}
done();
}).fail(function() {
tip('网络错误');
... ...
... ... @@ -2,12 +2,12 @@
<div class="edit-logistics-page">
<form class="edit-logistics">
<label @click="companylist">
选择快递公司<input class="company-val" type="text" name="company" value="{{company}}" readonly>
选择快递公司<input class="company-val" type="text" value="{{company_name}}" readonly>
<span class="icon icon-right"></span>
</label>
<label>
快递单号
<input class="num" type="number" name="num" v-model='num'>
<input class="num" type="number" v-model='num'>
</label>
</form>
<div class="submit" @click="submit">确认</div>
... ... @@ -19,7 +19,7 @@
const tip = require('common/tip');
module.exports = {
props: ['company'],
props: ['company_id', 'company_name'],
data() {
return {
num: '',
... ... @@ -32,11 +32,11 @@
});
},
submit: function(){
if (!this.company) {
if (!this.company_name) {
tip("请选择快递公司");
return false;
}
if (!/^[0-9]*$/.test(this.num)) {
if (!this.num || !/^[0-9]*$/.test(this.num)) {
tip("请输入正确的快递单号");
return false;
}
... ... @@ -45,8 +45,9 @@
method: 'POST',
url: '/home/save-logistics',
data: {
company: company,
num: num
company_id: this.company_id,
company_name: this.company_name,
num: this.num
}
}).then(function(res) {
if ($.type(res) !== 'object') {
... ...
... ... @@ -37,8 +37,7 @@
}
.product-list {
margin-bottom: 30px;
background: #fff;
margin-top: -4px;
border-top: 1px solid #eee;
border-bottom: 1px solid #eee;
}
... ...
... ... @@ -42,6 +42,7 @@
height: 170px;
font-size: 24px;
line-height: 1.5;
background: #fff;
&:after {
content: "";
... ... @@ -51,6 +52,7 @@
width: 690px;
height: 0;
border-bottom: 1px solid #eee;
z-index: 1;
}
.checkbox {
... ...
... ... @@ -12,10 +12,35 @@
<option v-for="reason in returnReason" v-bind:value="reason.id" selected="{{reason.id === product.reason.id}}">{{reason.name}}</option>
</select>
</div>
<div class="remark">
<textarea v-model="product.remark" rows="3" max-length="100" placeholder="退货原因说明"></textarea>
</div>
<div class="image-list clearfix">
<div class="image-item" v-for="image in imageListForShow">
<span v-on:click="deleteImage(image.index)" class="icon icon-close"></span>
<img v-bind:src="image.path">
</div>
<upload v-show="imageListForShow.length < 4" class="image-item" v-bind:image-list="product.imageList"></upload>
</div>
</div>
</template>
<script>
const upload = require('component/tool/upload.vue');
const getImgHost = function(url) {
let urlArr = url.split('/'),
num = urlArr[urlArr.length - 1].substr(1, 1),
domain = 'static.yhbimg.com/goodsimg';
url = domain + url;
if (num === '1') {
return '//img11.' + url;
} else {
return '//img12.' + url;
}
};
module.exports = {
props: ['product', 'refundData'],
computed: {
... ... @@ -24,25 +49,54 @@
id: 0,
name: '请选择'
}].concat(this.refundData.returnReason);
},
imageListForShow() {
const list = [];
this.product.imageList.forEach((path, index) => {
list.push({
index: index,
path: getImgHost(path) + '?imageView2/2/w/160/h/160'
});
});
return list;
}
},
methods: {
showTip() {
alert(this.refundData.specialNotice.remark);
},
deleteImage(index) {
this.product.imageList.splice(index, 1);
}
},
components: {
upload
}
};
</script>
<style>
.reason {
padding: 0 30px;
font-size: 32px;
line-height: 90px;
background: #f6f6f6;
&:after {
content: "";
display: block;
width: 100%;
height: 30px;
border-top: 1px solid #eee;
border-bottom: 1px solid #eee;
}
.tip {
position: relative;
padding: 0 30px;
font-size: 26px;
background: #fff;
.icon {
margin-right: 5px;
... ... @@ -61,8 +115,10 @@
.select-reason {
position: relative;
padding: 0 30px;
width: 100%;
height: 90px;
background: #fff;
&:after {
content: "";
... ... @@ -83,5 +139,73 @@
color: #b0b0b0;
}
}
.remark {
margin-top: 20px;
padding: 0 30px;
background: #fff;
border-top: 1px solid #eee;
textarea {
margin-top: 30px;
width: 100%;
font-size: 24px;
line-height: 40px;
resize: none;
}
}
.image-list {
padding: 30px;
background: #fff;
.image-item {
position: relative;
float: left;
margin-right: 20px;
width: 150px;
height: 150px;
&:last-child {
margin-right: 0;
}
.icon-close {
position: absolute;
right: -20px;
top: -20px;
font-size: 40px;
color: #fff;
background: #b0b0b0;
border-radius: 100%;
z-index: 2;
}
img {
width: 100%;
height: 100%;
}
}
.label-input {
position: relative;
width: 150px;
height: 150px;
font-size: 150px;
&:before {
content: "\e604";
}
input {
position: absolute;
left: 0;
top: 0;
width: 0;
height: 0;
opacity: 0;
}
}
}
}
</style>
... ...