Authored by 陈峰

Merge branch 'feature/import' into 'master'

Feature/import



See merge request !1
Showing 100 changed files with 4321 additions and 1 deletions

Too many changes to show.

To preserve performance only 100 of 100+ files are displayed.

vendors
... ...
{
"extends": "yoho",
"parserOptions": {
"sourceType": "module",
"ecmaVersion": 6,
"ecmaFeatures": {
"experimentalObjectRestSpread": true
}
},
"rules": {
"camelcase": "off",
"new-cap": "off",
"radix": "off",
"array-callback-return": "off"
},
"globals": {
"App": true,
"wx": true,
"getApp": true,
"Page": true,
"Component": true,
"getCurrentPages": true
}
}
... ...
# Created by https://www.gitignore.io/api/node
### Node ###
# Logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
# nyc test coverage
.nyc_output
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (http://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# Typescript v1 declaration files
typings/
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variables file
.env
# End of https://www.gitignore.io/api/node
.stylelintcache
# IDE
.idea
*.iml
.vscode
.DS_Store
\ No newline at end of file
... ...
registry=http://npm.yohops.com
... ...
vendors
... ...
{
"extends": "stylelint-config-yoho",
"rules": {
"string-quotes": "double",
"no-empty-source": null,
"unit-no-unknown": [true, {
"ignoreUnits": ["rpx"]
}]
}
}
... ...
{
"wxpath": "/Applications/wechatwebdevtools.app/Contents/Resources/app.nw/bin"
}
\ No newline at end of file
... ...
# yoho-miniapp-eshop
\ No newline at end of file
### 品牌官方小程序
===============
## API 各环境地址:
test3:http://api-test3.dev.yohocorp.com
test1:http://api-test1.dev.yohocorp.com
gray:http://apigray.yoho.cn
production:https://api.yoho.cn
## unionType:
测试环境:100000000011368
生产环境:100000000011678
\ No newline at end of file
... ...
import wx from './utils/wx';
import udid from './common/udid';
import event from './common/event';
import {verify} from './common/api';
import config from './config';
import Promise from './vendors/es6-promise';
import { MD5 } from './vendors/crypto';
import { wechatAuthLogin, verifySessionKey } from './common/login';
import { stringify } from './vendors/query-stringify';
import './router/index';
import Yas from './common/yas';
let yas;
// app.js
App({
globalData: {
userInfo: {},
unionID: '',
sessionKey: '',
thirdSession: '',
systemInfo: {},
appShopId: 0,
appUnionType: 0,
},
reportData: {
awakeReported: false
},
onLaunch(options) {
this.globalData.udid = udid.get(); // 生成 UDID
verify.gen(); // 此处返回是是 Promise,需要调用接口的业务,最好在 then 里边执行
let sysInfo = wx.getSystemInfoSync();
if (!sysInfo.screenHeight) {
sysInfo.screenHeight = sysInfo.windowHeight;
}
if (!sysInfo.screenWidth) {
sysInfo.screenWidth = sysInfo.windowWidth;
}
this.globalData.systemInfo = sysInfo;
wx.checkSession()
.then(() => { // 微信登录未过期
this.getWechatThirdSession();
this.getUserInfo();
this.getUnionID();
this.doLogin();
})
.catch(() => { // 微信登录过期
wx.setStorage({
key: 'thirdSession',
data: ''
});
wx.setStorage({
key: 'userInfo',
data: {}
});
wx.setStorage({
key: 'unionID',
data: ''
});
this.setUserInfo({});
this.setSessionKey('');
this.doLogin();
});
this.globalData.sid = MD5(`${new Date().getTime()}`).toString();
wx.getSystemInfo().then(res => {
this.globalData.systemInfo = res;
});
// 设置渠道来源
if (options && options.query && options.query.union_type) {
this.globalData.union_type = options.query.union_type;
}
// 设置微信场景
this.globalData.ch = options.scene;
yas = new Yas(this);
yas.report('YB_LAUNCH_APP');
},
onShow(options) {
// 设置微信场景
this.globalData.ch = options.scene;
yas.report('YB_ENTER_FOREGROUND');
if (this.reportData.awakeReported === false) {
let path = options.path;
if (Object.keys(options.query).length) {
path = `${path}?${stringify(options.query)}`;
}
yas.report('YB_AWAKE_MP', {PAGE_PATH: path});
this.reportData.awakeReported = true;
}
},
onHide() {
yas.report('YB_ENTER_BACKGROUND');
},
doLogin() {
this.getSessionKey();
event.on('login-type-report', params => {
yas.report('YB_MY_LOGIN', params);
});
setTimeout(() => {
this.wechatAuthLogin();
}, 1000);
},
_getSync(key) {
try {
return wx.getStorageSync(key);
} catch (e) {
console.log(`wx.getStorageSync get ${key} failed.`);
return {};
}
},
_setSync(key, value) {
try {
wx.setStorageSync(key, value);
} catch (e) {
console.log(`wx.setStorageSync set ${key} failed.`);
}
},
getWechatThirdSession() {
this.globalData.thirdSession = this._getSync('thirdSession');
return this.globalData.thirdSession;
},
setWechatThirdSession(session) {
this.globalData.thirdSession = session;
wx.setStorage({
key: 'thirdSession',
data: this.globalData.thirdSession
});
},
isLogin() {
return !!this.globalData.userInfo.uid;
},
getUid() {
return this.globalData.userInfo.uid || '';
},
getReportUid() {
return this.globalData.userInfo.uid || this._getSync('userInfo').uid || '';
},
getUserInfo() {
this.globalData.userInfo = this._getSync('userInfo');
},
getUnionID() {
this.globalData.unionID = this._getSync('unionID');
return this.globalData.unionID;
},
getSessionKey() {
this.globalData.sessionKey = this._getSync('sessionKey');
},
getOpenID() {
let openid;
if (this.globalData.openID) {
return this.globalData.openID;
}
openid = wx.getStorageSync('openID');
this.globalData.openID = openid;
return openid;
},
setOpenID(openID) {
if (openID) {
this.globalData.openID = openID;
wx.setStorage({
key: 'openID',
data: openID
});
}
},
setUnionID(unionID) {
this.globalData.unionID = unionID;
wx.setStorage({
key: 'unionID',
data: unionID
});
event.emit('wx-union-id-update');
},
setUserInfo(user) {
this.globalData.userInfo = user;
wx.setStorage({
key: 'userInfo',
data: this.globalData.userInfo
});
},
setSessionKey(sessionKey) {
this.globalData.sessionKey = sessionKey;
this._setSync('sessionKey', sessionKey);
},
resumeUserInfo() {
this.globalData.userInfo = this._getSync('userInfo');
},
resumeSessionKey() {
this.globalData.sessionKey = this._getSync('sessionKey');
},
wechatAuthLogin() {
if (!this.globalData.userInfo.uid || !this.globalData.sessionKey) {
return wechatAuthLogin()
.then(res => {
if (res.code === 10003) { // 微信号已绑定手机号
this.setUserInfo(res.data);
this.setSessionKey(res.data.sessionKey);
event.emit('user-login-success');
event.emit('login-type-report', {LOGIN_TYPE: 4});
}
if (res.code === 10004) { // 微信号未绑定手机号
// 自动登录未绑定静默处理、不强制绑定
}
})
.catch(() => {});
}
verifySessionKey().then(ves => {
if (ves.code !== 200) { // sessionKey已过期
// 清理原存储的用户信息
this.clearUserSession();
return wechatAuthLogin();
} else {
this.resumeUserInfo();
this.resumeSessionKey();
event.emit('user-login-success');
return Promise.resolve({});
}
})
.then(res => {
if (res.code === 10003) { // 微信号已绑定手机号
this.setUserInfo(res.data);
this.setSessionKey(res.data.sessionKey);
event.emit('user-login-success');
event.emit('login-type-report', {LOGIN_TYPE: 4});
}
if (res.code === 10004) { // 微信号未绑定手机号
// 自动登录未绑定静默处理、不强制绑定
}
})
.catch(() => {});
},
clearUserSession() {
this.setUserInfo({});
this.setSessionKey('');
},
getMiniappType() {
return wx.getExtConfigSync().miniappType;
},
getMiniappName() {
return wx.getExtConfigSync().miniappName;
},
getUnionType() {
return config.unionType;
},
getShopId() {
return this.globalData.unionShop && this.globalData.unionShop.shopId ||
this._getSync('unionShop').shopId;
},
getPaymentCode() {
return this.globalData.unionShop && this.globalData.unionShop.payment ||
this._getSync('unionShop').payment;
},
getAppType() {
return this.globalData.unionShop && this.globalData.unionShop.appType ||
this._getSync('unionShop').appType;
},
getAppId() {
return wx.getExtConfigSync().extAppid;
},
getSystemInfo() {
return this.globalData.systemInfo;
},
getPvid() {
return MD5(`${new Date().getTime()}${udid.get()}`).toString();
}
});
... ...
{
"pages": [
"pages/index/index",
"pages/cart/cart",
"pages/cart/gift/gift",
"pages/cart/ensure/ensure",
"pages/cart/invoice/invoice",
"pages/pay/success",
"pages/home/home",
"pages/home/order/order",
"pages/home/order/detail",
"pages/home/address/list",
"pages/home/service/service",
"pages/home/order/express/express",
"pages/account/bindMobile",
"pages/account/chooseArea",
"pages/product/search/search",
"pages/goodsDetail/goodsDetail",
"pages/goodsList/productPool",
"pages/goodsList/goodsList",
"pages/shop/category/category",
"pages/shop/list/list",
"pages/webview/webview",
"pages/jump/jump"
],
"window": {
"navigationBarTextStyle": "white",
"navigationBarBackgroundColor": "#3a3a3a",
"backgroundColor": "#fff",
"backgroundTextStyle": "dark",
"onReachBottomDistance": 100
},
"tabBar": {
"borderStyle": "#e0e0e0",
"color": "#b0b0b0",
"selectedColor": "#444444",
"list": [
{
"pagePath": "pages/index/index",
"text": "首页",
"iconPath": "static/images/home-light.png",
"selectedIconPath": "static/images/home-dark.png"
},
{
"pagePath": "pages/shop/category/category",
"text": "分类",
"iconPath": "static/images/category-light.png",
"selectedIconPath": "static/images/category-dark.png"
},
{
"pagePath": "pages/cart/cart",
"text": "购物车",
"iconPath": "static/images/cart-light.png",
"selectedIconPath": "static/images/cart-dark.png"
},
{
"pagePath": "pages/home/home",
"text": "我的",
"iconPath": "static/images/people-light.png",
"selectedIconPath": "static/images/people-dark.png"
}
]
}
}
... ...
.clearfix:after {
content: "";
display: block;
clear: both;
}
.right {
float: right;
}
.left {
float: left;
}
.hide {
display: none;
}
@import "./iconfont.wxss";
@import "./vendors/zanui/index.wxss";
... ...
import wx from '../utils/wx';
import config from '../config';
import udid from './udid';
import {HmacSHA256, MD5} from '../vendors/crypto';
import {stringify} from '../vendors/query-stringify';
export const verify = {
verifyMethod: 'resources.simple.pice',
update() {
return api.get({ // eslint-disable-line
data: {
method: this.verifyMethod
}
}).then(result => {
if (result && result.code === 200 && result.data && result.data.sk) {
wx.setStorageSync('verifyKey', result.data.sk);
}
return result;
});
},
gen() {
const self = this;
let maxTryTimes = 3;
function update() {
return self.update().then(result => {
if (result && result.code === 200 && result.data && result.data.sk) {
return result.data.sk;
} else if (maxTryTimes) {
maxTryTimes--;
return update();
} else {
return '';
}
});
}
return update();
},
get() {
const verifyKey = wx.getStorageSync('verifyKey');
if (!verifyKey) {
this.gen(); // 异步更新
}
return verifyKey || '';
},
sign(data, encode = false) {
const verifyKey = this.get();
if (!verifyKey) {
return '';
}
data = stringify(data, {
encode: encode
});
return HmacSHA256(data, verifyKey).toString();
},
computeSecret(data, encode = false) {
const newData = {
private_key: config.apiParams.private_key
};
Object.keys(data).sort().forEach(key => {
newData[key] = String(data[key]).trim();
});
newData.client_secret = MD5(stringify(newData, {
encode: encode
})).toString();
delete newData.private_key;
return newData;
}
};
const api = {
request(params) {
const app = getApp();
// 公有参数
params.data = this.addParams(params.data);
params = this.addSessionKey(params);
// client_secret
params.data = verify.computeSecret(params.data);
// 接口验签
if (!params.header['x-yoho-verify'] && params.data.method !== verify.verifyMethod) {
params.header['x-yoho-verify'] = verify.sign(params.data);
}
return wx.request(params).then(result => {
if (result.statusCode === 200) {
const resultData = result && result.data || {};
delete result.data;
resultData.result = result;
if (resultData.code === 508) {
verify.gen();
}
if (resultData.code && resultData.code === params.code) {
return resultData.data || {};
}
return resultData;
} else if (result.statusCode === 401) {
app.clearUserSession();
} else {
console.error('api statusCode:', result.statusCode);
return {
result
};
}
}).catch(err => {
console.error(err);
return {};
});
},
addSessionKey(params) {
const app = getApp();
params = Object.assign({}, params);
if (app && app.globalData && app.globalData.sessionKey) {
params.data.session_key = app.globalData.sessionKey;
params.data.uid = app.globalData.userInfo.uid;
params.header.Cookies = `JSESSIONID=${app.globalData.sessionKey}`;
}
return params;
},
addParams(params) {
return Object.assign({
business_line: config.apiParams.business_line,
client_type: config.apiParams.client_type,
app_version: config.apiParams.app_version,
udid: udid.get()
}, params);
},
get(params) {
// 默认值
params = Object.assign({
data: {},
header: {},
dataType: 'json'
}, params, {
url: config.domains[params.api || 'api'] + (params.url || ''),
});
// 构造好参数再请求
return this.request(params);
},
post(params) {
// 默认值
params = Object.assign({
method: 'post',
data: {},
header: {
'content-type': 'application/x-www-form-urlencoded'
},
dataType: 'json'
}, params, {
url: config.domains[params.api || 'api'] + (params.url || ''),
});
// 构造好参数再请求
return this.request(params);
}
};
module.exports = {
api,
verify
};
... ...
export default {
listeners: [],
on(type, handle) {
if (typeof handle === 'function') {
console.debug(`listen event ${type}`);
this.listeners.push([type, handle]);
}
},
emit(type, ...params) {
this.listeners.forEach(([listenType, handle]) => type === listenType && handle(...params));
console.debug(`receive event ${type}: ${JSON.stringify(params)}`);
},
removeAllListeners() {
this.listeners = [];
}
};
... ...
import wx from '../utils/wx';
import event from '../common/event';
import Promise from '../vendors/es6-promise';
import accountModel from '../models/account/index';
/**
* 用户未授权-发送验证码
* @param mobile 手机号
* @param area 国家码
* @param degrees 验证码
*/
function sendVerifyCode(area, mobile, degrees) {
return accountModel.sendSms(area, mobile, degrees);
}
/**
* 用户已授权-发送验证码
* @param mobile 手机号
* @param area 国家码
* @param degrees 验证码
*/
function sendVerifyCodeWithUnionId(area, mobile, degrees) {
return accountModel.sendCodeByMiniApp(area, mobile, getApp().getOpenID(), degrees);
}
/**
* 是否需要验证码
*/
function isNeedImgCheck() {
return accountModel.isNeedImgCheck();
}
/**
* 获取验证码按钮
* @param area
* @param mobile
* @param degrees 验证码
* @returns {*}
*/
function getVerifyCode(area, mobile, degrees) {
let app = getApp();
if (!app.globalData.unionID) {
return sendVerifyCode(area, mobile, degrees);
}
return sendVerifyCodeWithUnionId(area, mobile, degrees);
}
/**
* 用户未授权-直接登录
* @param area 国家码
* @param mobile 手机号
* @param verifyCode 验证码
*/
function autoSignin(area, mobile, verifyCode) {
return accountModel.autoSignIn(mobile, verifyCode, area);
}
/**
* 用户已授权-校验验证码
* @param area 国家码
* @param mobile 手机号
* @param verifyCode 验证码
*/
function checkVerifyCode(area, mobile, verifyCode) {
return accountModel.bindMiniapp(getApp().globalData.unionID, mobile, verifyCode, area);
}
/**
* 验证手机号
* @param area
* @param mobile
* @param verifyCode
*/
function bindMobileAction(area, mobile, verifyCode) {
if (!getApp().globalData.unionID) {
return autoSignin(area, mobile, verifyCode);
}
return checkVerifyCode(area, mobile, verifyCode);
}
/**
* 判断微信用户是否绑定
* @param unionID
* @param nickName
* @returns {Promise.<TResult>|*}
*/
function wechatUserIsBind(unionID, nickName) {
let app = getApp();
return accountModel.wechatUserIsBind(unionID, nickName)
.then(data => {
wx.hideLoading();
if (data.data &&
data.data.is_bind &&
data.data.is_bind === 'Y') { // 已经绑定
let userInfo = {};
userInfo.is_bind = data.data.is_bind;
userInfo.mobile = data.data.mobile;
userInfo.ssouid = data.data.ssouid;
userInfo.uid = data.data.uid;
userInfo.sessionKey = data.data.session_key;
app.setUserInfo(userInfo);
app.setSessionKey(userInfo.sessionKey);
return Promise.resolve({
code: 10003,
data: userInfo,
message: '微信用户已绑定手机号'
});
} else {
return Promise.resolve({
code: 10004,
message: '微信用户未绑定手机号'
});
}
});
}
/**
* 微信授权登录
* @returns {Promise.<T>}
*/
function wechatAuthLogin() { // showMsg: 是否显示拒绝授权提示
let app = getApp();
return wx.login()
.then(res => {
if (res.code) {
return accountModel.wechatMiniAppLogin(res.code);
}
})
.then(data => {
if (data.code !== 200) {
return Promise.reject({
succeed: false,
message: data.message
});
} else {
data = data.data;
app.setOpenID(data.openid);
app.setWechatThirdSession(data.srd_session);
// 调用getUnionID函数,再次获取unionID
if (!data.unionid) {
app.setUnionID('');
// change: 微信不再支持直接调用wx.getUserInfo
// return getUnionID(data.srd_session, showMsg);
} else {
app.setUnionID(data.unionid);
return wechatUserIsBind(data.unionid, '');
}
}
})
.catch(err => {
return Promise.reject(err);
});
}
/**
* open-type获取用户信息登录绑定
*/
function getUserInfoLogin(e) {
let app = getApp();
let router = global.router;
if (e.detail.errMsg !== 'getUserInfo:ok') {
return router.go('bindMobile');
}
let userInfo = e.detail.userInfo;
let nickName = userInfo.nickName;
accountModel.decodeUserInfo(app.getWechatThirdSession(), e.detail.encryptedData, e.detail.iv)
.then(data => {
if (data.data.union_id) {
app.setUnionID(data.data.union_id);
return wechatUserIsBind(data.data.union_id, nickName);
} else {
return router.go('bindMobile');
}
})
.then(res => {
// 已经绑定手机号
if (res.code === 10003) {
app.setUserInfo(res.data);
app.setSessionKey(res.data.sessionKey);
event.emit('user-login-success');
event.emit('login-type-report', {LOGIN_TYPE: 4});
}
// 手动授权登录未绑定强制绑定
if (res.code === 10004) {
return router.go('bindMobile');
}
})
.catch(() => {});
}
/**
* 验证sessionKey是否过期
* @returns {*}
*/
function verifySessionKey() {
return accountModel.verify();
}
/**
* 自动绑定微信小程序
* @param mobile
* @param areaCode
* @constructor
*/
function _bindMiniAppByAuto(mobile, areaCode) {
return accountModel.bindMiniAppByAuto(getApp().globalData.unionID, mobile, areaCode);
}
/**
* 解密获取微信用户绑定的手机号
* @param iv
* @param encryptedData
*/
function decodePhoneNumber(iv, encryptedData) {
return accountModel.decodeUserInfo(getApp().globalData.thirdSession, encryptedData, iv);
}
/**
* 获取微信手机号码
* @param e
*/
function getPhoneNumber(e) {
const app = getApp();
let router = global.router;
if (e.detail.errMsg === 'getPhoneNumber:ok') {
let phoneNumber;
let countryCode;
wx.showLoading();
decodePhoneNumber(e.detail.iv, e.detail.encryptedData)
.then(res => {
phoneNumber = res.data.phoneNumber;
countryCode = res.data.countryCode;
if (res.data.phoneNumber && res.data.countryCode) {
return _bindMiniAppByAuto(res.data.phoneNumber, res.data.countryCode);
}
return Promise.reject({});
})
.then(res => {
wx.hideLoading();
if (res.code === 200) {
let userInfo = {};
userInfo.uid = res.data.uid;
userInfo.is_bind = res.data.is_bind || '';
userInfo.mobile = res.data.profile;
userInfo.ssouid = res.data.ssouid;
userInfo.sessionKey = res.data.session_key;
app.setUserInfo(userInfo);
app.setSessionKey(userInfo.sessionKey);
event.emit('user-login-success');
if (res.data && res.data.is_register === 0) {
event.emit('bind-auto-register-type-report', {YB_REGISTER_SUCCESS: 5});
}
} else {
return wx.showModal({
title: '提示',
content: res.message || '该手机已是yoho账户,请使用手机号动态登录'
});
}
})
.then(res => {
if (res && res.confirm) {
router.go('bindMobile', {phone: phoneNumber, area: countryCode});
}
})
.catch(() => {
router.go('bindMobile', {phone: phoneNumber, area: countryCode});
});
} else {
router.go('bindMobile');
}
}
export {
wechatAuthLogin,
isNeedImgCheck,
getVerifyCode,
bindMobileAction,
verifySessionKey,
getPhoneNumber,
getUserInfoLogin
};
... ...
import uuid from '../vendors/uuid';
import wx from '../utils/wx';
export default {
get() {
let udid = wx.getStorageSync('udid');
if (udid) {
return udid;
}
udid = uuid();
wx.setStorageSync('udid', udid);
return udid;
}
};
... ...
import config from '../config';
import rules from '../router/rules';
import {MD5} from '../vendors/crypto';
import {parse} from '../vendors/query-stringify';
export default class yas {
constructor(app) {
let self = this;
this.app = app || getApp();
this.pvid = this.app.getPvid();
this.deviceInfo = {
os: '', // 系统类型
dm: '', // 设备型号
res: '', // 屏幕大小
osv: '', // 系统版本
ak: 'yhshop_mp',
ch: config.unionType,
udid: this.app.globalData.udid
};
// 获取设备信息
wx.getSystemInfo({
success(res) {
self.language = res.language;
if (res.platform === 'devtools') {
self.devEnv = true;
}
Object.assign(self.deviceInfo, {
os: res.platform,
dm: res.model,
res: `${res.screenWidth}*${res.screenHeight}`,
osv: res.system,
});
}
});
}
uploadData(params) {
let sid = '';
if (this.app && this.app.globalData) {
sid = this.app.globalData.sid || '';
}
// 开发环境不上报
if (this.devEnv) {
return console.log(params);
}
return wx.request({
url: config.domains.yasApi,
data: {_mlogs: JSON.stringify(params)},
method: 'POST',
header: {
'content-type': 'application/x-www-form-urlencoded',
'x-yoho-sid': MD5(sid).toString(),
},
});
}
report(event, info) {
let self = this;
let userInfo = info || {};
let statusInfo = {ln: this.language};
if (this.app) {
Object.assign(userInfo, {
UNION_ID: this.app.getUnionID(),
APP_ID: this.app.getAppId()
});
}
if (!userInfo.PV_ID) {
userInfo.PV_ID = this.pvid;
}
return new Promise(resolve => {
wx.getNetworkType({
success(res) {
switch (res.networkType) {
case 'wifi':
statusInfo.net = '1';
break;
case '2g':
statusInfo.net = '2';
break;
case '3g':
statusInfo.net = '3';
break;
case '4g':
statusInfo.net = '1';
break;
default:
statusInfo.net = '0';
break;
}
},
complete() {
return resolve(self.uploadData({
status: statusInfo,
device: self.deviceInfo,
events: [{
param: userInfo,
ts: new Date().getTime(),
op: event,
uid: self.app.getReportUid(),
sid: self.app.globalData.sid || '',
}]
}));
}
});
});
}
pageOpenReport(pvid, extra) {
let pages = getCurrentPages();
let currentPage = pages[pages.length - 1];
let path = `/${currentPage.route}`,
options = currentPage.options || {},
fromPage = options.fromPage || '',
fromParam = parse(decodeURIComponent(options.fromParam || ''));
let info = {PV_ID: pvid || this.pvid};
for (let i in rules) {
if (rules.hasOwnProperty(i) && rules[i].path === path) {
Object.assign(info, {
PAGE_PATH: path,
PAGE_NAME: rules[i].report && rules[i].report.pageName || i,
FROM_PAGE_NAME: fromPage && rules[fromPage].report && rules[fromPage].report.pageName || fromPage
});
info.PAGE_PARAM = '';
info.FROM_PAGE_PARAM = '';
if (rules[i].report && rules[i].report.paramKey) {
info.PAGE_PARAM = decodeURIComponent(options[rules[i].report.paramKey] || '');
}
if (fromPage && rules[fromPage].report && rules[fromPage].report.paramKey) {
info.FROM_PAGE_PARAM = decodeURIComponent(fromParam[rules[fromPage].report.paramKey] || '');
}
}
}
this.report('YB_PAGE_OPEN_L', Object.assign(info, extra || {}));
}
}
... ...
Component({
properties: {
address: {
type: Object,
value: {},
observer: '_addressChange'
},
frame: {
type: Boolean,
value: false
}
},
methods: {
_addressChange(address) {
let frame = false;
if (address) {
if (address.area && address.address) {
const length = 45;
let areaText = `${address.area} ${address.address}`;
if (areaText.length > length) {
areaText = areaText.substring(0, length) + '...';
}
address.areaText = areaText;
address.consignee = address.consignee || '';
address.mobile = address.mobile || '';
}
if (!address.consignee && !address.area && !address.address) {
frame = true;
}
}
this.setData({
address,
frame
});
},
chooseAddress() {
this.triggerEvent('chooseAddress');
},
addAddressByLocal() {
this.triggerEvent('addAddressByLocal');
},
addAddressByWechat() {
this.triggerEvent('addAddressByWechat');
}
}
});
... ...
{
"component": true
}
... ...
<view class="address-card">
<view wx:if="{{address}}" class="address {{frame ? 'frame' : ''}}" bindtap="chooseAddress">
<text class="iconfont icon-location"></text>
<view class="consignee">
<text class="name">{{address.consignee}}</text>
<text class="phone">{{address.mobile}}</text>
<text class="direction">{{address.areaText}}</text>
</view>
<text wx:if="{{!address.hideRightIcon}}" class="iconfont icon-right"></text>
</view>
<view wx:else class="address address-empty">
<view class="add-address" bindtap="addAddressByLocal">
<text class="left-icon">+</text>
<text class="text">手动新增收获地址</text>
<text class="iconfont icon-right"></text>
</view>
<view class="add-address add-wechat-address" bindtap="addAddressByWechat">
<text class="iconfont icon-wechat left-icon"></text>
<text class="text">一键获取微信地址</text>
<text class="iconfont icon-right"></text>
</view>
</view>
</view>
... ...
@import "../../../iconfont.wxss";
.address {
height: 210rpx;
line-height: 200rpx;
padding: 0 66rpx 10rpx 104rpx;
font-size: 28rpx;
background-color: #fff;
box-sizing: border-box;
position: relative;
}
.address:after {
content: "";
width: 100%;
height: 10rpx;
background: url("https://cdn.yoho.cn/yoho-brand-shop/assets/img/cart/address-bottom-line-min.png");
position: absolute;
left: 0;
bottom: 0;
}
.address .icon-location {
font-size: 54rpx;
color: #585858;
position: absolute;
left: 30rpx;
}
.address .icon-right {
font-size: 48rpx;
color: #e0e0e0;
position: absolute;
right: 18rpx;
}
.address .consignee {
width: 100%;
display: inline-block;
vertical-align: middle;
line-height: 1.5;
}
.address .consignee .name,
.address .consignee .phone {
max-width: 34%;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
display: inline-block;
font-weight: 700;
}
.address .consignee .name {
margin-right: 20rpx;
}
.address .consignee .direction {
display: block;
margin-top: 20rpx;
word-break: break-all;
}
.address-empty {
padding: 0;
}
.address-empty .add-address {
line-height: 100rpx;
}
.address-empty .add-wechat-address {
border-top: 1rpx solid #f0f0f0;
}
.address-empty .add-address .left-icon {
display: inline-block;
width: 46rpx;
height: 46rpx;
line-height: 44rpx;
vertical-align: middle;
text-align: center;
border: 1rpx solid #e0e0e0;
border-radius: 50%;
margin-left: 30rpx;
margin-right: 20rpx;
}
.address-empty .add-address .text {
vertical-align: middle;
}
.address.address-empty:after {
height: 1rpx;
background: #e0e0e0;
}
.address.frame .name,
.address.frame .phone,
.address.frame .direction {
width: 100%;
height: 36rpx;
background-color: #f3f3f3;
color: #f3f3f3;
}
... ...
Component({
properties: {
bundle: {
type: Object,
value: {},
observer: '_bundleChange'
},
index: {
type: Number,
value: 0
},
isEditing: {
type: Boolean,
value: false,
observer: '_editChange'
},
},
methods: {
_bundleChange(item) {
if (item) {
this.selected = item.selected;
}
this.setData({item});
},
_editChange(isEdit) {
let item = {...this.data.item};
if (!isEdit && this.selected) {
item.selected = this.selected;
this.setData({item});
}
},
chooseBundleAction() {
let item = {...this.data.item};
item.selected = item.selected === 'Y' ? 'N' : 'Y';
if (this.data.isEditing) {
this.triggerEvent('removeGoodsListInEdit', item);
} else {
if (item.selected !== 'Y' && item.pool_buy_number - item.pool_storage_number > 0) {
return wx.showToast({
title: '您勾选的商品库存不足',
icon: 'none',
duration: 1000
});
}
this.triggerEvent('chooseGoodsBundle', item);
}
this.setData({item});
},
editBundleNum(e) {
this.triggerEvent('editBundleNum', e.detail);
}
}
});
... ...
{
"component": true,
"usingComponents": {
"cart-edit": "/components/cart/edit/edit"
}
}
... ...
<view class="bundle-head" wx:if="{{item.pool_type === 3 && item.pool_batch_no}}">
<view class='bundle-title'>
<view class="choose-btn" bindtap="chooseBundleAction">
<text class="iconfont icon-duihao-fill" wx:if="{{item.selected === 'Y'}}"></text>
<text class="iconfont icon-round" wx:else></text>
</view>
<view class="bundle-tag">优惠套装</view>
<view class="bundle-name">{{item.pool_title}}</view>
<view class='bundle-buy-num' wx:if="{{!isEditing}}">x {{item.pool_buy_number}}</view>
</view>
<view class="edit-wrap" wx:if="{{isEditing}}">
<view class="bundle-edit-title">优惠数量</view>
<view class="bundle-edit-num">
<cart-edit goods="{{item}}" type="{{'bundle'}}" bind:editGoodsNum="editBundleNum"></cart-edit>
</view>
<view class='bundle-buy-num'>x {{item.pool_buy_number}}</view>
</view>
</view>
... ...
@import "../../../iconfont.wxss";
.bundle-title {
height: 80rpx;
line-height: 80rpx;
border-bottom: 1rpx solid #f0f0f0;
font-size: 0;
}
.bundle-title > view {
display: inline-block;
font-size: 24rpx;
}
.bundle-title .choose-btn {
width: 90rpx;
text-align: center;
}
.bundle-title .iconfont {
font-size: 40rpx;
}
.bundle-title .bundle-tag {
line-height: 1;
padding: 5rpx 7rpx;
border-radius: 4rpx;
background-color: #d0021b;
color: #fff;
margin-right: 10rpx;
}
.bundle-buy-num {
position: absolute;
right: 30rpx;
font-size: 26rpx;
color: #b0b0b0;
}
.edit-wrap {
padding: 16rpx 90rpx;
padding-right: 0;
border-bottom: 1rpx solid #f0f0f0;
font-size: 0;
margin-top: -4rpx;
background-color: #fff;
}
.edit-wrap > view {
display: inline-block;
font-size: 26rpx;
line-height: 80rpx;
vertical-align: middle;
}
.edit-wrap .bundle-edit-title {
width: 170rpx;
}
... ...
Component({
properties: {
goods: {
type: Object,
value: {},
observer: '_goodsChange'
},
type: {
type: String,
value: ''
}
},
methods: {
_goodsChange(goods) {
if (!goods) {
return;
}
if (this.data.type === 'bundle') {
goods.min_buy_number = goods.min_buy_number || 1;
goods.buy_number = goods.pool_buy_number;
goods.storage_number = goods.pool_storage_number;
} else {
if (goods.batch_no && goods.bundle_activity_id) {
goods.noNumEdit = true;
}
goods.colorSize = `颜色:${goods.factory_goods_name} 尺码:${goods.size_name}`;
}
if (goods.buy_number - goods.min_buy_number <= 0) {
goods.cutBtnHide = true;
}
if (goods.buy_number - goods.storage_number >= 0) {
goods.plusBtnHide = true;
}
this.setData({goods});
},
showToast(title) {
wx.showToast({
title: title,
icon: 'none',
duration: 1000
});
},
editGoodsNum(e) {
let info = {...this.data.goods};
let increment = -1;
let buyNum;
if (e.target.dataset.type === 'plus') {
info.isAdd = true;
increment = -increment;
}
if (this.data.type === 'bundle') {
info.pool_buy_number -= -increment;
buyNum = info.pool_buy_number;
} else {
info.buy_number -= -increment;
buyNum = info.buy_number;
}
if (buyNum - info.storage_number > 0) {
return this.showToast('对不起,没有更多库存了');
} else if (buyNum - info.min_buy_number < 0) {
if (info.min_buy_number > 1) {
return this.showToast(`最低${info.min_buy_number}件起`);
}
return;
}
this.triggerEvent('editGoodsNum', info);
},
editTabAction(e) {
if (this.editTimer) {
clearTimeout(this.editTimer);
}
this.editTimer = setTimeout(() => {
this.editGoodsNum(e);
}, 200);
},
editGoodsColorSize() {
this.triggerEvent('editGoodsColorSize', this.data.goods);
}
}
});
... ...
{
"component": true
}
... ...
<view class="goods-edit-wrap">
<view class="num-edit" wx:if="{{!goods.noNumEdit}}">
<view class="cut-btn opt-btn {{goods.cutBtnHide ? 'disable' : ''}}" data-type="cut" bindtap="editTabAction"></view>
<view class="buy-num">
<text>{{goods.buy_number}}</text>
</view>
<view class="plus-btn opt-btn {{goods.plusBtnHide ? 'disable' : ''}}" data-type="plus" bindtap="editTabAction"></view>
</view>
<view class="color-size" wx:if="{{goods.colorSize}}" bindtap="editGoodsColorSize">
<text class="color-size-text">{{goods.colorSize}}</text>
<text class="iconfont icon-bottom"></text>
</view>
</view>
... ...
@import "../../../iconfont.wxss";
.goods-edit-wrap {
width: 320rpx;
border: 1rpx solid #e0e0e0;
border-radius: 6rpx;
overflow: hidden;
}
.goods-edit-wrap .num-edit {
width: 100%;
height: 70rpx;
line-height: 70rpx;
display: flex;
flex-direction: row;
}
.num-edit > view {
display: flex;
flex-direction: row;
}
.num-edit .buy-num {
width: 180rpx;
text-align: center;
border-left: 1rpx solid #e0e0e0;
border-right: 1rpx solid #e0e0e0;
}
.num-edit .buy-num > text {
width: 100%;
}
.num-edit .opt-btn {
width: 70rpx;
position: relative;
}
.num-edit .opt-btn:before {
content: "";
width: 22rpx;
border-top: 5rpx solid #666;
position: absolute;
top: 50%;
left: 50%;
margin-top: -2rpx;
margin-left: -10rpx;
}
.num-edit .plus-btn:after {
content: "";
height: 22rpx;
border-left: 5rpx solid #666;
position: absolute;
top: 50%;
left: 50%;
margin-top: -9rpx;
margin-left: -1rpx;
}
.num-edit .disable {
opacity: 0.5;
}
.goods-edit-wrap .color-size {
width: 100%;
height: 70rpx;
line-height: 70rpx;
border-top: 1rpx solid #e0e0e0;
margin-top: -1rpx;
position: relative;
}
.color-size .color-size-text {
width: 90%;
padding: 0 10rpx;
text-align: center;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
box-sizing: border-box;
display: block;
}
.color-size .iconfont {
font-size: 32rpx;
position: absolute;
right: 10rpx;
top: 0;
}
... ...
const tagTypeList = ['price_gift', 'gift', 'ticket', 'advance'];
Component({
properties: {
goods: {
type: Object,
value: {},
observer: '_goodsChange'
},
index: {
type: Number,
value: 0
},
isEditing: {
type: Boolean,
value: false,
observer: '_editChange'
},
isEditSelectAll: {
type: String,
value: '',
observer: '_selectChange'
},
touchKey: {
type: String,
value: '',
observer: '_touchChange'
},
isInvalid: {
type: Boolean,
value: false
},
slideWidth: {
type: Number,
value: 70
},
scrollLeft: {
type: Number,
value: 0
}
},
methods: {
_goodsChange(item) {
if (item) {
this.selected = item.selected;
item.mark_price = item.sales_price;
switch (item.goods_type) {
case 'price_gift':
item.sales_price = +item.last_price;
if (item.storage_number > 1) {
item.storage_number = 1;
}
break;
case 'gift':
item.sales_price = 0;
if (item.storage_number > 1) {
item.storage_number = 1;
}
break;
default:
if (item.last_vip_price < item.sales_price) {
item.sales_price = item.last_vip_price;
}
break;
}
if (item.mark_price <= item.sales_price) {
delete item.mark_price;
}
if (tagTypeList.indexOf(item.goods_type) < 0 && item.buy_number > item.storage_number) {
item.goods_type = 'lowStorage';
}
item.isVipPrice = item.vip_discount_money > 0;
this.setData({
item,
scrollLeft: 0
});
}
},
_editChange(isEdit) {
let item = {...this.data.item};
if (isEdit) {
if (item.selected === 'Y') {
this.triggerEvent('removeGoodsListInEdit', item);
}
this.setData({isEditSelectAll: ''});
} else if (this.selected) {
item.selected = this.selected;
this.setData({item});
}
},
_selectChange(selectAll) {
if (!this.data.isEditing || !selectAll) {
return;
}
let item = {...this.data.item};
item.selected = selectAll;
this.triggerEvent('removeGoodsListInEdit', item);
this.setData({item});
},
_touchChange(key) {
if (this._isShowMore && key !== this.data.item.indexKey) {
this.toggleAnimation(false);
}
},
toggleAnimation(isShow) {
this._isShowMore = isShow;
this.setData({scrollLeft: isShow ? this.data.slideWidth : 0});
},
touchStartAction(e) {
this._touchStart = false;
if (this.data.isEditing || e.target.id === 'del-btn') {
return;
}
setTimeout(() => {
this.triggerEvent('goodsItemTouched', this.data.item);
}, 200);
if (this._isShowMore) {
return this.toggleAnimation(false);
}
this._touchStart = e.changedTouches[0];
},
touchEndAction(e) {
if (this.data.isEditing || !this._touchStart) {
return;
}
let touchEnd = e.changedTouches[0];
let offsetX = this._touchStart.clientX - touchEnd.clientX;
if (offsetX > 0) {
let show = false;
if (offsetX > this.data.slideWidth * 0.3) {
show = !show;
}
this.toggleAnimation(show);
}
},
chooseItemAction() {
let item = {...this.data.item};
item.selected = item.selected === 'Y' ? 'N' : 'Y';
if (this.data.isEditing) {
this.triggerEvent('removeGoodsListInEdit', item);
} else {
if (item.selected === 'Y' && item.buy_number - item.storage_number > 0) {
return wx.showToast({
title: '您勾选的商品库存不足',
icon: 'none',
duration: 1000
});
}
this.triggerEvent('chooseGoodsItem', item);
}
this.setData({item});
},
navToDetailAction() {
if (!this.data.isEditing) {
this.triggerEvent('navToDetailPage', this.data.item);
}
},
removeItemAction() {
this.triggerEvent('removeGoods', this.data.item);
},
editGoodsNum(e) {
this.triggerEvent('editGoodsNum', e.detail);
},
editGoodsColorSize(e) {
this.triggerEvent('editGoodsColorSize', e.detail);
}
}
});
... ...
{
"component": true,
"usingComponents": {
"cart-edit": "/components/cart/edit/edit"
}
}
... ...
<wxs src="../../../wxs/helper.wxs" module="helper" />
<view class="goods-item" bindtouchstart="touchStartAction" bindtouchend="touchEndAction">
<scroll-view scroll-x="true" scroll-left="{{scrollLeft}}" scroll-with-animation="true">
<view class="scoll-wrap" style="padding-right: {{slideWidth}}px;">
<view class="goods-item-wrap">
<view class="choose-btn" bindtap="chooseItemAction">
<block wx:if="{{item.goods_type !== 'gift' && !item.batch_no && !item.bundle_activity_id && !isInvalid}}">
<text class="iconfont icon-duihao-fill" wx:if="{{item.selected === 'Y'}}"></text>
<text class="iconfont icon-round" wx:else></text>
</block>
<view class="invalid-tag" wx:if="{{isInvalid}}">失效</view>
</view>
<image class="thumb" src="{{helper.image(item.goods_images || item.goods_image, 76, 100)}}" bindtap="navToDetailAction">
<block wx:if="{{!isInvalid}}">
<view class="goods-type price-gift-tag" wx:if="{{item.goods_type === 'price_gift'}}">
<text>加价购</text>
</view>
<view class="goods-type gift-tag" wx:if="{{item.goods_type === 'gift'}}">
<text>赠品</text>
</view>
<view class="goods-type virtual-tag" wx:if="{{item.goods_type === 'ticket'}}">
<text>虚拟商品</text>
</view>
<view class="goods-type advance-tag" wx:if="{{item.goods_type === 'advance'}}">
<text>预售</text>
</view>
<view class="goods-type low-storage-tag" wx:if="{{item.goods_type === 'lowStorage'}}">
<text>库存不足</text>
</view>
</block>
</image>
<view class="info">
<view class="info-wrap">
<view class="name" bindtap="navToDetailAction">{{item.product_name}}</view>
<block wx:if="{{!isInvalid}}">
<view class="color-size">颜色:{{item.factory_goods_name}} 尺码:{{item.size_name}}</view>
<view class="price">
<text class="sale-price">¥ {{helper.round(item.sales_price)}}</text>
<text wx:if="{{item.mark_price}}" class="mark-price">¥ {{helper.round(item.mark_price)}}</text>
<text class="vip-price-tig" wx:if="{{item.isVipPrice}}">VIP</text>
</view>
<!-- <view class="price-down" wx:if="{{item.price_down > 0}}">
<text>已降¥{{item.price_down}}</text>
</view> -->
<view class="info-edit" wx:if="{{isEditing}}">
<view class="edit-block">
<cart-edit goods="{{item}}" bind:editGoodsNum="editGoodsNum" bind:editGoodsColorSize="editGoodsColorSize"></cart-edit>
<view class="price">
<text class="sale-price">¥ {{helper.round(item.sales_price)}}</text>
<text wx:if="{{item.mark_price}}" class="mark-price">¥ {{helper.round(item.mark_price)}}</text>
<text class="vip-price-tig" wx:if="{{item.isVipPrice}}">VIP</text>
</view>
</view>
</view>
</block>
</view>
</view>
<view class="buy-num">
<text>x {{item.buy_number}}</text>
</view>
</view>
<view id="del-btn" class="del-btn" bindtap="removeItemAction" style="padding-right: {{slideWidth}}px;"></view>
</view>
</scroll-view>
<view class="del-text" style="width: {{slideWidth}}px;">
<text>删除</text>
</view>
</view>
... ...
@import "../../../iconfont.wxss";
.goods-item {
width: 100%;
height: 239rpx;
font-size: 26rpx;
position: relative;
overflow: hidden;
}
.goods-item:before {
content: '';
height: 1rpx;
position: absolute;
left: 100rpx;
right: 0;
top: -1rpx;
background-color: #f0f0f0;
}
.goods-item.top-none:before {
display: none;
}
.goods-item .del-btn,
.goods-item .del-text {
display: flex;
align-items: center;
position: absolute;
top: 0;
bottom: 0;
right: 0;
}
.goods-item .del-text {
color: #fff;
background-color: #ce0a24;
z-index: -1;
}
.goods-item .del-text > text {
width: 100%;
line-height: 1;
text-align: center;
}
.goods-item .red {
color: #d0021b;
}
.goods-item .scoll-wrap {
width: 100%;
position: relative;
padding-bottom: 40rpx;
}
.goods-item .goods-item-wrap {
height: 200rpx;
line-height: 200rpx;
padding: 20rpx 0;
background-color: #fff;
display: flex;
flex-direction: row;
position: relative;
}
.goods-item .choose-btn {
width: 90rpx;
text-align: center;
display: flex;
flex-direction: row;
position: relative;
}
.goods-item .choose-btn .iconfont {
font-size: 40rpx;
}
.goods-item .choose-btn .invalid-tag {
font-size: 20rpx;
line-height: 1;
color: #fff;
background-color: #666;
padding: 4rpx 10rpx;
border-radius: 13rpx;
position: absolute;
top: 50%;
left: 50%;
margin-left: -24rpx;
margin-top: -12rpx;
}
.goods-item .choose-btn .iconfont {
margin: auto;
}
.goods-item .thumb {
width: 150rpx;
height: 200rpx;
background-color: #f3f3f3;
display: flex;
flex-direction: row;
position: relative;
}
.goods-item .thumb .goods-type {
width: 100%;
height: 30rpx;
line-height: 30rpx;
font-size: 20rpx;
background-color: #444;
color: #fff;
text-align: center;
position: absolute;
left: 0;
bottom: 0;
}
.goods-item .thumb .price-gift-tag {
background-color: #fc1261;
}
.goods-item .thumb .gift-tag {
background-color: #85c45c;
}
.goods-item .thumb .low-storage-tag {
background-color: #b0b0b0;
}
.goods-item .thumb .virtual-tag {
background-color: #c80813;
}
.goods-item .info {
line-height: 1.5;
display: flex;
flex-direction: row;
}
.goods-item .info-wrap {
width: 400rpx;
padding-left: 20rpx;
position: relative;
}
.goods-item .info-wrap .name {
line-height: 1.4;
direction: flex;
display: -webkit-box;
overflow: hidden;
text-overflow: ellipsis;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
margin-top: 20rpx;
}
.goods-item .info-wrap .color-size {
padding: 10rpx 0;
line-height: 1.3;
color: #b0b0b0;
font-size: 24rpx;
}
.goods-item .info-wrap .sale-price {
color: #d0021b;
margin-right: 10rpx;
}
.goods-item .info-wrap .mark-price {
color: #b0b0b0;
margin-right: 10rpx;
text-decoration: line-through;
}
.goods-item .info-wrap .vip-price-tig {
height: 28rpx;
line-height: 28rpx;
padding: 0 14rpx;
border-radius: 16rpx;
font-size: 22rpx;
color: #fff;
background-color: #d0021b;
}
.goods-item .buy-num {
padding-top: 20rpx;
padding-right: 30rpx;
line-height: 1.4;
color: #b0b0b0;
position: absolute;
right: 0;
}
.goods-item .info-edit {
width: 100%;
height: 200rpx;
line-height: 200rpx;
position: absolute;
top: 0;
background-color: #fff;
}
.goods-item .info-edit .edit-block {
display: inline-block;
vertical-align: middle;
}
.goods-item .info-edit .price {
line-height: 1;
margin-top: 20rpx;
}
... ...
import udid from '../../common/udid';
import config from '../../config';
Component({
/**
* 组件的初始数据
*/
data: {
imageSrc: '',
degrees: [0, 0, 0, 0],
imageClass: [0, 0, 0, 0]
},
attached: function() {
this.setData({
imageSrc: this.getVerifyImage()
});
},
/**
* 组件的方法列表
*/
methods: {
/**
* 获取图片
*/
getVerifyImage: function() {
let timestamp = Date.parse(new Date());
let url = `${config.domains.api}/passport/img-check?business_line=${config.apiParams.business_line}&app_version=0.0.1&udid=${udid.get()}&client_type=${config.apiParams.client_type}&fromPage=${config.apiParams.client_type}&t=${timestamp}`; // eslint-disable-line
return url;
},
/**
* 更新图片
*/
refreshImage: function() {
this.setData({
imageSrc: this.getVerifyImage()
});
},
/**
* 改变图片的方向
*/
changeDirection: function(event) {
let indexNum = event.currentTarget.id.replace('image-', '');
let degrees = this.data.degrees;
let imageClass = this.data.imageClass;
degrees[indexNum] = (degrees[indexNum] + 1) % 4;
imageClass[indexNum] = (imageClass[indexNum] + 140) % 560;
this.setData({
degrees: degrees,
imageClass: imageClass
});
this.triggerEvent('refreshCode', {
degrees: this.data.degrees
});
}
}
});
... ...
{
"component": true,
"usingComponents": {}
}
... ...
<view class="top-box">
<text class="tip">请将下列图片点击翻转至正向朝上</text>
<text class="refresh" bindtap="refreshImage">换一批</text>
</view>
<view class="image-check-box">
<view class="image-box">
<image class="image image-0 image-margin-{{imageClass[0]}}" src="{{imageSrc}}" bindtap="changeDirection"
id="image-0"></image>
</view>
<view class="image-box">
<image class="image image-1 image-margin-{{imageClass[1]}}" src="{{imageSrc}}" bindtap="changeDirection"
id="image-1"></image>
</view>
<view class="image-box">
<image class="image image-2 image-margin-{{imageClass[2]}}" src="{{imageSrc}}" bindtap="changeDirection"
id="image-2"></image>
</view>
<view class="image-box">
<image class="image image-3 image-margin-{{imageClass[3]}}" src="{{imageSrc}}" bindtap="changeDirection"
id="image-3"></image>
</view>
</view>
... ...
.top-box {
margin-bottom: 20rpx;
display: flex;
justify-content: space-between;
}
.tip {
color: #444;
font-size: 28rpx;
}
.refresh {
font-size: 28rpx;
color: #d0021b;
}
.image-check-box {
display: flex;
flex-direction: row;
flex-wrap: nowrap;
justify-content: space-between;
}
.image-box {
width: 140rpx;
height: 140rpx;
overflow: hidden;
border: solid 1px #e0e0e0;
}
.image {
width: 560rpx;
height: 560rpx;
}
.image-0 {
margin-left: 0;
}
.image-1 {
margin-left: -140rpx;
}
.image-2 {
margin-left: -280rpx;
}
.image-3 {
margin-left: -420rpx;
}
.image-margin-140 {
margin-top: -140rpx;
}
.image-margin-280 {
margin-top: -280rpx;
}
.image-margin-420 {
margin-top: -420rpx;
}
... ...
Component({
properties: {
delayShow: {
type: Boolean,
value: false
}
},
ready: function() {
setTimeout(() => {
this.setData({
delayShow: true
});
}, 500);
}
});
... ...
<view class="bottom-copyright" wx:if="{{delayShow}}">
<image class="image" src="../../../static/images/yoho!buy@2x.png"></image>
</view>
... ...
.bottom-copyright {
height: 125rpx;
padding-top: 55rpx;
text-align: center;
background-color: #f0f0f0;
}
.bottom-copyright .image {
width: 222rpx;
height: 70rpx;
}
... ...
const router = global.router;
Component({
options: {
multipleSlots: true // 在组件定义时的选项中启用多slot支持
},
/**
* 组件的属性列表
* 用于组件自定义设置
*/
properties: {
show: {
type: Boolean,
value: true
},
showBackTop: {
type: Boolean,
value: false,
observer: '_showBackTop'
},
showCart: {
type: Boolean,
value: false
},
marginBottom: {
type: Number,
value: 100
},
showMenu: {
type: Boolean,
value: false
}
},
/**
* 私有数据,组件的初始数据
* 可用于模版渲染
*/
data: {
// 弹窗显示控制
isShow: true,
isExpand: false,
menuOpacity: 0,
homeAnimation: {},
shopcartAnimation: {},
searchAnimation: {},
indicatorAnimation: {},
menuAnimation: {},
isAnimation: false,
},
/**
* 组件的方法列表
* 更新属性和数据的方法与更新页面数据的方法类似
*/
methods: {
/*
* 公有方法
*/
hide() { // 隐藏
this.setData({isShow: !this.data.isShow});
},
show() { // 展示
this.setData({isShow: !this.data.isShow});
},
switchMenu() {
if (this.data.isAnimation) {
return;
}
let time = 150;
if (this.data.isExpand) {
this.takeback(time);
} else {
this.popout(time);
}
this.setData({
isAnimation: true
});
let delayTime = this.properties.showCart ? time * 3 : time * 2;
setTimeout(function() {
this.setData({
isAnimation: false
});
}.bind(this), delayTime);
this.setData({
isExpand: !this.data.isExpand,
});
},
_showBackTop: function(newVal) {
let animtionIndicator = wx.createAnimation({
duration: 300,
timingFunction: 'linear',
});
let animtionMenu = wx.createAnimation({
duration: 300,
timingFunction: 'linear',
});
if (newVal) {
animtionIndicator.opacity(1).scale(1).step();
animtionMenu.translateY(-49).step();
} else {
animtionIndicator.opacity(0).scale(0).step();
animtionMenu.translateY(0).step();
}
this.setData({
indicatorAnimation: animtionIndicator.export(),
menuAnimation: animtionMenu.export()
});
},
// 动画
popout(time) {
let animtionHome = wx.createAnimation({
duration: time,
timingFunction: 'ease-in-out'
});
let animationSearch = wx.createAnimation({
duration: time,
timingFunction: 'ease-in-out'
});
let animationShopcart = wx.createAnimation({
duration: time,
timingFunction: 'ease-in-out'
});
if (this.properties.showCart) {
animationShopcart.opacity(1).translateY(-49).step();
animationSearch.opacity(1).translateY(-49 * 2).step({duration: time * 2});
animtionHome.opacity(1).translateY(-49 * 3).step({duration: time * 3});
this.setData({
shopcartAnimation: animationShopcart.export(),
searchAnimation: animationSearch.export(),
homeAnimation: animtionHome.export()
});
} else {
animationSearch.opacity(1).translateY(-49).step();
animtionHome.opacity(1).translateY(-49 * 2).step({duration: time * 2});
this.setData({
searchAnimation: animationSearch.export(),
homeAnimation: animtionHome.export()
});
}
},
takeback(time) {
let animtionHome = wx.createAnimation({
duration: time,
timingFunction: 'ease-in-out'
});
let animationSearch = wx.createAnimation({
duration: time,
timingFunction: 'ease-in-out'
});
let animationShopcart = wx.createAnimation({
duration: time,
timingFunction: 'ease-in-out'
});
if (this.properties.showCart) {
animtionHome.opacity(0).translateY(0).step();
animationSearch.opacity(0).translateY(0).step();
animationShopcart.opacity(0).translateY(0).step();
this.setData({
homeAnimation: animtionHome.export({ duration: time * 3 })
});
setTimeout(function() {
animationSearch.opacity(0).translateY(0).step({ duration: time * 2 });
this.setData({
searchAnimation: animationSearch.export()
});
}.bind(this), time);
setTimeout(function() {
animationShopcart.opacity(0).translateY(0).step({ duration: time });
this.setData({
shopcartAnimation: animationShopcart.export()
});
}.bind(this), time * 2);
} else {
animtionHome.opacity(0).translateY(0).step();
animationSearch.opacity(0).translateY(0).step();
this.setData({
homeAnimation: animtionHome.export({ duration: time * 2 })
});
setTimeout(function() {
animationSearch.opacity(0).translateY(0).step({ duration: time });
this.setData({
searchAnimation: animationSearch.export()
});
}.bind(this), time);
}
},
jumpToHome() {
wx.switchTab({
url: '/pages/index/index',
});
},
jumpToSearch() {
router.go('productSearch');
},
jumpToShopCart() {
},
backToTop() {
this.triggerEvent('backtop');
}
}
});
... ...
<view hidden="{{!show}}" class="quick-navigation" style="bottom:{{marginBottom}}rpx">
<view wx:if="{{showMenu}}" class="qn-menu-group" animation="{{menuAnimation}}">
<view class="hide-menu-group">
<view class="qn-menu" animation="{{homeAnimation}}" bindtap="jumpToHome">
<text class="menu-text">首页</text>
</view>
<view class="qn-menu" animation="{{searchAnimation}}" bindtap="jumpToSearch">
<text class="menu-text">搜索</text>
</view>
<view class="qn-menu" animation="{{shopcartAnimation}}" bindtap="jumpToShopCart">
<text class="menu-text">购物车</text>
</view>
</view>
<view class="switch-menu qn-menu" bindtap="switchMenu">
<text wx:if="{{isExpand}}" class="iconfont icon-cha menu-text"></text>
<view wx:else class="two-line-text">
<text class="menu-text">快捷</text>
<text class="menu-text">导航</text>
</view>
</view>
</view>
<view wx:if="{{showBackTop}}" class="back-menu qn-menu" animation="{{indicatorAnimation}}" bindtap="backToTop">
<text class="iconfont icon-backtop menu-text"></text>
</view>
</view>
... ...
@import "../../../iconfont.wxss";
.quick-navigation {
position: fixed;
right: 30rpx;
bottom: 100rpx;
z-index: 20;
flex-direction: column;
display: flex;
width: 80rpx;
}
.quick-navigation .qn-menu {
width: 80rpx;
height: 80rpx;
line-height: 80rpx;
color: #fff;
font-size: 22rpx;
position: relative;
overflow: hidden;
}
.quick-navigation .qn-menu:before {
content: "";
width: 100%;
height: 100%;
background-color: #000;
border-radius: 50%;
opacity: 0.6;
position: absolute;
z-index: -1;
}
.quick-navigation .qn-menu .menu-text {
display: block;
text-align: center;
opacity: 0.8;
}
.quick-navigation .switch-menu .iconfont {
font-size: 66rpx;
}
.quick-navigation .qn-menu .two-line-text {
width: 100%;
line-height: 1.4;
display: inline-block;
vertical-align: middle;
}
.quick-navigation .hide-menu-group .qn-menu {
position: absolute;
opacity: 0;
}
.quick-navigation .back-menu {
bottom: 0;
opacity: 0;
position: absolute;
flex-direction: column;
align-self: center;
}
.quick-navigation .back-menu .iconfont {
font-size: 46rpx;
position: relative;
top: 4rpx;
opacity: 0.9;
}
... ...
Component({
properties: {
title: {
type: String,
value: '默认标题'
},
height: {
type: String,
value: 100,
observer: '_setHeight'
}
},
data: {
style: ''
},
methods: {
_setHeight: function(val) {
this.setData({
style: `height: ${val}rpx;line-height: ${val}rpx;`
});
}
}
});
... ...
<view class="view-title" style="{{style}}">
<span class="title-text">{{title}}</span>
</view>
... ...
.view-title {
font-size: 0;
height: 88rpx;
line-height: 88rpx;
text-align: center;
}
.view-title .title-text {
position: relative;
font-family: PingFang-SC-Semibold, sans-serif;
font-size: 28rpx;
color: #222;
letter-spacing: -0.34px;
}
.view-title .title-text:before {
content: "";
position: absolute;
top: 50%;
left: -40rpx;
display: inline-block;
margin-top: -2rpx;
height: 4rpx;
width: 20rpx;
background-color: #222;
}
.view-title .title-text:after {
content: "";
position: absolute;
top: 50%;
right: -40rpx;
margin-top: -2rpx;
display: inline-block;
height: 4rpx;
width: 20rpx;
background-color: #222;
}
... ...
import wx from '../../../utils/wx';
import formatImage from '../../../utils/formatImage';
Component({
properties: {
imageList: {
type: Array,
value: []
},
productName: {
type: String,
value: ''
}
},
data: {
indicatorDots: false,
interval: 5000,
autoplay: false,
duration: 500,
circular: true,
swiperCurrent: 0
},
methods: {
swiperChange: function(e) {
this.setData({
swiperCurrent: e.detail.current
});
},
previewImage: function(e) {
const src = e.currentTarget.dataset.src;
let imgList = this.properties.imageList.map(item => formatImage.image(item.image_url, 750, 1000));
wx.previewImage({
current: src,
urls: imgList
});
},
}
});
... ...
{
"component": true
}
... ...
<wxs src="../../../wxs/helper.wxs" module="helper" />
<view class='detail-banner'>
<swiper class="swiper-wrapper" indicator-dots="{{indicatorDots}}" autoplay="{{autoplay}}"
interval="{{interval}}" duration="{{duration}}" bindchange="swiperChange" circular="{{circular}}">
<block wx:for="{{imageList}}" wx:key="unique">
<swiper-item>
<image class="slide-image"
data-src="{{helper.image(item.image_url, 750, 800)}}"
src="{{helper.image(item.image_url, 750, 800)}}"
mode="aspectFit"
bindtap="previewImage"></image>
</swiper-item>
</block>
</swiper>
<view class="dots" wx:if="{{imageList.length > 1}}">
<view class="bg"></view>
<block wx:for="{{imageList}}" wx:key="unique">
<view class="dot{{index == swiperCurrent ? ' active' : ''}}"></view>
</block>
</view>
<view class="product-name">
<text class="name">{{productName}}</text>
</view>
</view>
... ...
.detail-banner {
position: relative;
width: 100%;
height: 800rpx;
background-color: #f4f4f4;
}
.detail-banner .swiper-wrapper {
height: 800rpx;
}
.detail-banner .slide-image {
width: 100%;
height: 100%;
}
.detail-banner .dots {
position: absolute;
left: 50%;
bottom: 100rpx;
display: flex;
padding: 5rpx 6rpx;
height: 20rpx;
box-sizing: border-box;
border-radius: 10rpx;
justify-content: center;
transform: translateX(-50%);
}
.detail-banner .dots .bg {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
opacity: 0.2;
background-color: #fff;
border-radius: 10rpx;
}
.detail-banner .dots .dot {
margin: 0 4rpx;
width: 10rpx;
height: 10rpx;
opacity: 0.2;
background: #fff;
border-radius: 100%;
}
.detail-banner .dots .dot.active {
opacity: 1;
}
.detail-banner .product-name {
position: absolute;
bottom: 0;
left: 0;
right: 0;
height: 80rpx;
width: 100%;
padding-left: 30rpx;
font-family: PingFang-SC-Regular, sans-serif;
color: #fff;
font-size: 28rpx;
font-weight: bold;
line-height: 80rpx;
letter-spacing: -0.39rpx;
opacity: 0.8;
background-color: #000;
}
... ...
let router = global.router;
Component({
properties: {
item: {
type: Object,
value: {},
observer: '_itemChange'
}
},
data: {
showMarketPrice: false
},
methods: {
goDetail: function() {
this.triggerEvent('productclick', {productSkn: this.properties.item.skn});
router.go('productDetail', {productSkn: this.properties.item.skn});
},
_itemChange: function(item) {
this.setData({
showMarketPrice: +item.marketPrice > 0 && +item.marketPrice > +item.salesPrice
});
}
}
});
... ...
{
"component": true
}
... ...
<wxs src="../../wxs/helper.wxs" module="helper" />
<view class="product-item" bindtap="goDetail">
<image class="item-image" mode="aspectFit" src="{{helper.image(item.defaultImages, 860, 644)}}"></image>
<view class="item-detail">
<text class="item-title">{{item.productName}}</text>
<view class="item-price">
<view class="price price1 {{showMarketPrice ? 'before-market-price' : ''}}">
¥{{helper.round(item.salesPrice)}}</view>
<view class="price price2" wx:if="{{showMarketPrice}}">¥{{helper.round(item.marketPrice)}}</view>
</view>
</view>
</view>
... ...
.product-item {
display: flex;
flex-direction: column;
margin-top: 65rpx;
}
.product-item .item-detail {
position: relative;
height: 128rpx;
}
.product-item .item-price {
position: absolute;
bottom: 0;
font-family: PingFang-SC-Regular, sans-serif;
font-size: 24rpx;
color: #444;
width: 322rpx;
max-width: 322rpx;
letter-spacing: 0.28rpx;
}
.item-price .price {
display: inline-block;
}
.price1.before-market-price {
color: #d0021b !important;
}
.product-item .item-price .price2 {
color: #b0b0b0;
margin-left: 10rpx;
text-decoration: line-through;
}
.product-item .item-image {
width: 322rpx;
height: 430rpx;
}
.product-item .item-title {
font-family: PingFang-SC-Regular, sans-serif;
font-size: 24rpx;
color: #444;
width: 322rpx;
max-width: 322rpx;
line-height: 1.3;
letter-spacing: 0.28px;
display: -webkit-box;
overflow: hidden;
text-overflow: ellipsis;
white-space: pre-wrap;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
margin: 20rpx 0;
}
... ...
Component({
properties: {
floorData: {
type: Object,
value: {}
},
curGender: {
type: String,
value: '',
observer: '_curGenderChange'
}
},
data: {
curSortType: 'discount',
curSortField: '',
curDiscount: '',
showSubGender: false,
showSubDefault: false,
subFilterText: '筛选',
subDefaultText: '默认'
},
methods: {
sortChange: function({target}) {
const sort = target.dataset.sort;
const setSort = field => {
this.setData({
curSortType: sort,
curSortField: field,
showSubGender: false,
showSubDefault: false
});
this.triggerEvent('sortchange', {curSort: field, gender: this.data.curGender});
};
switch (sort) {
case 'time':
setSort('s_t_desc');
break;
case 'popular':
setSort('s_n_asc');
break;
case 'price':
setSort(this.data.curSortField === 's_p_asc' ? 's_p_desc' : 's_p_asc');
break;
default:
break;
}
},
subFilterDefaultTap: function() {
if (this.data.curSortType !== 'discount') {
this.setData({
curSortType: 'discount',
showSubGender: false,
showSubDefault: false,
curSortField: this.data.curDiscount,
});
return this.triggerEvent('sortchange', {curSort: this.data.curDiscount, gender: this.data.curGender});
}
this.setData({
showSubGender: false,
showSubDefault: !this.data.showSubDefault
});
},
subFilterGenderTap: function() {
this.setData({
showSubDefault: false,
showSubGender: !this.data.showSubGender
});
},
selectDefaultTap: function(e) {
const dis = e.target.dataset.dis;
this.setData({
curDiscount: dis,
showSubDefault: false,
curSortField: dis,
subDefaultText: dis ? '折扣' : '默认'
});
this.triggerEvent('sortchange', {curSort: this.data.curDiscount, gender: this.data.curGender});
},
selectGenderTap: function(e) {
const gender = e.target.dataset.gender;
if (gender || gender === '') {
this.setData({
curGender: gender,
showSubGender: false,
subFilterText: gender === '' ? '全部' : (gender === '1,3' ? '男生' : '女生') // eslint-disable-line
});
}
this.triggerEvent('sortchange', {curSort: this.data.curSortField, gender});
},
_curGenderChange: function(gender) {
this.setData({
subFilterText: gender === '' ? '全部' : (gender === '1,3' ? '男生' : '女生') // eslint-disable-line
});
}
}
});
... ...
{
"component": true
}
... ...
<view class="product-list-filter">
<ul class="filter-items" bindtap="sortChange">
<li class="item {{ curSortType == 'discount' ? 'active' : ''}} default" catchtap="subFilterDefaultTap">
{{subDefaultText}}
<image class="item-icon" wx:if="{{curSortType == 'discount'}}"
src="../../static/images/triangle_active@2x.png"></image>
<image class="item-icon" wx:if="{{curSortType != 'discount'}}"
src="../../static/images/triangle_normal@2x.png"></image>
</li>
<li class="item {{curSortType == 'time' ? 'active' : ''}}" data-sort="time">新品</li>
<li class="item {{curSortType == 'popular' ? 'active' : ''}}" data-sort="popular">人气</li>
<li class="item {{curSortType == 'price' ? 'active' : ''}}" data-sort="price">
价格
<image class="item-icon" wx:if="{{curSortField != 's_p_asc' && curSortField != 's_p_desc'}}"
src="../../static/images/db_arrow_normal@2x.png"></image>
<image class="item-icon" wx:if="{{curSortField == 's_p_desc'}}"
src="../../static/images/db_down_active@2x.png"></image>
<image class="item-icon" wx:if="{{curSortField == 's_p_asc'}}"
src="../../static/images/db_up_active@2x.png"></image>
</li>
<li class="item {{(curGender) ? 'active' : ''}}" catchtap="subFilterGenderTap">
{{subFilterText}}
<image class="item-icon" wx:if="{{curGender == ''}}"
src="../../static/images/sg_down_normal@2x.png"></image>
<image class="item-icon" wx:if="{{curGender != ''}}"
src="../../static/images/sg_down_active@2x.png"></image>
</li>
</ul>
<view wx:if="{{showSubDefault}}" class="sub-default" bindtap="selectDefaultTap">
<view class="default-item {{curDiscount == '' ? 'active' : ''}}" data-dis="">
默认<text wx:if="{{curDiscount == ''}}" class="iconfont icon-duihao"></text>
</view>
<view class="separator"></view>
<view class="default-item {{curDiscount == 'p_d_desc' ? 'active' : ''}}" data-dis="p_d_desc">
折扣从高到低<text wx:if="{{curDiscount == 'p_d_desc'}}" class="iconfont icon-duihao"></text></view>
<view class="separator"></view>
<view class="default-item {{curDiscount == 'p_d_asc' ? 'active' : ''}}" data-dis="p_d_asc">
折扣从低到高<text wx:if="{{curDiscount == 'p_d_asc'}}" class="iconfont icon-duihao"></text></view>
</view>
<view wx:if="{{showSubGender}}" class="sub-gender" bindtap="selectGenderTap">
<view class="gender-item {{curGender == '' ? 'active' : ''}}" data-gender="">全部</view>
<view class="gender-item {{curGender == '1,3' ? 'active' : ''}}" data-gender="1,3">男生/BOYS</view>
<view class="gender-item {{curGender == '2,3' ? 'active' : ''}}" data-gender="2,3">女生/GIRLS</view>
</view>
</view>
... ...
@import "../../iconfont.wxss";
.product-list-filter {
position: relative;
font-size: 0;
height: 88rpx;
line-height: 88rpx;
background-color: #fff;
}
.product-list-filter .filter-items {
display: flex;
}
.product-list-filter .item {
position: relative;
flex: 1;
font-family: PingFang-SC-Regular, sans-serif;
text-align: center;
font-size: 28rpx;
color: #b0b0b0;
letter-spacing: 0.33px;
}
.product-list-filter .item .item-icon {
position: absolute;
height: 28rpx;
width: 20rpx;
top: 50%;
left: 50%;
margin-top: -14rpx;
margin-left: 34rpx;
}
.product-list-filter .item.default .item-icon {
position: absolute;
height: 10rpx;
width: 18rpx;
top: 50%;
left: 50%;
margin-top: -5rpx;
margin-left: 34rpx;
}
.product-list-filter .item:after {
display: inline-block;
content: "";
height: 50rpx;
width: 1rpx;
position: absolute;
top: 19rpx;
right: 0;
background-color: #eee;
transform: scaleX(0.5);
}
.product-list-filter .item:last-child:after {
display: inline-block;
content: "";
height: 0;
width: 0;
}
.product-list-filter .item.active {
color: #444;
}
.product-list-filter .sub-gender {
position: absolute;
top: 88rpx;
left: 0;
right: 0;
font-size: 28rpx;
background-color: #fff;
text-align: center;
}
.product-list-filter .sub-gender .gender-item {
height: 90rpx;
color: #b0b0b0;
position: relative;
}
.product-list-filter .sub-gender .gender-item.active {
color: #444;
}
.product-list-filter .sub-gender .gender-item:after {
display: inline-block;
content: "";
height: 1rpx;
position: absolute;
left: 0;
right: 0;
background-color: #eee;
}
.product-list-filter .sub-default {
position: absolute;
top: 88rpx;
left: 0;
right: 0;
font-size: 28rpx;
background-color: #fff;
text-align: left;
border-bottom: 1px solid #eee;
}
.product-list-filter .default-item .iconfont {
position: absolute;
right: 20rpx;
top: 0rpx;
font-size: 38rpx;
line-height: 90rpx;
}
.product-list-filter .sub-default .separator {
margin-left: 46rpx;
height: 1rpx;
background-color: #eee;
}
.product-list-filter .sub-default .default-item {
padding-left: 46rpx;
height: 90rpx;
color: #b0b0b0;
position: relative;
}
.product-list-filter .sub-default .default-item:first-child {
border-top: 1rpx solid #eee;
}
.product-list-filter .sub-default .default-item.active {
color: #444;
}
... ...
Component({
properties: {
list: {
type: Array,
value: []
},
showLoading: {
type: Boolean,
observer: '_loading'
},
showNoMore: {
type: Boolean,
observer: '_more'
}
},
data: {
_showLoading: false,
_showNoMore: false,
},
methods: {
click: function(e) {
const {productSkn} = e.detail;
const {idx} = e.currentTarget.dataset;
this.triggerEvent('productclick', {idx, productSkn});
},
_more: function(status) {
this.setData({
_showNoMore: status
});
},
_loading: function(status) {
this.setData({
_showLoading: status
});
}
}
});
... ...
{
"component": true,
"usingComponents": {
"product-item": "./item"
}
}
... ...
<view class="product-list">
<product-item wx:for="{{list}}"
bindproductclick="click"
wx:key="{{item.default_images}}" item="{{item}}" data-idx="{{index + 1}}"></product-item>
</view>
<view class="status-tip loading" hidden="{{!_showLoading}}">加载中...</view>
<view wx:if="{{_showNoMore}}" class="status-tip no-more">没有更多了</view>
... ...
.product-list {
width: 680rpx;
display: flex;
flex-wrap: wrap;
justify-content: space-between;
margin: 0 auto;
}
.status-tip {
color: #b1b1b1;
font-size: 26rpx;
line-height: 1;
text-align: center;
margin-top: 40rpx;
}
... ...
Component({
properties: {
item: {
type: Object,
value: {},
observer: '_itemChange'
},
index: {
type: Number,
value: 0
},
frame: {
type: Boolean,
value: false
}
},
methods: {
_itemChange(item) {
if (item && item.real_pay_price && item.sales_price > item.real_pay_price) {
item.mark_price = item.sales_price;
item.sales_price = item.real_pay_price;
this.setData({item});
}
},
itemTapped() {
this.triggerEvent('itemTapped', this.data.item);
}
}
});
... ...
<wxs src="../../../wxs/helper.wxs" module="helper" />
<view class="goods-item {{index ? '' : 'top-none'}} {{frame ? 'frame' : ''}}" bindtap="itemTapped">
<view class="thumb">
<image class="thumb" src="{{helper.image(item.goods_images || item.goods_image, 76, 100)}}"></image>
<view class="goods-type price-gift-tag" wx:if="{{item.goods_type === 'price_gift'}}">
<text>加价购</text>
</view>
<view class="goods-type gift-tag" wx:if="{{item.goods_type === 'gift'}}">
<text>赠品</text>
</view>
<view class="goods-type virtual-tag" wx:if="{{item.goods_type === 'ticket'}}">
<text>虚拟商品</text>
</view>
<view class="goods-type advance-tag" wx:if="{{item.goods_type === 'advance'}}">
<text>预售</text>
</view>
</view>
<view class="name-price">
<text class="name item-left">{{item.product_name}}</text>
<view class="price item-right red">
<text class="sale-price">¥ {{helper.round(item.sales_price)}}</text>
<text wx:if="{{item.mark_price}}" class="mark-price">¥ {{helper.round(item.mark_price)}}</text>
</view>
</view>
<view class="color-size-num">
<view class="color-size item-left">
<text class="color">颜色:{{item.factory_goods_name || item.color_name}}</text>
<text class="size">尺码:{{item.size_name}}</text>
</view>
<text class="num item-right">x {{item.buy_number}}</text>
</view>
<block wx:for="{{item.tags}}" wx:key="unique">
<view class="warn-tip red" wx:if="{{item === 'LRE'}}">
<text class="iconfont icon-warn-fill"></text>
<text class="tip">不支持7天无理由退换</text>
</view>
<view class="warn-tip red" wx:if="{{item === 'L15DE'}}">
<text class="iconfont icon-warn-fill"></text>
<text class="tip">不支持15天无理由换货</text>
</view>
</block>
</view>
... ...
@import "../../../iconfont.wxss";
.goods-item {
height: 200rpx;
padding: 20rpx;
padding-left: 180rpx;
border-top: 1rpx solid #e0e0e0;
font-size: 24rpx;
position: relative;
}
.goods-item .red {
color: #d0021b;
}
.goods-item.top-none {
border-top: 0;
}
.goods-item .thumb {
width: 150rpx;
height: 200rpx;
background-color: #f3f3f3;
position: absolute;
left: 0;
}
.goods-item .thumb image {
display: block;
width: 100%;
height: 100%;
}
.goods-item .thumb .goods-type {
width: 100%;
height: 30rpx;
line-height: 30rpx;
font-size: 20rpx;
background-color: #444;
color: #fff;
text-align: center;
position: absolute;
left: 0;
bottom: 0;
}
.goods-item .thumb .price-gift-tag {
background-color: #fc1261;
}
.goods-item .thumb .gift-tag {
background-color: #85c45c;
}
.goods-item .thumb .virtual-tag {
background-color: #c80813;
}
.goods-item .name-price {
font-size: 28rpx;
height: 80rpx;
line-height: 40rpx;
margin-bottom: 10rpx;
overflow: hidden;
}
.goods-item .price {
text-align: right;
}
.goods-item .price .mark-price {
color: #b0b0b0;
text-decoration: line-through;
display: block;
}
.goods-item .color-size-num {
color: #b0b0b0;
margin-bottom: 20rpx;
}
.goods-item .color-size .size {
margin-left: 40rpx;
}
.goods-item .warn-tip .iconfont {
margin-right: 10rpx;
}
.goods-item .item-left {
display: inline-block;
max-width: 70%;
}
.goods-item .item-right {
float: right;
}
.frame.goods-item .name,
.frame.goods-item .price,
.frame.goods-item .color-size,
.frame.goods-item .num {
width: 100%;
height: 36rpx;
background-color: #f3f3f3;
color: #f3f3f3;
}
.frame.goods-item .price {
width: 100rpx;
}
.frame.goods-item .color-size {
width: 50%;
}
.frame.goods-item .num {
width: 60rpx;
}
... ...
Component({
properties: {
url: {
type: String,
value: ''
},
// url类型
urlRule: String,
urlRuleData: Object,
// use for floors' click report
floorId: { // 楼层id
type: String,
value: ''
},
floorName: { // 楼层名称
type: String,
value: ''
},
floorIndex: { // 楼层序号(从1开始)
type: String,
value: ''
},
floorUrl: { // 楼层传参)
type: String,
value: ''
},
floorItemIndex: { // 楼层内部item的index(从1开始,所有楼层more按钮index传0)
type: String,
value: ''
}
},
methods: {
jumpTo: function(e) {
const {
floor_id,
floor_name,
floor_url,
floor_index,
floor_item_index
} = e.currentTarget.dataset;
this.triggerEvent('clickreport', {
F_ID: floor_id,
F_NAME: floor_name,
F_URL: floor_url,
F_INDEX: floor_index,
I_INDEX: floor_item_index
});
// 店铺装修楼层URL规则解析
if (this.data.urlRule === 'shopDecor') {
let url;
let params;
const data = this.data.urlRuleData;
switch (+data.linkType) {
// 商品池
case 0: {
url = 'productPool';
params = {
title: data.text,
productPool: data.resource
};
break;
}
// 商品详情
case 1: {
url = 'productDetail';
params = {
productSkn: data.resource
};
break;
}
// 自定义链接
case 2: {
url = 'webview';
params = {
url: data.resource
};
break;
}
}
return global.router.go(url, params);
}
return global.router.goUrl(this.data.url);
}
}
});
... ...
{
"component": true
}
... ...
<view class="anchor"
data-floor_id="{{floorId}}"
data-floor_url="{{floorUrl}}"
data-floor_name="{{floorName}}"
data-floor_index="{{floorIndex}}"
data-floor_item_index="{{floorItemIndex}}"
bindtap="jumpTo">
<slot></slot>
</view>
... ...
Component({
properties: {
swiperList: {
type: Array,
value: []
},
swieperSpeed: {
type: String,
value: '0'
},
// use for floors' click report
floorId: {
type: String,
value: ''
},
floorName: {
type: String,
value: ''
},
floorIndex: {
type: String, // start from 1
value: ''
}
},
data: {
indicatorDots: false,
interval: 5000,
autoplay: true,
duration: 500,
circular: true,
swiperCurrent: 0
},
methods: {
swiperChange: function(e) {
this.setData({
swiperCurrent: e.detail.current
});
},
// use for floors' click report
report: function(e) {
this.triggerEvent('clickreport', e.detail);
}
}
});
... ...
{
"component": true,
"usingComponents": {
"anchor": "../anchor/anchor"
}
}
... ...
<wxs src="../../../wxs/helper.wxs" module="helper" />
<view class="resource-focus">
<swiper class="swiper-wrapper" indicator-dots="{{indicatorDots}}"
autoplay="{{autoplay}}" interval="{{interval}}" duration="{{duration}}"
circular="{{circular}}" bindchange="swiperChange">
<block wx:for="{{swiperList}}" wx:key="{{index}}">
<swiper-item>
<anchor
bindclickreport="report"
url="{{item.url}}"
floor-id="{{floorId}}"
floor-name="{{floorName}}"
floor-url="{{item.url}}"
floor-index="{{floorIndex}}"
floor-item-index="{{index + 1}}">
<image mode="widthFix" src="{{helper.image(item.src, 750, 480)}}" class="slide-image"/>
</anchor>
</swiper-item>
</block>
</swiper>
<view class="dots">
<block wx:for="{{swiperList}}" wx:key="unique">
<view class="dot{{index == swiperCurrent ? ' active' : ''}}"></view>
</block>
</view>
</view>
... ...
.resource-focus {
position: relative;
width: 100%;
}
.resource-focus .swiper-wrapper {
height: 480rpx;
}
.resource-focus .slide-image {
width: 100%;
}
.resource-focus .dots {
position: absolute;
left: 0;
right: 0;
bottom: 30rpx;
display: flex;
justify-content: center;
}
.resource-focus .dots .dot {
margin: 0 8rpx;
width: 10rpx;
height: 10rpx;
opacity: 0.7;
background: #fff;
}
.resource-focus .dots .dot.active {
opacity: 1;
width: 20rpx;
}
... ...
import resourcesModel from '../../models/resources/index';
Component({
properties: {
refresh: {
type: Boolean,
value: false,
observer: '_refreshChange'
},
contentCode: {
type: String,
value: '',
observer: '_contentCodeChange'
},
// use for floors' click report
floorIndex: { // start from 1
type: String,
value: ''
},
floors: {
type: Array,
value: []
}
},
methods: {
_refreshChange: function(refresh) {
if (refresh) {
resourcesModel.getContent(this.data.contentCode)
.then(floors => {
this.setData({
floors
});
});
}
},
_contentCodeChange: function(code) {
resourcesModel.getContent(code)
.then(floors => {
this.setData({
floors
});
});
},
report: function(e) {
this.triggerEvent('clickreport', e.detail);
}
}
});
... ...
{
"component": true,
"usingComponents": {
"shop-carousel": "./shop/carousel/carousel",
"shop-single-image": "./shop/single-image/single",
"shop-double-image": "./shop/double-image/double",
"shop-four-image": "./shop/four-image/four",
"shop-triple-image": "./shop/triple-image/triple",
"shop-title": "./shop/title/title"
}
}
... ...
<view class="resources">
<view wx:for="{{floors}}" wx:key="{{index}}" bindclickreport="report">
<shop-carousel
bindclickreport="report"
wx:if="{{item.module_type == 'CarouselImage'}}"
data="{{item.data}}"
properties="{{item.properties}}"
padding-class="{{'padding-bottom-20'}}"
floor-id="{{item.template_id}}"
floor-name="{{item.module_type}}"
floor-index="{{item.module_order}}">
</shop-carousel>
<shop-single-image
bindclickreport="report"
wx:if="{{item.module_type == 'SingleImage'}}"
data="{{item.data}}"
properties="{{item.properties}}"
padding-class="{{'padding-bottom-20'}}"
floor-id="{{item.template_id}}"
floor-name="{{item.module_type}}"
floor-index="{{item.module_order}}">
</shop-single-image>
<shop-double-image
bindclickreport="report"
wx:if="{{item.module_type == 'DoubleImage'}}"
data="{{item.data}}"
properties="{{item.properties}}"
padding-class="{{'padding-bottom-20'}}"
floor-id="{{item.template_id}}"
floor-name="{{item.module_type}}"
floor-index="{{item.module_order}}">
</shop-double-image>
<shop-four-image
bindclickreport="report"
wx:if="{{item.module_type == 'FourImage'}}"
data="{{item.data}}"
properties="{{item.properties}}"
padding-class="{{'padding-bottom-20'}}"
floor-id="{{item.template_id}}"
floor-name="{{item.module_type}}"
floor-index="{{item.module_order}}">
</shop-four-image>
<shop-triple-image
bindclickreport="report"
wx:if="{{item.module_type == 'TripleImage'}}"
data="{{item.data}}"
properties="{{item.properties}}"
floor-id="{{item.template_id}}"
floor-name="{{item.module_type}}"
floor-index="{{item.module_order}}">
</shop-triple-image>
<shop-title
bindclickreport="report"
wx:if="{{item.module_type == 'Title'}}"
data="{{item.data}}"
properties="{{item.properties}}"
floor-id="{{item.template_id}}"
floor-name="{{item.module_type}}"
floor-index="{{item.module_order}}">
</shop-title>
</view>
</view>
... ...
Component({
properties: {
data: {
type: Array,
value: []
},
swieperSpeed: {
type: String,
value: '0'
},
// use for floors' click report
floorId: {
type: String,
value: ''
},
floorName: {
type: String,
value: ''
},
floorIndex: {
type: String, // start from 1
value: ''
},
properties: {
type: Object,
value: {}
}
},
data: {
indicatorDots: false,
interval: 5000,
autoplay: true,
duration: 500,
circular: true,
swiperCurrent: 0
},
methods: {
swiperChange: function(e) {
this.setData({
swiperCurrent: e.detail.current
});
},
// use for floors' click report
report: function(e) {
this.triggerEvent('clickreport', e.detail);
}
}
});
... ...
{
"component": true,
"usingComponents": {
"anchor": "../../anchor/anchor"
}
}
... ...
<wxs src="../../../../wxs/helper.wxs" module="helper" />
<view class="resource-shop-carousel {{properties.isModuleMargin ? 'margin-bottom-20' : ''}}">
<swiper class="swiper-wrapper" indicator-dots="{{indicatorDots}}"
autoplay="{{autoplay}}" interval="{{interval}}" duration="{{duration}}"
circular="{{circular}}" bindchange="swiperChange">
<block wx:for="{{data}}" wx:key="{{index}}">
<swiper-item>
<anchor
bindclickreport="report"
url-rule="shopDecor"
url-rule-data="{{item}}"
floor-id="{{floorId}}"
floor-name="{{floorName}}"
floor-index="{{floorIndex}}"
floor-item-index="{{index + 1}}">
<image mode="widthFix" src="{{helper.image(item.pic, 750, 480)}}" class="slide-image"/>
</anchor>
</swiper-item>
</block>
</swiper>
<view class="dots" wx:if="{{data.length > 1}}">
<block wx:for="{{data}}" wx:key="unique">
<view class="dot{{index == swiperCurrent ? ' active' : ''}}"></view>
</block>
</view>
</view>
... ...
.resource-shop-carousel {
position: relative;
width: 100%;
font-size: 0;
}
.resource-shop-carousel .swiper-wrapper {
height: 234rpx;
}
.resource-shop-carousel .slide-image {
width: 100%;
}
.resource-shop-carousel .dots {
position: absolute;
left: 0;
right: 0;
bottom: 10rpx;
display: flex;
justify-content: center;
}
.resource-shop-carousel .dots .dot {
margin: 0 4rpx;
width: 10rpx;
height: 10rpx;
opacity: 0.7;
border-radius: 50%;
background-color: #fff;
}
.resource-shop-carousel .dots .dot.active {
opacity: 1;
background-color: #007aff;
}
.resource-shop-carousel.margin-bottom-20 {
margin-bottom: 20rpx;
}
... ...
Component({
properties: {
data: {
type: Array,
value: []
},
properties: {
type: Object,
value: {}
},
// use for floors' click report
floorId: {
type: String,
value: ''
},
floorName: {
type: String,
value: ''
},
floorIndex: {
type: String, // start from 1
value: ''
}
},
methods: {
// use for floors' click report
report: function(e) {
this.triggerEvent('clickreport', e.detail);
}
}
});
... ...
{
"component": true,
"usingComponents": {
"anchor": "../../anchor/anchor"
}
}
... ...
<view class="resource-shop-double-image {{properties.isModuleMargin ? 'margin-bottom-20' : ''}}">
<anchor
wx:for="{{data}}"
wx:key="index"
class="anchor-item"
bindclickreport="report"
url-rule="shopDecor"
url-rule-data="{{item}}"
floor-id="{{floorId}}"
floor-name="{{floorName}}"
floor-index="{{floorIndex}}"
floor-item-index="{{index + 1}}">
<image class="image-item" src="{{item.pic}}" mode="widthFix"></image>
</anchor>
</view>
... ...
.resource-shop-double-image {
font-size: 0;
}
.resource-shop-double-image .anchor-item {
display: inline-block;
width: 50%;
font-size: 0;
}
.resource-shop-double-image .image-item {
width: 100%;
font-size: 0;
}
.resource-shop-double-image.margin-bottom-20 {
margin-bottom: 20rpx;
}
.resource-shop-double-image.margin-bottom-30 {
margin-bottom: 30rpx;
}
.resource-shop-double-image.padding-bottom-20 {
padding-bottom: 20rpx;
background-color: #f2f2f2;
}
.resource-shop-double-image.padding-bottom-30 {
padding-bottom: 30rpx;
background-color: #f2f2f2;
}
... ...
Component({
properties: {
data: {
type: Array,
value: []
},
properties: {
type: Object,
value: {}
},
// use for floors' click report
floorId: {
type: String,
value: ''
},
floorName: {
type: String,
value: ''
},
floorIndex: {
type: String, // start from 1
value: ''
}
},
methods: {
// use for floors' click report
report: function(e) {
this.triggerEvent('clickreport', e.detail);
}
}
});
... ...
{
"component": true,
"usingComponents": {
"anchor": "../../anchor/anchor"
}
}
... ...
<view class="resource-shop-four-image {{properties.isModuleMargin ? 'margin-bottom-20' : ''}}">
<anchor
wx:for="{{data}}"
wx:key="index"
class="anchor-item"
bindclickreport="report"
url-rule="shopDecor"
url-rule-data="{{item}}"
floor-id="{{floorId}}"
floor-name="{{floorName}}"
floor-index="{{floorIndex}}"
floor-item-index="{{index + 1}}">
<image class="image-item" src="{{item.pic}}" mode="widthFix"></image>
<text class="image-text">{{item.text}}</text>
</anchor>
</view>
... ...
.resource-shop-four-image {
font-size: 0;
}
.resource-shop-four-image .anchor-item {
display: inline-block;
width: 25%;
font-size: 0;
box-sizing: border-box;
border-left: 1px solid #e0e0e0;
border-top: 1px solid #e0e0e0;
border-bottom: 1px solid #e0e0e0;
}
.resource-shop-four-image .image-item {
width: 100%;
font-size: 0;
}
.resource-shop-four-image .image-text {
display: inline-block;
max-width: 100%;
font-size: 24rpx;
text-align: center;
box-sizing: border-box;
padding: 5rpx 10rpx;
background-color: #fff;
}
.resource-shop-four-image.margin-bottom-20 {
margin-bottom: 20rpx;
}
.resource-shop-four-image.margin-bottom-30 {
margin-bottom: 30rpx;
}
.resource-shop-four-image.padding-bottom-20 {
padding-bottom: 20rpx;
background-color: #f2f2f2;
}
.resource-shop-four-image.padding-bottom-30 {
padding-bottom: 30rpx;
background-color: #f2f2f2;
}
... ...
Component({
properties: {
data: {
type: Array,
value: []
},
properties: {
type: Object,
value: {}
},
// use for floors' click report
floorId: {
type: String,
value: ''
},
floorName: {
type: String,
value: ''
},
floorIndex: {
type: String, // start from 1
value: ''
}
},
methods: {
// use for floors' click report
report: function(e) {
this.triggerEvent('clickreport', e.detail);
}
}
});
... ...
{
"component": true,
"usingComponents": {
"anchor": "../../anchor/anchor"
}
}
... ...
<view class="resource-shop-single-image {{properties.isModuleMargin ? 'margin-bottom-20' : ''}}">
<anchor
class="anchor-item"
bindclickreport="report"
url-rule="shopDecor"
url-rule-data="{{data[0]}}"
floor-id="{{floorId}}"
floor-name="{{floorName}}"
floor-index="{{floorIndex}}"
floor-item-index="1">
<image class="image" src="{{data[0].pic}}" mode="widthFix"></image>
</anchor>
</view>
... ...
.resource-shop-single-image {
font-size: 0;
}
.resource-shop-single-image .image {
width: 750rpx;
}
.resource-shop-single-image.margin-bottom-20 {
margin-bottom: 20rpx;
}
... ...
Component({
properties: {
data: {
type: Object,
value: []
},
properties: {
type: Object,
value: {}
},
// use for floors' click report
floorId: {
type: String,
value: ''
},
floorName: {
type: String,
value: ''
},
floorIndex: {
type: String, // start from 1
value: ''
}
},
methods: {
// use for floors' click report
report: function(e) {
this.triggerEvent('clickreport', e.detail);
}
}
});
... ...
{
"component": true,
"usingComponents": {
"anchor": "../../anchor/anchor"
}
}
... ...
<view class="resource-shop-title {{properties.isModuleMargin ? 'margin-bottom-20' : ''}}">
<anchor
class="anchor-item"
bindclickreport="report"
url-rule="shopDecor"
url-rule-data="{{data[0]}}"
floor-id="{{floorId}}"
floor-name="{{floorName}}"
floor-index="{{floorIndex}}"
floor-item-index="1">
<text class="title">{{data[0].text}}</text>
<image class="more" src="../../../../static/images/triple-dot.png"></image>
</anchor>
</view>
... ...
.resource-shop-title {
height: 80rpx;
color: #444;
font-size: 32rpx;
text-align: center;
line-height: 80rpx;
background-color: #fff;
}
.resource-shop-title .more {
float: right;
width: 44rpx;
height: 8rpx;
margin-top: 36rpx;
margin-right: 30rpx;
}
.resource-shop-title.margin-bottom-20 {
margin-bottom: 20rpx;
}
... ...
Component({
properties: {
data: {
type: Array,
value: []
},
properties: {
type: Object,
value: {}
}
},
methods: {
// use for floors' click report
report: function(e) {
this.triggerEvent('clickreport', e.detail);
}
}
});
... ...