Browse Source

商城模版

mr-zhou-zhou 3 years ago
commit
971cd36461
82 changed files with 12058 additions and 0 deletions
  1. 2 0
      .gitignore
  2. 271 0
      App.vue
  3. 21 0
      LICENSE
  4. 44 0
      README.md
  5. 270 0
      components/Progress-Bar/Progress-Bar.css
  6. 70 0
      components/Progress-Bar/Progress-Bar.vue
  7. 18 0
      components/empty.vue
  8. 119 0
      components/mix-list-cell.vue
  9. 6 0
      components/mix-loading/mix-loading.vue
  10. 202 0
      components/share.vue
  11. 199 0
      components/uni-countdown/uni-countdown.vue
  12. 96 0
      components/uni-icons/icons.js
  13. 10 0
      components/uni-icons/uni-icons.vue
  14. 194 0
      components/uni-load-more/uni-load-more.vue
  15. 205 0
      components/uni-number-box.vue
  16. 142 0
      components/uni-rate/uni-rate.vue
  17. 226 0
      components/upload-images.vue
  18. 13 0
      components/z-table/table-render.js
  19. 782 0
      components/z-table/z-table.vue
  20. 225 0
      main.js
  21. 76 0
      manifest.json
  22. 30 0
      node_modules/jweixin-module/README.md
  23. 0 0
      node_modules/jweixin-module/lib/index.js
  24. 54 0
      node_modules/jweixin-module/package.json
  25. 21 0
      node_modules/mpvue-citypicker/LICENSE
  26. 107 0
      node_modules/mpvue-citypicker/README.md
  27. 119 0
      node_modules/mpvue-citypicker/package.json
  28. 249 0
      node_modules/mpvue-citypicker/src/mpvueCityPicker.vue
  29. 16 0
      package-lock.json
  30. 320 0
      pages.json
  31. 180 0
      pages/address/address.vue
  32. 203 0
      pages/address/addressManage.vue
  33. 481 0
      pages/cart/cart.vue
  34. 199 0
      pages/category/category.vue
  35. 355 0
      pages/favorite/favorite.vue
  36. 466 0
      pages/flash/list.vue
  37. 454 0
      pages/index/index.vue
  38. 22 0
      pages/money/money.vue
  39. 366 0
      pages/money/pay.vue
  40. 61 0
      pages/money/paySuccess.vue
  41. 153 0
      pages/notice/notice.vue
  42. 17 0
      pages/order/createOrder.vue
  43. 43 0
      pages/order/delivery.vue
  44. 172 0
      pages/order/evaluate.vue
  45. 790 0
      pages/order/order.vue
  46. 391 0
      pages/order/orderDetail.vue
  47. 406 0
      pages/order/refund.vue
  48. 101 0
      pages/product/evaluate.vue
  49. 432 0
      pages/product/list.vue
  50. 1224 0
      pages/product/product.vue
  51. 232 0
      pages/public/login.vue
  52. 289 0
      pages/public/register.vue
  53. 37 0
      pages/public/webview.vue
  54. 104 0
      pages/set/set.vue
  55. 419 0
      pages/user/user.vue
  56. 216 0
      pages/userinfo/userinfo.vue
  57. BIN
      static/arc.png
  58. BIN
      static/emptyCart.jpg
  59. BIN
      static/errorImage.jpg
  60. BIN
      static/missing-face.png
  61. BIN
      static/select.png
  62. BIN
      static/selected.png
  63. BIN
      static/tab-cart-current.png
  64. BIN
      static/tab-cart.png
  65. BIN
      static/tab-cate-current.png
  66. BIN
      static/tab-cate.png
  67. BIN
      static/tab-home-current.png
  68. BIN
      static/tab-home.png
  69. BIN
      static/tab-my-current.png
  70. BIN
      static/tab-my.png
  71. BIN
      static/temp/h1.png
  72. BIN
      static/temp/secskill-img.jpg
  73. BIN
      static/temp/share_moment.png
  74. BIN
      static/temp/share_qq.png
  75. BIN
      static/temp/share_qqzone.png
  76. BIN
      static/temp/share_wechat.png
  77. BIN
      static/user-bg.jpg
  78. BIN
      static/vip-card-bg.png
  79. BIN
      static/yticon.ttf
  80. 51 0
      store/index.js
  81. 53 0
      template.h5.html
  82. 34 0
      uni.scss

+ 2 - 0
.gitignore

@@ -0,0 +1,2 @@
+unpackage/*
+.DS_Store

+ 271 - 0
App.vue

@@ -0,0 +1,271 @@
+<script>
+	/**
+	 * vuex管理登陆状态,具体可以参考官方登陆模板示例
+	 */
+	import {
+		mapMutations,
+		mapState
+	} from 'vuex';
+	export default {
+		methods: {
+			...mapMutations(['login', 'logout', 'setUserInfo']),
+			// #ifdef H5
+			// 检查登录状态
+			async checkLogin() {
+				let user = uni.getStorageSync('userInfo');
+				if (user) {
+					this.login(user);
+				}
+				let result = await this.$api.request('/user/status');
+				if (!result) {
+					// 若没有登录则清空个人信息
+					this.logout();
+				}
+			},
+			// #endif
+		},
+		onLaunch: function() {
+			// 锁定屏幕竖向
+			// #ifdef APP-PLUS
+			plus.screen.lockOrientation('portrait-primary');
+			// #endif
+			
+			// 检查用户登录情况
+			// #ifdef H5
+			this.checkLogin();
+			// #endif
+			// #ifdef MP-WEIXIN
+			this.$wechatMiniLogin();
+			// #endif
+		
+		},
+		onShow: function() {
+			console.log('App Show')
+			
+		},
+		onHide: function() {
+			console.log('App Hide')
+		},
+	}
+</script>
+
+<style lang='scss'>
+	/* project id 1729059 */
+	@font-face {
+	  font-family: 'unishop';  
+	  font-weight: normal;
+	  font-style: normal;
+	  src: url('https://at.alicdn.com/t/font_1729059_llr8d2acjac.ttf') format('truetype');
+	}
+	
+	.yticon {
+		font-family: "unishop" !important;
+		font-size: 16px;
+		font-style: normal;
+		-webkit-font-smoothing: antialiased;
+		-moz-osx-font-smoothing: grayscale;
+	}
+	
+	.icon-shouhuodizhi:before{
+		content: "\e6b5";
+	}
+	
+	.icon-xuanzhong:before{
+		content: "\e64c";
+	}
+	
+	.icon-fenlei:before{
+		content: "\e71b";
+	}
+	
+	.icon-bianji:before{
+		content: "\e77d";
+	}
+	
+	.icon-jiahao:before{
+		content: "\e616";
+	}
+	
+	.icon-jianhao:before{
+		content: "\e617";
+	}
+	
+	.icon-wxpay:before{
+		content: "\e607";
+	}
+	
+	.icon-pay:before{
+		content: "\e624";
+	}
+	
+	.icon-alipay:before{
+		content: "\e60b";
+	}
+	
+	.icon-you:before{
+		content: "\e65f";
+	}
+	
+	.icon-huoche:before{
+		content: "\e6f0";
+	}
+	
+	.icon-shoucang:before{
+		content: "\e60a";
+	}
+	
+	.icon-gouwuche:before{
+		content: "\e60e";
+	}
+	
+	.icon-fangzi:before{
+		content: "\e657";
+	}
+	
+	.icon-daifukuan:before{
+		content: "\e601";
+	}
+	
+	.icon-daifahuo:before{
+		content: "\e704";
+	}
+	
+	.icon-daishouhuo:before{
+		content: "\e62f";
+	}
+	
+	.icon-pingjia:before{
+		content: "\e61d";
+	}
+	
+	.icon-shouhou:before{
+		content:"\e610";
+	}
+	
+	.icon-shoucang-setting:before{
+		content:"\e612";
+	}
+	
+	.icon-setting:before{
+		content:"\e62b";
+	}
+	
+	.icon-dizhi:before{
+		content: "\e67c";
+	}
+	
+	.icon-lajitong:before{
+		content: "\e615";
+	}
+
+	view,
+	scroll-view,
+	swiper,
+	swiper-item,
+	cover-view,
+	cover-image,
+	icon,
+	text,
+	rich-text,
+	progress,
+	button,
+	checkbox,
+	form,
+	input,
+	label,
+	radio,
+	slider,
+	switch,
+	textarea,
+	navigator,
+	audio,
+	camera,
+	image,
+	video {
+		box-sizing: border-box;
+	}
+	/* 骨架屏替代方案 */
+	.Skeleton {
+		background: #f3f3f3;
+		padding: 20upx 0;
+		border-radius: 8upx;
+	}
+
+	/* 图片载入替代方案 */
+	.image-wrapper {
+		font-size: 0;
+		background: #f3f3f3;
+		border-radius: 4px;
+
+		image {
+			width: 100%;
+			height: 100%;
+			transition: .6s;
+			opacity: 0;
+
+			&.loaded {
+				opacity: 1;
+			}
+		}
+	}
+
+	.clamp {
+		overflow: hidden;
+		text-overflow: ellipsis;
+		white-space: nowrap;
+		display: block;
+	}
+
+	.common-hover {
+		background: #f5f5f5;
+	}
+
+	/*边框*/
+	.b-b:after,
+	.b-t:after {
+		position: absolute;
+		z-index: 3;
+		left: 0;
+		right: 0;
+		height: 0;
+		content: '';
+		transform: scaleY(.5);
+		border-bottom: 1px solid $border-color-base;
+	}
+
+	.b-b:after {
+		bottom: 0;
+	}
+
+	.b-t:after {
+		top: 0;
+	}
+
+	/* button样式改写 */
+	uni-button,
+	button {
+		height: 80upx;
+		line-height: 80upx;
+		font-size: $font-lg + 2upx;
+		font-weight: normal;
+
+		&.no-border:before,
+		&.no-border:after {
+			border: 0;
+		}
+	}
+
+	uni-button[type=default],
+	button[type=default] {
+		color: $font-color-dark;
+	}
+
+	/* input 样式 */
+	.input-placeholder {
+		color: #999999;
+	}
+
+	.placeholder {
+		color: #999999;
+	}
+</style>

+ 21 - 0
LICENSE

@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2020 mingwei zheng
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.

+ 44 - 0
README.md

