Showing 44 changed files with 1717 additions and 11 deletions
import Vue from 'vue';
import {get} from 'lodash';
import App from './app.vue';
import {createRouter} from './router';
import {createStore} from './store';
... ... @@ -8,6 +9,7 @@ import titleMixin from './mixins/title';
import pluginCore from './plugins/core';
import lazyload from 'vue-lazyload';
import reportError from 'report-error';
import ReportApp from './common/report-app';
Vue.use(lazyload, {
preLoad: 2
... ... @@ -15,11 +17,12 @@ Vue.use(lazyload, {
Vue.use(pluginCore);
Vue.mixin(titleMixin);
export function createApp(context) {
const router = createRouter();
const store = createStore(context);
const reportApp = new ReportApp(store.$context.env);
const app = new Vue({
router,
store,
... ... @@ -27,6 +30,38 @@ export function createApp(context) {
reportError(context, 'server')(error);
return false;
},
methods: {
getAnalyticAppData() {
return new Promise(resolve => {
if (this._updated) {
return resolve();
}
this.$yoho.getAnalyticAppData('', data => {
this._updated = true;
reportApp.updateDeviceInfo(data);
resolve();
});
setTimeout(function () {
resolve();
}, 500);
});
},
async reportApp(type, pn, params = {}, pt) {
let user = await this.$sdk.getUser();
await this.getAnalyticAppData();
if (!params.mst) {
params.mst = get(this.$router, 'history.current.name', '');
}
reportApp.report(type, pt || 'BUSINESS', pn, params, get(user, 'uid'));
},
reportAppStart() {
this.reportApp('', 'BUSINESS_PLAN_A_ENTER', {locfun: 'mounted'});
}
},
render: h => h(App)
});
... ...
... ... @@ -64,12 +64,17 @@ export default {
transform: translate3d(100%, 0, 0);
}
.route-view-forword-enter-active {
z-index: 2;
}
.route-view-forword-leave-active {
z-index: -1;
transform: translate3d(-30%, 0, 0);
}
.route-view-back-enter {
z-index: 1;
z-index: -1;
transform: translate3d(-30%, 0, 0);
}
... ...
import axios from 'axios';
import md5 from 'md5';
export default class reportApp {
constructor(params = {}) {
this.isProd = !params.unProd;
this.events = {
ps: '0',
av: params.version,
ab: params.buildId,
ca: '0',
net: 'unknown',
sid: md5(`${params.udid}_${new Date().getTime()}`)
};
this.params = {
mst: '',
st: '',
ec: '',
ei: '',
url: '',
host: '',
locfun: ''
};
this.device = {
ak: `yohobuy_${params.isiOS ? 'ios' : 'android'}${params.isApp ? '' : '_h5'}`,
udid: params.udid || '',
ch: '',
os: params.isiOS ? 'IOS' : 'Android',
osv: params.osVersion,
dm: '',
};
}
updateDeviceInfo(info = {}) {
info.ch && (this.device.ch = info.ch);
info.dm && (this.device.dm = info.dm);
info.udid && (this.device.udid = info.udid);
info.av && (this.events.av = info.av);
info.ab && (this.events.ab = info.ab);
info.sid && (this.events.sid = info.sid);
}
report(type, pt, pn, params = {}, uid = 0) {
if (!this.isProd) {
return;
}
params = Object.assign({}, this.params, params);
if (window) {
Object.assign(params, {
host: window.location.host,
url: window.location.href
});
}
axios({
baseURL: '//app.yoho.cn/collect/v3',
url: '',
method: 'POST',
data: {
type: '',
device: this.device,
events: [
Object.assign({
uid: md5(`${uid}`),
ts: new Date().getTime() + '000000',
pt: pt,
pn: pn,
param: params
}, this.events)
]
}
})
}
}
... ...
... ... @@ -576,6 +576,18 @@ const yoho = {
}
},
getAnalyticAppData(args, success, fail) {
if (this.isYohoBuy && window.yohoInterface) {
window.yohoInterface.triggerEvent(success || nullFun, fail || nullFun, {
method: 'get.analyticAppData',
arguments: args
});
} else {
// tip(tipInfo);
}
},
setWebview(args, success, fail) {
if (this.isYohoBuy && window.yohoInterface) {
window.yohoInterface.triggerEvent(success || nullFun, fail || nullFun, {
... ... @@ -583,6 +595,7 @@ const yoho = {
arguments: args
});
} else {
// tip(tipInfo);
}
}
... ...
... ... @@ -4,7 +4,7 @@
<meta charset="utf-8">
<title>{{title}}</title>
<meta name="keywords" content="">
<meta name="description" content="">
<meta name="description" content="">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no">
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<meta content="yes" name="apple-mobile-web-app-capable">
... ...
<template>
<div class="header">
<div class="back-wrapper" @touchend="onBack">
<div class="back-wrapper flex" @touchend="onBack">
<div class="back"></div>
</div>
<div class="title flex" :style="{opacity}">
<slot>
<span class="title-inner">{{title}}</span>
</slot>
</div>
<div class="opts flex" :style="{opacity}">
<slot name="opts"></slot>
</div>
</div>
</template>
<script>
import {mapState} from 'vuex';
export default {
name: 'HeaderUfo',
props: {
title: String,
opacity: {
type: Number,
default: 1
}
},
computed: {
...mapState(['yoho'])
},
methods: {
onBack() {
this.$yoho.finishPage({});
if (this.yoho.homePage) {
this.$yoho.finishPage({});
} else {
this.$router.go(-1);
}
}
}
};
... ... @@ -24,10 +48,16 @@ export default {
padding-left: 20PX;
padding-right: 20PX;
background-color: #fff;
display: flex;
align-items: stretch;
box-sizing: border-box;
.back-wrapper {
.flex {
display: flex;
align-items: center;
}
.back-wrapper {
height: 100%;
width: 60PX;
}
... ... @@ -38,5 +68,29 @@ export default {
background: url(~statics/image/order/back@3x.png) no-repeat;
background-size: cover;
}
.title {
flex: 1;
justify-content: center;
font-size: 18PX;
letter-spacing: 0.09PX;
overflow: hidden;
z-index: 1;
.title-inner {
max-width: 90%;
line-height: 1.4;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
display: inline-block;
}
}
.opts {
width: 60PX;
height: inherit;
justify-content: flex-end;
}
}
</style>
... ...
<template>
<div class="layout">
<LayoutHeader class="layout-header"></LayoutHeader>
<LayoutHeader class="layout-header" :title="title" :opacity="opacity"></LayoutHeader>
<div class="layout-context" :class="{fixscroll: this.fixscroll}">
<slot></slot>
</div>
... ... @@ -12,7 +12,17 @@ import LayoutHeader from './header-ufo';
export default {
name: 'LayoutApp',
props: ['fixscroll'],
props: {
title: String,
opacity: {
type: Number,
default: 1
},
fixscroll: {
type: Boolean,
default: false
}
},
components: {
LayoutHeader
}
... ...
... ... @@ -2,5 +2,7 @@ import Order from './order';
import license from './license';
import alipay from './alipay';
import selfUfo from './selfUfo';
import mine from './mine';
import invite from './invite';
export default [...Order, ...license, ...alipay, ...selfUfo];
export default [...Order, ...license, ...alipay, ...selfUfo, ...mine, ...invite];
... ...
<template>
<div class="desc-wrapper">
<div class="title-wrapper">
<TitleComp txt="活动说明"></TitleComp>
</div>
<p class="desc">
1. 活动时间:2019/4/10 00:00:00-2019/4/25 23:59:59 <br/>
2. 此活动只针对全新鞋类订单发放奖励金(瑕疵品,二手交易订单除外)<br/>
3. 活动期间内,通过邀请新卖家入驻UFO飞碟好物平台,进行全新鞋类商品发售,在新卖家订单交易完成后,即可获得现金奖励。 <br/>
4. 邀请成功后,自新入驻卖家入驻成功之日起30天内,均可获得新卖家活动范围内鞋类订单每单10元的奖励金。 <br/>
5. 新入驻卖家在首次入驻UFO飞碟好物平台时,正确填写邀请码,即与邀请人绑定,绑定关系不可重复及更改。 <br/>
6. 绑定关系建立后,一旦退出UFO飞碟好物平台, 即使在活动期间内再次入驻,都将不再享有新入驻卖家订单的奖励金。 <br/>
7. 活动期间,已经入驻UFO飞碟好物平台的卖家,退出UFO飞碟好物平台后,再次入驻无法参加本次活动。 <br/>
8. 当月的奖励金在该月最后一个工作日结算,次月15日进行发放。
</p>
</div>
</template>
<script>
import TitleComp from '../detail/components/title';
export default {
name: 'DescPage',
components: {
TitleComp
}
};
</script>
<style lang="scss" scoped>
.desc-wrapper {
text-align: center;
margin-bottom: 40px;
}
.title-wrapper {
text-align: left;
margin-bottom: 60px;
}
.desc {
text-align: left;
font-size: 24px;
color: #94b0c4;
line-height: 50px;
}
</style>
... ...
<template>
<div class="wrapper">
<div class="title-wrapper">
<TitleComp txt="我的收款银行"></TitleComp>
</div>
<template v-if="!card">
<div class="item">
<span>您还未绑定银行卡,将无法收款</span>
<span class="link" @click="goCardAdd">添加银行卡</span>
</div>
</template>
<template v-else>
<div class="item">
<span>您已绑定银行卡</span>
<span class="link" @click="goCardList">查看银行卡</span>
</div>
</template>
</div>
</template>
<script>
import TitleComp from './title';
export default {
name: 'BankCardStatus',
components: {
TitleComp
},
props: {
card: {
type: Boolean,
default: false
}
},
methods: {
goCardList() {
this.$router.push({
name: 'bankcard'
});
},
goCardAdd() {
this.$router.push({
name: 'bankcard.add'
});
}
}
};
</script>
<style lang="scss" scoped>
.item {
text-align: left;
font-size: 24px;
margin-bottom: 20px;
color: white;
}
.title-wrapper {
text-align: left;
margin-bottom: 60px;
}
.btn {
display: inline-block;
width: 150px;
height: 50px;
line-height: 50px;
border: 1px solid black;
}
.link {
color: #64ad88;
}
</style>
... ...
<template>
<div class="bg">
</div>
</template>
<script>
export default {
name: 'Banner'
};
</script>
<style lang="scss" scoped>
.bg {
height: 620px;
background: url("~statics/image/invite/banner.jpg");
background-size: cover;
}
</style>
... ...
<template>
<div class="wrapper">
<div class="title-wrapper">
<TitleComp txt="我邀请的好友"></TitleComp>
</div>
<table class="table">
<thead class="header">
<tr>
<th width="28%" align="left">昵称</th>
<th width="60%" align="center">入驻时间</th>
<th width="12%" align="center">订单数</th>
</tr>
</thead>
<tbody class="tbody">
<tr v-for="item in list">
<td align="left">
<span class="nick-name">{{item.nickName}}</span>
</td>
<td align="center">
<span>{{item.enterTime}}</span>
</td>
<td align="center">
<span>{{item.orderNum}}</span>
</td>
</tr>
</tbody>
</table>
</div>
</template>
<script>
import TitleComp from './title';
export default {
name: 'FriendList',
components: {
TitleComp
},
props: {
list: {
type: Array,
default() {
return [];
}
}
}
};
</script>
<style lang="scss" scoped>
.wrapper {
text-align: center;
color: white;
}
.title-wrapper {
text-align: left;
margin-bottom: 60px;
}
.table {
margin-top: 20px;
width: 100%;
font-size: 24px;
th {
font-weight: bold;
color: white;
}
tbody {
tr {
height: 110px;
border-bottom: 1px solid rgba(255, 255, 255, 0.09);
span {
color: #94b0c4;
display: block;
height: 110px;
line-height: 110px;
}
}
}
}
.nick-name {
width: 150px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
</style>
... ...
<template>
<div class="wrapper">
<template v-if="data.showInviteCode">
<div class="item title mg4">我的邀请码</div>
<div class="item code mg3">{{data.showInviteCode}}</div>
<div class="item mg2">
<div class="btn copy">
复制
</div>
</div>
<div class="item count-wrapper">
<div class="count-item">
<div class="count">{{data.inviteeUidNum}}</div>
<div class="tip">已邀请好友数</div>
</div>
<div class="count-item">
<div class="count">{{data.finishedOrderNum}}</div>
<div class="tip">好友完成订单数</div>
</div>
</div>
</template>
<template v-else>
<div class="item mg5">
<div class="tip2">您还未入驻</div>
<div class="tip2">请先入驻获得邀请码</div>
</div>
<div class="item mg2">
<div class="btn" @click="onClick">
立即入驻
</div>
</div>
</template>
</div>
</template>
<script>
import ClipboardJS from 'clipboard';
import {get} from 'lodash';
export default {
name: 'InviteCode',
props: {
data: {
type: Object,
default() {
return {};
}
}
},
mounted() {
let vm = this;
this.clipboard = new ClipboardJS(this.$el.getElementsByClassName('copy'), {
text: () => {
return this.data.showInviteCode;
}
});
this.clipboard.on('success', function() {
vm.$createToast({
type: 'text',
txt: '复制成功'
}).show();
});
this.clipboard.on('error', function() {
vm.$createToast({
type: 'text',
txt: '复制失败'
}).show();
});
},
methods: {
async onClick() {
const result = await this.$store.dispatch('license/form/getStoreStatus', null, { root: true});
if (result.data === true || get(result, 'data.isStoredSeller', false)) {
this.$yoho.goPage('go.ufo', {
pagename: 'merchantsSettled',
isnavhidden: 1,
disabledSwiper: 1
});
} else if (result.data.storedBefore) {
this.$createToast({
txt: '您已退出入驻,没有入驻资格',
type: 'warn',
}).show();
} else {
this.$yoho.goPage('go.ufo', { pagename: 'MerchantEntry' });
}
this.$root.reportApp('', 'BUSINESS_PLAN_A_EVENT', {
locfun: 'click:storeNow'
});
}
}
};
</script>
<style lang="scss" scoped>
.wrapper {
width: 650px;
height: 504px;
text-align: center;
overflow: hidden;
background: url("~statics/image/invite/invite_bg@3x.png");
background-size: cover;
}
.item {
margin-top: 40px;
}
.title {
height: 40px;
line-height: 40px;
color: black;
font-size: 28px;
font-weight: bold;
}
.code {
height: 94px;
color: black;
font-weight: bolder;
font-size: 80px;
letter-spacing: 4px;
line-height: 94px;
}
.mg2 {
margin-top: 20px;
}
.mg3 {
margin-top: 12px;
}
.mg4 {
margin-top: 84px;
}
.mg5 {
margin-top: 116px;
margin-bottom: 54px;
}
.btn {
display: inline-block;
width: 464px;
height: 100px;
color: white;
background-color: #08304b;
font-size: 28px;
line-height: 100px;
}
.count-wrapper {
display: flex;
overflow: hidden;
margin: 40px auto;
width: 550px;
justify-content: space-around;
}
.count-item {
width: 232px;
height: 80px;
}
.count {
font-size: 28px;
height: 40px;
line-height: 40px;
}
.tip {
font-size: 22px;
color: #999;
height: 32px;
line-height: 32px;
}
.tip2 {
font-size: 40px;
font-weight: bold;
margin-bottom: 12px;
}
</style>
... ...
<template>
<div class="record-wrapper">
<div class="title-wrapper" style="display: flex; justify-content: space-between; align-items: baseline;">
<TitleComp txt="我的收款记录"></TitleComp>
<span class="font" style="display: inline-block; color: white;">每月15日发放上一周期的佣金</span>
</div>
<template v-if="list.length > 0">
<table class="table">
<thead class="header">
<tr>
<th width="35%" align="left">处理流水</th>
<th width="50%" align="center">收款时间</th>
<th width="15%" align="left">交易金额</th>
</tr>
</thead>
<tbody class="tbody">
<tr v-for="item in list" v-if="list.length > 0">
<td align="left">
<span>{{item.settleCode}}</span>
</td>
<td align="center">
<span>{{item.settleTime}}</span>
</td>
<td align="center">
<span style="color: #5ba082;">{{item.settleAmount}}</span>
</td>
</tr>
</tbody>
</table>
</template>
<template v-else>
<span>当前暂无收款记录</span>
</template>
</div>
</template>
<script>
import TitleComp from './title';
export default {
name: 'RecordList',
components: {
TitleComp
},
props: {
list: {
type: Array,
default() {
return [];
}
}
}
};
</script>
<style lang="scss" scoped>
.record-wrapper {
text-align: left !important;
color: white;
}
.title-wrapper {
margin-bottom: 60px;
.font {
font-size: 24px;
}
}
.tip {
display: flex;
justify-content: space-between;
}
.table {
margin-top: 20px;
width: 100%;
font-size: 24px;
th {
font-weight: bold;
color: white;
}
tbody {
tr {
height: 110px;
border-bottom: 1px solid rgba(255, 255, 255, 0.09);
span {
color: #94b0c4;
display: inline-block;
height: 110px;
line-height: 110px;
}
}
}
}
</style>
... ...
<template>
<div class="title">{{txt}}</div>
</template>
<script>
export default {
name: 'Title',
props: ['txt']
};
</script>
<style lang="scss" scoped>
.title {
display: inline-block;
height: 80px;
line-height: 80px;
font-size: 40px;
font-weight: bold;
color: white;
&:before {
content: "\00a0";
position: absolute;
display: inline-block;
width: 32px;
border-bottom: 6px solid #64ad88;
}
}
</style>
... ...
<template>
<div>
<BannerPage></BannerPage>
<div class="body-wrapper">
<InviteCode class="mg" :data="inviteCode"></InviteCode>
<FriendList class="mg" :list="inviteCode.inviteRecordList" v-if="inviteCode.inviteRecordList.length > 0"></FriendList>
<BankStatus class="mg" :card="getBandLength"></BankStatus>
<RecordList class="mg" :list="recordList"></RecordList>
<DescPage class="mg"></DescPage>
</div>
</div>
</template>
<script>
import InviteCode from './components/invite-code';
import FriendList from './components/friend-list';
import BankStatus from './components/bankcard-status';
import RecordList from './components/record-list';
import DescPage from '../components/desc';
import BannerPage from './components/banner';
import {createNamespacedHelpers} from 'vuex';
const {mapState, mapGetters} = createNamespacedHelpers('invite/invite');
export default {
name: 'detailPage',
computed: {
...mapState(['recordList', 'inviteCode']),
...mapGetters(['getBandLength'])
},
data() {
return {};
},
mounted() {
this.$store.dispatch('mine/bankCard/fetchBankCard', null, { root: true });
},
components: {
InviteCode,
FriendList,
BankStatus,
RecordList,
DescPage,
BannerPage
}
};
</script>
<style lang="scss" scoped>
.body-wrapper {
padding: 0 20PX;
}
.mg {
margin: 0 auto;
margin-bottom: 160px;
text-align: center;
&:last-child {
margin-bottom: 0;
padding-bottom: 120px;
}
}
</style>
... ...
export default [{
path: '/mapp/invite/detail.html',
name: 'invitePage',
component: () => import(/* webpackChunkName: "invite" */ './invite'),
meta: {
keepAlive: true
}
}];
... ...
<template>
<LayoutApp :title="title">
<Scroll class="scroll-wrapper" :options="options">
<DetailPage v-if="status"></DetailPage>
<WelcomePage v-else></WelcomePage>
</Scroll>
</LayoutApp>
</template>
<script>
import LayoutApp from '../components/layout/layout-app';
import WelcomePage from './welcome/welcome';
import DetailPage from './detail/detail';
import {Scroll} from 'cube-ui';
import {createNamespacedHelpers} from 'vuex';
const {mapState} = createNamespacedHelpers('invite/invite');
export default {
name: 'InvitePage',
async asyncData({store}) {
const {status} = await store.dispatch('invite/invite/fetchStatus');
if (status) {
await store.dispatch('invite/invite/fetchAll');
}
},
mounted() {
this.$root.reportAppStart();
},
data() {
return {
title: '卖家邀新返利',
options: {
bounce: false
}
};
},
computed: {
...mapState(['status'])
},
components: {
LayoutApp,
WelcomePage,
DetailPage,
Scroll
}
};
</script>
<style lang="scss" scoped>
.scroll-wrapper {
background-color: #08304b;
}
</style>
... ...
<template>
<div>
<BannerPage></BannerPage>
<div class="body-wrapper">
<InviteCode class="mg"></InviteCode>
<DescPage class="mg"></DescPage>
</div>
</div>
</template>
<script>
import BannerPage from '../detail/components/banner';
import DescPage from '../components/desc';
import InviteCode from '../detail/components/invite-code';
export default {
name: 'welcomePage',
components: {
BannerPage,
DescPage,
InviteCode
},
methods: {
}
};
</script>
<style lang="scss" scoped>
.body-wrapper {
padding: 0 20PX;
}
.tip {
font-weight: bold;
font-size: 40px;
}
.btn {
display: inline-block;
width: 200px;
height: 60px;
background-color: red;
color: white;
font-size: 20px;
line-height: 60px;
}
.mg {
margin: 0 auto;
margin-bottom: 160px;
text-align: center;
&:last-child {
margin-bottom: 0;
padding-bottom: 120px;
}
}
</style>
... ...
<template>
<LayoutApp :title="title">
<Cards v-if="bankCardList.length" ref="cards"></Cards>
<div v-else>
<p class="mian-tip">请添加持卡人本人银行卡</p>
<div class="form-wrap">
<div v-for="(item, index) in formList" :key="index" class="form-row">
<text class="cell-left">{{item.title}}</text>
<input class="cell-right" :placeholder="item.placeholder" :readonly="isReadonly(item)" :value="formValue[index]" @input="inputChange(index, $event)"/>
<div v-if="item.select" class="tap-act">
<Select
:options="selectOptions[item.select]"
:placeholder="item.placeholder"
@change="selectChange(index, $event)"
class="tab-go">
</Select>
</div>
</div>
</div>
<p class="error-tip">{{errorTip}}</p>
<div class="option-wrap">
<button class="save-btn" @click="checkBankCardInfo">确定</button>
</div>
</div>
</LayoutApp>
</template>
<script>
import { createNamespacedHelpers } from 'vuex';
import LayoutApp from '../../components/layout/layout-app';
import Cards from './components/cards';
import {Select} from 'cube-ui';
const {mapState, mapActions} = createNamespacedHelpers('mine/bankCard');
export default {
name: 'cardAdd',
title: '',
data() {
return {
title: '我的银行卡',
formList: [
{
title: '持卡人',
placeholder: '请输入持卡人姓名',
type: 'name',
errorTip: '您输入的姓名不符合规范,请重新输入',
regx: /^([\u4e00-\u9fa5]|[A-Za-z]){2,}$/
},
{
title: '身份证号',
placeholder: '请输入身份证号',
type: 'idCardNo',
errorTip: '您输入的身份证号码不符合规范,请重新输入',
regx: /^(\d|X|x){15,18}$/
},
{
title: '开户行',
placeholder: '请选择开户行',
type: 'bankCode',
errorTip: '请选择开户行',
select: 'bankList',
},
{
title: '分行 支行',
placeholder: '例如:南京分行雨花支行',
type: 'bankBranch',
errorTip: '请输入正确的分行和支行信息'
},
{
title: '银行卡号',
placeholder: '请输入银行卡号',
type: 'bankCardNo',
errorTip: '您输入的银行卡号不符合规范,请重新输入',
regx: /^\d{10,22}$/
},
],
formValue: [],
errorTip: '',
};
},
mounted() {
if (!this.banks.length) {
this.fetchBankList();
}
this.$root.reportAppStart();
},
computed: {
...mapState(['banks', 'bankCardList']),
selectOptions() {
let bankList = [];
this._bankObj = {};
if (this.banks && this.banks.length) {
this.banks.forEach(val => {
this._bankObj[val.bankCode] = val.bankName;
bankList.push({
text: val.bankName,
value: val.bankCode
});
});
}
return {bankList};
}
},
methods: {
...mapActions(['fetchBankCard', 'fetchBankList', 'checkBankCard', 'bindBankCard']),
isReadonly(item) {
return !!item.select;
},
inputChange(index, e) {
this.changeFormValue(index, e.target.value);
},
selectChange(index, val) {
let name = this._bankObj[val] || '';
this.changeFormValue(index, {
name,
value: val,
toString() {
return this.name
}
});
},
changeFormValue(index, val) {
let max = Math.max(index, this.formValue.length);
let formValue = [];
this.errorTip = '';
for (let i = 0; i <= max; i++) {
if (i === index) {
formValue[i] = val || '';
} else {
formValue[i] = this.formValue[i] || '';
}
}
this.formValue = formValue;
},
checkBankCardInfo() {
let errorTip = '';
let data = {};
this.formList.some((val, index) => {
if (errorTip) {
return true;
}
let input = this.formValue[index] || '';
let value = input.value || input || '';
if (val.regx) {
if (!val.regx.test(value)) {
errorTip = val.errorTip;
}
} else if (!value) {
errorTip = val.errorTip;
}
data[val.type] = value;
});
if (errorTip) {
this.errorTip = errorTip;
} else {
this.checkBankCard(data).then(res => {
if (res.code === 200) {
this.bindBankCardInfo(data);
} else {
this.errorTip = res.message || '信息有误或网络异常,请稍后重试';
}
});
}
},
bindBankCardInfo(info) {
this.$root.reportApp('', 'BUSINESS_PLAN_A_EVENT', {
locfun: 'click:bindBankCard'
});
let confirm = this.$createDialog({
type: 'confirm',
title: '提示',
content: `请确认您填写的银行信息,提交后只能致电客服修改<br><br>持卡人: ${info.name}<br>身份证号: ${info.idCardNo || ''}<br>银行卡号: ${info.bankCardNo || ''}`,
confirmBtn: {
text: '提交',
active: true,
},
cancelBtn: {
text: '取消',
},
onConfirm: () => {
this.bindBankCard(info).then(res => {
if (res.code === 200) {
this.fetchBankCard();
} else {
this.$createDialog({
type: 'alert',
content: res.message || '提交失败或网络异常,请稍后重试',
confirmBtn: {
text: '我知道了',
active: true
}
}).show()
}
});
}
}).show();
}
},
components: {
LayoutApp,
Select,
Cards
}
};
</script>
<style lang="scss" scoped>
/deep/ .layout-context {
background-color: #f0f0f0!important;
}
.mian-tip {
padding: 0 30px;
line-height: 80px;
margin-top: 28px;
font-size: 28px;
color: #444;
}
.form-wrap {
background-color: #fff;
padding-left: 30px;
font-size: 28px;
.form-row:first-child {
border-top: 0;
}
}
.form-row {
height: 88px;
line-height: 90px;
padding-right: 30px;
border-top: 1px solid #e9e9e9;
display: flex;
position: relative;
box-sizing: border-box;
.cell-left {
width: 150px;
}
.cell-right {
flex-grow: 1;
}
.tap-act {
font-size: 50px;
color: #b0b0b0;
font-weight: 300;
}
.tab-go {
position: absolute;
left: 150px;
right: 30px;
top: 0;
bottom: 0;
z-index: 2;
opacity: 0;
}
}
.error-tip {
padding: 30px 30px 0 30px;
font-size: 28px;
line-height: 1.5;
color: #d0021b;
min-height: 72px;
}
.option-wrap {
margin: 20px 30px;
.save-btn {
width: 100%;
display: block;
font-size: 32px;
line-height: 100px;
color: #fff;
background: #444;
border: 0;
border-radius: 8px;
}
}
</style>
... ...
<template>
<LayoutApp title="我的银行卡">
<Cards ref="cards"></Cards>
</LayoutApp>
</template>
<script>
import LayoutApp from '../../components/layout/layout-app';
import Cards from './components/cards';
export default {
name: 'cardList',
mounted() {
this.$refs.cards.init();
this.$root.reportAppStart();
},
components: {
LayoutApp,
Cards
}
};
</script>
<style lang="scss" scoped>
/deep/ .layout-context {
background-color: #fefefe!important;
}
</style>
... ...
<template>
<div>
<div class="card-list">
<div v-for="(card, index) in bankCardList" :key="index" class="card-item">
<div class="card-info">
<p class="bank-name">{{card.bankName}}</p>
<p class="card-id">
<span>****</span>
<span>****</span>
<span>****</span>
<span class="num">{{card.bankCardNo}}</span>
</p>
<p class="user-name">持卡人:{{card.name}}</p>
</div>
</div>
</div>
<p class="change-tip">更换银行卡请致电官方客服</p>
<p class="tel-num">
<a href="tel:400-889-9646" class="tel-href">400-889-9646</a>
</p>
</div>
</template>
<script>
import { createNamespacedHelpers } from 'vuex';
import LayoutApp from '../../../components/layout/layout-app';
import {Scroll} from 'cube-ui';
const {mapState, mapActions} = createNamespacedHelpers('mine/bankCard');
export default {
methods: {
...mapActions(['fetchBankCard']),
init() {
if (!this.bankCardList.length) {
this.fetchBankCard();
}
}
},
computed: {
...mapState(['bankCardList'])
},
components: {
LayoutApp,
}
};
</script>
<style lang="scss" scoped>
.card-list {
padding: 50px;
}
.card-item {
margin: 30px;
padding: 40px;
background: linear-gradient(#363636, #1b181d);
color: #fff;
border-radius: 10px;
position: relative;
overflow: hidden;
&:after {
content: "";
width: 100%;
height: 120%;
background: linear-gradient(116deg, #666, #111);
transform: rotate(63deg);
position: absolute;
top: -20%;
right: -31%;
z-index: 1;
opacity: 0.4;
}
.card-info {
position: relative;
z-index: 2;
}
.bank-name {
font-size: 44px;
}
.card-id {
font-size: 32px;
padding-right: 30px;
margin: 60px 0 50px;
display: flex;
justify-content: space-between;
letter-spacing: 2px;
.num {
font-size: 40px;
position: relative;
top: -10px;
}
}
.user-name {
font-size: 28px;
opacity: 0.9;
position: relative;
bottom: -6px;
}
}
.change-tip,
.tel-num {
text-align: center;
font-size: 28px;
line-height: 1.5;
color: #444;
margin-bottom: 14px;
}
.tel-href {
color: #d0021b;
font-size: 27px;
text-decoration: underline;
}
</style>
... ...
export default [{
path: '/mapp/mine/ufo/bankcard.html',
name: 'bankcard',
component: () => import(/* webpackChunkName: "invite" */ './card-list')
},{
path: '/mapp/mine/ufo/bankcard/add',
name: 'bankcard.add',
component: () => import(/* webpackChunkName: "invite" */ './card-add')
}];
... ...
import bankCard from './bank-card';
export default [...bankCard];
... ...
... ... @@ -210,6 +210,7 @@ export default {
<style lang="scss" scoped>
.order-page {
& > .title {
line-height: 95px;
font-size: 68px;
font-weight: bold;
padding-top: 24px;
... ... @@ -229,6 +230,7 @@ export default {
}
.pro-info {
line-height: 32px;
overflow: hidden;
flex: 1;
padding-left: 40px;
... ...
... ... @@ -4,6 +4,8 @@ import {createApi} from 'create-api';
import storeYoho from './yoho';
import storeUfo from './order';
import storeLicense from './license';
import storeMine from './mine';
import storeInvite from './invite';
Vue.use(Vuex);
... ... @@ -13,7 +15,9 @@ export function createStore(context) {
modules: {
yoho: storeYoho(),
ufo: storeUfo(),
license: storeLicense()
license: storeLicense(),
mine: storeMine(),
invite: storeInvite()
},
strict: process.env.NODE_ENV !== 'production'
});
... ...
import invite from './invite';
export default function() {
return {
namespaced: true,
modules: {
invite: invite()
}
};
}
... ...
import * as Types from './types';
import {get} from 'lodash';
export default {
async fetchOrderList({ commit }) {
const result = await this.$api.get('/api/ufo/invite/recordList');
if (result.code === 200) {
commit(Types.FETCH_INVITE_ORDERLIST, { list: get(result, 'data.list', []) });
}
},
async fetchFriendList({ commit }) {
const result = await this.$api.get('/api/ufo/invite/friendList');
if (result.code === 200) {
commit(Types.FETCH_INVITE_CODE, Object.assign({
inviteRecordList: []
}, result.data));
}
},
async fetchStatus({ commit }) {
const result = await this.$api.get('/api/ufo/invite/status');
if (result.code === 200) {
commit(Types.FETCH_INVITE_STATUS, { status: result.data });
return { status: result.data };
}
commit(Types.FETCH_INVITE_STATUS, { status: false });
return { status: false };
},
async fetchAll({ dispatch }) {
return Promise.all([
dispatch('fetchFriendList'),
dispatch('fetchOrderList')
]);
}
};
... ...
import actions from './actions';
import mutations from './mutations';
export default function() {
return {
namespaced: true,
state: {
card: null,
recordList: [],
inviteCode: {
showInviteCode: '',
inviteeUidNum: 0,
finishedOrderNum: 0,
inviteRecordList: []
},
status: 0
},
actions,
mutations,
getters: {
getBandLength(state, getters, rootState) {
return rootState.mine.bankCard.bankCardList.length === 1;
}
}
};
}
... ...
import * as Types from './types';
export default {
[Types.FETCH_INVITE_STATUS](state, {status}) {
state.status = status;
},
[Types.FETCH_INVITE_ORDERLIST](state, {list}) {
state.recordList = list;
},
[Types.FETCH_INVITE_CODE](state, data) {
state.inviteCode = data;
}
};
... ...
export const FETCH_BANK_CARD_REQUEST = 'FETCH_BANK_CARD_REQUEST';
export const FETCH_BANK_CARD_FAILD = 'FETCH_BANK_CARD_FAILD';
export const FETCH_BANK_CARD_SUCCESS = 'FETCH_BANK_CARD_SUCCESS';
export const FETCH_INVITE_STATUS = 'FETCH_INVITE_STATUS';
export const FETCH_INVITE_ORDERLIST = 'FETCH_INVITE_ORDERLIST';
export const FETCH_INVITE_CODE = 'FETCH_INVITE_CODE';
... ...
import * as Types from './types';
import {get} from 'lodash';
export default {
async fetchBankCard({commit}) {
commit(Types.FETCH_BANK_CARD_REQUEST);
const result = await this.$api.get('/api/ufo/bankcard/getBankCard');
if (result && result.code === 200) {
commit(Types.FETCH_BANK_CARD_SUCCESS, {
card: result.data
});
} else {
commit(Types.FETCH_BANK_CARD_FAILD);
}
return result || {};
},
async fetchBankList({commit}) {
commit(Types.FETCH_BANK_LIST_REQUEST);
const result = await this.$api.get('/api/ufo/bankcard/getBankList');
if (result && result.code === 200) {
commit(Types.FETCH_BANK_LIST_SUCCESS, {
banks: result.data
});
} else {
commit(Types.FETCH_BANK_LIST_FAILD);
}
return result || {};
},
async checkBankCard({commit}, {name, idCardNo, bankCode, bankBranch, bankCardNo}) {
const result = await this.$api.post('/api/ufo/bankcard/checkBankCard', {
name,
idCardNo,
bankCode,
bankBranch,
bankCardNo
});
return result || {};
},
async bindBankCard({commit}, {name, idCardNo, bankCode, bankBranch, bankCardNo}) {
const result = await this.$api.post('/api/ufo/bankcard/bindBankCard', {
name,
idCardNo,
bankCode,
bankBranch,
bankCardNo
});
return result || {};
},
};
... ...
import actions from './actions';
import mutations from './mutations';
export default function() {
return {
namespaced: true,
state: {
fetchingBank: false,
fetchingBankCard: false,
banks: [],
bankCardList: [],
},
actions,
mutations
};
}
... ...
import * as Types from './types';
export default {
[Types.FETCH_BANK_LIST_REQUEST](state) {
state.banks = {};
state.fetchingBank = true;
},
[Types.FETCH_BANK_LIST_FAILD](state) {
state.fetchingBank = false;
},
[Types.FETCH_BANK_LIST_SUCCESS](state, {banks}) {
state.fetchingBank = false;
state.banks = banks;
},
[Types.FETCH_BANK_CARD_REQUEST](state) {
state.bankCardList = [];
state.fetchingBank = true;
},
[Types.FETCH_BANK_CARD_FAILD](state) {
state.fetchingBank = false;
},
[Types.FETCH_BANK_CARD_SUCCESS](state, {card}) {
state.fetchingBank = false;
card && state.bankCardList.push(card);
},
};
... ...
export const FETCH_BANK_CARD_REQUEST = 'FETCH_BANK_CARD_REQUEST';
export const FETCH_BANK_CARD_FAILD = 'FETCH_BANK_CARD_FAILD';
export const FETCH_BANK_CARD_SUCCESS = 'FETCH_BANK_CARD_SUCCESS';
export const FETCH_BANK_LIST_REQUEST = 'FETCH_BANK_LIST_REQUEST';
export const FETCH_BANK_LIST_FAILD = 'FETCH_BANK_LIST_FAILD';
export const FETCH_BANK_LIST_SUCCESS = 'FETCH_BANK_LIST_SUCCESS';
... ...
import bankCard from './bank-card';
export default function() {
return {
namespaced: true,
modules: {
bankCard: bankCard()
}
};
}
... ...
... ... @@ -22,6 +22,7 @@ export default function() {
visible: true,
pageVisible: false,
direction: 'forword',
homePage: true
},
mutations: {
[Types.SET_ENV](state, {context}) {
... ... @@ -52,6 +53,8 @@ export default function() {
});
state.direction = 'forword';
}
state.homePage = state.historys.length <= 1;
},
[Types.NEED_LOGIN](state, {needLogin}) {
state.context.needLogin = needLogin;
... ...
... ... @@ -97,4 +97,53 @@ module.exports = {
params: {
}
},
'/api/ufo/bankcard/getBankCard': {
ufo: true,
api: 'ufo.bankcard.getBankCard',
},
'/api/ufo/bankcard/getBankList': {
ufo: true,
api: 'ufo.bankcard.getBankList',
cache: true,
},
'/api/ufo/bankcard/bindBankCard': {
ufo: true,
api: 'ufo.bankcard.bindBankCard',
params: {
idCardNo: {type: String, require: true},
name: {type: String, require: true},
bankCardNo: {type: Number, require: true},
bankBranch: {type: String, require: true},
bankCode: {type: String, require: true}
}
},
'/api/ufo/bankcard/checkBankCard': {
ufo: true,
api: 'ufo.bankcard.checkBankCard',
params: {
idCardNo: {type: String, require: true},
name: {type: String, require: true},
bankCardNo: {type: Number, require: true},
bankBranch: {type: String, require: true},
bankCode: {type: String, require: true}
}
},
'/api/ufo/invite/recordList': {
ufo: true,
api: 'ufo.invite.getInviteSettlementList',
params: {
}
},
'/api/ufo/invite/status': {
ufo: true,
api: 'ufo.invite.toEnter',
params: {
}
},
'/api/ufo/invite/friendList': {
ufo: true,
api: 'ufo.invite.code',
params: {
}
},
};
... ...
... ... @@ -20,6 +20,7 @@ exports.createApp = async(app) => {
app.locals.devEnv = app.get('env') === 'development';
app.locals.proEnv = app.get('env') === 'production';
app.locals.version = pkg.version;
app.locals.buildId = 'web' + new Date().getTime() + '000000';
if (config.zookeeperServer) {
const monitor = global.yoho.monitorSender;
... ...
... ... @@ -56,6 +56,14 @@ module.exports = (req, res, next) => {
// client ip
req.yoho.clientIp = _getClientIp(req);
if (req.yoho.isApp) {
req.yoho.udid = _.get(req, 'query.udid', '');
}
if (!req.yoho.udid) {
req.yoho.udid = _.get(req, 'cookies.udid', '');
}
Object.assign(res.locals, req.yoho);
next();
... ...
... ... @@ -12,6 +12,7 @@ const {createBundleRenderer} = require('vue-server-renderer');
const logger = global.yoho.logger;
const config = global.yoho.config;
const isProd = process.env.NODE_ENV === 'production';
const isDev = process.env.NODE_ENV === 'development' || !process.env.NODE_ENV;
let renderer;
... ... @@ -34,16 +35,20 @@ if (!isDev) {
}
const getContext = (req) => {
return {
let res = {
url: req.url,
title: '',
user: req.user,
env: {
udid: req.yoho.udid,
isApp: req.yoho.isApp,
isiOS: req.yoho.isiOS,
isAndroid: req.yoho.isAndroid,
isYohoApp: req.yoho.isYohoApp,
clientIp: req.yoho.clientIp,
version: req.query.app_version || pkg.version,
osVersion: req.query.os_version || '',
buildId: req.app.locals.buildId
},
ua: req.get('user-agent'),
hostname: os.hostname(),
... ... @@ -51,6 +56,12 @@ const getContext = (req) => {
udid: _.get(req, 'cookies.udid', 'yoho'),
path: `[${req.method}]${routeEncode.getRouter(req)}`,
};
if (!isProd) {
res.env.unProd = true;
}
return res;
};
const handlerError = (err = {}, req, res, next) => {
... ...
... ... @@ -35,6 +35,7 @@
"axios": "^0.18.0",
"body-parser": "^1.18.3",
"client-sessions": "^0.8.0",
"clipboard": "^2.0.4",
"connect-multiparty": "^2.2.0",
"connect-redis": "^3.4.0",
"cookie-parser": "^1.4.3",
... ...