@@ -0,0 +1,44 @@
+# uniShop
+
+#### 介绍
+{**本项目简称uniShop,是一个完整的商城。基于[uniapp框架](https://uniapp.dcloud.io/)开发。**}
+
+#### 体验
+![微信小程序](https://images.gitee.com/uploads/images/2020/0415/215327_77d28dac_1588098.jpeg "微信小程序")
+
+#### 后台演示
+1.  后端演示地址 [后端地址](http://demo.shop.weivee.com/admin) http://demo.shop.weivee.com/admin
+
+#### 后台源码
+1.  后端源码地址 [源码地址](https://www.fastadmin.net/store/unishop.html) https://www.fastadmin.net/store/unishop.html 
+
+#### 安装教程
+
+1.  git clone 下来之后直接在HBuilderX打开即可
+2.  接口地址在main.js修改
+
+#### 参与贡献
+
+1.  Fork 本仓库
+2.  新建 Feat_xxx 分支
+3.  提交代码
+4.  新建 Pull Request
+
+
+#### 特别鸣谢
+
+1.  uni-app https://uniapp.dcloud.io/
+2.  mix-mall https://ext.dcloud.net.cn/plugin?id=200
+3.  mpvue-citypicker https://github.com/MPComponent/mpvue-citypicker  
+4.  vue.js https://cn.vuejs.org/
+
+
+#### 版权信息
+
+unishop遵循MIT开源协议发布,并提供免费使用。
+
+本项目包含的第三方源码和二进制文件之版权信息另行标注。
+
+版权所有Copyright © 2020 by uniShop
+
+All rights reserved。

File diff suppressed because it is too large
+ 270 - 0
components/Progress-Bar/Progress-Bar.css


+ 70 - 0
components/Progress-Bar/Progress-Bar.vue

@@ -0,0 +1,70 @@
+<template>
+	<view>
+		<view class="progress" :class="Type" :style="{width: widthUpx + 'rpx'}">
+			<view class="progress-text" v-if="Vice == true" :style="{width: widthUpx + 'rpx'}">
+				已抢{{Sold}}件
+				{{ Width + '%' }}
+			</view>
+			<view class="progress-bar" :style="{width: Width+'%'}"><view class="progress-text" v-if="Main == true">{{ Width + '%' }}</view></view>
+		</view>
+	</view>
+</template>
+
+<script>
+export default {
+	props: {
+		// 已卖多少件
+		Sold: {
+			type: Number,
+			default: 0
+		},
+		// 进度条宽度
+		widthUpx: {
+			type: Number,
+			default: 710
+		},
+		// 进度条百分比
+		Width: {
+			type: Number,
+			default: 0
+		},
+		// 进度条样式
+		/*
+			aqua
+			copper
+			candy
+			neon
+			shine
+			zigzag
+			diamond
+			hearts
+			sparkle
+		*/
+		Type: {
+			type: String,
+			default: 'aqua',
+		},
+		// 主进度显示百分比
+		Main: {
+			type: Boolean,
+			default: true,
+		},
+		// 副进度显示百分比
+		Vice: {
+			type: Boolean,
+			default: true,
+		},
+		
+	},
+	data() {
+		return {
+
+		};
+	},
+	methods: {}
+};
+</script>
+
+<style>
+@import url('Progress-Bar.css');
+</style>

File diff suppressed because it is too large
+ 18 - 0
components/empty.vue


+ 119 - 0
components/mix-list-cell.vue

@@ -0,0 +1,119 @@
+<template>
+	<view class="content">
+		
+		<view class="mix-list-cell" :class="border" @click="eventClick" hover-class="cell-hover"  :hover-stay-time="50">
+			<text
+				v-if="icon"
+				class="cell-icon yticon"
+				:style="[{
+					color: iconColor,
+				}]"
+				:class="icon"
+			></text>
+			<text class="cell-tit clamp">{{title}}</text>
+			<text v-if="tips" class="cell-tip">{{tips}}</text>
+			<text class="cell-more yticon"
+				:class="typeList[navigateType]"
+			></text>
+		</view>
+
+	</view>
+</template>
+ 
+<script>
+	/**
+	 *  简单封装了下, 应用范围比较狭窄,可以在此基础上进行扩展使用
+	 *  比如加入image, iconSize可控等
+	 */
+	export default {
+		data() {
+			return {
+				typeList: {
+					left: 'icon-zuo',
+					right: 'icon-you',
+					up: 'icon-shang',
+					down: 'icon-xia'
+				},
+			}
+		},
+		props: {
+			icon: {
+				type: String,
+				default: ''
+			},
+			title: {
+				type: String,
+				default: '标题'
+			},
+			tips: {
+				type: String,
+				default: ''
+			},
+			navigateType: {
+				type: String,
+				default: 'right'
+			},
+			border: {
+				type: String,
+				default: 'b-b'
+			},
+			hoverClass: {
+				type: String,
+				default: 'cell-hover'
+			},
+			iconColor: {
+				type: String,
+				default: '#333'
+			}
+		},
+		methods: {
+			eventClick(){
+				this.$emit('eventClick');
+			}
+		},
+	}
+</script>
+
+<style lang='scss'>
+
+	.icon .mix-list-cell.b-b:after{
+		left: 90upx;
+	}
+	.mix-list-cell{
+		display:flex;
+		align-items:baseline;
+		padding: 20upx $page-row-spacing;
+		line-height:60upx;
+		position:relative;
+		
+		&.cell-hover{
+			background:#fafafa;
+		}
+		&.b-b:after{
+			left: 30upx;
+		}
+
+		.cell-icon{
+			align-self:center;
+			width:56upx;
+			max-height:60upx;
+			font-size:38upx;
+		}
+		.cell-more{
+			align-self: center;
+			font-size:30upx;
+			color:$font-color-base;
+			margin-left:$uni-spacing-row-sm;
+		}
+		.cell-tit{
+			flex: 1;
+			font-size: $font-base;
+			color: $font-color-dark;
+			margin-right:10upx;
+		}
+		.cell-tip{
+			font-size: $font-sm+2upx;
+			color: $font-color-light;
+		}
+	}
+</style>

File diff suppressed because it is too large
+ 6 - 0
components/mix-loading/mix-loading.vue


+ 202 - 0
components/share.vue

@@ -0,0 +1,202 @@
+<template>
+	<view v-if="show" class="mask" @click="toggleMask" @touchmove.stop.prevent="stopPrevent"
+		:style="{backgroundColor: backgroundColor}"
+	>
+		<view 
+			class="mask-content"
+			@click.stop.prevent="stopPrevent"
+			:style="[{
+				height: config.height, 
+				transform: transform
+			}]"
+		>
+			<scroll-view class="view-content" scroll-y>
+				<view class="share-header">
+					分享到
+				</view>
+				<view class="share-list">
+					<view 
+						v-for="(item, index) in shareList" :key="index"
+						class="share-item" 
+						@click="shareToFriend(item.text)"
+					>
+						<image :src="item.icon" mode=""></image>
+						<text>{{item.text}}</text>
+					</view>
+				</view>
+			</scroll-view>
+			<view class="bottom b-t" @click="toggleMask">取消</view>
+		</view>
+	</view>
+</template>
+
+<script>
+	export default {
+		data() {
+			return {
+				transform: 'translateY(50vh)',
+				timer: 0,
+				backgroundColor: 'rgba(0,0,0,0)',
+				show: false,
+				config: {},
+			};
+		},
+		props:{
+			contentHeight:{
+				type: Number,
+				default: 0
+			},
+			//是否是tabbar页面
+			hasTabbar:{
+				type: Boolean,
+				default: false
+			},
+			shareList:{
+				type: Array,
+				default: function(){
+					return [];
+				}
+			}
+		},
+		created() {
+			const height = uni.upx2px(this.contentHeight) + 'px';
+			this.config = {
+				height: height,
+				transform: `translateY(${height})`,
+				backgroundColor: 'rgba(0,0,0,.4)',
+			}
+			this.transform = this.config.transform;
+		},
+		methods:{
+			toggleMask(){
+				//防止高频点击
+				if(this.timer == 1){
+					return;
+				}
+				this.timer = 1;
+				setTimeout(()=>{
+					this.timer = 0;
+				}, 500)
+				
+				if(this.show){
+					this.transform = this.config.transform;
+					this.backgroundColor = 'rgba(0,0,0,0)';
+					setTimeout(()=>{
+						this.show = false;
+						this.hasTabbar && uni.showTabBar();
+					}, 200)
+					return;
+				}
+				
+				this.show = true;
+				//等待mask重绘完成执行
+				if(this.hasTabbar){
+					uni.hideTabBar({
+						success: () => {
+							setTimeout(()=>{
+								this.backgroundColor = this.config.backgroundColor;
+								this.transform = 'translateY(0px)';
+							}, 10)
+						}
+					});
+				}else{
+					setTimeout(()=>{
+						this.backgroundColor = this.config.backgroundColor;
+						this.transform = 'translateY(0px)';
+					}, 10)
+				}
+			},
+			//防止冒泡和滚动穿透
+			stopPrevent(){},
+			//分享操作
+			shareToFriend(type){
+				this.$api.msg(`分享给${type}`);
+				this.toggleMask();
+			},
+		}
+	}
+</script>
+
+<style lang='scss'>
+	.mask{
+		position:fixed;
+		left: 0;
+		top: 0;
+		right: 0;
+		bottom: 0;
+		display:flex;
+		justify-content: center;
+		align-items: flex-end;
+		z-index: 998;
+		transition: .3s;
+		.bottom{
+			position:absolute;
+			left: 0;
+			bottom: 0;
+			display:flex;
+			justify-content: center;
+			align-items: center;
+			width: 100%;
+			height: 90upx;
+			background: #fff;
+			z-index: 9;
+			font-size: $font-base + 2upx;
+			color: $font-color-dark;
+		}
+	}
+	
+	.mask-content{
+		width: 100%;
+		height: 580upx;
+		transition: .3s;
+		background: #fff;
+		&.has-bottom{
+			padding-bottom: 90upx;
+		}
+		.view-content{
+			height: 100%;
+		}
+	}
+	.share-header{
+		height: 110upx;
+		font-size: $font-base+2upx;
+		color: font-color-dark;
+		display:flex;
+		align-items:center;
+		justify-content: center;
+		padding-top: 10upx;
+		&:before, &:after{
+			content: '';
+			width: 240upx;
+			heighg: 0;
+			border-top: 1px solid $border-color-base;
+			transform: scaleY(.5);
+			margin-right: 30upx;
+		}
+		 &:after{
+			 margin-left: 30upx;
+			 margin-right: 0;
+		 }
+	}
+	.share-list{
+		display:flex;
+		flex-wrap: wrap;
+	}
+	.share-item{
+		min-width: 33.33%;
+		display:flex;
+		flex-direction: column;
+		justify-content: center;
+		align-items: center;
+		height: 180upx;
+		image{
+			width: 80upx;
+			height: 80upx;
+			margin-bottom: 16upx;
+		}
+		text{
+			font-size: $font-base;
+			color: $font-color-base;
+		}
+	}
+</style>

+ 199 - 0
components/uni-countdown/uni-countdown.vue

@@ -0,0 +1,199 @@
+<template>
+	<view class="uni-countdown">
+		<text v-if="showDay" :style="{ borderColor: borderColor, color: color, backgroundColor: backgroundColor, width: borderWidth }" class="uni-countdown__number">{{ d }}</text>
+		<text v-if="showDay" :style="{ color: splitorColor }" class="uni-countdown__splitor">天</text>
+		<text :style="{ borderColor: borderColor, color: color, backgroundColor: backgroundColor, width: borderWidth  }" class="uni-countdown__number">{{ h }}</text>
+		<text :style="{ color: splitorColor }" class="uni-countdown__splitor">{{ showColon ? ':' : '时' }}</text>
+		<text :style="{ borderColor: borderColor, color: color, backgroundColor: backgroundColor, width: borderWidth  }" class="uni-countdown__number">{{ i }}</text>
+		<text :style="{ color: splitorColor }" class="uni-countdown__splitor">{{ showColon ? ':' : '分' }}</text>
+		<text :style="{ borderColor: borderColor, color: color, backgroundColor: backgroundColor, width: borderWidth  }" class="uni-countdown__number">{{ s }}</text>
+		<text v-if="!showColon" :style="{ color: splitorColor }" class="uni-countdown__splitor">秒</text>
+	</view>
+</template>
+<script>
+	export default {
+		name: 'UniCountdown',
+		props: {
+			showDay: {
+				type: Boolean,
+				default: true
+			},
+			showColon: {
+				type: Boolean,
+				default: true
+			},
+			backgroundColor: {
+				type: String,
+				default: '#FFFFFF'
+			},
+			borderColor: {
+				type: String,
+				default: '#000000'
+			},
+			color: {
+				type: String,
+				default: '#000000'
+			},
+			splitorColor: {
+				type: String,
+				default: '#000000'
+			},
+			day: {
+				type: Number,
+				default: 0
+			},
+			hour: {
+				type: Number,
+				default: 0
+			},
+			minute: {
+				type: Number,
+				default: 0
+			},
+			second: {
+				type: Number,
+				default: 0
+			},
+			borderWidth: {
+				type: String,
+				default: "52rpx"
+			}
+		},
+		data() {
+			return {
+				timer: null,
+				syncFlag: false,
+				d: '00',
+				h: '00',
+				i: '00',
+				s: '00',
+				leftTime: 0,
+				seconds: 0
+			}
+		},
+		watch: {
+			day(val) {
+				this.changeFlag()
+			},
+			hour(val) {
+				this.changeFlag()
+			},
+			minute(val) {
+				this.changeFlag()
+			},
+			second(val) {
+				this.changeFlag()
+			}
+		},
+		created: function(e) {
+			this.startData();
+		},
+		beforeDestroy() {
+			clearInterval(this.timer)
+		},
+		methods: {
+			toSeconds(day, hours, minutes, seconds) {
+				return day * 60 * 60 * 24 + hours * 60 * 60 + minutes * 60 + seconds
+			},
+			timeUp() {
+				clearInterval(this.timer)
+				this.timer = 0;
+				// 优化建议:提前1秒去请求
+				this.$emit('timeup', this)
+			},
+			countDown() {
+				let seconds = this.seconds
+				let [day, hour, minute, second] = [0, 0, 0, 0]
+				if (seconds > 0) {
+					day = Math.floor(seconds / (60 * 60 * 24))
+					hour = Math.floor(seconds / (60 * 60)) - (day * 24)
+					minute = Math.floor(seconds / 60) - (day * 24 * 60) - (hour * 60)
+					second = Math.floor(seconds) - (day * 24 * 60 * 60) - (hour * 60 * 60) - (minute * 60)
+				} else {
+					this.timeUp()
+				}
+				if (day < 10) {
+					day = '0' + day
+				}
+				if (hour < 10) {
+					hour = '0' + hour
+				}
+				if (minute < 10) {
+					minute = '0' + minute
+				}
+				if (second < 10) {
+					second = '0' + second
+				}
+				this.d = day
+				this.h = hour
+				this.i = minute
+				this.s = second
+			},
+			startData() {
+				this.seconds = this.toSeconds(this.day, this.hour, this.minute, this.second)
+				if (this.seconds <= 0) {
+					this.timeUp()
+					return
+				}
+				if (this.timer > 0){
+					return;
+				}
+				this.countDown()
+				this.timer = setInterval(() => {
+					this.seconds--
+					if (this.seconds < 0) {
+						this.timeUp()
+						return
+					}
+					this.countDown()
+				}, 1000)
+				//console.log('timer:' + this.timer);
+			},
+			changeFlag() {
+				if (!this.syncFlag) {
+					this.seconds = this.toSeconds(this.day, this.hour, this.minute, this.second)
+					this.startData();
+					this.syncFlag = true;
+				} 
+			}
+		}
+	}
+</script>
+<style lang="scss" scoped>
+	@import '~@/uni.scss';
+	$countdown-height: 48rpx;
+	$countdown-width: 52rpx;
+
+	.uni-countdown {
+		/* #ifndef APP-NVUE */
+		display: flex;
+		/* #endif */
+		flex-direction: row;
+		justify-content: flex-start;
+		padding: 2rpx 0;
+	}
+
+	.uni-countdown__splitor {
+		/* #ifndef APP-NVUE */
+		display: flex;
+		/* #endif */
+		justify-content: center;
+		line-height: $countdown-height;
+		padding: 5rpx;
+		font-size: $uni-font-size-sm;
+	}
+
+	.uni-countdown__number {
+		/* #ifndef APP-NVUE */
+		display: flex;
+		/* #endif */
+		justify-content: center;
+		align-items: center;
+		width: $countdown-width;
+		height: $countdown-height;
+		line-height: $countdown-height;
+		margin: 5rpx;
+		text-align: center;
+		font-size: $uni-font-size-sm;
+	}
+</style>

+ 96 - 0
components/uni-icons/icons.js

@@ -0,0 +1,96 @@
+export default {
+	'contact': '\ue100',
+	'person': '\ue101',
+	'personadd': '\ue102',
+	'contact-filled': '\ue130',
+	'person-filled': '\ue131',
+	'personadd-filled': '\ue132',
+	'phone': '\ue200',
+	'email': '\ue201',
+	'chatbubble': '\ue202',
+	'chatboxes': '\ue203',
+	'phone-filled': '\ue230',
+	'email-filled': '\ue231',
+	'chatbubble-filled': '\ue232',
+	'chatboxes-filled': '\ue233',
+	'weibo': '\ue260',
+	'weixin': '\ue261',
+	'pengyouquan': '\ue262',
+	'chat': '\ue263',
+	'qq': '\ue264',
+	'videocam': '\ue300',
+	'camera': '\ue301',
+	'mic': '\ue302',
+	'location': '\ue303',
+	'mic-filled': '\ue332',
+	'speech': '\ue332',
+	'location-filled': '\ue333',
+	'micoff': '\ue360',
+	'image': '\ue363',
+	'map': '\ue364',
+	'compose': '\ue400',
+	'trash': '\ue401',
+	'upload': '\ue402',
+	'download': '\ue403',
+	'close': '\ue404',
+	'redo': '\ue405',
+	'undo': '\ue406',
+	'refresh': '\ue407',
+	'star': '\ue408',
+	'plus': '\ue409',
+	'minus': '\ue410',
+	'circle': '\ue411',
+	'checkbox': '\ue411',
+	'close-filled': '\ue434',
+	'clear': '\ue434',
+	'refresh-filled': '\ue437',
+	'star-filled': '\ue438',
+	'plus-filled': '\ue439',
+	'minus-filled': '\ue440',
+	'circle-filled': '\ue441',
+	'checkbox-filled': '\ue442',
+	'closeempty': '\ue460',
+	'refreshempty': '\ue461',
+	'reload': '\ue462',
+	'starhalf': '\ue463',
+	'spinner': '\ue464',
+	'spinner-cycle': '\ue465',
+	'search': '\ue466',
+	'plusempty': '\ue468',
+	'forward': '\ue470',
+	'back': '\ue471',
+	'left-nav': '\ue471',
+	'checkmarkempty': '\ue472',
+	'home': '\ue500',
+	'navigate': '\ue501',
+	'gear': '\ue502',
+	'paperplane': '\ue503',
+	'info': '\ue504',
+	'help': '\ue505',
+	'locked': '\ue506',
+	'more': '\ue507',
+	'flag': '\ue508',
+	'home-filled': '\ue530',
+	'gear-filled': '\ue532',
+	'info-filled': '\ue534',
+	'help-filled': '\ue535',
+	'more-filled': '\ue537',
+	'settings': '\ue560',
+	'list': '\ue562',
+	'bars': '\ue563',
+	'loop': '\ue565',
+	'paperclip': '\ue567',
+	'eye': '\ue568',
+	'arrowup': '\ue580',
+	'arrowdown': '\ue581',
+	'arrowleft': '\ue582',
+	'arrowright': '\ue583',
+	'arrowthinup': '\ue584',
+	'arrowthindown': '\ue585',
+	'arrowthinleft': '\ue586',
+	'arrowthinright': '\ue587',
+	'pulldown': '\ue588',
+	'closefill': '\ue589',
+	'sound': '\ue590',
+	'scan': '\ue612'
+}

File diff suppressed because it is too large
+ 10 - 0
components/uni-icons/uni-icons.vue


+ 194 - 0
components/uni-load-more/uni-load-more.vue

@@ -0,0 +1,194 @@
+<template>
+	<view class="uni-load-more">
+		<view class="uni-load-more__img" v-show="status === 'loading' && showIcon">
+			<view class="load1">
+				<view :style="{background:color}"></view>
+				<view :style="{background:color}"></view>
+				<view :style="{background:color}"></view>
+				<view :style="{background:color}"></view>
+			</view>
+			<view class="load2">
+				<view :style="{background:color}"></view>
+				<view :style="{background:color}"></view>
+				<view :style="{background:color}"></view>
+				<view :style="{background:color}"></view>
+			</view>
+			<view class="load3">
+				<view :style="{background:color}"></view>
+				<view :style="{background:color}"></view>
+				<view :style="{background:color}"></view>
+				<view :style="{background:color}"></view>
+			</view>
+		</view>
+		<text class="uni-load-more__text" :style="{color:color}">{{status === 'more' ? contentText.contentdown : (status === 'loading' ? contentText.contentrefresh : contentText.contentnomore)}}</text>
+	</view>
+</template>
+
+<script>
+	export default {
+		name: "uni-load-more",
+		props: {
+			status: {
+				//上拉的状态:more-loading前;loading-loading中;noMore-没有更多了
+				type: String,
+				default: 'more'
+			},
+			showIcon: {
+				type: Boolean,
+				default: true
+			},
+			color: {
+				type: String,
+				default: "#777777"
+			},
+			contentText: {
+				type: Object,
+				default () {
+					return {
+						contentdown: "上拉显示更多",
+						contentrefresh: "正在加载...",
+						contentnomore: "没有更多数据了"
+					};
+				}
+			}
+		},
+		data() {
+			return {}
+		}
+	}
+</script>
+
+<style>
+	@charset "UTF-8";
+
+	.uni-load-more {
+		display: flex;
+		flex-direction: row;
+		height: 80upx;
+		align-items: center;
+		justify-content: center
+	}
+
+	.uni-load-more__text {
+		font-size: 28upx;
+		color: #999
+	}
+
+	.uni-load-more__img {
+		height: 24px;
+		width: 24px;
+		margin-right: 10px
+	}
+
+	.uni-load-more__img>view {
+		position: absolute
+	}
+
+	.uni-load-more__img>view view {
+		width: 6px;
+		height: 2px;
+		border-top-left-radius: 1px;
+		border-bottom-left-radius: 1px;
+		background: #999;
+		position: absolute;
+		opacity: .2;
+		transform-origin: 50%;
+		animation: load 1.56s ease infinite
+	}
+
+	.uni-load-more__img>view view:nth-child(1) {
+		transform: rotate(90deg);
+		top: 2px;
+		left: 9px
+	}
+
+	.uni-load-more__img>view view:nth-child(2) {
+		transform: rotate(180deg);
+		top: 11px;
+		right: 0
+	}
+
+	.uni-load-more__img>view view:nth-child(3) {
+		transform: rotate(270deg);
+		bottom: 2px;
+		left: 9px
+	}
+
+	.uni-load-more__img>view view:nth-child(4) {
+		top: 11px;
+		left: 0
+	}
+
+	.load1,
+	.load2,
+	.load3 {
+		height: 24px;
+		width: 24px
+	}
+
+	.load2 {
+		transform: rotate(30deg)
+	}
+
+	.load3 {
+		transform: rotate(60deg)
+	}
+
+	.load1 view:nth-child(1) {
+		animation-delay: 0s
+	}
+
+	.load2 view:nth-child(1) {
+		animation-delay: .13s
+	}
+
+	.load3 view:nth-child(1) {
+		animation-delay: .26s
+	}
+
+	.load1 view:nth-child(2) {
+		animation-delay: .39s
+	}
+
+	.load2 view:nth-child(2) {
+		animation-delay: .52s
+	}
+
+	.load3 view:nth-child(2) {
+		animation-delay: .65s
+	}
+
+	.load1 view:nth-child(3) {
+		animation-delay: .78s
+	}
+
+	.load2 view:nth-child(3) {
+		animation-delay: .91s
+	}
+
+	.load3 view:nth-child(3) {
+		animation-delay: 1.04s
+	}
+
+	.load1 view:nth-child(4) {
+		animation-delay: 1.17s
+	}
+
+	.load2 view:nth-child(4) {
+		animation-delay: 1.3s
+	}
+
+	.load3 view:nth-child(4) {
+		animation-delay: 1.43s
+	}
+
+	@-webkit-keyframes load {
+		0% {
+			opacity: 1
+		}
+
+		100% {
+			opacity: .2
+		}
+	}
+</style>

+ 205 - 0
components/uni-number-box.vue

@@ -0,0 +1,205 @@
+<template>
+	<view class="uni-numbox" @click.stop="nothing">
+		<view class="uni-numbox-minus" 
+			@click="_calcValue('subtract')"
+		>
+			<text class="yticon icon-jianhao" :class="isMin?'uni-numbox-disabled': ''" ></text>
+			<!-- <text class="yticon icon-jianhao" :class="minDisabled?'uni-numbox-disabled': ''" ></text> -->
+		</view>
+		<input 
+			class="uni-numbox-value" 
+			type="number" 
+			:disabled="disabled"
+			:value="inputValue"
+			@input="_onBlur"
+			@blur="_onBlur"
+		>
+		<view 
+			class="uni-numbox-plus" 
+			@click="_calcValue('add')"
+		>
+			<text class="yticon icon-jiahao" :class="isMax?'uni-numbox-disabled': ''" ></text>
+			<!-- <text class="yticon icon-jiahao" :class="maxDisabled?'uni-numbox-disabled': ''" ></text> -->
+		</view>
+	</view>
+</template>
+<script>
+	export default {
+		name: 'uni-number-box',
+		props: {
+			isMax: {
+				type: Boolean,
+				default: false
+			},
+			isMin: {
+				type: Boolean,
+				default: false
+			},
+			index: {
+				type: Number,
+				default: 0
+			},
+			value: {
+				type: Number,
+				default: 0
+			},
+			min: {
+				type: Number,
+				default: -Infinity
+			},
+			max: {
+				type: Number,
+				default: Infinity
+			},
+			step: {
+				type: Number,
+				default: 1
+			},
+			disabled: {
+				type: Boolean,
+				default: false
+			}
+		},
+		data() {
+			return {
+				inputValue: this.value,
+				minDisabled: false,
+				maxDisabled: false
+			}
+		},
+		created(){
+			this.maxDisabled = this.isMax;
+			this.minDisabled = this.isMin;
+		},
+		computed: {
+			
+		},
+		watch: {
+			value(number){
+				this.inputValue = number;
+			},
+			inputValue(number) {
+				const data = {
+					number: number,
+					index: this.index
+				}
+				this.$emit('eventChange', data);
+			}
+		},
+		methods: {
+			nothing(){},
+			_calcValue(type) {
+				const scale = this._getDecimalScale();
+				let value = this.inputValue * scale;
+				let newValue = 0;
+				let step = this.step * scale;
+				
+				if(type === 'subtract'){
+					newValue = value - step;
+					if (newValue <= this.min){
+						this.minDisabled = true;
+					}
+					if(newValue < this.min){
+						newValue = this.min
+					}
+					if(newValue < this.max && this.maxDisabled === true){
+						this.maxDisabled = false;
+					}
+				}else if(type === 'add'){
+					newValue = value + step;
+					if (newValue >= this.max){
+						this.maxDisabled = true;
+					}
+					if(newValue > this.max){
+						newValue = this.max
+					}
+					if(newValue > this.min && this.minDisabled === true){
+						this.minDisabled = false;
+					}
+				}
+				if(newValue === value){
+					return;
+				}
+				this.inputValue = newValue / scale;
+			},
+			_getDecimalScale() {
+				let scale = 1;
+				// 浮点型
+				if (~~this.step !== this.step) {
+					scale = Math.pow(10, (this.step + '').split('.')[1].length);
+				}
+				return scale;
+			},
+			_onBlur(event) {
+				let value = event.detail.value;
+				if (!value) {
+					this.inputValue = 0;
+					return
+				}
+				value = +value;
+				if (value > this.max) {
+					value = this.max;
+				} else if (value < this.min) {
+					value = this.min
+				}
+
+				this.inputValue = value
+			}
+		}
+	}
+</script>
+<style>
+	.uni-numbox {
+		position:absolute;
+		left: 30upx;
+		bottom: 0;
+		display: flex;
+		justify-content: flex-start;
+		align-items: center;
+		width:230upx;
+		height: 70upx;
+		background:#f5f5f5;
+	}
+
+	.uni-numbox-minus,
+	.uni-numbox-plus {
+		margin: 0;
+		background-color: #f5f5f5;
+		width: 70upx;
+		height: 100%;
+		line-height: 70upx;
+		text-align: center;
+		position: relative;
+	}
+	.uni-numbox-minus .yticon,
+	.uni-numbox-plus .yticon{
+		font-size: 36upx;
+		color: #555;
+	}
+
+	.uni-numbox-minus {
+		border-right: none;
+		border-top-left-radius: 6upx;
+		border-bottom-left-radius: 6upx;
+	}
+
+	.uni-numbox-plus {
+		border-left: none;
+		border-top-right-radius: 6upx;
+		border-bottom-right-radius: 6upx;
+	}
+
+	.uni-numbox-value {
+		position: relative;
+		background-color: #f5f5f5;
+		width: 90upx;
+		height: 50upx;
+		text-align: center;
+		padding: 0;
+		font-size: 30upx;
+	}
+
+	.uni-numbox-disabled.yticon {
+		color: #d6d6d6;
+	}
+</style>

+ 142 - 0
components/uni-rate/uni-rate.vue

@@ -0,0 +1,142 @@
+<template>
+	<view class="uni-rate">
+		<view :key="index" :style="{ marginLeft: margin + 'px' }" @click="_onClick(index)" class="uni-rate__icon" v-for="(star, index) in stars">
+			<uni-icons :color="color" :size="size" :type="isFill ? 'star-filled' : 'star'" />
+			<!-- #ifdef APP-NVUE -->
+			<view :style="{ width: star.activeWitch.replace('%','')*size/100+'px'}" class="uni-rate__icon-on">
+				<uni-icons style="text-align: left;" :color="activeColor" :size="size" type="star-filled" />
+			</view>
+			<!-- #endif -->
+			<!-- #ifndef APP-NVUE -->
+			<view :style="{ width: star.activeWitch,top:-size/2+'px' }" class="uni-rate__icon-on">
+				<uni-icons :color="activeColor" :size="size" type="star-filled" />
+			</view>
+			<!-- #endif -->
+		</view>
+	</view>
+</template>
+
+<script>
+	import uniIcons from "../uni-icons/uni-icons.vue";
+	export default {
+		name: "UniRate",
+		components: {
+			uniIcons
+		},
+		props: {
+			isFill: {
+				// 星星的类型,是否镂空
+				type: [Boolean, String],
+				default: true
+			},
+			color: {
+				// 星星的颜色
+				type: String,
+				default: "#ececec"
+			},
+			activeColor: {
+				// 星星选中状态颜色
+				type: String,
+				default: "#ffca3e"
+			},
+			size: {
+				// 星星的大小
+				type: [Number, String],
+				default: 24
+			},
+			value: {
+				// 当前评分
+				type: [Number, String],
+				default: 0
+			},
+			max: {
+				// 最大评分
+				type: [Number, String],
+				default: 5
+			},
+			margin: {
+				// 星星的间距
+				type: [Number, String],
+				default: 0
+			},
+			disabled: {
+				// 是否可点击
+				type: [Boolean, String],
+				default: false
+			}
+		},
+		data() {
+			return {
+				valueSync: ""
+			};
+		},
+		computed: {
+			stars() {
+				const value = this.valueSync ? this.valueSync : 0;
+				const starList = [];
+				const floorValue = Math.floor(value);
+				const ceilValue = Math.ceil(value);
+				// console.log("ceilValue: " + ceilValue);
+				// console.log("floorValue: " + floorValue);
+				for (let i = 0; i < this.max; i++) {
+					if (floorValue > i) {
+						starList.push({
+							activeWitch: "100%"
+						});
+					} else if (ceilValue - 1 === i) {
+						starList.push({
+							activeWitch: (value - floorValue) * 100 + "%"
+						});
+					} else {
+						starList.push({
+							activeWitch: "0"
+						});
+					}
+				}
+				//console.log("starList[4]: " + starList[4].activeWitch);
+				return starList;
+			}
+		},
+		created() {
+			this.valueSync = Number(this.value);
+		},
+		methods: {
+			_onClick(index) {
+				if (this.disabled) {
+					return;
+				}
+				this.valueSync = index + 1;
+				this.$emit("change", {
+					value: this.valueSync
+				});
+			}
+		}
+	};
+</script>
+
+<style lang="scss" scoped>
+	.uni-rate {
+		/* #ifndef APP-NVUE */
+		display: flex;
+		/* #endif */
+		line-height: 0;
+		font-size: 0;
+		flex-direction: row;
+	}
+
+	.uni-rate__icon {
+		position: relative;
+		line-height: 0;
+		font-size: 0;
+		display: inline-block;
+	}
+
+	.uni-rate__icon-on {
+		overflow: hidden;
+		position: absolute;
+		top: 0;
+		left: 0;
+		line-height: 1;
+		text-align: left;
+	}
+</style>

+ 226 - 0
components/upload-images.vue

@@ -0,0 +1,226 @@
+<template>
+	<view class="upload-content">
+		<block v-for="(item, index) in imageList" :key="index">
+			<view class="upload-item">
+				<image class="upload-img" :src="item.filePath" mode="aspectFill" @click="previewImage(index)"></image>
+				<image class="upload-del-btn" 
+					@click="delImage(index)" 
+					src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACYAAAAmCAYAAACoPemuAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoV2luZG93cykiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6RjNBODgzQjUwNDM5MTFFOUJDMjlGN0UwRTJGMjVCNjQiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6RjNBODgzQjYwNDM5MTFFOUJDMjlGN0UwRTJGMjVCNjQiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDpGM0E4ODNCMzA0MzkxMUU5QkMyOUY3RTBFMkYyNUI2NCIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDpGM0E4ODNCNDA0MzkxMUU5QkMyOUY3RTBFMkYyNUI2NCIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PuYQTIAAAAHNSURBVHjazJgxasMwFIbjnsD4BDmCLxDQDZrNQ5bcILlBvXvpDRLIWmiHFkyH2t0LyVbwEkPpbE/eYlUCGYQax096UuIffkIgij7ryU/vyZuYy2e+Z56K70R8lsJcOfPn5AriMCvmZ2YKdMW8EQ/hRA9iEorwXlpZtObMRySQ6kysvrFWloHUEIcmUBuHUDLccmxQskErt7wyVLdy00tQ4Q2g5De2V9kNwWjffiNDA33f/42i6E13Qj5uNptlwJD+SyODuaqqqi/KVBTFOxSKA7VtW/NxSZI8AcbE2nuraZpvKgSBk6G4drsdBOwogz1CwAghH/JEl+BUKJ1VltPHHjoIAoeE4l53YFqb+RKcBSgqyqXhtxEKZwkKB3YOThYCqjPuCDoHZwGK3mGLtdPp5LmqTI1Dqe4p26EkNqA4iE6eg2z+iQ0okyQMATvYgLIIt9Y6knTyFBIudHqIq3BpmkL60VJ9O0sXZY8MBwSLtdNGEAQ/i8XiVXcz83EcEPDbuq/fzMdYWt+6GTmMsX2rh9q3TtsxNrzXhKt1rwg6rR1Dhdjqo7QMlWOvoWTF4imxQMRF7eaL8L5ohmwrLgDB8pCQc8DlcG7y538CDABJNGPqfaJgfgAAAABJRU5ErkJggg==" 
+					mode="scaleToFill">
+				</image>
+				<view class="upload-progress" v-if="item.progress < 100">{{item.progress}}%</view>
+			</view>
+		</block>
+		<view class="upload-add-btn" v-if="rduLength > 0" @click="chooseImage"></view>
+	</view>
+</template>
+
+<script>
+export default {
+	data() {
+		return {
+			imageList: []
+		};
+	},
+	props: {
+		url: {
+			type: String,
+			value: '' //上传接口地址
+		},
+		count: {
+			type: Number,
+			value: 4 //单次可选择的图片数量
+		},
+		length: {
+			type: Number,
+			value: 50 //可上传总数量
+		}
+	},
+	computed: {
+		rduLength(){
+			return this.length - this.imageList.length;
+		}
+	},
+	methods: {
+		//选择图片
+		chooseImage: function(){
+			uni.chooseImage({
+				count: this.rduLength < this.count ? this.rduLength : this.count, //最多可以选择的图片张数,默认9
+				sizeType: ['original', 'compressed'], //original 原图,compressed 压缩图,默认二者都有
+				sourceType: ['album'], //album 从相册选图,camera 使用相机,默认二者都有
+				success: (res)=> {
+					const images = res.tempFilePaths;
+					this.uploadFiles(images);
+				}
+			});
+		},
+		//上传图片
+		async uploadFiles(images){
+			this.imageList.push({
+				filePath: images[0],
+				progress: 0
+			});
+			uni.showLoading({
+				title: '请稍后..',
+				mask: true,
+			})
+			try{
+				const uploadUrl = await this.uploadImage(images[0]);
+			}catch(err){
+				console.log(err);
+				return;
+			}
+			
+			if(uploadUrl !== false){
+				images.splice(0, 1);
+				this.imageList[this.imageList.length - 1].src = uploadUrl;
+
+				//判断是否需要继续上传
+				if(images.length > 0 && this.rduLength > 0){
+					this.uploadFiles(images);
+				}else{
+					uni.hideLoading();
+				}
+			}else{
+				//上传失败处理
+				this.imageList.pop();
+				uni.hideLoading();
+				uni.showToast({
+					title: '上传中出现问题,已终止上传',
+					icon: 'none',
+					mask: true,
+					duration: 2000
+				});
+			}
+		},
+		uploadImage: function(file){
+			return new Promise((resolve, reject)=> {
+				//发送给后端的附加参数
+				const formData = {
+					thumb_mode: 1,  
+				};
+				this.uploadTask = uni.uploadFile({
+					url: this.url, 
+					filePath: file,
+					name: 'file',
+					formData: formData,
+					success(uploadFileResult){
+						const uploadFileRes = JSON.parse(uploadFileResult.data) || {};
+						if(uploadFileRes.status === 1 && uploadFileRes.data){
+							resolve(uploadFileRes.data);
+						}else{
+							reject('接口返回错误');
+						}
+					}, 
+					fail(){
+						reject('网络链接错误');
+					}
+				});
+				//上传进度
+				this.uploadTask.onProgressUpdate((progressRes)=> {
+					this.imageList[this.imageList.length - 1].progress = progressRes.progress;
+				});
+			});
+		},
+		//删除图片
+		delImage: function(index){
+			uni.showModal({
+				content: '确定要放弃这张图片么?',
+				success: (confirmRes)=> {
+					if (confirmRes.confirm) {
+						this.imageList.splice(index, 1);
+					} 
+				}
+			});
+		},
+		//预览图片
+		previewImage: function(index){
+			const urls = [];
+			this.imageList.forEach((item)=> {
+				urls.push(item.filePath);
+			})
+			uni.previewImage({
+				current: urls[index],
+				urls: urls,
+				indicator: "number"
+			})
+		}
+	}
+}
+</script>
+
+<style lang="scss">
+	.upload-content{
+		padding:24upx 0 0 28upx;
+		background-color: #fff;
+		overflow:hidden;
+	}
+	.upload-item{
+		position: relative;
+		float:left;
+		width:150upx;
+		height:150upx;
+		margin-right:30upx;
+		margin-bottom:30upx;
+		&:nth-child(4n){
+			margin-right:0;
+		}
+		.upload-img{
+			width:100%;
+			height:100%;
+			border-radius:8upx;
+		}
+		.upload-del-btn{
+			position: absolute;
+			right:-16upx;
+			top:-14upx;
+			width:36upx;
+			height:36upx;
+			border: 4upx solid #fff;
+			border-radius: 100px;
+		}
+		.upload-progress{
+			position: absolute;
+			left:0;
+			top:0;
+			display:flex;
+			align-items:center;
+			justify-content: center;
+			width:100%;
+			height:100%;
+			background-color: rgba(0,0,0,.4);
+			color:#fff;
+			font-size:24upx;
+			border-radius:8upx;
+		}
+	}
+	.upload-add-btn {
+		position: relative;
+		float:left;
+		width: 150upx;
+		height: 150upx;
+		z-index: 99;
+		border-radius:8upx;
+		background:#f9f9f9;
+		&:before,
+		&:after {
+			content: " ";
+			position: absolute;
+			top: 50%;
+			left: 50%;
+			-webkit-transform: translate(-50%, -50%);
+			transform: translate(-50%, -50%);
+			width: 4upx;
+			height: 60upx;
+			background-color: #d6d6d6;
+		}
+		&:after {
+			width: 60upx;
+			height: 4upx;
+		}
+		&:active {
+			background-color: #f7f7f7;
+		}
+	}
+
+</style>

+ 13 - 0
components/z-table/table-render.js

@@ -0,0 +1,13 @@
+import Vue from 'vue'
+
+Vue.mixin({
+	methods: {
+		tableNameRender(h, {
+			row,
+			col
+		}) {
+			console.log(row)
+			return h('view', row.name)
+		}
+	}
+})

+ 782 - 0
components/z-table/z-table.vue

@@ -0,0 +1,782 @@
+<template>
+	<view class="z-table">
+		<view class="z-table-main" :style="compluteHeight">
+			<view v-if="!tableLoaded && (!tableData || !columns)" :class="['z-loading', {ztableLoading: tableShow}]">
+				<view class="z-loading-animate"></view>
+			</view>
+			<view class="z-table-container">
+				<view class="z-table-pack">
+					<view class="z-table-title">
+						<view class="z-table-title-item" :class="{ 'z-table-stick-side': stickSide && index == 0 }" :style="{ width: item.width ? item.width + 'rpx' : '200rpx' }"
+						 v-for="(item, index) in columns" :key="index" @click="sort(item.key, index)">
+							<view v-if="showSelect && !singleSelect && index === 0" class="select-box" @click="doSelect(true)">
+								<view :class="['select-tip', {'selected': selectAll}]"></view>
+							</view>
+							<view :class="['z-table-col-text', {'text-left': titleTextAlign === 'left', 'text-center': titleTextAlign === 'center', 'text-right': titleTextAlign === 'right'}]">
+								<view v-html="getTitleText(item.title)"></view>
+								<view v-if="item.hasOwnProperty('key') && item.hasOwnProperty('sort') && tableData.length" class="sort">
+									<view class="up-arrow" :class="{ action: nowSortKey == item.key && sortType == 'asc' }"></view>
+									<view class="down-arrow" :class="{ action: nowSortKey == item.key && sortType == 'desc' }"></view>
+								</view>
+							</view>
+						</view>
+					</view>
+					<view v-if="tableData.length" :class="['table-container-box', {'short-table': !longTable && showBottomSum}]">
+						<view class="z-table-container-row" :class="{ 'z-table-has-bottom': showBottomSum }" v-for="(row, iIndex) in tableData"
+						 :key="iIndex">
+							<view :class="['z-table-container-col', { 'z-table-stick-side': stickSide && jIndex == 0 }]" :style="{ width: col.width ? col.width + 'rpx' : '200rpx' }"
+							 v-for="(col, jIndex) in columns" :key="jIndex" @click="itemClick(row, col)">
+								<view v-if="showSelect && jIndex === 0" class="select-box" @click="doSelect(false, iIndex)">
+									<view :class="['select-tip', {'selected': selectArr.includes(iIndex)}]"></view>
+								</view>
+								<view :class="['z-table-col-text', {'text-left': textAlign === 'left', 'text-center': textAlign === 'center', 'text-right': textAlign === 'right'}]">
+									<view v-if="!col.isLink" v-html="getRowContent(row, col)">
+										<!-- <view v-if="!col.render" v-html="getRowContent(row, col)"></view> -->
+										<!-- <renderComponents v-else :row="row" :col="col" /> -->
+									</view>
+									<!-- #ifdef H5 -->
+									<router-link v-else-if="setUrl(row, col).indexOf('http') != 0" :to="setUrl(row, col)" v-html="getRowContent(row, col)"></router-link>
+									<a v-else-if="col.isLink" :href="setUrl(row, col)" v-html="getRowContent(row, col)"></a>
+									<!-- #endif -->
+									<!-- #ifndef H5 -->
+									<navigator v-else-if="col.isLink" :url="setUrl(row, col)" v-html="getRowContent(row, col)"></navigator>
+									<!-- #endif -->
+								</view>
+							</view>
+						</view>
+					</view>
+					<view :class="['z-table-bottom', {'long-table': longTable}]" v-if="showBottomSum && tableData.length">
+						<view class="z-table-bottom-col" :class="{ 'z-table-stick-side': stickSide && sumIndex == 0 }" :style="{ width: sumCol.width ? sumCol.width + 'rpx' : '200rpx' }"
+						 v-for="(sumCol, sumIndex) in columns" :key="sumIndex">
+							<view class="z-table-bottom-text">
+								<!-- <view v-if="sumIndex != 0" class="z-table-bottom-text-title">{{ sumCol.title }}</view> -->
+								<text :class="{ sum: sumIndex == 0 }">{{ sumIndex == 0 ? '总计' : dosum(sumCol.key) }}</text>
+							</view>
+						</view>
+					</view>
+				</view>
+			</view>
+			<view v-if="tableData && tableData.length == 0 && !tableLoaded" class="table-empty">
+				<!-- image v-if="!showLoading" class="empty-img" src="../static/empty.png"></image -->
+				<view v-html="showLoading ? '' : emptyText"></view>
+			</view>
+		</view>
+	</view>
+</template>
+
+<script>
+	/*
+	 * 表格使用
+	 * 注意如果需要异步加载,需要把tableData初始值设为false,当没有数据的时候值为空数组
+	 * props: tableData [Array | Boolean] | 表格数据 如果为false则显示loading
+	 * 		 columns [Array | Boolean] | 数据映射表 如果为false则显示loading 每列params => title(表头文字可以是html字符串模版), width(每列宽度) [, key(对应tableData的字段名) || format(自定义内容), sort(是否要排序), isLink(是否显示为超链接Object)]
+	 * 										   format格式: {template: 字符串模版用#key#表示需要被替换的数据,names: 对应template属性内要被替换的内容的key}
+	 * 										   isLink格式: {url: 链接地址, params: 地址带的参数Array[key|value, key|value, ...]每一项都是key和value以'|'链接,如果不带'|'默认键值同名
+	 * 										   listenerClick(是否监听点击事件Boolean)}
+	 * 		 stickSide Boolean | 是否固定右侧首栏 默认不显示
+	 * 		 showBottomSum Boolean | 是否显示底部统计 默认不显示
+	 * 		 showLoading Boolean | 是否首次加载首次加载不显示暂无数据内容
+	 * 		 emptyText String | 空数据显示的文字内容
+	 *		 tableHeight Number | 设置表格高度会滚动
+	 *		 sort Boolean | 开启排序
+	 * 		 showSelect Boolean | 开启选择
+	 *		 singleSelect Boolean | 在开启选择的状态下是否开起单选
+	 * 		 textAlign String | 内容对齐方式 left center right
+	 * 		 titleTextAlign String | 表头对齐方式 left center right
+	 *
+	 * event: onSort | 排序事件 返回{key: 被排序列的字段名, type: 正序'asc'/倒序'desc'}
+	 *		  onSelect | 选中时触发 返回选择的行的下标
+	 * 		  onClick | 单元格点击事件 返回点击单元格所属行的数据
+	 *
+	 * function: resetSort | 调用后重置排序 *注意:不会触发sort事件
+	 *
+	 * */
+	import Vue from 'vue'
+	// import tableRender from './table-render'
+
+	export default {
+		data() {
+			return {
+				version: '1.1.0',
+				nowSortKey: '',
+				sortType: 'desc', // asc/desc 升序/降序
+				longTable: true,
+				lineHeight: uni.upx2px(64),
+				tableLoaded: false,
+				tableShow: true,
+				selectAll: false,
+				selectArr: []
+			}
+		},
+		// mixin: [tableRender],
+		computed: {
+			compluteHeight() {
+				return this.tableHeight ?
+					'height: ' + uni.upx2px(this.tableHeight) + 'px' :
+					''
+			}
+		},
+		props: {
+			tableData: {
+				type: [Array, Boolean],
+				default () {
+					return false
+				}
+			},
+			columns: {
+				/*
+				 *
+				 * [{title: xxx, key: 当前列展示对象名, width: 列宽, render: function}]
+				 *
+				 * */
+				type: [Array, Boolean],
+				required: true
+			},
+			stickSide: {
+				type: Boolean,
+				default: false
+			},
+			showBottomSum: {
+				type: Boolean,
+				default: false
+			},
+			showLoading: {
+				type: Boolean,
+				default: true
+			},
+			emptyText: {
+				type: String,
+				default: '暂无数据'
+			},
+			tableHeight: {
+				type: [Number, Boolean],
+				default: 0
+			},
+			showSelect: {
+				type: Boolean,
+				default: false
+			},
+			singleSelect: {
+				type: Boolean,
+				default: false
+			},
+			textAlign: {
+				type: String,
+				default: 'left' // right|center|left
+			},
+			titleTextAlign: {
+				type: String,
+				default: 'left' // right|center|left
+			}
+		},
+		mounted() {
+			this.init()
+		},
+		// components: {
+		// 	renderComponents: {
+		// 		functional: true,
+		// 		props: {
+		// 			row: {
+		// 				type: Object,
+		// 				required: true
+		// 			},
+		// 			col: {
+		// 				type: Object,
+		// 				required: true
+		// 			}
+		// 		},
+		// 		render: function(h, ctx) {
+		// 			return _this[ctx.props.col.render](h, ctx.props)
+		// 		}
+		// 	}
+		// },
+		watch: {
+			columns() {
+				this.init()
+			},
+			tableData() {
+				this.init()
+			}
+		},
+		methods: {
+			async init() {
+				// 重置选择内容
+				this.selectAll = false
+				this.selectArr = []
+				this.tableLoaded = false
+				this.tableShow = true
+				let _this = this
+				let container = await _this.getPageSize('.z-table-container'),
+					pack = await _this.getPageSize('.z-table-pack')
+				_this.timer && clearTimeout(_this.timer)
+				if (container && pack) {
+					_this.$nextTick(function() {
+						if (_this.tableData && _this.tableData.length) {
+							_this.tableShow = false
+							_this.timer = setTimeout(function() {
+								_this.tableLoaded = true
+							}, 300)
+						}
+					})
+					if (container.height != pack.height) {
+						_this.longTable = true
+					} else {
+						_this.longTable = false
+					}
+				} else {
+					_this.tableLoaded = false
+					_this.$nextTick(function() {
+						_this.tableShow = true
+					})
+				}
+			},
+			getPageSize(selecter) {
+				// 获取元素信息
+				let query = uni.createSelectorQuery().in(this),
+					_this = this
+				return new Promise((resolve, reject) => {
+					query
+						.select(selecter)
+						.boundingClientRect(res => {
+							resolve(res)
+						})
+						.exec()
+				})
+			},
+			dosum(key) {
+				let sum = '-'
+				if (this.tableData) {
+					if (
+						this.tableData.every(item => {
+							return !Number.isNaN(item[key] - 0)
+						})
+					) {
+						sum = 0
+						this.tableData.map((item, index) => {
+							if (!key && index != 0) {
+								sum = '-'
+							} else {
+								let val = item[key] - 0
+								if (Number.isNaN(val)) {
+									sum += 0
+								} else {
+									sum += val
+								}
+							}
+						})
+					}
+				}
+				// sum = sum == 0 ? "-" : sum
+				return this.numTransform(sum)
+			},
+			getRowContent(row, col) {
+				// 表格值处理函数
+				// 如果columns带了key则显示对应的key
+				// 如果columns带的format则按规定返回format后的html
+				// format规定: params names <Array> 对应tableData的键名,作为匹配template中两个#之间动态内容的名字
+				//			   params template <String> html字符串模版
+				let tempHTML = ''
+				let rowKey = row[col.key]
+				if ([null, ''].includes(rowKey)) {
+					rowKey = '-'
+				}
+				if (rowKey || rowKey === 0) {
+					tempHTML = isNaN(rowKey - 0) ?
+						rowKey :
+						this.numTransform(rowKey - 0)
+					// tempHTML = tempHTML == 0 ? "-" : tempHTML
+				} else if (!!col.format) {
+					let tempFormat = col.format.template
+					col.format.names.map(item => {
+						let regexp = new RegExp(`\#${item}\#`, 'mg')
+						tempFormat = tempFormat.replace(regexp, row[item])
+					})
+					tempHTML = tempFormat
+				} else if (!col.render) {
+					let error = new Error('数据的key或format值至少一个不为空')
+					throw error
+				}
+				// console.log(tempHTML)
+				return tempHTML.toString()
+			},
+			sort(key, index) {
+				if (!key || !this.columns[index].sort) {
+					return
+				}
+				// 排序功能: 如果点击的排序按钮是原先的 那么更改排序类型
+				//			如果点击的另一个排序按钮 那么选择当前排序并且排序类型改为降序(desc)
+				if (key != this.nowSortKey) {
+					this.nowSortKey = key
+					this.sortType = 'desc'
+				} else {
+					this.toggleSort()
+				}
+				this.$emit('onSort', {
+					key: this.nowSortKey,
+					type: this.sortType
+				})
+			},
+			toggleSort() {
+				this.sortType = this.sortType == 'asc' ? 'desc' : 'asc'
+			},
+			numTransform(n) {
+				if (Number.isNaN(n - 0)) {
+					return n
+				}
+				if (Math.abs(n) >= 100000000) {
+					n = Number((n / 100000000).toFixed(1)) + '亿'
+				} else if (Math.abs(n) >= 10000) {
+					n = Number((n / 10000).toFixed(1)) + '万'
+				}
+				return n.toString()
+			},
+			resetSort() {
+				// 重置排序状态
+				this.nowSortKey = ''
+				this.sortType = 'desc'
+			},
+			setUrl(row, col) {
+				if (!col.isLink) {
+					return
+				}
+				let urlParam = {}
+				let {
+					isLink: {
+						url,
+						params = []
+					}
+				} = col
+				params.forEach(item => {
+					if (~item.indexOf('|')) {
+						let temp = item.split('|')
+						urlParam[temp[0]] = row[temp[1]]
+					} else {
+						urlParam[item] = row[item]
+					}
+				})
+				url = this.setUrlParams(url, urlParam)
+				return url
+			},
+			setUrlParams(url, params) {
+				let tempUrl = url,
+					keyArr = Object.keys(params)
+				keyArr.forEach(item => {
+					tempUrl += `&${item}=${params[item]}`
+				})
+				tempUrl = tempUrl.replace(/\&/, '?')
+				return tempUrl
+			},
+			itemClick(row, col) {
+				if (col.listenerClick) {
+					this.$emit('onClick', row)
+				}
+			},
+			doSelect(isAll = false, index) {
+				let temp = new Set()
+				if (isAll) {
+					// 全选
+					if (!this.selectAll) {
+						for (let i = 0; i < this.tableData.length; i++) {
+							temp.add(i)
+						}
+					}
+				} else {
+					// if (!this.singleSelect) {
+					// 	this.selectArr.forEach(item => {
+					// 		temp.add(item)
+					// 	})
+					// }
+					this.selectArr.forEach(item => {
+						temp.add(item)
+					})
+					if (temp.has(index)) {
+						temp.delete(index)
+					} else {
+						if (this.singleSelect) {
+							temp.clear()
+						}
+						temp.add(index)
+					}
+				}
+				this.selectArr = Array.from(temp)
+				// console.log(this.selectArr)
+				if (this.selectArr.length == this.tableData.length) {
+					this.selectAll = true
+				} else {
+					this.selectAll = false
+				}
+				
+				this.$emit('onSelect', this.selectArr)
+			},
+			// 1.1.1
+			getTitleText(title) {
+				// 自定义表头
+				let tempHTML = title
+				return tempHTML.toString()
+			}
+		}
+	}
+</script>
+
+<style lang="scss">
+	.navigator-hover {
+		background: transparent;
+		opacity: 1;
+	}
+
+	@mixin ellipsis($num: 1) {
+		overflow: hidden;
+		text-overflow: ellipsis;
+
+		@if $num==1 {
+			white-space: nowrap;
+		}
+
+		@else {
+			display: -webkit-box;
+			-webkit-line-clamp: $num;
+			/* autoprefixer: off */
+			-webkit-box-orient: vertical;
+			/* autoprefixer: on */
+		}
+	}
+
+	// 三角形
+	%triangle-basic {
+		content: '';
+		height: 0;
+		width: 0;
+		overflow: hidden;
+	}
+
+	@mixin triangle($direction, $size, $borderColor) {
+		@extend %triangle-basic;
+
+		@if $direction==top {
+			border-bottom: $size solid $borderColor;
+			border-left: $size dashed transparent;
+			border-right: $size dashed transparent;
+			border-top: 0;
+		}
+
+		@else if $direction==right {
+			border-left: $size solid $borderColor;
+			border-top: $size dashed transparent;
+			border-bottom: $size dashed transparent;
+			border-right: 0;
+		}
+
+		@else if $direction==bottom {
+			border-top: $size solid $borderColor;
+			border-left: $size dashed transparent;
+			border-right: $size dashed transparent;
+			border-bottom: 0;
+		}
+
+		@else if $direction==left {
+			border-right: $size solid $borderColor;
+			border-top: $size dashed transparent;
+			border-bottom: $size dashed transparent;
+			border-left: 0;
+		}
+	}
+
+	a {
+		text-decoration: none;
+	}
+
+	.z-table {
+		position: relative;
+		display: inline-block;
+		height: 100%;
+		min-height: 130rpx;
+		width: 100%;
+		background: #fff;
+		border: solid 2rpx #ccc;
+		font-size: $uni-font-size-sm;
+		box-sizing: border-box;
+		transform: translateZ(0);
+
+		.z-table-main {
+			height: 100%;
+			box-sizing: border-box;
+		}
+
+		.z-table-container {
+			height: 100%;
+			overflow: scroll;
+			box-sizing: border-box;
+		}
+
+		.z-table-pack {
+			position: relative;
+			min-height: 100%;
+			width: fit-content;
+		}
+
+		.z-table-title {
+			position: sticky;
+			top: 0;
+			height: 64rpx;
+			z-index: 1;
+
+			.z-table-title-item {
+				border-bottom: solid 1rpx #dbdbdb;
+				background: #f8f8f8;
+			}
+
+			.z-table-stick-side {
+				position: sticky;
+				top: 0;
+				left: 0;
+				border-right: solid 1rpx #dbdbdb;
+				box-sizing: border-box;
+			}
+		}
+
+		.table-container-box.short-table {
+			padding-bottom: 48rpx;
+		}
+
+		.z-table-title,
+		.z-table-container-row {
+			display: flex;
+			width: fit-content;
+			white-space: nowrap;
+			box-sizing: border-box;
+
+			.z-table-title-item,
+			.z-table-container-col {
+				@include ellipsis();
+				display: inline-flex;
+				padding: 0 16rpx;
+				height: 64rpx;
+				align-items: center;
+				line-height: 64rpx;
+				box-sizing: border-box;
+			}
+		}
+
+		.z-table-container-row {
+			z-index: 0;
+			border-bottom: solid 1rpx #f4f4f4;
+			box-sizing: border-box;
+		}
+
+		.z-table-stick-side {
+			position: sticky;
+			left: 0;
+			background: #f7f9ff;
+			border-right: solid 1rpx #dbdbdb;
+			box-sizing: border-box;
+		}
+
+		.z-table-bottom {
+			position: absolute;
+			bottom: 0;
+			z-index: 9;
+			display: flex;
+			justify-items: center;
+			width: fit-content;
+			background: #4298f7 !important;
+			color: #fff !important;
+			white-space: nowrap;
+			box-sizing: border-box;
+
+			&.long-table {
+				position: sticky;
+			}
+
+			.z-table-stick-side {
+				background: #4298f7 !important;
+				box-sizing: border-box;
+			}
+
+			.z-table-bottom-col {
+				display: inline-flex;
+				align-items: center;
+				text-align: center;
+				padding: 16rpx;
+				box-sizing: border-box;
+			}
+
+			.z-table-bottom-text {
+				line-height: 100%;
+				box-sizing: border-box;
+			}
+
+			.z-table-bottom-text-title {
+				margin-bottom: 10rpx;
+				font-size: 22rpx;
+				color: #aad0ff;
+				box-sizing: border-box;
+			}
+
+			.sum {
+				margin-left: 14rpx;
+				font-size: 28rpx;
+				box-sizing: border-box;
+			}
+		}
+
+		.table-empty {
+			position: absolute;
+			top: 64rpx;
+			height: 64rpx;
+			line-height: 64rpx;
+			width: 100%;
+			text-align: center;
+		}
+
+		.sort {
+			display: flex;
+			padding: 5rpx;
+			flex-direction: column;
+			justify-content: center;
+
+			.up-arrow {
+				@include triangle(top, 10rpx, #ccc);
+				display: block;
+				margin-bottom: 5rpx;
+
+				&.action {
+					@include triangle(top, 10rpx, #4298f7);
+				}
+			}
+
+			.down-arrow {
+				@include triangle(bottom, 10rpx, #ccc);
+				display: block;
+
+				&.action {
+					@include triangle(bottom, 10rpx, #4298f7);
+				}
+			}
+		}
+
+		// 1.0.5
+		.z-loading {
+			position: absolute;
+			top: 0;
+			left: 0;
+			z-index: 2;
+			display: flex;
+			align-items: center;
+			justify-content: center;
+			height: 100%;
+			width: 100%;
+			background: #fff;
+			opacity: 0;
+			transition: all 0.3s;
+
+			&.ztableLoading {
+				opacity: 1;
+			}
+
+			.z-loading-animate {
+				position: relative;
+				display: inline-block;
+				width: 30rpx;
+				height: 30rpx;
+				margin-right: 20rpx;
+				border-radius: 100%;
+				border: solid 6rpx #ccc;
+				vertical-align: middle;
+				animation: rotate 1s ease-in-out infinite;
+
+				&::after {
+					content: '';
+					display: block;
+					position: absolute;
+					top: -10rpx;
+					z-index: 1;
+					background: #fff;
+					width: 20rpx;
+					height: 20rpx;
+					border-radius: 10rpx;
+				}
+			}
+
+			@keyframes rotate {
+				from {
+					transform: rotate(0deg);
+				}
+
+				to {
+					transform: rotate(360deg);
+				}
+			}
+		}
+
+		// 1.1.0
+		.select-box {
+			display: inline-block;
+			width: 26rpx;
+			height: 26rpx;
+			line-height: 14rpx;
+			margin-right: 15rpx;
+			border: solid 2rpx #4298f7;
+			border-radius: 4rpx;
+			background: #fff;
+			text-align: center;
+		}
+
+		.select-tip {
+			display: inline-block;
+			opacity: 0;
+			transform: rotate(90deg);
+			transition: all .3s;
+
+			&.selected {
+				position: relative;
+				top: 4rpx;
+				left: -4rpx;
+				height: 4rpx;
+				background: #4298f7;
+				width: 10rpx;
+				opacity: 1;
+				transform: rotate(45deg);
+
+				&:before,
+				&:after {
+					content: '';
+					position: absolute;
+					display: block;
+					height: 4rpx;
+					background: #4298f7;
+				}
+
+				&:before {
+					bottom: -2rpx;
+					left: -4rpx;
+					width: 8rpx;
+					transform: rotate(-90deg);
+				}
+
+				&:after {
+					bottom: 16rpx;
+					right: -16rpx;
+					width: 34rpx;
+					transform: rotate(-90deg);
+				}
+			}
+		}
+		
+		// 1.1.1
+		.z-table-col-text {
+			display: flex;
+			width: 100%;
+			flex: 1;
+			justify-content: flex-start;
+			align-content: center;
+			
+			&.text-center {
+				justify-content: center;
+			}
+			
+			&.text-right {
+				justify-content: flex-end;
+			}
+		}
+	}
+</style>

+ 225 - 0
main.js

@@ -0,0 +1,225 @@
+import Vue from 'vue'
+import store from './store'
+import App from './App'
+
+// 后端api地址
+Vue.prototype.$unishow = "http://unishop:8888/addons/unishop";
+//Vue.prototype.$unishow = "http://t.fastadmin-ceshi.com:8888/addons/unishop";
+//Vue.prototype.$unishow = "http://shop.weivee.com/addons/unishop";
+
+// 为了方便每次上传的时候忘记修改上面的参数
+uni.getSystemInfo({
+	success(res) { 
+		//console.log(res)
+		if (res.platform != "devtools") {
+			Vue.prototype.$unishow = "https://shop.weivee.com/addons/unishop";
+		}
+	}
+})
+
+
+
+// 平台号
+// #ifdef APP-PLUS
+Vue.prototype.$platform = 'APP-PLUS';
+// #endif
+// #ifdef H5
+Vue.prototype.$platform = 'H5';
+// #endif
+// #ifdef MP-WEIXIN
+Vue.prototype.$platform = 'MP-WEIXIN';
+// #endif
+// #ifdef MP-ALIPAY
+Vue.prototype.$platform = 'MP-ALIPAY';
+// #endif
+// #ifdef MP-BAIDU
+Vue.prototype.$platform = 'MP-BAIDU';
+// #endif
+// #ifdef MP-TOUTIAO
+Vue.prototype.$platform = 'MP-TOUTIAO';
+// #endif
+
+
+// 提示
+const msg = (title, duration = 3000, mask = false, icon = 'none') => {
+	//统一提示方便全局修改
+	if (Boolean(title) === false) {
+		return;
+	}
+	uni.showToast({
+		title,
+		duration,
+		mask,
+		icon
+	});
+	setTimeout(function() {
+		uni.hideToast();
+	}, duration)
+}
+
+// 返回上一页
+const prePage = () => {
+	let pages = getCurrentPages();
+	let prePage = pages[pages.length - 2];
+	// #ifdef H5
+	return prePage;
+	// #endif
+	return prePage.$vm;
+}
+
+// 检查有没有登录
+const checkLogin = () => {
+	return new Promise(resolve => {
+		if (Vue.prototype.$store.state.hasLogin == false) {
+			uni.showModal({
+				title: '温馨提示',
+				content: '你还没,请先登录',
+				success(res) {
+					if (res.confirm) {
+						// 账户秘密登录
+						let url = '/pages/public/login';
+						uni.navigateTo({
+							url: url
+						});
+					}
+					resolve(false);
+				}
+			})
+		} else {
+			resolve(true);
+		}
+	});
+}
+
+// 深拷贝
+const deepCopy = (p, c) => {
+	var c = c || {};
+	for (var i in p) {
+		if (typeof p[i] === "object") {
+			c[i] = (p[i].constructor === Array) ? [] : {};
+			deepCopy(p[i], c[i])
+		} else {
+			c[i] = p[i]
+		}
+	}
+	return c;
+}
+
+// 同步网络请求
+const request = async (url, method = 'GET', data = {}, showMsg = true) => {
+	let header = {
+		'content-type': 'application/x-www-form-urlencoded',
+		'lang': Vue.prototype.$store.state.lang,
+		'platform': Vue.prototype.$platform
+	};
+	if (Vue.prototype.$store.state.userInfo.token) {
+		header.token = Vue.prototype.$store.state.userInfo.token;
+	}
+	if (Vue.prototype.$store.state.cookie) {
+		header.cookie = Vue.prototype.$store.state.cookie;
+	}
+	var [error, res] = await uni.request({
+		url: Vue.prototype.$unishow + url,
+		method: method,
+		header: header,
+		data: data,
+		timeout: 5000
+	});
+	if (url == '/pay/submit'){
+		console.log(res);
+	}
+	return new Promise(function(revolve){
+		if (error) {
+			showMsg && msg(JSON.stringify(res));
+			revolve(false);
+		}
+		
+		if (res) {
+			if (res.header.hasOwnProperty('Set-Cookie')) {
+				let cookie = res.header['Set-Cookie'].replace("; path=/", "");
+				Vue.prototype.$store.commit('setCookie', cookie);
+			}
+			if (res.hasOwnProperty('data')) {
+				if (res.data.hasOwnProperty('code') && res.data.code == 401) {
+					// 未登录 或 登录失效
+					Vue.prototype.$store.commit('logout');
+				}
+				if (res.data.hasOwnProperty('code') && res.data.code == 1) {
+					if (res.data.msg) {
+						showMsg && msg(res.data.msg);
+					} else {
+						uni.hideToast();
+					}
+					
+					revolve(res.data.data);
+				} else {
+					if (res.data.hasOwnProperty('msg')) {
+						showMsg && msg(res.data.msg);
+					} else {
+						showMsg && msg('返回参数错误');
+					}
+					revolve(false);
+				}
+			} else {
+				showMsg && msg('不能识别数据');
+				revolve(false);
+			}
+		}
+	});
+	
+}
+
+// 跳转判断是否登录
+const navTo = (url, check = true) => {
+	if (check && !Vue.prototype.$store.state.hasLogin) {
+		url = '/pages/public/login';
+	}
+	uni.navigateTo({
+		url: url
+	});
+}
+
+Vue.config.productionTip = false
+Vue.prototype.$fire = new Vue();
+Vue.prototype.$store = store;
+Vue.prototype.$api = {
+	msg,
+	prePage,
+	checkLogin,
+	request,
+	deepCopy,
+	navTo
+};
+
+// #ifdef MP-WEIXIN
+// 微信小程序
+const wechatMiniLogin = async () => {
+	msg('登录中');
+	let [error, loginRes] = await uni.login({
+		provider: 'weixin'
+	});
+	if (loginRes.hasOwnProperty('code')) {
+		let data = await request('/user/authSession', 'GET', {
+			code: loginRes.code
+		});
+		if (data) {
+			if (data.hasOwnProperty('userInfo') && data.userInfo.token && data.userInfo.token != '') {
+				Vue.prototype.$store.commit('login', data.userInfo);
+				//Vue.prototype.$store.mutations.login(data.userInfo)
+			}
+		}
+		return true;
+	} else {
+		msg('登录失败');
+		return false;
+	}
+};
+Vue.prototype.$wechatMiniLogin = wechatMiniLogin;
+// #endif
+
+App.mpType = 'app'
+
+const app = new Vue({
+	...App
+})
+app.$mount()

+ 76 - 0
manifest.json

@@ -0,0 +1,76 @@
+{
+    "name" : "uni-shop",
+    "appid" : "__UNI__3E17984",
+    "description" : "",
+    "versionName" : "1.0.0",
+    "versionCode" : "100",
+    "transformPx" : false,
+    "app-plus" : {
+        /* 5+App特有相关 */
+        "usingComponents" : true,
+        "splashscreen" : {
+            "alwaysShowBeforeRender" : true,
+            "waiting" : true,
+            "autoclose" : true,
+            "delay" : 0
+        },
+        "modules" : {},
+        /* 模块配置 */
+        "distribute" : {
+            /* 应用发布信息 */
+            "android" : {
+                /* android打包配置 */
+                "permissions" : [
+                    "<uses-feature android:name=\"android.hardware.camera\"/>",
+                    "<uses-feature android:name=\"android.hardware.camera.autofocus\"/>",
+                    "<uses-permission android:name=\"android.permission.ACCESS_FINE_LOCATION\"/>",
+                    "<uses-permission android:name=\"android.permission.CALL_PHONE\"/>",
+                    "<uses-permission android:name=\"android.permission.CAMERA\"/>",
+                    "<uses-permission android:name=\"android.permission.CHANGE_NETWORK_STATE\"/>",
+                    "<uses-permission android:name=\"android.permission.CHANGE_WIFI_STATE\"/>",
+                    "<uses-permission android:name=\"android.permission.FLASHLIGHT\"/>",
+                    "<uses-permission android:name=\"android.permission.GET_ACCOUNTS\"/>",
+                    "<uses-permission android:name=\"android.permission.MODIFY_AUDIO_SETTINGS\"/>",
+                    "<uses-permission android:name=\"android.permission.MOUNT_UNMOUNT_FILESYSTEMS\"/>",
+                    "<uses-permission android:name=\"android.permission.READ_CONTACTS\"/>",
+                    "<uses-permission android:name=\"android.permission.READ_LOGS\"/>",
+                    "<uses-permission android:name=\"android.permission.RECORD_AUDIO\"/>",
+                    "<uses-permission android:name=\"android.permission.VIBRATE\"/>",
+                    "<uses-permission android:name=\"android.permission.WAKE_LOCK\"/>",
+                    "<uses-permission android:name=\"android.permission.WRITE_CONTACTS\"/>",
+                    "<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>"
+                ]
+            },
+            "ios" : {},
+            /* ios打包配置 */
+            "sdkConfigs" : {
+                "payment" : {}
+            }
+        },
+        "nativePlugins" : {}
+    },
+    /* SDK配置 */
+    "quickapp" : {},
+    /* 快应用特有相关 */
+    "mp-weixin" : {
+        /* 小程序特有相关 */
+        "usingComponents" : true,
+        "appid" : "wx73b3aa7f870c7d5c",
+        "setting" : {
+            "urlCheck" : false
+        },
+        "permission" : {}
+    },
+    "h5" : {
+        "title" : "喂喂商城",
+        "domain" : "shop.weivee.com",
+        "router" : {
+            "base" : "/h5/"
+        },
+        "template" : "template.h5.html",
+        "devServer" : {
+            "disableHostCheck" : true,
+            "https" : false
+        }
+    }
+}

+ 30 - 0
node_modules/jweixin-module/README.md

@@ -0,0 +1,30 @@
+# jweixin-module
+
+微信JS-SDK
+
+## 安装
+
+### NPM
+
+```shell
+npm install jweixin-module --save
+```
+
+### UMD
+
+```http
+https://unpkg.com/jweixin-module/out/index.js
+```
+
+## 使用
+
+```js
+var jweixin = require('jweixin-module')
+jweixin.ready(function(){
+    // TODO
+});
+```
+
+## 完整API
+
+>[微信JS-SDK说明文档](https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421141115)

File diff suppressed because it is too large
+ 0 - 0
node_modules/jweixin-module/lib/index.js


+ 54 - 0
node_modules/jweixin-module/package.json

@@ -0,0 +1,54 @@
+{
+  "_from": "jweixin-module",
+  "_id": "jweixin-module@1.6.0",
+  "_inBundle": false,
+  "_integrity": "sha512-dGk9cf+ipipHmtzYmKZs5B2toX+p4hLyllGLF6xuC8t+B05oYxd8fYoaRz0T30U2n3RUv8a4iwvjhA+OcYz52w==",
+  "_location": "/jweixin-module",
+  "_phantomChildren": {},
+  "_requested": {
+    "type": "tag",
+    "registry": true,
+    "raw": "jweixin-module",
+    "name": "jweixin-module",
+    "escapedName": "jweixin-module",
+    "rawSpec": "",
+    "saveSpec": null,
+    "fetchSpec": "latest"
+  },
+  "_requiredBy": [
+    "#USER",
+    "/"
+  ],
+  "_resolved": "https://registry.npmjs.org/jweixin-module/-/jweixin-module-1.6.0.tgz",
+  "_shasum": "4a7ea614083e3c9c3f49e2fdc2bb882cfa58dfcd",
+  "_spec": "jweixin-module",
+  "_where": "/Users/zhengmingwei/Desktop/Project/uni-app/uni-shop",
+  "author": {
+    "name": "Shengqiang Guo"
+  },
+  "bugs": {
+    "url": "https://github.com/zhetengbiji/jweixin-module/issues"
+  },
+  "bundleDependencies": false,
+  "deprecated": false,
+  "description": "微信JS-SDK",
+  "devDependencies": {},
+  "homepage": "https://github.com/zhetengbiji/jweixin-module#readme",
+  "keywords": [
+    "wxjssdk",
+    "weixin",
+    "jweixin",
+    "wechat",
+    "jssdk",
+    "wx"
+  ],
+  "license": "ISC",
+  "main": "lib/index.js",
+  "name": "jweixin-module",
+  "repository": {
+    "type": "git",
+    "url": "git+https://github.com/zhetengbiji/jweixin-module.git"
+  },
+  "scripts": {},
+  "version": "1.6.0"
+}

+ 21 - 0
node_modules/mpvue-citypicker/LICENSE

@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2018 MPComponent
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.

+ 107 - 0
node_modules/mpvue-citypicker/README.md

@@ -0,0 +1,107 @@
+# mpvue-citypicker
+
+> 基于 mpvue 框架的城市选择器,含 code
+
+数据来源于 [Administrative-divisions-of-China](https://github.com/modood/Administrative-divisions-of-China),用 nodejs 对其数据进行了重组。
+
+![npm version](https://img.shields.io/npm/v/mpvue-citypicker.svg?style=flat)
+![download](https://img.shields.io/npm/dm/mpvue-citypicker.svg?style=flat)
+![license](https://img.shields.io/github/license/MPComponent/mpvue-citypicker.svg)
+
+## 使用
+
+* 安装
+``` bash
+npm install mpvue-citypicker --save
+```
+
+* 在页面中使用
+``` vue
+<template>
+    <mpvue-city-picker ref="mpvueCityPicker" :pickerValueDefault="pickerValueDefault" @onChange="onChange" @onCancel="onCancel" @onConfirm="onConfirm"></mpvue-city-picker>
+</template>
+
+<script>
+import mpvueCityPicker from 'mpvue-citypicker';
+export default {
+  data() {
+    return {
+      pickerValueDefault: [0, 0, 1]
+    };
+  },
+  components: {
+    mpvueCityPicker
+  },
+  methods: {
+    showCityPicker() {
+      this.$refs.mpvueCityPicker.show();
+    },
+    onChange(e) {
+      console.log(e);
+    },
+    onCancel(e) {
+      console.log(e);
+    },
+    onConfirm(e) {
+      console.log(e);
+    }
+  }
+};
+</script>
+```
+
+* 初始化
+
+在父组件中调用 ` mpvueCityPicker` 实例中的 `show` 方法即可
+
+``` javascript
+this.$refs.mpvueCityPicker.show();
+```
+
+## 效果
+<div align="center">
+  <img src="https://github.com/KuangPF/mpvue-citypicker/blob/master/data/img/mpvue-citypicker01.png" width="320px">
+  <img src="https://github.com/KuangPF/mpvue-citypicker/blob/master/data/img/mpvue-citypicker02.png" width="320px">
+</div>
+
+## 参数说明
+
+### pickerValueDefault
+* 说明:citypicker 默认选中值
+* 类型:Array
+* 可选值:-
+* 是否必填: 否
+* 默认值:[0, 0, 0]
+
+### themeColor
+* 说明:主题色
+* 类型:String
+* 可选值:-
+* 是否必填: 否
+* 默认值:#1aad19
+
+### onChange
+* 说明:citypicker 组件滚动时回调,返回选中的返回 label , value 以及 code 的值
+* 类型:EventHandle
+* 可选值:-
+* 是否必填: 否
+* 默认值:-
+
+### onConfirm
+* 说明:citypicker 组件点击确定时回调,返回选中的返回 label , value 以及 code 的值
+* 类型:EventHandle
+* 可选值:-
+* 是否必填: 否
+* 默认值:-
+
+### onCancel
+* 说明:citypicker 组件点击取消时回调,返回选中的返回 label , value 以及 code 的值
+* 类型:EventHandle
+* 可选值:-
+* 是否必填: 否
+* 默认值:-
+
+
+## 版本日志
+
+[version logs](https://github.com/MPComponent/mpvue-citypicker/releases)

+ 119 - 0
node_modules/mpvue-citypicker/package.json

@@ -0,0 +1,119 @@
+{
+  "_from": "mpvue-citypicker",
+  "_id": "mpvue-citypicker@1.0.6",
+  "_inBundle": false,
+  "_integrity": "sha512-V8TuUILYw7pFV0ii02tXKUtLmoa+mTxxJS85dEIc4sp85dVTLNi2GmXqBoz9MkT+QwKeOf3fPvFZzoucERz5rA==",
+  "_location": "/mpvue-citypicker",
+  "_phantomChildren": {},
+  "_requested": {
+    "type": "tag",
+    "registry": true,
+    "raw": "mpvue-citypicker",
+    "name": "mpvue-citypicker",
+    "escapedName": "mpvue-citypicker",
+    "rawSpec": "",
+    "saveSpec": null,
+    "fetchSpec": "latest"
+  },
+  "_requiredBy": [
+    "#USER",
+    "/"
+  ],
+  "_resolved": "https://registry.npmjs.org/mpvue-citypicker/-/mpvue-citypicker-1.0.6.tgz",
+  "_shasum": "861b9d4b1ed2245ad860d7e29dd2872d8fa7c74a",
+  "_spec": "mpvue-citypicker",
+  "_where": "/Users/zhengmingwei/Desktop/Project/uni-app/uni-shop",
+  "author": {
+    "name": "KuangPF",
+    "email": "1633397286@qq.com"
+  },
+  "browserslist": [
+    "> 1%",
+    "last 2 versions",
+    "not ie <= 8"
+  ],
+  "bugs": {
+    "url": "https://github.com/MPComponent/mpvue-citypicker/issues"
+  },
+  "bundleDependencies": false,
+  "dependencies": {},
+  "deprecated": false,
+  "description": "基于 mpvue 框架的城市选择器,含 code ",
+  "devDependencies": {
+    "babel-core": "^6.22.1",
+    "babel-eslint": "^8.2.3",
+    "babel-loader": "^7.1.1",
+    "babel-plugin-transform-runtime": "^6.22.0",
+    "babel-preset-env": "^1.3.2",
+    "babel-preset-stage-2": "^6.22.0",
+    "babel-register": "^6.22.0",
+    "chalk": "^2.4.0",
+    "connect-history-api-fallback": "^1.3.0",
+    "copy-webpack-plugin": "^4.5.1",
+    "css-loader": "^0.28.11",
+    "cssnano": "^3.10.0",
+    "eslint": "^4.19.1",
+    "eslint-config-standard": "^11.0.0",
+    "eslint-friendly-formatter": "^4.0.1",
+    "eslint-loader": "^2.0.0",
+    "eslint-plugin-html": "^4.0.3",
+    "eslint-plugin-import": "^2.11.0",
+    "eslint-plugin-node": "^6.0.1",
+    "eslint-plugin-promise": "^3.4.0",
+    "eslint-plugin-standard": "^3.0.1",
+    "eventsource-polyfill": "^0.9.6",
+    "express": "^4.16.3",
+    "extract-text-webpack-plugin": "^3.0.2",
+    "file-loader": "^1.1.11",
+    "friendly-errors-webpack-plugin": "^1.7.0",
+    "glob": "^7.1.2",
+    "html-webpack-plugin": "^3.2.0",
+    "http-proxy-middleware": "^0.18.0",
+    "mpvue": "^2.0.0",
+    "mpvue-loader": "^2.0.0",
+    "mpvue-template-compiler": "^2.0.0",
+    "mpvue-webpack-target": "^1.0.0",
+    "optimize-css-assets-webpack-plugin": "^3.2.0",
+    "ora": "^2.0.0",
+    "portfinder": "^1.0.13",
+    "postcss-loader": "^2.1.4",
+    "postcss-mpvue-wxss": "^1.0.0",
+    "prettier": "~1.12.1",
+    "px2rpx-loader": "^0.1.10",
+    "relative": "^3.0.2",
+    "rimraf": "^2.6.0",
+    "semver": "^5.3.0",
+    "shelljs": "^0.8.1",
+    "uglifyjs-webpack-plugin": "^1.2.5",
+    "url-loader": "^1.0.1",
+    "vue-style-loader": "^4.1.0",
+    "webpack": "^3.11.0",
+    "webpack-bundle-analyzer": "^2.2.1",
+    "webpack-dev-middleware-hard-disk": "^1.12.0",
+    "webpack-merge": "^4.1.0",
+    "webpack-mpvue-asset-plugin": "^0.1.1"
+  },
+  "engines": {
+    "node": ">= 4.0.0",
+    "npm": ">= 3.0.0"
+  },
+  "files": [
+    "src"
+  ],
+  "homepage": "https://github.com/MPComponent/mpvue-citypicker#readme",
+  "license": "MIT",
+  "main": "src/mpvueCityPicker.vue",
+  "name": "mpvue-citypicker",
+  "private": false,
+  "repository": {
+    "type": "git",
+    "url": "git+https://github.com/MPComponent/mpvue-citypicker.git"
+  },
+  "scripts": {
+    "build": "node build/build.js",
+    "dev": "node build/dev-server.js",
+    "lint": "eslint --ext .js,.vue src",
+    "start": "node build/dev-server.js"
+  },
+  "version": "1.0.6"
+}

+ 249 - 0
node_modules/mpvue-citypicker/src/mpvueCityPicker.vue

@@ -0,0 +1,249 @@
+<template>
+	<div class="mpvue-picker">
+		<div :class="{'pickerMask':showPicker}" @click="maskClick" catchtouchmove="true"></div>
+		<div class="mpvue-picker-content " :class="{'mpvue-picker-view-show':showPicker}">
+			<div class="mpvue-picker__hd" catchtouchmove="true">
+				<div class="mpvue-picker__action" @click="pickerCancel">取消</div>
+				<div class="mpvue-picker__action" :style="{color:themeColor}" @click="pickerConfirm">确定</div>
+			</div>
+			<picker-view indicator-style="height: 40px;" class="mpvue-picker-view" :value="pickerValue" @change="pickerChange">
+				<block>
+					<picker-view-column>
+						<div class="picker-item" v-for="(item,index) in provinceDataList" :key="index">{{item.label}}</div>
+					</picker-view-column>
+					<picker-view-column>
+						<div class="picker-item" v-for="(item,index) in cityDataList" :key="index">{{item.label}}</div>
+					</picker-view-column>
+					<picker-view-column>
+						<div class="picker-item" v-for="(item,index) in areaDataList" :key="index">{{item.label}}</div>
+					</picker-view-column>
+				</block>
+			</picker-view>
+		</div>
+	</div>
+</template>
+
+<script>
+	
+	export default {
+		data() {
+			return {
+				pickerValue: [0, 0, 0],
+				provinceDataList: [],
+				cityDataList: [],
+				areaDataList: [],
+				showPicker: false,
+				pickerValueDefault:[0,0,0]
+			};
+		},
+		async created() {
+			
+			// 微信小程序不需要这行 ,H5需要
+			// #ifndef MP
+			this.pickerValue = await this.handPickValueDefault();
+			this._$emit('onConfirm');
+			// #endif
+		},
+		props: {
+			/* 默认值 */
+			// pickerValueDefault: {
+			// 	type: Array,
+			// 	default: [0, 0, 0]
+			// },
+			/* 主题色 */
+			themeColor: {
+				type: String,
+				default: '#1aad19'
+			}
+		},
+		methods: {
+			async creat(pickerValueDefault) {
+				this.pickerValueDefault = pickerValueDefault;
+				this.pickerValue = await this.handPickValueDefault();
+				this._$emit('onConfirm');
+			},
+			show() {
+				setTimeout(() => {
+					this.showPicker = true;
+				}, 0);
+			},
+			maskClick() {
+				this.pickerCancel();
+			},
+			pickerCancel() {
+				this.showPicker = false;
+				this._$emit('onCancel');
+			},
+			pickerConfirm(e) {
+				this.showPicker = false;
+				this._$emit('onConfirm');
+			},
+			showPickerView() {
+				this.showPicker = true;
+			},
+			async handPickValueDefault() {
+				let tempPickerValue = this.pickerValueDefault;
+				console.log(tempPickerValue);
+				
+				this.provinceDataList = await this.$api.request('/address/area?pid=0');
+				this.cityDataList = await this.$api.request('/address/area?pid=' + (tempPickerValue[0] != 0 ? tempPickerValue[0] : this.provinceDataList[0].id));
+				this.areaDataList = await this.$api.request('/address/area?pid=' + (tempPickerValue[1] != 0 ? tempPickerValue[1] : this.cityDataList[0].id));
+		
+				for (let i in this.provinceDataList) {
+					if (this.provinceDataList[i].id == tempPickerValue[0]) {
+						tempPickerValue[0] = i;
+						break;
+					}
+				}
+				for (let i in this.cityDataList) {
+					if (this.cityDataList[i].id == tempPickerValue[1]) {
+						tempPickerValue[1] = i;
+						break;
+					}
+				}
+				for (let i in this.areaDataList) {
+					if (this.areaDataList[i].id == tempPickerValue[2]) {
+						tempPickerValue[2] = i;
+						break;
+					}
+				}
+				
+				return tempPickerValue;
+			},
+			async pickerChange(e) {
+				let changePickerValue = e.mp.detail.value;
+	
+				if (this.pickerValue[0] !== changePickerValue[0]) {
+					// 第一级发生滚动
+					// this.cityDataList = cityData[changePickerValue[0]];
+					// this.areaDataList = areaData[changePickerValue[0]][0];
+
+					let provinceId = this.provinceDataList[changePickerValue[0]].id;
+					this.cityDataList = await this.$api.request('/address/area?pid=' + provinceId);
+					this.areaDataList = await this.$api.request('/address/area?pid=' + this.cityDataList[0].id);
+
+					changePickerValue[1] = 0;
+					changePickerValue[2] = 0;
+				} else if (this.pickerValue[1] !== changePickerValue[1]) {
+					// 第二级滚动
+					// this.areaDataList =
+					// 	areaData[changePickerValue[0]][changePickerValue[1]];
+
+					let cityId = this.cityDataList[changePickerValue[1]].id;
+					this.areaDataList = await this.$api.request('/address/area?pid=' + cityId);
+
+					changePickerValue[2] = 0;
+				}
+				this.pickerValue = changePickerValue;
+				this._$emit('onChange');
+			},
+			_$emit(emitName) {
+				let pickObj = {
+					label: this._getLabel(),
+					value: this._getAreaId(),
+					cityCode: this._getCityCode()
+				};
+				this.$emit(emitName, pickObj);
+			},
+			_getLabel() {
+				let pcikerLabel =
+					this.provinceDataList[this.pickerValue[0]].label +
+					'-' +
+					this.cityDataList[this.pickerValue[1]].label +
+					'-' +
+					this.areaDataList[this.pickerValue[2]].label;
+				return pcikerLabel;
+			},
+			_getCityCode() {
+				return this.areaDataList[this.pickerValue[2]].value;
+			},
+			_getAreaId() {
+				let areaId = [
+					this.provinceDataList[this.pickerValue[0]].id,
+					this.cityDataList[this.pickerValue[1]].id,
+					this.areaDataList[this.pickerValue[2]].id
+				];
+				return areaId;
+			}
+		}
+	};
+</script>
+
+<style>
+	.pickerMask {
+		position: fixed;
+		z-index: 1000;
+		top: 0;
+		right: 0;
+		left: 0;
+		bottom: 0;
+		background: rgba(0, 0, 0, 0.6);
+	}
+
+	.mpvue-picker-content {
+		position: fixed;
+		bottom: 0;
+		left: 0;
+		width: 100%;
+		transition: all 0.3s ease;
+		transform: translateY(100%);
+		z-index: 3000;
+	}
+
+	.mpvue-picker-view-show {
+		transform: translateY(0);
+	}
+
+	.mpvue-picker__hd {
+		display: flex;
+		padding: 9px 15px;
+		background-color: #fff;
+		position: relative;
+		text-align: center;
+		font-size: 17px;
+	}
+
+	.mpvue-picker__hd:after {
+		content: " ";
+		position: absolute;
+		left: 0;
+		bottom: 0;
+		right: 0;
+		height: 1px;
+		border-bottom: 1px solid #e5e5e5;
+		color: #e5e5e5;
+		transform-origin: 0 100%;
+		transform: scaleY(0.5);
+	}
+
+	.mpvue-picker__action {
+		display: block;
+		flex: 1;
+		color: #1aad19;
+	}
+
+	.mpvue-picker__action:first-child {
+		text-align: left;
+		color: #888;
+	}
+
+	.mpvue-picker__action:last-child {
+		text-align: right;
+	}
+
+	.picker-item {
+		text-align: center;
+		line-height: 40px;
+		text-overflow: ellipsis;
+		white-space: nowrap;
+	}
+
+	.mpvue-picker-view {
+		position: relative;
+		bottom: 0;
+		left: 0;
+		width: 100%;
+		height: 238px;
+		background-color: rgba(255, 255, 255, 1);
+	}
+</style>

+ 16 - 0
package-lock.json

@@ -0,0 +1,16 @@
+{
+  "requires": true,
+  "lockfileVersion": 1,
+  "dependencies": {
+    "jweixin-module": {
+      "version": "1.6.0",
+      "resolved": "https://registry.npmjs.org/jweixin-module/-/jweixin-module-1.6.0.tgz",
+      "integrity": "sha512-dGk9cf+ipipHmtzYmKZs5B2toX+p4hLyllGLF6xuC8t+B05oYxd8fYoaRz0T30U2n3RUv8a4iwvjhA+OcYz52w=="
+    },
+    "mpvue-citypicker": {
+      "version": "1.0.6",
+      "resolved": "https://registry.npmjs.org/mpvue-citypicker/-/mpvue-citypicker-1.0.6.tgz",
+      "integrity": "sha512-V8TuUILYw7pFV0ii02tXKUtLmoa+mTxxJS85dEIc4sp85dVTLNi2GmXqBoz9MkT+QwKeOf3fPvFZzoucERz5rA=="
+    }
+  }
+}

+ 320 - 0
pages.json

@@ -0,0 +1,320 @@
+{
+	"pages": [
+
+		{
+			"path": "pages/index/index",
+			"style": {
+				"enablePullDownRefresh": true,
+				// #ifdef MP 
+				"navigationBarTitleText": "喂喂商城",
+				//"navigationStyle": "custom",
+				// #endif 
+				"app-plus": {
+					"titleNView": {
+						"titleText": "喂喂商城"
+						//"type": "transparent"
+						// "searchInput": {
+						// 	"backgroundColor": "rgba(231, 231, 231,.7)",
+						// 	"borderRadius": "16px",
+						// 	"placeholder": "请输入地址 如:大钟寺",
+						// 	"disabled": true,
+						// 	"placeholderColor": "#606266"
+						// },
+						// "buttons": [{
+						// 		"fontSrc": "/static/yticon.ttf",
+						// 		"text": "\ue60d",
+						// 		"fontSize": "26",
+						// 		"color": "#303133",
+						// 		"float": "left",
+						// 		"background": "rgba(0,0,0,0)"
+						// 	},
+						// 	{
+						// 		"fontSrc": "/static/yticon.ttf",
+						// 		"text": "\ue744",
+						// 		"fontSize": "27",
+						// 		"color": "#303133",
+						// 		"background": "rgba(0,0,0,0)",
+						// 		"redDot": true
+						// 	}
+						// ]
+					}
+				}
+			}
+		},
+		{
+			"path": "pages/product/product",
+			"style": {
+				"navigationBarTitleText": "详情展示",
+				"enablePullDownRefresh": true,
+				"app-plus": {
+					"titleNView": {
+						"type": "transparent"
+					}
+				}
+			}
+		}, {
+			"path": "pages/set/set",
+			"style": {
+				"navigationBarTitleText": "设置"
+			}
+		},
+		{
+			"path": "pages/userinfo/userinfo",
+			"style": {
+				"navigationBarTitleText": "修改资料"
+			}
+		}, {
+			"path": "pages/cart/cart",
+			"style": {
+				"enablePullDownRefresh": true,
+				"navigationBarTitleText": "购物车"
+			}
+		}, {
+			"path": "pages/public/login",
+			"style": {
+				"navigationBarTitleText": "",
+				"navigationStyle": "custom",
+				"app-plus": {
+					"titleNView": false,
+					"animationType": "slide-in-bottom"
+				}
+			}
+		}, {
+			"path": "pages/public/register",
+			"style": {
+				"navigationBarTitleText": "",
+				"navigationStyle": "custom",
+				"app-plus": {
+					"titleNView": false,
+					"animationType": "slide-in-bottom"
+				}
+			}
+		}, {
+			"path": "pages/user/user",
+			"style": {
+				"navigationBarTitleText": "我的",
+				// #ifdef MP
+				"navigationStyle": "custom",
+				// #endif
+				"app-plus": {
+					"bounce": "none",
+					"titleNView": {
+						"type": "transparent"
+						// "buttons": [{
+						// 		"fontSrc": "/static/yticon.ttf",
+						// 		"text": "\ue60f",
+						// 		"fontSize": "24",
+						// 		"color": "#303133",
+						// 		"width": "46px",
+						// 		"background": "rgba(0,0,0,0)"
+						// 	},
+						// 	{
+						// 		"fontSrc": "/static/yticon.ttf",
+						// 		"text": "\ue744",
+						// 		"fontSize": "28",
+						// 		"color": "#303133",
+						// 		"background": "rgba(0,0,0,0)",
+						// 		"redDot": true
+						// 	}
+						// ]
+					}
+				}
+			}
+		}, {
+			"path": "pages/order/order",
+			"style": {
+				"navigationBarTitleText": "我的订单",
+				"enablePullDownRefresh": true,
+				"app-plus": {
+					"bounce": "none"
+				}
+			}
+		}, 
+		{
+			"path": "pages/order/orderDetail",
+			"style": {
+				"enablePullDownRefresh": true,
+				"navigationBarTitleText": "订单详情",
+				"app-plus": {
+					"bounce": "none",
+					"titleNView": {
+						"type": "transparent"
+					}
+				}
+			}
+		},{
+			"path": "pages/money/money",
+			"style": {}
+		}, {
+			"path": "pages/order/createOrder",
+			"style": {
+				"navigationBarTitleText": "创建订单"
+			}
+		}, {
+			"path": "pages/address/address",
+			"style": {
+				"navigationBarTitleText": "收货地址"
+			}
+		}, {
+			"path": "pages/address/addressManage",
+			"style": {
+				"navigationBarTitleText": ""
+			}
+		}, {
+			"path": "pages/money/pay",
+			"style": {
+				"navigationBarTitleText": "支付"
+			}
+		},
+		{
+			"path": "pages/money/paySuccess",
+			"style": {
+				"navigationBarTitleText": "支付成功"
+			}
+		}
+	    ,{
+            "path" : "pages/notice/notice",
+            "style" : {
+				"navigationBarTitleText": "通知"
+			}
+        }
+        ,{
+            "path" : "pages/category/category",
+            "style" : {
+				"navigationBarTitleText": "分类",
+				"enablePullDownRefresh": true,
+				"app-plus": {
+					"bounce": "none"
+				}
+			}
+        }
+        ,{
+            "path" : "pages/product/list",
+            "style" : {
+				"enablePullDownRefresh": true,
+				"navigationBarTitleText": "商品列表"
+			}
+        }
+		,{
+		    "path" : "pages/flash/list",
+		    "style" : {
+				"enablePullDownRefresh": true,
+				"navigationBarTitleText": "限时秒杀"
+			}
+		},
+		{
+		    "path" : "pages/favorite/favorite",
+		    "style" : {
+				"enablePullDownRefresh": true,
+				"navigationBarTitleText": "我的收藏"
+			}
+		},
+		{
+		    "path" : "pages/public/webview",
+		    "style" : {
+				"enablePullDownRefresh": true,
+				"navigationBarTitleText": "物流"
+			}
+		},
+		{
+		    "path" : "pages/order/evaluate",
+		    "style" : {
+				"enablePullDownRefresh": true,
+				"navigationBarTitleText": "发表评论"
+			}
+		},
+		{
+		    "path" : "pages/order/refund",
+		    "style" : {
+				"enablePullDownRefresh": true,
+				"navigationBarTitleText": "申请售后"
+			}
+		},
+		{
+		    "path" : "pages/product/evaluate",
+		    "style" : {
+				"enablePullDownRefresh": true,
+				"navigationBarTitleText": "评价"
+			}
+		},
+		{
+		    "path" : "pages/order/delivery",
+		    "style" : {
+				"enablePullDownRefresh": false,
+				"navigationBarTitleText": "运费模板"
+			}
+		}
+    ],
+	"globalStyle": {
+		"navigationBarTextStyle": "black",
+		"navigationBarTitleText": "uni-app",
+		"navigationBarBackgroundColor": "#FFFFFF",
+		"backgroundColor": "#f8f8f8"
+	},
+	"tabBar": {
+		"color": "#C0C4CC",
+		"selectedColor": "#fa436a",
+		"borderStyle": "black",
+		"backgroundColor": "#ffffff",
+		"list": [{
+				"pagePath": "pages/index/index",
+				"iconPath": "static/tab-home.png",
+				"selectedIconPath": "static/tab-home-current.png",
+				"text": "首页"
+			},
+			{
+				"pagePath": "pages/category/category",
+				"iconPath": "static/tab-cate.png",
+				"selectedIconPath": "static/tab-cate-current.png",
+				"text": "分类"
+			},
+			{
+				"pagePath": "pages/cart/cart",
+				"iconPath": "static/tab-cart.png",
+				"selectedIconPath": "static/tab-cart-current.png",
+				"text": "购物车"
+			},
+			{
+				"pagePath": "pages/user/user",
+				"iconPath": "static/tab-my.png",
+				"selectedIconPath": "static/tab-my-current.png",
+				"text": "我的"
+			}
+		]
+	},
+	"condition" : { //模式配置,仅开发期间生效
+		"current": 0, //当前激活的模式(list 的索引项)
+		"list": [
+			{
+				"name": "index", //模式名称
+				"path": "pages/index/index", //启动页面,必选
+				"query": "" //启动参数,在页面的onLoad函数里面得到
+			},
+			{
+				"name": "comment",
+				"path": "pages/order/comment",
+				"query":"order_id=nAyML8YWRXU1O2b&image=/shop/20200315/5507a02e4f1a43c72d8360e4194b42a5.png&title=万里无一万里无一万里无一万里无一万里无一万里无一苹果X&spec=白色"
+			},
+			{
+				"name": "orderDetail",
+				"path":"pages/order/orderDetail",
+				"query":"order_id=XDW6kZLJD4ijNEE"
+			},
+			{
+				"name":"orderRefund",
+				"path":"pages/order/refund",
+				"query":"order_id=7Rw23P1oW1CyPgO"
+			},
+			{
+				"name":"userinfo",
+				"path":"pages/userinfo/userinfo",
+				"query":""
+			},
+			{
+				"name": "deliveryTemplate",
+				"path":"pages/order/delivery",
+				"query":""
+			}
+		]
+	}
+}

+ 180 - 0
pages/address/address.vue

@@ -0,0 +1,180 @@
+<template>
+	<view class="content b-t">
+		<view class="list b-b" v-for="(item, index) in addressList" :key="index" @click="checkAddress(item)">
+			<view class="wrapper">
+				<view class="address-box">
+					<text v-if="item.is_default" class="tag">默认</text>
+					<text class="address">{{item.province.name+item.city.name+item.area.name+' '+item.address}}</text>
+				</view>
+				<view class="u-box">
+					<text class="name">{{item.name}}</text>
+					<text class="mobile">{{item.mobile}}</text>
+				</view>
+			</view>
+			<text class="yticon icon-bianji" @click.stop="addAddress('edit', item.id)"></text>
+
+			<text class="yticon icon-lajitong" @click.stop="deleteAddress(item.id,index)"></text>
+		</view>
+
+		<button class="add-btn" @click="addAddress('add')">新增地址</button>
+	</view>
+</template>
+
+<script>
+	export default {
+		data() {
+			return {
+				source: 0,
+				addressList: []
+			}
+		},
+		onLoad(option) {
+			console.log(option.source);
+			this.source = option.source;
+		},
+		onShow() {
+			this.getList();
+		},
+		methods: {
+			//获取我的收货地址
+			async getList() {
+				let list = await this.$api.request('/address/all', 'POST', {
+					page: 1,
+					pagesize: 50
+				});
+				if (list) {
+					this.addressList = list;
+				}
+			},
+			//选择地址
+			checkAddress(item) {
+				if (this.source == 1) {
+					//this.$api.prePage()获取上一页实例,在App.vue定义
+					this.$api.prePage().addressData = item;
+					uni.navigateBack()
+				}
+			},
+			addAddress(type, id = 0) {
+				uni.navigateTo({
+					url: `/pages/address/addressManage?type=${type}&id=${id}`
+				})
+			},
+			//添加或修改成功之后回调
+			refreshList(data, type) {
+				//添加或修改后事件,这里直接在最前面添加了一条数据,实际应用中直接刷新地址列表即可
+				this.addressList.unshift(data);
+
+				console.log(data, type);
+			},
+			async deleteAddress(id, index) {
+				let [error, res] = await uni.showModal({
+					title: '确定删除地址?',
+					content: this.addressList[index].address
+				})
+
+				if (res.confirm) {
+					let data = await this.$api.request('/address/delete?id=' + id);
+					if (data) {
+						if (this.$api.prePage().addressData && this.$api.prePage().addressData.id) {
+							if (this.$api.prePage().addressData.id == this.addressList[index].id) {
+								this.$api.prePage().addressData = {};
+							}
+						}
+						
+						this.addressList.splice(index, 1);
+					}
+				}
+
+			}
+		}
+	}
+</script>
+
+<style lang='scss'>
+	page {
+		padding-bottom: 120upx;
+	}
+
+	.content {
+		position: relative;
+	}
+
+	.list {
+		display: flex;
+		align-items: center;
+		padding: 20upx 30upx;
+		;
+		background: #fff;
+		position: relative;
+	}
+
+	.wrapper {
+		display: flex;
+		flex-direction: column;
+		flex: 1;
+	}
+
+	.address-box {
+		display: flex;
+		align-items: center;
+
+		.tag {
+			font-size: 24upx;
+			color: $base-color;
+			margin-right: 10upx;
+			background: #fffafb;
+			border: 1px solid #ffb4c7;
+			border-radius: 4upx;
+			padding: 4upx 10upx;
+			line-height: 1;
+		}
+
+		.address {
+			font-size: 30upx;
+			color: $font-color-dark;
+		}
+	}
+
+	.u-box {
+		font-size: 28upx;
+		color: $font-color-light;
+		margin-top: 16upx;
+
+		.name {
+			margin-right: 30upx;
+		}
+	}
+
+	.icon-bianji {
+		display: flex;
+		align-items: center;
+		height: 80upx;
+		font-size: 40upx;
+		color: $font-color-light;
+		padding-left: 30upx;
+	}
+
+	.icon-lajitong {
+		color: $font-color-light;
+		padding-left: 25rpx;
+	}
+
+
+	.add-btn {
+		position: fixed;
+		left: 30upx;
+		right: 30upx;
+		bottom: 16upx;
+		z-index: 95;
+		display: flex;
+		align-items: center;
+		justify-content: center;
+		width: 690upx;
+		height: 80upx;
+		font-size: 32upx;
+		color: #fff;
+		background-color: $base-color;
+		border-radius: 10upx;
+		box-shadow: 1px 2px 5px rgba(219, 63, 96, 0.4);
+	}
+</style>

+ 203 - 0
pages/address/addressManage.vue

@@ -0,0 +1,203 @@
+<template>
+	<view class="content">
+		<view class="row b-b">
+			<text class="tit">联系人</text>
+			<input class="input" type="text" v-model="addressData.name" placeholder="收货人姓名" placeholder-class="placeholder" />
+		</view>
+		<view class="row b-b">
+			<text class="tit">手机号</text>
+			<input class="input" type="number" v-model="addressData.mobile" placeholder="收货人手机号码" placeholder-class="placeholder" />
+		</view>
+		<view class="row b-b" v-on:click="showCityPicker()">
+			<text class="tit">地区</text>
+			<view class="input">
+				{{cityLebel}}
+			</view>
+		</view>
+		<view class="row b-b">
+			<text class="tit">详细</text>
+			<input class="input" type="text" v-model="addressData.address" placeholder="详细地址,楼号" placeholder-class="placeholder" />
+		</view>
+
+		<view class="row default-row">
+			<text class="tit">设为默认</text>
+			<switch :checked="addressData.is_default" color="#fa436a" @change="switchChange" />
+		</view>
+		<button class="add-btn" @click="confirm">提交</button>
+		
+		<mpvue-city-picker @onChange="onChange" @onCancel="onCancel"
+		 @onConfirm="onConfirm" ref="mpvueCityPicker" ></mpvue-city-picker>
+	</view>
+</template>
+
+<script>
+	import mpvueCityPicker from '@/node_modules/mpvue-citypicker/src/mpvueCityPicker.vue';
+	export default {
+		components: {
+			mpvueCityPicker
+		},
+		data() {
+			return {
+				addressData: {
+					name: '',
+					mobile: '',
+					address: '',
+					province_id: 0,
+					city_id: 0,
+					area_id: 0,
+					is_default: false
+				},
+				pickerValueDefault: [0, 0, 0] ,//城市选择器默认值 省市区id
+				cityLebel:'请选择地区',
+			}
+		},
+		onLoad(option) {
+			let title = '新增收货地址';
+			if (option.type === 'edit') {
+				
+				this.getInfo(option.id);
+				
+				title = '编辑收货地址'
+			} else {
+
+				this.$refs.mpvueCityPicker.creat(this.pickerValueDefault);
+			}
+			this.manageType = option.type;
+			uni.setNavigationBarTitle({ 
+				title
+			})
+		},
+		methods: {
+			// 获取地址详情
+			async getInfo(id){
+				let addressData = await this.$api.request(`/address/info?id=${id}`);
+				if (addressData) {
+					console.log(addressData);
+					
+					this.addressData = addressData;
+					let pickerValueDefault = [];
+					pickerValueDefault.push(addressData.province_id);
+					pickerValueDefault.push(addressData.city_id);
+					pickerValueDefault.push(addressData.area_id);
+					this.pickerValueDefault = pickerValueDefault;
+					
+					this.$refs.mpvueCityPicker.creat(pickerValueDefault);
+				}
+			},
+			// 城市选择器
+			showCityPicker() {
+				this.$refs.mpvueCityPicker.show();
+			},
+			// 城市选择器改变值
+			onChange(e) {
+				// console.log('选择的值')
+				// console.log(e);
+			},
+			// 城市选择器关闭
+			onCancel(e) {
+				//console.log(e);
+			},
+			// 城市选择器确定
+			onConfirm(e) {
+				//console.log(e);
+				this.cityLebel = e.label;
+				this.pickerValueDefault = e.value;
+				
+				this.addressData.province_id = this.pickerValueDefault[0];
+				this.addressData.city_id = this.pickerValueDefault[1];
+				this.addressData.area_id = this.pickerValueDefault[2];
+			},
+			//默认地址
+			switchChange(e) {
+				this.addressData.is_default = e.detail.value;
+			},
+			//提交
+			async confirm() {
+				//Deep Clone
+				let data = JSON.parse(JSON.stringify(this.addressData));
+				if (!data.name) {
+					this.$api.msg('请填写收货人姓名');
+					return;
+				}
+				if (!/(^1[3|4|5|7|8][0-9]{9}$)/.test(data.mobile)) {
+					this.$api.msg('请输入正确的手机号码');
+					return;
+				}
+				if (!data.address) {
+					this.$api.msg('请填详细地址信息');
+					return;
+				}
+				console.log(data.is_default);
+				data.is_default = data.is_default == true ? 1 : 0;
+				let action = this.manageType == 'edit' ? 'edit' : 'add';
+				let result = await this.$api.request('/address/' + action, 'POST', data);
+				if (result) {
+					this.$api.prePage().refreshList(data, this.manageType);
+					setTimeout(() => {
+						uni.navigateBack()
+					}, 800)
+				}
+			},
+		}
+	}
+</script>
+
+<style lang="scss">
+	page {
+		background: $page-color-base;
+		padding-top: 16upx;
+	}
+
+	.row {
+		display: flex;
+		align-items: center;
+		position: relative;
+		padding: 0 30upx;
+		height: 110upx;
+		background: #fff;
+
+		.tit {
+			flex-shrink: 0;
+			width: 120upx;
+			font-size: 30upx;
+			color: $font-color-dark;
+		}
+
+		.input {
+			flex: 1;
+			font-size: 30upx;
+			color: $font-color-dark;
+		}
+
+		.icon-shouhuodizhi {
+			font-size: 36upx;
+			color: $font-color-light;
+		}
+	}
+
+	.default-row {
+		margin-top: 16upx;
+
+		.tit {
+			flex: 1;
+		}
+
+		switch {
+			transform: translateX(16upx) scale(.9);
+		}
+	}
+
+	.add-btn {
+		display: flex;
+		align-items: center;
+		justify-content: center;
+		width: 690upx;
+		height: 80upx;
+		margin: 60upx auto;
+		font-size: $font-lg;
+		color: #fff;
+		background-color: $base-color;
+		border-radius: 10upx;
+		box-shadow: 1px 2px 5px rgba(219, 63, 96, 0.4);
+	}
+</style>

+ 481 - 0
pages/cart/cart.vue

@@ -0,0 +1,481 @@
+<template>
+	<view class="container">
+		<!-- 空白页 -->
+		<view v-if="(!hasLogin || empty===true) && state != 'load'" class="empty">
+			<image src="/static/emptyCart.jpg" mode="aspectFit"></image>
+			<view v-if="hasLogin" class="empty-tips">
+				空空如也
+				<navigator class="navigator" v-if="hasLogin" url="../index/index" open-type="switchTab">随便逛逛></navigator>
+			</view>
+			<view v-else class="empty-tips">
+				空空如也
+				<view class="navigator" @click="navToLogin">去登陆></view>
+			</view>
+		</view>
+		<view v-else>
+			<!-- 列表 -->
+			<view class="cart-list">
+				<block v-for="(item, index) in cartList" :key="item.id">
+					<view class="cart-item" :class="{'b-b': index!==cartList.length-1}" :style="{'background':item.isset?'':'#f5f5f5'}" 
+						@click="navTo(`/pages/product/product?id=${item.product_id}&flash=0`)"
+					>
+						<view class="image-wrapper">
+							<image :src="item.image" class="loaded" mode="aspectFill"></image>
+							<view v-if="item.isset == true" class="yticon icon-xuanzhong checkbox" :class="{checked: item.choose}" @click.stop="check('item', index)"></view>
+						</view>
+						<view class="item-right">
+							<text class="clamp title">{{item.title}}</text>
+							<text class="attr" v-if="item.spec">{{item.spec}}</text>
+							<text class="price">¥{{item.nowPrice}} <text style="color:red"> {{cartPrice(item.oldPrice, item.nowPrice)}}</text></text>
+							<uni-number-box class="step" :min="1" :max="item.stock" :disabled="item.number>=item.stock" :value="cartList[index].number"
+							 :isMax="item.number>=item.stock?true:false" :isMin="item.number===1" :index="index" @eventChange="numberChange"></uni-number-box>
+						</view>
+						<text class="del-btn yticon icon-lajitong" @click.stop="deleteCartItem(index)"></text>
+						<text class="invalid" v-if="item.isset == false">失效</text>
+						<text class="invalid" v-if="item.stock == 0 && item.isset == true">库存不足</text>
+					</view>
+				</block>
+			</view>
+			<!-- 底部菜单栏 -->
+			<view class="action-section" v-if="state != 'load'">
+				<view class="checkbox">
+					<image :src="allChoose?'/static/selected.png':'/static/select.png'" mode="aspectFit" @click="check('all')"></image>
+					<view class="clear-btn" :class="{show: allChoose}" @click="clearCart">
+						清空
+					</view>
+				</view>
+				<view class="total-box">
+					<text class="price">¥{{total}}</text>
+				</view>
+				<button type="primary" class="no-border confirm-btn" @click="createOrder">去结算</button>
+			</view>
+		</view>
+	</view>
+</template>
+
+<script>
+	import {
+		mapState
+	} from 'vuex';
+	import uniNumberBox from '@/components/uni-number-box.vue'
+	export default {
+		components: {
+			uniNumberBox
+		},
+		data() {
+			return {
+				total: 0, //总价格
+				allChoose: false, //全选状态  true|false
+				empty: false, //空白页现实  true|false
+				cartList: [],
+				state : 'load'
+			};
+		},
+		onLoad() {
+			
+		},
+		onPullDownRefresh() {
+			this.state = 'load';
+			this.cartList = [];
+			this.getCart();
+		},
+		onShow() {
+			this.state = 'load';
+			this.cartList = [];
+			this.getCart();
+		},
+		watch: {
+			//显示空白页
+			cartList(e) {
+				let empty = e.length === 0 ? true : false;
+				if (this.empty !== empty) {
+					this.empty = empty;
+				}
+			}
+		},
+		computed: {
+			...mapState(['hasLogin'])
+		},
+		methods: {
+			async getCart() {
+				let login = await this.$api.checkLogin();
+				if (login) {
+					let data = await this.$api.request('/cart');
+					uni.stopPullDownRefresh();
+					this.state = 'loaded';
+					if (data){
+						this.cartList = data;
+						this.calcTotal();
+					}
+					
+				}
+			},
+			cartPrice(oldPrice, nowPrice) {
+				let string = '';
+				if (oldPrice < nowPrice) {
+					let number = (nowPrice - oldPrice).toFixed(2);
+					string = ' ↑涨价 ' + number + '元';
+				} else if (oldPrice > nowPrice) {
+					let number = (oldPrice - nowPrice).toFixed(2);
+					string = ' ↓降价 ' + number + '元';
+				}
+				return string;
+			},
+			navToLogin() {
+				uni.navigateTo({
+					url: '/pages/public/login'
+				})
+			},
+			//选中状态处理
+			async check(type, index) {
+				
+				let trueArr = [];
+				let falseArr = [];
+				let oldChoose = [];
+				const list = this.cartList;
+				//保存旧的数据
+				list.forEach(item => {
+					if(item.choose){
+						oldChoose.push(item.cart_id);
+					}
+				})
+				
+				//本地处理
+				if (type === 'item') {
+					this.cartList[index].choose = !this.cartList[index].choose;
+					if (this.cartList[index].choose) {
+						trueArr.push(this.cartList[index].cart_id);
+					} else {
+						falseArr.push(this.cartList[index].cart_id);
+					}
+				} else {
+					const choose = !this.allChoose
+					list.forEach(item => {
+						item.choose = choose;
+						if (item.isset) {
+							if (choose) {
+								trueArr.push(item.cart_id);
+							} else {
+								falseArr.push(item.cart_id);
+							}
+						}
+					})
+					this.allChoose = choose;
+				}
+				this.calcTotal(type);
+				
+				//远程处理
+				let result = await this.$api.request('/cart/choose_change', 'POST', {trueArr,falseArr});
+				if (!result) {
+					//恢复原来勾选的状态
+					list.forEach(item => {
+						if (oldChoose.indexOf(item.cart_id) >= 0) {
+							item.choose = 1;
+						} else {
+							item.choose = 0;
+						}
+					})
+					this.calcTotal(type);
+				}
+				
+			},
+			//数量
+			async numberChange(data) {
+				let oldNumber = this.cartList[data.index].number;
+				let newNumber = data.number;
+				this.cartList[data.index].number = newNumber;
+				this.calcTotal();
+				
+				let cart_id = this.cartList[data.index].cart_id;
+				let result = await this.$api.request('/cart/number_change?id='+cart_id, 'GET', {number:newNumber}, false);
+				if (!result) {
+					this.cartList[data.index].number = oldNumber;
+					this.calcTotal();
+				}
+				
+			},
+			//删除
+			async deleteCartItem(index) {
+				let list = this.cartList;
+				let row = list[index];
+				let id = row.cart_id;
+								
+				uni.showModal({
+					content: '确认删除 ' + list[index].title + '?' ,
+					success: async (e) => {
+						if (e.confirm) {
+							let result = await this.$api.request('/cart/delete?', 'POST', {id:id});
+							if (result) {
+								let tempCart = this.cartList.splice(index, 1);
+								this.calcTotal();
+							}
+						}
+					}
+				})
+
+			},
+			//清空
+			async clearCart() {
+				let [error, res] = await uni.showModal({
+					title: '确认清空?'
+				});
+				if (res.confirm) {
+					let id = [];
+					this.cartList.forEach(item=>{
+						id.push(item.cart_id);
+					});
+					let data = this.$api.request('/cart/delete', 'POST',{id:id});
+					let that = this;
+					if (data) {
+						setTimeout(function(){
+							that.state = 'load';
+							that.cartList = [];
+							that.getCart();
+						},300);
+					}
+				}
+			},
+			//计算总价
+			calcTotal() {
+				let list = this.cartList;
+				if (list.length === 0) {
+					this.empty = true;
+					return;
+				}
+				let total = 0;
+				let choose = true;
+				list.forEach(item => {
+					if (item.isset) {
+						if (item.choose == 1) {
+							total += item.nowPrice * item.number;
+						} else if (choose === true) {
+							choose = false;
+						}
+					}
+				})
+				this.allChoose = choose;
+				this.total = total.toFixed(2);
+			},
+			//创建订单
+			createOrder() {
+				let list = this.cartList;
+				let cartId = [];
+				list.forEach(item => {
+					if (item.choose) {
+						cartId.push(item.cart_id);
+					}
+				})
+				if (cartId.length == 0) {
+					this.$api.msg('没有选中商品');
+					return;
+				}
+				this.$api.navTo(`/pages/order/createOrder?cart=${cartId.join(',')}`);
+			},
+			navTo(url){
+				this.$api.navTo(url);
+			}
+		}
+	}
+</script>
+
+<style lang='scss'>
+	.container {
+		padding-bottom: 134upx;
+		
+		/* 空白页 */
+		.empty {
+			position: fixed;
+			left: 0;
+			top: 0;
+			width: 100%;
+			height: 100vh;
+			padding-bottom: 100upx;
+			display: flex;
+			justify-content: center;
+			flex-direction: column;
+			align-items: center;
+			background: #fff;
+
+			image {
+				width: 240upx;
+				height: 160upx;
+				margin-bottom: 30upx;
+			}
+
+			.empty-tips {
+				display: flex;
+				font-size: $font-sm+2upx;
+				color: $font-color-disabled;
+
+				.navigator {
+					color: $uni-color-primary;
+					margin-left: 16upx;
+				}
+			}
+		}
+	}
+
+	/* 购物车列表项 */
+	.cart-item {
+		display: flex;
+		position: relative;
+		padding: 30upx 40upx;
+
+		.image-wrapper {
+			width: 230upx;
+			height: 230upx;
+			flex-shrink: 0;
+			position: relative;
+
+			image {
+				border-radius: 8upx;
+			}
+		}
+
+		.checkbox {
+			position: absolute;
+			left: -16upx;
+			top: -16upx;
+			z-index: 8;
+			font-size: 44upx;
+			line-height: 1;
+			padding: 4upx;
+			color: $font-color-disabled;
+			background: #fff;
+			border-radius: 50px;
+		}
+
+		.item-right {
+			display: flex;
+			flex-direction: column;
+			flex: 1;
+			overflow: hidden;
+			position: relative;
+			padding-left: 30upx;
+
+			.title,
+			.price {
+				font-size: $font-base + 2upx;
+				color: $font-color-dark;
+				height: 40upx;
+				line-height: 40upx;
+			}
+
+			.attr {
+				font-size: $font-sm + 2upx;
+				color: $font-color-light;
+				height: 50upx;
+				line-height: 50upx;
+			}
+
+			.price {
+				height: 50upx;
+				line-height: 50upx;
+			}
+		}
+
+		.del-btn {
+			padding: 4upx 10upx;
+			font-size: 34upx;
+			height: 50upx;
+			color: $font-color-light;
+		}
+
+		.invalid {
+			position: absolute;
+			right: 0;
+			bottom: 0;
+			background: #999999;
+			color: #ffffff;
+			padding: 6upx 12upx;
+			border-radius: 10upx;
+			font-size: 26upx;
+			margin-right: 50upx;
+			margin-bottom: 32upx;
+		}
+	}
+
+	/* 底部栏 */
+	.action-section {
+		/* #ifdef H5 */
+		margin-bottom: 100upx;
+		/* #endif */
+		position: fixed;
+		left: 30upx;
+		bottom: 30upx;
+		z-index: 95;
+		display: flex;
+		align-items: center;
+		width: 690upx;
+		height: 100upx;
+		padding: 0 30upx;
+		background: rgba(255, 255, 255, .9);
+		box-shadow: 0 0 20upx 0 rgba(0, 0, 0, .5);
+		border-radius: 16upx;
+
+		.checkbox {
+			height: 52upx;
+			position: relative;
+
+			image {
+				width: 52upx;
+				height: 100%;
+				position: relative;
+				z-index: 5;
+			}
+		}
+
+		.clear-btn {
+			position: absolute;
+			left: 26upx;
+			top: 0;
+			z-index: 4;
+			width: 0;
+			height: 52upx;
+			line-height: 52upx;
+			padding-left: 38upx;
+			font-size: $font-base;
+			color: #fff;
+			background: $font-color-disabled;
+			border-radius: 0 50px 50px 0;
+			opacity: 0;
+			transition: .2s;
+
+			&.show {
+				opacity: 1;
+				width: 120upx;
+			}
+		}
+
+		.total-box {
+			flex: 1;
+			display: flex;
+			flex-direction: column;
+			text-align: right;
+			padding-right: 40upx;
+
+			.price {
+				font-size: $font-lg;
+				color: $font-color-dark;
+			}
+
+		}
+
+		.confirm-btn {
+			padding: 0 38upx;
+			margin: 0;
+			border-radius: 100px;
+			height: 76upx;
+			line-height: 76upx;
+			font-size: $font-base + 2upx;
+			background: $uni-color-primary;
+			box-shadow: 1px 2px 5px rgba(217, 60, 93, 0.72)
+		}
+	}
+
+	/* 复选框选中状态 */
+	.action-section .checkbox.checked,
+	.cart-item .checkbox.checked {
+		color: $uni-color-primary;
+	}
+</style>

+ 199 - 0
pages/category/category.vue

@@ -0,0 +1,199 @@
+<template>
+	<view class="content">
+		<scroll-view scroll-y class="left-aside">
+			<view v-for="item in flist" :key="item.id" class="f-item b-b" :class="{active: item.id === currentId}" @click="tabtap(item)">
+				{{item.name}}
+			</view>
+		</scroll-view>
+		<scroll-view scroll-with-animation scroll-y class="right-aside" @scroll="asideScroll" :scroll-top="tabScrollTop">
+			<view v-for="item in flist" :key="item.id" class="s-list" :id="'main-'+item.id">
+				<text class="s-item">{{item.name}}</text>
+				<view class="t-list">
+					<view @click="navToList(item.id, sitem.id)" v-if="sitem.pid === item.id" class="t-item" v-for="sitem in slist" :key="sitem.id">
+						<image :src="sitem.image"></image>
+						<text>{{sitem.name}}</text>
+					</view>
+				</view>
+			</view>
+		</scroll-view>
+	</view>
+</template>
+
+<script>
+
+	export default {
+		data() {
+			return {
+				sizeCalcState: false,
+				tabScrollTop: 0,
+				currentId: 0,
+				flist: [],
+				slist: [],
+			}
+		},
+		computed: {
+
+		},
+		onShareAppMessage(e){
+			
+		},
+		onLoad(){
+			this.loadData();
+		},
+		methods: {
+			async loadData(){
+				var that = this;
+				//let list = await this.$api.json('cateList');
+				let list = await this.$api.request('/category/all');
+				if (list) {
+					uni.stopPullDownRefresh();
+					list.forEach(item=>{
+						if(item.pid == 0){
+							if (that.currentId == 0) {
+								that.currentId = item.id;
+							}
+							this.flist.push(item);  //pid为父级id, 没有pid或者pid=0是一级分类
+						}else {
+							this.slist.push(item); //没有图的是2级分类
+						}
+					})
+					
+				}
+				
+			},
+			//一级分类点击
+			tabtap(item){
+				if(!this.sizeCalcState){
+					this.calcSize();
+				}
+				
+				this.currentId = item.id;
+				let index = this.flist.findIndex(fitem=>fitem.id === item.id);
+				this.tabScrollTop = this.flist[index].top;
+			},
+			//右侧栏滚动
+			asideScroll(e){
+				if(!this.sizeCalcState){
+					this.calcSize();
+				}
+				let scrollTop = e.detail.scrollTop;
+				let tabs = this.flist.filter(item=>item.top <= scrollTop).reverse();
+				if(tabs.length > 0){
+					this.currentId = tabs[0].id;
+				}
+			},
+			//计算右侧栏每个tab的高度等信息
+			calcSize(){
+				let h = 0;
+				this.flist.forEach(item=>{
+					let view = uni.createSelectorQuery().select("#main-" + item.id);
+					view.fields({
+						size: true
+					}, data => {
+						item.top = h;
+						h += data.height;
+						item.bottom = h;
+					}).exec();
+				})
+				this.sizeCalcState = true;
+			},
+			navToList(fid, sid) {
+				uni.navigateTo({
+					url: `/pages/product/list?fid=${fid}&sid=${sid}`
+				});
+			}
+		},
+		onPullDownRefresh() {
+			this.flist = [];
+			this.slist = [];
+			this.sizeCalcState = false;
+			this.loadData();
+		}
+	}
+</script>
+
+<style lang='scss'>
+	page,
+	.content {
+		height: 100%;
+		background-color: #f8f8f8;
+	}
+
+	.content {
+		display: flex;
+	}
+	.left-aside {
+		flex-shrink: 0;
+		width: 200upx;
+		height: 100%;
+		background-color: #fff;
+	}
+	.f-item {
+		display: flex;
+		align-items: center;
+		justify-content: center;
+		width: 100%;
+		height: 100upx;
+		font-size: 28upx;
+		color: $font-color-base;
+		position: relative;
+		&.active{
+			color: $base-color;
+			background: #f8f8f8;
+			&:before{
+				content: '';
+				position: absolute;
+				left: 0;
+				top: 50%;
+				transform: translateY(-50%);
+				height: 36upx;
+				width: 8upx;
+				background-color: $base-color;
+				border-radius: 0 4px 4px 0;
+				opacity: .8;
+			}
+		}
+	}
+
+	.right-aside{
+		flex: 1;
+		overflow: hidden;
+		padding-left: 20upx;
+	}
+	.s-item{
+		display: flex;
+		align-items: center;
+		height: 70upx;
+		padding-top: 8upx;
+		font-size: 28upx;
+		color: $font-color-dark;
+	}
+	.t-list{
+		display: flex;
+		flex-wrap: wrap;
+		width: 100%;
+		background: #fff;
+		padding-top: 12upx;
+		&:after{
+			content: '';
+			flex: 99;
+			height: 0;
+		}
+	}
+	.t-item{
+		flex-shrink: 0;
+		display: flex;
+		justify-content: center;
+		align-items: center;
+		flex-direction: column;
+		width: 176upx;
+		font-size: 26upx;
+		color: #666;
+		padding-bottom: 20upx;
+		
+		image{
+			width: 140upx;
+			height: 140upx;
+		}
+	}
+</style>

+ 355 - 0
pages/favorite/favorite.vue

@@ -0,0 +1,355 @@
+<template>
+	<view class="content">
+		<scroll-view class="list-scroll-content" scroll-y @scrolltolower="loadData">
+			<!-- 空白页 -->
+			<empty v-if="favorite.loaded === true && favorite.list.length === 0"></empty>
+
+			<!-- 产品列表 -->
+			<view v-for="(item, index) in favorite.list" :key="index" class="order-item">
+				<view class="info" @click="navToDetailPage(item.product.product_id, item.status)">
+					<view class="image">
+						<image mode="aspectFill" :src="item.product.image"></image>
+					</view>
+					<view class="detail">
+						<view class="title">{{item.product.title}}</view>
+						<view class="price">
+							<view class="sales">¥{{item.product.sales_price}} </view>
+							<view class="market"> ¥{{item.product.market_price}}</view>
+						</view>
+						<view class="invalid" v-if="item.status == 0">失效</view>
+						<text class="del-btn yticon icon-lajitong" @click.stop="deleteFavorite(item.product.product_id,index)"></text>
+					</view>
+				</view>
+			</view>
+
+			<uni-load-more :status="favorite.loadingType"></uni-load-more>
+		</scroll-view>
+	</view>
+</template>
+
+<script>
+	import uniLoadMore from '@/components/uni-load-more/uni-load-more.vue';
+	import empty from "@/components/empty";
+
+	export default {
+		components: {
+			uniLoadMore,
+			empty
+		},
+		computed: {
+		},
+		data() {
+			return {
+				favorite: {
+					list:[],
+					loadingType: 'more'
+				},
+				page: 1,
+				pageSize: 20
+			}
+		},
+		onLoad() {
+			this.loadData();
+		},
+		onPullDownRefresh() {
+			this.favorite = {};
+			this.favorite.list = [];
+			this.page = 1;
+			this.loadData();
+		},
+		methods: {
+			/**
+			 * 获取收藏数据
+			 */
+			async loadData(source = false) {
+				
+				uni.stopPullDownRefresh();
+
+				var favorite = this.favorite;
+
+				if (favorite.loadingType === 'loading') {
+					//防止重复加载
+					return;
+				}
+				if (favorite.loadingType == 'noMore') {
+					//没有更多数据
+					return;
+				}
+
+				favorite.loadingType = 'loading';
+
+				let list = await this.$api.request('/product/favoriteList', 'GET', {
+					page: this.page,
+					pagesize: this.pageSize
+				});
+				if (list && list.length > 0) {
+					if (list.length >= this.pageSize) {
+						//判断是否还有数据, 有改为 more, 没有改为noMore
+						favorite.loadingType = 'more';
+					} else {
+						favorite.loadingType = 'noMore';
+					}
+					// 页数加一
+					this.page++;
+
+					list.forEach((item, index) => {
+						favorite.list.push(item);
+					});
+
+				} else {
+					favorite.loadingType = 'noMore';
+				}
+			},
+			// 商品详情页
+			navToDetailPage(product_id, status) {
+				if (status == 0) {
+					this.$api.msg('此商品已失效');
+					return;
+				}
+				uni.navigateTo({
+					url: `/pages/product/product?id=${product_id}&flash=0`
+				});
+			},
+			// 删除
+			async deleteFavorite(id, index) {
+				let data = await this.$api.request('/product/favorite?id='+id);
+				if (data) {
+					this.favorite.list.splice(index,1);
+				}
+			}
+		}
+	}
+</script>
+
+<style lang="scss">
+	page,
+	.content {
+		background: #f5f5f5;
+		height: 100%;
+	}
+
+	.list-scroll-content{
+		height: 100%;
+	}
+	.order-item {
+		display: flex;
+		flex-direction: column;
+		padding-left: 30upx;
+		background: #fff;
+		margin-top: 16upx;
+
+		.info {
+			display: flex;
+			flex-direction: row;
+
+			.image {
+				image {
+					width: 250upx;
+					height: 250upx;
+					border-radius: 10upx;
+				}
+			}
+
+			.detail {
+				width: 440upx;
+				height: 250upx;
+				padding-left: 30upx;
+				padding-top: 6upx;
+				position: relative;
+
+				.title {
+					color: #303133;
+					width: 370rpx;
+					-webkit-line-clamp: 2;
+					overflow: hidden;
+					display: -webkit-box;
+					-webkit-box-orient: vertical;
+				}
+
+				.introduction {
+					color: #999999;
+					font-size: 26upx;
+				}
+
+				.price {
+					display: flex;
+					flex-direction: row;
+					margin-top: 20upx;
+					.sales {
+						font-size: 40upx;
+						color: $base-color;
+						font-weight: 500;
+					}
+
+					.market {
+						vertical-align: bottom;
+						font-size: 25rpx;
+						text-decoration: line-through;
+						line-height: 60rpx;
+					}
+				}
+
+				.ProgressBar {
+					position: absolute;
+					bottom: 0;
+				}
+
+				.loot {
+					position: absolute;
+					right: 0;
+					bottom: 14rpx;
+					background: $base-color;
+					color: #fff;
+					padding: 4upx 14upx;
+					border-radius: 4upx;
+					box-shadow: 2upx 2upx 8upx -2px #000;
+					font-size: 32rpx;
+				}
+				.invalid{
+					color: $base-color;
+					position: absolute;
+					right: 0;
+					bottom: 0;
+				}
+				.yticon{
+					position: absolute;
+					right: 0;
+					top: 20rpx;
+				}
+			}
+		}
+	}
+
+
+	/* load-more */
+	.uni-load-more {
+		display: flex;
+		flex-direction: row;
+		height: 80upx;
+		align-items: center;
+		justify-content: center
+	}
+
+	.uni-load-more__text {
+		font-size: 28upx;
+		color: #999
+	}
+
+	.uni-load-more__img {
+		height: 24px;
+		width: 24px;
+		margin-right: 10px
+	}
+
+	.uni-load-more__img>view {
+		position: absolute
+	}
+
+	.uni-load-more__img>view view {
+		width: 6px;
+		height: 2px;
+		border-top-left-radius: 1px;
+		border-bottom-left-radius: 1px;
+		background: #999;
+		position: absolute;
+		opacity: .2;
+		transform-origin: 50%;
+		animation: load 1.56s ease infinite
+	}
+
+	.uni-load-more__img>view view:nth-child(1) {
+		transform: rotate(90deg);
+		top: 2px;
+		left: 9px
+	}
+
+	.uni-load-more__img>view view:nth-child(2) {
+		transform: rotate(180deg);
+		top: 11px;
+		right: 0
+	}
+
+	.uni-load-more__img>view view:nth-child(3) {
+		transform: rotate(270deg);
+		bottom: 2px;
+		left: 9px
+	}
+
+	.uni-load-more__img>view view:nth-child(4) {
+		top: 11px;
+		left: 0
+	}
+
+	.load1,
+	.load2,
+	.load3 {
+		height: 24px;
+		width: 24px
+	}
+
+	.load2 {
+		transform: rotate(30deg)
+	}
+
+	.load3 {
+		transform: rotate(60deg)
+	}
+
+	.load1 view:nth-child(1) {
+		animation-delay: 0s
+	}
+
+	.load2 view:nth-child(1) {
+		animation-delay: .13s
+	}
+
+	.load3 view:nth-child(1) {
+		animation-delay: .26s
+	}
+
+	.load1 view:nth-child(2) {
+		animation-delay: .39s
+	}
+
+	.load2 view:nth-child(2) {
+		animation-delay: .52s
+	}
+
+	.load3 view:nth-child(2) {
+		animation-delay: .65s
+	}
+
+	.load1 view:nth-child(3) {
+		animation-delay: .78s
+	}
+
+	.load2 view:nth-child(3) {
+		animation-delay: .91s
+	}
+
+	.load3 view:nth-child(3) {
+		animation-delay: 1.04s
+	}
+
+	.load1 view:nth-child(4) {
+		animation-delay: 1.17s
+	}
+
+	.load2 view:nth-child(4) {
+		animation-delay: 1.3s
+	}
+
+	.load3 view:nth-child(4) {
+		animation-delay: 1.43s
+	}
+
+	@-webkit-keyframes load {
+		0% {
+			opacity: 1
+		}
+
+		100% {
+			opacity: .2
+		}
+	}
+</style>

+ 466 - 0
pages/flash/list.vue

@@ -0,0 +1,466 @@
+<template>
+	<view class="content">
+		<view class="navbar">
+			<view v-for="(item, index) in navList" :key="index" class="nav-item" :class="{current: tabCurrentIndex === index}"
+			 @click="tabClick(index)">
+				<view class="hour">
+					<view>{{item.starttime_hour}}</view>
+					<view class="text">{{state[item.state].text}}</view>
+				</view>
+			</view>
+		</view>
+
+		<swiper :current="tabCurrentIndex" class="swiper-box" duration="300" @change="changeTab">
+			<swiper-item v-for="(tabItem, tabIndex) in navList" :key="tabIndex">
+				<scroll-view class="list-scroll-content" scroll-y @scrolltolower="loadData">
+					<!-- 空白页 -->
+					<empty v-if="tabItem.loaded === true && tabItem.list.length === 0"></empty>
+
+					<!-- 产品列表 -->
+					<view v-for="(item, index) in tabItem.list" :key="index" class="order-item">
+						<view class="info" @click="navToDetailPage(item.product.product_id, tabItem.flash_id)">
+							<view class="image">
+								<image mode="aspectFill" :src="item.product.image"></image>
+							</view>
+							<view class="detail">
+								<view class="title">{{item.product.title}}</view>
+								<view class="introduction">{{item.introduction}}</view>
+								<view class="price">
+									<view class="sales">¥{{item.product.sales_price}} </view>
+									<view class="market"> ¥{{item.product.market_price}}</view>
+								</view>
+								<ProgressBar class="ProgressBar" :Sold="item.sold" :widthUpx="250" :Width="percentage(item.number,item.sold)"
+								 Type="candy" :Vice="true"></ProgressBar>
+								<view class="loot">{{tabItem.state > 0 ? "马上抢" : "未开始"}}</view>
+							</view>
+						</view>
+					</view>
+
+					<uni-load-more :status="tabItem.loadingType"></uni-load-more>
+
+				</scroll-view>
+			</swiper-item>
+		</swiper>
+
+	</view>
+</template>
+
+<script>
+	import uniLoadMore from '@/components/uni-load-more/uni-load-more.vue';
+	import empty from "@/components/empty";
+	import ProgressBar from '@/components/Progress-Bar/Progress-Bar';
+
+	export default {
+		components: {
+			uniLoadMore,
+			empty,
+			ProgressBar
+		},
+		computed: {},
+		data() {
+			return {
+				pageSize: 15,
+				tabCurrentIndex: 0,
+				navList: [],
+				state: [{
+					text: '未开始'
+				}, {
+					text: '已开抢'
+				}, {
+					text: '抢购进行中'
+				}]
+			}
+		},
+		onPullDownRefresh() {
+			this.loadNavBar();
+		},
+		onLoad(options) {
+			this.loadNavBar();
+		},
+		methods: {
+			// 加载标签
+			async loadNavBar() {
+				let navbar = await this.$api.request('/flash/navbar', 'GET');
+				uni.stopPullDownRefresh();
+				if (navbar) {
+					this.navList = navbar;
+					this.navList.forEach((item, index) => {
+						item = Object.assign(item, {
+							list: [],
+							page: 1,
+							loadingType: 'more'
+						}); //新增一个数组用来储存商品
+						if (item.current) {
+							this.tabCurrentIndex = index;
+						}
+						// 建议把后端处理的starttime_hour和text字段在这里用js处理
+					});
+					this.loadData('tabChange', this.navList[this.tabCurrentIndex].flash_id);
+					this.setNavigationBarTitle(this.navList[this.tabCurrentIndex].title);
+					//console.log(this.navList);
+				}
+			},
+			// 加载数据
+			async loadData(source = false, flash_id = 0) {
+				//这里是将订单挂载到tab列表下
+				let index = this.tabCurrentIndex;
+				let navItem = this.navList[index];
+
+				if (source === 'tabChange' && navItem.loaded === true) {
+					//tab切换只有第一次需要加载数据
+					return;
+				}
+				if (navItem.loadingType === 'loading') {
+					//防止重复加载
+					return;
+				}
+				if (navItem.loadingType == 'noMore') {
+					//没有更多数据
+					return;
+				}
+
+				navItem.loadingType = 'loading';
+
+				let result = await this.$api.request('/flash/product', 'GET', {
+					flash_id: flash_id,
+					page: navItem.page,
+					pagesize: this.pageSize
+				});
+				if (result) {
+					if (result.length >= this.pageSize) {
+						//判断是否还有数据, 有改为 more, 没有改为noMore
+						navItem.loadingType = 'more';
+					} else {
+						navItem.loadingType = 'noMore';
+					}
+					// 页数加一
+					navItem.page++;
+
+					result.forEach((item, index) => {
+						navItem.list.push(item);
+					})
+					//loaded新字段用于表示数据加载完毕,如果为空可以显示空白页
+					this.$set(navItem, 'loaded', true);
+				}
+			},
+			//顶部tab点击
+			tabClick(index) {
+				this.tabCurrentIndex = index;
+			},
+			//swiper 切换
+			changeTab(e) {
+				this.tabCurrentIndex = e.target.current;
+				this.loadData('tabChange', this.navList[this.tabCurrentIndex].flash_id);
+				this.setNavigationBarTitle(this.navList[this.tabCurrentIndex].title);
+
+			},
+			// 计算百分比
+			percentage(number, sold) {
+				if (sold == 0) {
+					return 0;
+				}
+				return parseInt(sold / number * 100);
+			},
+			// 商品详情页
+			navToDetailPage(product_id, flash_id = 0) {
+				uni.navigateTo({
+					url: `/pages/product/product?id=${product_id}&flash=${flash_id}`
+				});
+			},
+			// 设置导航栏名称
+			setNavigationBarTitle(title) {
+				uni.setNavigationBarTitle({
+					title: '限时秒杀|' + title
+				});
+			}
+
+		}
+	}
+</script>
+
+<style lang="scss">
+	page,
+	.content {
+		//background: $page-color-base;
+		background: #f5f5f5;
+		height: 100%;
+	}
+
+	.swiper-box {
+		height: calc(100% - 50px);
+	}
+
+	.list-scroll-content {
+		height: 100%;
+	}
+
+	.navbar {
+		display: flex;
+		height: 50px;
+		padding: 0 5px;
+		background: #000;
+		box-shadow: 0 1px 5px rgba(0, 0, 0, .06);
+		position: relative;
+		z-index: 10;
+		overflow: auto;
+
+		.nav-item {
+			flex: 1;
+			display: flex;
+			justify-content: center;
+			align-items: center;
+			height: 100%;
+			font-size: 15px;
+			//color: $font-color-dark;
+			color: #fff;
+			font-weight: 900;
+			position: relative;
+			text-align: center;
+			min-width: 150upx;
+
+			.hour {
+				flex-flow: column;
+				display: flex;
+
+				.text {
+					font-size: 10px;
+					font-weight: 400;
+				}
+			}
+
+			&.current {
+				//color: $base-color;
+				background-color: $base-color;
+
+				&:after {
+					content: '';
+					position: absolute;
+					//left: 50%;
+					bottom: -10upx;
+					//transform: translateX(-50%);
+					transform: rotate(45deg);
+					//width: 44px;
+					width: 20upx;
+					//height: 0;
+					height: 20upx;
+					background-color: $base-color;
+					//border-bottom: 2px solid $base-color;
+				}
+			}
+		}
+	}
+
+	.order-item {
+		display: flex;
+		flex-direction: column;
+		padding-left: 30upx;
+		background: #fff;
+		margin-top: 16upx;
+
+		.info {
+			display: flex;
+			flex-direction: row;
+
+			.image {
+				image {
+					width: 250upx;
+					height: 250upx;
+					border-radius: 10upx;
+				}
+			}
+
+			.detail {
+				width: 440upx;
+				height: 250upx;
+				padding-left: 30upx;
+				padding-top: 6upx;
+				position: relative;
+
+				.title {
+					color: #303133;
+					overflow: hidden;
+					text-overflow: ellipsis;
+					white-space: nowrap;
+				}
+
+				.introduction {
+					color: #999999;
+					font-size: 26upx;
+					-webkit-line-clamp: 2;
+					overflow: hidden;
+					display: -webkit-box;
+					-webkit-box-orient: vertical;
+				}
+
+				.price {
+					position: absolute;
+					bottom: 56upx;
+					display: flex;
+					flex-direction: row;
+
+					.sales {
+						font-size: 40upx;
+						color: $base-color;
+						font-weight: 500;
+					}
+
+					.market {
+						vertical-align: bottom;
+						font-size: 25upx;
+						text-decoration: line-through;
+					}
+				}
+
+				.ProgressBar {
+					position: absolute;
+					bottom: 0;
+				}
+
+				.loot {
+					position: absolute;
+					right: 0;
+					bottom: 14rpx;
+					background: $base-color;
+					color: #fff;
+					padding: 4upx 14upx;
+					border-radius: 4upx;
+					box-shadow: 2upx 2upx 8upx -2px #000;
+					font-size: 32rpx;
+				}
+			}
+		}
+	}
+
+
+	/* load-more */
+	.uni-load-more {
+		display: flex;
+		flex-direction: row;
+		height: 80upx;
+		align-items: center;
+		justify-content: center
+	}
+
+	.uni-load-more__text {
+		font-size: 28upx;
+		color: #999
+	}
+
+	.uni-load-more__img {
+		height: 24px;
+		width: 24px;
+		margin-right: 10px
+	}
+
+	.uni-load-more__img>view {
+		position: absolute
+	}
+
+	.uni-load-more__img>view view {
+		width: 6px;
+		height: 2px;
+		border-top-left-radius: 1px;
+		border-bottom-left-radius: 1px;
+		background: #999;
+		position: absolute;
+		opacity: .2;
+		transform-origin: 50%;
+		animation: load 1.56s ease infinite
+	}
+
+	.uni-load-more__img>view view:nth-child(1) {
+		transform: rotate(90deg);
+		top: 2px;
+		left: 9px
+	}
+
+	.uni-load-more__img>view view:nth-child(2) {
+		transform: rotate(180deg);
+		top: 11px;
+		right: 0
+	}
+
+	.uni-load-more__img>view view:nth-child(3) {
+		transform: rotate(270deg);
+		bottom: 2px;
+		left: 9px
+	}
+
+	.uni-load-more__img>view view:nth-child(4) {
+		top: 11px;
+		left: 0
+	}
+
+	.load1,
+	.load2,
+	.load3 {
+		height: 24px;
+		width: 24px
+	}
+
+	.load2 {
+		transform: rotate(30deg)
+	}
+
+	.load3 {
+		transform: rotate(60deg)
+	}
+
+	.load1 view:nth-child(1) {
+		animation-delay: 0s
+	}
+
+	.load2 view:nth-child(1) {
+		animation-delay: .13s
+	}
+
+	.load3 view:nth-child(1) {
+		animation-delay: .26s
+	}
+
+	.load1 view:nth-child(2) {
+		animation-delay: .39s
+	}
+
+	.load2 view:nth-child(2) {
+		animation-delay: .52s
+	}
+
+	.load3 view:nth-child(2) {
+		animation-delay: .65s
+	}
+
+	.load1 view:nth-child(3) {
+		animation-delay: .78s
+	}
+
+	.load2 view:nth-child(3) {
+		animation-delay: .91s
+	}
+
+	.load3 view:nth-child(3) {
+		animation-delay: 1.04s
+	}
+
+	.load1 view:nth-child(4) {
+		animation-delay: 1.17s
+	}
+
+	.load2 view:nth-child(4) {
+		animation-delay: 1.3s
+	}
+
+	.load3 view:nth-child(4) {
+		animation-delay: 1.43s
+	}
+
+	@-webkit-keyframes load {
+		0% {
+			opacity: 1
+		}
+
+		100% {
+			opacity: .2
+		}
+	}
+</style>

File diff suppressed because it is too large
+ 454 - 0
pages/index/index.vue


+ 22 - 0
pages/money/money.vue

@@ -0,0 +1,22 @@
+<template>
+	<view>
+		
+	</view>
+</template>
+
+<script>
+	export default {
+		data() {
+			return {
+				
+			}
+		},
+		methods: {
+			
+		}
+	}
+</script>
+
+<style>
+
+</style>

+ 366 - 0
pages/money/pay.vue

@@ -0,0 +1,366 @@
+<template>
+	<view class="app">
+		<view class="price-box">
+			<text>支付金额</text>
+			<text class="price">{{total}}</text>
+		</view>
+
+		<view class="pay-type-list">
+			<view class="type-item b-b" @click="changePayType(1)" v-if="payTypeList.wxpay && total > 0">
+				<text class="icon yticon icon-wxpay"></text>
+				<view class="con">
+					<text class="tit">微信支付</text>
+				</view>
+				<label class="radio">
+					<radio value="" color="#fa436a" :checked='payType == 1' />
+					</radio>
+				</label>
+			</view>
+			<view class="type-item b-b" @click="changePayType(2)" v-if="payTypeList.alipay && total > 0">
+				<text class="icon yticon icon-alipay"></text>
+				<view class="con">
+					<text class="tit">支付宝支付</text>
+				</view>
+				<label class="radio">
+					<radio value="" color="#fa436a" :checked='payType == 2' />
+					</radio>
+				</label>
+			</view>
+			<view class="type-item b-b" @click="changePayType(3)" v-if="payTypeList.offline">
+				<text class="icon yticon icon-pay"></text>
+				<view class="con">
+					<text class="tit">货到付款</text>
+				</view>
+				<label class="radio">
+					<radio value="" color="#fa436a" :checked='payType == 3' />
+					</radio>
+				</label>
+			</view>
+		</view>
+
+		<text class="mix-btn" @click="confirm">确认支付</text>
+	</view>
+</template>
+
+<script>
+	// #ifdef H5
+	var jweixin = require('jweixin-module');
+	// #endif
+
+	export default {
+		data() {
+			return {
+				payType: 1,
+				orderInfo: {},
+				orderId: '',
+				out_trade_no: '',
+				payTypeList: {
+					wxpay: false,
+					alipay: false
+				},
+				total: 0.00
+			};
+		},
+		onLoad(options) {
+			this.total = options.total;
+			this.orderId = options.order_id;
+			this.out_trade_no = options.out_trade_no;
+			this.getPayType();
+		},
+		methods: {
+			// 获取支付方式
+			async getPayType() {
+				let type = await this.$api.request('/pay/getPayType');
+				if (type) {
+					this.payTypeList = type;
+				}
+			},
+			//选择支付方式
+			changePayType(type) {
+				this.payType = type;
+				switch (type) {
+					case 1: // 微信支付
+
+						break;
+					case 2: // 支付宝支付
+
+						break;
+					case 3: // 货到付款
+						break;
+				}
+			},
+			//确认支付
+			async confirm() {
+
+				if (this.payType == 1) {
+					// #ifdef H5 || APP-PLUS || MP-WEIXIN
+					this.weixinPay();
+					// #endif
+				} else if (this.payType == 2) {
+					// 支付宝支付
+					this.alipay();
+				} else if (this.payType == 3) {
+					// 货到付款
+					this.offlinePay();
+				}
+			},
+			async alipay() {
+				
+				// #ifdef H5
+				window.open(this.$unishow + '/pay/alipay?order_id='+this.orderId);
+				
+				setTimeout(function() {
+					uni.showModal({
+						title: '提示',
+						content: '是否已支付?',
+						cancelText: '否',
+						confirmText: '是',
+						success: function(res) {
+							if (res.confirm) {
+								uni.redirectTo({
+									url: '/pages/order/order?state=0'
+								});
+							} else if (res.cancel) {
+								//console.log('用户点击取消');
+							}
+						},
+						fail: function(res) {
+							//console.log(res)
+						}
+					});
+				}, 3000);
+				// #endif
+
+				// #ifdef APP-PLUS
+				let orderInfo = await this.$api.request('/pay/alipay', 'POST',{
+					order_id : this.orderId
+				});
+				if (orderInfo) {
+					//console.log(orderInfo);
+					uni.requestPayment({
+						provider: 'alipay',
+						orderInfo: orderInfo,
+						success: function (res) {
+							console.log('success:' + JSON.stringify(res));
+							uni.redirectTo({
+								url: '/pages/money/paySuccess'
+							})
+						},
+						fail: function (err) {
+							console.log('fail:' + JSON.stringify(err));
+							that.$api.msg('支付失败');
+						}
+					});
+				}
+				
+				// #endif
+				
+			},
+			async weixinPay() {
+				let data = await this.$api.request('/pay/unify', 'GET', {
+					order_id: this.orderId
+				});
+				let that = this;
+				if (data) {
+
+					if (data.trade_type == 'MWEB') {
+						// #ifdef H5
+						// 微信外的H5
+						location.href = data.mweb_url;
+						// #endif
+
+						// #ifdef APP-PLUS
+						// app 使用h5支付
+						var wv; //计划创建的webview 
+						wv = plus.webview.create("", "custom-webview", {
+							'uni-app': 'none', //不加载uni-app渲染层框架,避免样式冲突
+						})
+						wv.loadURL(data.mweb_url, {
+							Referer: data.referer
+						});
+
+						setTimeout(function() {
+							uni.showModal({
+								title: '提示',
+								content: '是否已支付?',
+								cancelText: '否',
+								confirmText: '是',
+								success: function(res) {
+									if (res.confirm) {
+										uni.redirectTo({
+											url: '/pages/order/order?state=0'
+										});
+									} else if (res.cancel) {
+										//console.log('用户点击取消');
+									}
+								},
+								fail: function(res) {
+									//console.log(res)
+								}
+							});
+						}, 3000);
+						// #endif
+
+					} else if (data.trade_type == 'JSAPI') {
+
+						// #ifdef H5
+						// 微信内的H5
+						let config = await this.$api.request('/pay/jssdkBuildConfig');
+						if (config) {
+							jweixin.config(config);
+							jweixin.ready(function() {
+								jweixin.chooseWXPay({
+									timestamp: data.timeStamp, // 支付签名时间戳,注意微信jssdk中的所有使用timestamp字段均为小写。但最新版的支付后台生成签名使用的timeStamp字段名需大写其中的S字符
+									nonceStr: data.nonce_str, // 支付签名随机串,不长于 32 位
+									package: 'prepay_id=' + data.prepay_id, // 统一支付接口返回的prepay_id参数值,提交格式如:prepay_id=\*\*\*)
+									signType: 'MD5', // 签名方式,默认为'SHA1',使用新版支付需传入'MD5'
+									paySign: data.paySign, // 支付签名
+									success: function(res) {
+										// 支付成功后的回调函数
+										uni.redirectTo({
+											url: '/pages/money/paySuccess'
+										})
+									},
+									fail: function(err) {
+										//console.log('fail:' + JSON.stringify(err));
+										//that.$api.msg('fail:' + JSON.stringify(err))
+										that.$api.msg('支付失败');
+									}
+								})
+							});
+							jweixin.error(function(res) {
+								//that.$api.msg(JSON.stringify(res));
+								that.$api.msg('支付失败');
+							});
+						} else {
+							that.$api.msg('支付失败');
+						}
+						// #endif
+
+						// #ifdef MP-WEIXIN
+						uni.requestPayment({
+							provider: 'wxpay',
+							timeStamp: data.timeStamp,
+							nonceStr: data.nonce_str,
+							package: 'prepay_id=' + data.prepay_id,
+							signType: 'MD5',
+							paySign: data.paySign,
+							success: function(res) {
+								uni.redirectTo({
+									url: '/pages/money/paySuccess'
+								})
+							},
+							fail: function(err) {
+								//console.log('fail:' + JSON.stringify(err));
+								//that.$api.msg('fail:' + JSON.stringify(err))
+								that.$api.msg('支付失败');
+							}
+						});
+						// #endif
+					}
+				}
+			},
+
+			async offlinePay() {
+				let data = await this.$api.request('/pay/offline', 'GET', {
+					order_id: this.orderId
+				});
+				if (data) {
+					uni.redirectTo({
+						url: '/pages/money/paySuccess'
+					});
+				}
+			}
+		}
+	}
+</script>
+
+<style lang='scss'>
+	.app {
+		width: 100%;
+	}
+
+	.price-box {
+		background-color: #fff;
+		height: 265upx;
+		display: flex;
+		flex-direction: column;
+		justify-content: center;
+		align-items: center;
+		font-size: 28upx;
+		color: #909399;
+
+		.price {
+			font-size: 50upx;
+			color: #303133;
+			margin-top: 12upx;
+
+			&:before {
+				content: '¥';
+				font-size: 40upx;
+			}
+		}
+	}
+
+	.pay-type-list {
+		margin-top: 20upx;
+		background-color: #fff;
+		padding-left: 60upx;
+
+		.type-item {
+			height: 120upx;
+			padding: 20upx 0;
+			display: flex;
+			justify-content: space-between;
+			align-items: center;
+			padding-right: 60upx;
+			font-size: 30upx;
+			position: relative;
+		}
+
+		.icon {
+			width: 100upx;
+			font-size: 52upx;
+		}
+
+		.icon-pay {
+			color: #fe8e2e;
+		}
+
+		.icon-wxpay {
+			color: #36cb59;
+		}
+
+		.icon-alipay {
+			color: #01aaef;
+		}
+
+		.tit {
+			font-size: $font-lg;
+			color: $font-color-dark;
+			margin-bottom: 4upx;
+		}
+
+		.con {
+			flex: 1;
+			display: flex;
+			flex-direction: column;
+			font-size: $font-sm;
+			color: $font-color-light;
+		}
+	}
+
+	.mix-btn {
+		display: flex;
+		align-items: center;
+		justify-content: center;
+		width: 630upx;
+		height: 80upx;
+		margin: 80upx auto 30upx;
+		font-size: $font-lg;
+		color: #fff;
+		background-color: $base-color;
+		border-radius: 10upx;
+		box-shadow: 1px 2px 5px rgba(219, 63, 96, 0.4);
+	}
+</style>

+ 61 - 0
pages/money/paySuccess.vue

@@ -0,0 +1,61 @@
+<template>
+	<view class="content">
+		<text class="success-icon yticon icon-xuanzhong"></text>
+		<text class="tit">支付成功</text>
+		<view class="btn-group">
+			<navigator url="/pages/order/order?state=0" open-type="redirect" class="mix-btn">查看订单</navigator>
+			<navigator url="/pages/index/index" open-type="switchTab" class="mix-btn hollow">返回首页</navigator>
+		</view>
+	</view>
+</template>
+
+<script>
+	export default {
+		data() {
+			return {
+				
+			}
+		},
+		methods: {
+			
+		}
+	}
+</script>
+
+<style lang='scss'>
+	.content{
+		display: flex;
+		flex-direction: column;
+		justify-content: center;
+		align-items: center;
+	}
+	.success-icon{
+		font-size: 160upx;
+		color: #fa436a;
+		margin-top: 100upx;
+	}
+	.tit{
+		font-size: 38upx;
+		color: #303133;
+	}
+	.btn-group{
+		padding-top: 100upx;
+	}
+	.mix-btn {
+		margin-top: 30upx;
+		display: flex;
+		align-items: center;
+		justify-content: center;
+		width: 600upx;
+		height: 80upx;
+		font-size: $font-lg;
+		color: #fff;
+		background-color: $base-color;
+		border-radius: 10upx;
+		&.hollow{
+			background: #fff;
+			color: #303133;
+			border: 1px solid #ccc;
+		}
+	}
+</style>

+ 153 - 0
pages/notice/notice.vue

@@ -0,0 +1,153 @@
+<template>
+	<view>
+		<view class="notice-item">
+			<text class="time">11:30</text>
+			<view class="content">
+				<text class="title">新品上市,全场满199减50</text>
+				<view class="img-wrapper">
+					<image class="pic" src="https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1556465765776&di=57bb5ff70dc4f67dcdb856e5d123c9e7&imgtype=0&src=http%3A%2F%2Fimg.zcool.cn%2Fcommunity%2F01fd015aa4d95fa801206d96069229.jpg%401280w_1l_2o_100sh.jpg"></image>
+				</view>
+				<text class="introduce">
+					虽然做了一件好事,但很有可能因此招来他人的无端猜测,例如被质疑是否藏有其他利己动机等,乃至谴责。即便如此,还是要做好事。
+				</text>
+				<view class="bot b-t">
+					<text>查看详情</text>
+					<text class="more-icon yticon icon-you"></text>
+				</view>
+			</view>
+		</view>
+		<view class="notice-item">
+			<text class="time">昨天 12:30</text>
+			<view class="content">
+				<text class="title">新品上市,全场满199减50</text>
+				<view class="img-wrapper">
+					<image class="pic" src="https://ss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=3761064275,227090144&fm=26&gp=0.jpg"></image>
+					<view class="cover">
+						活动结束
+					</view>
+				</view>
+				<view class="bot b-t">
+					<text>查看详情</text>
+					<text class="more-icon yticon icon-you"></text>
+				</view>
+			</view>
+		</view>
+		<view class="notice-item">
+			<text class="time">2019-07-26 12:30</text>
+			<view class="content">
+				<text class="title">新品上市,全场满199减50</text>
+				<view class="img-wrapper">
+					<image class="pic" src="https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1556465765776&di=57bb5ff70dc4f67dcdb856e5d123c9e7&imgtype=0&src=http%3A%2F%2Fimg.zcool.cn%2Fcommunity%2F01fd015aa4d95fa801206d96069229.jpg%401280w_1l_2o_100sh.jpg"></image>
+					<view class="cover">
+						活动结束
+					</view>
+				</view>
+				<text class="introduce">新品上市全场2折起,新品上市全场2折起,新品上市全场2折起,新品上市全场2折起,新品上市全场2折起</text>
+				<view class="bot b-t">
+					<text>查看详情</text>
+					<text class="more-icon yticon icon-you"></text>
+				</view>
+			</view>
+		</view>
+	</view>
+</template>
+
+<script>
+	export default {
+		data() {
+			return {
+
+			}
+		},
+		methods: {
+
+		}
+	}
+</script>
+
+<style lang='scss'>
+	page {
+		background-color: #f7f7f7;
+		padding-bottom: 30upx;
+	}
+
+	.notice-item {
+		display: flex;
+		flex-direction: column;
+		align-items: center;
+	}
+
+	.time {
+		display: flex;
+		align-items: center;
+		justify-content: center;
+		height: 80upx;
+		padding-top: 10upx;
+		font-size: 26upx;
+		color: #7d7d7d;
+	}
+
+	.content {
+		width: 710upx;
+		padding: 0 24upx;
+		background-color: #fff;
+		border-radius: 4upx;
+	}
+
+	.title {
+		display: flex;
+		align-items: center;
+		height: 90upx;
+		font-size: 32upx;
+		color: #303133;
+	}
+
+	.img-wrapper {
+		width: 100%;
+		height: 260upx;
+		position: relative;
+	}
+
+	.pic {
+		display: block;
+		width: 100%;
+		height: 100%;
+		border-radius: 6upx;
+	}
+
+	.cover {
+		display: flex;
+		justify-content: center;
+		align-items: center;
+		position: absolute;
+		left: 0;
+		top: 0;
+		width: 100%;
+		height: 100%;
+		background-color: rgba(0, 0, 0, .5);
+		font-size: 36upx;
+		color: #fff;
+	}
+
+	.introduce {
+		display: inline-block;
+		padding: 16upx 0;
+		font-size: 28upx;
+		color: #606266;
+		line-height: 38upx;
+	}
+
+	.bot {
+		display: flex;
+		align-items: center;
+		justify-content: space-between;
+		height: 80upx;
+		font-size: 24upx;
+		color: #707070;
+		position: relative;
+	}
+
+	.more-icon {
+		font-size: 32upx;
+	}
+</style>

File diff suppressed because it is too large
+ 17 - 0
pages/order/createOrder.vue


+ 43 - 0
pages/order/delivery.vue

@@ -0,0 +1,43 @@
+<template>
+	<view>
+		<zTable :tableData="tableData" :columns="arr"></zTable>
+	</view>
+</template>
+
+<script>
+	import zTable from '@/components/z-table/z-table';
+	export default{
+		components:{
+			zTable
+		},
+		data() {
+			return{
+				tableData:['key1', 'key2'],
+				arr:[
+					{
+						"title": "title1",
+						"key" :"0"
+					},
+					{
+						"title": "title2",
+						"key" :"1"
+					}
+				]
+			}
+		},
+		onLoad() {
+			this.getTemplate();
+		},
+		methods:{
+			async getTemplate(){
+				let data = this.$api.request('');
+				if (data) {
+					
+				}
+			}
+		}
+	}
+</script>
+
+<style lang="scss">
+</style>

+ 172 - 0
pages/order/evaluate.vue

@@ -0,0 +1,172 @@
+<template>
+	<view class="bg">
+		<view class="header">
+			<image class="image" :src="image"></image>
+			<view class="right">
+				<view class="title">{{title}}</view>
+				<view class="spec">{{spec}}</view>
+			</view>
+		</view>
+		<view class="rate">
+			<view class="dec">描述相符</view>
+			<!-- 自定义星星大小 -->
+			<uni-rate class="start" active-color="#e64340" margin="10" size="30" :value="rate" @change="start"></uni-rate>
+		</view>
+		<view class="comment">
+			<textarea @input="input" maxlength="300" placeholder="写满20字,有机会被选为优质评价被更多人看到哦~" placeholder-class="placeholder"></textarea>
+		</view>
+		<view class="bottom">
+			<radio class="radio" color="#e64340" :checked='radio' @click="clickRadio" />公开</radio>
+			<view class="dec">公开头像昵称,大家可以看到我</view>
+		</view>
+		<button class="button" @click="submit" type="warn">提交</button>
+	</view>
+</template>
+
+<script>
+	import uniRate from '@/components/uni-rate/uni-rate.vue'
+
+	export default{
+		computed:{
+
+		},
+		components: {
+			uniRate
+		},
+		data(){
+			return {
+				order_id:0,
+				product_id:0,
+				title:'',
+				image:'',
+				spec:'',
+				rate:5,
+				radio:true,
+				textarea:''
+			}
+		},
+		onLoad(options) {
+			this.title = options.title;
+			this.image = options.image;
+			this.spec = options.spec;
+			this.order_id = options.order_id;
+			this.product_id = options.product_id;
+		},
+		methods:{
+			// 星星
+			start(e) {
+				this.rate = e.value;
+				switch (this.rate) {
+					case 3:
+						this.radio = false;
+						break;
+					case 5:
+						this.radio = true;
+						break;
+				}
+			},
+			// 单选
+			clickRadio(e) {
+				this.radio = !this.radio;
+			},
+			// 输入事件
+			input(e) {
+				this.textarea = e.detail.value;
+			},
+			// 提交
+			async submit(){
+				let data = this.$api.request('/order/comment', 'POST', 
+				{
+					order_id:this.order_id,
+					product_id:this.product_id,
+					rate:this.rate,
+					anonymous:!this.radio ? 1: 0,
+					comment:this.textarea,
+				});
+				if (data) {
+					let prePage = this.$api.prePage()
+					if (prePage.tabCurrentIndex) {
+						prePage.tabCurrentIndex = 0;
+					}
+					let that = this;
+					setTimeout(function(){
+						prePage.pullDownRefresh();
+						uni.navigateBack();
+					},2000)
+				}
+			}
+		}
+	}
+</script>
+
+<style lang="scss">
+	page{
+		background: #f5f5f5;
+	}
+	.bg{
+		margin: 20rpx;
+		border-radius: 20rpx;
+		background-color: #ffffff;
+		padding-bottom: 10rpx;
+	}
+	.header {
+		padding: 20rpx;
+		.image{
+			width: 120rpx;
+			height: 120rpx;
+		}
+		.right{
+			display: inline-block;
+			line-height: 45rpx;
+			vertical-align: text-bottom;
+			padding-bottom: 10rpx; 
+			width: 550rpx;
+			padding-left:20rpx;
+			.title{
+				color: #707277;
+				font-size: 33rpx;
+				text-overflow: ellipsis;
+				white-space: nowrap;
+				overflow: hidden;
+			}
+			.spec{
+				color: #91949a;
+				font-size: 30rpx;
+			}
+		}
+	}
+	.rate{
+		padding:0 20rpx 20rpx;
+		.dec{
+			display: inline-block;
+			color: #707277;
+		}
+		.start{
+			width: 400rpx;
+			display: inline-block;
+
+		}
+	}
+	.comment{
+		padding: 20rpx;
+		textarea{
+			padding: 30rpx;
+			background-color: #f5f5f5;
+			width: 100%;
+		}
+	}
+	.bottom{
+		padding: 20rpx 20rpx 40rpx;
+		.radio{
+			vertical-align: bottom;
+			float: left;
+		}
+		.dec{
+			color: #91949a;
+			float: right;
+		}
+	}
+	.button{
+		margin: 20rpx;
+	}
+</style>

+ 790 - 0
pages/order/order.vue

@@ -0,0 +1,790 @@
+<template>
+	<view class="content">
+		<view class="navbar">
+			<view v-for="(item, index) in navList" :key="index" class="nav-item" :class="{current: tabCurrentIndex === index}"
+			 @click="tabClick(index)">
+				{{item.text}}
+			</view>
+		</view>
+
+		<swiper :current="tabCurrentIndex" class="swiper-box" duration="300" @change="changeTab">
+			<swiper-item class="tab-content" v-for="(tabItem,tabIndex) in navList" :key="tabIndex">
+				<scroll-view class="list-scroll-content" scroll-y @scrolltolower="loadData">
+					<!-- 空白页 -->
+					<empty v-if="tabItem.loaded === true && tabItem.orderList.length === 0"></empty>
+
+					<!-- 订单列表 -->
+					<view v-for="(item,index) in tabItem.orderList" :key="index" class="order-item" @click="navTo('/pages/order/orderDetail?order_id='+item.order_id)">
+						<view class="i-top b-b">
+							<text class="time">{{item.createtime}}</text>
+							<text class="state" :style="{color: item.stateTipColor}">{{item.stateTip + (item.refund_status_text ? ':'+item.refund_status_text : '')}} </text>
+							<text v-if="item.state===9" class="del-btn yticon icon-lajitong" @click.stop="deleteOrder(index)"></text>
+						</view>
+
+						<view class="goods-box-single" v-for="(goodsItem, goodsIndex) in item.products"
+						 :key="goodsIndex">
+							<image class="goods-img" :src="goodsItem.image" mode="aspectFill"></image>
+							<view class="right">
+								<text class="refund" v-if="goodsItem.refund">退款成功</text>
+								<text class="title clamp">{{goodsItem.title}}</text>
+								<text class="attr-box">{{goodsItem.spec}} x {{goodsItem.number}}</text>
+								<text class="price">{{goodsItem.price}}</text>
+								<button class="action-btn" v-if="item.have_received != 0 && goodsItem.evaluate == false && goodsItem.refund==false" @click.stop="button('evaluate', {order_id:item.order_id,id:goodsItem.id,image:goodsItem.image,title:goodsItem.title,spec:goodsItem.spec})">评价</button>
+							</view>
+						</view>
+
+						<view class="price-box">
+							共
+							<text class="num">{{quantity(item.products)}}</text>
+							件商品 合计
+							<text class="price">{{item.total_price}}</text>
+
+							<text v-if="item.discount_price > 0">(已优惠¥{{item.discount_price}})</text>
+							<text v-if="item.delivery_price > 0">(含运费¥{{item.delivery_price}})</text>
+						</view>
+						<view class="action-box b-t" v-if="item.state != 9">
+							<button class="action-btn" v-if="item.state == 1" @click.stop="button('cancel',item)">取消订单</button>
+							<button class="action-btn recom" v-if="item.have_paid == 0" @click.stop="button('pay',item)">立即支付</button>
+							<!-- <button class="action-btn" v-if="item.have_paid != 0 && item.have_delivered == 0">提醒发货</button> -->
+							<button class="action-btn" v-if="item.have_paid != 0" @click.stop="button('delivery',item)">查看物流</button>
+							<button class="action-btn" v-if="item.have_paid != 0 && item.have_received == 0" @click.stop="button('recerved',item)">确认收货</button>
+							<!-- <button class="action-btn" v-if="item.have_received != 0 && item.have_commented != 0">追加评价</button> -->
+							<button class="action-btn" v-if="item.have_paid != 0" @click.stop="button('refund', item)">申请售后</button>
+						</view>
+						<view class="action-box b-t" v-if="item.state == 9 && item.status == -1">
+							<button class="action-btn" v-if="item.have_paid != 0" @click.stop="button('refund', item)">查看售后</button>
+						</view>
+					</view>
+					<uni-load-more :status="tabItem.loadingType"></uni-load-more>
+				</scroll-view>
+			</swiper-item>
+		</swiper>
+	</view>
+</template>
+
+<script>
+	import uniLoadMore from '@/components/uni-load-more/uni-load-more.vue';
+	import empty from "@/components/empty";
+	export default {
+		components: {
+			uniLoadMore,
+			empty
+		},
+		data() {
+			return {
+				tabCurrentIndex: 0,
+				navList: [{
+						state: 0,
+						text: '全部',
+						loadingType: 'more',
+						orderList: [],
+						page: 1
+					},
+					{
+						state: 1,
+						text: '待付款',
+						loadingType: 'more',
+						orderList: [],
+						page: 1
+					},
+					{
+						state: 2,
+						text: '待发货',
+						loadingType: 'more',
+						orderList: [],
+						page: 1
+					},
+					{
+						state: 3,
+						text: '待收货',
+						loadingType: 'more',
+						orderList: [],
+						page: 1
+					},
+					{
+						state: 4,
+						text: '评价',
+						loadingType: 'more',
+						orderList: [],
+						page: 1
+					},
+					{
+						state: 5,
+						text: '售后',
+						loadingType: 'more',
+						orderList: [],
+						page: 1
+					}
+				],
+				pageSize: 10
+			};
+		},
+		onLoad(options) {
+			/**
+			 * 修复app端点击除全部订单外的按钮进入时不加载数据的问题
+			 * 替换onLoad下代码即可
+			 */
+			this.tabCurrentIndex = +options.state;
+			// #ifndef MP
+			this.loadData()
+			// #endif
+			// #ifdef MP
+			if (options.state == 0) {
+				this.loadData()
+			}
+			// #endif
+		},
+		onPullDownRefresh() {
+			this.pullDownRefresh()
+		},
+		methods: {
+			//获取订单列表
+			async loadData(source) {
+				//这里是将订单挂载到tab列表下
+				let index = this.tabCurrentIndex;
+				let navItem = this.navList[index];
+				let state = navItem.state;
+
+				if (source === 'tabChange' && navItem.loaded === true) {
+					//tab切换只有第一次需要加载数据
+					return;
+				}
+				if (navItem.loadingType === 'loading') {
+					//防止重复加载
+					return;
+				}
+				if (navItem.loadingType == 'noMore') {
+					//没有更多数据
+					return;
+				}
+
+				navItem.loadingType = 'loading';
+
+				let result = await this.$api.request('/order/getOrders', 'GET', {
+					type: state,
+					page: navItem.page,
+					pagesize: this.pageSize
+				});
+				uni.stopPullDownRefresh();
+				if (!result) {
+					navItem.loadingType = 'more';
+				} else {
+					//console.log(result)
+					if (result.length >= this.pageSize) {
+						//判断是否还有数据, 有改为 more, 没有改为noMore
+						navItem.loadingType = 'more';
+					} else {
+						navItem.loadingType = 'noMore';
+					}
+					// 页数加一
+					navItem.page++;
+					result.forEach(item => {
+						item = Object.assign(item, this.orderStateExp(item.state));
+						navItem.orderList.push(item);
+					});
+					//loaded新字段用于表示数据加载完毕,如果为空可以显示空白页
+					this.$set(navItem, 'loaded', true);
+				}
+
+			},
+
+			//swiper 切换
+			changeTab(e) {
+				this.tabCurrentIndex = e.target.current;
+				this.loadData('tabChange');
+			},
+			//顶部tab点击
+			tabClick(index) {
+				this.tabCurrentIndex = index;
+			},
+			//删除订单
+			async deleteOrder(index) {
+				let [error, res] = await uni.showModal({
+					title: '确认删除订单'
+				});
+				if (res.confirm) { 
+					let order_id = this.navList[this.tabCurrentIndex].orderList[index].order_id;
+					let result = await this.$api.request('/order/delete?order_id=' + order_id)
+					if (result) {
+						this.navList[this.tabCurrentIndex].orderList.splice(index, 1);
+					}
+				}
+				
+			},
+			//取消订单
+			async cancelOrder(item) {
+				let that = this;
+				let [error, res] = await uni.showModal({
+					title: '确认取消订单',
+					content: '取消之后不可恢复',
+				});
+				if (res.confirm) {
+					let result = await that.$api.request('/order/cancel?order_id=' + item.order_id);
+					if (result) {
+						let {
+							stateTip,
+							stateTipColor
+						} = that.orderStateExp(9);
+						item = Object.assign(item, {
+							state: 9,
+							stateTip,
+							stateTipColor
+						});
+						// 更新全部订单里面的状态
+						let position = that.navList[0].orderList.findIndex(val => val.order_id === item.order_id);
+						if (position !== -1) {
+							that.navList[0].orderList[position] = item;
+						}
+
+						//取消订单后删除待付款中该项
+						let list = that.navList[1].orderList;
+						let index = list.findIndex(val => val.order_id === item.order_id);
+						index !== -1 && list.splice(index, 1);
+					}
+					return true;
+				}
+				//this.$api.msg('取消失败');
+				return false;
+			},
+			// 收货
+			async receivedOrder(item) {
+				let that = this;
+				let [error, res] = await uni.showModal({
+					title: '确认收货'
+				});
+				if (res.confirm == true) {
+					let res = await that.$api.request('/order/received', 'GET', {
+						order_id: item.order_id
+					});
+					if (res) {
+						// 已确认收货
+				
+						let {
+							stateTip,
+							stateTipColor
+						} = that.orderStateExp(4);
+						
+						// 更新全部订单里面的状态
+						let position = that.navList[0].orderList.findIndex(val => val.order_id === item.order_id);
+						if (position !== -1) {
+							let item = that.navList[0].orderList[position];
+							console.log(item);
+							that.navList[0].orderList[position] = Object.assign(item, {
+								state: 4,
+								stateTip,
+								stateTipColor,
+								have_received: 1
+							});
+						}
+				
+						//收货订单后删除待收货中该项
+						let list = that.navList[3].orderList;
+						let index = list.findIndex(val => val.order_id === item.order_id);
+						index !== -1 && list.splice(index, 1);
+					}
+					return true;
+				}
+				this.$api.msg('收货失败');
+				return false;
+			},
+			// 订单状态文字和颜色
+			orderStateExp(state) {
+				let stateTip = '',
+					stateTipColor = '#fa436a';
+				switch (+state) {
+					case 0:
+						stateTip = '交易成功';
+						break;
+					case 1:
+						stateTip = '待付款';
+						break;
+					case 2:
+						stateTip = '待发货';
+						break;
+					case 3:
+						stateTip = '待收货';
+						break;
+					case 4:
+						stateTip = '待评价';
+						break;
+					case 5:
+						stateTip = '售后';
+						break;
+					case 6:
+						stateTip = '拒绝退款';
+						break;
+					case 9:
+						stateTip = '订单已关闭';
+						stateTipColor = '#909399';
+						break;
+
+						//更多自定义
+				}
+				return {
+					stateTip,
+					stateTipColor
+				};
+			},
+			// 计算当前订单有多少个商品
+			quantity(products) {
+				let number = 0;
+				for (let i in products) {
+					number += parseInt(products[i].number);
+				}
+				return number;
+			},
+			navTo(url) {
+				this.$api.navTo(url);
+			},
+			// 下面的一排按钮
+			button(action, item) {
+				switch (action) {
+					case 'cancel':
+						this.cancelOrder(item);
+						break;
+					case 'pay':
+						this.navTo('/pages/money/pay?order_id=' + item.order_id + '&total=' + item.total_price);
+						break;
+					case 'delivery':
+						this.navTo('/pages/public/webview?type=kd&number=' + item.extend.express_number);
+						break;
+					case 'recerved':
+						this.receivedOrder(item);
+						break;
+					case 'evaluate':
+						this.$api.navTo('/pages/order/evaluate?product_id='+item.id+'&order_id='+item.order_id+'&image='+item.image+'&title='+item.title+'&spec='+item.spec);
+						break;
+					case 'refund':
+						this.$api.navTo('/pages/order/refund?order_id=' + item.order_id);
+						break;
+				}
+			},
+			pullDownRefresh() {
+				this.navList = [];
+				this.navList = [{
+						state: 0,
+						text: '全部',
+						loadingType: 'more',
+						orderList: [],
+						page: 1
+					},
+					{
+						state: 1,
+						text: '待付款',
+						loadingType: 'more',
+						orderList: [],
+						page: 1
+					},
+					{
+						state: 2,
+						text: '待发货',
+						loadingType: 'more',
+						orderList: [],
+						page: 1
+					},
+					{
+						state: 3,
+						text: '待收货',
+						loadingType: 'more',
+						orderList: [],
+						page: 1
+					},
+					{
+						state: 4,
+						text: '评价',
+						loadingType: 'more',
+						orderList: [],
+						page: 1
+					},
+					{
+						state: 5,
+						text: '售后',
+						loadingType: 'more',
+						orderList: [],
+						page: 1
+					}
+				];
+				this.loadData();
+			}
+		},
+	}
+</script>
+
+<style lang="scss">
+	page,
+	.content {
+		background: $page-color-base;
+		height: 100%;
+	}
+
+	.swiper-box {
+		height: calc(100% - 40px);
+	}
+
+	.list-scroll-content {
+		height: 100%;
+	}
+
+	.navbar {
+		display: flex;
+		height: 40px;
+		padding: 0 5px;
+		background: #fff;
+		box-shadow: 0 1px 5px rgba(0, 0, 0, .06);
+		position: relative;
+		z-index: 10;
+
+		.nav-item {
+			flex: 1;
+			display: flex;
+			justify-content: center;
+			align-items: center;
+			height: 100%;
+			font-size: 15px;
+			color: $font-color-dark;
+			position: relative;
+
+			&.current {
+				color: $base-color;
+
+				&:after {
+					content: '';
+					position: absolute;
+					left: 50%;
+					bottom: 0;
+					transform: translateX(-50%);
+					width: 44px;
+					height: 0;
+					border-bottom: 2px solid $base-color;
+				}
+			}
+		}
+	}
+
+	.uni-swiper-item {
+		height: auto;
+	}
+
+	.order-item {
+		display: flex;
+		flex-direction: column;
+		padding-left: 30upx;
+		background: #fff;
+		margin-top: 16upx;
+
+		.i-top {
+			display: flex;
+			align-items: center;
+			height: 80upx;
+			padding-right: 30upx;
+			font-size: $font-base;
+			color: $font-color-dark;
+			position: relative;
+
+			.time {
+				flex: 1;
+			}
+
+			.state {
+				color: $base-color;
+			}
+
+			.del-btn {
+				padding: 10upx 0 10upx 36upx;
+				font-size: $font-lg;
+				color: $font-color-light;
+				position: relative;
+
+				&:after {
+					content: '';
+					width: 0;
+					height: 30upx;
+					border-left: 1px solid $border-color-dark;
+					position: absolute;
+					left: 20upx;
+					top: 50%;
+					transform: translateY(-50%);
+				}
+			}
+		}
+
+		/* 多条商品 */
+		.goods-box {
+			height: 160upx;
+			padding: 20upx 0;
+			white-space: nowrap;
+
+			.goods-item {
+				width: 120upx;
+				height: 120upx;
+				display: inline-block;
+				margin-right: 24upx;
+			}
+
+			.goods-img {
+				display: block;
+				width: 100%;
+				height: 100%;
+			}
+		}
+
+		/* 单条商品 */
+		.goods-box-single {
+			display: flex;
+			padding: 20upx 0;
+
+			.goods-img {
+				display: block;
+				width: 120upx;
+				height: 120upx;
+			}
+
+			.right {
+				flex: 1;
+				display: flex;
+				flex-direction: column;
+				padding: 0 30upx 0 24upx;
+				overflow: hidden;
+				position: relative;
+				.title {
+					font-size: $font-base + 2upx;
+					color: $font-color-dark;
+					line-height: 1;
+				}
+
+				.attr-box {
+					font-size: $font-sm + 2upx;
+					color: $font-color-light;
+					padding: 10upx 12upx;
+				}
+
+				.price {
+					font-size: $font-base + 2upx;
+					color: $font-color-dark;
+
+					&:before {
+						content: '¥';
+						font-size: $font-sm;
+						margin: 0 2upx 0 8upx;
+					}
+				}
+				.action-btn{
+					width: 160rpx;
+					height: 60rpx;
+					padding: 0;
+					text-align: center;
+					line-height: 60rpx;
+					font-size: 26rpx;
+					color: #303133;
+					background: #fff;
+					border-radius: 100px;
+					float: right;
+					position: absolute;
+					right: 30rpx;
+					bottom: 0;
+				}
+				.refund{
+					position: absolute;
+					right: 30rpx;
+					font-size: 28rpx;
+					color: #fa436a;
+					top: 35rpx;
+				}
+			}
+		}
+
+		.price-box {
+			display: flex;
+			justify-content: flex-end;
+			align-items: baseline;
+			padding: 20upx 30upx;
+			font-size: $font-sm + 2upx;
+			color: $font-color-light;
+
+			.num {
+				margin: 0 8upx;
+				color: $font-color-dark;
+			}
+
+			.price {
+				font-size: $font-lg;
+				color: $font-color-dark;
+
+				&:before {
+					content: '¥';
+					font-size: $font-sm;
+					margin: 0 2upx 0 8upx;
+				}
+			}
+		}
+
+		.action-box {
+			display: flex;
+			justify-content: flex-end;
+			align-items: center;
+			height: 100upx;
+			position: relative;
+			padding-right: 30upx;
+		}
+
+		.action-btn {
+			width: 160upx;
+			height: 60upx;
+			margin: 0;
+			margin-left: 24upx;
+			padding: 0;
+			text-align: center;
+			line-height: 60upx;
+			font-size: $font-sm + 2upx;
+			color: $font-color-dark;
+			background: #fff;
+			border-radius: 100px;
+
+			&:after {
+				border-radius: 100px;
+			}
+
+			&.recom {
+				background: #fff9f9;
+				color: $base-color;
+
+				&:after {
+					border-color: #f7bcc8;
+				}
+			}
+		}
+	}
+
+
+	/* load-more */
+	.uni-load-more {
+		display: flex;
+		flex-direction: row;
+		height: 80upx;
+		align-items: center;
+		justify-content: center
+	}
+
+	.uni-load-more__text {
+		font-size: 28upx;
+		color: #999
+	}
+
+	.uni-load-more__img {
+		height: 24px;
+		width: 24px;
+		margin-right: 10px
+	}
+
+	.uni-load-more__img>view {
+		position: absolute
+	}
+
+	.uni-load-more__img>view view {
+		width: 6px;
+		height: 2px;
+		border-top-left-radius: 1px;
+		border-bottom-left-radius: 1px;
+		background: #999;
+		position: absolute;
+		opacity: .2;
+		transform-origin: 50%;
+		animation: load 1.56s ease infinite
+	}
+
+	.uni-load-more__img>view view:nth-child(1) {
+		transform: rotate(90deg);
+		top: 2px;
+		left: 9px
+	}
+
+	.uni-load-more__img>view view:nth-child(2) {
+		transform: rotate(180deg);
+		top: 11px;
+		right: 0
+	}
+
+	.uni-load-more__img>view view:nth-child(3) {
+		transform: rotate(270deg);
+		bottom: 2px;
+		left: 9px
+	}
+
+	.uni-load-more__img>view view:nth-child(4) {
+		top: 11px;
+		left: 0
+	}
+
+	.load1,
+	.load2,
+	.load3 {
+		height: 24px;
+		width: 24px
+	}
+
+	.load2 {
+		transform: rotate(30deg)
+	}
+
+	.load3 {
+		transform: rotate(60deg)
+	}
+
+	.load1 view:nth-child(1) {
+		animation-delay: 0s
+	}
+
+	.load2 view:nth-child(1) {
+		animation-delay: .13s
+	}
+
+	.load3 view:nth-child(1) {
+		animation-delay: .26s
+	}
+
+	.load1 view:nth-child(2) {
+		animation-delay: .39s
+	}
+
+	.load2 view:nth-child(2) {
+		animation-delay: .52s
+	}
+
+	.load3 view:nth-child(2) {
+		animation-delay: .65s
+	}
+
+	.load1 view:nth-child(3) {
+		animation-delay: .78s
+	}
+
+	.load2 view:nth-child(3) {
+		animation-delay: .91s
+	}
+
+	.load3 view:nth-child(3) {
+		animation-delay: 1.04s
+	}
+
+	.load1 view:nth-child(4) {
+		animation-delay: 1.17s
+	}
+
+	.load2 view:nth-child(4) {
+		animation-delay: 1.3s
+	}
+
+	.load3 view:nth-child(4) {
+		animation-delay: 1.43s
+	}
+
+	@-webkit-keyframes load {
+		0% {
+			opacity: 1
+		}
+
+		100% {
+			opacity: .2
+		}
+	}
+</style>

+ 391 - 0
pages/order/orderDetail.vue

@@ -0,0 +1,391 @@
+<template>
+	<view>
+		<view class="header">
+			<view class="left">{{status + (order.refund_status_text ? ':'+order.refund_status_text : '')}}</view>
+
+		</view>
+		<view class="delivery" v-if="order.have_delivered">
+			<view class="icon">
+				<text class="cell-icon yticon icon-huoche" style="color: #ffffff"></text>
+			</view>
+			<view class="info">
+				<view v-if="order.express_number">快递编号:{{order.express_number}}</view>
+				<view>发货时候:{{order.deliveredtime}}</view>
+				<!-- <text class="yticon right icon-you"></text> -->
+			</view>
+
+		</view>
+		<view class="address" v-if="order.delivery">
+			<view class="icon">
+				<text class="cell-icon yticon icon-dizhi" style="color: #ffffff"></text>
+			</view>
+			<view class="info">
+				<view class="name">{{order.delivery.username}}<text class="mobile">{{order.delivery.mobile}}</text></view>
+				<view class="address">{{order.delivery.address}}</view>
+			</view>
+		</view>
+		<view class="product" v-if="order.products">
+			<view class="goods-box-single" v-for="(goodsItem, goodsIndex) in order.products" :key="goodsIndex" @click="navTo('/pages/product/product?id='+goodsItem.id+'&flash=0')">
+				<image class="goods-img" :src="goodsItem.image" mode="aspectFill"></image>
+				<view class="right">
+					<text class="title clamp">{{goodsItem.title}}</text>
+					<text class="attr-box">{{goodsItem.spec}} x {{goodsItem.number}}</text>
+					<text class="price">{{goodsItem.price}}</text>
+					<button class="action-btn" v-if="order.have_received != 0 && goodsItem.evaluate == false" @click.stop="button('evaluate', goodsItem)">评价</button>
+				</view>
+			</view>
+
+			<view class="price">
+				<view>商品总价 <text>¥ {{order.order_price}}</text></view>
+				<view>运费 <text>¥ {{order.delivery_price}}</text></view>
+				<view>优惠 <text>¥ {{order.discount_price}}</text></view>
+				<view class="total">实付款(含运费) <text>¥ {{order.total_price}}</text></view>
+			</view>
+		</view>
+		<view class="order" v-if="order.createtime">
+			<view class="title">订单信息</view>
+			<view>订单编号:{{order.out_trade_no}}</view>
+			<view>创建时间:{{order.createtime}}</view>
+			<view>付款时间:{{order.have_paid ? order.paidtime : '未付款'}}</view>
+			<view>发货时间:{{order.have_delivered ? order.deliveredtime : '未发货'}}</view>
+			<view v-if="order.have_received">成交时间:{{order.receivedtime}}</view>
+			<view v-if="order.have_refunded">退货时间:{{order.refundedtime}}</view>
+		</view>
+		<view style="height: 10rpx;">
+			<!--兼容苹果系统下margin-bottom不生效-->
+		</view>
+		<view class="bottom" v-if="order.state != 9">
+			<button class="action-btn" v-if="order.state == 1" @click.stop="button('cancel')">取消订单</button>
+			<button class="action-btn recom" v-if="order.have_paid == 0" @click.stop="button('pay')">立即支付</button>
+			<button class="action-btn" v-if="order.have_paid != 0" @click.stop="button('delivery')">查看物流</button>
+			<button class="action-btn" v-if="order.have_paid != 0 && order.have_received == 0" @click.stop="button('recerved')">确认收货</button>
+			<button class="action-btn" v-if="order.have_paid != 0" @click.stop="button('refund')">申请售后</button>
+		</view>
+		<view class="bottom" v-if="order.state == 9 && order.status == -1">
+			<button class="action-btn" v-if="order.have_paid != 0" @click.stop="button('refund')">查看售后</button>
+		</view>
+	</view>
+</template>
+
+<script>
+	
+	export default {
+		data() {
+			return {
+				order_id: 0,
+				status: '订单状态',
+				order: {}
+			}
+		},
+		onLoad(options) {
+			this.order_id = options.order_id;
+			this.detail(options.order_id);
+		},
+		methods: {
+			async detail(order_id) {
+				let data = await this.$api.request('/order/detail?order_id=' + order_id);
+				uni.stopPullDownRefresh();
+				if (data) {
+					this.status = this.orderStateExp(data.state);
+					this.order = data;
+				}
+			},
+			// 订单状态文字和颜色
+			orderStateExp(state) {
+				let stateTip = '',
+					stateTipColor = '#fa436a';
+				switch (+state) {
+					case 0:
+						stateTip = '交易成功';
+						break;
+					case 1:
+						stateTip = '待付款';
+						break;
+					case 2:
+						stateTip = '待发货';
+						break;
+					case 3:
+						stateTip = '待收货';
+						break;
+					case 4:
+						stateTip = '待评价';
+						break;
+					case 5:
+						stateTip = '售后';
+						break;
+					case 6:
+						stateTip = '拒绝退款';
+						break;
+					case 9:
+						stateTip = '订单已关闭';
+						break;
+
+						//更多自定义
+				}
+				return stateTip;
+			},
+			navTo(url) {
+				this.$api.navTo(url);
+			},
+			// 按钮动作
+			async button(action, item = {}) {
+				let detail = false;
+				switch (action) {
+					case 'cancel':
+						detail = await this.$api.prePage().cancelOrder({
+							order_id: this.order_id
+						});
+						break;
+					case 'pay':
+						this.navTo('/pages/money/pay?order_id=' + this.order_id + '&total=' + this.order.total_price);
+						break;
+					case 'delivery':
+						this.navTo('/pages/public/webview?type=kd&number=' + this.order.express_number);
+						break;
+					case 'recerved':
+						detail = await this.$api.prePage().receivedOrder({
+							order_id: this.order_id
+						});
+						break;
+					case 'evaluate':
+						this.$api.navTo('/pages/order/evaluate?product_id=' + item.id + '&order_id=' + this.order_id + '&image=' + item.image +
+							'&title=' + item.title + '&spec=' + item.spec);
+						break;
+					case 'refund':
+						this.$api.navTo('/pages/order/refund?order_id=' + this.order_id);
+						break;
+				}
+				if (detail) {
+					this.detail(this.order_id);
+				}
+			},
+			pullDownRefresh() {
+				this.detail(this.order_id);
+			}
+		},
+		onPullDownRefresh() {
+			this.pullDownRefresh();
+		}
+	}
+</script>
+
+<style lang="scss">
+	page {
+		background: #f5f5f5;
+	}
+
+	.header {
+		height: 200rpx;
+		background: linear-gradient(to right, #ffac30, #fa436a, #F56C6C);
+
+		.left {
+			color: #ffffff;
+			line-height: 200rpx;
+			padding-left: 100rpx;
+		}
+	}
+
+	.delivery {
+		color: #545454;
+		background: #ffffff;
+		border-bottom: 1px solid #e9e9e9;
+
+		.icon {
+			background: #00a7c8;
+			height: 60rpx;
+			width: 60rpx;
+			display: inline-block;
+			border-radius: 100rpx;
+			margin: 50rpx 30rpx;
+			text-align: center;
+			line-height: 60rpx;
+		}
+
+		.info {
+			display: inline-flex;
+			flex-direction: column;
+			justify-content: center;
+			width: 600rpx;
+			vertical-align: middle;
+			margin: 30rpx 0;
+			position: relative;
+
+			.right {
+				position: absolute;
+				right: 0;
+			}
+		}
+
+	}
+
+	.address {
+		color: #545454;
+		background: #ffffff;
+
+		.icon {
+			background: linear-gradient(to right, #ffac30, #fa436a, #F56C6C);
+			height: 60rpx;
+			width: 60rpx;
+			display: inline-block;
+			border-radius: 100rpx;
+			margin: 50rpx 30rpx;
+			text-align: center;
+			line-height: 60rpx;
+		}
+
+		.info {
+			display: inline-flex;
+			flex-direction: column;
+			justify-content: center;
+			width: 600rpx;
+			vertical-align: middle;
+			margin: 30rpx 0;
+
+			.mobile {
+				color: #9b9b9b;
+				margin-left: 30rpx;
+			}
+		}
+	}
+
+	.product {
+		background: #ffffff;
+		margin-top: 20rpx;
+		padding: 30rpx;
+
+		/* 单条商品 */
+		.goods-box-single {
+			display: flex;
+			padding: 20upx 0;
+			border-bottom: 1rpx solid #eaeaea;
+
+			.goods-img {
+				display: block;
+				width: 120upx;
+				height: 120upx;
+			}
+
+			.right {
+				flex: 1;
+				display: flex;
+				flex-direction: column;
+				padding: 0 30upx 0 24upx;
+				overflow: hidden;
+				position: relative;
+
+				.title {
+					font-size: $font-base + 2upx;
+					color: $font-color-dark;
+					line-height: 1;
+				}
+
+				.attr-box {
+					font-size: $font-sm + 2upx;
+					color: $font-color-light;
+					padding: 10upx 12upx;
+				}
+
+				.price {
+					font-size: $font-base + 2upx;
+					color: $font-color-dark;
+					margin-top: 0;
+
+					&:before {
+						content: '¥';
+						font-size: $font-sm;
+						margin: 0 2upx 0 8upx;
+					}
+				}
+
+				.action-btn {
+					width: 160rpx;
+					height: 60rpx;
+					padding: 0;
+					text-align: center;
+					line-height: 60rpx;
+					font-size: 26rpx;
+					color: #303133;
+					background: #fff;
+					border-radius: 100px;
+					float: right;
+					position: absolute;
+					right: 0;
+					bottom: 0;
+				}
+			}
+		}
+
+		.price {
+			color: #9b9b9b;
+			font-size: 26rpx;
+			margin-top: 30rpx;
+
+			text {
+				float: right;
+			}
+
+			.total {
+				font-size: 35rpx;
+
+				text {
+					color: #F56C6C;
+				}
+			}
+		}
+	}
+
+	.order {
+		background: #ffffff;
+		margin-top: 20rpx;
+		padding: 30rpx;
+
+		.title {
+			border-left: 4rpx solid #F56C6C;
+			padding-left: 17rpx;
+			color: #898989;
+			font-size: 35rpx;
+			margin-bottom: 20rpx;
+		}
+
+		font-size: 26rpx;
+		color: #9b9b9b;
+		line-height: 45rpx;
+		margin-bottom: 120rpx;
+	}
+
+	.bottom {
+		position: fixed;
+		width: 100%;
+		height: 100rpx;
+		background: #ffffff;
+		bottom: 0;
+		border-top: 1px solid #e9e9e9;
+
+		.action-btn {
+			width: 160rpx;
+			height: 60rpx;
+			margin: 20rpx;
+			padding: 0;
+			text-align: center;
+			line-height: 60rpx;
+			font-size: 26rpx;
+			color: #303133;
+			background: #fff;
+			border-radius: 100px;
+			float: right;
+
+			&:after {
+				border-radius: 100px;
+			}
+
+			&.recom {
+				background: #fff9f9;
+				color: $base-color;
+
+				&:after {
+					border-color: #f7bcc8;
+				}
+			}
+		}
+	}
+</style>

+ 406 - 0
pages/order/refund.vue

@@ -0,0 +1,406 @@
+<template>
+	<view>
+		<view class="header" v-if="order.refund_status != ''">
+			<view class="left">{{order.refund_status_text}}</view>
+		</view>
+		<view class="product">
+			<view class="goods-for" v-for="(goodsItem, goodsIndex) in order.products" :key="goodsIndex" @click="check(goodsIndex)">
+				<view class="goods-box-single">
+					<image class="goods-img" :src="goodsItem.image" mode="aspectFill"></image>
+					<view class="right">
+						<text class="title clamp">{{goodsItem.title}}</text>
+						<text class="attr-box">{{goodsItem.spec}} x {{goodsItem.number}}</text>
+						<text class="price">{{goodsItem.price}}</text>
+					</view>
+					<view class="yticon icon-xuanzhong checkbox" :class="{checked: goodsItem.choose}"></view>
+				</view>
+			</view>
+		</view>
+		
+		<view class="yt-list-cell" v-if=" (order.refund_status && order.refund_status == 2) || (order.refund && order.refund.express_number)">
+			<view class="cell-tit clamp">快递编号
+				<input style="max-width: 350rpx;" class="input" type="text" v-model="expressNumber" placeholder="请填写快递编号" placeholder-class="placeholder" />
+			</view>
+			<button type="warn" @click="confirmDelivery" v-if="order.refund_status == 2">确认发货</button>
+		</view>
+		
+		<view class="yt-list-cell">
+			<view class="cell-tit clamp">货物状态</view>
+			<view class="cell-tip">
+				<picker :disabled="order.status == 1 ? false : true" @change="receivingStatusChange" range-key="name" :value="receivingStatusIndex" :range="receivingStatus">
+					<view class="uni-input">{{receivingStatus[receivingStatusIndex].name}}</view>
+				</picker>
+				<text class="yticon icon-you"></text>
+			</view>
+		</view>
+
+		<view class="yt-list-cell">
+			<view class="cell-tit clamp">服务类型</view>
+			<view class="cell-tip">
+				<picker :disabled="order.status == 1 ? false : true" @change="typeChange" range-key="name" :value="serviceTypeIndex" :range="serviceType">
+					<view class="uni-input">{{serviceType[serviceTypeIndex].name}}</view>
+				</picker>
+				<text class="yticon icon-you"></text>
+			</view>
+		</view>
+		
+		<view class="yt-list-cell" v-if="order.total_price > 0 && (serviceTypeIndex == 0 || serviceTypeIndex == 1)">
+			<view class="cell-tit clamp">退款金额: <text style="color: #ed6b00;">¥ {{order.total_price}}</text>
+				<input :disabled="order.status == 1 ? false : true" type="digit" v-model="amount" :placeholder="'最多 ¥'+order.total_price + ', 含发货邮费 ¥'+ order.delivery_price" placeholder-class="placeholder" />
+			</view>
+		</view>
+		
+		<view class="yt-list-cell">
+			<view class="cell-tit clamp">换货原因</view>
+			<view class="cell-tip">
+				<picker :disabled="order.status == 1 ? false : true" @change="reasonTypeChange" range-key="name" :value="reasonTypeIndex" :range="reasonType">
+					<view class="uni-input">{{reasonType[reasonTypeIndex].name}}</view>
+				</picker>
+				<text class="yticon icon-you"></text>
+			</view>
+		</view>
+
+		<view class="yt-list-cell">
+			<view class="cell-tit clamp">换货说明
+				<input :disabled="order.status == 1 ? false : true" class="input" type="text" v-model="refundExplain" placeholder="选填" placeholder-class="placeholder" />
+			</view>
+		</view>
+		
+		<button v-if="order.status == 1" class="button" @click="submit" type="warn">提交</button>
+	</view>
+</template>
+
+<script>
+	import uniRate from '@/components/uni-rate/uni-rate.vue'
+	export default {
+		computed: {
+			orderProductIds(){
+				var order_product_id = [];
+				this.order.products.forEach(item=>{
+					if (item.choose == 1) {
+						order_product_id.push(item.order_product_id);
+					}
+				});
+				return order_product_id.join(',');
+			},
+			status() {
+				if (this.order.have_refunded == 0) {
+					return '申请中';
+				} else {
+					return '已退货'
+				}
+			}
+		},
+		components: {
+			uniRate
+		},
+		data() {
+			return {
+				order_id: 0,
+				order: {},
+				amount: '',
+				serviceType:[{
+						'value': 0,
+						'name': '我要退款(无需退货)'
+					},
+					{
+						'value': 1,
+						'name': '我要退货退款'
+					},
+					{
+						'value': 2,
+						'name': '换货'
+					},
+					{
+						'value': 3,
+						'name':'请选择'
+					}
+				],
+				serviceTypeIndex:3,
+				receivingStatus: [{
+						'value': 0,
+						'name': '未收到'
+					},
+					{
+						'value': 1,
+						'name': '已收到'
+					},
+					{
+						'value': 2,
+						'name': '请选择'
+					}
+				],
+				receivingStatusIndex: 2,
+				reasonType: [{
+						'name': '其他'
+					},
+					{
+						'name': '拍错/不喜欢/效果不好'
+					},
+					{
+						'name': '质量与商品描述不符'
+					},
+					{
+						'name': '版本/批次/颜色/容量等与商品描述不符'
+					},
+					{
+						'name': '发错货'
+					},
+					{
+						'name': '假冒品牌'
+					}
+				],
+				reasonTypeIndex: 0,
+				refundExplain: '',
+				expressNumber: ''
+			}
+		},
+		onPullDownRefresh() {
+			this.refundInfo();
+		},
+		onLoad(options) {
+			this.order_id = options.order_id;
+			this.refundInfo();
+		},
+		methods: {
+			async refundInfo() {
+				let data = await this.$api.request('/order/refundInfo', 'POST', {
+					order_id: this.order_id
+				});
+				uni.stopPullDownRefresh();
+				if (data) {
+					this.order = data;
+					
+					if (data.status == -1) { // 状态为退款
+						let refund = data.refund;
+						this.amount = refund.amount;
+						let that = this;
+						this.reasonType.filter(function(item, index){
+							if (item.name == refund.reason_type) {
+								that.reasonTypeIndex = index;
+							}
+						})
+						this.receivingStatusIndex = refund.receiving_status;
+						this.refundExplain = refund.refund_explain;
+						this.serviceTypeIndex = refund.service_type;
+						this.expressNumber = refund.express_number;
+					}
+				}
+			},
+			async submit(){
+				if (this.receivingStatusIndex == 2) {
+					this.$api.msg('请选择货物状态');
+					return;
+				}
+				
+				if ((this.serviceTypeIndex == 0 || this.serviceTypeIndex == 1) && parseFloat(this.order.total_price) > 0) {
+					if (!this.amount) {
+						this.$api.msg('请填写退货金额');
+						return;
+					}
+
+					if (parseFloat(this.amount) > parseFloat(this.order.total_price)) {
+						this.$api.msg('退款金额不得大于订单金额');
+						return;
+					}
+				}
+				
+				if (this.serviceTypeIndex == 3) {
+					this.$api.msg('请选择服务类型')
+					return;
+				}
+				
+				let data = await this.$api.request('/order/refund', 'POST', {
+					order_id: this.order_id,
+					amount: this.amount ? parseFloat(this.amount) : 0,
+					service_type: this.serviceType[this.serviceTypeIndex].value,
+					receiving_status: this.receivingStatus[this.receivingStatusIndex].value,
+					reason_type: this.reasonType[this.reasonTypeIndex].name,
+					refund_explain: this.refundExplain,
+					order_product_id: this.orderProductIds
+				});
+				if (data) {
+					this.refundInfo();
+				}
+			},
+			// 更改商品状态
+			receivingStatusChange(e) {
+				this.receivingStatusIndex = e.detail.value;
+			},
+			// 更改换货原因
+			reasonTypeChange(e) {
+				this.reasonTypeIndex = e.detail.value;
+			},
+			// 选择服务类型
+			typeChange(e) {
+				this.serviceTypeIndex = e.detail.value;
+			},
+			// 选商品
+			check(index) {
+				this.order.products[index].choose = this.order.products[index].choose ? 0 : 1;
+			},
+			// 确认发货
+			async confirmDelivery(){
+				if (this.expressNumber == '') {
+					this.$api.msg('请添加物流单号');
+					return;
+				}
+				let data = await this.$api.request('/order/refundDelivery', 'POST', {order_id: this.order_id, express_number:this.expressNumber});
+				if (data) {
+					this.refundInfo();
+				}
+			}
+		}
+	}
+</script>
+
+<style lang="scss">
+	page {
+		background: #f5f5f5;
+	}
+	
+	.header {
+		height: 200rpx;
+		background: linear-gradient(to right, #ffac30, #fa436a, #F56C6C);
+
+		.left {
+			color: #ffffff;
+			line-height: 200rpx;
+			padding-left: 100rpx;
+		}
+	}
+
+	.product {
+		background: #ffffff;
+		margin-top: 20rpx;
+		padding: 30rpx;
+		.goods-for{
+			border-bottom: 1rpx solid #eaeaea;
+		}
+
+		/* 单条商品 */
+		.goods-box-single {
+			display: flex;
+			padding: 20upx 0;
+			
+			position: relative;
+			
+			.goods-img {
+				display: block;
+				width: 120upx;
+				height: 120upx;
+			}
+
+			.right {
+				flex: 1;
+				display: flex;
+				flex-direction: column;
+				padding: 0 30upx 0 24upx;
+				overflow: hidden;
+				position: relative;
+
+				.title {
+					font-size: $font-base + 2upx;
+					color: $font-color-dark;
+					line-height: 1;
+				}
+
+				.attr-box {
+					font-size: $font-sm + 2upx;
+					color: $font-color-light;
+					padding: 10upx 12upx;
+				}
+
+				.price {
+					font-size: $font-base + 2upx;
+					color: $font-color-dark;
+					margin-top: 0;
+
+					&:before {
+						content: '¥';
+						font-size: $font-sm;
+						margin: 0 2upx 0 8upx;
+					}
+				}
+
+				.action-btn {
+					width: 160rpx;
+					height: 60rpx;
+					padding: 0;
+					text-align: center;
+					line-height: 60rpx;
+					font-size: 26rpx;
+					color: #303133;
+					background: #fff;
+					border-radius: 100px;
+					float: right;
+					position: absolute;
+					right: 0;
+					bottom: 0;
+				}
+			}
+			.checkbox {
+				position: absolute;
+				left: -16upx;
+				top: -16upx;
+				z-index: 8;
+				font-size: 44upx;
+				line-height: 1;
+				padding: 4upx;
+				color: $font-color-disabled;
+				background: #fff;
+				border-radius: 50px;
+			}
+			.checkbox.checked {
+				color: $uni-color-primary;
+			}
+		}
+	}
+
+	.yt-list-cell {
+		margin-top: 16upx;
+		background: #fff;
+		display: flex;
+		align-items: center;
+		padding: 10upx 30upx 10upx;
+		line-height: 70upx;
+		position: relative;
+
+		.cell-tit {
+			flex: 1;
+			font-size: 26rpx;
+			color: #000000;
+			margin-right: 10rpx;
+
+			.input {
+				display: inline-block;
+				vertical-align: middle;
+				line-height: 26rpx;
+				margin-left: 20rpx;
+				width: 550rpx;
+			}
+		}
+
+		.cell-tip {
+			font-size: 26rpx;
+			color: $font-color-dark;
+
+			picker {
+				display: inline-block;
+				width: 500rpx;
+				text-align: right;
+				padding-right: 20rpx;
+				color: $font-color-light;
+			}
+
+			.yticon {
+				font-size: 26rpx;
+			}
+		}
+
+	}
+	
+	.button{
+		margin: 20rpx;
+	}
+</style>

+ 101 - 0
pages/product/evaluate.vue

@@ -0,0 +1,101 @@
+<template>
+	<view>
+		<!-- 评价 -->
+		<view class="eva-section">
+			<view class="eva-box" v-for="(item, index) in list" :key="index">
+				<image class="portrait" :src="item.avatar" mode="aspectFill"></image>
+				<view class="right">
+					<text class="name">{{item.username || '游客'}}</text>
+					<text class="con">{{item.comment}}</text>
+					<view class="bot">
+						<text class="attr" v-if="item.spec != ''">购买类型:{{item.spec}}</text>
+						<text class="attr" v-else></text>
+						<text class="time">{{item.createtime_text}}</text>
+					</view>
+				</view>
+			</view>
+		</view>
+	</view>
+</template>
+
+<script>
+	export default{
+		data(){
+			return {
+				product_id: 0,
+				page:1,
+				pageSize:20,
+				list:[]
+			}
+		},
+		onLoad(options){
+			this.product_id = options.product_id;
+			this.getEvaluete();
+		},
+		onPullDownRefresh() {
+			this.page = 1;
+			this.list = [];
+			this.getEvaluete();
+		},
+		onReachBottom() {
+			this.getEvaluete();
+		},
+		methods:{
+			async getEvaluete(){
+				let comment = await this.$api.request('/product/evaluate', 'GET', {product_id:this.product_id,page:this.page,pagesize:this.pageSize});
+				uni.stopPullDownRefresh();
+				if (comment) {
+					this.page++;
+					comment.forEach(item=>{
+						this.list.push(item);
+					});
+				}
+			}
+		}
+	}
+</script>
+
+<style lang='scss'>
+	/* 评价 */
+	.eva-section {
+		display: flex;
+		flex-direction: column;
+		padding: 20upx 30upx;
+		background: #fff;
+		margin-top: 16upx;
+	}
+	
+	.eva-box {
+		display: flex;
+		padding: 20upx 0;
+	
+		.portrait {
+			flex-shrink: 0;
+			width: 80upx;
+			height: 80upx;
+			border-radius: 100px;
+		}
+	
+		.right {
+			flex: 1;
+			display: flex;
+			flex-direction: column;
+			font-size: $font-base;
+			color: $font-color-base;
+			padding-left: 26upx;
+				
+			.con {
+				font-size: $font-base;
+				color: $font-color-dark;
+				padding: 20upx 0;
+			}
+	
+			.bot {
+				display: flex;
+				justify-content: space-between;
+				font-size: $font-sm;
+				color: $font-color-light;
+			}
+		}
+	}
+</style>

+ 432 - 0
pages/product/list.vue

@@ -0,0 +1,432 @@
+<template>
+	<view class="content">
+		<view class="navbar" :style="{position:headerPosition,top:headerTop}">
+			<view class="nav-item" :class="{current: filterIndex === 0}" @click="tabClick(0)">
+				综合排序
+			</view>
+			<view class="nav-item" :class="{current: filterIndex === 1}" @click="tabClick(1)">
+				销量优先
+			</view>
+			<view class="nav-item" :class="{current: filterIndex === 2}" @click="tabClick(2)">
+				<text>价格</text>
+				<view class="p-box">
+					<text :class="{active: priceOrder === 1 && filterIndex === 2}" class="yticon icon-shang"></text>
+					<text :class="{active: priceOrder === 2 && filterIndex === 2}" class="yticon icon-shang xia"></text>
+				</view>
+			</view>
+			<text class="cate-item yticon icon-fenlei" @click="toggleCateMask('show')"></text>
+		</view>
+		<view class="goods-list">
+			<view 
+				v-for="(item, index) in goodsList" :key="index"
+				class="goods-item"
+				@click="navToDetailPage(item)"
+			>
+				<view class="image-wrapper">
+					<image :src="item.image" mode="aspectFill"></image>
+				</view>
+				<text class="title clamp">{{item.title}}</text>
+				<view class="price-box">
+					<text class="price">{{item.sales_price}}</text>
+					<text> {{item.sales}} 人已购</text>
+				</view>
+			</view>
+		</view>
+		<uni-load-more :status="loadingType"></uni-load-more>
+		
+		<view class="cate-mask" :class="cateMaskState===0 ? 'none' : cateMaskState===1 ? 'show' : ''" @click="toggleCateMask">
+			<view class="cate-content" @click.stop.prevent="stopPrevent" @touchmove.stop.prevent="stopPrevent">
+				<scroll-view scroll-y class="cate-list">
+					<view v-for="item in cateList" :key="item.id">
+						<view class="cate-item b-b two" :class="{active: item.id==fId}" @click="changeFirst(item.id)">{{item.name}}</view>
+						<view
+							v-for="tItem in item.child" :key="tItem.id" 
+							class="cate-item b-b"
+							:class="{active: tItem.id==sId}"
+							@click="changeSecond(tItem)">
+							{{tItem.name}}
+						</view>
+					</view>
+				</scroll-view>
+			</view>
+		</view>
+		
+	</view>
+</template>
+
+<script>
+	import uniLoadMore from '@/components/uni-load-more/uni-load-more.vue';
+	
+	export default {
+		components: {
+			uniLoadMore	
+		},
+		data() {
+			return {
+				cateMaskState: 0, //分类面板展开状态
+				headerPosition:"fixed",
+				headerTop:"0px",
+				loadingType: 'more', //加载更多状态
+				filterIndex: 0, 
+				fId: 0, // 已选一级分类
+				sId: 0, // 已选二级分类id
+				priceOrder: 0, //1 价格从低到高 2价格从高到低
+				cateList: [],
+				goodsList: [],
+				page:1
+			};
+		},
+		computed:{
+			
+		},
+		onShareAppMessage(e){
+			return{
+				path: '/pages/product/list?fid='+this.fId+'&sid='+this.sId
+			};
+		},
+		onLoad(options){
+			// #ifdef H5
+			this.headerTop = document.getElementsByTagName('uni-page-head')[0].offsetHeight+'px';
+			// #endif
+			this.fId = options.fid;
+			this.sId = options.sid ? options.sid : 0;
+			this.loadCateList(options.fid,options.sid);
+			this.loadData();
+		},
+		onPageScroll(e){
+			//兼容iOS端下拉时顶部漂移
+			if(e.scrollTop>=0){
+				this.headerPosition = "fixed";
+			}else{
+				this.headerPosition = "absolute";
+			}
+		},
+		//下拉刷新
+		onPullDownRefresh(){
+			this.loadData('refresh');
+		},
+		//加载更多
+		onReachBottom(){
+			this.loadData();
+		},
+		methods: {
+			//加载分类
+			async loadCateList(fid, sid){
+				const that = this;
+				//let list = await this.$api.json('cateList');
+				let list = await this.$api.request('/category/all');
+				let cateList = list.filter(item=>item.pid == 0);	
+				cateList.forEach(item=>{
+					let tempList = list.filter(val=>val.pid == item.id);
+					item.child = tempList;
+				});
+				this.cateList = cateList;
+			},
+			//加载商品 ,带下拉刷新和上滑加载
+			async loadData(type='add', loading){
+				//没有更多直接返回
+				if(type === 'add'){
+					if(this.loadingType === 'nomore'){
+						return;
+					}
+					this.loadingType = 'loading';
+				}else{
+					this.loadingType = 'more'
+				}
+				
+				let by = this.filterIndex == 0 ? 'weigh' : (this.filterIndex == 1 ? 'sales':'sales_price');
+				let desc = 'desc';
+				if (this.filterIndex == 2) {
+					desc = this.priceOrder === 1 ? 'desc': 'asc';
+				}
+				
+				if(type === 'refresh'){
+					this.goodsList = [];
+					this.page = 1;
+				}
+				
+				let goodsList = await this.$api.request('/product/lists', 'GET', {fid:this.fId,sid:this.sId,page:this.page,by:by,desc:desc});
+				uni.stopPullDownRefresh();
+				if (!goodsList) {
+					this.loadingType = 'nomore';
+					return;
+				}
+				
+				this.goodsList = this.goodsList.concat(goodsList);
+				
+				//判断是否还有下一页,有是more  没有是nomore
+				this.loadingType  = goodsList.length > 20 ? 'nomore' : 'more';
+				if(goodsList.length < 20){
+					this.loadingType = 'nomore';
+				}else{
+					this.loadingType = 'more';
+					this.page++;
+				}
+				
+				if(type === 'refresh'){
+					if(loading == 1){
+						uni.hideLoading()
+					}else{
+						uni.stopPullDownRefresh();
+					}
+				}
+			},
+			//筛选点击
+			tabClick(index){
+				if(this.filterIndex === index && index !== 2){
+					return;
+				}
+				this.filterIndex = index;
+				if(index === 2){
+					this.priceOrder = this.priceOrder === 1 ? 2: 1;
+				}else{
+					this.priceOrder = 0;
+				}
+				uni.pageScrollTo({
+					duration: 300,
+					scrollTop: 0
+				})
+				this.loadData('refresh', 1);
+				uni.showLoading({
+					title: '正在加载'
+				})
+			},
+			//显示分类面板
+			toggleCateMask(type){
+				let timer = type === 'show' ? 10 : 300;
+				let	state = type === 'show' ? 1 : 0;
+				this.cateMaskState = 2;
+				setTimeout(()=>{
+					this.cateMaskState = state;
+				}, timer)
+			},
+			// 点击一级分类
+			changeFirst(id){
+				this.page = 1;
+				this.fId = id;
+				this.sId = 0;
+				this.toggleCateMask();
+				uni.pageScrollTo({
+					duration: 300,
+					scrollTop: 0
+				});
+				this.loadData('refresh', 1);
+				uni.showLoading({
+					title: '正在加载'
+				});
+			},
+			// 点击二级分类
+			changeSecond(item){
+				this.page = 1;
+				this.fId = item.pid;
+				this.sId = item.id;
+				this.toggleCateMask();
+				uni.pageScrollTo({
+					duration: 300,
+					scrollTop: 0
+				});
+				this.loadData('refresh', 1);
+				uni.showLoading({
+					title: '正在加载'
+				});
+			},
+			//详情
+			navToDetailPage(item){
+				uni.navigateTo({
+					url: `/pages/product/product?id=${item.product_id}`
+				});
+			},
+			stopPrevent(){}
+		},
+	}
+</script>
+
+<style lang="scss">
+	page, .content{
+		background: $page-color-base;
+	}
+	.content{
+		padding-top: 96upx;
+	}
+
+	.navbar{
+		position: fixed;
+		left: 0;
+		top: var(--window-top);
+		display: flex;
+		width: 100%;
+		height: 80upx;
+		background: #fff;
+		box-shadow: 0 2upx 10upx rgba(0,0,0,.06);
+		z-index: 10;
+		.nav-item{
+			flex: 1;
+			display: flex;
+			justify-content: center;
+			align-items: center;
+			height: 100%;
+			font-size: 30upx;
+			color: $font-color-dark;
+			position: relative;
+			&.current{
+				color: $base-color;
+				&:after{
+					content: '';
+					position: absolute;
+					left: 50%;
+					bottom: 0;
+					transform: translateX(-50%);
+					width: 120upx;
+					height: 0;
+					border-bottom: 4upx solid $base-color;
+				}
+			}
+		}
+		.p-box{
+			display: flex;
+			flex-direction: column;
+			.yticon{
+				display: flex;
+				align-items: center;
+				justify-content: center;
+				width: 30upx;
+				height: 14upx;
+				line-height: 1;
+				margin-left: 4upx;
+				font-size: 26upx;
+				color: #888;
+				&.active{
+					color: $base-color;
+				}
+			}
+			.xia{
+				transform: scaleY(-1);
+			}
+		}
+		.cate-item{
+			display: flex;
+			justify-content: center;
+			align-items: center;
+			height: 100%;
+			width: 80upx;
+			position: relative;
+			font-size: 44upx;
+			&:after{
+				content: '';
+				position: absolute;
+				left: 0;
+				top: 50%;
+				transform: translateY(-50%);
+				border-left: 1px solid #ddd;
+				width: 0;
+				height: 36upx;
+			}
+		}
+	}
+
+	/* 分类 */
+	.cate-mask{
+		position: fixed;
+		left: 0;
+		top: var(--window-top);
+		bottom: 0;
+		width: 100%;
+		background: rgba(0,0,0,0);
+		z-index: 95;
+		transition: .3s;
+		
+		.cate-content{
+			width: 630upx;
+			height: 100%;
+			background: #fff;
+			float:right;
+			transform: translateX(100%);
+			transition: .3s;
+		}
+		&.none{
+			display: none;
+		}
+		&.show{
+			background: rgba(0,0,0,.4);
+			
+			.cate-content{
+				transform: translateX(0);
+			}
+		}
+	}
+	.cate-list{
+		display: flex;
+		flex-direction: column;
+		height: 100%;
+		.cate-item{
+			display: flex;
+			align-items: center;
+			height: 90upx;
+			padding-left: 30upx;
+ 			font-size: 28upx;
+			color: #555;
+			position: relative;
+		}
+		.two{
+			height: 64upx;
+			color: #303133;
+			font-size: 30upx;
+			background: #f8f8f8;
+		}
+		.active{
+			color: $base-color;
+		}
+	}
+
+	/* 商品列表 */
+	.goods-list{
+		display:flex;
+		flex-wrap:wrap;
+		padding: 0 30upx;
+		background: #fff;
+		.goods-item{
+			display:flex;
+			flex-direction: column;
+			width: 48%;
+			padding-bottom: 40upx;
+			&:nth-child(2n+1){
+				margin-right: 4%;
+			}
+		}
+		.image-wrapper{
+			width: 100%;
+			height: 330upx;
+			border-radius: 3px;
+			overflow: hidden;
+			image{
+				width: 100%;
+				height: 100%;
+				opacity: 1;
+			}
+		}
+		.title{
+			font-size: $font-lg;
+			color: $font-color-dark;
+			line-height: 80upx;
+		}
+		.price-box{
+			display: flex;
+			align-items: center;
+			justify-content: space-between;
+			padding-right: 10upx;
+			font-size: 24upx;
+			color: $font-color-light;
+		}
+		.price{
+			font-size: $font-lg;
+			color: $uni-color-primary;
+			line-height: 1;
+			&:before{
+				content: '¥';
+				font-size: 26upx;
+			}
+		}
+	}
+	
+
+</style>

+ 1224 - 0
pages/product/product.vue

@@ -0,0 +1,1224 @@
+<template>
+	<view class="container">
+		<view class="carousel">
+			<swiper indicator-dots circular=true duration="400">
+				<swiper-item class="swiper-item" v-for="(item,index) in product.images_text" :key="index">
+					<view class="image-wrapper">
+						<image :src="item" class="loaded" @click="previewImage(index)" mode="aspectFill"></image>
+					</view>
+				</swiper-item> 
+			</swiper>
+		</view>
+
+		<!-- 秒杀的话才显示 -->
+		<view class="flash" v-if="flash">
+			<view class="sales_price" v-if="product.market_price"><view class="symbol">¥</view>{{specProduct.sales_price}}</view>
+			<view class="left" v-if="product.market_price">
+				<view class="market_price">¥{{specProduct.market_price}}</view>
+				<view class="sold" v-if="progress.number">秒{{progress.number}}件</view>
+			</view>
+			<view class="right">
+				<view class="time" v-if="countdown && progress.number != progress.sold">
+					{{product.flash.text}}
+					<uni-countdown
+					ref="countd"
+					:showDay="countdown.day > 0 ? true : false" 
+					:day="countdown.day" 
+					:hour="countdown.hour" 
+					:minute="countdown.minute"
+					:second="countdown.second" 
+					@timeup="timeup"
+					color="#fffa30" 
+					borderWidth="22rpx" 
+					splitorColor="#fffa30"  
+					background-color="#282f2c00" 
+					border-color="#00B26A"></uni-countdown>
+				</view>
+				<view class="time" v-else :class="{'flashDone': progress.number == false}">抢购已结束</view>
+				<view class="progress" v-if="progress.number">
+					<ProgressBar 
+					class="ProgressBar" 
+					:Sold="progress.sold" 
+					:widthUpx="250"
+					:Width="percentage(progress.number, progress.sold)" 
+					Type="candy" 
+					:Vice="true"></ProgressBar>
+				</view>
+			</view>
+		</view>
+
+		<view class="introduce-section" v-if="product.product_id">
+			<text class="title">{{product.title}}</text>
+			<view class="price-box" v-if="flash == false">
+				<text class="price-tip">¥</text>
+				<text class="price">{{specProduct.sales_price}}</text>
+				<text class="m-price" v-if="specProduct.market_price">¥{{specProduct.market_price}}</text>
+				<text class="coupon-tip" v-if="specProduct.market_price > 0 && specProduct.market_price > specProduct.sales_price">{{(specProduct.sales_price/specProduct.market_price*10).toFixed(1)}}折</text>
+			</view>
+			<view class="bot-row">
+				<text>销量: {{product.sales}}</text>
+				<text>总库存: {{product.stock}}</text>
+				<text>浏览量: {{product.look}}</text>
+			</view>
+		</view>
+
+		<!--  分享 分销 第二版本做-->
+		<!-- <view class="share-section" @click="share">
+			<view class="share-icon">
+				<text class="yticon icon-xingxing"></text>
+				 返
+			</view>
+			<text class="tit">该商品分享可领49减10红包</text>
+			<text class="yticon icon-bangzhu1"></text>
+			<view class="share-btn">
+				立即分享
+				<text class="yticon icon-you"></text>
+			</view>
+		</view> -->
+
+		<view class="c-list">
+			<view class="c-row b-b" @click="toggleSpec" v-if="product.use_spec">
+				<text class="tit">购买类型</text>
+				<view class="con">
+					<text class="selected-text">
+						{{specSelectedName}}
+					</text>
+					<text style="margin-left: 50rpx;">库存:{{specProduct.stock}}</text>
+				</view>
+				<text class="yticon icon-you"></text>
+			</view>
+			<view class="c-row b-b" @click="toggleCoupon" v-if="product.coupon.length">
+				<text class="tit">优惠券</text>
+				<text class="con t-r red">查看可用优惠券</text>
+				<text class="yticon icon-you"></text>
+			</view>
+			<view class="c-row b-b" v-if="product.server">
+				<text class="tit">服务</text>
+				<view class="bz-list con">
+					{{product.server}}
+				</view>
+			</view>
+		</view>
+
+		<!-- 评价 -->
+		<view class="eva-section" v-if="product.evaluate_data && product.evaluate_data.count > 0">
+			<view class="e-header" @click="$api.navTo('/pages/product/evaluate?product_id='+product.product_id)">
+				<text class="tit">评价</text>
+				<text>({{product.evaluate_data.count}})</text>
+				<text class="tip">好评率 {{product.evaluate_data.avg}}%</text>
+				<text class="yticon icon-you"></text>
+			</view>
+			<view class="eva-box" v-for="(item, index) in product.evaluate_list" :key="index">
+				<image class="portrait" :src="item.avatar" mode="aspectFill"></image>
+				<view class="right">
+					<text class="name">{{item.username}}</text>
+					<text class="con">{{item.comment}}</text>
+					<view class="bot">
+						<text class="attr" v-if="item.spec">购买类型:{{item.spec}}</text>
+						<text class="attr" v-else></text>
+						<text class="time">{{item.createtime_text}}</text>
+					</view>
+				</view>
+			</view>
+		</view>
+
+		<view class="detail-desc" v-if="product.desc">
+			<view class="d-header">
+				<text>图文详情</text>
+			</view>
+			<rich-text :nodes="product.desc"></rich-text>
+		</view>
+
+		<!-- 底部操作菜单 -->
+		<view class="page-bottom">
+			<navigator url="/pages/index/index" open-type="switchTab" class="p-b-btn">
+				<text class="yticon icon-fangzi"></text>
+				<text>首页</text>
+			</navigator>
+			<navigator url="/pages/cart/cart" open-type="switchTab" class="p-b-btn">
+				<text class="yticon icon-gouwuche"></text>
+				<text>购物车</text>
+				<text class="cart-count" v-if="product.cart_num">{{product.cart_num}}</text>
+			</navigator>
+			<view class="p-b-btn" :class="{active: favorite}" @click="toFavorite" v-if="!flash">
+				<text class="yticon icon-shoucang"></text>
+				<text>收藏</text>
+			</view>
+			<view class="p-b-btn" v-else>
+				<!-- 站位 -->
+				<text></text>
+			</view>
+
+			<view class="action-btn-group">
+				<button :class="{'only': flash}" type="primary" class=" action-btn no-border buy-now-btn" @click="buy">立即购买</button>
+				<button v-if="!flash" type="primary" class=" action-btn no-border add-cart-btn" @click="addCart">加入购物车</button>
+			</view>
+		</view>
+
+		<!-- 领取优惠券-模态层弹窗 -->
+		<view class="popup spec" :class="couponClass" @touchmove.stop.prevent="stopPrevent" @click="toggleCoupon">
+			<!-- 遮罩层 -->
+			<view class="mask"></view>
+			<view class="layer attr-content content-coupon" @click.stop="stopPrevent">
+				
+				<!-- 优惠券页面,仿mt -->
+				<view class="coupon-item" v-for="(item, index) in product.coupon" :key="index">
+					<view class="con">
+						<view class="left">
+							<text class="title">{{item.title}}</text>
+							<text class="time">有效期至{{item.endtime_text}}</text>
+						</view>
+						<view class="right">
+							<text class="price">{{item.value}}</text>
+							<text>满{{item.least}}可用</text>
+						</view>
+						<view class="circle l"></view>
+						<view class="circle r"></view>
+					</view>
+					<text class="tips">限一张使用</text>
+				</view>
+			</view>
+			<button class="btn retract" @click="toggleCoupon">收起</button>
+		</view>
+
+		<!-- 规格-模态层弹窗 -->
+		<view class="popup spec" :class="specClass" @touchmove.stop.prevent="stopPrevent" @click="toggleSpec">
+			<!-- 遮罩层 -->
+			<view class="mask"></view>
+			<view class="layer attr-content" @click.stop="stopPrevent">
+				<view class="a-t">
+					<image v-if="specProduct.image" mode="aspectFill" :src="specProduct.image"></image>
+					<view class="right">
+						<text class="price">¥{{specProduct.sales_price}}</text>
+						<text class="stock">库存:{{specProduct.stock}}件</text>
+						<view class="selected">
+							已选:
+							<text class="selected-text">
+								{{specSelectedName}}
+							</text>
+						</view>
+					</view>
+				</view>
+				<view v-for="(item,index) in specList" :key="index" class="attr-list">
+					<text>{{item.name}}</text>
+					<view class="item-list">
+						<text v-for="(childItem, childIndex) in specChildList" v-if="childItem.pid === item.id" :key="childIndex" class="tit"
+						 :class="{selected: childItem.selected}" @click="selectSpec(childIndex, childItem.pid)">
+							{{childItem.name}}
+						</text>
+					</view>
+				</view>
+				<button class="btn" @click="toggleSpec">完成</button>
+			</view>
+		</view>
+
+		<!-- 分享 -->
+		<!-- <share ref="share" :contentHeight="580" :shareList="shareList"></share> -->
+	</view>
+</template>
+
+<script>
+	import {
+		mapGetters
+	} from 'vuex';
+	import share from '@/components/share';
+	import ProgressBar from '@/components/Progress-Bar/Progress-Bar';
+	import uniCountdown from '@/components/uni-countdown/uni-countdown.vue';
+
+	export default {
+		components: {
+			share,
+			ProgressBar,
+			uniCountdown
+		},
+		computed: {
+			...mapGetters(['userInfo', 'hasLogin']),
+			
+			specSelectedName() {
+				return this.specSelected.join(' ');
+			},
+			specProduct() {
+				if (this.product.use_spec == 1) {
+					let market_price = this.product.market_price;
+					let sales_price = this.product.sales_price;
+					let stock = this.product.stock;
+					let image = this.product.image;
+					let specSelectedName = this.specSelected.join(' ');
+					let specTableList = this.specTableList;
+					for (var item of this.specTableList) {
+						if (item.value.join(' ') == specSelectedName) {
+							market_price = item.market_price;
+							sales_price = item.sales_price;
+							stock = item.stock;
+							image = item.image;
+						}
+
+					}
+					return {
+						market_price,
+						sales_price,
+						stock,
+						image
+					};
+				} else {
+					return this.product;
+				}
+			}
+		},
+		data() {
+			return {
+				couponClass: 'none',
+				specClass: 'none',
+				specSelected: [],
+				favorite: false,
+				shareList: [],
+				specList: [],
+				specChildList: [],
+				specTableList: [],
+				product: {},
+				flash: false,
+				id: false,
+				countdown: {},
+				progress:{
+					sold:1,
+					number:1
+				}
+			};
+		},
+		onPullDownRefresh(){
+			if (this.$refs && this.$refs.countd) {
+				this.$refs.countd.syncFlag = false;
+			}
+			this.getDetail(this.id, this.flash?this.flash:0);
+		},
+		onShareAppMessage(e) {
+			return {
+				title: this.product.title,
+			};
+		},
+		onLoad(options) {
+			this.id = options.id;
+			let flash_id = options.flash ? options.flash : 0;
+			if (flash_id != 0) {
+				this.flash = flash_id;
+			}
+			this.getDetail(this.id, flash_id);
+		},
+		methods: {
+			// 为0时刷新页面
+			timeup(){
+			    this.getDetail(this.id, this.flash?this.flash:0);
+			},
+			// 获取商品详情
+			async getDetail(id, flash_id) {
+				let apiUrl = flash_id == 0 ? '/product/detail' : '/flash/productDetail'
+				let product = await this.$api.request(apiUrl + `?id=${id}&flash_id=${flash_id}`, 'GET');
+				uni.stopPullDownRefresh();
+				if (!product) {
+					setTimeout(function(){
+						uni.navigateBack();
+					}, 3000);
+					return;
+				}
+				this.product = product;
+				if (product.flash) {
+					this.countdown = product.flash.countdown;
+					this.progress = product.flash;
+				}
+				this.favorite = this.product.favorite;
+				if (this.product.use_spec) {
+					let specList = this.product.spec_list;
+					let specTableList = this.product.spec_table_list;
+				
+					let e = 1;
+					let ee = 1;
+					let specChildList = [];
+					for (let i in specList) {
+						specList[i].id = e++;
+						for (let ii in specList[i].child) {
+							specChildList.push({
+								id: ee++,
+								pid: specList[i].id,
+								name: specList[i].child[ii]
+							})
+						}
+					}
+					this.specList = specList;
+					this.specChildList = specChildList;
+					this.specTableList = specTableList;
+					//console.log(this.specList)
+					//console.log(specChildList)
+				
+					//规格 默认选中第一条
+					this.specSelected = [];
+					this.specList.forEach(item => {
+						for (let cItem of this.specChildList) {
+							if (cItem.pid === item.id) {
+								this.$set(cItem, 'selected', true);
+								this.specSelected.push(cItem.name);
+								break; //forEach不能使用break
+							}
+						}
+					})
+				}
+			},
+			//领取优惠券开关
+			toggleCoupon() {
+				if (this.couponClass === 'show') {
+					this.couponClass = 'hide';
+					setTimeout(() => {
+						this.couponClass = 'none';
+					}, 250);
+				} else if (this.couponClass === 'none') {
+					this.couponClass = 'show';
+				}
+			},
+			//规格弹窗开关
+			toggleSpec() {
+				if (this.specClass === 'show') {
+					this.specClass = 'hide';
+					setTimeout(() => {
+						this.specClass = 'none';
+					}, 250);
+				} else if (this.specClass === 'none') {
+					this.specClass = 'show';
+				}
+			},
+			//选择规格
+			selectSpec(index, pid) {
+				let list = this.specChildList;
+				list.forEach(item => {
+					if (item.pid === pid) {
+						this.$set(item, 'selected', false);
+					}
+				})
+
+				this.$set(list[index], 'selected', true);
+				//存储已选择
+				/**
+				 * 修复选择规格存储错误
+				 * 将这几行代码替换即可
+				 * 选择的规格存放在specSelected中
+				 */
+				this.specSelected = [];
+				//console.log(list)
+				list.forEach(item => {
+					if (item.selected === true) {
+						this.specSelected.push(item.name);
+					}
+				})
+
+			},
+			//分享
+			share() {
+				this.$refs.share.toggleMask();
+			},
+			//收藏
+			async toFavorite() {
+				if (this.flash) {
+					this.$api.msg('秒杀商品不能收藏');
+					return;
+				}
+				let is_login = await this.$api.checkLogin();
+				if (is_login) {
+					this.favorite = !this.favorite;
+					let bool = await this.$api.request('/product/favorite?id=' + this.product.product_id);
+					if (!bool) {
+						this.favorite = !this.favorite;
+					}
+				}
+			},
+			async buy() {
+				let is_login = await this.$api.checkLogin();
+				if (is_login) {
+					let spec = '';
+					if (this.product.use_spec == 1) {
+						spec = this.specSelected.join(',');
+					}
+					let url = `/pages/order/createOrder?id=${this.product.product_id}&spec=${spec}`;
+					if (this.flash) {
+						url = url + `&flash_id=${this.flash}`;
+					}
+					uni.navigateTo({
+						url:url 
+					});
+				}
+			},
+			//添加购物车
+			async addCart() {
+				if (this.flash) {
+					this.$api.msg('秒杀商品不能加入购物车');
+					return;
+				}
+				let is_login = await this.$api.checkLogin();
+				if (is_login) {
+					let spec = '';
+					if(this.product.use_spec == 1) {
+						spec = this.specSelected.join(',');
+					}
+					let data = await this.$api.request('/cart/add?id=' + this.product.product_id + '&spec='+ spec);
+					if (data) {
+						this.product.cart_num++;
+					}
+				}
+			},
+			stopPrevent() {},
+			// 计算百分比
+			percentage(number, sold) {
+				if (!sold) {
+					return 0;
+				}
+				return parseInt(sold / number * 100);
+			},
+			// 查看图片
+			previewImage(index){
+				uni.previewImage({
+					current:this.product.images_text[index],
+					urls:this.product.images_text,
+					indicator:"number",
+					loop: true
+				})
+			}
+		},
+
+	}
+</script>
+
+<style lang='scss'>
+	page {
+		background: $page-color-base;
+		padding-bottom: 160upx;
+	}
+
+	.icon-you {
+		font-size: $font-base + 2upx;
+		color: #888;
+	}
+
+	.carousel {
+		height: 722upx;
+		position: relative;
+
+		swiper {
+			height: 100%;
+		}
+
+		.image-wrapper {
+			width: 100%;
+			height: 100%;
+		}
+
+		.swiper-item {
+			display: flex;
+			justify-content: center;
+			align-content: center;
+			height: 750upx;
+			overflow: hidden;
+
+			image {
+				width: 100%;
+				height: 100%;
+			}
+		}
+
+	}
+
+	/* 标题简介 */
+	.introduce-section {
+		background: #fff;
+		padding: 20upx 30upx;
+
+		.title {
+			font-size: 32upx;
+			color: $font-color-dark;
+			height: 50upx;
+			line-height: 50upx;
+		}
+
+		.price-box {
+			display: flex;
+			align-items: baseline;
+			height: 64upx;
+			padding: 10upx 0;
+			font-size: 26upx;
+			color: $uni-color-primary;
+		}
+
+		.price {
+			font-size: $font-lg + 2upx;
+		}
+
+		.m-price {
+			margin: 0 12upx;
+			color: $font-color-light;
+			text-decoration: line-through;
+		}
+
+		.coupon-tip {
+			align-items: center;
+			padding: 4upx 10upx;
+			background: $uni-color-primary;
+			font-size: $font-sm;
+			color: #fff;
+			border-radius: 6upx;
+			line-height: 1;
+			transform: translateY(-4upx);
+		}
+
+		.bot-row {
+			display: flex;
+			align-items: center;
+			height: 50upx;
+			font-size: $font-sm;
+			color: $font-color-light;
+
+			text {
+				flex: 1;
+			}
+		}
+	}
+
+	/* 分享 */
+	.share-section {
+		display: flex;
+		align-items: center;
+		color: $font-color-base;
+		background: linear-gradient(left, #fdf5f6, #fbebf6);
+		padding: 12upx 30upx;
+
+		.share-icon {
+			display: flex;
+			align-items: center;
+			width: 70upx;
+			height: 30upx;
+			line-height: 1;
+			border: 1px solid $uni-color-primary;
+			border-radius: 4upx;
+			position: relative;
+			overflow: hidden;
+			font-size: 22upx;
+			color: $uni-color-primary;
+
+			&:after {
+				content: '';
+				width: 50upx;
+				height: 50upx;
+				border-radius: 50%;
+				left: -20upx;
+				top: -12upx;
+				position: absolute;
+				background: $uni-color-primary;
+			}
+		}
+
+		.icon-xingxing {
+			position: relative;
+			z-index: 1;
+			font-size: 24upx;
+			margin-left: 2upx;
+			margin-right: 10upx;
+			color: #fff;
+			line-height: 1;
+		}
+
+		.tit {
+			font-size: $font-base;
+			margin-left: 10upx;
+		}
+
+		.icon-bangzhu1 {
+			padding: 10upx;
+			font-size: 30upx;
+			line-height: 1;
+		}
+
+		.share-btn {
+			flex: 1;
+			text-align: right;
+			font-size: $font-sm;
+			color: $uni-color-primary;
+		}
+
+		.icon-you {
+			font-size: $font-sm;
+			margin-left: 4upx;
+			color: $uni-color-primary;
+		}
+	}
+
+	.c-list {
+		font-size: $font-sm + 2upx;
+		color: $font-color-base;
+		background: #fff;
+
+		.c-row {
+			display: flex;
+			align-items: center;
+			padding: 20upx 30upx;
+			position: relative;
+		}
+
+		.tit {
+			width: 140upx;
+		}
+
+		.con {
+			flex: 1;
+			color: $font-color-dark;
+
+			.selected-text {
+				margin-right: 10upx;
+			}
+		}
+
+		.bz-list {
+			height: 40upx;
+			font-size: $font-sm+2upx;
+			color: $font-color-dark;
+
+			text {
+				display: inline-block;
+				margin-right: 30upx;
+			}
+		}
+
+		.con-list {
+			flex: 1;
+			display: flex;
+			flex-direction: column;
+			color: $font-color-dark;
+			line-height: 40upx;
+		}
+
+		.red {
+			color: $uni-color-primary;
+		}
+	}
+
+	/* 评价 */
+	.eva-section {
+		display: flex;
+		flex-direction: column;
+		padding: 20upx 30upx;
+		background: #fff;
+		margin-top: 16upx;
+
+		.e-header {
+			display: flex;
+			align-items: center;
+			height: 70upx;
+			font-size: $font-sm + 2upx;
+			color: $font-color-light;
+
+			.tit {
+				font-size: $font-base + 2upx;
+				color: $font-color-dark;
+				margin-right: 4upx;
+			}
+
+			.tip {
+				flex: 1;
+				text-align: right;
+			}
+
+			.icon-you {
+				margin-left: 10upx;
+			}
+		}
+	}
+
+	.eva-box {
+		display: flex;
+		padding: 20upx 0;
+
+		.portrait {
+			flex-shrink: 0;
+			width: 80upx;
+			height: 80upx;
+			border-radius: 100px;
+		}
+
+		.right {
+			flex: 1;
+			display: flex;
+			flex-direction: column;
+			font-size: $font-base;
+			color: $font-color-base;
+			padding-left: 26upx;
+				
+			.con {
+				font-size: $font-base;
+				color: $font-color-dark;
+				padding: 20upx 0;
+			}
+
+			.bot {
+				display: flex;
+				justify-content: space-between;
+				font-size: $font-sm;
+				color: $font-color-light;
+			}
+		}
+	}
+
+	/*  详情 */
+	.detail-desc {
+		background: #fff;
+		margin-top: 16upx;
+		.d-header {
+			display: flex;
+			justify-content: center;
+			align-items: center;
+			height: 80upx;
+			font-size: $font-base + 2upx;
+			color: $font-color-dark;
+			position: relative;
+
+			text {
+				padding: 0 20upx;
+				background: #fff;
+				position: relative;
+				z-index: 1;
+			}
+
+			&:after {
+				position: absolute;
+				left: 50%;
+				top: 50%;
+				transform: translateX(-50%);
+				width: 300upx;
+				height: 0;
+				content: '';
+				border-bottom: 1px solid #ccc;
+			}
+		}
+	}
+
+	/* 规格选择弹窗 */
+	.attr-content {
+		padding: 10upx 30upx;
+
+		.a-t {
+			display: flex;
+
+			image {
+				width: 170upx;
+				height: 170upx;
+				flex-shrink: 0;
+				margin-top: -40upx;
+				border-radius: 8upx;
+				;
+			}
+
+			.right {
+				display: flex;
+				flex-direction: column;
+				padding-left: 24upx;
+				font-size: $font-sm + 2upx;
+				color: $font-color-base;
+				line-height: 42upx;
+
+				.price {
+					font-size: $font-lg;
+					color: $uni-color-primary;
+					margin-bottom: 10upx;
+				}
+
+				.selected-text {
+					margin-right: 10upx;
+				}
+			}
+		}
+
+		.attr-list {
+			display: flex;
+			flex-direction: column;
+			font-size: $font-base + 2upx;
+			color: $font-color-base;
+			padding-top: 30upx;
+			padding-left: 10upx;
+		}
+
+		.item-list {
+			padding: 20upx 0 0;
+			display: flex;
+			flex-wrap: wrap;
+
+			text {
+				display: flex;
+				align-items: center;
+				justify-content: center;
+				background: #eee;
+				margin-right: 20upx;
+				margin-bottom: 20upx;
+				border-radius: 100upx;
+				min-width: 60upx;
+				height: 60upx;
+				padding: 0 20upx;
+				font-size: $font-base;
+				color: $font-color-dark;
+			}
+
+			.selected {
+				background: #fbebee;
+				color: $uni-color-primary;
+			}
+		}
+	}
+
+	/*  弹出层 */
+	.popup {
+		position: fixed;
+		left: 0;
+		top: 0;
+		right: 0;
+		bottom: 0;
+		z-index: 99;
+
+		&.show {
+			display: block;
+
+			.mask {
+				animation: showPopup 0.2s linear both;
+			}
+
+			.layer {
+				animation: showLayer 0.2s linear both;
+			}
+		}
+
+		&.hide {
+			.mask {
+				animation: hidePopup 0.2s linear both;
+			}
+
+			.layer {
+				animation: hideLayer 0.2s linear both;
+			}
+		}
+
+		&.none {
+			display: none;
+		}
+
+		.mask {
+			position: fixed;
+			top: 0;
+			width: 100%;
+			height: 100%;
+			z-index: 1;
+			background-color: rgba(0, 0, 0, 0.4);
+		}
+
+		.layer {
+			position: fixed;
+			z-index: 99;
+			bottom: 0;
+			width: 100%;
+			min-height: 40vh;
+			border-radius: 10upx 10upx 0 0;
+			background-color: #fff;
+
+			.btn {
+				height: 66upx;
+				line-height: 66upx;
+				border-radius: 100upx;
+				background: $uni-color-primary;
+				font-size: $font-base + 2upx;
+				color: #fff;
+				margin: 30upx auto 20upx;
+
+			}
+		}
+
+		@keyframes showPopup {
+			0% {
+				opacity: 0;
+			}
+
+			100% {
+				opacity: 1;
+			}
+		}
+
+		@keyframes hidePopup {
+			0% {
+				opacity: 1;
+			}
+
+			100% {
+				opacity: 0;
+			}
+		}
+
+		@keyframes showLayer {
+			0% {
+				transform: translateY(120%);
+			}
+
+			100% {
+				transform: translateY(0%);
+			}
+		}
+
+		@keyframes hideLayer {
+			0% {
+				transform: translateY(0);
+			}
+
+			100% {
+				transform: translateY(120%);
+			}
+		}
+	}
+
+	/* 底部操作菜单 */
+	.page-bottom {
+		position: fixed;
+		left: 30upx;
+		bottom: 30upx;
+		z-index: 95;
+		display: flex;
+		justify-content: center;
+		align-items: center;
+		width: 690upx;
+		height: 100upx;
+		background: rgba(255, 255, 255, .9);
+		box-shadow: 0 0 20upx 0 rgba(0, 0, 0, .5);
+		border-radius: 16upx;
+
+		.p-b-btn {
+			display: flex;
+			flex-direction: column;
+			align-items: center;
+			justify-content: center;
+			font-size: $font-sm;
+			color: $font-color-base;
+			width: 96upx;
+			height: 80upx;
+			position: relative;
+			
+			/* 购物车数量 */
+			.cart-count{
+				border: 4rpx solid #fa436a;
+				width: 40rpx;
+				height: 40rpx;
+				color: #fa436a;
+				text-align: center;
+				border-radius: 40rpx;
+				position: absolute;
+				background: #fff;
+				top: -10rpx;
+				right: 0;
+			}
+
+			.yticon {
+				font-size: 46upx;
+				line-height: 48upx;
+				color: $font-color-light;
+			}
+
+			&.active,
+			&.active .yticon {
+				color: $uni-color-primary;
+			}
+		}
+
+		.action-btn-group {
+			display: flex;
+			height: 76upx;
+			border-radius: 100px;
+			overflow: hidden;
+			box-shadow: 0 20upx 40upx -16upx #fa436a;
+			box-shadow: 1px 2px 5px rgba(219, 63, 96, 0.4);
+			background: linear-gradient(to right, #ffac30, #fa436a, #F56C6C);
+			margin-left: 20upx;
+			position: relative;
+			
+			.only {
+				width: 360rpx!important;
+			}
+
+			&:after {
+				content: '';
+				position: absolute;
+				top: 50%;
+				right: 50%;
+				transform: translateY(-50%);
+				height: 28upx;
+				width: 0;
+				border-right: 1px solid rgba(255, 255, 255, .5);
+			}
+
+			.action-btn {
+				display: flex;
+				align-items: center;
+				justify-content: center;
+				width: 180upx;
+				height: 100%;
+				font-size: $font-base;
+				padding: 0;
+				border-radius: 0;
+				background: transparent;
+			}
+		}
+		
+	}
+	.flash {
+		height: 100upx;
+		background: linear-gradient(to right, #ffac30, #fa436a, #F56C6C);
+		display: flex;
+		flex-direction: row;
+		position: relative;
+		.sales_price{
+			.symbol{
+				font-size: 30upx;
+				display: inline;
+			}
+			color: #fff;
+			font-size: 50upx;
+		}
+		.left{
+			font-size: 28upx;
+			padding: 10upx;
+			.market_price{
+				color: #DCDFE6;
+				text-decoration: line-through;
+			}
+			.sold{
+				color: #E4E7ED;
+			}
+		}
+		.right{
+			height: 100%;
+			width: 300upx;
+			position: absolute;
+			right: 0;
+			padding: 4upx;
+			margin-right: 20upx;
+			.time{
+				font-size: 30upx;
+				color: #fffa30;
+				text-align: center;
+				.uni-countdown{
+					display: inline-flex;
+				}
+			}
+			.progress{
+				position: absolute;
+				bottom: 7rpx;
+				right: 10upx;
+			}
+			.flashDone{
+				font-size: 40rpx;
+				height: 90rpx;
+				line-height: 90rpx;
+			}
+		}
+	}
+	
+	
+	
+	/* 优惠券列表 */
+	.content-coupon{
+		padding-bottom: 100rpx;
+		max-height: 800rpx;
+		overflow: auto;
+	}
+	.retract{
+		position: fixed;
+		bottom: 0;
+		width: 700rpx;
+		z-index: 100;
+		margin: 20rpx 25rpx;
+		color:#ffffff;
+		background: #fa436a;
+	}
+	.coupon-item {
+		display: flex;
+		flex-direction: column;
+		background: #fff;
+	
+		.con {
+			display: flex;
+			align-items: center;
+			position: relative;
+			height: 120upx;
+			padding: 0 30upx;
+	
+			&:after {
+				position: absolute;
+				left: 0;
+				bottom: 0;
+				content: '';
+				width: 100%;
+				height: 0;
+				border-bottom: 1px dashed #f3f3f3;
+				transform: scaleY(50%);
+			}
+		}
+	
+		.left {
+			display: flex;
+			flex-direction: column;
+			justify-content: center;
+			flex: 1;
+			overflow: hidden;
+			height: 100upx;
+		}
+	
+		.title {
+			font-size: 32upx;
+			color: $font-color-dark;
+			margin-bottom: 10upx;
+		}
+	
+		.time {
+			font-size: 24upx;
+			color: $font-color-light;
+		}
+	
+		.right {
+			display: flex;
+			flex-direction: column;
+			justify-content: center;
+			align-items: center;
+			font-size: 26upx;
+			color: $font-color-base;
+			height: 100upx;
+		}
+	
+		.price {
+			font-size: 44upx;
+			color: $base-color;
+	
+			&:before {
+				content: '¥';
+				font-size: 34upx;
+			}
+		}
+	
+		.tips {
+			font-size: 24upx;
+			color: $font-color-light;
+			line-height: 60upx;
+			padding-left: 30upx;
+		}
+	
+		.circle {
+			position: absolute;
+			left: -6upx;
+			bottom: -10upx;
+			z-index: 10;
+			width: 20upx;
+			height: 20upx;
+			background: #f3f3f3;
+			border-radius: 100px;
+	
+			&.r {
+				left: auto;
+				right: -6upx;
+			}
+		}
+	}
+</style>

+ 232 - 0
pages/public/login.vue

@@ -0,0 +1,232 @@
+<template>
+	<view class="container">
+		
+		<view class="back-btn yticon icon-zuojiantou-up" @click="navBack"></view>
+		
+		<!-- 设置白色背景防止软键盘把下部绝对定位元素顶上来盖住输入框等 -->
+		<view class="wrapper">
+			<view class="left-top-sign">LOGIN</view>
+			<view class="welcome">
+				欢迎回来!
+			</view>
+			
+			<view class="input-content">
+				<view class="input-item">
+					<text class="tit">手机号码</text>
+					<input 
+						type="text" 
+						:value="mobile" 
+						placeholder="请输入手机号码"
+						data-key="mobile"
+						@input="inputChange"
+					/>
+				</view>
+				<view class="input-item">
+					<text class="tit">密码</text>
+					<input 
+						type="mobile" 
+						value="" 
+						placeholder="6-18位不含特殊字符的数字、字母组合"
+						placeholder-class="input-empty"
+						maxlength="20"
+						password 
+						data-key="password"
+						@input="inputChange"
+						@confirm="toLogin"
+					/>
+				</view>
+			</view>
+			<button class="confirm-btn" @click="toLogin" :disabled="logining">登录</button>
+			<!-- #ifdef MP-WEIXIN -->
+			<button class="confirm-btn" open-type="getPhoneNumber" @getphonenumber="loginForWechatMini">授权微信绑定电话号码一键登录</button>
+			<!-- #endif -->
+			
+			<view class="forget-section">
+				<view><label @click="register('resetpwd')">忘记密码</label> - <label @click="register('register')">立马注册</label></view>
+			</view>
+			
+			
+		</view>
+	</view>
+</template>
+
+<script>
+	import {  
+        mapMutations  
+    } from 'vuex';
+	
+	export default{
+		data(){
+			return {
+				mobile: '',
+				password: '',
+				logining: false
+			}
+		},
+		onLoad(){
+			
+		},
+		methods: {
+			...mapMutations(['login']),
+			inputChange(e){
+				const key = e.currentTarget.dataset.key;
+				this[key] = e.detail.value;
+			},
+			navBack(){
+				uni.navigateBack();
+			},
+			async toLogin(){
+				this.logining = true;
+				let data = await this.$api.request('/user/login', 'POST', {
+					mobile: this.mobile,
+					password: this.password
+				})
+				if (data) {
+					this.$store.commit('login', data)
+					
+					this.logining = true;
+					
+					setTimeout(function(){
+						uni.navigateBack();
+					}, 2000)
+					
+				} else {
+					this.logining = false;
+				}
+				
+			},
+			register(event){
+				uni.navigateTo({
+					url:'./register?event='+event
+				})
+			},
+			// #ifdef MP-WEIXIN
+			async loginForWechatMini(e) {
+				if (e.hasOwnProperty('detail')) {
+					let data = await this.$api.request('/user/loginForWechatMini', 'POST',{
+						encryptedData: e.detail.encryptedData,
+						iv: e.detail.iv
+					});
+					
+					if (data) {
+						this.$store.commit('login', data);
+						this.$api.msg('登录成功');
+						setTimeout(function() {
+							uni.navigateBack();
+						},2000);
+					}
+				}
+			},
+			// #endif
+		},
+
+	}
+</script>
+
+<style lang='scss'>
+	page{
+		background: #fff;
+	}
+	.container{
+		padding-top: 160rpx;
+		position:relative;
+		width: 100vw;
+		height: 100vh;
+		overflow: hidden;
+		background: #fff;
+	}
+	.wrapper{
+		position:relative;
+		z-index: 90;
+		background: #fff;
+		padding-bottom: 40upx;
+	}
+	.back-btn{
+		position:absolute;
+		left: 40upx;
+		z-index: 9999;
+		padding-top: var(--status-bar-height);
+		top: 40upx;
+		font-size: 40upx;
+		color: $font-color-dark;
+	}
+	.left-top-sign{
+		font-size: 120upx;
+		color: $page-color-base;
+		position:relative;
+		left: -16upx;
+	}
+	.welcome{
+		position:relative;
+		left: 50upx;
+		top: -90upx;
+		font-size: 46upx;
+		color: #555;
+		text-shadow: 1px 0px 1px rgba(0,0,0,.3);
+	}
+	.input-content{
+		padding: 0 60upx;
+	}
+	.input-item{
+		display:flex;
+		flex-direction: column;
+		align-items:flex-start;
+		justify-content: center;
+		padding: 0 30upx;
+		background:$page-color-light;
+		height: 120upx;
+		border-radius: 4px;
+		margin-bottom: 50upx;
+		&:last-child{
+			margin-bottom: 0;
+		}
+		.tit{
+			height: 50upx;
+			line-height: 56upx;
+			font-size: $font-sm+2upx;
+			color: $font-color-base;
+		}
+		input{
+			height: 60upx;
+			font-size: $font-base + 2upx;
+			color: $font-color-dark;
+			width: 100%;
+		}	
+	}
+
+	.confirm-btn{
+		width: 630upx;
+		height: 76upx;
+		line-height: 76upx;
+		border-radius: 50px;
+		margin-top: 70upx;
+		background: $uni-color-primary;
+		color: #fff;
+		font-size: $font-lg;
+		&:after{
+			border-radius: 100px;
+		}
+	}
+	.forget-section{
+		font-size: $font-sm+10upx;
+		color: $font-color-spec;
+		text-align: center;
+		margin-top: 100upx;
+		label{
+			margin: 10upx 60upx;
+		}
+	}
+	.register-section{
+		position:absolute;
+		left: 0;
+		bottom: 50upx;
+		width: 100%;
+		font-size: $font-sm+2upx;
+		color: $font-color-base;
+		text-align: center;
+		text{
+			color: $font-color-spec;
+			margin-left: 10upx;
+		}
+	}
+</style>

+ 289 - 0
pages/public/register.vue

@@ -0,0 +1,289 @@
+<template>
+	<view class="container">
+		<view class="back-btn yticon icon-zuojiantou-up" @click="navBack"></view>
+		
+		<!-- 设置白色背景防止软键盘把下部绝对定位元素顶上来盖住输入框等 -->
+		<view class="wrapper">
+			<view class="left-top-sign">{{desc}}</view>
+			<view class="welcome">
+				{{desc}}
+			</view>
+			<view class="input-content">
+				<view class="input-item" v-if="event == 'register'">
+					<text class="tit">用户名</text>
+					<input 
+						type="text" 
+						:value="username" 
+						placeholder="请输入用户名"
+						data-key="username"
+						@input="inputChange"
+					/>
+				</view>
+				<view class="input-item">
+					<text class="tit">手机号码</text>
+					<input 
+						type="number" 
+						:value="mobile" 
+						placeholder="请输入手机号码"
+						data-key="mobile"
+						@input="inputChange"
+					/>
+				</view>
+				<view class="input-item">
+					<text class="tit">密码</text>
+					<input 
+						type="text" 
+						value="" 
+						placeholder="6-18位不含特殊字符的数字、字母组合"
+						placeholder-class="input-empty"
+						maxlength="20"
+						password 
+						data-key="password"
+						@confirm="toLogin"
+						@input="inputChange"
+					/>
+				</view>
+				<view class="input-item">
+					<text class="tit">验证码</text>
+					<input 
+						type="text" 
+						:value="captcha" 
+						placeholder="请输入验证码"
+						data-key="captcha"
+						@input="inputChange"
+					/>
+					<button class="get_captcha" @click="getCaptcha">{{get_captcha}}</button>
+				</view>
+			</view>
+			<button class="confirm-btn" @click="toLogin" :disabled="logining">
+				{{status}}
+			</button>
+			<view class="forget-section">
+
+			</view>
+		</view>
+	</view>
+</template>
+
+<script>
+	import {  
+        mapMutations  
+    } from 'vuex';
+	
+	export default{
+		data(){
+			return {
+				username: '',
+				mobile: '',
+				password: '',
+				captcha:'',
+				logining: false,
+				status: '',
+				desc: '',
+				get_captcha: '获取验证码',
+				event: ''
+			}
+		},
+		onLoad(options){
+			if(options.event == 'register'){
+				this.status = '立即注册';
+				this.desc = '注册账号'
+			}else{
+				this.status = '确认修改';
+				this.desc = '修改密码'
+			}
+			this.event = options.event;
+		},
+		methods: {
+			...mapMutations(['login']),
+			async getCaptcha(){
+				let that = this;
+				let num = 60;
+				
+				if (this.mobile == '') {
+					this.$api.msg('请输入手机号码');
+					return;
+				}
+				
+				let data = await this.$api.request('/sms/send', 'POST', {
+					mobile: this.mobile,
+					event: this.event
+				});
+				if (data) {
+					that.get_captcha = num + ' S';
+					let intervalId = setInterval(function(){
+						num--;
+						that.get_captcha = num + ' S';
+						if (num == 0) {
+							that.get_captcha = '获取验证码'
+							clearInterval(intervalId);
+						}
+					}, 1000);
+				}
+				
+			},
+			navBack(){
+				uni.navigateBack();
+			},
+			inputChange (e) {
+				const key = e.currentTarget.dataset.key;
+				this[key] = e.detail.value;
+			},
+			async toLogin(){
+				this.logining = true; // 按钮锁
+				
+				let url = this.event == 'register' ? '/user/register' : '/user/resetpwd';
+				
+				let data =  await this.$api.request(url, 'POST', {
+					mobile: this.mobile,
+					password: this.password,
+					username: this.username,
+					captcha: this.captcha,
+					event: this.event
+				});
+				if (data) {
+					this.login(data.userinfo);
+					setTimeout(function(){
+						uni.navigateBack(); 
+					}, 3000);
+				} else {
+					this.logining = false;
+				}
+			},
+			forget(){
+				uni.navigateTo({
+					url:'./forget'
+				})
+			},
+			register(){
+				uni.navigateTo({
+					url:'./forget'
+				})
+			}
+		},
+
+	}
+</script>
+
+<style lang='scss'>
+	page{
+		background: #fff;
+	}
+	.container{
+		padding-top: 80rpx;
+		position:relative;
+		width: 100vw;
+		height: 100vh;
+		overflow: hidden;
+		background: #fff;
+	}
+	.wrapper{
+		position:relative;
+		z-index: 90;
+		background: #fff;
+		padding-bottom: 40upx;
+	}
+	.back-btn{
+		position:absolute;
+		left: 40upx;
+		z-index: 9999;
+		padding-top: var(--status-bar-height);
+		top: 40upx;
+		font-size: 40upx;
+		color: $font-color-dark;
+	}
+	.left-top-sign{
+		font-size: 120upx;
+		color: $page-color-base;
+		position:relative;
+		left: -16upx;
+	}
+	
+	.welcome{
+		position:relative;
+		left: 50upx;
+		top: -90upx;
+		font-size: 46upx;
+		color: #555;
+		text-shadow: 1px 0px 1px rgba(0,0,0,.3);
+	}
+	.input-content{
+		padding: 0 60upx;
+	}
+	.input-item{
+		display:flex;
+		flex-direction: column;
+		align-items:flex-start;
+		justify-content: center;
+		padding: 0 30upx;
+		background:$page-color-light;
+		height: 120upx;
+		border-radius: 4px;
+		margin-bottom: 50upx;
+		position: relative;
+		&:last-child{
+			margin-bottom: 0;
+		}
+		.tit{
+			height: 50upx;
+			line-height: 56upx;
+			font-size: $font-sm+2upx;
+			color: $font-color-base;
+		}
+		input{
+			height: 60upx;
+			font-size: $font-base + 2upx;
+			color: $font-color-dark;
+			width: 100%;
+		}
+		.get_captcha {
+			position: absolute;
+			right: 0;
+			display: block;
+			width: 40%;
+			background: $uni-color-primary;
+			color: #fff;
+			text-align: center;
+			line-height: 76upx;
+			border-radius: 50upx;
+			z-index: 100;
+		}
+	}
+
+	.confirm-btn{
+		width: 630upx;
+		height: 76upx;
+		line-height: 76upx;
+		border-radius: 50px;
+		margin-top: 70upx;
+		background: $uni-color-primary;
+		color: #fff;
+		font-size: $font-lg;
+		&:after{
+			border-radius: 100px;
+		}
+	}
+	.forget-section{
+		font-size: $font-sm+10upx;
+		color: $font-color-spec;
+		text-align: center;
+		margin-top: 100upx;
+		label{
+			margin: 10upx 60upx;
+		}
+	}
+	.register-section{
+		position:absolute;
+		left: 0;
+		bottom: 50upx;
+		width: 100%;
+		font-size: $font-sm+2upx;
+		color: $font-color-base;
+		text-align: center;
+		text{
+			color: $font-color-spec;
+			margin-left: 10upx;
+		}
+	}
+	
+</style>

+ 37 - 0
pages/public/webview.vue

@@ -0,0 +1,37 @@
+<template>
+	<view>
+		<web-view :src="url"></web-view>
+	</view>
+</template>
+
+<script>
+	
+	export default {
+		data() {
+			return {
+				url: ''
+			}
+		},
+		created() {
+
+		},
+		onLoad(options) {
+			switch (options.type) {
+				case 'kd':
+					this.url = "https://m.kuaidi100.com/result.jsp?nu=" + options.number;
+					break;
+				case 'MWEB':
+					uni.setNavigationBarTitle({
+						title: '微信支付'
+					});
+
+					
+
+					break;
+			}
+		}
+	}
+</script>
+
+<style>
+</style>

+ 104 - 0
pages/set/set.vue

@@ -0,0 +1,104 @@
+<template>
+	<view class="container">
+		<view class="list-cell b-b m-t" @click="navTo('/pages/userinfo/userinfo')" hover-class="cell-hover" :hover-stay-time="50">
+			<text class="cell-tit">个人资料</text>
+			<text class="cell-more yticon icon-you"></text>
+		</view>
+		<view class="list-cell log-out-btn" @click="toLogout">
+			<text class="cell-tit">退出登录</text>
+		</view>
+	</view>
+</template>
+
+<script>
+	import {  
+	    mapMutations  
+	} from 'vuex';
+	export default {
+		data() {
+			return {
+				
+			};
+		},
+		methods:{
+			...mapMutations(['logout']),
+
+			navTo(url){
+				uni.navigateTo({
+					url:url
+				});
+			},
+			//退出登录
+			toLogout(){
+				uni.showModal({
+				    content: '确定要退出登录么',
+				    success: (e)=>{
+				    	if(e.confirm){
+				    		this.logout();
+				    		setTimeout(()=>{
+				    			uni.navigateBack();
+				    		}, 200)
+				    	}
+				    }
+				});
+			},
+			//switch
+			switchChange(e){
+				let statusTip = e.detail.value ? '打开': '关闭';
+				this.$api.msg(`${statusTip}消息推送`);
+			},
+
+		}
+	}
+</script>
+
+<style lang='scss'>
+	page{
+		background: $page-color-base;
+	}
+	.list-cell{
+		display:flex;
+		align-items:baseline;
+		padding: 20upx $page-row-spacing;
+		line-height:60upx;
+		position:relative;
+		background: #fff;
+		justify-content: center;
+		&.log-out-btn{
+			margin-top: 40upx;
+			.cell-tit{
+				color: $uni-color-primary;
+				text-align: center;
+				margin-right: 0;
+			}
+		}
+		&.cell-hover{
+			background:#fafafa;
+		}
+		&.b-b:after{
+			left: 30upx;
+		}
+		&.m-t{
+			margin-top: 16upx; 
+		}
+		.cell-more{
+			align-self: baseline;
+			font-size:$font-lg;
+			color:$font-color-light;
+			margin-left:10upx;
+		}
+		.cell-tit{
+			flex: 1;
+			font-size: $font-base + 2upx;
+			color: $font-color-dark;
+			margin-right:10upx;
+		}
+		.cell-tip{
+			font-size: $font-base;
+			color: $font-color-light;
+		}
+		switch{
+			transform: translateX(16upx) scale(.84);
+		}
+	}
+</style>

+ 419 - 0
pages/user/user.vue

@@ -0,0 +1,419 @@
+<template>  
+    <view class="container">  
+		
+		<view class="user-section">
+			<image class="bg" src="/static/user-bg.jpg"></image>
+			<view class="user-info-box">
+				<view class="portrait-box">
+					<image class="portrait" :src="userInfo.avatar ? userInfo.avatar : '/static/missing-face.png'"></image>
+				</view>
+				<view class="info-box">
+					<text class="username">{{userInfo.username || '游客'}}</text>
+				</view>
+			</view>
+			<view class="vip-card-box">
+				<image class="card-bg" src="/static/vip-card-bg.png" mode=""></image>
+				<!-- <view class="b-btn">
+					立即开通
+				</view> -->
+				<view class="tit">
+					<text class="yticon icon-iLinkapp-"></text>
+					喂喂会员
+				</view>
+				<text class="e-m">uniShop</text> 
+				<!-- <text class="e-b">开通会员开发无bug 一测就上线</text> -->
+			</view>
+		</view>
+		
+		<view 
+			class="cover-container"
+			:style="[{
+				transform: coverTransform,
+				transition: coverTransition
+			}]"
+			@touchstart="coverTouchstart"
+			@touchmove="coverTouchmove"
+			@touchend="coverTouchend"
+		>
+			<image class="arc" src="/static/arc.png"></image>
+			
+			<!-- 订单 -->
+			<view class="order-header">
+				<view class="title">我的订单</view>
+				<view class="item" @click="$api.navTo('/pages/order/order?state=0')" >查看全部订单
+				<text class="yticon right icon-you"></text>
+			</view>
+			</view>
+			<view class="order-section">
+				<view class="order-item" @click="$api.navTo('/pages/order/order?state=1')"  hover-class="common-hover" :hover-stay-time="50">
+					<text class="yticon icon-daifukuan"></text>
+					<text class="num" v-if="orderNum.unpaid > 0">{{orderNum.unpaid}}</text>
+					<text>待付款</text>
+				</view>
+				<view class="order-item" @click="$api.navTo('/pages/order/order?state=2')" hover-class="common-hover"  :hover-stay-time="50">
+					<text class="yticon icon-daifahuo"></text>
+					<text class="num" v-if="orderNum.undelivered > 0">{{orderNum.undelivered}}</text>
+					<text>待发货</text>
+				</view>
+				<view class="order-item" @click="$api.navTo('/pages/order/order?state=3')" hover-class="common-hover"  :hover-stay-time="50">
+					<text class="yticon icon-daishouhuo"></text>
+					<text class="num" v-if="orderNum.unreceived > 0">{{orderNum.unreceived}}</text>
+					<text>待收货</text>
+				</view>
+				<view class="order-item" @click="$api.navTo('/pages/order/order?state=4')" hover-class="common-hover"  :hover-stay-time="50">
+					<text class="yticon icon-pingjia"></text>
+					<text class="num" v-if="orderNum.uncomment > 0">{{orderNum.uncomment}}</text>
+					<text>评价</text>
+				</view>
+				<view class="order-item" @click="$api.navTo('/pages/order/order?state=5')" hover-class="common-hover"  :hover-stay-time="50">
+					<text class="yticon icon-shouhou"></text>
+					<text class="num" v-if="orderNum.refund > 0">{{orderNum.refund}}</text>
+					<text>退款/售后</text>
+				</view>
+			</view>
+			<!-- 浏览历史 -->
+			<view class="history-section icon">
+				<!-- <view class="sec-header">
+					<text class="yticon icon-lishijilu"></text>
+					<text>浏览历史</text>
+				</view> -->
+				<!-- <scroll-view scroll-x class="h-list">
+					<image @click="navTo('/pages/product/product')" src="https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1553105186633&di=c121a29beece4e14269948d990f9e720&imgtype=0&src=http%3A%2F%2Fimg004.hc360.cn%2Fm8%2FM04%2FDE%2FDE%2FwKhQplZ-QteEBvsbAAAAADUkobU751.jpg" mode="aspectFill"></image>
+					<image @click="navTo('/pages/product/product')" src="https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1553105231218&di=09534b9833b5243296630e6d5b728eff&imgtype=0&src=http%3A%2F%2Fimg002.hc360.cn%2Fm1%2FM05%2FD1%2FAC%2FwKhQcFQ3iN2EQTo8AAAAAHQU6_8355.jpg" mode="aspectFill"></image>
+					<image @click="navTo('/pages/product/product')" src="https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1553105320890&di=c743386be51f2c4c0fd4b75754d14f3c&imgtype=0&src=http%3A%2F%2Fimg007.hc360.cn%2Fhb%2FMTQ1OTg4ODY0MDA3Ny05OTQ4ODY1NDQ%3D.jpg" mode="aspectFill"></image>
+					<image @click="navTo('/pages/product/product')" src="https://ss2.bdstatic.com/70cFvnSh_Q1YnxGkpoWK1HF6hhy/it/u=2691146630,2165926318&fm=26&gp=0.jpg" mode="aspectFill"></image>
+					<image @click="navTo('/pages/product/product')" src="https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1553105443324&di=8141bf13f3f208c61524d67f9bb83942&imgtype=0&src=http%3A%2F%2Fimg.zcool.cn%2Fcommunity%2F01ac9a5548d29b0000019ae98e6d98.jpg" mode="aspectFill"></image>
+					<image @click="navTo('/pages/product/product')" src="https://ss0.bdstatic.com/70cFuHSh_Q1YnxGkpoWK1HF6hhy/it/u=191678693,2701202375&fm=26&gp=0.jpg" mode="aspectFill"></image>
+				</scroll-view> -->
+				<!-- <list-cell icon="icon-iconfontweixin" iconColor="#e07472" title="我的钱包" tips="您的会员还有3天过期"></list-cell> -->
+				<list-cell icon="icon-dizhi" iconColor="#5fcda2" title="地址管理" @eventClick="$api.navTo('/pages/address/address')"></list-cell>
+				<!-- <list-cell icon="icon-share" iconColor="#9789f7" title="分享" tips="邀请好友赢10万大礼"></list-cell> -->
+				<!-- <list-cell icon="icon-pinglun-copy" iconColor="#ee883b" title="晒单" tips="晒单抢红包"></list-cell> -->
+				<list-cell icon="icon-shoucang-setting" iconColor="#54b4ef" title="我的收藏" @eventClick="$api.navTo('/pages/favorite/favorite')"></list-cell>
+				<list-cell icon="icon-setting" iconColor="#e07472" title="设置" border="" @eventClick="$api.navTo('/pages/set/set')"></list-cell>
+			</view>
+		</view>
+			
+		
+    </view>  
+</template>  
+<script>  
+	import listCell from '@/components/mix-list-cell';
+    import {  
+        mapState 
+    } from 'vuex';  
+	let startY = 0, moveY = 0, pageAtTop = true;
+    export default {
+		components: {
+			listCell
+		},
+		data() {
+			return {
+				coverTransform: 'translateY(0px)',
+				coverTransition: '0s',
+				moving: false,
+				orderNum: {
+					unpaid: 0,
+					undelivered: 0,
+					unreceived: 0,
+					uncomment: 0,
+					refund: 0
+				}
+			}
+		},
+		onLoad(){
+			
+		},
+		onShow() {
+			if (this.hasLogin) {
+				this.getOrderNum();
+			}
+		},
+		// #ifndef MP
+		onNavigationBarButtonTap(e) {
+			const index = e.index;
+			if (index === 0) {
+				this.navTo('/pages/set/set');
+			}else if(index === 1){
+				// #ifdef APP-PLUS
+				const pages = getCurrentPages();
+				const page = pages[pages.length - 1];
+				const currentWebview = page.$getAppWebview();
+				currentWebview.hideTitleNViewButtonRedDot({
+					index
+				});
+				// #endif
+				uni.navigateTo({
+					url: '/pages/notice/notice'
+				})
+			}
+		},
+		// #endif
+        computed: {
+			...mapState(['hasLogin','userInfo'])
+		},
+        methods: {
+			/**
+			 *  会员卡下拉和回弹
+			 *  1.关闭bounce避免ios端下拉冲突
+			 *  2.由于touchmove事件的缺陷(以前做小程序就遇到,比如20跳到40,h5反而好很多),下拉的时候会有掉帧的感觉
+			 *    transition设置0.1秒延迟,让css来过渡这段空窗期
+			 *  3.回弹效果可修改曲线值来调整效果,推荐一个好用的bezier生成工具 http://cubic-bezier.com/
+			 */
+			coverTouchstart(e){
+				if(pageAtTop === false){
+					return;
+				}
+				this.coverTransition = 'transform .1s linear';
+				startY = e.touches[0].clientY;
+			},
+			coverTouchmove(e){
+				moveY = e.touches[0].clientY;
+				let moveDistance = moveY - startY;
+				if(moveDistance < 0){
+					this.moving = false;
+					return;
+				}
+				this.moving = true;
+				if(moveDistance >= 80 && moveDistance < 100){
+					moveDistance = 80;
+				}
+		
+				if(moveDistance > 0 && moveDistance <= 80){
+					this.coverTransform = `translateY(${moveDistance}px)`;
+				}
+			},
+			coverTouchend(){
+				if(this.moving === false){
+					return;
+				}
+				this.moving = false;
+				this.coverTransition = 'transform 0.3s cubic-bezier(.21,1.93,.53,.64)';
+				this.coverTransform = 'translateY(0px)';
+			},
+			// 获取订单数量
+			async getOrderNum(){
+				let data = await this.$api.request('/order/count');
+				if (data) {
+					this.orderNum = {
+						unpaid: data.unpaid ? data.unpaid : 0,
+						undelivered: data.undelivered ? data.undelivered : 0,
+						unreceived: data.unreceived ? data.unreceived : 0,
+						uncomment: data.uncomment ? data.uncomment : 0,
+						refund: data.refund ? data.refund : 0
+					};
+				}
+			}
+        }  
+    }  
+</script>  
+<style lang='scss'>
+	page{
+		background: #f5f5f5;
+	}
+	%flex-center {
+	 display:flex;
+	 flex-direction: column;
+	 justify-content: center;
+	 align-items: center;
+	}
+	%section {
+	  display:flex;
+	  justify-content: space-around;
+	  align-content: center;
+	  background: #fff;
+	  border-radius: 10upx;
+	}
+
+	.user-section{
+		height: 520upx;
+		padding: 100upx 30upx 0;
+		position:relative;
+		.bg{
+			position:absolute;
+			left: 0;
+			top: 0;
+			width: 100%;
+			height: 100%;
+			filter: blur(1px);
+			opacity: .7;
+		}
+	}
+	.user-info-box{
+		height: 180upx;
+		display:flex;
+		align-items:center;
+		position:relative;
+		z-index: 1;
+		.portrait{
+			width: 130upx;
+			height: 130upx;
+			border-radius: 50%;
+		}
+		.username{
+			font-size: $font-lg + 6upx;
+			color: $font-color-dark;
+			margin-left: 20upx;
+		}
+	}
+
+	.vip-card-box{
+		display:flex;
+		flex-direction: column;
+		color: #f7d680;
+		height: 240upx;
+		background: linear-gradient(left, rgba(0,0,0,.7), rgba(0,0,0,.8));
+		border-radius: 16upx 16upx 0 0;
+		overflow: hidden;
+		position: relative;
+		padding: 20upx 24upx;
+		.card-bg{
+			position:absolute;
+			top: 20upx;
+			right: 0;
+			width: 380upx;
+			height: 260upx;
+		}
+		.b-btn{
+			position: absolute;
+			right: 20upx;
+			top: 16upx;
+			width: 132upx;
+			height: 40upx;
+			text-align: center;
+			line-height: 40upx;
+			font-size: 22upx;
+			color: #36343c;
+			border-radius: 20px;
+			background: linear-gradient(left, #f9e6af, #ffd465);
+			z-index: 1;
+		}
+		.tit{
+			font-size: $font-base+2upx;
+			color: #f7d680;
+			margin-bottom: 28upx;
+			.yticon{
+				color: #f6e5a3;
+				margin-right: 16upx;
+			}
+		}
+		.e-b{
+			font-size: $font-sm;
+			color: #d8cba9;
+			margin-top: 10upx;
+		}
+	}
+	.cover-container{
+		margin-top: -150upx;
+		padding: 0 30upx;
+		position:relative;
+		background: #f5f5f5;
+		padding-bottom: 20upx;
+		.arc{
+			position:absolute;
+			left: 0;
+			top: -34upx;
+			width: 100%;
+			height: 36upx;
+		}
+	}
+	.tj-sction{
+		@extend %section;
+		.tj-item{
+			@extend %flex-center;
+			flex-direction: column;
+			height: 140upx;
+			font-size: $font-sm;
+			color: #75787d;
+		}
+		.num{
+			font-size: $font-lg;
+			color: $font-color-dark;
+			margin-bottom: 8upx;
+		}
+	}
+	.order-header {
+		@extend %section;
+		padding: 28upx 0 0;
+		border-radius: 10upx 10upx 0 0;
+		margin-top: 20upx;
+		font-size: $font-base;
+		.title {
+			width: 50%;
+			color: $font-color-dark;
+			margin-left: 30upx;
+		}
+		.item {
+			width: 50%;
+			color:$font-color-base;
+			text-align: right;
+			margin-right: 8upx;
+		}
+	}
+	.order-section{
+		@extend %section;
+		border-radius: 0 0 10upx 10upx;
+		padding: 28upx 0;
+		.order-item {
+			@extend %flex-center;
+			width: 120upx;
+			height: 120upx;
+			border-radius: 10upx;
+			font-size: $font-sm;
+			color: $font-color-dark;
+			position: relative;
+			.num{
+				border: 4rpx solid #fa436a;
+				width: 40rpx;
+				height: 40rpx;
+				color: #fa436a;
+				text-align: center;
+				border-radius: 40rpx;
+				position: absolute;
+				background: #ffffff;
+				top: 0;
+				right: 18rpx;
+			}
+		}
+		.yticon{
+			font-size: 48upx;
+			margin-bottom: 18upx;
+			color: #fa436a;
+		}
+		.icon-shouhoutuikuan{
+			font-size:44upx;
+		}
+	}
+	.history-section{
+		padding:  0;
+		margin-top: 20upx;
+		background: #fff;
+		border-radius:10upx;
+		.sec-header{
+			display:flex;
+			align-items: center;
+			font-size: $font-base;
+			color: $font-color-dark;
+			line-height: 40upx;
+			margin-left: 30upx;
+			.yticon{
+				font-size: 44upx;
+				color: #5eba8f;
+				margin-right: 16upx;
+				line-height: 40upx;
+			}
+		}
+		.h-list{
+			white-space: nowrap;
+			padding: 30upx 30upx 0;
+			image{
+				display:inline-block;
+				width: 160upx;
+				height: 160upx;
+				margin-right: 20upx;
+				border-radius: 10upx;
+			}
+		}
+	}
+	
+</style>

+ 216 - 0
pages/userinfo/userinfo.vue

@@ -0,0 +1,216 @@
+<template>
+	<view>
+		<view class="user-section">
+			<image class="bg" src="/static/user-bg.jpg"></image>
+			<text class="bg-upload-btn yticon icon-paizhao"></text>
+			<view class="portrait-box">
+				<image class="portrait" :src="avatar ? avatar : '/static/missing-face.png'"></image>
+				<text class="pt-upload-btn yticon icon-paizhao"></text>
+				<button class="button" open-type="getUserInfo" @getuserinfo="getAvatar"></button>
+			</view>
+		</view>
+
+		<!-- #ifdef MP-WEIXIN -->
+		<view class="yt-list-cell">
+			<view class="cell-tit clamp">用户名
+				<input class="input" type="text" v-model="username" />
+				<button class="input username" open-type="getUserInfo" @getuserinfo="getUserInfo">获取微信名称</button>
+			</view>
+		</view>
+		<view class="yt-list-cell">
+			<view class="cell-tit clamp">手机号码
+				<input v-if="mobile" disabled="true" class="input" type="text" v-model="mobile" />
+				<button v-else class="input" open-type="getPhoneNumber" @getphonenumber="getPhoneNumber">授权获取微信绑定的手机号码</button>
+			</view>
+		</view>
+		<!-- #endif -->
+		<!-- #ifndef MP-WEIXIN -->
+		<view class="yt-list-cell">
+			<view class="cell-tit clamp">用户名
+				<input class="input" type="text" v-model="username" />
+			</view>
+		</view>
+		<view class="yt-list-cell">
+			<view class="cell-tit clamp">手机号码
+				<input v-if="mobile" disabled="true" class="input" type="text" v-model="mobile" />
+			</view>
+		</view>
+		<!-- #endif -->
+
+		<view class="yt-list-cell">
+			<button type="primary" @click="submit">提交保存</button>
+		</view>
+
+	</view>
+</template>
+
+<script>
+	import {
+		mapState,
+		mapMutations
+	} from 'vuex';
+	export default {
+		data() {
+			return {
+				username: '',
+				mobile: '',
+				avatar: ''
+			};
+		},
+		computed: {
+			...mapState(['userInfo'])
+		},
+		onLoad() {
+			this.username = this.userInfo.username;
+			this.mobile = this.userInfo.mobile;
+			this.avatar = this.userInfo.avatar ? this.userInfo.avatar : '';
+		},
+		methods: {
+			...mapMutations(['setUserInfo']),
+			getAvatar(e) {
+				this.avatar = e.detail.userInfo.avatarUrl;
+			},
+			getUserInfo(e) {
+				this.username = e.detail.userInfo.nickName;
+			},
+			async getPhoneNumber(e) {
+				let data = await this.$api.request('/user/decryptData', 'POST', {
+					encryptedData: e.detail.encryptedData,
+					iv: e.detail.iv
+				});
+				if (data) {
+					this.mobile = data.phoneNumber;
+				}
+			},
+			async submit() {
+				if (this.username == '') {
+					this.$api.msg('用户名称不能为空');
+					return;
+				}
+				if (this.username == this.userInfo.username &&
+					this.mobile == this.userInfo.mobile &&
+					this.avatar == this.userInfo.avatar) {
+					this.$api.msg('已修改');
+					return;
+				}
+				let data = await this.$api.request('/user/edit', 'POST', {
+					username: this.username,
+					mobile: this.mobile,
+					avatar: this.avatar
+				});
+				if (data) {
+					let userInfo = this.userInfo;
+					userInfo.username = this.username;
+					userInfo.mobile = this.mobile;
+					userInfo.avatar = this.avatar;
+					this.setUserInfo(userInfo);
+				}
+			}
+		}
+	}
+</script>
+
+<style lang="scss">
+	page {
+		background: $page-color-base;
+	}
+
+	.user-section {
+		display: flex;
+		align-items: center;
+		justify-content: center;
+		height: 460upx;
+		padding: 40upx 30upx 0;
+		position: relative;
+
+		.bg {
+			position: absolute;
+			left: 0;
+			top: 0;
+			width: 100%;
+			height: 100%;
+			filter: blur(1px);
+			opacity: .7;
+		}
+
+		.portrait-box {
+			width: 200upx;
+			height: 200upx;
+			border: 6upx solid #fff;
+			border-radius: 50%;
+			position: relative;
+			z-index: 2;
+
+			.button {
+				position: absolute;
+				width: 100%;
+				height: 100%;
+				top: 0;
+				opacity: 0;
+			}
+		}
+
+		.portrait {
+			position: relative;
+			width: 100%;
+			height: 100%;
+			border-radius: 50%;
+		}
+
+		.yticon {
+			position: absolute;
+			line-height: 1;
+			z-index: 5;
+			font-size: 48upx;
+			color: #fff;
+			padding: 4upx 6upx;
+			border-radius: 6upx;
+			background: rgba(0, 0, 0, .4);
+		}
+
+		.pt-upload-btn {
+			right: 0;
+			bottom: 10upx;
+		}
+
+		.bg-upload-btn {
+			right: 20upx;
+			bottom: 16upx;
+		}
+	}
+
+
+	.yt-list-cell {
+		margin-top: 16rpx;
+		background: #fff;
+		display: flex;
+		align-items: center;
+		padding: 10rpx 30rpx;
+		line-height: 70rpx;
+		position: relative;
+
+		.cell-tit {
+			flex: 1;
+			font-size: 26rpx;
+			color: #000000;
+			margin-right: 10rpx;
+
+			.input {
+				display: inline-block;
+				vertical-align: middle;
+				margin-left: 20rpx;
+				width: 550rpx;
+				float: right;
+				line-height: 70rpx !important;
+				height: 70rpx !important;
+			}
+
+			.username {
+				width: 300rpx;
+				position: absolute;
+				right: 30rpx;
+				z-index: 100;
+			}
+		}
+	}
+</style>

BIN
static/arc.png


BIN
static/emptyCart.jpg


BIN
static/errorImage.jpg


BIN
static/missing-face.png


BIN
static/select.png


BIN
static/selected.png


BIN
static/tab-cart-current.png


BIN
static/tab-cart.png


BIN
static/tab-cate-current.png


BIN
static/tab-cate.png


BIN
static/tab-home-current.png


BIN
static/tab-home.png


BIN
static/tab-my-current.png


BIN
static/tab-my.png


BIN
static/temp/h1.png


BIN
static/temp/secskill-img.jpg


BIN
static/temp/share_moment.png


BIN
static/temp/share_qq.png


BIN
static/temp/share_qqzone.png


BIN
static/temp/share_wechat.png


BIN
static/user-bg.jpg


BIN
static/vip-card-bg.png


BIN
static/yticon.ttf


+ 51 - 0
store/index.js

@@ -0,0 +1,51 @@
+import Vue from 'vue'
+import Vuex from 'vuex'
+
+Vue.use(Vuex)
+
+const store = new Vuex.Store({
+	state: {
+		hasLogin: false,
+		userInfo: {
+			token: ''
+		},
+		lang: 'zh-cn',
+		cookie: ''
+	}, 
+	mutations: {
+		login(state, provider) {
+			state.hasLogin = true;
+			state.userInfo = provider;
+			uni.setStorage({ //缓存用户信息
+			    key: 'userInfo',  
+			    data: provider  
+			});
+		},
+		logout(state) {
+			state.hasLogin = false;
+			state.userInfo = {};
+			uni.removeStorage({  
+                key: 'userInfo'  
+            });
+		},
+		setUserInfo(state, provider) {
+			state.userInfo = provider;
+			uni.setStorage({//缓存用户信息
+			    key: 'userInfo',  
+			    data: provider  
+			});
+		},
+		setCookie(state, provider) {
+			state.cookie = provider;
+			uni.setStorage({
+				key: 'cookieKey',
+				data: provider
+			});
+		}
+	},
+	actions: {
+	
+	}
+})
+
+export default store

+ 53 - 0
template.h5.html

@@ -0,0 +1,53 @@
+<!DOCTYPE html>
+<html lang="zh-CN">
+	<head>
+		<meta charset="utf-8">
+		<meta http-equiv="X-UA-Compatible" content="IE=edge">
+		<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
+		<title>
+			<%= htmlWebpackPlugin.options.title %>
+		</title>
+		<!-- 正式发布的时候使用,开发期间不启用。↓ -->
+        <!-- <script src="/h5/touch-emulator.js"></script>
+		<script>
+            TouchEmulator();
+			if (document.documentElement.clientWidth > 1024) {
+				window.location.href = '/h5/pcguide.html#'+location.pathname+location.search;
+			}
+		</script>
+        <style>
+            ::-webkit-scrollbar{
+                display: none;
+            }
+        </style>
+        <script>
+            var _hmt = _hmt || [];
+            (function() {
+                var hm = document.createElement("script");
+                hm.src = "https://hm.baidu.com/hm.js?";// 百度统计key
+                var s = document.getElementsByTagName("script")[0];
+                s.parentNode.insertBefore(hm, s);
+            })();
+        </script> -->
+        <!-- 正式发布的时候使用,开发期间不启用。↑ -->
+		<script>
+			document.addEventListener('DOMContentLoaded', function() {
+				document.documentElement.style.fontSize = document.documentElement.clientWidth / 20 + 'px'
+			})
+		</script>
+		<link rel="stylesheet" href="<%= BASE_URL %>static/index.css" />
+	</head>
+	<body>
+		<!-- 该文件为 H5 平台的模板 HTML,并非应用入口。 -->
+		<!-- 请勿在此文件编写页面代码或直接运行此文件。 -->
+		<!-- 详见文档:https://uniapp.dcloud.io/collocation/manifest?id=h5-template -->
+		<noscript>
+			<strong>Please enable JavaScript to continue.</strong>
+		</noscript>
+		<div id="app"></div>
+		<!-- built files will be auto injected -->
+		<script>
+			/*BAIDU_STAT*/
+		</script>
+	</body>
+</html>

+ 34 - 0
uni.scss

@@ -0,0 +1,34 @@
+
+/* 页面左右间距 */
+$page-row-spacing: 30upx;
+$page-color-base: #f8f8f8;
+$page-color-light: #f8f6fc;
+$base-color: #fa436a;
+
+/* 文字尺寸 */
+$font-sm: 24upx;
+$font-base: 28upx;
+$font-lg: 32upx;
+/*文字颜色*/
+$font-color-dark: #303133;
+$font-color-base: #606266;
+$font-color-light: #909399;
+$font-color-disabled: #C0C4CC;
+$font-color-spec: #4399fc;
+$font-color-999:#999999;
+
+/* 边框颜色 */
+$border-color-dark: #DCDFE6;
+$border-color-base: #E4E7ED;
+$border-color-light: #EBEEF5;
+/* 图片加载中颜色 */
+$image-bg-color: #eee;
+/* 行为相关颜色 */
+$uni-color-primary:#fa436a;
+$uni-color-success: #4cd964;
+$uni-color-warning: #f0ad4e;
+$uni-color-error: #dd524d;
+/* 背景颜色*/
+$bgcolor_white:#ffffff;
+
+

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