Browse Source

添加特产商城模块

zhuxiuping 2 years ago
parent
commit
66c112ca88
100 changed files with 36069 additions and 13 deletions
  1. 17 3
      App.vue
  2. 14 2
      main.js
  3. 7 7
      package-lock.json
  4. 1 1
      package.json
  5. 441 0
      packageShang/common/common.js
  6. 163 0
      packageShang/common/common.scss
  7. 33 0
      packageShang/common/config.js
  8. 432 0
      packageShang/common/db.js
  9. 2091 0
      packageShang/common/iscroll.js
  10. 185 0
      packageShang/common/poster.js
  11. 90 0
      packageShang/common/request.js
  12. 108 0
      packageShang/common/store.js
  13. 310 0
      packageShang/components/iconfont/iconfont.css
  14. 133 0
      packageShang/components/use-action-sheet/use-action-sheet.vue
  15. 12548 0
      packageShang/components/use-address/city-data/area.js
  16. 1507 0
      packageShang/components/use-address/city-data/city.js
  17. 143 0
      packageShang/components/use-address/city-data/province.js
  18. 420 0
      packageShang/components/use-address/use-address.vue
  19. 245 0
      packageShang/components/use-count-down/use-count-down.vue
  20. 109 0
      packageShang/components/use-empty/use-empty.vue
  21. 86 0
      packageShang/components/use-header/use-header.vue
  22. 154 0
      packageShang/components/use-hot-goods/use-hot-goods.vue
  23. 90 0
      packageShang/components/use-list-title/use-list-title.vue
  24. 46 0
      packageShang/components/use-loadmore/use-loadmore.vue
  25. 157 0
      packageShang/components/use-login/use-login.vue
  26. 76 0
      packageShang/components/use-mask/use-mask.vue
  27. 211 0
      packageShang/components/use-number-box/use-number-box.vue
  28. 4914 0
      packageShang/components/use-pickeraddr/data.js
  29. 105 0
      packageShang/components/use-pickeraddr/use-pickeraddr.vue
  30. 280 0
      packageShang/components/use-popup/use-popup.vue
  31. 1201 0
      packageShang/components/use-qrcode/qrcode.js
  32. 201 0
      packageShang/components/use-qrcode/use-qrcode.vue
  33. 87 0
      packageShang/components/use-rate/use-rate.vue
  34. 120 0
      packageShang/components/use-ring/use-ring.vue
  35. 207 0
      packageShang/components/use-stepper/use-stepper.vue
  36. 71 0
      packageShang/components/use-totop/use-totop.vue
  37. 180 0
      packageShang/components/use-upload/use-upload.vue
  38. 154 0
      packageShang/pages/goods/goods-evaluate.vue
  39. 442 0
      packageShang/pages/goods/goods-list.vue
  40. 911 0
      packageShang/pages/goods/goods.vue
  41. 213 0
      packageShang/pages/home/search/search.vue
  42. 29 0
      packageShang/pages/index/index.vue
  43. 453 0
      packageShang/pages/order/create.vue
  44. 508 0
      packageShang/pages/pay/pay.vue
  45. 46 0
      packageShang/pages/pay/success.vue
  46. 146 0
      packageShang/pages/tabbar.vue
  47. 563 0
      packageShang/pages/tabbar/cart.vue
  48. 357 0
      packageShang/pages/tabbar/category.vue
  49. 340 0
      packageShang/pages/tabbar/home.vue
  50. 488 0
      packageShang/pages/tabbar/user.vue
  51. 381 0
      packageShang/pages/user/address/address-edit.vue
  52. 244 0
      packageShang/pages/user/address/address.vue
  53. 216 0
      packageShang/pages/user/collect/collect.vue
  54. 48 0
      packageShang/pages/user/integral/detail.vue
  55. 119 0
      packageShang/pages/user/integral/sign.vue
  56. 563 0
      packageShang/pages/user/order/order-detail.vue
  57. 240 0
      packageShang/pages/user/order/order-evaluate.vue
  58. 160 0
      packageShang/pages/user/order/order-express.vue
  59. 296 0
      packageShang/pages/user/order/order-refund.vue
  60. 658 0
      packageShang/pages/user/order/order.vue
  61. 20 0
      packageShang/static/customicons.css
  62. BIN
      packageShang/static/customicons.ttf
  63. BIN
      packageShang/static/images/empty/cart.jpg
  64. BIN
      packageShang/static/images/empty/empty.jpg
  65. BIN
      packageShang/static/images/empty/search.jpg
  66. BIN
      packageShang/static/images/home/banner.png
  67. BIN
      packageShang/static/images/home/baobao.png
  68. BIN
      packageShang/static/images/home/fushi.png
  69. BIN
      packageShang/static/images/home/huiyuan.png
  70. BIN
      packageShang/static/images/home/jiadian.png
  71. BIN
      packageShang/static/images/home/jiaju.png
  72. BIN
      packageShang/static/images/home/phone.png
  73. BIN
      packageShang/static/images/home/shucai.png
  74. BIN
      packageShang/static/images/home/xiezi.png
  75. BIN
      packageShang/static/images/home/xihu.png
  76. BIN
      packageShang/static/images/home/youhui.png
  77. BIN
      packageShang/static/images/logo.png
  78. BIN
      packageShang/static/images/right_icon.png
  79. BIN
      packageShang/static/images/tabbar/cart-active.png
  80. BIN
      packageShang/static/images/tabbar/cart.png
  81. BIN
      packageShang/static/images/tabbar/category-active.png
  82. BIN
      packageShang/static/images/tabbar/category.png
  83. BIN
      packageShang/static/images/tabbar/home-active.png
  84. BIN
      packageShang/static/images/tabbar/home.png
  85. BIN
      packageShang/static/images/tabbar/shopping-active.png
  86. BIN
      packageShang/static/images/tabbar/shopping.png
  87. BIN
      packageShang/static/images/tabbar/user-active.png
  88. BIN
      packageShang/static/images/tabbar/user.png
  89. BIN
      packageShang/static/images/user/VIP.png
  90. BIN
      packageShang/static/images/user/default.png
  91. BIN
      packageShang/static/images/user/user-bg.png
  92. BIN
      packageShang/static/images/客服.png
  93. BIN
      packageShang/static/logo.png
  94. BIN
      packageShang/static/uni.png
  95. 11 0
      packageShang/uni_modules/lime-painter/changelog.md
  96. 57 0
      packageShang/uni_modules/lime-painter/components/lime-painter/canvas.js
  97. 790 0
      packageShang/uni_modules/lime-painter/components/lime-painter/draw.js
  98. 107 0
      packageShang/uni_modules/lime-painter/components/lime-painter/gradient.js
  99. 326 0
      packageShang/uni_modules/lime-painter/components/lime-painter/index.vue
  100. 0 0
      packageShang/uni_modules/lime-painter/components/lime-painter/layout.js

+ 17 - 3
App.vue

@@ -1,14 +1,28 @@
 <script>
 <script>
-export default {
+export default {
+	globalData: {
+		baseurl: 'https://www.daweilinli.com/cloud-mall',
+	}, 
 	onLaunch: function () {},
 	onLaunch: function () {},
 	onShow: function () {},
 	onShow: function () {},
 	onHide: function () {}
 	onHide: function () {}
 }
 }
 </script>
 </script>
 
 
-<style>
+<style>
+	@import url("/packageShang/components/iconfont/iconfont.css");
+	@import url("/packageShang/common/common.scss");
 /*每个页面公共css */
 /*每个页面公共css */
 page {
 page {
 	background: rgba(234, 234, 234, 1);
 	background: rgba(234, 234, 234, 1);
-}
+}
+@font-face {
+	  font-family: 'iconfont';  /* project id 2092181 */
+	  src: url('https://at.alicdn.com/t/font_2092181_f2k93z2wkgk.eot');
+	  src: url('https://at.alicdn.com/t/font_2092181_f2k93z2wkgk.eot?#iefix') format('embedded-opentype'),
+	  url('https://at.alicdn.com/t/font_2092181_f2k93z2wkgk.woff2') format('woff2'),
+	  url('https://at.alicdn.com/t/font_2092181_f2k93z2wkgk.woff') format('woff'),
+	  url('https://at.alicdn.com/t/font_2092181_f2k93z2wkgk.ttf') format('truetype'),
+	  url('https://at.alicdn.com/t/font_2092181_f2k93z2wkgk.svg#iconfont') format('svg');
+	}
 </style>
 </style>

+ 14 - 2
main.js

@@ -1,7 +1,11 @@
 import App from './App'
 import App from './App'
 
 
 // #ifndef VUE3
 // #ifndef VUE3
-import Vue from 'vue'
+import Vue from 'vue'
+import $config from './packageShang/common/config.js'
+
+import $store from './packageShang/common/store.js'
+import $api from './packageShang/common/common.js'
 import './uni.promisify.adaptor'
 import './uni.promisify.adaptor'
 
 
 import {
 import {
@@ -12,7 +16,15 @@ import {
 Vue.prototype.$myRequest = myRequest
 Vue.prototype.$myRequest = myRequest
 Vue.prototype.$myRequest_shop = myRequest_shop
 Vue.prototype.$myRequest_shop = myRequest_shop
 
 
-Vue.config.productionTip = false
+Vue.config.productionTip = false
+Vue.prototype.$config = $config
+Vue.prototype.$api = $api
+Vue.prototype.$store = $store
+
+// 注册当前环境 
+$api.register_env((env) => {
+	Vue.prototype.$env = env;
+});
 App.mpType = 'app'
 App.mpType = 'app'
 const app = new Vue({
 const app = new Vue({
 	...App
 	...App

+ 7 - 7
package-lock.json

@@ -5,20 +5,20 @@
   "packages": {
   "packages": {
     "": {
     "": {
       "dependencies": {
       "dependencies": {
-        "dayjs": "^1.11.9"
+        "dayjs": "^1.11.10"
       }
       }
     },
     },
     "node_modules/dayjs": {
     "node_modules/dayjs": {
-      "version": "1.11.9",
-      "resolved": "https://registry.npmmirror.com/dayjs/-/dayjs-1.11.9.tgz",
-      "integrity": "sha512-QvzAURSbQ0pKdIye2txOzNaHmxtUBXerpY0FJsFXUMKbIZeFm5ht1LS/jFsrncjnmtv8HsG0W2g6c0zUjZWmpA=="
+      "version": "1.11.10",
+      "resolved": "https://registry.npmmirror.com/dayjs/-/dayjs-1.11.10.tgz",
+      "integrity": "sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ=="
     }
     }
   },
   },
   "dependencies": {
   "dependencies": {
     "dayjs": {
     "dayjs": {
-      "version": "1.11.9",
-      "resolved": "https://registry.npmmirror.com/dayjs/-/dayjs-1.11.9.tgz",
-      "integrity": "sha512-QvzAURSbQ0pKdIye2txOzNaHmxtUBXerpY0FJsFXUMKbIZeFm5ht1LS/jFsrncjnmtv8HsG0W2g6c0zUjZWmpA=="
+      "version": "1.11.10",
+      "resolved": "https://registry.npmmirror.com/dayjs/-/dayjs-1.11.10.tgz",
+      "integrity": "sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ=="
     }
     }
   }
   }
 }
 }

+ 1 - 1
package.json

@@ -1,5 +1,5 @@
 {
 {
   "dependencies": {
   "dependencies": {
-    "dayjs": "^1.11.9"
+    "dayjs": "^1.11.10"
   }
   }
 }
 }

+ 441 - 0
packageShang/common/common.js

@@ -0,0 +1,441 @@
+'use strict';
+
+import $config from './config.js'
+
+class Common {
+	constructor(arg) {
+
+	}
+
+	/**
+	 * @description 日期格式化
+	 */
+	format(date, fmt) {
+		if (typeof date === 'string') {
+			date = date.replace(/\.|\-/g, '/');
+		}
+		if (typeof date !== 'object') {
+			date = new Date(date);
+		}
+		
+		fmt = fmt || 'yyyy-MM-dd hh:mm:ss'
+		let o = {
+			"M+": date.getMonth() + 1, //月份   
+			"d+": date.getDate(), //日   
+			"h+": date.getHours(), //小时   
+			"m+": date.getMinutes(), //分   
+			"s+": date.getSeconds(), //秒
+			"q+": Math.floor((date.getMonth() + 3) / 3), //季度
+			"S": date.getMilliseconds() //毫秒
+		};
+		if (/(y+)/.test(fmt))
+			fmt = fmt.replace(RegExp.$1, (date.getFullYear() + "").substr(4 - RegExp.$1.length));
+		for (let k in o)
+			if (new RegExp("(" + k + ")").test(fmt))
+				fmt = fmt.replace(RegExp.$1, (RegExp.$1.length == 1) ? (o[k]) : (("00" + o[k]).substr(("" + o[k]).length)));
+		return fmt;
+	}
+
+	format_price(_number, _sep) {
+		_number = typeof _number != "undefined" && _number > 0 ? _number + '' : "";
+		if (_number.indexOf('.') != -1) {
+			_number = _number.split('.')[0];
+		}
+		_number = _number.replace(new RegExp("^(\\d{" + (_number.length % 3 ? _number.length % 3 : 0) + "})(\\d{3})", "g"),
+			"$1 $2").replace(/(\d{3})+?/gi, "$1 ").trim()
+		if (typeof _sep != "undefined" && _sep != " ") {
+			_number = _number.replace(/\s/g, _sep);
+		}
+		return _number;
+	}
+
+	get_price_decimal(_price) {
+		_price = _price + '';
+
+		if (_price.indexOf('.') != -1) {
+			return '.' + _price.split('.')[1];
+		} else {
+			_price = Math.random(2).toFixed(2);
+		}
+
+		return this.get_price_decimal(_price);
+	}
+
+	/**
+	 * @description 全球唯一 guid
+	 */
+	guid() {
+		return (this.__s4() + this.__s4() + "-" + this.__s4() + "-" + this.__s4() + "-" + this.__s4() + "-" + this.__s4() +
+			this.__s4() + this.__s4());
+	}
+	__s4() {
+		return (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1);
+	}
+
+	/**
+	 * @description 消息提示 toast
+	 */
+	msg(title, duration = 1500, mask = false, icon = 'none') {
+		if (!title) return;
+
+		uni.showToast({
+			title,
+			duration,
+			mask,
+			icon
+		});
+	}
+	
+	/**
+	 * @description 消息提示 alert
+	 */
+	alert(content, callback) {
+		
+		uni.showModal({
+			title: '提示',
+			content: content,
+			showCancel: false,
+			success(res) {
+				if (res.confirm) {
+					if(typeof callback === 'function') {
+						callback();
+					}
+		
+				} else if (res.cancel) {
+				}
+			}
+		})
+	}
+
+	/**
+	 * @description 获取路径参数
+	 */
+	get_params(data) {
+		let params = '';
+		if (typeof data === 'object') {
+			for (let _ in data) {
+				params += '&' + encodeURIComponent(_) + '=' + encodeURIComponent(data[_]);
+			}
+			params = '?' + params.slice(1);
+		} else if (typeof data === 'string') {
+			if (data.indexOf('?') != -1) params = data;
+			else params = '?' + data;
+		} else {
+			params = data;
+		}
+
+		return params;
+	}
+
+	/**
+	 * @description 去除两端空格
+	 * */
+	trim(str) {
+		return str.replace(/\s+/g, "");
+	}
+
+	/**
+	 * @description 注册当前环境
+	 * */
+	register_env(callback) {
+
+		const __timer = setTimeout(() => {
+			uni.getSystemInfo({
+				success(sis) {
+					let env = {
+						platform: ''
+					};
+
+					if (uni.canIUse('getAccountInfoSync')) {
+						{
+							const accountInfo = uni.getAccountInfoSync();
+							if (accountInfo && accountInfo.miniProgram) {
+								env.appid = accountInfo.miniProgram.appId;
+							}
+						}
+					}
+
+					env.brand = sis.brand;
+					env.language = sis.language;
+					env.model = sis.model;
+					env.platform = sis.platform;
+					env.screenHeight = sis.screenHeight;
+					env.screenWidth = sis.screenWidth;
+					env.statusBarHeight = sis.statusBarHeight;
+					env.system = sis.system;
+					env.version = sis.version;
+					env.windowHeight = sis.windowHeight;
+					env.windowWidth = sis.windowWidth;
+					env.pixelRatio = sis.pixelRatio;
+					env.pixelRatio = sis.pixelRatio;
+					env.mobileType = sis.platform;
+					env.is_mp = false;
+					env.is_wx = false;
+					env.is_h5 = false;
+					env.is_app = false;
+
+					if (uni.canIUse('SDKVersion')) {
+						env.sdkversion = my.SDKVersion;
+					}
+
+					// #ifdef MP-WEIXIN
+					env.platform = "weixin";
+					env.platform_name = "微信";
+					env.is_mp = true;
+					env.is_wx = true;
+					// #endif
+					// #ifdef MP-QQ
+					env.platform = "qq";
+					env.platform_name = "QQ";
+					env.is_mp = true;
+					env.app_name = sis.AppPlatform;
+					// #endif
+					// #ifdef MP-ALIPAY
+					env.platform = "alipay";
+					env.platform_name = "支付宝";
+					env.is_mp = true;
+					env.app_name = sis.app;
+					// #endif
+					// #ifdef MP-BAIDU
+					env.platform = "baidu";
+					env.platform_name = "百度";
+					env.is_mp = true;
+					env.app_name = sis.host;
+					// #endif
+					// #ifdef MP-TOUTIAO
+					env.platform = "toutiao";
+					env.platform_name = "头条";
+					env.is_mp = true;
+					env.app_name = sis.appName;
+					// #endif					
+					// #ifdef MP-360
+					env.platform = "360";
+					env.platform_name = "360";
+					env.is_h5 = true;
+					env.app_name = "360小程序";
+					// #endif
+					// #ifdef H5-WX
+					env.platform = "h5-wx";
+					env.platform_name = "微信服务号";
+					env.is_h5 = true;
+					env.is_wx = true;
+					// #endif
+					// #ifdef H5
+					env.platform = "h5";
+					env.platform_name = "h5";
+					env.is_h5 = true;
+					// #endif
+					// #ifdef APP-VUE || APP-NVUE || APP-PLUS || APP-PLUS-NVUE
+					env.platform = "app";
+					env.platform_name = "app";
+					env.is_mp = true;
+					// #endif
+
+					env.platform_icon = "icon" + env.platform;
+					env.sis = sis;
+
+					uni.getNetworkType({
+						success(res) {
+							console.log(res.networkType);
+							env.networkType = res.networkType
+						},
+						complete() {
+							console.log('set storage env', env);
+							uni.setStorage({
+								key: '__env',
+								data: env
+							})
+
+							if (typeof callback === 'function') {
+								callback(env);
+							}
+						}
+					});
+				}
+			});
+
+			clearTimeout(__timer);
+		}, 1);
+
+	}
+
+	/**
+	 * @description 运行环境
+	 * */
+	get_env(callback) {
+		// return uni.getStorageSync('env');
+		uni.getStorage({
+			key: '__env',
+			success(res) {
+				if (typeof callback === 'function') {
+					callback(res.data || {});
+				}
+			}
+		})
+	}
+
+	dom(a, b) {
+		if (arguments.length === 1 && typeof arguments[0] == 'string') {
+			if (document.querySelector) {
+				return document.querySelector(arguments[0])
+			}
+		} else if (arguments.length === 2) {
+			if (typeof a === 'string')
+				a = this.dom(a);
+			if (a.querySelector) {
+				return a.querySelector(b)
+			}
+		}
+		return a
+	}
+
+	domAll(a, b) {
+		if (arguments.length === 1 && typeof arguments[0] == 'string') {
+			if (document.querySelectorAll) {
+				return document.querySelectorAll(arguments[0])
+			}
+		} else if (arguments.length === 2) {
+			if (typeof a === 'string')
+				a = this.dom(a);
+			if (a.querySelectorAll) {
+				return a.querySelectorAll(b)
+			}
+		}
+		return a
+	}
+
+	/**
+	 * @description 打印 info 日志
+	 * */
+	info(msg) {
+		console.info(msg)
+	}
+
+	/**
+	 * @description 当前页面数组
+	 * */
+	pages() {
+		return getCurrentPages();
+	}
+
+	/**
+	 * @description 返回上一级页面|跳转首页
+	 * */
+	back() {
+		if (getCurrentPages().length > 1) {
+			uni.navigateBack({})
+			return
+		}
+
+		this.tohome();
+	}
+
+	/**
+	 * @description 指定元素选择器 offset
+	 */
+	offset(selector, callback) {
+		let query = uni.createSelectorQuery().select(selector);
+		// console.log('offset query', query);
+		if (typeof callback === 'function') {
+			query.boundingClientRect((res) => {
+				callback(res);
+			});
+		}
+	}
+
+	/**
+	 * @description 超时
+	 */
+	timerout(callback, timer = 1000) {
+		let _timer = setTimeout(() => {
+			if (typeof callback === 'function') {
+				callback();
+			}
+			clearTimeout(_timer);
+		}, timer);
+	}
+
+	/**
+	 * @description 跳转登录页
+	 */
+	tologin() {
+		uni.navigateTo({
+			url: $config.route.login
+		});
+	}
+
+	/**
+	 * @description 跳转首页
+	 */
+	tohome() {
+		uni.navigateTo({
+			url: $config.route.home
+		});
+	}
+
+	/**
+	 * @description 跳转订单页
+	 */
+	toorder() {
+		uni.redirectTo({
+			url: $config.route.order
+		});
+	}
+
+	/**
+	 * @description 跳转支付页
+	 */
+	topay(params) {
+		params.money = params.money || 0;
+		params.type = params.type || 'navigate';
+
+		if (params.type == 'redirect') {
+			uni.redirectTo({
+				url: $config.route.pay + this.get_params(params)
+			});
+			return;
+		}
+
+		uni.navigateTo({
+			url: $config.route.pay + this.get_params(params)
+		});
+	}
+
+	/**
+	 * @description 跳转搜索页
+	 */
+	tosearch() {
+		uni.reLaunch({
+			url: $config.route.search
+		});
+	}
+
+	/**
+	 * @description 跳转产品详情页
+	 */
+	togoods(params) {
+		uni.navigateTo({
+			url: $config.route.goods + this.get_params(params)
+		});
+	}
+
+	/**
+	 * @description 跳转产品列表页
+	 */
+	togoodslist(params) {
+		uni.navigateTo({
+			url: $config.route.goodslist + this.get_params(params)
+		});
+	}
+	
+	/**
+	 * @description 获取路径文件名称
+	 */
+	getFileName(path) {
+		if (path.indexOf('/') === -1) return '';
+		
+		return path.split('/').reverse()[0];
+	}
+}
+
+export default new Common()

+ 163 - 0
packageShang/common/common.scss

@@ -0,0 +1,163 @@
+
+/* ==================
+        初始化
+ ==================== */
+body {
+	background-color: #fff;
+	font-size: 28rpx;
+	color: #333;
+	line-height: 1.6;
+	font-family: "mp-quote", -apple-system-font, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;
+}
+
+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;}
+switch { transform: translateX(16rpx) scale(0.9); }
+
+/**
+ * 页面公共样式
+ **/
+input{height: inherit;}
+uni-button, button {font-weight: normal;}
+uni-button.no-border:before, uni-button.no-border:after, button.no-border:before, button.no-border:after {border: 0 none;}
+button{border: 0 none;}
+button.btn {background-color: transparent;padding-left: 0;padding-right: 0; height: inherit; line-height:inherit;}
+
+view[class*='-area']{ }
+view[class*='-round-area']{ border-radius: 20rpx; }
+
+.safe-area-inset-bottom {padding-bottom: 0; padding-bottom: constant(safe-area-inset-bottom);padding-bottom: env(safe-area-inset-bottom);} 
+.use-page { min-height: 100%; width: 100%; background: #fff; } .use-hover-class { opacity: 0.6; }
+
+/**
+ * 组件公共样式
+ **/
+.price{font-size: 36rpx; color: rgba(36, 147, 241, 1); line-height: 1; font-weight: 580;}.m-price{font-size: 24rpx;text-decoration: line-through;color: #909399;margin-left: 20rpx;}
+.price::before{ content: '¥'; font-size: 24rpx; } .m-price::before{ content: '¥'; font-size: 24rpx; }
+.price::after{ content: attr(data-decimal); font-size: 24rpx; }
+.price-add::before { content: '+'; font-weight: 600; }
+.price-sub::before { content: '-'; font-weight: 600; }
+.badge{ position: absolute; top: 0; right: 0;background: rgba(36, 147, 241, 1); color: #fff;font-size: 12px;	line-height: 1;display: inline-block;padding: 3px 6px;border-radius: 50px;font-weight: normal !important;}
+.badge-small{transform: scale(0.8);transform-origin: center center;}
+
+.dn{ display: none !important; }
+.diblock{ display: inline-block; }
+/* flex 布局 */
+.dflex{display: flex; -webkit-align-items: center; align-items: center;}
+.dflex-s{display: flex; -webkit-align-items: flex-start; align-items: flex-start;}
+.dflex-e{display: flex; justify-content: flex-end; align-items: baseline;}
+.dflex-c{display: flex; justify-content: center; align-items: center;}
+.dflex-a{display: flex; justify-content: space-around; align-items: center;}
+.dflex-b{display: flex; justify-content: space-between; align-items: center;}
+.dflex-flow-c{flex-flow: column;}
+.dflex-wrap-w{-ms-flex-wrap: wrap; -webkit-flex-wrap: wrap; flex-wrap: wrap;}
+.flex1{flex: 1;}
+
+.box-sizing-b { box-sizing: border-box; } .box-sizing-c { box-sizing: content-box; }
+
+/* 间隔槽 */ 
+.gap{display: block;width: 100%;height: 20rpx;background: #f5f5f5;}
+/* 垂直线 */ 
+.vertical-line{ border-left:1px solid #ededed; height: 30%; position:absolute; top: 50%; transform: translate(0, -50%); right: 0; }
+/* 下边框线 */
+.border-bottom{border-bottom: 1px solid #f5f5f5;}
+
+/* 文字超出省略 */ 
+.clamp{overflow: hidden;text-overflow: ellipsis;white-space: nowrap;display: block;width: 100%;}
+.clamp-2{display: -webkit-box;overflow: hidden;text-overflow: ellipsis;word-wrap: break-word;white-space: normal !important;-webkit-line-clamp: 2;-webkit-box-orient: vertical; line-height: 36rpx;}
+
+.ws-np {white-space: nowrap;}
+.line-height-1 {line-height: 1;}
+/* 文字对齐 */ 
+.tac{text-align: center;}.tar{text-align: right;}.tal{text-align: left;}
+
+/* 相对定位|绝对定位|固定定位 */ 
+.fixed{position: fixed;z-index: 1;}
+.pos-r{position: relative;}.pos-f{position: fixed;z-index: 1;}.pos-a{position: absolute;}
+.pos-t-c { top: 50%; transform: translateY(-50%); }
+.pos-l-c { left: 50%; transform: translateX(-50%); }
+.pos-tl-c { top: 50%; left: 50%; transform: translate(-50%, -50%); }
+.pos-top {top: 0} .pos-right {right: 0} .pos-bottom {bottom: 0} .pos-left {left: 0} .pos-full{top:0; right: 0; bottom: 0; left: 0;}
+
+/* 圆角 */ 
+.border-radius-big{border-radius: 100rpx;}.border-radius-lg{border-radius: 50rpx;}.border-radius{border-radius: 20rpx;}.border-radius-xs{border-radius: 6rpx;}.border-radius-sm{border-radius: 10rpx;}.border-radius-c{border-radius: 50%;}
+view[class*='border-radius']{ overflow: hidden;}
+
+.fixed-top { width: 100rpx;height: 100rpx;right: 30rpx;bottom: 30rpx;background: #fff;box-shadow: 0px 0px 7px 3px #f0f0f0;border-radius: 50%;display: flex;align-items: center;justify-content: center;position: fixed;z-index: 9;}
+.fixed-top .iconfont {font-size: 44rpx;}
+/* 底部操作区 */ 
+.fixed-oper-area {position: fixed;left: 0;bottom: 0;width: 100%;height: 100rpx;z-index: 998;font-size: 36rpx;
+	box-shadow: 0 -1px 5px rgba(0, 0, 0, 0.1);
+}
+
+/* 宽高 */
+.wh-full{width: 100%;height: 100%;}.h-full{height: 100%;}.w-full{width: 100%;}
+
+/* 背景色 */
+.bg-main { background: #fff; } .bg-drak { background: #f5f5f5; } .bg-base{ background-color: rgba(255, 255, 255, 1); color: rgba(36, 147, 241, 1);width: 158rpx;height: 71rpx;line-height: 71rpx;font-size: 24rpx;margin:-60rpx 0 0 562rpx; } 
+.bg-base-sou{background-color:rgba(36, 147, 241, 1) ; color: rgba(255, 255, 255, 1);width: 158rpx;height: 71rpx;font-size: 24rpx;text-align: center;line-height: 71rpx;}
+.bg-base-tuikuan{ background-color: rgba(36, 147, 241, 1); color: rgba(255, 255, 255, 1);width: 158rpx;height: 71rpx;font-size: 24rpx; }
+.bg-base-succ{background-color: rgba(255, 255, 255, 1); color: rgba(36, 147, 241, 1);width: 158rpx;height: 71rpx;font-size: 24rpx;}
+.bg-base-ping{background-color: rgba(255, 255, 255, 1); color: rgba(36, 147, 241, 1);width: 158rpx;height: 71rpx;font-size: 24rpx;}
+.bg-base-pay{background-color: rgba(36, 147, 241, 1); color: rgba(255, 255, 255, 1);width: 158rpx;height: 71rpx;font-size: 24rpx;}
+.bg-base-address{ background-color: rgba(36, 147, 241, 1); color: rgba(255, 255, 255, 1);width: 158rpx; } 
+.bg-base2{ background-color: rgba(36, 147, 241, 1); color: #fff; }
+.bg-base-zhi{background-color: rgba(36, 147, 241, 1);}
+.bg-base-a-tijiao{background-color: rgba(255, 255, 255, 1); color: rgba(36, 147, 241, 1);width: 158rpx;height: 71rpx;}
+.bg-base-ding{background-color: rgba(36, 147, 241, 1); color:rgba(255, 255, 255, 1) ;width: 158rpx;height: 71rpx;}
+.bg-warn{background: #ffbc49; color: #333;} .bg-disabled {background: #adadad !important; color: rgba(36, 147, 241, 1) !important;}
+.ft-main { color: #333; } .ft-base { color: rgba(36, 147, 241, 1); } .ft-dark { color: #bbb; } .ft-black{ color: #333; } .ft-white { color: #fff; }
+.fs-xxs { font-size: 22rpx; } .fs-xs { font-size: 24rpx; } .fs-sm { font-size: 28rpx; } .fs { font-size: 32rpx; } .fs-lg { font-size: 36rpx; } .fs-xl { font-size: 40rpx; } .fs-xxl { font-size: 50rpx; }  .fs-xxxl { font-size: 60rpx; } .fs-big { font-size: 52rpx; }
+.fs-20 {font-size: 20rpx;}.fs-30 {font-size: 30rpx;} .fs-34 { font-size: 34rpx; }
+.fwb{font-weight: 580;}.fwbd{ font-weight: bold; }.active{ color: rgba(36, 147, 241, 1) !important; } .disabled{ color: #bbb !important; }
+.border-line{border-bottom: 1px solid #f7f7f7;}
+
+.image-sm {width: 100rpx; height: 100rpx;} .overflow-hidden{overflow: hidden;}
+.headimg {border: 1px solid #f5f5f5;margin: 194rpx 0 0 30rpx;}
+
+/* 外边距 */
+.margin-0{margin: 0;}.margin-xs{margin: 10rpx;}.margin-sm{margin: 0rpx;}.margin{margin: 30rpx;}.margin-lg{margin: 40rpx;}.margin-xl{margin: 50rpx;}.margin-big{margin: 100rpx;}
+.margin-top-xs{margin-top: 10rpx;}.margin-top-sm{margin-top: 20rpx;}.margin-top{margin-top: 30rpx;}.margin-top-lg{margin-top: 40rpx;}.margin-top-xl{margin-top: 50rpx;}.margin-top-big{margin-top: 100rpx;}
+.margin-right-xs{margin-right: 10rpx;}.margin-right-sm{margin-right: 20rpx;}.margin-right{margin-right: 30rpx;}.margin-right-lg{margin-right: 40rpx;}.margin-right-xl{margin-right: 50rpx;}
+.margin-bottom-xs{margin-bottom: 10rpx;border-radius: 50%;}.margin-bottom-sm{margin-bottom: 20rpx;}.margin-bottom{margin-bottom: 30rpx;}.margin-bottom-lg{margin-bottom: 40rpx;}.margin-bottom-xl{margin-bottom: 50rpx;} .margin-bottom-big {margin-bottom: 100rpx;}
+.margin-left-xs{margin-left: 10rpx;}.margin-left-sm{margin-left: 15rpx;}.margin-left-kai{margin-left: 50rpx;}.margin-left-user{margin: -110rpx 0 0 170rpx;font-size: 40rpx;color: rgba(255, 255, 255, 1);}.margin-left{margin-left: 30rpx;}.margin-left-lg{margin-left: 40rpx;}.margin-left-xl{margin-left: 50rpx;}
+
+.margin-lr-xs{margin-left: 10rpx;margin-right: 10rpx;}.margin-lr-sm{margin-left: 20rpx;margin-right: 20rpx;}.margin-lr{margin-left: 30rpx;margin-right: 30rpx;}.margin-lr-lg{margin-left: 40rpx;margin-right: 40rpx;}.margin-lr-xl{margin-left: 50rpx;margin-right: 50rpx;}
+.margin-tb-xs{margin-top: 10rpx;margin-bottom: 10rpx;}.margin-tb-sm{margin-top: 20rpx;margin-bottom: 20rpx;}.margin-tb{margin-top: 30rpx;margin-bottom: 30rpx;}.margin-tb-lg{margin-top: 40rpx;margin-bottom: 40rpx;}.margin-tb-xl{margin-top: 50rpx;margin-bottom: 50rpx;}
+
+/* 内边距 */
+.padding-0{padding: 0;}.padding-xs{padding: 10rpx;}.padding-sm{padding: 20rpx;}.padding{padding: 30rpx;}.padding-lg{padding: 40rpx;}.padding-xl{padding: 50rpx;}
+.padding-top-xs{padding-top: 10rpx;}.padding-top-sm{padding-top: 20rpx;}.padding-top-use{padding-top: 0rpx;}.padding-top{padding-top: 30rpx;}.padding-top-lg{padding-top: 40rpx;}.padding-top-xl{padding-top: 0rpx;}.padding-top-big{padding-top: 0rpx;}
+.padding-right-xs{padding-right: 10rpx;}.padding-right-sm{padding-right: 20rpx;}.padding-right{padding-right: 30rpx;}.padding-right-lg{padding-right: 40rpx;}.padding-right-xl{padding-right: 50rpx;}.padding-right-0{padding-right: 0;}
+.padding-bottom-xs{padding-bottom: 10rpx;}.padding-bottom-sm{padding-bottom: 20rpx;}.padding-bottom{padding-bottom: 30rpx;}.padding-bottom-lg{padding-bottom: 40rpx;}.padding-bottom-xl{padding-bottom: 50rpx;}.padding-bottom-big{padding-bottom: 100rpx;}
+.padding-left-xs{padding-left: 10rpx;}.padding-left-sm{padding-left: 20rpx;}.padding-left{padding-left: 30rpx;}.padding-left-lg{padding-left: 40rpx;}.padding-left-xl{padding-left: 50rpx;}
+
+.padding-lr-xs{padding-left: 10rpx;padding-right: 10rpx;}.padding-lr-sm{padding-left: 20rpx;padding-right: 20rpx;}.padding-lr{padding-left: 30rpx;padding-right: 30rpx;}.padding-lr-lg{padding-left: 40rpx;padding-right: 40rpx;}.padding-lr-xl{padding-left: 50rpx;padding-right: 50rpx;}
+.padding-lr-16{padding-left: 16rpx;padding-right: 16rpx;}
+.padding-tb-xs{padding-top: 10rpx;padding-bottom: 10rpx;}.padding-tb-sm{padding-top: 20rpx;padding-bottom: 20rpx;}.padding-tb{padding-top: 30rpx;padding-bottom: 30rpx;}.padding-tb-lg{padding-top: 40rpx;padding-bottom: 40rpx;}.padding-tb-xl{padding-top: 50rpx;padding-bottom: 50rpx;}
+.padding-tb-16{padding-top: 16rpx;padding-bottom: 16rpx;}
+.animated{ -webkit-animation-duration: .55s; animation-duration: .55s; -webkit-animation-fill-mode: both;  animation-fill-mode: both; }
+.animated-all { transition: all .5s; }
+
+.animated.rotate { animation: rotate .75s linear infinite; transform-origin: center center; }
+@keyframes rotate { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }
+
+.animated.fade-in { animation: fadein .75s linear;}
+@keyframes fadein { from { opacity: 0; } to { opacity: 1; } }
+.animated.fade-out { animation-name: fadeout; }
+@keyframes fadeout { from { opacity: 1; } to { opacity: 0; } }
+
+/* uni css */
+.uni-page-head-btn {
+	display: flex;
+	align-items: center;
+	justify-content: center;
+}
+
+/* #ifdef H5 || MP-360 */
+::-webkit-scrollbar {
+	width: 0;
+	height: 0;
+	color: transparent;
+	display: none;
+}
+/* #endif */

+ 33 - 0
packageShang/common/config.js

@@ -0,0 +1,33 @@
+'use strict';
+
+let config = {
+	debug: !0,
+	version: '1.0.9.230428',
+	
+	route: {
+		home: '/packageShang/pages/tabbar/home',
+		login: '/pages/login/login',
+		order: '/packageShang/pages/user/order/order',
+		pay: '/packageShang/pages/pay/pay',
+		search: '/packageShang/pages/home/search/search',
+		goods: '/packageShang/pages/goods/goods',
+		goodslist: '/packageShang/pages/goods/goods-list'
+	},
+	provider: '',
+	provider_names: {
+		'weixin': '微信',
+		'qq': 'QQ',
+		'alipay': '支付宝',
+		'baidu': '百度',
+		'toutiao': '头条',
+	},
+	
+	const: {
+		__app: '__app',
+		__member: '__member',
+		__access_token: 'USE_ACCESS_TOKEN'
+	},
+	
+};
+
+export default config

+ 432 - 0
packageShang/common/db.js

@@ -0,0 +1,432 @@
+import $config from './config.js'
+import $api from './common.js'
+
+let db = {};
+let unidb = uniCloud.database();
+
+
+db = new Proxy(db, {
+	get: function(target, key) {
+		return new DbContext(key);
+	}
+});
+
+function DbContext(name) {
+	this.table = name;
+	this.where_obj = {};
+	this.where_arr = [];
+	this.where_str = '';
+	
+	this.istemp = false;
+	this.temp = null;
+	
+	this.configDefault = {
+		hideLoading: true
+	};
+	this.response = {
+		code: 200,
+		datas: {},
+		msg: ''
+	}
+}
+
+DbContext.prototype = {
+	table: '', // 数据集合|表名
+	where_obj: {}, // where 条件
+	where_arr: [], // where 条件
+	where_str: '', // where 条件
+	istemp: false,
+	temp: null,
+	response: {}, // 统一响应格式
+	configDefault: {}	// 配置文件
+};
+
+DbContext.prototype.config = function(config) {
+	// console.log('config', config)
+	if (typeof config === 'object') {
+		Object.assign(this.configDefault, config);
+	}
+	
+	return this
+}
+DbContext.prototype.where = function(params) {
+	// console.log('params', params)
+	if (typeof params === 'object') {
+		Object.assign(this.where_obj, params);
+	}
+	if (typeof params === 'string') {
+		this.where_arr.push(params);
+	}
+	return this
+}
+DbContext.prototype.whereif = function(_flag, _obj) {
+	if (_flag) this.where(_obj);
+	return this;
+}
+
+DbContext.prototype.collection = function() {
+	return unidb.collection(this.table);
+}
+
+DbContext.prototype.add = function(obj) {
+	uni.showLoading({
+		title: '请求中'
+	})
+	return this.collection()
+		.add(obj)
+		.then(res => {
+			if ($config.debug) {
+				console.log(this.table + ' add', res);
+			}
+			if (res.result) {
+				this.response.datas = res.result;
+				if (res.result.errCode === 0) this.response.code = 200;
+
+				return Promise.resolve(this.response);
+			}
+
+			return Promise.reject(res);
+		})
+		.catch(err => {
+			if ($config.debug) {
+				console.log(this.table + ' add catch', err);
+				$api.alert(err.message);
+			}
+			return Promise.reject(err);
+		})
+		.finally(res => {
+			this.configDefault.hideLoading && uni.hideLoading();
+		});
+}
+
+DbContext.prototype.remove = function(_id) {
+	uni.showLoading({
+		title: '请求中'
+	})
+
+	if (_id) {
+		return this.collection()
+			.doc(_id)
+			.remove()
+			.then(res => {
+				if ($config.debug) {
+					console.log(this.table + ' remove', res);
+				}
+				if (res.result) {
+					this.response.datas = res.result;
+					if (res.result.errCode === 0) this.response.code = 200;
+
+					return Promise.resolve(this.response);
+				}
+
+				return Promise.reject(res);
+			})
+			.catch(err => {
+				if ($config.debug) {
+					console.log(this.table + ' remove catch', err);
+					$api.alert(err.message);
+				}
+				return Promise.reject(err);
+			})
+			.finally(res => {
+				this.configDefault.hideLoading && uni.hideLoading();
+			});
+	}
+	this.where_str = this.where_arr.join(' && ');
+	return this.collection()
+		.where(this.where_str || this.where_obj)
+		.remove()
+		.then(res => {
+			if ($config.debug) {
+				console.log(this.table + ' remove', res);
+			}
+			if (res.result) {
+				this.response.datas = res.result;
+				if (res.result.errCode === 0) this.response.code = 200;
+
+				return Promise.resolve(this.response);
+			}
+
+			return Promise.reject(res);
+		})
+		.catch(err => {
+			if ($config.debug) {
+				console.log(this.table + ' remove catch', err);
+				$api.alert(err.message);
+			}
+			return Promise.reject(err);
+		})
+		.finally(res => {
+			this.configDefault.hideLoading && uni.hideLoading();
+		});
+}
+
+DbContext.prototype.set = function(_id, obj) {
+	uni.showLoading({
+		title: '请求中'
+	})
+	return this.collection()
+		.doc(_id)
+		.set(obj)
+		.then(res => {
+			if ($config.debug) {
+				console.log(this.table + ' set', res);
+			}
+			if (res.result) {
+				this.response.datas = res.result;
+				if (res.result.errCode === 0) this.response.code = 200;
+
+				return Promise.resolve(this.response);
+			}
+
+			return Promise.reject(res);
+		})
+		.catch(err => {
+			if ($config.debug) {
+				console.log(this.table + ' set catch', err);
+				$api.alert(err.message);
+			}
+			return Promise.reject(err);
+		})
+		.finally(res => {
+			this.configDefault.hideLoading && uni.hideLoading();
+		});
+}
+
+DbContext.prototype.update = function(_id, obj) {
+	uni.showLoading({
+		title: '请求中'
+	})
+	
+	if (_id && typeof _id === 'string') {
+		return this.collection()
+			.doc(_id)
+			.update(obj)
+			.then(res => {
+				if ($config.debug) {
+					console.log(this.table + ' update', res);
+				}
+				if (res.result) {
+					this.response.datas = res.result;
+					if (res.result.errCode === 0) this.response.code = 200;
+
+					return Promise.resolve(this.response);
+				}
+
+				return Promise.reject(res);
+			})
+			.catch(err => {
+				if ($config.debug) {
+					console.log(this.table + ' doc update catch', err);
+					$api.alert(err.message);
+				}
+				return Promise.reject(err);
+			})
+			.finally(res => {
+				this.configDefault.hideLoading && uni.hideLoading();
+			});
+	}
+	
+	if (typeof _id === 'object') obj = _id;
+	this.where_str = this.where_arr.join(' && ');
+	return this.collection()
+		.where(this.where_str || this.where_obj)
+		.update(obj)
+		.then(res => {
+			if ($config.debug) {
+				console.log(this.table + ' update', res);
+			}
+			if (res.result) {
+				this.response.datas = res.result;
+				if (res.result.errCode === 0) this.response.code = 200;
+
+				return Promise.resolve(this.response);
+			}
+
+			return Promise.reject(res);
+		})
+		.catch(err => {
+			if ($config.debug) {
+				console.log(this.table + ' update catch', err);
+				$api.alert(err.message);
+			}
+			return Promise.reject(err);
+		})
+		.finally(res => {
+			this.configDefault.hideLoading && uni.hideLoading();
+		});
+}
+
+// 
+DbContext.prototype.tofirst = function(_id) {
+	// #ifndef APP || APP-NVUE
+	uni.showNavigationBarLoading();
+	// #endif
+	if (typeof _id === 'string') {
+		return this.collection()
+			.doc(_id)
+			.get({
+				getOne: true
+			})
+			.then(res => {
+				if ($config.debug) {
+					console.log(this.table + ' doc.tofirst', res);
+				}
+				
+				if (res.result && res.result.errCode === 0) {
+					this.response.code = 200;
+					this.response.datas = res.result.data || {};
+				
+					return Promise.resolve(this.response);
+				}
+
+				return Promise.reject(res);
+			})
+			.catch(err => {
+				if ($config.debug) {
+					console.log(this.table + ' doc.tofirst catch', err);
+					$api.alert(err.message);
+				}
+				return Promise.reject(err);
+			})
+			.finally(res => {
+				// #ifndef APP || APP-NVUE
+				uni.hideNavigationBarLoading();
+				// #endif
+			});
+	}
+	
+	const req = Object.assign({orderby: ''}, _id);
+	this.where_str = this.where_arr.join(' && ');
+	return this.collection()
+		.where(this.where_str || this.where_obj)
+		.orderBy(req.orderby)
+		.get({
+			getOne: true
+		})
+		.then(res => {
+			if ($config.debug) {
+				console.log(this.table + ' tofirst', res);
+			}
+			if (res.result && res.result.errCode === 0) {
+				this.response.code = 200;
+				this.response.datas = res.result.data || {};
+
+				return Promise.resolve(this.response);
+			}
+
+			return Promise.reject(res);
+		})
+		.catch(err => {
+			if ($config.debug) {
+				console.log(this.table + ' tofirst catch', err);
+				$api.alert(err.message);
+			}
+			// return Promise.reject(err);
+		})
+		.finally(res => {
+			// #ifndef APP || APP-NVUE
+			uni.hideNavigationBarLoading();
+			// #endif
+		});
+}
+
+DbContext.prototype.tolist = function(req) {
+	// 页码 页数 排序
+	req = Object.assign({
+		page: 1,
+		rows: 30,
+		orderby: ''
+	}, req);
+	// #ifndef APP || APP-NVUE
+	uni.showNavigationBarLoading();
+	// #endif
+	
+	let ctx = this.collection();
+	
+	this.where_str = this.where_arr.join(' && ');
+	if (Object.keys(this.where_obj).length > 0) { ctx = ctx.where(this.where_obj); }
+	if (this.where_str) { ctx = ctx.where(this.where_str); }
+	
+	return ctx
+		.orderBy(req.orderby)
+		.skip((req.page - 1) * req.rows)
+		.limit(req.rows)
+		.get()
+		.then(res => {
+			if ($config.debug) {
+				console.log(this.table + ' tolist', res);
+			}
+			if (res.result) {
+				this.response.datas = res.result.data;
+
+				if (res.result.errCode === 0) this.response.code = 200;
+
+				return Promise.resolve(this.response);
+			}
+
+			return Promise.reject(res);
+		})
+		.catch(err => {
+			if ($config.debug) {
+				console.log(this.table + ' tolist catch', err);
+				$api.alert(err.message);
+			}
+			return Promise.reject(err);
+		})
+		.finally(res => {
+			// #ifndef APP || APP-NVUE
+			uni.hideNavigationBarLoading();
+			// #endif
+		});
+}
+
+DbContext.prototype.totable = function(req) {
+	// 页码 页数 排序
+	req = Object.assign({
+		page: 1,
+		rows: 10,
+		orderby: ''
+	}, req);
+	// #ifndef APP || APP-NVUE
+	uni.showNavigationBarLoading();
+	// #endif
+	this.where_str = this.where_arr.join(' && ');
+	return this.collection()
+		.where(this.where_str || this.where_obj)
+		.orderBy(req.orderby)
+		.skip((req.page - 1) * req.rows)
+		.limit(req.rows)
+		.get({
+			getCount: true
+		})
+		.then(res => {
+			if ($config.debug) {
+				console.log(this.table + ' totable', res);
+			}
+			if (res.result) {
+				this.response.datas.rows = res.result.data;
+				this.response.datas.total = res.result.count;
+
+				if (res.result.errCode === 0) this.response.code = 200;
+
+				return Promise.resolve(this.response);
+			}
+
+			return Promise.reject(res);
+		})
+		.catch(err => {
+			if ($config.debug) {
+				console.log(this.table + ' totable catch', err);
+				$api.alert(err.message);
+			}
+			return Promise.reject(err);
+		})
+		.finally(res => {
+			// #ifndef APP || APP-NVUE
+			uni.hideNavigationBarLoading();
+			// #endif
+		});
+}
+
+export default db;

File diff suppressed because it is too large
+ 2091 - 0
packageShang/common/iscroll.js


+ 185 - 0
packageShang/common/poster.js

@@ -0,0 +1,185 @@
+let uposter = {
+	// 产品海报
+	goods: {
+		width: '750rpx',
+		height: '1114rpx',
+		background: '#ff6a6c',
+		views: [{
+				type: 'view',
+				css: {
+					left: '40rpx',
+					top: '144rpx',
+					background: '#fff',
+					radius: '30rpx',
+					width: '670rpx',
+					height: '900rpx',
+					shadow: '0 20rpx 48rpx rgba(0,0,0,.05)'
+				}
+			},
+			// 1. 会员头像
+			{
+				type: 'image',
+				src: 'https://thirdwx.qlogo.cn/mmopen/vi_32/Q0j4TwGTfTLj1JIHX0icWAu4Jw920k6ZIAqNd9ZfHLalqWsweMLphLrcwRJCYr0hcyeY6Y5Kyaqibl6icTAykDWRA/132',
+				mode: 'widthFix',
+				css: {
+					left: '50rpx',
+					top: '40rpx',
+					width: '84rpx',
+					height: '84rpx',
+					radius: '50%',
+					color: '#999',
+					shadow: '0px 0px 7px #f0f0f0'
+				}
+			},
+			// 2. 会员名称
+			{
+				type: 'text',
+				text: 'Usecloud',
+				css: {
+					color: '#fff',
+					left: '154rpx',
+					top: '40rpx',
+					fontSize: '32rpx',
+					fontWeight: 'bold'
+				}
+			},
+			{
+				type: 'text',
+				text: '为您挑选了一个好物',
+				css: {
+					color: '#fff',
+					left: '154rpx',
+					top: '90rpx',
+					fontSize: '24rpx'
+				}
+			},
+			// 4. 产品图
+			{
+				type: 'image',
+				src: 'https://vkceyugu.cdn.bspapp.com/VKCEYUGU-7e00db99-ad65-4b9f-a74b-61bccb92b124/11bd19fa-6a70-49fd-9e71-72950828f6a8.jpg',
+				mode: 'widthFix',
+				css: {
+					left: '72rpx',
+					top: '176rpx',
+					width: '606rpx',
+					height: '606rpx',
+					radius: '12rpx'
+				}
+			},
+			{
+				type: 'text',
+				text: '¥',
+				css: {
+					color: '#ff6a6c',
+					left: '70rpx',
+					top: '840rpx',
+					fontSize: '26rpx',
+				}
+			},
+			// 6. 产品价格
+			{
+				type: 'text',
+				text: '1339.90',
+				css: {
+					color: '#ff6a6c',
+					left: '94rpx',
+					top: '812rpx',
+					fontSize: '56rpx',
+					fontWeight: 'bold'
+				}
+			},
+			// 7. 产品介绍
+			{
+				type: 'text',
+				text: '小米10 Xiaomi/小米手机小米10手机骁龙865 1亿像素双模5G官方正品 骁龙865旗舰处理器 1亿像素8K电影相机',
+				css: {
+					maxLines: 2,
+					width: '450rpx',
+					color: '#333',
+					left: '76rpx',
+					top: '908rpx',
+					fontSize: '30rpx',
+					lineHeight: '50rpx'
+				}
+			},
+			// 8. 产品二维码
+			{
+				type: 'image',
+				src: '',
+				mode: 'widthFix',
+				css: {
+					left: '545rpx',
+					top: '875rpx',
+					width: '135rpx',
+					height: '135rpx',
+					background: '#fff'
+				}
+			},
+			{
+				type: 'view',
+				css: {
+					top: '1040rpx',
+					height: '100rpx',
+					background: '#ff6a6c'
+				}
+			},
+			{
+				type: 'view',
+				css: {
+					left: '190rpx',
+					top: '1078rpx',
+					border: '1px solid #fff',
+					width: '70rpx'
+				}
+			},
+			{
+				type: 'text',
+				text: '用云 · 让开发更简单',
+				css: {
+					color: '#fff',
+					left: '276rpx',
+					top: '1060rpx',
+					fontSize: '24rpx',
+				}
+			},
+			{
+				type: 'view',
+				css: {
+					left: '496rpx',
+					top: '1078rpx',
+					border: '1px solid #fff',
+					width: '64rpx'
+				}
+			}
+		]
+	},
+	// 产品海报数据
+	getGoodsData: function (member, goods, qrcode) {
+		// 会员头像
+		this.goods.views[1].src = member.member_headimg;
+		// 会员名称
+		this.goods.views[2].text = member.member_nickname;
+		// 产品图
+		this.goods.views[4].src = goods.img;
+		// 产品价格
+		this.goods.views[6].text = '' + goods.price / 100;
+		// 产品名称
+		this.goods.views[7].text = goods.name + ' ' + goods.name_pw;
+		
+		// 二维码
+		this.goods.views[8].src = qrcode;
+		return this.goods;
+	},
+	
+	// 会员海报
+	member: {
+
+	},
+	// 分销商海报
+	fxs: {
+
+	},
+	
+};
+
+export default uposter;

+ 90 - 0
packageShang/common/request.js

@@ -0,0 +1,90 @@
+// 全局请求封装
+const base_url = 'https://chtech.ncjti.edu.cn/homestaytemporary/cloud-mall'
+// 需要修改token,和根据实际修改请求头
+export default (params) => {
+    let url = params.url;
+    let method = params.method || "get";
+    let data = params.data || {};
+	let header = params.header || {}
+
+    // let header = {}
+    // if (method == "post") {
+    //     header = {
+    //         'Content-Type': 'application/json'
+    //     };
+    // }
+    // 获取本地token
+    // if (uni.getStorageSync("token")) {
+    //     header['User-Token'] =uni.getStorageSync("token");
+    // }
+
+    return new Promise((resolve, reject) => {
+        uni.request({
+            url: base_url + url,
+            method: method,
+            header: header,
+            data: data,
+            success(response) {
+                const res = response
+                // 根据返回的状态码做出对应的操作
+                //获取成功
+                console.log(res.statusCode);
+                if (res.statusCode == 200) {
+                    resolve(res.data);
+                } else {
+                    uni.clearStorageSync()
+                    switch (res.statusCode) {
+                        case 401:
+                            uni.showModal({
+                                title: "提示",
+                                content: "请登录",
+                                showCancel: false,
+                                success(res) {
+                                    setTimeout(() => {
+                                        uni.navigateTo({
+                                            url: "/pages/login/login",
+                                        })
+                                    }, 1000);
+                                },
+                            });
+                            break;
+                        case 404:
+                            uni.showToast({
+                                title: '请求地址不存在...',
+                                duration: 2000,
+                            })
+                            break;
+                        default:
+                            uni.showToast({
+                                title: '请重试...',
+                                duration: 2000,
+                            })
+                            break;
+                    }
+                }
+            },
+            fail(err) {
+                console.log(err)
+                if (err.errMsg.indexOf('request:fail') !== -1) {
+                    wx.showToast({
+                        title: '网络异常',
+                        icon: "error",
+                        duration: 2000
+                    })
+                } else {
+                    wx.showToast({
+                        title: '未知异常',
+                        duration: 2000
+                    })
+                }
+                reject(err);
+
+            },
+            complete() {
+                // 不管成功还是失败都会执行
+                uni.hideLoading();
+                uni.hideToast();
+            }
+        });
+    }).catch((e) => {});
+};

+ 108 - 0
packageShang/common/store.js

@@ -0,0 +1,108 @@
+import Vue from 'vue'
+import Vuex from 'vuex'
+import $config from './config.js'
+
+Vue.use(Vuex)
+
+const store = new Vuex.Store({
+	state: {
+		islogin: false,
+		member: {},
+		token: '',
+		token_expired: 0,
+
+		__key_member: 'usemall_member',
+		__key_token: 'uni_id_token',
+		__key_token_expired: 'uni_id_token_expired',
+	},
+	mutations: {
+		// 登录成功
+		login(state, res) {
+			// 用户已登录
+			state.islogin = true;
+			
+			state.member = res.member;
+			state.token = res.user.token;
+			state.token_expired = res.user.tokenExpired;
+
+			// 存储会员 member 数据
+			uni.setStorage({
+				key: state.__key_member,
+				data: state.member
+			})
+
+			// 存储uid token 数据
+			uni.setStorage({
+				key: state.__key_token,
+				data: state.token
+			})
+			// 存储uid token_expired 数据
+			uni.setStorage({
+				key: state.__key_token_expired,
+				data: state.token_expired
+			})
+		},
+		
+		// 加载 Token
+		loadToken(state) {
+			state.member = uni.getStorageSync(state.__key_member);
+			state.token = uni.getStorageSync(state.__key_token);
+			state.token_expired = uni.getStorageSync(state.__key_token_expired);
+			
+			if (state.token_expired > new Date().getTime()) {
+				state.islogin = true;
+			}
+		},
+
+		// 注销 
+		logout(state) {
+			state.islogin = false;
+			state.member = {};
+			state.token = '';
+			state.token_expired = 0;
+
+			uni.removeStorage({
+				key: state.__key_member
+			})
+			uni.removeStorage({
+				key: state.__key_token
+			})
+			uni.removeStorage({
+				key: state.__key_token_expired
+			})
+		},
+		
+		// token 令牌
+		token(state, token, token_expired) {
+			state.token = token;
+			state.token_expired = token_expired;
+
+			// 存储uid token 数据
+			uni.setStorage({
+				key: state.__key_token,
+				data: state.token
+			})
+			// 存储uid token_expired 数据
+			uni.setStorage({
+				key: state.__key_token_expired,
+				data: state.token_expired
+			})
+		},
+		
+		// 修改 member 数据
+		putMember (state, user) {
+			state.member = user;
+			
+			// 存储会员 member 数据
+			uni.setStorage({
+				key: state.__key_member,
+				data: state.member
+			})
+		}
+	},
+	actions: {
+
+	}
+})
+
+export default store

File diff suppressed because it is too large
+ 310 - 0
packageShang/components/iconfont/iconfont.css


+ 133 - 0
packageShang/components/use-action-sheet/use-action-sheet.vue

@@ -0,0 +1,133 @@
+<template>
+	<use-popup mode="bottom" v-model="value" 
+		length="auto" 
+		:border-radius="borderRadius" 
+		:popup="false" 
+		:maskCloseAble="maskCloseAble"
+		:safeAreaInsetBottom="safeAreaInsetBottom" 
+		:z-index="zIndex"
+		@close="popupClose">
+		<view v-if="tips.text" class="fs-sm padding-tb tac border-bottom" :style="[tipsStyle]">
+			{{tips.text}}
+		</view>
+		<block v-for="(item, index) in datas" :key="index">
+			<view class="dflex-c padding-tb line-height-1" :class="[index < datas.length - 1 ? 'border-bottom' : '']" :style="[itemStyle(index)]" hover-class="use-hover-class" :hover-stay-time="150" @touchmove.stop.prevent @tap="itemClick(index)">
+				{{item.text}}
+			</view>
+		</block>
+		<view v-if="cancelBtn" class="gap"></view>
+		<view v-if="cancelBtn" class="dflex-c padding-tb line-height-1" hover-class="use-hover-class" :hover-stay-time="150" @touchmove.stop.prevent @tap="closeClick">取消</view>
+	</use-popup>
+</template>
+
+<script>
+	import usePopup from '../use-popup/use-popup.vue'
+	export default {
+		components:{
+			usePopup
+		},
+		props: {
+			// 点击遮罩是否可以关闭actionsheet
+			maskCloseAble: {
+				type: Boolean,
+				default: true
+			},
+			// 按钮的文字数组,可以自定义颜色和字体大小,字体单位为rpx
+			list: {
+				type: Array,
+				default () {
+					// 如下
+					// return [{
+					// 	text: '确定',
+					// 	color: '',
+					// 	fontSize: ''
+					// }]
+					return [];
+				}
+			},
+			// 顶部的提示文字
+			tips: {
+				type: Object,
+				default () {
+					return {
+						text: '',
+						color: '',
+						fontSize: '26'
+					}
+				}
+			},
+			// 底部的取消按钮
+			cancelBtn: {
+				type: Boolean,
+				default: true
+			},
+			// 是否开启底部安全区适配,开启的话,会在iPhoneX机型底部添加一定的内边距
+			safeAreaInsetBottom: {
+				type: Boolean,
+				default: true
+			},
+			// 通过双向绑定控制组件的弹出与收起
+			value: {
+				type: Boolean,
+				default: false
+			},
+			// 弹出的顶部圆角值
+			borderRadius: {
+				type: [String, Number],
+				default: 0
+			},
+			// 弹出的z-index值
+			zIndex: {
+				type: [String, Number],
+				default: 10030
+			}
+		},
+		watch: {
+			list(nv, ov) {
+				console.log('use-action-sheet', {nv:nv, ov:ov});
+				this.datas = nv;
+			}
+		},
+		data() {
+			return {
+				datas: []
+			}
+		},
+		computed: {
+			// 顶部提示的样式
+			tipsStyle() {
+				let style = {};
+				if (this.tips.color) style.color = this.tips.color;
+				if (this.tips.fontSize) style.fontSize = this.tips.fontSize + 'rpx';
+				return style;
+			},
+			// 操作项目的样式
+			itemStyle(index) {
+				return (index) => {
+					let style = {};
+					if (this.list[index].color) style.color = this.list[index].color;
+					if (this.list[index].fontSize) style.fontSize = this.list[index].fontSize + 'rpx';
+					return style;
+				}
+			}
+		},
+		methods: {
+			// 弹窗关闭回执事件
+			popupClose() {
+				this.$emit('input', false);
+				this.$emit('close');
+			},
+			// 手动点击取消按钮
+			closeClick() {
+				this.$emit('input', false);
+			},
+			// 操作菜单 item 点击事件
+			itemClick(index) {
+				this.$emit('click', index);
+				this.$emit('input', false);
+			}
+		}
+	}
+</script>
+
+<style lang="scss"></style>

File diff suppressed because it is too large
+ 12548 - 0
packageShang/components/use-address/city-data/area.js


File diff suppressed because it is too large
+ 1507 - 0
packageShang/components/use-address/city-data/city.js


+ 143 - 0
packageShang/components/use-address/city-data/province.js

@@ -0,0 +1,143 @@
+/* eslint-disable */
+var provinceData = [{
+    "label": "北京市",
+    "value": "11"
+  },
+  {
+    "label": "天津市",
+    "value": "12"
+  },
+  {
+    "label": "河北省",
+    "value": "13"
+  },
+  {
+    "label": "山西省",
+    "value": "14"
+  },
+  {
+    "label": "内蒙古自治区",
+    "value": "15"
+  },
+  {
+    "label": "辽宁省",
+    "value": "21"
+  },
+  {
+    "label": "吉林省",
+    "value": "22"
+  },
+  {
+    "label": "黑龙江省",
+    "value": "23"
+  },
+  {
+    "label": "上海市",
+    "value": "31"
+  },
+  {
+    "label": "江苏省",
+    "value": "32"
+  },
+  {
+    "label": "浙江省",
+    "value": "33"
+  },
+  {
+    "label": "安徽省",
+    "value": "34"
+  },
+  {
+    "label": "福建省",
+    "value": "35"
+  },
+  {
+    "label": "江西省",
+    "value": "36"
+  },
+  {
+    "label": "山东省",
+    "value": "37"
+  },
+  {
+    "label": "河南省",
+    "value": "41"
+  },
+  {
+    "label": "湖北省",
+    "value": "42"
+  },
+  {
+    "label": "湖南省",
+    "value": "43"
+  },
+  {
+    "label": "广东省",
+    "value": "44"
+  },
+  {
+    "label": "广西壮族自治区",
+    "value": "45"
+  },
+  {
+    "label": "海南省",
+    "value": "46"
+  },
+  {
+    "label": "重庆市",
+    "value": "50"
+  },
+  {
+    "label": "四川省",
+    "value": "51"
+  },
+  {
+    "label": "贵州省",
+    "value": "52"
+  },
+  {
+    "label": "云南省",
+    "value": "53"
+  },
+  {
+    "label": "西藏自治区",
+    "value": "54"
+  },
+  {
+    "label": "陕西省",
+    "value": "61"
+  },
+  {
+    "label": "甘肃省",
+    "value": "62"
+  },
+  {
+    "label": "青海省",
+    "value": "63"
+  },
+  {
+    "label": "宁夏回族自治区",
+    "value": "64"
+  },
+  {
+    "label": "新疆维吾尔自治区",
+    "value": "65"
+  },
+  {
+    "label": "台湾",
+    "value": "66"
+  },
+  {
+    "label": "香港",
+    "value": "67"
+  },
+  {
+    "label": "澳门",
+    "value": "68"
+  },
+  {
+    "label": "钓鱼岛",
+    "value": "69"
+  }
+]
+export default provinceData;

+ 420 - 0
packageShang/components/use-address/use-address.vue

@@ -0,0 +1,420 @@
+<template>
+	<view class="use-address" v-if="showPopup" @touchmove.stop.prevent="clear">
+		<!-- 遮罩层 -->
+		<view
+			class="use-address-mask"
+			@touchmove.stop.prevent="clear"
+			v-if="maskClick"
+			:class="[ani + '-mask', animation ? 'mask-ani' : '']"
+			:style="{
+				'background-color': maskBgColor
+			}"
+			@tap="hideMask(true)"
+		></view>
+
+		<view class="use-address-content use-address--fixed" :class="[type, ani + '-content', animation ? 'content-ani' : '']">
+			<view class="use-address__header">
+				<view class="use-address__header-btn-box" @click="pickerCancel">
+					<text class="use-address__header-text" :style="{ color: cancelColor, fontSize: btnFontSize }">取消</text>
+				</view>
+				<view class="use-address__header-btn-box" @click="pickerConfirm">
+					<text class="use-address__header-text" :style="{ color: confirmColor || themeColor, fontSize: btnFontSize }">确定</text>
+				</view>
+			</view>
+			<view class="use-address__box">
+				<picker-view indicator-style="height: 70rpx;" class="use-address-view" :value="pickerValue" @change="pickerChange">
+					<picker-view-column>
+						<!-- #ifndef APP-NVUE -->
+						<view class="picker-item" :style="{ fontSize: fontSize }" v-for="(item, index) in provinceDataList" :key="index">{{ item.label }}</view>
+						<!-- #endif -->
+						<!-- #ifdef APP-NVUE -->
+						<text class="picker-item" :style="{ fontSize: fontSize }" v-for="(item, index) in provinceDataList" :key="index">{{ item.label }}</text>
+						<!-- #endif -->
+					</picker-view-column>
+					<picker-view-column>
+						<!-- #ifndef APP-NVUE -->
+						<view class="picker-item" :style="{ fontSize: fontSize }" v-for="(item, index) in cityDataList" :key="index">{{ item.label }}</view>
+						<!-- #endif -->
+						<!-- #ifdef APP-NVUE -->
+						<text class="picker-item" :style="{ fontSize: fontSize }" v-for="(item, index) in cityDataList" :key="index">{{ item.label }}</text>
+						<!-- #endif -->
+					</picker-view-column>
+					<picker-view-column>
+						<!-- #ifndef APP-NVUE -->
+						<view class="picker-item" :style="{ fontSize: fontSize }" v-for="(item, index) in areaDataList" :key="index">{{ item.label }}</view>
+						<!-- #endif -->
+						<!-- #ifdef APP-NVUE -->
+						<text class="picker-item" :style="{ fontSize: fontSize }" v-for="(item, index) in areaDataList" :key="index">{{ item.label }}</text>
+						<!-- #endif -->
+					</picker-view-column>
+				</picker-view>
+			</view>
+		</view>
+	</view>
+</template>
+
+<script>
+/**
+ * Simple-addres  地址联动组件
+ * @description 三级地址联动,支持(app)nvue、小程序、H5
+ * @tutorial https://ext.dcloud.net.cn/plugin?id=1084
+ * @property {String} animation 是否开启动画
+ * @property {String} type = [bottom] 弹出层类型,暂时只支持底部弹出
+ * @property {Boolean} maskClick = [true | false] 是否允许点击遮罩层关闭
+ * @property {Boolean} show = [true | false]  显示或隐藏地址组件
+ * @property {String} maskBgColor 遮罩层背景颜色
+ * @property {String} cancelColor 取消按钮颜色,默认为:#1aad19
+ * @property {String} confirmColor 确认按钮颜色,默认为:themeColor
+ * @property {String} themeColor 主题颜色,后续会废弃该配置,建议使用`cancelColor`或`confirmColor`
+ * @property {String} btnFontSize 取消、确认按钮字体大小,默认为`uni.scss里的 $uni-font-size-base `
+ * @property {String} fontSize picker-item字体大小,默认为:28rpx
+ * @property {Array} pickerValueDefault 默认值,可以通过function queryIndex 获取
+ * @property {Function} queryIndex 根据自定义信息返回对应的index
+ * @property {Function} open 打开
+ * @example  <use-address ref="simpleAddress" :pickerValueDefault="cityPickerValueDefault" @onConfirm="onConfirm" themeColor='#007AFF'></use-address>
+ */
+
+import provinceData from './city-data/province.js';
+import cityData from './city-data/city.js';
+import areaData from './city-data/area.js';
+export default {
+	name: 'simpleAddress',
+	props: {
+		mode: {
+			// 地址类型
+			// default 则代表老版本根据index索引获取数据
+			//
+			type: String,
+			default: 'default'
+		},
+		// 开启动画
+		animation: {
+			type: Boolean,
+			default: true
+		},
+		/* 弹出层类型,可选值;
+				bottom:底部弹出层
+			*/
+		type: {
+			type: String,
+			default: 'bottom'
+		},
+		// maskClick
+		maskClick: {
+			type: Boolean,
+			default: true
+		},
+		show: {
+			type: Boolean,
+			default: true
+		},
+		maskBgColor: {
+			type: String,
+			default: 'rgba(0, 0, 0, 0.4)' //背景颜色 rgba(0, 0, 0, 0.4) 为空则调用 uni.scss
+		},
+		themeColor: {
+			type: String,
+			default: '' // 确认按钮颜色(向下兼容)
+		},
+		cancelColor: {
+			type: String,
+			default: '' // 取消按钮颜色
+		},
+		confirmColor: {
+			type: String,
+			default: '' // 确认按钮颜色
+		},
+		fontSize: {
+			type: String,
+			default: '28rpx' // picker-item字体大小
+		},
+		btnFontSize: {
+			type: String,
+			default: '' // 按钮的字体大小
+		},
+		/* 默认值 */
+		pickerValueDefault: {
+			type: Array,
+			default() {
+				return [0, 0, 0];
+			}
+		}
+	},
+	data() {
+		return {
+			ani: '',
+			showPopup: false,
+			pickerValue: [0, 0, 0],
+			provinceDataList: [],
+			cityDataList: [],
+			areaDataList: []
+		};
+	},
+	watch: {
+		show(newValue) {
+			if (newValue) {
+				this.open();
+			} else {
+				this.close();
+			}
+		},
+		pickerValueDefault() {
+			this.init();
+		}
+	},
+	created() {
+		this.init();
+	},
+	methods: {
+		init() {
+			this.handPickValueDefault(); // 对 pickerValueDefault 做兼容处理
+			this.provinceDataList = provinceData;
+			this.cityDataList = cityData[this.pickerValueDefault[0]];
+			this.areaDataList = areaData[this.pickerValueDefault[0]][this.pickerValueDefault[1]];
+			this.pickerValue = this.pickerValueDefault;
+		},
+		handPickValueDefault() {
+			if (this.pickerValueDefault !== [0, 0, 0]) {
+				if (this.pickerValueDefault[0] > provinceData.length - 1) {
+					this.pickerValueDefault[0] = provinceData.length - 1;
+				}
+				if (this.pickerValueDefault[1] > cityData[this.pickerValueDefault[0]].length - 1) {
+					this.pickerValueDefault[1] = cityData[this.pickerValueDefault[0]].length - 1;
+				}
+				if (this.pickerValueDefault[2] > areaData[this.pickerValueDefault[0]][this.pickerValueDefault[1]].length - 1) {
+					this.pickerValueDefault[2] = areaData[this.pickerValueDefault[0]][this.pickerValueDefault[1]].length - 1;
+				}
+			}
+		},
+		pickerChange(e) {
+			let changePickerValue = e.detail.value;
+			if (this.pickerValue[0] !== changePickerValue[0]) {
+				// 第一级发生滚动
+				this.cityDataList = cityData[changePickerValue[0]];
+				this.areaDataList = areaData[changePickerValue[0]][0];
+				changePickerValue[1] = 0;
+				changePickerValue[2] = 0;
+			} else if (this.pickerValue[1] !== changePickerValue[1]) {
+				// 第二级滚动
+				this.areaDataList = areaData[changePickerValue[0]][changePickerValue[1]];
+				changePickerValue[2] = 0;
+			}
+			this.pickerValue = changePickerValue;
+			this._$emit('onChange');
+		},
+		_$emit(emitName) {
+			let pickObj = {
+				label: this._getLabel(),
+				value: this.pickerValue,
+				cityCode: this._getCityCode(),
+				areaCode: this._getAreaCode(),
+				provinceCode: this._getProvinceCode(),
+				labelArr: this._getLabel().split('-')
+			};
+			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.cityDataList[this.pickerValue[1]].value;
+		},
+		_getProvinceCode() {
+			return this.provinceDataList[this.pickerValue[0]].value;
+		},
+		_getAreaCode() {
+			return this.areaDataList[this.pickerValue[2]].value;
+		},
+		queryIndex(params = [], type = 'value') {
+			// params = [ 11 ,1101,110101 ];
+			// 1.获取省份的index
+			let provinceIndex = provinceData.findIndex(res => res[type] == params[0]);
+			let cityIndex = cityData[provinceIndex].findIndex(res => res[type] == params[1]);
+			let areaIndex = areaData[provinceIndex][cityIndex].findIndex(res => res[type] == params[2]);
+			return {
+				index: [provinceIndex, cityIndex, areaIndex],
+				data: {
+					province: provinceData[provinceIndex],
+					city: cityData[provinceIndex][cityIndex],
+					area: areaData[provinceIndex][cityIndex][areaIndex]
+				}
+			};
+		},
+		clear() {},
+		hideMask() {
+			this._$emit('onCancel');
+			this.close();
+		},
+		pickerCancel() {
+			this._$emit('onCancel');
+			this.close();
+		},
+		pickerConfirm() {
+			this._$emit('onConfirm');
+			this.close();
+		},
+		open() {
+			this.showPopup = true;
+			this.$nextTick(() => {
+				setTimeout(() => {
+					this.ani = 'simple-' + this.type;
+				}, 100);
+			});
+		},
+		close(type) {
+			if (!this.maskClick && type) return;
+			this.ani = '';
+			this.$nextTick(() => {
+				setTimeout(() => {
+					this.showPopup = false;
+				}, 300);
+			});
+		}
+	}
+};
+</script>
+
+<style lang="scss" scoped>
+.use-address {
+	/* #ifndef APP-NVUE */
+	display: flex;
+	/* #endif */
+	flex-direction: column;
+}
+
+.use-address-mask {
+	position: fixed;
+	bottom: 0;
+	top: 0;
+	left: 0;
+	right: 0;
+
+	transition-property: opacity;
+	transition-duration: 0.3s;
+	opacity: 0;
+	/* #ifndef APP-NVUE */
+	z-index: 99;
+	/* #endif */
+}
+
+.mask-ani {
+	transition-property: opacity;
+	transition-duration: 0.2s;
+}
+
+.simple-bottom-mask {
+	opacity: 1;
+}
+
+.simple-center-mask {
+	opacity: 1;
+}
+
+.use-address--fixed {
+	position: fixed;
+	bottom: 0;
+	left: 0;
+	right: 0;
+	transition-property: transform;
+	transition-duration: 0.3s;
+	transform: translateY(460rpx);
+	/* #ifndef APP-NVUE */
+	z-index: 99;
+	/* #endif */
+}
+
+.use-address-content {
+	background-color: #ffffff;
+}
+
+.simple-content-bottom {
+	bottom: 0;
+	left: 0;
+	right: 0;
+	transform: translateY(500rpx);
+}
+
+.content-ani {
+	transition-property: transform, opacity;
+	transition-duration: 0.2s;
+}
+
+.simple-bottom-content {
+	transform: translateY(0);
+}
+
+.simple-center-content {
+	transform: scale(1);
+	opacity: 1;
+}
+
+.use-address__header {
+	position: relative;
+	/* #ifndef APP-NVUE */
+	display: flex;
+	/* #endif */
+	flex-direction: row;
+	flex-wrap: nowrap;
+	justify-content: space-between;
+	border-bottom-color: #f2f2f2;
+	border-bottom-style: solid;
+	border-bottom-width: 1rpx;
+}
+
+.use-address--fixed-top {
+	/* #ifndef APP-NVUE */
+	display: flex;
+	/* #endif */
+	flex-direction: row;
+	justify-content: space-between;
+	border-top-color: $uni-border-color;
+	border-top-style: solid;
+	border-top-width: 1rpx;
+}
+
+.use-address__header-btn-box {
+	/* #ifndef APP-NVUE */
+	display: flex;
+	/* #endif */
+	flex-direction: row;
+	align-items: center;
+	justify-content: center;
+	height: 70rpx;
+}
+
+.use-address__header-text {
+	text-align: center;
+	font-size: $uni-font-size-base;
+	color: #1aad19;
+	line-height: 70rpx;
+	padding-left: 40rpx;
+	padding-right: 40rpx;
+}
+
+.use-address__box {
+	position: relative;
+}
+
+.use-address-view {
+	position: relative;
+	bottom: 0;
+	left: 0;
+	/* #ifndef APP-NVUE */
+	width: 100%;
+	/* #endif */
+	/* #ifdef APP-NVUE */
+	width: 750rpx;
+	/* #endif */
+	height: 408rpx;
+	background-color: rgba(255, 255, 255, 1);
+}
+
+.picker-item {
+	text-align: center;
+	line-height: 70rpx;
+	text-overflow: ellipsis;
+	font-size: 28rpx;
+}
+</style>

+ 245 - 0
packageShang/components/use-count-down/use-count-down.vue

@@ -0,0 +1,245 @@
+<template>
+	<view class="use-count-down dflex">
+		<view v-if="showDays" class="use-count-down-item dflex-c" :style="[itemStyle]">
+			<view class="use-count-down-time" :style="[letterStyle]">
+				{{ d }}
+			</view>
+		</view>
+		<view v-if="showDays" class="use-count-down-colon dflex-c"
+			:style="{fontSize: separatorSize + 'rpx', color: separatorColor, paddingBottom: separator == 'colon' ? '4rpx' : 0}">
+			{{ separator == 'colon' ? ':' : '天' }}
+		</view>
+		<view v-if="showHours" class="use-count-down-item dflex-c" :style="[itemStyle]">
+			<view class="use-count-down-time" :style="{ fontSize: fontSize + 'rpx', color: color}">
+				{{ h }}
+			</view>
+		</view>
+		<view v-if="showHours" class="use-count-down-colon dflex-c"
+			:style="{fontSize: separatorSize + 'rpx', color: separatorColor, paddingBottom: separator == 'colon' ? '4rpx' : 0}">
+			{{ separator == 'colon' ? ':' : '时' }}
+		</view>
+		<view v-if="showMinutes" class="use-count-down-item dflex-c" :style="[itemStyle]">
+			<view class="use-count-down-time" :style="{ fontSize: fontSize + 'rpx', color: color}">
+				{{ i }}
+			</view>
+		</view>
+		<view v-if="showMinutes" class="use-count-down-colon dflex-c"
+			:style="{fontSize: separatorSize + 'rpx', color: separatorColor, paddingBottom: separator == 'colon' ? '4rpx' : 0}">
+			{{ separator == 'colon' ? ':' : '分' }}
+		</view>
+		<view v-if="showSeconds" class="use-count-down-item dflex-c" :style="[itemStyle]">
+			<view class="use-count-down-time" :style="{ fontSize: fontSize + 'rpx', color: color}">
+				{{ s }}
+			</view>
+		</view>
+		<view v-if="showSeconds && separator == 'zh'" class="use-count-down-colon dflex-c"
+			:style="{fontSize: separatorSize + 'rpx', color: separatorColor, paddingBottom: separator == 'colon' ? '4rpx' : 0}">
+			秒
+		</view>
+	</view>
+</template>
+
+<script>
+export default {
+	props: {
+		// 倒计时的时间,秒为单位
+		timestamp: {
+			type: [Number, String],
+			default: 0
+		},
+		// 是否自动开始倒计时
+		autoplay: {
+			type: Boolean,
+			default: true
+		},
+		// 用英文冒号(colon)或者中文(zh)当做分隔符,false的时候为中文,如:"11:22"或"11时22秒"
+		separator: {
+			type: String,
+			default: 'colon'
+		},
+		// 分隔符的大小,单位rpx
+		separatorSize: {
+			type: [Number, String],
+			default: 30
+		},
+		// 分隔符颜色
+		separatorColor: {
+			type: String,
+			default: "#303133"
+		},
+		// 字体颜色
+		color: {
+			type: String,
+			default: '#303133'
+		},
+		// 字体大小,单位rpx
+		fontSize: {
+			type: [Number, String],
+			default: 30
+		},
+		// 背景颜色
+		bgColor: {
+			type: String,
+			default: '#fff'
+		},
+		// 数字框高度,单位rpx
+		height: {
+			type: [Number, String],
+			default: 'auto'
+		},
+		// 是否显示数字框
+		showBorder: {
+			type: Boolean,
+			default: false
+		},
+		// 边框颜色
+		borderColor: {
+			type: String,
+			default: '#303133'
+		},
+		// 是否显示秒
+		showSeconds: {
+			type: Boolean,
+			default: true
+		},
+		// 是否显示分钟
+		showMinutes: {
+			type: Boolean,
+			default: true
+		},
+		// 是否显示小时
+		showHours: {
+			type: Boolean,
+			default: true
+		},
+		// 是否显示“天”
+		showDays: {
+			type: Boolean,
+			default: true
+		},
+	},
+	watch: {
+		// 监听时间戳的变化
+		timestamp(newVal, oldVal) {
+			// 如果倒计时间发生变化,清除定时器,重新开始倒计时
+			clearInterval(this.timer);
+			this.start();
+		}
+	},
+	data() {
+		return {
+			d: '00', // 天的默认值
+			h: '00', // 小时的默认值
+			i: '00', // 分钟的默认值
+			s: '00', // 秒的默认值
+			timer: null ,// 定时器
+			seconds: 0, // 记录不停倒计过程中变化的秒数
+		};
+	},
+	computed: {
+		// 倒计时item的样式,item为分别的时分秒部分的数字
+		itemStyle() {
+			let style = {};
+			if(this.height) {
+				style.height = this.height + 'rpx';
+				style.width = this.height + 'rpx';
+			}
+			if(this.showBorder) {
+				style.borderStyle = 'solid';
+				style.borderColor = this.borderColor;
+				style.borderWidth = '1px';
+			}
+			if(this.bgColor) {
+				style.backgroundColor = this.bgColor;
+			}
+			return style;
+		},
+		// 倒计时数字的样式
+		letterStyle() {
+			let style = {};
+			if(this.fontSize) style.fontSize = this.fontSize +  'rpx';
+			if(this.color) style.color = this.color;
+			return style;
+		}
+	},
+	mounted() {
+		// 如果自动倒计时
+		this.autoplay && this.timestamp && this.start();
+	},
+	methods: {
+		// 倒计时
+		start() {
+			if (this.timestamp <= 0) return;
+			this.seconds = Number(this.timestamp);
+			this.formatTime(this.seconds);
+			this.timer = setInterval(() => {
+				this.seconds--;
+				// 发出change事件
+				this.$emit('change', this.seconds);
+				if (this.seconds < 0) {
+					return this.end();
+				}
+				this.formatTime(this.seconds);
+			}, 1000);
+		},
+		// 格式化时间
+		formatTime(seconds) {
+			// 小于等于0的话,结束倒计时
+			seconds <= 0 && this.end();
+			let [day, hour, minute, second] = [0, 0, 0, 0];
+			day = Math.floor(seconds / (60 * 60 * 24));
+			// 判断是否显示“天”参数,如果不显示,将天部分的值,加入到小时中
+			// hour为给后面计算秒和分等用的(基于显示天的前提下计算)
+			hour = Math.floor(seconds / (60 * 60)) - day * 24;
+			// showHour为需要显示的小时
+			let showHour = null;
+			if(this.showDays) {
+				showHour = hour;
+			} else {
+				showHour = Math.floor(seconds / (60 * 60));
+			}
+			minute = Math.floor(seconds / 60) - hour * 60 - day * 24 * 60;
+			second = Math.floor(seconds) - day * 24 * 60 * 60 - hour * 60 * 60 - minute * 60;
+			// 如果小于10,在前面补上一个"0"
+			hour = hour < 10 ? '0' + hour : hour;
+			minute = minute < 10 ? '0' + minute : minute;
+			second = second < 10 ? '0' + second : second;
+			this.d = day;
+			this.h = showHour;
+			this.i = minute;
+			this.s = second;
+		},
+		// 停止倒计时
+		end() {
+			// 清除定时器
+			clearInterval(this.timer);
+			this.timer = null;
+			this.$emit('end', {});
+		}
+	},
+	beforeDestroy() {
+		clearInterval(this.timer);
+		this.timer = null;
+	}
+};
+</script>
+
+<style lang="scss">
+	.use-count-down {}	
+	.use-count-down-item {
+		padding: 2rpx;
+		border-radius: 6rpx;
+		white-space: nowrap;
+		transform: translateZ(0);
+	}	
+	.use-count-down-time {}	
+	.use-count-down-colon {
+		padding: 0 5rpx;
+		line-height: 1;
+		padding-bottom: 4rpx;
+	}
+	.use-count-down-scale {
+		transform: scale(0.9);
+		transform-origin: center center;
+	}
+</style>

+ 109 - 0
packageShang/components/use-empty/use-empty.vue

@@ -0,0 +1,109 @@
+<template>
+	<view class="use-empty-container tac" :class="eStyle == 'round' ? 'padding-sm' : ''" :style="{ height: height }">
+		<view class="use-empty h-full dflex-c dflex-flow-c" :class="eStyle == 'round' ? 'round border-radius bg-main' : ''">
+			<image v-if="imgurl" :src="imgurl"></image>
+			<view v-if="tip" class="title padding-sm">{{tip}}</view>
+			<button class="no-border use-btn" @click="to">{{btnTip}}</button>
+		</view>
+	</view>
+</template>
+
+<script>
+export default {
+	props: {
+		eStyle: {
+			type: String,
+			default: 'square'
+		},
+		eType: {
+			type: String,
+			default: 'other'
+		},
+		tip: {
+			type: String,
+			default: '暂无数据'
+		},
+		btnTip: {
+			type: String,
+			default: '去逛逛'
+		},
+		btnGoto: {
+			type: String,
+			default: '/packageShang/pages/tabbar/home'
+		},
+		auto: {
+			type: Boolean,
+			default: !0
+		},
+		height: {
+			type: String,
+			default: '100vh'
+		}
+	},
+	computed: {
+		dtype: {
+			get(){
+				console.log('get dtype', arguments);
+				let imgobj = this.imgs.find(x => x.type == this.eType);
+				this.imgurl = imgobj ? imgobj.url : this.imgurl_dft;
+				return this.eType
+			},
+			set(val){
+				console.log('set dtype', arguments);
+			}
+		}
+	},
+	data() {
+		return {
+			imgurl_dft: '/packageShang/static/images/empty/empty.jpg',
+			imgurl: '',
+			imgs: [
+				{ type: 'cart', url: '/packageShang/static/images/empty/cart.jpg' },
+				{ type: 'search', url: '/packageShang/static/images/empty/search.jpg' },
+				{ type: 'other', url: '/packageShang/static/images/empty/empty.jpg' }
+			]
+		};
+	},
+	methods: {
+		to() {
+			this.$emit('goto', {
+				type: 'goto'
+			});
+
+			if (this.auto) {
+				// 跳转指定页
+				uni.navigateTo({
+					url: this.btnGoto
+				})
+			}
+		}
+	}
+};
+</script>
+
+<style lang="scss">
+.use-empty-container{
+	.use-empty {
+		.round {
+			padding: 25% 50rpx;
+
+			button{
+				width: 220rpx;
+			}	
+		}
+		image {
+			width: 160rpx;
+			height: 160rpx;
+		}
+
+		.title {
+			color: #c0c0c0;
+		}
+
+		.use-btn {
+			font-size: $font-base + 2upx;
+			display: inline-block;
+		}
+	}
+}
+</style>

+ 86 - 0
packageShang/components/use-header/use-header.vue

@@ -0,0 +1,86 @@
+<template>
+	<view>
+		<view class="use-header dflex padding-lr w-full bg-main" :class="fixed ? 'fixed' : ''">
+			<!-- 头部组件 -->
+			<view class="use-search dflex-b border-radius-lg padding-lr w-full" @click="search">
+				<view class="iconfont iconsousuo-01"><text class="text">{{ searchTip }}</text></view>
+			</view>
+		</view>
+		
+		<!-- 头部组件占位符 -->
+		<view v-if="fixed && placeholder" class="use-header-placeholder"></view>
+	</view>
+</template>
+
+<script>
+export default {
+	props: {
+		fixed: {
+			type: [Number, Boolean],
+			default: false
+		},
+		placeholder: {
+			type: [Number, Boolean],
+			default: !0
+		},
+		searchAuto: {
+			type: [Number, Boolean],
+			default: !0
+		},
+		searchTip: {
+			type: String,
+			default: '搜索关键字'
+		}
+	},
+	data() {
+		return {};
+	},
+	methods: {
+		search() {
+			this.$emit('search', {
+				type: 'search'
+			});
+
+			if (this.searchAuto) {
+				// 跳转搜索页
+				uni.navigateTo({
+					url: '/packageShang/pages/home/search/search'
+				})
+			}
+		}
+	}
+};
+</script>
+
+<style lang="scss">
+.use-header-placeholder {
+	height: 100rpx;
+}
+
+.use-header {
+	// height: 100rpx;
+}
+.fixed {
+    position: inherit;
+    z-index: 1;
+}
+.bg-main {
+    background: transparent!important;
+}
+
+.use-search {
+	height: 72rpx;
+	line-height: 72rpx;
+	background-color: #f5f5f5;
+
+	.text {
+		font-size: 28rpx;
+		color: rgba(204, 204, 204, 1);
+	}
+
+	.iconfont {
+		font-size: $font-base + 6upx;
+		color: rgba(153, 153, 153, 1);
+	}
+}
+</style>

+ 154 - 0
packageShang/components/use-hot-goods/use-hot-goods.vue

@@ -0,0 +1,154 @@
+<template>
+	<!-- 热卖产品 -->
+	<view class="use-hot-goods">
+		<!-- 列表标题 -->
+		<use-list-title v-if="hotDatas && hotDatas.length > 0" :title="title" size="32" fwt="600" :type="titleType" color="#333" iconfont="iconremen" @goto="hot"></use-list-title>
+		
+		<view class="list dflex-b dflex dflex-wrap-w w-full">
+			<view v-for="(item, index) in hotDatas" :key="index" class="item border-radius-sm padding-bottom-sm" @click="to_detail(item)">
+				<view class="image-wrapper" v-if="((item.imgs).indexOf(',')) != -1"><image mode="aspectFill" :lazy-load="true" :src="((item.imgs).substring(0, ((item.imgs).indexOf(','))))"></image></view>
+				<view class="image-wrapper" v-else><image mode="aspectFill" :lazy-load="true" :src="item.imgs"></image></view>
+				<text class="title clamp padding-sm">{{ item.name }}</text>
+				<view class="padding-left-sm">
+					<text class="price">{{ item.price }}</text>
+					<text class="m-price">{{ item.marketPrice  }}</text>
+				</view>
+			</view>
+		</view>
+		
+		<!-- 用云版权 -->
+		<use-copyright></use-copyright>
+	</view>
+</template>
+
+<script>
+	// import {
+	// 		getGoodshot
+	// 	} from '@/util/homeJie.js'
+	import useListTitle from '../../components/use-list-title/use-list-title.vue'
+export default {
+	components:{
+		useListTitle,
+	},
+	props: {
+		title: {
+			type: String,
+			default: '热卖产品'
+		},
+		titleType: {
+			type: String,
+			default: 'square'
+		},
+		autoload: {
+			type: String,
+			default: 'auto'
+		},
+		datas: {
+			type: Array,
+			default: () => []
+		},
+	},
+	data() {
+		return {
+			hotDatas: [
+			]
+		};
+	},
+	watch: {
+		datas(){
+			this.hotDatas = this.datas;
+		}
+	},
+	created() {
+		if(this.autoload === 'auto'){
+			// this.loadData();
+		}
+	},
+	methods: {
+		loadData() {
+			let _self = this;
+			_self.hotDatas=[]
+			var data='?hot=1'
+			uni.request({
+				url: common.url2 + '/goods/open/page'+data,
+				method:'GET',
+				success(res) {
+					res = res.data
+					if(res.success){
+						var total=res.data.totalCount
+						data='?hot=1&pageSize='+total
+						uni.request({
+							url: common.url2 + '/goods/open/page'+data,
+							method:'GET',
+							success(res) {
+								res = res.data
+								if(res.success){
+									res.data.list.forEach(data => {
+										_self.hotDatas.push(data)
+									})
+								}else{
+									_self.$message.warning('没有符合条件的数据!')
+								}
+							}
+						})
+					}else{
+						_self.$message.warning('没有符合条件的数据!')
+					}
+				}
+			})
+		},
+		// goto() {
+		// 	console.log('goto');
+		// 	this.$emit('goto', {
+		// 		type: 'goto'
+		// 	});
+		// },
+		hot() {
+			this.$api.togoodslist({hot: 1});
+		},
+		to_detail(options) {
+			this.$api.togoods({id: options.id});
+		}
+	}
+};
+</script>
+
+<style lang="scss">
+.use-hot-goods {
+	margin: 15rpx 0 0 30rpx;
+	width: 690rpx;
+	opacity: 1;
+	border-radius: 14rpx;
+	background-color: rgba(255, 255, 255, 1);
+	
+	.list{
+		padding: 0 3vw 20rpx;
+	}
+	
+	.item { 
+		width: 40vw;
+		overflow: hidden;
+		margin-top: 2vw;
+		background: #fff;
+
+		&:nth-child(2n) {
+			margin-left: 1vw;
+		}
+		&:nth-child(2n + 1) {
+			margin-right: 1vw;
+		}
+	}
+	
+	.image-wrapper {
+		width: 100%;
+		height: 300rpx;
+		overflow: hidden;
+	
+		image {
+			width: 100%;
+			height: 100%;
+			opacity: 1;
+		}
+	}
+}
+</style>

+ 90 - 0
packageShang/components/use-list-title/use-list-title.vue

@@ -0,0 +1,90 @@
+<template>
+	<view :class="type == 'round' ? 'padding-lr-sm' : ''">
+		<!-- 列表标题 -->
+		<view class="use-list-title dflex-b padding-lr w-full bg-main" :class="type == 'round' ? 'border-radius' : ''" @click="goto">
+			<view class="dflex">
+				<view v-if="iconfont && iconfont != ' '" class="iconfont __left margin-right-sm" :class="iconfont" :style="{ color: color }"></view>
+				<text :style="{fontSize: size + 'rpx', fontWeight: fwt }">{{ title }}</text>
+			</view>
+			<view class="dflex">
+				<text v-if="tip" class="tip margin-right-xs">{{tip}}</text>
+				<view class="iconfont fs-sm" :class="rightIcon" :style="{ color: rightColor }"></view>
+			</view>
+		</view>
+	</view>
+</template>
+
+<script>
+export default {
+	props: {
+		type: {
+			type: String,
+			default: 'square'
+		},
+		title: {
+			type: String,
+			default: '列表标题'
+		},
+		size: {
+			type: String,
+			default: '28'
+		},
+		fwt: {
+			type: String,
+			default: 'normal'
+		},
+		tip: {
+			type: [String, Number],
+			default: ''
+		},
+		iconfont: {
+			type: String,
+			default: 'icondaifahuo-'
+		},
+		color: {
+			type: String,
+			default: '#C0C4CC'
+		},
+		rightIcon: {
+			type: String,
+			default: 'iconjiantou-01'
+		},
+		rightColor: {
+			type: String,
+			default: '#c0c0c0'
+		}
+	},
+	data() {
+		return {};
+	},
+	methods: {
+		goto() {
+			this.$emit('goto', {
+				type: 'goto'
+			});
+		}
+	}
+};
+</script>
+
+<style lang="scss">
+.use-list-title {
+	height: 100rpx;
+	border-radius: 14px;
+	line-height: 100rpx;
+}
+.use-list-title {
+	.__left{
+		font-size: $font-lg + 8upx;
+	}
+	
+	.iconfont {
+		color: #c0c0c0;
+	}
+	
+	.tip {
+		font-size: $font-sm;
+		color: #c0c0c0;
+	}
+}
+</style>

+ 46 - 0
packageShang/components/use-loadmore/use-loadmore.vue

@@ -0,0 +1,46 @@
+<template>
+	<view class="use-loadmore padding-tb-sm dflex-c">
+		<view v-if="type !== 'nomore'" class="iconfont iconxiangqing animated rotate margin-right-sm ft-base"></view>
+		<text class="ft-dark">{{ tip || defaults[type] }}</text>
+	</view>
+</template>
+
+<script>
+export default {
+	props: {
+		type: {
+			type: String,
+			default: "more"
+		},
+		tip: {
+			type: String,
+			default: ""
+		},
+		defaults: {
+			type: Object,
+			default () {				
+				return {
+					more: '上拉显示更多',
+					loading: '用云 · 让开发更简单',
+					nomore: '已经到底了'
+				}
+			}
+		} 
+	},
+	data() {
+		return {
+			
+		};
+	},
+	computed: {
+		
+	},
+	methods: {
+		
+	}
+};
+</script>
+
+<style lang="scss">
+.use-loadmore {}
+</style>

+ 157 - 0
packageShang/components/use-login/use-login.vue

@@ -0,0 +1,157 @@
+<template>
+	<view class="use-login wh-full">
+		<!-- 弹出框 -->
+		<view v-if="is_show" class="l-mask"></view>
+		<view v-if="is_show" class="box-container">
+			<view class="title fs-lg"><text>{{title}}</text></view>
+			<view class="dflex dflex-flow-c margin-top">
+				<image class="image-sm" mode="aspectFill" src="../../static/images/user/default.png"></image>
+				<text class="">微信授权</text>
+			</view>
+			<view class="btn-contaer margin-top-lg">
+				<button class="no-border" @click="cancel">取消</button>
+				<button class="no-border" :open-type="type" :lang="lang" @getuserinfo="getUserInfo" @getphonenumber="getPhoneNumber" :withCredentials="withCredentials">确定</button>
+			</view>
+		</view>
+	</view>
+</template>
+
+<script>
+	export default {
+		props: {
+			// getUserInfo getPhoneNumber
+			type: {
+				type: String,
+				default: 'getUserInfo'
+			},
+			lang: {
+				type: String,
+				default: 'zh_CN'
+			},
+			withCredentials: {
+				type: Boolean,
+				default: true
+			},
+			title: {
+				type: String,
+				default: '获取授权信息'
+			},
+			show: {
+				type: [String, Number, Boolean],
+				default: 0
+			}
+		},
+		computed: {
+			__show: {
+				get(){
+					console.log('is_show', this.show);
+					this.is_show = this.show;
+					return this.show;
+				},
+				set(){
+					
+				}
+			}
+		},
+		data() {
+			return {
+				is_show: this.show
+			};
+		},
+		methods: {
+			cancel() {
+				this.is_show = false;
+				this.$emit('cancel', {
+					type: 'cancel'
+				});
+			},
+			getUserInfo(wx_userinfo){
+				this.is_show = false;
+				let _this = this;
+				if (!wx_userinfo.detail.iv) {
+					this.$emit('auth', {
+						type: 'userinfo',
+						result: 'cancel',
+					});
+					return false;
+				}
+				console.log('-------用户授权,并获取用户基本信息和加密数据------');
+				
+			},
+			getPhoneNumber(wx_phonenumber){
+				this.is_show = false;
+				uni.showLoading({
+					title: '加载中'
+				});
+				if (!wx_phonenumber.detail.iv) {
+					this.$emit('auth', {						
+						type: 'phonenumber',
+						result: 'cancel',
+					});
+					return false;
+				}
+				
+				console.log('-------用户授权,并获取用户基本信息和加密数据------');
+				console.log(wx_phonenumber.detail);
+				
+			}
+		}
+	};
+</script>
+
+<style lang="scss">
+	.use-login{
+		overflow: hidden;
+	}
+	
+	.use-login .l-mask {
+		position: absolute;
+		top: 0;
+		left: 0;
+		bottom: 0;
+		right: 0;
+		background: rgba(51, 51, 51, 0.5);
+		z-index: 999;
+	}
+	
+	.use-login .box-container {
+		position: absolute;
+		width: 80vw;
+		background: #fff;
+		left: 50%;
+		transform: translate(-50%, -50%);
+		top: 50%;
+		z-index: 999;
+		border-radius: 20rpx;
+		text-align: center;
+		padding: 30rpx;
+	
+		.title {
+			margin-top: 30rpx;
+			font-size: 34rpx;
+			font-weight: 600;
+		}
+	
+		.btn-contaer {
+			display: flex;
+		}
+	
+		button {
+			background: #eee;
+			color: #333;
+			width: 50%;
+			border-radius: 50rpx;
+			border: 1px solid #eee;
+			font-size: 30rpx;
+			padding: 16rpx 0;
+			line-height: inherit;
+			
+			&:last-child {
+				margin-left: 10px;
+				background: $base-color;
+				border: 1px solid $base-color;
+				color: #fff;
+			}
+		}
+	}
+</style>

+ 76 - 0
packageShang/components/use-mask/use-mask.vue

@@ -0,0 +1,76 @@
+<template>
+	<view class="use-mask pos-f pos-full overflow-hidden" :class="[show ? 'use-mask-show' : '']" :style="[maskStyle]" @touchmove.stop.prevent @tap="click">
+		<slot />
+	</view>
+</template>
+
+<script>
+	export default {
+		props: {
+			// 是否显示遮罩
+			show: {
+				type: Boolean,
+				default: false
+			},
+			// 层级z-index
+			zIndex: {
+				type: [Number, String],
+				default: '10000'
+			},
+			// 遮罩的动画样式, 是否使用使用zoom进行scale进行缩放
+			zoom: {
+				type: Boolean,
+				default: true
+			},
+			// 遮罩的过渡时间,单位为ms
+			duration: {
+				type: [Number, String],
+				default: 300
+			},
+			// 是否可以通过点击遮罩进行关闭
+			maskClickAble: {
+				type: Boolean,
+				default: true
+			}
+		},
+		computed: {
+			maskStyle() {
+				let style = {
+					transition: `all ${this.duration / 1000}s ease-in-out`,
+					zIndex: this.zIndex,
+				};
+				if (this.show) {
+					style.zIndex = this.zIndex;
+				} else {
+					style.zIndex = -1;
+				}
+				// 缩放
+				if (this.zoom) {
+					style.transform = 'scale(1.2, 1.2)';
+				}
+				
+				return style;
+			}
+		},
+		methods: {
+			click() {
+				if (this.maskClickAble) {
+					this.$emit('click');
+				}
+			}
+		}
+	}
+</script>
+
+<style lang="scss">
+	.use-mask {
+		opacity: 0;
+		background-color: rgba(0, 0, 0, 0.6);
+	}
+
+	.use-mask-show {
+		opacity: 1;
+		visibility: visible;
+		transform: scale(1);
+	}
+</style>

+ 211 - 0
packageShang/components/use-number-box/use-number-box.vue

@@ -0,0 +1,211 @@
+<template>
+	<view class="use-numbox pos-a dflex-c border-radius-big" :class="direction">
+		<view class="use-numbox-minus pos-r tac h-full" 
+			@tap.stop="_calcValue('subtract')"
+		>
+			<text class="iconfont iconjian fs-sm" :class="minDisabled?'use-numbox-disabled': ''" ></text>
+		</view>
+		<input 
+			class="use-numbox-value pos-r tac" 
+			type="number" 
+			:disabled="disabled"
+			:value="inputValue" 
+			
+			@blur="_onBlur"
+		>
+		<view 
+			class="use-numbox-plus pos-r tac h-full"
+			@tap.stop="_calcValue('add')"
+		>
+			<text class="iconfont iconjia fs-sm" :class="maxDisabled?'use-numbox-disabled': ''" ></text>
+		</view>
+	</view>
+</template>
+<script>
+	export default {
+		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
+			},
+			direction: {
+				type: String,
+				default: 'left'
+			}
+		},
+		data() {
+			return {
+				inputValue: 0,
+				minDisabled: false,
+				maxDisabled: false
+			}
+		},
+		created(){
+			this.inputValue = this.value;
+			this.maxDisabled = this.isMax;
+			this.minDisabled = this.isMin;
+		},
+		computed: {
+
+		},
+		watch: {
+			value(nVal, oVal) {
+				this.inputValue = nVal;
+			},
+			inputValue(nVal, oVal) {
+			}
+		},
+		methods: {
+			_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;
+				this.onChange();
+			},
+			_getDecimalScale() {
+				let scale = 1;
+				// 浮点型
+				if (~~this.step !== this.step) {
+					scale = Math.pow(10, (this.step + '').split('.')[1].length);
+				}
+				return scale;
+			},
+			_onBlur(event) {
+				console.log('_onBlur', event);
+				let value = event.detail.value;
+				if (!value) {
+					this.inputValue = 0;
+					this.onChange();
+					return
+				}
+				value = +value;
+				if (value > this.max) {
+					value = this.max;
+				} else if (value < this.min) {
+					value = this.min
+				}
+				
+				this.inputValue = 0;
+				this.$nextTick(() => {
+					this.inputValue = value;
+					this.onChange();
+				})
+			},
+			onChange() {
+				const data = {
+					number: this.inputValue,
+					index: this.index
+				}
+				this.$emit('eventChange', data);
+			}
+		}
+	}
+</script>
+<style>
+	.use-numbox {
+		position:absolute;
+		background:#f5f5f5;
+	}
+	
+	.use-numbox.left{
+		left: 30rpx;
+		bottom: 0;
+	}
+	.use-numbox.right{
+		width: 150px;
+		right: 0;
+		bottom: 10;
+	}
+
+	.use-numbox-minus,
+	.use-numbox-plus {
+		margin: 0;
+		background-color: #f5f5f5;
+		/* padding: 12rpx 20rpx; */
+	}
+	.use-numbox-minus .iconfont,
+	.use-numbox-plus .iconfont {
+		color: #555;
+		font-weight: 700;
+	}
+
+	.use-numbox-minus {
+		border-right: none;
+		border-top-left-radius: 6upx;
+		border-bottom-left-radius: 6upx;
+	}
+
+	.use-numbox-plus {
+		border-left: none;
+		border-top-right-radius: 6upx;
+		border-bottom-right-radius: 6upx;
+	}
+
+	.use-numbox-value {
+		background-color: #f5f5f5;
+		/* width: 66rpx; */
+		height: 50rpx;
+		padding: 0;
+	}
+
+	.use-numbox-disabled.iconfont {
+		color: #bbb;
+	}
+</style>

File diff suppressed because it is too large
+ 4914 - 0
packageShang/components/use-pickeraddr/data.js


+ 105 - 0
packageShang/components/use-pickeraddr/use-pickeraddr.vue

@@ -0,0 +1,105 @@
+<template>
+	<view>
+		<picker @change="bindPickerChange" @columnchange="columnchange" :range="array" range-key="name" :value="value" mode="multiSelector">
+			<slot></slot>
+		</picker>
+	</view>
+</template>
+
+<script>
+	import AllAddress from './data.js'
+	let selectVal = ['', '', '']
+
+	export default {
+		data() {
+			return {
+				value: [0, 0, 0],
+				array: [],
+				index: 0
+			}
+		},
+		created() {
+			this.initSelect()
+		},
+		methods: {
+			// 初始化地址选项
+			initSelect() {
+				this.updateSourceDate() // 更新源数据
+					.updateAddressDate() // 更新结果数据
+					.$forceUpdate() // 触发双向绑定
+			},
+			// 地址控件改变控件
+			columnchange(d) {
+				this.updateSelectIndex(d.detail.column, d.detail.value) // 更新选择索引
+					.updateSourceDate() // 更新源数据
+					.updateAddressDate() // 更新结果数据
+					.$forceUpdate() // 触发双向绑定
+			},
+
+			/**
+			 * 更新源数据
+			 * */
+			updateSourceDate() {
+				this.array = []
+				this.array[0] = AllAddress.map(obj => {
+					return {
+						name: obj.name
+					}
+				})
+				this.array[1] = AllAddress[this.value[0]].city.map(obj => {
+					return {
+						name: obj.name
+					}
+				})
+				this.array[2] = AllAddress[this.value[0]].city[this.value[1]].area.map(obj => {
+					return {
+						name: obj
+					}
+				})
+				return this
+			},
+
+			/**
+			 * 更新索引
+			 * */
+			updateSelectIndex(column, value) {
+				let arr = JSON.parse(JSON.stringify(this.value))
+				arr[column] = value
+				if (column === 0) {
+					arr[1] = 0
+					arr[2] = 0
+				}
+				if (column === 1) {
+					arr[2] = 0
+				}
+				this.value = arr
+				return this
+			},
+
+			/**
+			 * 更新结果数据 
+			 * */
+			updateAddressDate() {
+				selectVal[0] = this.array[0][this.value[0]].name
+				selectVal[1] = this.array[1][this.value[1]].name
+				selectVal[2] = this.array[2][this.value[2]].name
+				return this
+			},
+
+			/**
+			 * 点击确定
+			 * */
+			bindPickerChange(e) {
+				this.$emit('change', {
+					index: this.value,
+					data: selectVal
+				})
+				return this
+			}
+
+		}
+	}
+</script>
+
+<style>
+</style>

+ 280 - 0
packageShang/components/use-popup/use-popup.vue

@@ -0,0 +1,280 @@
+<template>
+	<view v-if="visibleSync" class="pos-f pos-full overflow-hidden use-popup" :class="{ 'use-popup-visible': showPopup }" :style="[customStyle]">
+		<use-mask :show="showPopup && mask" :maskClickAble="maskCloseAble" @click="maskClick"></use-mask>
+		<view class="pos-a use-popup-content" 
+			:class="[
+				bgclass,
+				safeAreaInsetBottom ? 'safe-area-inset-bottom' : '',
+				'use-popup-' + mode,
+				showPopup ? 'use-popup-content-visible' : '',
+				zoom && mode == 'center' ? 'use-animation-zoom' : ''
+			]" :style="[style]" @tap.stop.prevent @touchmove.stop.prevent @tap="modeCenterClose(mode)">
+			<view v-if="mode == 'center'" class="bg-main pos-r use-mode-center-box" :style="[centerStyle]" @tap.stop.prevent @touchmove.stop.prevent>
+				<slot />
+			</view>
+			<block v-else>
+				<slot />
+			</block>
+		</view>
+	</view>
+</template>
+
+<script>
+	import useMask from '../use-mask/use-mask.vue'
+	export default {
+		components:{
+			useMask
+		},
+		props: {
+			/**
+			 * 显示状态
+			 */
+			show: {
+				type: Boolean,
+				default: false
+			},
+			/**
+			 * 弹出方向,left|right|top|bottom|center
+			 */
+			mode: {
+				type: String,
+				default: 'left'
+			},
+			/**
+			 * 是否显示遮罩
+			 */
+			mask: {
+				type: Boolean,
+				default: true
+			},
+			/**
+			 * 背景 class 样式
+			 * */
+			bgclass: {
+				type: String,
+				default: 'bg-main'
+			},
+			// 抽屉的宽度(mode=left|right),或者高度(mode=top|bottom),单位rpx,或者"auto"
+			// 或者百分比"50%",表示由内容撑开高度或者宽度
+			length: {
+				type: [Number, String],
+				default: 'auto'
+			},
+			// 是否开启缩放动画,只在mode=center时有效
+			zoom: {
+				type: Boolean,
+				default: true
+			},
+			// 是否开启底部安全区适配,开启的话,会在iPhoneX机型底部添加一定的内边距
+			safeAreaInsetBottom: {
+				type: Boolean,
+				default: false
+			},
+			// 是否可以通过点击遮罩进行关闭
+			maskCloseAble: {
+				type: Boolean,
+				default: true
+			},
+			// 用户自定义样式
+			customStyle: {
+				type: Object,
+				default () {
+					return {};
+				}
+			},
+			value: {
+				type: Boolean,
+				default: false
+			},
+			// 此为内部参数,不在文档对外使用,为了解决Picker和keyboard等融合了弹窗的组件
+			// 对v-model双向绑定多层调用造成报错不能修改props值的问题
+			popup: {
+				type: Boolean,
+				default: true
+			},
+			// 显示显示弹窗的圆角,单位rpx
+			borderRadius: {
+				type: [Number, String],
+				default: 0
+			},
+			zIndex: {
+				type: [Number, String],
+				default: '10020'
+			}
+		},
+		data() {
+			return {
+				visibleSync: false,
+				showPopup: false,
+			};
+		},
+		watch: {
+			value(val) {
+				if (val) {
+					this.open();
+				} else {
+					if (this.showPopup) this.close();
+				}
+			}
+		},
+		computed: {
+			// 根据mode的位置,设定其弹窗的宽度(mode = left|right),或者高度(mode = top|bottom)
+			style() {
+				let style = {};
+				let translate = '100%';
+				// 判断是否是否百分比或者auto值,是的话,直接使用该值,否则默认为rpx单位的数值
+				let length = (/%$/.test(this.length) || this.length == 'auto') ? this.length : uni.upx2px(this.length) + 'px';
+				// 如果是左边或者上边弹出时,需要给translate设置为负值,用于隐藏
+				if (this.mode == 'left' || this.mode == 'top') { 
+					translate = length == 'auto' ? '-100%' : '-' + length; 
+				}
+				if (this.mode == 'left' || this.mode == 'right') {
+					style = {
+						width: length,
+						height: '100%',
+						transform: `translate3D(${translate},0px,0px)`
+					};
+				} else if (this.mode == 'top' || this.mode == 'bottom') {
+					style = {
+						width: '100%',
+						height: length,
+						transform: `translate3D(0px,${translate},0px)`
+					};
+				}
+				style.zIndex = this.zIndex;
+				
+				// 如果用户设置了borderRadius值,添加弹窗的圆角
+				if (this.borderRadius) {
+					switch (this.mode) {
+						case 'top':
+							style.borderRadius = `0 0 ${this.borderRadius}rpx ${this.borderRadius}rpx`;
+							break;
+						case 'right':
+							style.borderRadius = `${this.borderRadius}rpx 0 0 ${this.borderRadius}rpx`;
+							break;
+						case 'bottom':
+							style.borderRadius = `${this.borderRadius}rpx ${this.borderRadius}rpx 0 0`;
+							break;
+						case 'left':
+							style.borderRadius = `0 ${this.borderRadius}rpx ${this.borderRadius}rpx 0`;
+							break;
+						default:
+							break;
+					}
+					// 不加可能圆角无效
+					style.overflow = 'hidden';
+				}
+				return style;
+			},
+			// 中部弹窗的特有样式
+			centerStyle() {
+				let style = {};
+				let length = (/%$/.test(this.length) || this.length == 'auto') ? this.length : uni.upx2px(this.length) + 'px';
+				style.width = length;
+				style.zIndex = this.zIndex;
+				if (this.borderRadius) {
+					style.borderRadius = `${this.borderRadius}rpx`;
+					// 不加可能圆角无效
+					style.overflow = 'hidden';
+				}
+				return style;
+			}
+		},
+		created() {
+			// 先让弹窗组件渲染,再改变遮罩和抽屉元素的样式,让其动画其起作用(必须要有延时,才会有效果)
+			this.visibleSync = this.value;
+			this.$api.timerout(() => {
+				this.showPopup = this.value;
+			}, 30);
+		},
+		methods: {
+			open() {
+				this.change('visibleSync', 'showPopup', true);
+			},
+			close() {
+				this.change('showPopup', 'visibleSync', false);
+			},
+			// 遮罩被点击
+			maskClick() {
+				this.close();
+			},
+			// 中部弹出时,需要.use-popup-content将居中内容,此元素会铺满屏幕,点击需要关闭弹窗
+			// 让其只在mode=center时起作用
+			modeCenterClose(mode) {
+				if (mode != 'center' || !this.maskCloseAble) return;
+				this.close();
+			},
+			// 此处的原理是,关闭时先通过动画隐藏弹窗和遮罩,再移除整个组件
+			// 打开时,先渲染组件,延时一定时间再让遮罩和弹窗的动画起作用
+			change(param1, param2, state) {
+				// 如果this.popup为false,以为着为picker,actionsheet等组件调用了popup组件
+				if (this.popup) this.$emit('input', state);
+				this[param1] = state;
+				
+				this.$api.timerout(() => {
+					this[param2] = state;
+					this.$emit(state ? 'open' : 'close');
+				}, state ? 30 : 100);
+			}
+		}
+	};
+</script>
+
+<style lang="scss">
+	.use-popup {
+		z-index: 10010;
+	}
+
+	.use-popup-content {
+		z-index: 10020;
+		transition: all 0.25s ease-in-out;
+	}
+
+	.use-popup-top {
+		top: 0;
+		right: 0;
+		left: 0;
+	}
+	.use-popup-right {
+		top: 0;
+		right: 0;
+		bottom: 0;
+	}
+	.use-popup-bottom {
+		right: 0;
+		bottom: 0;
+		left: 0;
+	}
+	.use-popup-left {
+		top: 0;
+		bottom: 0;
+		left: 0;
+	}	
+	.use-popup-center {
+		display: flex;
+		align-items: center;
+		justify-content: center;
+		flex-direction: column;
+		top: 0;
+		right: 0;
+		bottom: 0;
+		left: 0;
+		opacity: 0;
+		z-index: 99999;
+	}
+
+	.use-mode-center-box {
+		min-width: 100rpx;
+		min-height: 100rpx;
+	}
+	.use-popup-content-visible.use-popup-center {
+		transform: scale(1);
+		opacity: 1;
+	}
+	.use-animation-zoom {
+		transform: scale(1.15);
+	}
+	.use-popup-content-visible {
+		transform: translate3D(0px, 0px, 0px) !important;
+	}
+</style>

File diff suppressed because it is too large
+ 1201 - 0
packageShang/components/use-qrcode/qrcode.js


+ 201 - 0
packageShang/components/use-qrcode/use-qrcode.vue

@@ -0,0 +1,201 @@
+<template xlang="wxml" minapp="mpvue">
+	<view class="use-qrcode">
+		<!-- #ifndef MP-ALIPAY -->
+		<canvas v-if="!result" class="use-qrcode-canvas" :canvas-id="cid" :style="{width:qrsize+'px',height:qrsize+'px'}" />
+		<!-- #endif -->
+		
+		<!-- #ifdef MP-ALIPAY -->
+		<canvas v-if="!result" :id="cid" :width="qrsize" :height="qrsize" class="use-qrcode-canvas" />
+		<!-- #endif -->
+		
+		<image v-show="show" :src="result" :style="{width:size+'px', height:size+'px'}" />
+	</view>
+</template>
+
+<script>
+import QRCode from "./qrcode.js"
+let qrcode
+export default {
+	name: "use-qrcode",
+	props: {
+		cid: {
+			type: String,
+			default: 'use-qrcode-canvas'
+		},
+		size: {
+			type: Number,
+			default: 150
+		},
+		qrsize: {
+			type: [Number, String],
+			default: 300
+		},
+		show: {
+			type: Boolean,
+			default: true
+		},
+		val: {
+			type: String,
+			default: ''
+		},
+		background: {
+			type: String,
+			default: '#ffffff'
+		},
+		foreground: {
+			type: String,
+			default: '#000000'
+		},
+		pdground: {
+			type: String,
+			default: '#000000'
+		},
+		icon: {
+			type: String,
+			default: ''
+		},
+		iconSize: {
+			type: Number,
+			default: 40
+		},
+		lv: {
+			type: Number,
+			default: 3
+		},
+		onval: {
+			type: Boolean,
+			default: false
+		},
+		loadMake: {
+			type: Boolean,
+			default: false
+		},
+		usingComponents: {
+			type: Boolean,
+			default: true
+		},
+		showLoading: {
+			type: Boolean,
+			default: true
+		},
+		loadingText: {
+			type: String,
+			default: '二维码生成中'
+		},
+	},
+	data() {
+		return {
+			result: '',
+		}
+	},
+	methods: {
+		_makeCode() {
+			let that = this
+			if (this._empty(this.val)) {
+				uni.showToast({
+					title: '二维码内容不能为空',
+					icon: 'none',
+					duration: 2000
+				});
+				return;
+			} 
+			
+			qrcode = new QRCode({
+				context: that, // 上下文环境
+				canvasId: that.cid, // canvas-id
+				usingComponents: that.usingComponents, // 是否是自定义组件
+				showLoading: that.showLoading, // 是否显示loading
+				loadingText: that.loadingText, // loading文字
+				text: that.val, // 生成内容
+				size: that.qrsize, // 二维码大小
+				background: that.background, // 背景色
+				foreground: that.foreground, // 前景色
+				pdground: that.pdground, // 定位角点颜色
+				correctLevel: that.lv, // 容错级别
+				image: that.icon, // 二维码图标
+				imageSize: that.iconSize,// 二维码图标大小
+				cbResult: function (res) { // 生成二维码的回调
+					that._result(res);
+					
+				},
+			});
+		},
+		_clearCode() {
+			this._result('')
+			qrcode.clear()
+		},
+		_saveCode() {
+			let that = this;
+			if (!this.result) {
+				return;
+			}
+			
+			uni.saveImageToPhotosAlbum({
+				filePath: that.result,
+				success: function () {
+					uni.showToast({
+						title: '二维码保存成功',
+						icon: 'success',
+						duration: 2000
+					});
+				}
+			});
+		},
+		_result(res) {
+			this.result = res;
+			this.$emit('result', res)
+		},
+		_empty(v) {
+			let tp = typeof v,
+				rt = false;
+			if (tp == "number" && String(v) == "") {
+				rt = true
+			} else if (tp == "undefined") {
+				rt = true
+			} else if (tp == "object") {
+				if (JSON.stringify(v) == "{}" || JSON.stringify(v) == "[]" || v == null) rt = true
+			} else if (tp == "string") {
+				if (v == "" || v == "undefined" || v == "null" || v == "{}" || v == "[]") rt = true
+			} else if (tp == "function") {
+				rt = false
+			}
+			return rt
+		}
+	},
+	watch: {
+		size: function (n, o) {
+			if (n != o && !this._empty(n)) {
+				this.cSize = n
+				if (!this._empty(this.val)) {
+					setTimeout(() => {
+						this._makeCode()
+					}, 100);
+				}
+			}
+		},
+		val: function (n, o) {
+			if (this.onval) {
+				if (n != o && !this._empty(n)) {
+					setTimeout(() => {
+						this._makeCode()
+					}, 0);
+				}
+			}
+		}
+	},
+	mounted: function () {
+		if (this.loadMake) {
+			if (!this._empty(this.val)) {
+				setTimeout(() => {
+					this._makeCode()
+				}, 0);
+			}
+		}
+	},
+}
+</script>
+
+<style>
+.use-qrcode { }
+.use-qrcode-canvas { position: fixed; top: -100vh; left: -100vw; z-index: -1; }
+</style>

+ 87 - 0
packageShang/components/use-rate/use-rate.vue

@@ -0,0 +1,87 @@
+<template>
+	<view class="use-rate dflex">
+		<view class="pos-r margin-left-sm" v-for="(star,index) in stars" :key="index" 
+			:style="{ marginLeft: margin+'px' }" @click="onClick(index)">
+			<view class="iconfont iconshoucang-01 ft-dark"></view>
+			<view class="pos-a active" :style="{ width: star.activeWitch }">
+				<view class="iconfont iconshoucang- ft-base"></view>
+			</view>
+		</view>
+	</view>
+</template>
+
+<script>
+	export default {
+		props: {
+			value: { //当前评分
+				type: [Number, String],
+				default: 0
+			},
+			margin: { //评分间距
+				type: [Number, String],
+				default: 0
+			},
+			max: { //最大评分
+				type: [Number, String],
+				default: 5
+			},
+			disabled: { //是否可点击
+				type: [Boolean, String],
+				default: false
+			}
+		},
+		data() {
+			// console.log('data')
+			return {
+				maxSync: this.max,
+				valueSync: this.value
+			}
+		},
+		computed: {
+			stars() {
+				const max = Number(this.maxSync) ? Number(this.maxSync) : 5
+				const value = Number(this.valueSync) ? Number(this.valueSync) : 0
+				const starList = []
+				const floorValue = Math.floor(value)
+				const ceilValue = Math.ceil(value)
+				for (let i = 0; i < 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'
+						})
+					}
+				}
+				return starList
+			}
+		},
+		methods: {
+			onClick(index) {
+				if (this.disabled || this.disabled === 'true') {
+					return
+				}
+				
+				this.valueSync = index + 1
+				this.$emit('change', {
+					value: this.valueSync
+				})
+			}
+		}
+	}
+</script>
+
+<style lang="scss">
+	.use-rate {
+		.active {
+			overflow: hidden;
+			top: 0;
+		}
+	}
+</style>

+ 120 - 0
packageShang/components/use-ring/use-ring.vue

@@ -0,0 +1,120 @@
+<template>
+	<view class="use-ring pos-r">
+		<view class="circle-ring pos-r">
+			<view class="circle-left" :style="circleLeftCss" @transitionend="leftSuccess"></view>
+			<view class="circle-right" :style="circleRightCss" @transitionend="rightSuccess"></view>
+			<view class="circle-bottom-left"></view>
+			<view class="circle-bottom-right"></view>
+		</view>
+		<view class="pos-a pos-tl-c ft-white">
+			<slot />
+		</view>
+	</view>
+</template>
+
+<script>
+	export default {
+		props: {
+			value: {
+				type: [Number, String],
+				default: 0
+			},
+			max: {
+				type: [Number, String],
+				default: 100
+			},
+			activeColor: {
+				type: String,
+				default: '#ffbc49'
+			},
+			baseColor: {
+				type: String,
+				default: '#fff'
+			}
+		},
+		data() {
+			return {
+				circleLeftCss: '',
+				circleRightCss: '',
+				type: 'right'
+			}
+		},
+		methods: {
+			draw(val) {
+				if (val !== 0) {
+					val = val || this.value;
+				}
+				
+				const percent = val / this.max;
+				
+				if (percent <= 0.5) {
+					this.circleRightCss = `transform: rotate(${percent * 360}deg)`;
+					this.type = 'right';
+				} else {
+					this.circleRightCss =
+						`transform: rotate(180deg); transition: opacity 0s step-end 1s, transform 1s linear; opacity: 0`;
+					this.circleLeftCss =
+						`transition: transform ${(percent - 0.5) / 0.5}s linear 1s; transform: rotate(${percent * 360 - 180}deg)`;
+					this.type = 'left';
+				}
+			},
+			leftSuccess() {
+				if (this.type == 'left') {
+					this.$emit('success');
+				}
+			},
+			rightSuccess() {
+				if (this.type == 'right') {
+					this.$emit('success');
+				}
+			}
+		}
+	}
+</script>
+
+<style lang="scss">
+	.use-ring {
+		
+		.circle-ring {
+			width: 200rpx;
+			height: 200rpx;
+			
+			-webkit-mask: radial-gradient(transparent 86rpx, #fff 88rpx);
+			overflow: hidden;
+			border-radius: 50%;
+			
+			view {
+				width: 50%;
+				height: 100%;
+				position: absolute;
+			}
+		}
+		
+		.circle-left {
+			background: #fff;
+			transform-origin: 100% 50%;
+			left: 0;
+			z-index: 0;
+		}
+
+		.circle-right {
+			background: #fff;
+			transition: transform 1s linear;
+			transform-origin: 0% 50%;
+			right: 0;
+			z-index: 2;
+		}
+
+		.circle-bottom-left {
+			background: #ffbc49;
+			left: 0;
+			z-index: -1;
+		}
+
+		.circle-bottom-right {
+			background: #ffbc49;
+			right: 0;
+			z-index: 1;
+		}
+	}
+</style>

+ 207 - 0
packageShang/components/use-stepper/use-stepper.vue

@@ -0,0 +1,207 @@
+<template>
+	<view class="use-stepper border-radius-big" :class="direction">
+		<view class="use-stepper-minus" 
+			@click="_calcValue('subtract')"
+		>
+			<text class="iconfont iconjian fs-sm" :class="minDisabled?'use-stepper-disabled': ''" ></text>
+		</view>
+		<input 
+			class="use-stepper-value" 
+			type="number" 
+			:disabled="disabled"
+			:value="inputValue" 
+			
+			@blur="_onBlur"
+		>
+		<view 
+			class="use-stepper-plus"
+			@click="_calcValue('add')"
+		>
+			<text class="iconfont iconjia fs-sm" :class="maxDisabled?'use-stepper-disabled': ''" ></text>
+		</view>
+	</view>
+</template>
+<script>
+	export default {
+		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
+			},
+			direction: {
+				type: String,
+				default: 'left'
+			}
+		},
+		data() {
+			return {
+				inputValue: this.value,
+				minDisabled: false,
+				maxDisabled: false
+			}
+		},
+		created(){
+			this.maxDisabled = this.isMax;
+			this.minDisabled = this.isMin;
+		},
+		computed: {
+
+		},
+		watch: {
+			inputValue(number) {
+				const data = {
+					number: number,
+					index: this.index
+				}
+				this.$emit('eventChange', data);
+			}
+		},
+		methods: {
+			_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>
+	.use-stepper {
+		position:absolute;
+		display: flex;
+		justify-content: flex-start;
+		align-items: center;
+		height: 70rpx;
+		background:#f5f5f5;
+	}
+	
+	.use-stepper.left{
+		left: 30upx;
+		bottom: 0;
+	}
+	.use-stepper.right{
+		right: 0;
+		bottom: 0;
+	}
+
+	.use-stepper-minus,
+	.use-stepper-plus {
+		margin: 0;
+		background-color: #f5f5f5;
+		width: 70rpx;
+		height: 100%;
+		line-height: 70rpx;
+		text-align: center;
+		position: relative;
+	}
+	.use-stepper-minus .iconfont,
+	.use-stepper-plus .iconfont {
+		color: #555;
+		font-weight: 700;
+	}
+
+	.use-stepper-minus {
+		border-right: none;
+		border-top-left-radius: 6upx;
+		border-bottom-left-radius: 6upx;
+	}
+
+	.use-stepper-plus {
+		border-left: none;
+		border-top-right-radius: 6upx;
+		border-bottom-right-radius: 6upx;
+	}
+
+	.use-stepper-value {
+		position: relative;
+		background-color: #f5f5f5;
+		width: 66rpx;
+		height: 50rpx;
+		text-align: center;
+		padding: 0;
+	}
+
+	.use-stepper-disabled.yticon {
+		color: #d6d6d6;
+	}
+</style>

+ 71 - 0
packageShang/components/use-totop/use-totop.vue

@@ -0,0 +1,71 @@
+<template>
+	<view class="use-totop fixed-top animated" :class="visible ? 'fade-in' : 'dn'" :style="{ bottom: bottom + 'rpx', right: right + 'rpx' }" @tap.stop="totop">
+		<text class="iconfont iconzhiding"></text>
+	</view>
+</template>
+
+<script>
+	export default {
+		props: {
+			top: {
+				type: Number,
+				default: 100
+			},
+			
+			right: {
+				type: String,
+				default: '30'
+			},
+			bottom: {
+				type: String,
+				default: '30'
+			},
+			
+			duration:{
+				type: Number,
+				default: 120
+			},
+			scrollTop: {
+				type: Number,
+				default: 0
+			}
+		},
+		watch: {
+			scrollTop(nv, ov) {
+				this.s_top = nv;
+				this.change();
+			}
+		},
+		data() {
+			return {
+				s_top: 0,
+				visible: false
+			};
+		},
+		methods: {
+			totop: function() {
+				uni.pageScrollTo({
+					scrollTop: 0,
+					duration: this.duration
+				})
+				
+				this.$emit('to', {
+					type: 'to',
+					scrollTop: this.s_top 
+				});
+			},
+			change(scrollTop) {
+				this.s_top = scrollTop;
+				if(this.s_top > this.top) {
+					if(!this.visible) this.visible = true;
+				} else {
+					if(this.visible) this.visible = false;
+				}
+			}
+		}
+	}
+</script>
+
+<style lang="scss">
+	.use-totop {}
+</style>

+ 180 - 0
packageShang/components/use-upload/use-upload.vue

@@ -0,0 +1,180 @@
+<template>
+	<view class="use-upload dflex dflex-wrap-w">
+		<view class="item pos-r" v-for="(item, index) in imgs" @click="preImage(item, imgs)" :key="index">
+			<!-- <video v-if="item.type.indexOf('video/') !== -1" :src="item.url"></video> -->
+			<image :src="item" mode="aspectFill"></image>
+			<view class="del pos-a bg-main dflex-c border-radius-c iconfont iconlajitong-01 ft-dark" @tap.stop="delImage(index)"></view>
+		</view>
+
+		<view class="item dflex-c" v-if="imgs.length < limit" @tap="chooseImage">
+			<view class="iconfont iconxiangji-01 fs-big ft-dark"></view>
+		</view>
+	</view>
+</template>
+
+<script>
+	import common from '../../../static/comon.js'
+	export default {
+		props: {
+			limit: {
+				type: Number,
+				default: 6
+			}
+		},
+		data() {
+			return {
+				imgs: []
+			};
+		},
+		methods: {
+			chooseImage() {
+				let _this = this;
+	// 			uni.chooseImage({
+	// 				count: _this.limit, //count: 6, //默认9
+	// 				sizeType: ['original', 'compressed'], //可以指定是原图还是压缩图,默认二者都有
+	// 				sourceType: ['album'], //从相册选择
+	// 				success: function(res) {
+ 
+	// 					console.log(JSON.stringify(res.tempFilePaths));
+	// 					this.imgs=JSON.stringify(res.tempFilePaths)
+ 
+	// 				}
+	// 			});
+
+				uni.chooseImage({
+					count: _this.limit,
+					// 可以指定是原图|压缩图,默认二者都有
+					sizeType: ['original', 'compressed'],
+					success: (res) => {
+						console.log(JSON.stringify(res.tempFilePaths));
+						// _this.imgs.push((res.tempFilePaths)[0])
+						// console.log(_this.imgs)
+						const file = res.tempFilePaths[0];
+						
+						// 文件上传
+						uni.uploadFile({
+							url:getApp().globalData.baseurl+ '/file/open/', //自己的后端接口(默认发送post请求)
+							filePath:file,
+							name:"file",  //这里应为自己后端文件形参的名字
+							data:{
+		//                     
+							},
+							header:{
+								'Content-type' : 'multipart/form-data'
+							},
+							success(res) {
+								res = res.data
+								console.log(JSON.parse(res).success)
+								if(JSON.parse(res).success){
+									_this.imgs.push(JSON.parse(res).data)
+									_this.sendData()
+								}
+							}
+						})
+						
+						// uni.showLoading({
+						// 	title: '上传中',
+						// 	mask: true
+						// });
+						
+						for (var i = 0; i < res.tempFilePaths.length; i++) {
+							console.log(i,'41')
+							//解决跨越问题,让通过微信上传的图片你能够展示在浏览器前端
+							// uni.getImageInfo({
+							// 	src: res.tempFilePaths[0],
+							// 	success: (path) => {
+							// 		pathToBase64(path.path).then(base64 => {
+							// 				console.log("base64="+base64); // 这就是转为base64格式的图片
+							// 				_this.imgs=base64
+							// 			})
+							// 			.catch(error => {
+							// 				console.error(error)
+							// 			})
+							// 	}
+							// })
+							// const result = uni.uploadFile({
+							// 	filePath: res.tempFilePaths[i],
+							// 	cloudPath: _this.$api.getFileName(res.tempFilePaths[i]),
+							// 	onUploadProgress: function(progressEvent) {
+							// 		console.log(progressEvent,'0p1')
+							// 		var percentCompleted = Math.round((progressEvent.loaded *
+							// 			100) / progressEvent.total);
+							// 	}
+							// }).then(uRes => {
+							// 	console.log(uRes, res.tempFiles[i],'0p')
+							// 	uRes.url = uRes.fileID;
+							// 	uRes.type = res.tempFiles[i].type || '';
+							// 	uRes.size = res.tempFiles[i].size;
+								
+							// 	_this.imgs.push(uRes);
+							// 	_this.sendData()
+							// }).catch(err => {
+							// 	console.log('use-upload', err);
+							// });
+						}
+
+						uni.hideLoading();
+					}
+				});
+			},
+			preImage(item, urls) {
+				if (item.type.indexOf('video/') !== -1) {
+					return;
+				}
+				
+				let _urls = urls.filter(x => x.type.indexOf('image/') !== -1).map(ele => {
+					return ele.url;
+				});
+				
+				console.log(item, urls);
+				uni.previewImage({
+					current: item.url,
+					urls: _urls,
+					longPressActions: {
+						itemList: ['发送给朋友', '保存图片', '收藏'],
+						success: function(data) {
+							console.log('选中了第' + (data.tapIndex + 1) + '个按钮,第' + (data.index + 1) + '张图片');
+						},
+						fail: function(err) {
+							console.log(err.errMsg);
+						}
+					}
+				});
+			},
+
+			delImage(idx) {
+				this.imgs.splice(idx, 1);
+				this.sendData();
+			},
+
+			sendData() {
+				this.$emit('upload', this.imgs);
+			}
+		}
+	};
+</script>
+
+<style lang="scss">
+	.use-upload {
+		.item {
+			width: 23%;
+			margin-right: 2%;
+			height: 150rpx;
+			border: 1px solid #f0f0f0;
+
+			image, video {
+				width: 100%;
+				height: 100%;
+			}
+		}
+
+		.del {
+			top: -30rpx;
+			right: -10rpx;
+			width: 50rpx;
+			height: 50rpx;
+			z-index: 99;
+			box-shadow: 0px 3px 5px #f0f0f0;
+		}
+	}
+</style>

+ 154 - 0
packageShang/pages/goods/goods-evaluate.vue

@@ -0,0 +1,154 @@
+<template>
+	<view class="">
+		<view class="gap"></view>
+		<view class="state-area dflex padding bg-main">
+			<view class="item margin-right-sm" :class="{ active: state == item.name }" v-for="(item, index) in stateDatas" :key="index" @click="change(item)">
+				<text style="font-size: 26rpx;">{{ item.name }}({{ item.cnt }})</text>
+			</view>
+		</view>
+		<view class="gap"></view>
+
+		<!-- 评价区 -->
+		<view class="evaluate-area">
+			<view class="padding-lr padding-top bg-main">
+				<view class="eva-box dflex-s padding-bottom-lg" v-for="(item, index) in evaluateDatas" :key="index">
+					<image class="portrait border-radius-c" :src="item.member_headimg"></image>
+					<view class="right-area flex1 padding-left-sm">
+						<view class="dflex-b ft-dark">
+							<view class="dflex">
+								<text class="name margin-right">{{ item.member_name }}</text>
+								<use-rate :value="item.review_cnt" disabled></use-rate>
+							</view>
+							<text class="time fs-xs">{{ $api.format(item.create_time, 'yyyy-MM-dd') }}</text>
+						</view>
+						<view class="fs-sm ft-main padding-top-xs padding-bottom-sm">{{ item.review_content }}</view>
+						<view class="dflex dflex-wrap-w">
+							<image v-for="(img, i) in item.review_imgs" :src="img" :key="i" mode="widthFix" class="" @click="preview(item.imgs, img)"></image>
+						</view>
+						<view class="">
+							<text class="fs-xs ft-dark">{{ item.goods_type || '套餐1' }}</text>
+						</view>
+					</view>
+				</view>
+			</view>
+		</view>
+	</view>
+</template>
+<script>
+export default {
+	data() {
+		return {
+			// 评价状态
+			stateDatas: [],
+			// 评价数据
+			evaluateDatas: [],
+			// 商品ID
+			goods_id: 0,
+			// 评价类型
+			state: '全部'
+		};
+	},
+	onLoad(res) {
+		this.goods_id = res.id;
+
+		this.loadData();
+	},
+	methods: {
+		loadData() {
+			const $ = this.$dbcmd.aggregate;
+			this.$db['usemall-goods-comment']
+				.collection()
+				.aggregate()
+				.match({
+					goods_id: $.eq(this.goods_id),
+					state: '显示'
+				})
+				.group({
+					_id: '$review_type',
+					num: $.sum(1)
+				})
+				.end()
+				.then(res => {
+					let __stateDatas = [];
+					let total = 0;
+					res.result.data.forEach(x => {
+						__stateDatas.push({ name: x._id, cnt: x.num });
+						total += x.num;
+					});
+
+					this.stateDatas = [{ name: '全部', cnt: total }, ...__stateDatas];
+				});
+
+			this.$db['usemall-goods-comment']
+				.where({ 
+					goods_id: this.goods_id,
+					state: '显示'
+				})
+				.whereif(this.state != '全部', { review_type: this.state })
+				.tolist({ rows: 30, orderby: 'create_time desc' })
+				.then(res => {
+					if (res.code === 200) {
+						this.evaluateDatas = res.datas;
+					}
+				});
+		},
+		change(options) {
+			this.state = options.name;
+
+			this.loadData();
+		},
+		preview(imgs, cur) {
+			if (!imgs) return;
+
+			uni.previewImage({
+				urls: imgs,
+				current: cur,
+				longPressActions: {
+					itemList: ['发送给朋友', '保存图片', '收藏'],
+					success: function(data) {
+						console.log(res);
+					},
+					fail: function(err) {
+						console.log(err);
+					}
+				}
+			});
+		}
+	}
+};
+</script>
+<style lang="scss">
+page {
+	background: $page-color-base;
+}
+
+.state-area {
+	.item {
+		text {
+			padding: 10rpx 20rpx;
+			border: 1px solid #f0f0f0;
+			border-radius: 30rpx;
+		}
+
+		&:last-child {
+			margin-right: 0;
+		}
+	}
+}
+
+.evaluate-area {
+	.portrait {
+		flex-shrink: 0;
+		width: 80rpx;
+		height: 80rpx;
+	}
+	.right-area {
+		image {
+			margin-right: 10rpx;
+			margin-bottom: 10rpx;
+			height: 200rpx;
+			width: 30%;
+		}
+	}
+}
+</style>

+ 442 - 0
packageShang/pages/goods/goods-list.vue

@@ -0,0 +1,442 @@
+<template>
+	<view :class="!empty ? 'padding-top-big' : '' ">
+
+		<!-- 空白页 -->
+		<use-empty v-if="empty" e-style="round" e-type="search" tip="搜索数据为空" btn-tip="重新搜索" height="70vh" :auto="false"
+			@goto="tosearch"></use-empty>
+
+		<!-- 列表区 -->
+		<view v-else>
+			<!-- 筛选区 -->
+			<view class="navbar pos-f w-full dflex bg-main" :style="{ position: headerPosition }">
+				<view class="nav-item dflex-c flex1 pos-r h-full" :class="{active: filterIndex === 0}"
+					@click="navbarClick(0)">
+					综合排序
+				</view>
+				<!-- <view class="nav-item dflex-c flex1 pos-r h-full" :class="{active: filterIndex === 1}"
+					@click="navbarClick(1)">
+					销量优先
+				</view> -->
+				<view class="nav-item dflex-c flex1 pos-r h-full" :class="{active: filterIndex === 2}"
+					@click="navbarClick(2)">
+					<text>价格</text>
+					<view class="">
+						<view class="iconfont iconjiantou02 ft-dark dflex-c"
+							:class="{active: priceOrder === 1 && filterIndex === 2}"></view>
+						<view class="iconfont iconjiantou ft-dark dflex-c"
+							:class="{active: priceOrder === 2 && filterIndex === 2}"></view>
+					</view>
+				</view>
+			</view>
+
+			<!-- 商品列表区 -->
+			<view class="goods-list">
+				<view class="list dflex-b dflex dflex-wrap-w w-full" style="padding-top: 60px;">
+					<view class="item bg-main border-radius-sm padding-bottom-sm" v-for="(item, index) in goodsDatas"
+						:key="index" @click="togoods(item)">
+						<view class="image-wrapper">
+							<image mode="aspectFill" :lazy-load="true" v-if="((item.imgs).indexOf(',')) != -1"
+							 :src="((item.imgs).substring(0, ((item.imgs).indexOf(','))))"></image>
+							 <image mode="aspectFill" :lazy-load="true" v-else
+							  :src="item.imgs"></image>
+						</view>
+						<text class="title clamp padding-sm">{{ item.name }}</text>
+						<view class="padding-left-sm dflex-b">
+							<text class="price">{{ item.price }}</text>
+							<text class="ft-dark margin-right-sm fs-xs">
+								<template v-if="item.state=='1'">已售 {{item.saleCnt }}</template>
+								<template v-if="item.state=='0'">已下架</template>
+								<template v-if="item.state=='2'">审核中</template>
+							</text>
+						</view>
+					</view>
+				</view>
+			</view>
+
+			<!-- 上拉加载更多 -->
+			<use-loadmore :type="loadmoreType"></use-loadmore>
+		</view>
+
+		<!-- 置顶 -->
+		<use-totop ref="usetop"></use-totop>
+
+		<!-- 03. 猜你想要 -->
+		<use-hot-goods v-if="empty" title-type="round" title="猜你想要"></use-hot-goods>
+	</view>
+</template>
+
+<script>
+	import {
+		searchadd
+	} from '../../utils/api_goods.js'
+	import {
+		goodslistlimit
+	} from '../../utils/api_home.js'
+	import useEmpty from '../../components/use-empty/use-empty.vue'
+	import useLoadmore from '../../components/use-loadmore/use-loadmore.vue'
+	import useTotop from '../../components/use-totop/use-totop.vue'
+	import useHotGoods from '../../components/use-hot-goods/use-hot-goods.vue'
+	export default {
+		components:{
+			useEmpty,
+			useLoadmore,
+			useTotop,
+			useHotGoods
+		},
+		data() {
+			return {
+				empty: false,
+				headerPosition: "fixed",
+				// 0综合排序 1销量优先 2价格排序
+				filterIndex: 0,
+				// 1价格从低到高 2价格从高到低
+				priceOrder: 0,
+				// 商品数据
+				goodsDatas: [],
+				// 加载更多状态
+				loadmoreType: 'more',
+				// 请求数据
+				reqdata: {
+					page: 1,
+					rows: 8,
+					sidx: 'sort',
+					sord: 'asc'
+				},
+				scrollTop: 0,
+				pan:0,//0是搜索,1是热门,2是限时
+				leiId:'',//类别id
+				leiLevel:'',//类别level
+				keyword:'',//关键字
+				now_date:'',//当前时间
+			};
+		},
+		watch: {
+			// 显示空白页
+			goodsDatas(e) {
+				let empty = e.length === 0;
+				if (this.empty !== empty) {
+					this.empty = empty;
+				}
+			}
+		},
+		onPageScroll(e) {
+			// 兼容iOS端下拉时顶部漂移
+			if (e.scrollTop >= 0) {
+				this.headerPosition = "fixed";
+			} else {
+				this.headerPosition = "absolute";
+			}
+			// this.scrollTop = e.scrollTop
+			this.$refs.usetop.change(e.scrollTop);
+		},
+		//下拉刷新
+		onPullDownRefresh() {
+			this.loadData('refresh');
+		},
+		//加载更多
+		onReachBottom() {
+			this.loadData();
+		},
+		onLoad(options) {
+			this.getNowDate()
+			var userId=options.userId || ''
+			var searchCnt=options.searchCnt || 1
+			var createBy=options.createBy || ''
+			var createTime=decodeURIComponent(options.createTime) || this.now_date
+			this.leiId=options.cid || ''
+			this.leiLevel=options.level || ''
+			this.keyword=decodeURIComponent(options.keyword) || ''
+			let title = '搜索列表';
+			if (options && options.hot) {
+				title = '热门推荐';
+				this.pan=1
+			} else if (options && options.limited) {
+				title = '限时精选';
+				this.pan=2
+			}
+
+			uni.setNavigationBarTitle({
+				title: title
+			})
+
+			for (let key in options) {
+				this.reqdata[key] = decodeURIComponent(options[key]);
+			}
+			var data={
+				"id": 0,
+				"userId": '',
+				"keyword": this.keyword,
+				"searchCnt": searchCnt,
+				"createBy": createBy,
+				"createTime": this.now_date,
+				"updateTime": this.now_date,
+				"updateBy": ""
+			}
+			var headers={
+			   'Content-Type': 'application/json; charset=utf-8',
+			}
+			//新增历史搜索
+			searchadd(data,headers).then((res) => {
+				if(res.success){
+				}
+			})
+
+			this.loadData();
+		},
+		methods: {
+			//获取当前时间
+			getNowDate() {
+			  var _this = this;
+			  // this.timer = setInterval(function() {
+				var aData = new Date();
+				var month = aData.getMonth() < 9 ? "0" + (aData.getMonth() + 1) : aData.getMonth() + 1;
+				var date = aData.getDate() <= 9 ? "0" + aData.getDate() : aData.getDate();
+				var date2 = aData.getDate() <= 9 ? "0" + (aData.getDate()-1) : (aData.getDate()-1);
+				var Hour = aData.getHours() <= 9 ? "0" + (aData.getHours()) : aData.getHours();
+				var Miunte = aData.getMinutes() <= 9 ? "0" + (aData.getMinutes()) : aData.getMinutes();
+				var Seconds = aData.getSeconds() <= 9 ? "0" + (aData.getSeconds()) : aData.getSeconds();
+				// console.log(aData.getTime())
+				_this.now_date = aData.getFullYear() + "-" + month + "-" + date + ' '+ Hour +":"+ Miunte +":"+ Seconds;
+				// console.log(aData.getFullYear() + "-" + month + "-" + date2)昨天
+			  // }, 86400000);
+			},
+			// 加载商品,下拉刷新|上拉加载
+			loadData(type = 'add', loading) {
+
+				if (this.loadmoreType === 'loading') {
+					// 防止重复加载
+					return;
+				}
+
+				if (loading == 1 || type == 'refresh') {
+					// 从首页开始加载
+					this.reqdata.page = 1;
+				}
+
+				// 没有更多直接返回 
+				if (type === 'add') {
+					if (this.loadmoreType === 'nomore') {
+						return;
+					}
+					// 加载中
+					this.loadmoreType = 'loading';
+				} else {
+					// 更多
+					this.loadmoreType = 'more'
+				}
+				
+				if (this.pan==2){
+					//限时精选
+					let _self = this;
+					_self.goodsDatas=[]
+					var data='?limited=1&orderField='+_self.reqdata.sidx+'&order='+_self.reqdata.sord
+					goodslistlimit(data).then((res) => {
+						if (res.success) {
+							var total=res.data.totalCount
+							data='?limited=1&pageSize='+total+'&orderField='+_self.reqdata.sidx+'&order='+_self.reqdata.sord
+							goodslistlimit(data).then((res) => {
+								if (res.success) {
+									res.data.list.forEach(data => {
+										_self.goodsDatas.push(data)
+									})
+								}else{
+									_self.$message.warning('没有符合条件的数据!')
+								}
+							})
+						}
+					})
+				}else if (this.pan==1){
+					let _self = this;
+					_self.goodsDatas=[]
+					var data='?hot=1'+'&orderField='+_self.reqdata.sidx+'&order='+_self.reqdata.sord
+					// 热门推荐
+					goodslistlimit(data).then((res) => {
+						if (res.success) {
+							var total=res.data.totalCount
+							data='?hot=1&pageSize='+total+'&orderField='+_self.reqdata.sidx+'&order='+_self.reqdata.sord
+							goodslistlimit(data).then((res) => {
+								if (res.success) {
+									res.data.list.forEach(data => {
+										_self.goodsDatas.push(data)
+									})
+								}else{
+									_self.$message.warning('没有符合条件的数据!')
+								}
+							})
+						}
+					})
+				}
+				
+				let _self = this;
+				//根据类别
+				var data='?categoryId='+_self.leiId+'&categoryLevel='+_self.leiLevel
+					+'&curPage='+_self.reqdata.page+'&pageSize='+_self.reqdata.rows
+					+'&goodsName='+_self.keyword+'&orderField='+_self.reqdata.sidx+'&order='+_self.reqdata.sord
+				goodslistlimit(data).then((res) => {
+					if (res.success) {
+						if (res.data) {
+							if (loading == 1 || type == 'refresh') {
+								_self.goodsDatas = [];
+							}
+							let _datas = [];
+							res.data.list.forEach((row) => {
+								if(row.state === '1'){
+									_datas.push(row);
+								}
+							});
+							_self.goodsDatas = [..._self.goodsDatas, ..._datas];
+											
+							if (res.data.list.length >= _self.reqdata.rows) {
+								_self.reqdata.page++;
+								_self.loadmoreType = 'more'
+							} else {
+								_self.loadmoreType = 'nomore'
+							}
+						} else {
+							_self.loadmoreType = 'nomore'
+						}
+					}
+					if (_self.goodsDatas.length === 0) {
+						_self.empty = true;
+					}
+										
+					if (loading == 1) {
+						uni.hideLoading()
+					} else if (type == 'refresh') {
+						uni.stopPullDownRefresh();
+					}
+				})
+			},
+			// 点击筛选 
+			navbarClick(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;
+				}
+
+				if (this.filterIndex == 0) {
+					// 综合排序
+					this.reqdata.sidx = 'sort';
+					this.reqdata.sord = 'asc';
+				} else if (this.filterIndex == 1) {
+					// 销量优先
+					this.reqdata.sidx = 'saleCnt';
+					this.reqdata.sord = 'desc';
+				} else if (this.filterIndex == 2) {
+					// 价格排序
+					this.reqdata.sidx = 'price';
+					if (this.priceOrder == 1) {
+						// 降序
+						this.reqdata.sord = 'desc';
+					} else if (this.priceOrder == 2) {
+						// 升序
+						this.reqdata.sord = 'asc';
+					}
+				}
+
+				uni.pageScrollTo({
+					duration: 300,
+					scrollTop: 0
+				})
+
+				this.loadData('refresh', 1);
+				uni.showLoading({
+					title: '正在加载'
+				})
+			},
+			// 搜索页
+			tosearch() {
+				if (this.$api.pages().length > 1) {
+					uni.navigateBack();
+					return;
+				}
+				this.$api.tosearch();
+			},
+			// 商品详情
+			togoods(options) {
+				this.$api.togoods({
+					id: options.id
+				});
+			}
+		},
+	}
+</script>
+
+<style lang="scss">
+	page {
+		background-color: $page-color-base;
+	}
+
+	.navbar {
+		top: var(--window-top);
+		left: 0;
+		height: 100rpx;
+		box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, .06);
+		z-index: 10;
+
+		.nav-item {
+			font-size: 30rpx;
+
+			&.active {
+				&:after {
+					content: '';
+					position: absolute;
+					left: 50%;
+					bottom: 0;
+					transform: translateX(-50%);
+					width: 120rpx;
+					height: 0;
+					border-bottom: 4rpx solid $base-color;
+				}
+			}
+		}
+
+		.iconfont {
+			width: 30rpx;
+			height: 14rpx;
+			font-size: 20rpx;
+			line-height: 1;
+			margin-left: 4rpx;
+		}
+	}
+
+	.goods-list {
+		.list {
+			padding: 0 3vw 20rpx;
+		}
+
+		.item {
+			width: 46vw;
+			overflow: hidden;
+			margin-top: 2vw;
+
+			&:nth-child(2n) {
+				margin-left: 1vw;
+			}
+
+			&:nth-child(2n + 1) {
+				margin-right: 1vw;
+			}
+		}
+
+		.image-wrapper {
+			width: 100%;
+			height: 300rpx;
+			overflow: hidden;
+
+			image {
+				width: 100%;
+				height: 100%;
+				opacity: 1;
+			}
+		}
+	}
+</style>

+ 911 - 0
packageShang/pages/goods/goods.vue

@@ -0,0 +1,911 @@
+<template>
+	<view>
+		<!-- 01. 轮播区 -->
+		<view class="swiper-area w-full pos-f">
+			<swiper class="h-full pos-r" indicator-dots circular="true" duration="400">
+				<swiper-item v-for="(item, index) in swiperDatas" :key="index">
+					<view class="wh-full"><image :src="item" class="wh-full loaded" lazy-load="true" mode="aspectFill"></image></view>
+				</swiper-item>
+			</swiper>
+		</view>
+
+		<!-- 02. 商品数据区 -->
+		<view class="goods-area bg-main padding">
+			<view class="price-box dflex-b">
+				<view>
+					<text class="price fwb fs-big">{{ goods.price || '' }}</text>
+					<text class="m-price" v-if="goods.marketPrice > 0">{{ goods.marketPrice || '' }}</text>
+				</view>
+				<view class="dflex fs-sm ft-dark">
+					<!-- #ifdef MP-WEIXIN || H5 -->
+					<view class="margin-right-sm dflex" @click="shareOpen">
+						<view class="iconfont iconfenxiang padding-right-xs"></view>
+						<text>分享</text>
+					</view>
+					<!-- #endif -->
+					<!-- #ifdef MP-ALIPAY || MP-BAIDU || MP-QQ || MP-TOUTIAO -->
+					<button class="btn no-border padding-0 fs-sm ft-dark" open-type="share" @click="shareOpen">
+						<view class="margin-right-sm dflex">
+							<view class="iconfont iconfenxiang padding-right-xs"></view>
+							<text>分享</text>
+							<text>{{ goods.shareCnt || '0' }}</text>
+						</view>
+					</button>
+					<!-- #endif -->
+					<view class="margin-right-sm dflex">
+						<view class="padding-right-xs padding-right-xs">已售</view>
+						<text>{{ goods.saleCnt || '0' }}</text>
+					</view>
+					<view class="dflex">
+						<view class="padding-right-xs padding-right-xs">库存</view>
+						<text>{{ goods.stockNum || '0' }}</text>
+					</view>
+				</view>
+			</view>
+			<text class="title1 fs">{{ goods.name || '' }}</text>
+		</view>
+		<view class="gap"></view>
+		<!-- 分享 -->
+		<use-popup mode="bottom" bgclass=" " v-model="shareShow">
+			<view class="share-area margin border-radius bg-main" @click="shareClose">
+				<view class="tac w-full padding-sm">分享</view>
+				<view class="padding-lr margin-bottom-xl dflex-b pos-r">
+					<!-- #ifdef MP-WEIXIN -->
+					<button class="dflex-c dflex-flow-c no-border btn" style="color: #07c160" open-type="share">
+						<view class="iconfont iconweixin padding-lr-sm border-radius-c fs-xxxl"></view>
+						<view class="dflex-c fs-sm ft-dark">微信好友</view>
+					</button>
+					<!-- <view class="vertical-line"></view> -->
+					<!-- #endif -->
+					<!-- <button class="dflex-c dflex-flow-c no-border btn ft-base" @click="createPoster">
+						<view class="iconfont iconhaibao1 padding-lr-sm border-radius-c fs-xxxl"></view>
+						<view class="dflex-c fs-sm ft-dark">生成海报</view>
+					</button> -->
+				</view>
+			</view>
+		</use-popup>
+
+		<!-- 海报二维码 -->
+		<view class="qrcode tac padding-tb pos-f pos-tl-c">
+			<!-- <use-qrcode :onval="true" :val="posterQRcode" :show="false" loading-text="生成海报中" qrsize="200" @result="posterQRcodeResult"></use-qrcode> -->
+		</view>
+
+		<!-- 海报 -->
+		<view v-if="posterShow && !posterUrl" class="poster pos-f pos-tl-c padding">
+			<l-painter custom-style="position: fixed;z-index: -1;top: -200vh;left: -100vw;" :board="posterData" isRenderImage @success="posterSuccess" />
+		</view>
+		<!-- <use-popup mode="bottom" bgclass=" " v-model="posterShow" @close="">
+			<view class="padding border-radius margin">
+				<view v-if="!posterUrl" class="tac bg-main padding border-radius pos-a pos-l-c" style="bottom: 45vh">海报生成中,请稍等</view>
+				<view class="w-full" style="height: 70vh"><image :src="posterUrl" class="wh-full" mode="aspectFit"></image></view>
+
+				<view class="padding w-full margin-top">
+					<view class="dflex-b border-radius-big"> -->
+						<!-- #ifdef MP -->
+						<!-- <view class="tac padding-tb-sm flex1 bg-base" @click="posterSave">保存到相册</view> -->
+						<!-- #endif -->
+						<!-- #ifdef H5 || MP-360 -->
+						<!-- <view class="tac padding-tb-sm flex1 bg-base">长按图片保存到相册</view> -->
+						<!-- #endif -->
+					<!-- </view>
+				</view>
+			</view>
+		</use-popup> -->
+
+		<!-- 03. 规格区 -->
+		<!-- <view v-if="skuDatas.length > 0" class="sku-area bg-main padding-lr padding-top padding-bottom-xs pos-r">
+			<view class="con dflex dflex-wrap-w">
+				<view
+					class="margin-right-sm margin-bottom-sm dflex bg-drak border-radius-lg padding-tb-16 padding-lr"
+					:class="{ active: item.selected }"
+					v-for="(item, index) in skuDatas"
+					:key="index"
+					@click="selectSKU(item)"
+				>
+					<text class="fs-xs">{{ item.name }}</text>
+				</view>
+			</view>
+		</view>
+		<view v-if="skuDatas.length > 0" class="gap"></view> -->
+
+		<!-- 04.01 优惠券 -->
+		<!-- <use-list-title title="优惠" tip="领取优惠券" color="#ff6a6c" iconfont="iconyouhui" @goto="couponShow = true"></use-list-title> -->
+		<!-- 04.01 优惠券弹出层 -->
+		<use-popup mode="bottom" v-model="couponShow" @open="couponOpen">
+			<!-- 优惠券区 -->
+			<scroll-view>
+				<view class="coupon-area padding bg-drak">
+					<view class="coupon-item bg-main pos-r fs-xs" v-for="(item, index) in couponDatas" :key="index">
+						<view class="content pos-r padding dflex-b">
+							<view class="">
+								<view class="margin-bottom-xs fs">{{ item.name }}</view>
+								<view class="ft-dark">有效期至 {{ item.end_time.split(' ')[0] }}</view>
+							</view>
+							<view class="tar">
+								<view class="margin-bottom-xs price">{{ item.price }}</view>
+								<view v-if="item.order_amount > 0" class="ft-dark">满{{ item.order_amount }}可用</view>
+								<view v-else class="ft-dark">不限</view>
+							</view>
+
+							<view class="circle l"></view>
+							<view class="circle r"></view>
+						</view>
+						<view class="dflex-b">
+							<text class="ft-dark padding-lr">{{ item.type }}</text>
+							<text class="ft-base padding-tb-sm padding-lr" @click="couponReceive(item.id)">立即领取</text>
+						</view>
+					</view>
+				</view>
+			</scroll-view>
+		</use-popup>
+
+		<!-- 04.02 服务标签 -->
+		<view class="bg-main padding-lr padding-top padding-bottom-xs pos-r" @click="tagShow = true">
+			<view class="dflex dflex-wrap-w">
+				<view v-for="(item, index) in tagDatas" :key="index" class="margin-right-sm margin-bottom-sm dflex">
+					<view class="iconfont iconyiwancheng- fwb fs-xs ft-base margin-right-xs"></view>
+					<text class="fs-xs">{{ item.data.tagName }}</text>
+				</view>
+			</view>
+			<!-- <view class="icon-detail pos-a"><view class="iconfont iconxiangqing ft-dark fs-sm"></view></view> -->
+		</view>
+		<view class="gap"></view>
+		<!-- 04.02 服务标签弹出层 -->
+		<use-popup mode="bottom" v-model="tagShow">
+			<view class="tac w-full padding-sm">服务说明</view>
+			<view class="padding-lr padding-bottom-sm">
+				<view v-for="(item, index) in tagDatas" :key="index" class="margin-right-sm margin-bottom-sm dflex dflex-s">
+					<view class="iconfont iconyiwancheng- fwb fs ft-base margin-right-xs"></view>
+					<view>
+						<view class="fs-sm">{{ item.data.tagName }}</view>
+						<view class="fs-xs">{{item.data.tagDesc}}</view>
+					</view>
+				</view>
+			</view>
+		</use-popup>
+
+		<!-- 05. 评价区 -->
+		<view class="evaluate-area" v-if="evaluateDatas.length > 0">
+			<use-list-title :title="evaluateTitle" tip="好评率 100%" color="#ff6a6c" iconfont=" " @goto="toevaluate"></use-list-title>
+			<view class="padding-lr bg-main">
+				<view class="eva-box dflex-s padding-bottom-lg" v-for="(item, index) in evaluateDatas" :key="index">
+					<image class="portrait border-radius-c" :src="item.member_headimg"></image>
+					<view class="right-area flex1 padding-left-sm">
+						<view class="dflex-b ft-dark">
+							<view class="dflex">
+								<text class="name margin-right">{{ item.member_name }}</text>
+								<use-rate :value="item.reviewCnt" disabled></use-rate>
+							</view>
+							<text class="time fs-xs">{{ $api.format(item.createTime, 'yyyy-MM-dd') }}</text>
+						</view>
+						<view class="fs-sm ft-main padding-top-xs padding-bottom-sm">{{ item.reviewContent }}</view>
+						<view class="dflex dflex-wrap-w">
+							<image
+								class=""
+								mode="widthFix"
+								v-for="(img, i) in item.reviewImgs"
+								:lazy-load="true"
+								:key="i"
+								:src="img"
+								@click="preview(item.reviewImgs, img)"
+							></image>
+							<!-- <image class="border-radius-xs" v-if="((item.imgs).indexOf(',')) != -1"
+							 mode="aspectFill" :lazy-load="true" :src="((item.imgs).substring(0, ((item.imgs).indexOf(','))))"></image>
+							 <image class="border-radius-xs" v-else
+							  mode="aspectFill" :lazy-load="true" :src="item.imgs"></image> -->
+						</view>
+						<view class="">
+							<text class="fs-xs ft-dark">{{ item.goods_type || '套餐1' }}</text>
+						</view>
+					</view>
+				</view>
+			</view>
+
+			<view class="gap"></view>
+		</view>
+
+		<!-- 06. 详情区 -->
+		<view class="detail-area bg-main">
+			<view class="d-header padding dflex-c"><text>图文详情</text></view>
+			<rich-text class="pro-detail" :nodes="goods.desc"></rich-text>
+		</view>
+
+		<!-- 07. 操作区 -->
+		<view class="oper-area pos-f dflex-b w-full padding-lr-sm">
+			<view class="btn-area dflex dflex-flow-c" @click="tohome">
+				<text class="iconfont iconshouye-1"></text>
+				<text>首页</text>
+			</view>
+
+			<!-- #ifndef MP-ALIPAY || H5 || MP-360 -->
+			<button class="btn no-border dflex" open-type="contact">
+				<view class="btn-area dflex-c dflex-flow-c">
+					<text class="iconfont iconkefu-01"></text>
+					<text>客服</text>
+				</view>
+			</button>
+			<!-- #endif -->
+
+			<view class="btn-area dflex dflex-flow-c" :class="{ active: favorite }" @click="tofavorite">
+				<text class="iconfont" :class="favorite ? 'iconshoucang-' : 'iconshoucang-01'"></text>
+				<text>收藏</text>
+			</view>
+			<view class="flex1 btn-container dflex-b border-radius-big">
+				<view class="tac padding-tb-sm flex1 bg-warn" v-if="(goods.stockNum > goods.saleCnt) && goods.state == 1" @click="tocart(goods)">加入购物车</view>
+				<view class="tac padding-tb-sm flex1 bg-base-gou" v-if="(goods.stockNum > goods.saleCnt) && goods.state == 1" @click="tobuy(goods)">立即购买</view>
+				<view class="tac padding-tb-sm flex1 bg-disabled" v-if="(goods.stockNum == goods.saleCnt) && goods.state == 1">已售磐</view>
+				<view class="tac padding-tb-sm flex1 bg-disabled" v-if="goods.state == 0">已下架</view>
+			</view>
+		</view>
+
+		<!-- #ifdef MP-ALIPAY -->
+		<view class="fixed-top dflex-c dflex-flow-c"><contact-button tnt-inst-id="0Xu_1aaW" scene="SCE00225456" size="50" color="#bbb" /></view>
+		<!-- #endif -->
+
+		<!-- 置顶 -->
+		<use-totop ref="usetop" bottom="120"></use-totop>
+	</view>
+</template>
+
+<script>
+// #ifdef MP-ALIPAY
+import aliParse from 'mini-html-parser2';
+// #endif
+import {
+		collectif,
+		footgoods,
+		goodsxiang,
+		collecttian,
+		cartadd,
+		pingjia
+	} from '../../utils/api_goods.js'
+import usePopup from '../../components/use-popup/use-popup.vue'
+import useListTitle from '../../components/use-list-title/use-list-title.vue'
+import useRate from '../../components/use-rate/use-rate.vue'
+import usetotop from '../../components/use-totop/use-totop.vue'
+import lPainter from '@/packageShang/uni_modules/lime-painter/components/lime-painter/';
+import uposter from '@/packageShang/common/poster.js';
+import { mapState } from 'vuex';
+export default {
+	components:{
+		lPainter,
+		useListTitle,
+		useRate,
+		usePopup,
+		usetotop
+	},
+	computed: {
+		...mapState(['islogin', 'member'])
+	},
+	data() {
+		return {
+			// 商品ID
+			id: 0,
+			// 分享ID
+			mid: 0,
+			// 商品数据
+			goods: {},
+			// 轮播图
+			swiperDatas: [],
+			// SKU
+			sku: {},
+			skuDatas: [],
+			// 分享
+			shareShow: false,
+			// 海报
+			posterQRcode: '',
+			posterUrl: '',
+			posterShow: false,
+			posterData: {},
+
+			//优惠券
+			couponShow: false,
+			couponDatas: [],
+			// 服务标签
+			tagShow: false,
+			tagDatas: [],
+			// 商品评价
+			evaluateDatas: [],
+			evaluateTitle: '评价',
+			// 商品详情
+			html_nodes: '',
+			// 收藏
+			favorite: false,//是否收藏
+
+			scrollTop: 0
+		};
+	},
+	watch: {
+		sku(e) {
+			this.goods.price = e.price;
+			this.goods.marketPrice = e.market_price;
+			this.goods.stockNum = e.num;
+		}
+	},
+	onShareAppMessage: function(ops) {
+		let _this = this;
+		let mid = 0;
+		if (_this.member && _this.member._id) {
+			mid = _this.member._id;
+		}
+
+		return {
+			title: _this.goods.share_title,
+			path: `/pages/goods/goods?id=${this.id}&mid=${mid}`, //这里设定都是以"/page"开头,并拼接好传递的参数
+			success: function(res) {
+				// 转发成功
+				console.log('转发成功', res);
+			},
+			fail: function(res) {
+				// 转发失败
+				console.log('转发失败', res);
+			}
+		};
+	},
+	onPageScroll(e) {
+		//this.scrollTop = e.scrollTop;
+		this.$refs.usetop.change(e.scrollTop);
+	},
+	onLoad(options) {
+
+		if (options) {
+			this.mid = options.mid || '';
+			if (options.id) {
+				this.id = options.id;
+			} else if (options.q) {
+				let query = decodeURIComponent(options.q) || decodeURIComponent(uni.getStorageInfoSync('__scene_query_q'));
+				this.resolveQueryq(query);
+			}
+		}
+
+		if (!this.id) {
+			this.$api.msg('商品ID无效');
+			return;
+		}
+		
+	},
+	onShow(options) {
+		if (!this.id) {
+			const query = decodeURIComponent(uni.getStorageInfoSync('__scene_query_q'));
+			this.resolveQueryq(query);
+		}
+		var _self=this
+		var header={
+			"Mall-Token": uni.getStorageSync('tokenId')
+		}
+		//是否收藏
+		var data=_self.id
+		collectif(data,header).then((res) => {
+			if(res.data){
+				_self.favorite=true
+			}else{
+				_self.favorite=false
+			}
+			_self.loadData();
+		})
+		
+		//商品足迹
+		footgoods(data,header).then((res) => {
+			if(res.success){
+			}
+		})
+		
+	},
+
+	methods: {
+		loadData() {
+			let _self = this;
+			var data=_self.id
+			_self.swiperDatas = []
+			goodsxiang(data).then((res) => {
+				if(res.success){
+					_self.swiperDatas=(res.data.imgs).split(",")
+					_self.goods = res.data;
+					
+					// 服务标签
+					if (typeof _self.goods.goodsServiceTags === 'string') {
+						_self.goods.goodsServiceTags = _self.goods.goodsServiceTags.split(',').filter(x => x);
+					}
+								
+					let __tagDatas = [];
+								
+					_self.goods.goodsServiceTags.forEach((data, index) => {
+						__tagDatas.push({
+							data: data,
+							selected: index == 0
+						});
+					});
+								
+					_self.tagDatas = __tagDatas;
+				}
+			})
+			
+			//商品评价
+			var data='?goodsId='+_self.id
+			pingjia(data).then((res) => {
+				console.log('评价',res)
+				if(res.success){
+					// 商品评价
+					_self.evaluateDatas = res.data.list;
+					if (res.data.evaluate_cnt) _self.evaluateTitle = `评价(${res.data.evaluate_cnt})`;
+		
+					// if (typeof res.data.goods.imgs === 'string') {
+					// 	_self.swiperDatas = res.data.goods.imgs.split(',').filter(x => x);
+					// } else {
+					// 	_self.swiperDatas = res.data.goods.imgs;
+					// }
+				}
+			})
+			// await this.$func.usemall
+			// 	.call('goods/detail', {
+			// 		goods_id: this.id,
+			// 		share_mid: this.mid
+			// 	})
+			// 	.then(res => {
+			// 		if (res.code === 200) {
+			// 			// 商品评价
+			// 			this.evaluateDatas = res.datas.evaluate;
+			// 			if (res.datas.evaluate_cnt) this.evaluateTitle = `评价(${res.datas.evaluate_cnt})`;
+
+			// 			if (typeof res.datas.goods.imgs === 'string') {
+			// 				this.swiperDatas = res.datas.goods.imgs.split(',').filter(x => x);
+			// 			} else {
+			// 				this.swiperDatas = res.datas.goods.imgs;
+			// 			}
+			// 			this.goods = res.datas.goods;
+			// 			// 商品详情
+			// 			let __goods_detail = res.datas.goods_detail;
+			// 			// #ifndef MP-ALIPAY
+			// 			this.html_nodes = __goods_detail.desc_mobile;
+			// 			// #endif
+
+			// 			// #ifdef MP-ALIPAY
+			// 			this.html_nodes = [];
+			// 			aliParse(__goods_detail.desc_mobile.replace(/"><*/gi, '"/><'), (err, nodes) => {
+			// 				if (!err) {
+			// 					this.html_nodes = nodes;
+			// 				}
+			// 			});
+			// 			// #endif
+
+			// 			// 商品SKU
+			// 			let __goods_skus = res.datas.goods_skus;
+			// 			if (__goods_skus.length > 0) {
+			// 				let __skuDatas = [];
+			// 				__goods_skus.forEach((sku, index) => {
+			// 					// 	{ id: 1, name: '45寸(大规格)', price: 788, market_price: 999, num: 0, selected: !0 },
+			// 					__skuDatas.push({
+			// 						id: sku._id,
+			// 						sku: sku.goods_sku,
+			// 						name: sku.spec,
+			// 						price: sku.price,
+			// 						market_price: sku.market_price || this.goods.market_price,
+			// 						num: sku.stock_num,
+			// 						selected: index == 0
+			// 					});
+			// 				});
+			// 				this.skuDatas = __skuDatas;
+			// 			}
+
+			// 			// SKU
+			// 			if (this.skuDatas.length > 0) {
+			// 				this.sku = this.skuDatas[0];
+			// 			}
+
+			// 			// 服务标签
+			// 			if (typeof this.goods.tags === 'string') {
+			// 				this.goods.tags = this.goods.tags.split(',').filter(x => x);
+			// 			}
+
+			// 			let __tagDatas = [];
+
+			// 			this.goods.tags.forEach((data, index) => {
+			// 				__tagDatas.push({
+			// 					name: data,
+			// 					selected: index == 0
+			// 				});
+			// 			});
+
+			// 			this.tagDatas = __tagDatas;
+
+			// 			// 收藏状态
+			// 			this.favorite = this.goods.collected === 1;
+			// 			return;
+			// 		}
+			// 		this.$api.msg(res.msg);
+			// 	});
+		},
+		// 处理 query q 数据
+		resolveQueryq(query) {
+			const arr = query
+				.split('/')
+				.slice(-1)[0]
+				.split('_');
+			if (arr.length == 2) this.mid = arr[1];
+			this.id = arr[0];
+		},
+		// 图片预览
+		preview(imgs, cur) {
+			if (!imgs) return;
+
+			uni.previewImage({
+				urls: imgs,
+				current: cur,
+				longPressActions: {
+					itemList: ['发送给朋友', '保存图片', '收藏'],
+					success: function(data) {
+						console.log(res);
+					},
+					fail: function(err) {
+						console.log(err);
+					}
+				}
+			});
+		},
+
+		// 打开分享
+		shareOpen() {
+			// if (!this.loginCheck()) return;
+			this.shareShow = true
+		},
+		// 关闭分享
+		shareClose() {
+			// if (!this.loginCheck()) return;
+			this.shareShow = false
+		},
+		// 创建海报
+		createPoster() {
+			if (this.posterUrl) {
+				this.posterShow = true;
+				return;
+			}
+			uni.showLoading({
+				title: '生成海报中'
+			});
+			
+			// #ifdef MP
+			// 此处的二维码内容,需自己在小程序端配置普通二维码规则
+			this.posterQRcode = `https://usemall.use-cloud.com/wxmp-product/${this.goods.id}_${this.member._id}`;
+			// #endif
+			
+			// #ifdef H5
+			// 如果为 h5,二维码内容需配置为线上版本产品详情路径
+			this.posterQRcode = `https://usemall-h5.use-cloud.com/#/pages/goods/goods?id=${this.goods.id}&mid=${this.member._id}`;
+			// #endif
+		},
+		// 海报二维码生成成功
+		posterQRcodeResult(res) {
+			// 获取产品海报数据
+			this.posterData = uposter.getGoodsData(this.member, this.goods, res);
+			// console.log('this.posterData', this.posterData);
+
+			this.posterShow = true;
+		},
+		// 海报生成完成
+		posterSuccess(res) {
+			this.posterUrl = res;
+
+			uni.hideLoading();
+		},
+		// 保存海报
+		posterSave() {
+			if (this.posterUrl) {
+				uni.showLoading({
+					title: '保存中'
+				})
+				
+				uni.saveImageToPhotosAlbum({
+					filePath: this.posterUrl,
+					success: function() {
+						uni.hideLoading();
+						
+						uni.showToast({
+							title: '海报保存成功',
+							icon: 'success',
+							duration: 2000
+						});
+					}
+				});
+			}
+		},
+
+		// 商品SKU
+		selectSKU(res) {
+			this.skuDatas.forEach(item => {
+				if (res.sku == item.sku) {
+					this.$set(item, 'selected', true);
+				} else {
+					this.$set(item, 'selected', false);
+				}
+			});
+
+			this.sku = res;
+		},
+
+		// 评论
+		toevaluate() {
+			uni.navigateTo({
+				url: `/pages/goods/goods-evaluate?id=${this.id}`
+			});
+		},
+		// 首页
+		tohome() {
+			this.$api.tohome();
+		},
+		// 收藏
+		tofavorite() {
+			// if (!this.loginCheck()) return;
+			
+			this.favorite = !this.favorite;
+			let _data = {
+				goods_id: this.id,
+				state: !this.favorite ? '已取消' : '已收藏'
+			};
+			var header={
+				"Mall-Token": uni.getStorageSync('tokenId')
+			}
+			var data=this.id
+			collecttian(data,header).then((res) => {
+				if(res.success){
+					!this.favorite ? this.$api.msg('取消成功') : this.$api.msg('收藏成功');
+					return;
+				}
+				this.$api.msg(res.msg);
+			})
+		},
+		// 加入购物车
+		tocart(params) {
+			var _self=this
+			var data='?num=1&'+'goodId='+params.id
+			cartadd(data).then((res) => {
+				if(res.success){
+					_self.$api.msg('加购成功');
+					return;
+				}
+				_self.$api.msg(res.msg);
+			})
+			// this.$func.usemall
+			// 	.call('goods/addcart', {
+			// 		goods_id: params._id,
+			// 		goods_num: 1,
+			// 		goods_sku: this.sku.id
+			// 	})
+			// 	.then(res => {
+			// 		if (res.code === 200) {
+			// 			this.$api.msg(res.datas.msg);
+			// 			return;
+			// 		}
+
+			// 		this.$api.msg(res.msg);
+			// 	});
+		},
+		// 立即购买
+		tobuy(item) {
+			let _this = this;
+			// if (!this.loginCheck()) return;
+			
+			uni.navigateTo({
+				url: `/packageShang/pages/order/create?goods_id=${this.id}&sku_id=${this.sku.id || ''}&type=1`
+			});
+		},
+		// 检测是否已登录
+		// loginCheck() {
+		// 	if (!this.islogin) {
+		// 		let _this = this;
+		// 		uni.showModal({
+		// 			title: '授权登录',
+		// 			success: function(res) {
+		// 				if (res.confirm) {
+		// 					_this.$api.tologin();
+		// 				}
+		// 			}
+		// 		});
+		// 		return false;
+		// 	}
+			
+		// 	return true;
+		// }
+	}
+};
+</script>
+
+<style lang="scss">
+page {
+	background: $page-color-base;
+	padding-bottom: 120rpx;
+}
+
+contact-button {
+	display: flex;
+	justify-content: center;
+	align-items: center;
+	width: 50px;
+	height: 50px;
+}
+
+.fixed-top {
+	bottom: 230rpx;
+}
+
+/* 01. 轮播区 */
+.swiper-area {
+	height: 720rpx;
+	top: 0;
+	z-index: -1;
+}
+
+/* #ifndef MP */
+.swiper-area {
+	margin-top: calc(44px + env(safe-area-inset-top));
+	margin-top: 44px;
+}
+/* #endif */
+
+/* #ifdef APP-PLUS */
+.swiper-area {
+	margin-top: 0;
+}
+/* #endif */
+
+/* 02. 商品数据区 */
+.goods-area {
+	margin-top: 720rpx;
+
+	.price-box {
+		display: flex;
+		align-items: baseline;
+	}
+	.title1 {
+		height: 46rpx;
+		line-height: 46rpx;
+	}
+
+	.title {
+		color: $font-color-dark;
+		height: 46rpx;
+		line-height: 46rpx;
+	}
+}
+
+.share-area {
+	.vertical-line {
+		right: 50%;
+		height: 40%;
+	}
+}
+
+/* 03. 规格区 */
+.sku-area .active {
+	background: $base-color;
+	color: #fff !important;
+}
+
+/* 04. 服务区 */
+.icon-detail {
+	right: 30rpx;
+	top: 24rpx;
+}
+
+/* 05. 评价 */
+.evaluate-area {
+	.portrait {
+		flex-shrink: 0;
+		width: 80rpx;
+		height: 80rpx;
+	}
+
+	.right-area {
+		image {
+			margin-right: 10rpx;
+			margin-bottom: 10rpx;
+			height: 200rpx;
+			width: 30%;
+		}
+	}
+}
+
+/* 06. 详情区 */
+.detail-area {
+	.d-header {
+		font-size: $font-base + 2upx;
+		position: relative;
+
+		text {
+			padding: 0 20rpx;
+			background: #fff;
+			position: relative;
+			z-index: 1;
+		}
+
+		&:after {
+			position: absolute;
+			left: 50%;
+			top: 50%;
+			transform: translateX(-50%);
+			width: 300rpx;
+			height: 0;
+			content: '';
+			border-bottom: 1px solid #ccc;
+		}
+	}
+
+	/* 产品详情 */
+	.pro-detail {
+		width: 100%;
+		overflow: hidden;
+		-webkit-touch-callout: none;
+
+		img {
+			width: 100%;
+			max-width: 100%;
+			overflow: hidden;
+		}
+	}
+}
+
+/* 07. 操作区 */
+.oper-area {
+	left: 0;
+	bottom: 0;
+	background: rgba(255, 255, 255, 0.95);
+	box-shadow: 0 0 20rpx 0 #f0f0f0;
+	height: 100rpx;
+	z-index: 95;
+
+	.btn-area {
+		font-size: $font-sm;
+		color: $font-color-base;
+		width: 96rpx;
+
+		.iconfont {
+			font-size: 40rpx;
+			line-height: 48rpx;
+		}
+	}
+}
+
+/* 优惠券区 */
+.coupon-area {
+	max-height: 60vh;
+	min-height: 30vh;
+
+	.coupon-item {
+		margin-bottom: 20rpx;
+
+		&:last-child {
+			margin-bottom: 0;
+		}
+
+		.content {
+			&:after {
+				position: absolute;
+				left: 0;
+				bottom: 0;
+				content: '';
+				width: 100%;
+				height: 0;
+				border-bottom: 1px dashed #f3f3f3;
+				transform: scaleY(50%);
+			}
+		}
+
+		.circle {
+			position: absolute;
+			bottom: -10rpx;
+			z-index: 10;
+			width: 20rpx;
+			height: 20rpx;
+			background: #f5f5f5;
+			border-radius: 50%;
+
+			&.r {
+				right: -6rpx;
+			}
+
+			&.l {
+				left: -6rpx;
+			}
+		}
+	}
+}
+</style>

+ 213 - 0
packageShang/pages/home/search/search.vue

@@ -0,0 +1,213 @@
+<template>
+	<view class="use-page">
+
+		<!-- 搜索内容区 -->
+		<view class="search-area pos-r w-full padding-lr dflex-b">
+			<view class="h-full flex1 dflex-c">
+				<view class="icon-search pos-a">
+					<text class="iconfont iconsousuo-01"></text>
+				</view>
+				<input type="text" class="pos-a padding-left padding-tb-xs border-radius-lg box-sizing-b" maxlength="20"
+					placeholder="请输入关键字" @input="shuru" v-model="keyword" />
+			</view>
+
+			<view class="bg-base-sou border-radius-big padding-tb-xs padding-lr margin-left" @click="search">搜索</view>
+		</view>
+		<view class="gap"></view>
+
+		<!-- 搜索历史区 -->
+		<view class="padding-lr w-full padding-top-lg" v-if="historyDatas">
+			<view class="dflex-b">
+				<view class="dflex">
+					<text>搜索历史</text>
+				</view>
+				<view class="iconfont iconfont iconlajitong-01 dflex-c ft-dark padding-sm" @click="clear"></view>
+			</view>
+			<view class="dflex dflex-wrap-w">
+				<view
+					class="item margin-right-sm margin-bottom-sm dflex bg-drak border-radius-lg padding-tb-xs padding-lr"
+					v-for="(item,index) in historyDatas" :key="index" @click="search('history', item)">
+					<text>{{item.keyword}}</text>
+				</view>
+			</view>
+		</view>
+		<!-- 热门搜索区 -->
+		<view class="padding-lr w-full padding-top-lg" v-if="hotDatas">
+			<view class="padding-bottom-sm dflex-b">
+				<view class="dflex">
+					<text>热门搜索</text>
+				</view>
+			</view>
+			<view class="dflex dflex-wrap-w">
+				<view
+					class="item margin-right-sm margin-bottom-sm dflex bg-drak border-radius-lg padding-tb-xs padding-lr"
+					v-for="(item, index) in hotDatas" :key="index" @click="search('hot', item)">
+					<text>{{item.keyword}}</text>
+				</view>
+			</view>
+		</view>
+	</view>
+</template>
+
+<script>
+	import {
+		searchlist,
+		searchhot,
+		searchclear
+	} from '../../../utils/api_goods.js'
+	const _history = 'usemall-search-history'
+	const _hot = 'usemall-search-hot'
+	import { mapState } from 'vuex';
+	export default {
+		computed: {
+			...mapState(['islogin'])
+		},
+		data() {
+			return {
+				// 搜索关键字
+				keyword: '',
+
+				// 历史搜索
+				historyDatas: [],
+				// 热门搜索
+				hotDatas: [],
+			};
+		},
+		onShow() {
+			// 初始化
+			this.keyword = '';
+
+			// 加载数据
+			this.loadData();
+		},
+		methods: {
+			loadData() {
+				let _self=this
+				//搜索历史列表
+				searchlist().then((res) => {
+					if(res.success){
+						_self.historyDatas = res.data.list
+					}else{
+						_self.$message.warning('没有符合条件的数据!')
+					}
+				})
+				//热门搜索列表
+				searchhot().then((res) => {
+					if(res.success){
+						_self.hotDatas = res.data.list
+					}else{
+						_self.$message.warning('没有符合条件的数据!')
+					}
+				})
+			},
+
+			shuru(event){
+				this.keyword=event.detail.value
+				console.log(this.keyword,'res')
+			},
+			// 搜索
+			search(type, item) {
+				switch (type) {
+					case 'history':
+						this.keyword=item.keyword
+						for( var i=0;i<this.historyDatas.length;i++){
+							if(item.keyword=this.historyDatas[i].keyword){
+								var id=this.historyDatas[i].id
+								var userId=this.historyDatas[i].userId
+								var searchCnt=parseInt(this.historyDatas[i].searchCnt)+1
+								var createBy=this.historyDatas[i].createBy
+								var createTime=this.historyDatas[i].createTime
+							}
+						}
+						this.$api.togoodslist({
+							keyword: item.keyword,
+							userId:userId,
+							searchCnt:searchCnt,
+							createBy:createBy,
+							createTime:createTime,
+						});
+						break;
+					case 'hot':
+						this.keyword=item.keyword
+						for( var i=0;i<this.historyDatas.length;i++){
+							if(item.keyword=this.historyDatas[i].keyword){
+								var id=this.historyDatas[i].id
+								var userId=this.historyDatas[i].userId
+								var searchCnt=parseInt(this.historyDatas[i].searchCnt)+1
+								var createBy=this.historyDatas[i].createBy
+								var createTime=this.historyDatas[i].createTime
+							}
+						}
+						this.$api.togoodslist({
+							keyword: item.keyword,
+							userId:userId,
+							searchCnt:searchCnt,
+							createBy:createBy,
+							createTime:createTime,
+						});
+						break;
+					default:
+						this.$api.togoodslist({
+							keyword: this.keyword,
+							searchCnt:1,
+						});
+						break;
+				}
+			},
+			// 清空搜索历史
+			clear() {
+
+				let _this = this;
+				var ids=[]
+				_this.historyDatas.forEach(data => {
+					ids.push(data.id)
+				})
+
+				uni.showModal({
+					title: '提示',
+					content: '清空搜索历史',
+					success: function(res) {
+						if (res.confirm) {
+							var data=ids
+							searchclear(data).then((res) => {
+								if(res.success){
+									_this.historyDatas = [];
+									return;
+								}
+								_this.$api.msg(res.msg)
+							})
+						} else if (res.cancel) {
+							console.log('用户点击取消');
+						}
+					}
+				});
+			}
+		}
+	};
+</script>
+
+<style lang="scss">
+	.search-area {
+		height: 120rpx;
+
+		input {
+			width: 560rpx;
+			padding-left: 100rpx;
+			height: 64rpx;
+			line-height: 64rpx;
+			background-color: #f5f5f5;
+			padding-top: 10px;
+		}
+
+		.icon-search {
+			top: 50%;
+			left: 40rpx;
+			transform: translate(0, -50%);
+			z-index: 1;
+
+			text {
+				color: #c0c0c0;
+			}
+		}
+	}
+</style>

+ 29 - 0
packageShang/pages/index/index.vue

@@ -0,0 +1,29 @@
+<template>
+	<view class="container">
+		
+		<view class="intro">本项目已包含uni ui组件,无需import和注册,可直接使用。在代码区键入字母u,即可通过代码助手列出所有可用组件。光标置于组件名称处按F1,即可查看组件文档。</view>
+		<text class="intro">详见:</text>
+		<uni-link :href="href" :text="href"></uni-link>
+	</view>
+</template>
+
+<script>
+	export default {
+		data() {
+			return {
+				href: 'https://uniapp.dcloud.io/component/README?id=uniui'
+			}
+		},
+		methods: {
+
+		}
+	}
+</script>
+
+<style>
+	.container {
+		padding: 20px;
+		font-size: 14px;
+		line-height: 24px;
+	}
+</style>

+ 453 - 0
packageShang/pages/order/create.vue

@@ -0,0 +1,453 @@
+<template>
+	<view>
+		<!-- 收货人 -->
+		<view class="gap"></view>
+		<use-list-title v-if="!(addrData && addrData.id)" color="#333" title="选择收货人" iconfont="icondizhi-" @goto="toaddr"></use-list-title>
+		<view v-else class="padding dflex-b bg-main" @click="toaddr">
+			<view class="dflex">
+				<view class="iconfont icondizhi- margin-right ft-main"></view>
+				<view class="w-full dflex-wrap-w">
+					<view class="margin-bottom-xs">
+						<text>{{ addrData.province }}{{ addrData.city }}{{ addrData.county }} {{ addrData.address }}</text>
+					</view>
+					<view>
+						<text>{{ addrData.receiverName }}</text>
+						<text class="margin-left">{{ addrData.phoneNum }}</text>
+					</view>
+				</view>
+			</view>
+
+			<view class="iconfont iconjiantou-01 fs-sm"></view>
+		</view>
+		<view class="gap"></view>
+
+		<view class="goods-area bg-main padding">
+			<!-- 商品列表 -->
+			<view class="goods-item" :class="{ 'margin-top': index > 0 }" v-for="(item, index) in goodsDatas"
+				:key="index">
+				<view class="pos-r">
+					<image mode="aspectFill" :src="item.goodsMasterImg"></image>
+					<!-- <view v-if="(item.goods.stock_num < 10 || item.goods.stock_num < item.goods.goods_num)"
+						class="disabled dflex-c dflex-flow-c pos-a pos-tl-c border-radius-c">
+						<text>库存不足</text><text class="margin-left-xs fs-xs" v-if="item.goods.stock_num > 0">剩余
+							{{item.goods.stock_num}}</text>
+					</view> -->
+				</view>
+				<view class="flex1 padding-left-sm">
+					<text class="title clamp-2">{{ item.goodsName || '' }} </text>
+					<view class="ft-dark fs-xs padding-top-xs">
+						<text v-if="cart_ids && cart_ids.length > 0" class="margin-right">× {{item.goodsCount}}</text>
+						<!-- {{ (item.goods_sku && item.goods_sku.spec) || '&nbsp;&nbsp;' }} -->
+					</view>
+					<view class="pos-r dflex-b padding-top">
+						<view class="price flex1">{{ item.goodsActualPrice || '' }}</view>
+
+						<!-- + - 数量 -->
+						<!-- <use-number-box v-if="!(cart_ids && cart_ids.length > 0)" :min="1" :max="item.goods.stock_num"
+							:value="item.goods.goods_num > item.goods.stock_num ? item.goods.stock_num : item.goods.goods_num"
+							:is-max="item.goods.goods_num >= item.goods.stock_num" :is-min="item.goods.goods_num === 1"
+							:index="index" direction="right" @eventChange="numberChange"></use-number-box> -->
+							<use-number-box v-if="!(cart_ids && cart_ids.length > 0)" :min="1"
+								:value="item.goodsCount"
+								:index="index" direction="right" @eventChange="numberChange"></use-number-box>
+					</view>
+				</view>
+			</view>
+		</view>
+		<view class="gap"></view>
+
+		<!-- 优惠券 -->
+		<!-- <use-list-title title="优惠券" :tip="couponName" color="#333" iconfont="iconyouhui" @goto="couponShow = true">
+		</use-list-title> -->
+		<!-- 优惠券弹出层 -->
+		<use-popup mode="bottom" v-model="couponShow" @open="couponOpen">
+			<!-- 优惠券区 -->
+			<!-- <view class="coupon-area padding bg-drak">
+				<view class="coupon-item bg-main pos-r fs-xs" v-for="(item, index) in couponDatas" :key="index">
+					<view class="content pos-r padding dflex-b">
+						<view class="">
+							<view class="margin-bottom-xs fs">{{ item.name }}</view>
+							<view class="ft-dark">有效期至 {{ item.end_time.split(' ')[0] }}</view>
+						</view>
+						<view class="tar">
+							<view class="margin-bottom-xs price">{{ item.price }}</view>
+							<view v-if="item.order_amount > 0" class="ft-dark">满{{ item.order_amount }}可用</view>
+							<view v-else class="ft-dark">不限</view>
+						</view>
+
+						<view class="circle l"></view>
+						<view class="circle r"></view>
+					</view>
+					<view class="dflex-b">
+						<text class="ft-dark padding-lr">{{ item.type }}</text>
+						<text class="ft-base padding-tb-sm padding-lr" @click="couponUse(item)">立即使用</text>
+					</view>
+				</view>
+				<view v-if="!couponDatas || couponDatas.length <= 0" class="coupon-none">
+					<text class="coupon-none-tip">开发中!!!!!</text>
+				</view>
+			</view> -->
+		</use-popup>
+		<view class="gap"></view>
+
+		<!-- 金额明细 -->
+		<view class="bg-main">
+			<view class="dflex-b padding-lr padding-tb-sm">
+				<view class="flex1">总金额</view>
+				<view class=""><text style="font-size: 24rpx;">¥</text>{{ goods_money }}</view>
+			</view>
+
+			<!-- <view v-if="total_coupon_money > 0" class="dflex-b padding-lr padding-tb-sm">
+				<view class="flex1">优惠金额</view>
+				<view class="ft-base">-¥{{ total_coupon_money }}</view>
+			</view> -->
+
+			<view class="dflex-b padding-lr padding-tb-sm">
+				<view class="margin-right-xl">备注</view>
+				<input class="flex1 padding-sm" type="text" v-model="order_desc" placeholder="请填写买家备注"
+					placeholder-class="placeholder" />
+			</view>
+		</view>
+		<view class="gap"></view>
+
+		<!-- 底部  -->
+		<view class="oper-area pos-f pos-bottom w-full dflex-b bg-main safe-area-inset-bottom padding-left">
+			<view>
+				<text class="fs-sm">实付款</text>
+				<text class="price margin-left-sm fs-xl">{{ total_money }}</text>
+			</view>
+			<view class="submit dflex-c bg-base-ding fs animated-all" :class="is_submit === 1 ? 'bg-disabled' : ''" @click="submit">提交订单</view>
+		</view>
+	</view>
+</template>
+
+<script>
+	import {
+		confirmorder,
+		submitorder
+	} from '../../utils/api_user.js'
+	import useNumberBox from '../../../packageShang/components/use-number-box/use-number-box.vue'
+	// 收获人地址
+	const _address = 'usemall-member-address'
+	export default {
+		components:{
+			useNumberBox
+		},
+		data() {
+			return {
+				// 收货地址
+				addrData: {},
+				// 商品数据
+				goodsDatas: [],
+				// 产品金额
+				goods_money: 0,
+
+				// 优惠券
+				couponShow: false,
+				couponName: '选择优惠券',
+				couponDatas: [],
+				// 优惠券类型 满减|折扣
+				coupon_type: '满减',
+				// 优惠券金额
+				coupon_money: 0,
+				// 实付金额
+				total_money: 0,
+				// 优惠金额
+				total_coupon_money: 0,
+
+				// 购物车 ids
+				cart_ids: [],
+				// 商品 id
+				goods_id: 0,
+				// 商品数量
+				goods_num: 1,
+				// 商品 sku id
+				goods_sku_id: 0,
+				// 使用优惠券ID
+				order_coupon_id: 0,
+				// 买家备注
+				order_desc: '',
+
+				is_submit: 1,
+				platform: '',
+				platform_name: '',
+				type:'',//确认订单type
+				submitToken:'',//提交令牌
+			};
+		},
+		onLoad(options) {
+			console.log(options)
+			this.type=options.type
+			let _this = this;
+			this.$api.get_env(res => {
+				this.platform = res.platform;
+				this.platform_name = res.platform_name;
+			});
+
+			// 商品 ids
+			this.goods_id = options.goods_id || '';
+			// 商品 sku
+			this.goods_sku_id = options.sku_id || '';
+
+			// 购物车 ids
+			if (options.cart_ids) {
+			this.cart_ids = options.cart_ids.split(',');
+}
+			// 加载商品数据
+			this.loadData();
+
+			// uni.$on('__event_choice_address', data => {
+			// 	this.addrData = data;
+			// });
+		},
+		onShow() {
+			// if (this.addrData && this.addrData.id) return;
+			// this.$db[_address].where('create_uid == $env.uid').tofirst()
+			// 	.then(res => {
+			// 		console.log('res', res);
+			// 		if (res && res.code === 200) {
+			// 			this.addrData = res.datas;
+			// 		}
+			// 	})
+
+		},
+		methods: {
+			// 加载数据
+			loadData() {
+				var _self=this
+				if(_self.type==1){
+					var goodsId=_self.goods_id
+				}else{
+					var goodsId=''
+				}
+				// 确认订单
+				var data={
+					"type": _self.type,
+					"goodId": goodsId
+				}
+				confirmorder(data).then((res) => {
+					if(res.success){
+						_self.goodsDatas = res.data.orderDetails;
+						_self.addrData=res.data.address
+						_self.submitToken=res.data.token.token
+						_self.calcTotalMoney();
+						_self.is_submit = 0;
+						return
+					}
+					_self.$api.msg(res.msg);
+				})
+			},
+			// 计算实际支付 总金额
+			calcTotalMoney() {
+				// 服务项总金额
+				let service_money = 0;
+
+				this.goods_money = 0;
+				this.goodsDatas.forEach(x => {
+					if (x.goods_sku && x.goods_sku.price) {
+						x.goods.price = x.goods_sku.price;
+						x.goods.stock_num = x.goods_sku.stock_num;
+					}
+					if (x.cart && x.goodsCount) {
+						x.goodsCount = x.goodsCount;
+					}
+
+					this.goods_money += (x.goodsActualPrice) * x.goodsCount;
+					this.total_money =this.goods_money
+				});
+
+				// 商品金额 + 服务金额 - 优惠金额
+				// if (this.coupon_type == '满减') {
+				// 	this.total_coupon_money = this.coupon_money;
+				// 	this.total_money = (this.goods_money + service_money - this.coupon_money).toFixed(2);
+				// } else if (this.coupon_type == '折扣') {
+				// 	this.total_coupon_money = (this.goods_money + service_money) - ((this.goods_money + service_money) *
+				// 		this.coupon_money).toFixed(2);
+				// 	this.total_money = ((this.goods_money + service_money) * this.coupon_money).toFixed(2);
+				// }
+			},
+			// +- 下单数量
+			numberChange(options) {
+				let data = this.goodsDatas[options.index];
+				data.goodsCount = options.number;
+
+				if (this.goods_id) this.goods_num = options.number;
+
+				this.calcTotalMoney();
+			},
+			// 打开优惠券
+			couponOpen() {
+				let _this = this;
+				// 加载可使用优惠券数据 couponDatas
+				
+			},
+			// 使用优惠券
+			couponUse(coupon) {
+				let _this = this;
+
+				_this.order_coupon_id = coupon._id;
+				_this.coupon_money = coupon.price;
+				_this.coupon_type = coupon.type;
+				_this.couponName = coupon.name;
+
+				_this.calcTotalMoney();
+
+				_this.couponShow = false;
+			},
+			// 提交订单
+			submit() {
+				if (!(this.addrData && this.addrData.id)) {
+					this.$api.msg('请选择收货人');
+					return;
+				}
+
+				if (this.is_submit) {
+					this.$api.msg('提交中');
+					return;
+				}
+				this.is_submit = 1;
+
+				let _this = this;
+				if(_this.type==1){
+					var goodsId=_this.goods_id
+					var goodNum=_this.goods_num
+				}else{
+					var goodsId=_this.cart_ids.toString()
+					var goodNum= _this.goodsDatas.reduce((sum,current)=>sum+current.goodsCount,0)
+				}
+
+				let obj = {
+					"submitToken": _this.submitToken,
+					"userAddressId": _this.addrData.id,
+					"buyerRemark": _this.order_desc,
+					"orderPaidAmt": _this.total_money,
+					"submitType": _this.type,
+					"orderFrom": "1",
+					"orderDetails": [
+						{
+							"goodsId": goodsId,
+							"goodsCount": goodNum
+						}
+					]
+				};
+				// 提交订单
+				submitorder(obj).then((res) => {
+					if(res.success){
+						// 跳转支付页
+						_this.$api.topay({
+							order_id: res.data.orderId,
+							money: res.data.orderAmount,
+							type: 'redirect'
+						});
+						return
+					}
+					_this.$api.msg(res.msg);
+					_this.is_submit = 0;
+				})
+			},
+			// 选择收货人
+			toaddr() {
+				uni.navigateTo({
+					url: '/packageShang/pages/user/address/address?source=1',
+					complete() {}
+				});
+			},
+		}
+	};
+</script>
+
+<style lang="scss">
+	page {
+		background: $page-color-base;
+		padding-bottom: 100rpx;
+	}
+
+	.goods-area {
+		.goods-item {
+			display: flex;
+
+			.disabled {
+				color: #fff !important;
+				width: 70%;
+				height: 70%;
+				background-color: rgba(51, 51, 51, 0.5);
+			}
+
+			image {
+				flex-shrink: 0;
+				display: block;
+				width: 180rpx;
+				height: 180rpx;
+				border-radius: 4rpx;
+			}
+		}
+	}
+
+	.oper-area {
+		z-index: 998;
+		box-shadow: 0 -1px 5px rgba(0, 0, 0, 0.1);
+
+		.submit {
+			width: 280rpx;
+			height: 100rpx;
+		}
+	}
+
+	/* 优惠券区 */
+	.coupon-area {
+		max-height: 60vh;
+		overflow: auto;
+
+		.coupon-item {
+			margin-bottom: 20rpx;
+
+			&:last-child {
+				margin-bottom: 0;
+			}
+
+			.content {
+				&:after {
+					position: absolute;
+					left: 0;
+					bottom: 0;
+					content: '';
+					width: 100%;
+					height: 0;
+					border-bottom: 1px dashed #f3f3f3;
+					transform: scaleY(50%);
+				}
+			}
+
+			.circle {
+				position: absolute;
+				bottom: -10rpx;
+				z-index: 10;
+				width: 20rpx;
+				height: 20rpx;
+				background: #f5f5f5;
+				border-radius: 50%;
+
+				&.r {
+					right: -6rpx;
+				}
+
+				&.l {
+					left: -6rpx;
+				}
+			}
+		}
+	}
+
+	.coupon-none {
+		width: 100%;
+		height: 100%;
+		line-height: 30vh;
+		display: flex;
+		align-items: center;
+		justify-content: center;
+
+		.coupon-none-tip {
+			color: #909399;
+		}
+	}
+</style>

+ 508 - 0
packageShang/pages/pay/pay.vue

@@ -0,0 +1,508 @@
+<template>
+	<view class="app w-full">
+		<view class="price-box dflex-c dflex-flow-c">
+			<view>支付金额</view>
+			<view class="price fs-xxxl margin-top-sm">{{money}}</view>
+		</view>
+
+		<view class="pay-type-list">
+			<!-- #ifdef MP-WEIXIN -->
+			<view class="type-item dflex-b pos-r padding-tb-sm" @click="changePayType('微信支付')">
+				<text class="iconfont iconweixin"></text>
+				<view class="item flex1">
+					<text class="tit">微信支付</text>
+					<text>推荐使用</text>
+				</view>
+				<label class="radio">
+					<radio value="" color="#FF6A6C" :checked="pay_way == '微信支付'" :disabled="money <= 0" />
+					</radio>
+				</label>
+			</view>
+			<!-- #endif -->
+			
+			<!-- #ifdef MP-ALIPAY -->
+			<view class="type-item dflex-b pos-r padding-tb-sm" @click="changePayType('支付宝')">
+				<text class="iconfont iconalipay"></text>
+				<view class="item flex1">
+					<text class="tit">支付宝支付</text>
+					<text>推荐使用</text>
+				</view>
+				<label class="radio">
+					<radio value="" color="#FF6A6C" :checked="pay_way == '支付宝'" :disabled="money <= 0" />
+					</radio>
+				</label>
+			</view>
+			<!-- #endif -->
+
+			<!-- #ifdef MP-BAIDU -->
+			<view class="type-item dflex-b pos-r padding-tb-sm" @click="changePayType('百度钱包')">
+				<text class="iconfont iconbaidu"></text>
+				<view class="item flex1">
+					<text class="tit">百度钱包</text>
+					<text>推荐使用</text>
+				</view>
+				<label class="radio">
+					<radio value="" color="#FF6A6C" :checked="pay_way == '百度钱包'" :disabled="money <= 0" />
+					</radio>
+				</label>
+			</view>
+			<!-- #endif -->
+
+			<!-- #ifdef MP-QQ -->
+			<view class="type-item dflex-b pos-r padding-tb-sm" @click="changePayType('QQ钱包')">
+				<text class="iconfont iconqq"></text>
+				<view class="item flex1">
+					<text class="tit">QQ钱包</text>
+					<text>推荐使用</text>
+				</view>
+				<label class="radio">
+					<radio value="" color="#FF6A6C" :checked="pay_way == 'QQ钱包'" :disabled="money <= 0" />
+					</radio>
+				</label>
+			</view>
+			<!-- #endif -->
+
+			<!-- #ifdef MP-TOUTIAO -->
+			<view class="type-item dflex-b pos-r padding-tb-sm" @click="changePayType('头条支付', '微信支付', 'MWEB')">
+				<text class="iconfont icontoutiao"></text>
+				<view class="item flex1">
+					<text class="tit">收银台</text>
+					<text>推荐使用</text>
+				</view>
+				<label class="radio">
+					<radio value="wxpay" color="#ff6a6c" colors="#ff6a6c" :checked="pay_way == '头条支付' && pay_trade_type == 'MWEB'"
+					 :disabled="money <= 0" />
+					</radio>
+				</label>
+			</view>
+			<!-- #endif -->
+
+			<!-- #ifdef H5 || MP-360 -->
+			<view class="type-item dflex-b pos-r padding-tb-sm" @click="changePayType('微信支付', '微信支付', 'NATIVE')">
+				<text class="iconfont iconweixin"></text>
+				<view class="item flex1">
+					<text class="tit">微信支付</text>
+					<text>推荐使用 扫一扫 微信支付二维码</text>
+				</view>
+				<label class="radio">
+					<radio value="wxpay" color="#ff6a6c" colors="#ff6a6c" :checked="pay_way == '微信支付' && pay_trade_type == 'NATIVE'" :disabled="money <= 0" /></radio>
+				</label>
+			</view>
+			<view class="qrcode tac padding-tb">
+				<use-qrcode
+					:onval="true"
+					:val="qrcode"
+					qrsize="200"
+					@result="qrcode_rs"
+				></use-qrcode>
+			</view>
+			
+			<view v-if="time_remaining" class="dflex-c">
+				<use-count-down :show-days="false" separator="zh" separator-color="#333" color="#fff" bg-color="#333" font-size="24" :timestamp="time_remaining" @end="ontimeend"></use-count-down>
+			</view>
+			<!-- #endif -->
+		</view>
+
+		<view class="padding w-full margin-top-big pos-a" style="bottom: 30rpx;">
+			<view class="dflex-b border-radius-big">
+				<!-- #ifndef H5 || MP-360 -->
+				<view class="tac padding-tb-sm flex1 bg-base-pay" :class="is_submit === 1 ? 'bg-disabled' : ''" @click="confirm">{{pay_tip}}</view>
+				<!-- #endif -->
+				
+				<!-- #ifdef H5 || MP-360 -->
+				<view class="tac padding-tb-sm flex1 bg-base-pay" :class="is_submit === 1 ? 'bg-disabled' : ''" @click="check">{{pay_tip}}</view>
+				<!-- #endif -->
+			</view>
+		</view>
+	</view>
+</template>
+
+<script>
+	import {
+		getapipay
+	} from '../../utils/api_user.js'
+	import common from '../../../static/comon.js'
+	export default {
+		data() {
+			return {
+				money: 0,
+				is_submit: 0,
+				// 平台支付方式
+				pay_way: '微信支付',
+				// 原始支付方式 微信支付 支付宝支付
+				pay_original: '',
+				// JSAPI,NATIVE,APP,H5支付固定传 MWEB
+				pay_trade_type: '',
+				pay_tip: '确认支付',
+				
+				qrcode: '',
+				time_remaining: 0,
+				order_id:'',
+				// out_order_no:'',//订单编号
+			};
+		},
+		computed: {
+
+		},
+		onLoad(options) {
+			this.money = options.money || 0;
+			this.order_id = options.order_id;
+			this.loadData();
+
+			// #ifdef MP-WEIXIN 
+			this.pay_way = '微信支付';
+			this.pay_original = '微信支付';
+			this.pay_trade_type = 'JSAPI';
+			// #endif
+			// #ifdef MP-ALIPAY 
+			this.pay_way = '支付宝';
+			this.pay_original = '支付宝支付';
+			this.pay_trade_type = '';
+			// #endif
+			// #ifdef MP-BAIDU 
+			this.pay_way = '百度钱包';
+			// #endif
+			// #ifdef MP-QQ
+			this.pay_way = 'QQ钱包';
+			// #endif
+			// #ifdef MP-TOUTIAO
+			this.$api.timerout(() => {
+				this.pay_way = '头条支付';
+				this.pay_original = '微信支付';
+				this.pay_trade_type = 'MWEB';
+			}, 0)
+			// #endif
+			// #ifdef H5 || MP-360
+			this.$api.timerout(() => {
+				this.pay_way = '微信支付';
+				this.pay_original = '微信支付';
+				this.pay_trade_type = 'NATIVE';
+			}, 0)
+			
+			this.pay_tip = '已完成支付';
+			// #endif
+		},
+
+		methods: {
+			qrcode_rs(res) {
+			},
+			loadData() {
+				let _this = this;
+			},
+			
+			//选择支付方式
+			changePayType(type, original, trade_type) {
+				if (this.money <= 0) return;
+
+				this.pay_way = type;
+				if (original) this.pay_original = original;
+				if (trade_type) this.pay_trade_type = trade_type;
+				
+				if(this.pay_trade_type == 'NATIVE') {
+					this.loadQRCode();
+				}
+			},
+			//确认支付
+			confirm() {
+				if (this.is_submit) return;
+
+				this.is_submit = 1;
+
+				let _this = this;
+				//获取小程序支付参数
+				var data=_this.order_id
+				getapipay(data).then((res) => {
+					if(res.success){
+						if(res.data){
+							var pay_datas={}
+							pay_datas = {
+								timeStamp:res.data.timeStamp,
+								nonceStr:res.data.nonceStr,
+								package:res.data.packageStr,
+								signType:res.data.signType,
+								paySign:res.data.paySign
+							};
+							// 检查当前 session 是否有效
+							if(uni.canIUse('checkSession')){
+								uni.checkSession({
+									success() {
+										// 调用支付
+										_this.topayment(pay_datas, _this.order_id);
+									},
+									fail() {
+										// 当前 session 无效,调用 uni.login 获取数据
+										uni.login({
+											success() {
+												// 调用支付
+												_this.topayment(pay_datas, _this.order_id);
+											},
+											fail() {
+								
+											}
+										})
+									},
+								})
+							} else {
+								// 调用支付
+								_this.topayment(pay_datas, _this.order_id);
+							}
+						}else {
+								uni.setStorage({
+									key: '__order_state',
+									data: '待付款',
+									success(res) {
+										console.log(res);
+									},
+									complete() {
+										_this.$api.toorder();
+									}
+								});
+							}
+						return;
+					}
+					_this.$api.timerout(() => {
+						if (typeof res.msg === 'object') {
+							res.msg = res.msg.errorMessage;
+						}
+						_this.$api.msg(res.msg, 5000);
+						_this.is_submit = 0;
+					}, 800);
+				})
+			},
+			topayment(pay_datas, order_id) {
+				let _this = this;
+				console.log(pay_datas)
+
+				wx.requestPayment({
+					...pay_datas,
+					success: function(pres) {
+						// #ifdef MP-TOUTIAO
+						if (pres.code !== 0) {
+							_this.is_submit = 0;
+							switch (pres.code) {
+								case 1:
+									_this.$api.msg('支付超时,请重新支付');
+									break;
+								case 2:
+									_this.$api.msg('已取消,请重新支付');
+									break;
+								case 3:
+									_this.$api.msg('支付关闭,请重新支付');
+									break;
+								case 4:
+									_this.$api.msg('支付取消,请重新支付');
+									break;
+								case 9:
+								default:
+									_this.$api.msg('支付失败,请重新支付');
+									break;
+							}
+							return;
+						}
+						// #endif
+
+						uni.redirectTo({
+							url: `/packageShang/pages/pay/success?order_id=${order_id}`
+						});
+						return;
+					},
+					fail: function(err) {
+						console.log('requestPayment fail:', err);
+
+						uni.setStorage({
+							key: '__order_state',
+							data: '待付款',
+							success(res) {
+								console.log(res);
+							},
+							complete() {
+								_this.$api.toorder();
+							}
+						});
+					}
+				});
+			},
+			// 检测订单支付状态
+			check(){
+				let _this = this;
+				
+				_this.$func.usemall.call("order/paystate", {
+					order_id: _this.order_id
+				}).then(res => {
+					// 商户后端查询的微信支付状态,通知收银台支付结果
+					/*
+					  0:支付成功
+					  1:支付超时
+					  2:支付失败
+					  3:支付关闭
+					  9:订单状态未知/未支付
+					*/
+					if (res.code == 200) {
+						let code = 9;
+						let trade_state = res.datas.trade_state || "";
+			
+						if (res.datas.pay_state == '已付款') {
+							code = 0
+						} else if (trade_state == 'SUCCESS') {
+							code = 0;
+						} else if (trade_state == 'NOTPAY' || trade_state == 'PAYERROR') {
+							code = 2;
+						} else if (trade_state == 'CLOSED') {
+							code = 3;
+						}
+						
+						switch (code) {
+							case 0:
+								uni.redirectTo({
+									url: `/pages/pay/success?order_id=${_this.order_id}`
+								});
+								break;
+							case 1:
+								_this.$api.msg('支付超时,请重新支付', 3500);
+								break;
+							case 2:
+								_this.$api.msg('已取消,请重新支付', 3500);
+								break;
+							case 3:
+								_this.$api.msg('支付关闭,请重新支付', 3500);
+								break;
+							case 4:
+								_this.$api.msg('支付取消,请重新支付', 3500);
+								break;
+							case 9:
+							default:
+								_this.$api.msg('支付失败,请重新支付', 3500);
+								break;
+						}
+						return;
+					}
+					
+				}).catch(err => {
+					
+				});
+			},
+			// #ifdef H5 || MP-360
+			loadQRCode(){
+				let _this = this;
+				let obj = {
+					order_id: _this.order_id,
+					pay_way: _this.pay_way,
+					pay_original: _this.pay_original,
+					pay_trade_type: _this.pay_trade_type,
+				};
+				uni.showLoading({
+					title: '请求中'
+				})
+				// this.$api.alert('二维码支付开发中');
+				_this.$func.usemall.call('order/pay', obj).then(res => {
+					console.log('支付接口', obj);
+					uni.hideLoading();
+					
+					if (res.code === 200) {
+						if (res.datas) {
+							_this.qrcode = res.datas.codeUrl;
+							_this.time_remaining = res.datas.time_remaining;
+						} else {
+							uni.setStorage({
+								key: '__order_state',
+								data: '待付款',
+								success(res) {
+									console.log(res);
+								},
+								complete() {
+									_this.$api.toorder();
+								}
+							});
+						}
+									
+						return;
+					}
+					
+					_this.$api.timerout(() => {
+						_this.$api.msg(res.msg, 5000);
+						_this.is_submit = 0;
+					}, 800);
+				});
+			},
+			ontimeend(){
+				let _this = this;
+				uni.showModal({
+				    title: '提示',
+				    content: '支付二维码已过期',
+					confirmText: '重新生成',
+				    success: function (res) {
+				        if (res.confirm) {
+				            _this.loadQRCode();
+				        } else if (res.cancel) {
+				            console.log('用户点击取消');
+				        }
+				    }
+				});
+			},
+			// #endif
+
+		}
+	}
+</script>
+
+<style lang='scss'>
+	.app { }
+
+	.price-box {
+		height: 266rpx;
+		font-size: 28rpx;
+		color: #909399;
+	}
+
+	.pay-type-list {
+		padding-left: 60rpx;
+		padding-right: 60rpx;
+
+		.type-item {
+			height: 120rpx;
+			font-size: 30rpx;
+		}
+
+		.iconfont {
+			width: 100rpx;
+			font-size: 52rpx;
+		}
+
+		.iconhuiyuan {
+			color: #fe8e2e;
+		}
+
+		.iconweixin {
+			color: #36cb59;
+		}
+
+		.iconalipay {
+			color: #01aaef;
+		}
+
+		.iconqq {
+			color: #13c6fe;
+		}
+
+		.iconbaidu {
+			color: #306cff;
+		}
+
+		.icontoutiao {
+			color: #f85959;
+		}
+
+		.tit {
+			font-size: $font-lg;
+			/* color: $font-color-dark; */
+			margin-bottom: 4rpx;
+		}
+
+		.item {
+			display: flex;
+			flex-direction: column;
+			font-size: $font-sm;
+			color: $font-color-light;
+		}
+	}
+</style>

+ 46 - 0
packageShang/pages/pay/success.vue

@@ -0,0 +1,46 @@
+<template>
+	<view class="dflex-flow-c">
+		<view class="dflex-c dflex-flow-c" style="margin-top: 180rpx;">
+			<view style="font-size: 115rpx" class="fs-big ft-base iconfont iconyiwancheng-"></view>
+			<view class="title">支付成功</view>
+		</view>
+		<view class="padding-lr-16" style="margin-top: 200rpx;">
+			<view class="dflex-b border-radius-big "><view class=" tac padding-tb-sm flex1 bg-base-succ" @click="toOrder">查看订单</view></view>
+			<view class=" border-radius-big margin-tb-lg padding-lr-16"><view class="tac padding-tb-sm flex1 " @click="fanhui">返回首页</view></view>
+		</view>
+	</view>
+</template>
+
+<script>
+export default {
+	data() {
+		return {
+			order_id: ''
+		};
+	},
+	onLoad(option) {
+		this.order_id = option.order_id;
+	},
+	methods: {
+		toOrder() {
+			if (this.order_id) {
+				this.$api.toorder();
+				return;
+			}
+		},
+		//返回首页
+		fanhui(){
+			wx.switchTab({
+				url:'pages/home3/home3'
+			})
+		}
+	}
+};
+</script>
+
+<style lang="scss">
+.title {
+	font-size: 38rpx;
+	color: #303133;
+}
+</style>

+ 146 - 0
packageShang/pages/tabbar.vue

@@ -0,0 +1,146 @@
+<template>
+	<view class="tabbar-container">
+		<block>
+			<view class="tabbar-item" v-for="(item, index) in tabbarList" :key="index" :class="[item.centerItem ? ' center-item' : '']" @click="changeItem(item)">
+				<view class="item-top"><image :src="currentItem == item.id ? item.selectIcon : item.icon"></image></view>
+				<view class="item-bottom" :class="[currentItem == item.id ? 'item-active' : '']">
+					<text>{{ item.text }}</text>
+				</view>
+			</view>
+		</block>
+	</view>
+</template>
+ 
+<script>
+export default {
+	props: {
+		currentPage: {
+			type: Number,
+			default: 0
+		}
+	},
+	data() {
+		return {
+			currentItem: 0,
+			tabbarList: [
+				{
+					id: 0,
+					path: '/packageShang/pages/tabbar/home',
+					icon: '/packageShang/static/images/tabbar/home.png',
+					selectIcon: '/packageShang/static/images/tabbar/home-active.png',
+					text: '首页',
+					centerItem: false
+				},
+				{
+					id: 1,
+					path: '/packageShang/pages/tabbar/category',
+					icon: '/packageShang/static/images/tabbar/category.png',
+					selectIcon: '/packageShang/static/images/tabbar/category-active.png',
+					text: '分类',
+					centerItem: true
+				},
+				{
+					id: 2,
+					path: '/packageShang/pages/tabbar/cart',
+					icon: '/packageShang/static/images/tabbar/cart.png',
+					selectIcon: '/packageShang/static/images/tabbar/cart-active.png',
+					text: '购物车',
+					centerItem: false
+				},
+				{
+					id: 3,
+					path: '/packageShang/pages/tabbar/user',
+					icon: '/packageShang/static/images/tabbar/user.png',
+					selectIcon: '/packageShang/static/images/tabbar/user-active.png',
+					text: '我的',
+					centerItem: false
+				}
+			]
+		};
+	},
+	mounted() {
+		this.currentItem = this.currentPage;
+		uni.hideTabBar();
+	},
+	methods: {
+		changeItem(item) {
+			let _this = this;
+			//_this.currentItem = item.id;
+			uni.redirectTo({
+				url: item.path
+			});
+		}
+	}
+};
+</script>
+<style>
+view {
+	padding: 0;
+	margin: 0;
+	box-sizing: border-box;
+}
+.tabbar-container {
+	position: fixed;
+	bottom: 0;
+	left: 0;
+	width: 100%;
+	height: 167rpx;
+	box-shadow: 0 0 5px #999;
+	display: flex;
+	align-items: center;
+	padding: 5rpx 0;
+	color: #999999;
+	background-color: white;
+	/* background-image: url('../static/images/tabbar/bgkuang.png'); */
+	background-size: 100%;
+}
+.tabbar-container .tabbar-item {
+	width: 33%;
+	height: 100rpx;
+	display: flex;
+	flex-direction: column;
+	justify-content: center;
+	align-items: center;
+	text-align: center;
+}
+.tabbar-container .item-active {
+	color: #1E7DFB;
+}
+.tabbar-container .center-item {
+	display: block;
+	position: relative;
+}
+.tabbar-container .tabbar-item .item-top {
+	width: 70rpx;
+	height: 70rpx;
+	padding: 10rpx;
+	top: 24px;
+	position: absolute;
+}
+.tabbar-container .center-item .item-top {
+	flex-shrink: 0;
+	width: 70rpx;
+	height: 70rpx;
+	position: absolute;
+	top: 10rpx;
+	left: calc(60% - 50rpx);
+	border-radius: 50%;
+	/* box-shadow: 0 0 5px #999; */
+	/* background-color: #ffffff; */
+}
+.tabbar-container .tabbar-item .item-top image {
+	width: 100%;
+	height: 100%;
+}
+.tabbar-container .tabbar-item .item-bottom {
+	font-size: 28rpx;
+	width: 100%;
+	top: 55px;
+	position: absolute;
+}
+.tabbar-container .center-item .item-bottom {
+	position: absolute;
+	top: 37px;
+	position: absolute;
+}
+</style>

+ 563 - 0
packageShang/pages/tabbar/cart.vue

@@ -0,0 +1,563 @@
+<template>
+	<view class="container bg-drak" :class="{ 'margin-bottom-big': !empty }">
+		<!-- <view class="top-bg">
+			<view class="top-kuang">
+				<view class="top-title">购物车</view>
+			</view>
+		</view> -->
+		<!-- 00. 未授权登录 -->
+		<!-- <use-empty v-if="!islogin" e-style="round" e-type="unauthorized" tip="当前未授权" btn-tip="去登录" height="70vh"
+			:auto="false" @goto="tologin">11</use-empty> -->
+		<!-- 00. 空白页 -->
+		<use-empty v-if="empty" :auto="true" e-style="round" e-type="cart" tip="购物车数据为空" height="70vh"></use-empty>
+		<!-- 00. 列表 -->
+		<view v-else>
+			<!-- 01. 购物车列表1 -->
+			<view class="cart-list padding-sm">
+				<block v-for="(item, index) in cartDatas" :key="index">
+					<view class="cart-item bg-main margin-bottom-sm padding-lg pos-r dflex-s border-radius">
+						<view class="image-wrapper pos-r" @click="togoods(item)">
+							<!-- 商品图片 -->
+							<image class="border-radius-xs wh-full" mode="aspectFill" :lazy-load="true"
+								:src="item.goodsMasterImg">
+							</image>
+							<!-- 选中|未选中按钮 -->
+							<view v-if="item.limitNum > 0 && item.limitNum >= item.goodsCount"
+								class="iconfont checkbox pos-a bg-main border-radius-big"
+								:class="{active: item.check, iconxuanzhongzhuangtai: item.check, iconweixuanzhongzhuangtai: !item.check}"
+								@tap.stop="check('item', index)"></view>
+
+							<view v-if="(item.limitNum < item.goodsCount)"
+								class="disabled dflex-c dflex-flow-c pos-a pos-tl-c border-radius-c">
+								<text>库存不足</text><text class="margin-left-xs fs-xs" v-if="item.limitNum > 0">剩余
+									{{item.limitNum}}</text>
+							</view>
+						</view>
+						<view class="item-right padding-left pos-r">
+							<!-- 商品名称 -->
+							<view class="clamp-2 title" @click="togoods(item)">{{item.goodsName}}
+							</view>
+							<view class="ft-dark fs-xs padding-top-xs">{{ item.goods_sku.spec || '&nbsp;&nbsp;' }}</view>
+							<view class="padding-tb-sm">
+								<text class="price">{{ item.goodsActualPrice }}</text>
+								<text class="m-price"
+									v-if="item.marketPrice > 0">{{ item.marketPrice}}</text>
+							</view>
+
+							<!-- + - 购物车数量 -->
+							<use-number-box :min="1" :max="item.limitNum || 1" :value="item.goodsCount"
+								:is-max="item.goodsCount >= item.limitNum" :is-min="item.goodsCount === 1"
+								:index="index" :disabled="item.goodsCount >= item.limitNum"
+								@eventChange="numberChange">
+							</use-number-box>
+						</view>
+
+						<!-- 删除 -->
+						<view class="del-btn iconfont iconlajitong-01 pos-a border-radius-c dflex-c ft-dark fs-xl"
+							@tap.stop="deleteCart(item.goodsId)"></view>
+					</view>
+				</block>
+			</view>
+
+			<!-- 02. 底部操作栏 -->
+			<view class="action-section dflex w-full bg-main pos-f padding-right">
+				<view class="checkbox pos-r h-full dflex-c">
+					<view class="padding-lr iconfont"
+						:class="{active:allChecked,iconxuanzhongzhuangtai: allChecked, iconweixuanzhongzhuangtai: !allChecked}"
+						@click="check('all')"></view>
+					<view class="clear-btn pos-a tac ft-white" :class="{ show: allChecked }" @click="clearCart">清空
+					</view>
+				</view>
+				<view class="total-box flex1 tar padding-right-lg">
+					<text class="price">{{total || 0}}</text>
+				</view>
+				<button type="primary" class="payment no-border border-radius-lg fs" @click="createOrder">去结算</button>
+			</view>
+		</view>
+
+		<!-- 03. 猜你喜欢 -->
+		<use-hot-goods :datas="goodsHotDatas" title-type="round" title="热门推荐"></use-hot-goods>
+		<view style="width: 100%;height: 100px;"></view>
+		<!-- <tabbar :current-page="2"></tabbar> -->
+	</view>
+</template>
+
+<script>
+	import {
+		goodscart,
+		cartcheck,
+		cartcheckpiliang,
+		changecart,
+		deletecart,
+		clearcart
+	} from '../../utils/api_cart.js'
+	import {
+		goodslistlimit
+	} from '../../utils/api_home.js'
+	// import tabbar from '../tabbar.vue'
+	import useHotGoods from '../../components/use-hot-goods/use-hot-goods.vue'
+	import useNumberBox from '../../components/use-number-box/use-number-box.vue'
+	import useEmpty from '../../components/use-empty/use-empty.vue'
+	export default {
+		components:{
+			useNumberBox,
+			useHotGoods,
+			useEmpty,
+			// tabbar
+		},
+		data() {
+			return {
+				// 空白页
+				empty: false,//false
+				// 购物车数据
+				cartDatas: [
+					// {goods:{name:'111'}}
+				],
+				// countType:'',//商品类型数量
+				// 全选状态
+				allChecked: false,
+				// 总价格
+				total: 0,
+				// 热门推荐
+				goodsHotDatas: [],
+			};
+		},
+		watch: {
+			//显示空白页
+			cartDatas(e) {
+				console.log(e,'e')
+				let empty = e.length === 0;
+				if (this.empty !== empty) {
+					this.empty = empty;
+				}
+			}
+		},
+		// 监听页面显示。页面每次出现在屏幕上都触发,包括从下级页面点返回露出当前页面
+		onShow() {
+			this.loadData();
+			this.get_good_hot()
+			var j=0
+			for(var i=0;i<this.cartDatas.length;i++){
+				if(this.cartDatas[index].check){
+					j++
+				}
+			}
+			if(j==this.cartDatas.length){
+				this.allChecked=true
+			}else{
+				this.allChecked=false
+			}
+			
+		},
+		// 下拉刷新
+		onPullDownRefresh() {
+			this.loadData(() => {
+				uni.stopPullDownRefresh();
+			});
+		},
+
+		methods: {
+			//请求数据
+			loadData(callback) {
+				var _self = this;
+				_self.cartDatas=[]
+				var header={
+					"Mall-Token": uni.getStorageSync('tokenId')
+				}
+				goodscart(header).then((res) => {
+					if (res.success) {
+						if(res.data.items==null){
+						}else {
+							let _cartDatas = [];
+							res.data.items.forEach(data => {
+								_self.cartDatas.push(data)
+							})
+							//计算总价
+							_self.calcTotal();
+							if (typeof callback === 'function') {
+								// 数据加载完成回调函数
+								callback();
+							}
+						}
+					}
+				})
+				// // 更改为临时表方式查询
+				// const goodsTemp = db.collection('usemall-goods').getTemp();
+				// const goodsSkuTemp = db.collection('usemall-goods-sku').getTemp();
+				
+				// db.collection(_cart, goodsTemp, goodsSkuTemp)
+				// 	.where('create_uid == $env.uid')
+				// 	.field(
+				// 		'_id, goodsCount, goods_sku.spec, goods.price, goods.market_price, limitNum, goods.name,goods.name_pw ,last_modify_time, goods._id as goods_id, goods.img, goods.state'
+				// 	)
+				// 	.orderBy('last_modify_time desc')
+				// 	.get()
+				// 	.then(res => {
+				// 		if (res && res.result && res.result.errCode === 0) {
+				// 			let _cartDatas = [];
+				// 			res.result.data.forEach(x => {
+				// 				x.goods = x.goods[0];
+				// 				x.goods_id = x.goods_id[0];
+				// 				x.goods_sku = x.goods_sku[0] || {};
+								
+				// 				if (x.goods && x.goods_id) _cartDatas.push(x);
+				// 			});
+				// 			// 购物车数据
+				// 			this.cartDatas = _cartDatas;
+				// 			console.log('购',this.cartDatas)
+				// 			// 计算总价
+				// 			this.calcTotal();
+
+				// 			if (typeof callback === 'function') {
+				// 				// 数据加载完成回调函数
+				// 				callback();
+				// 			}
+				// 		}
+				// 	})
+				// return;
+			},
+			// 热门推荐
+			get_good_hot(){
+				let _self = this;
+				_self.goodsHotDatas=[]
+				var data='?hot=1'
+				goodslistlimit(data).then((res) => {
+					console.log('轮播',res)
+					if (res.success) {
+						var total=res.data.totalCount
+						data='?hot=1&pageSize='+total
+						goodslistlimit(data).then((res) => {
+							if (res.success) {
+								res.data.list.forEach(data => {
+									_self.goodsHotDatas.push(data)
+								})
+							}else{
+								_self.$message.warning('没有符合条件的数据!')
+							}
+						})
+					}
+				})
+			},
+			// 跳转登录页
+			tologin() {
+				this.$api.tologin();
+			},
+			// 跳转商品页
+			togoods(item) {
+				this.$api.togoods({
+					id: item.goodsId
+				});
+			},
+
+			// 选中状态处理
+			check(type, index) {
+				var _self=this
+				if (type === 'item') {
+					_self.cartDatas[index].check = !_self.cartDatas[index].check;
+					if(_self.cartDatas[index].check){
+						var check=1
+					}else{
+						var check=0
+					}
+					var j=0
+					for(var i=0;i<_self.cartDatas.length;i++){
+						if(_self.cartDatas[index].check){
+							j++
+						}
+					}
+					if(j==_self.cartDatas.length){
+						_self.allChecked=true
+					}else{
+						_self.allChecked=false
+					}
+					//购物车选中或取消
+					var header={
+						"Mall-Token": uni.getStorageSync('tokenId')
+					}
+					var params=_self.cartDatas[index].goodsId
+					var data=check
+					cartcheck(params,data,header).then((res) => {
+						if (res.success) {
+							_self.calcTotal();
+						}
+					})
+				} else {
+					const check = !_self.allChecked
+					var goodIds=[]
+					var ids=''
+					_self.cartDatas.forEach(item => {
+						item.check = check;
+						_self.allChecked = check;
+						goodIds.push(item.goodsId)
+					})
+					_self.allChecked = check;
+					if(_self.allChecked){
+						var check2=1
+					}else{
+						var check2=0
+					}
+					ids=goodIds.toString()
+					//购物车批量选中或取消
+					var params='?check='+check2+'&goodIds='+ids
+					var header={
+						"Mall-Token": uni.getStorageSync('tokenId')
+					}
+					cartcheckpiliang(params,header).then((res) => {
+						if (res.success) {
+							_self.calcTotal();
+						}
+					})
+				}
+
+				_self.calcTotal();
+			},
+			// +- 数量
+			numberChange(data) {
+				var _self=this
+				let cart = _self.cartDatas[data.index];
+				var num=data.number-(_self.cartDatas[data.index].goodsCount)
+				changecart
+				//修改购物车接口
+				var params='?num='+num+'&goodId='+cart.goodsId
+				var header={
+					"Mall-Token": uni.getStorageSync('tokenId')
+				}
+				changecart(params,header).then((res) => {
+					if (res.success) {
+						_self.loadData();
+					}
+					_self.$api.msg(res.msg);
+				})
+
+				// this.$db[_cart].update(cart._id, {
+				// 	countType: data.number
+				// }).then(res => {
+				// 	if (res.code === 200) {
+				// 		cart.countType = data.number;
+				// 		this.calcTotal();
+				// 		return;
+				// 	}
+				// 	this.$api.msg(res.msg);
+				// });
+			},
+			// 删除
+			deleteCart(id) {
+				let _self = this
+				uni.showModal({
+					title: '提示',
+					content: '删除购物车',
+					success: function(res) {
+						if (res.confirm) {
+								var params=id
+								var header={
+									"Mall-Token": uni.getStorageSync('tokenId')
+								}
+								deletecart(params,header).then((res) => {
+									if (res.success) {
+										_self.loadData();
+									}
+									_self.$api.msg(res.msg);
+								})
+							// _self.$db[_cart].where('create_uid == $env.uid').remove(id)
+							// 	.then(res => {
+							// 		if (res.code === 200) {
+							// 			_self.loadData();
+							// 		}
+							// 	})
+						} else if (res.cancel) {
+						}
+					}
+				});
+			},
+			// 清空
+			clearCart() {
+				let _self = this;
+				uni.showModal({
+					title: '提示',
+					content: '清空购物车',
+					success: function(res) {
+						if (res.confirm) {
+							var header={
+								"Mall-Token": uni.getStorageSync('tokenId')
+							}
+							clearcart(header).then((res) => {
+								if (res.success) {
+									_self.loadData();
+								}
+								_self.$api.msg(res.msg);
+							})
+							// _self.$db[_cart].where('create_uid == $env.uid').remove()
+							// 	.then(res => {
+							// 		if (res.code === 200) {
+							// 			_self.cartDatas = [];
+							// 			return;
+							// 		}
+							// 		_self.$api.msg(res.msg)
+							// 	})
+						} else if (res.cancel) {
+						}
+					}
+				});
+			},
+			// 计算总价
+			calcTotal() {
+				if (this.cartDatas.length === 0) {
+					this.empty = true;
+					return;
+				}
+
+				let total = 0,
+					check = true;
+
+				this.cartDatas.forEach(item => {
+					if (item.check) {
+						// 存在库存
+						if (item.limitNum > 0 && item.limitNum >= item.goodsCount) {
+							total += (item.goodsActualPrice) * item.goodsCount;
+						}
+					} else if (check) {
+						check = false;
+					}
+				})
+
+				this.allChecked = check;
+				this.total = Number(total.toFixed(2));
+			},
+			// 创建订单
+			createOrder() {
+				let cart_ids = [];
+				this.cartDatas.forEach(item => {
+					// 选中有库存购物车
+					if (item.check && item.limitNum > 0 && item.limitNum > item.goodsCount) {
+						cart_ids.push(item.goodsId)
+					}
+				})
+				if (cart_ids.length <= 0) {
+					this.$api.msg('请选择结算商品')
+					return;
+				}
+				uni.navigateTo({
+					url: `/packageShang/pages/order/create?cart_ids=${cart_ids.join(',')}&type=2`
+				})
+			},
+		},
+	}
+</script>
+
+<style lang='scss'>
+	page {
+		min-height: 100%;
+	}
+	/* 顶部背景 */
+	.top-bg{
+		width: 100%;
+		height: 196rpx;
+		background: rgba(255, 255, 255, 1);
+		
+		.top-kuang{
+			width: 100%;
+			height: 176rpx;
+			background: rgba(255, 255, 255, 1);
+			box-shadow: 0px 2rpx 0px rgba(236, 236, 236, 1);
+			.top-title{
+				padding: 106rpx 0 0 0rpx;
+				text-align: center;
+				font-size: 36rpx;
+				font-weight: 500;
+				line-height: 52rpx;
+				color: rgba(0, 0, 0, 1);
+			}
+		}
+	}
+
+	/* 购物车列表项 */
+	.cart-item {
+
+		&:last-child {
+			margin-bottom: 0;
+		}
+
+		.image-wrapper {
+			width: 230rpx;
+			height: 230rpx;
+			flex-shrink: 0;
+
+			image {
+				opacity: 1;
+			}
+		}
+
+		.checkbox {
+			top: -16rpx;
+			left: -16rpx;
+			color: $font-color-disabled;
+			line-height: 1;
+			font-size: 46rpx;
+			padding: 5rpx;
+			z-index: 8;
+		}
+
+		.disabled {
+			color: #fff !important;
+			width: 70%;
+			height: 70%;
+			background-color: rgba(51, 51, 51, 0.5);
+		}
+
+		.item-right {
+			width: 140px;
+			height: 260rpx;
+			overflow: hidden;
+		}
+
+		.del-btn {
+			bottom: 40rpx;
+			right: 30rpx;
+			width: 70rpx;
+			height: 70rpx;
+		}
+	}
+
+	/* 底部栏 */
+	.action-section {
+		z-index: 999;
+		bottom: 0px;
+		height: 100rpx;
+
+		.checkbox {
+			.iconfont {
+				font-size: 46rpx;
+				color: #2C405A;
+			}
+		}
+
+		.clear-btn {
+			left: 100rpx;
+			background: #2C405A;
+			border-radius: 0 50rpx 50rpx 0;
+			padding: 12rpx 0;
+			transition: all .2s;
+
+			width: 0;
+			opacity: 0;
+
+			&.show {
+				width: 120rpx;
+				opacity: 1;
+			}
+		}
+
+		.payment {
+			padding: 0 40rpx;
+			font-size: $font-base;
+			background: $uni-color-primary;
+		}
+	}
+
+	/* #ifdef H5 || MP-360 */
+	.action-section {
+		margin-bottom: 50px;
+	}
+
+	/* #endif */
+</style>

+ 357 - 0
packageShang/pages/tabbar/category.vue

@@ -0,0 +1,357 @@
+<template>
+	<view class="wh-full">
+		<view class="top-bg">
+			<!-- <view class="top-kuang">
+				<view class="top-title">分类</view>
+			</view>
+			<view style="width: 100%;height: 26rpx;"></view> -->
+			<!-- 头部组件 -->
+			<use-header :fixed="headerFixed" :placeholder="headerPlaceholder" :search-tip="searchTip" :search-auto="searchAuto"></use-header>
+		</view>
+
+		<!-- 分类 -->
+		<view class="category dflex-s h-full padding-top-big" v-for="item in fdatas" :key="item.id">
+			<!-- 左侧一级分类 -->
+			<view class="h-full left">
+				<scroll-view scroll-y class="h-full">
+					<view  class="item dflex-c" v-for="item in fdatas" :key="item.id"
+						:class="{ active: item.id === cid }" @click="fSelect(item)">{{ item.name }}</view>
+				</scroll-view>
+			</view>
+
+			<!-- 右侧 1二级分类 2商品列表 -->
+			<scroll-view class="h-full right bg-main" scroll-with-animation scroll-y :scroll-top="top"
+				:style="{ height: scrollHeight }" @scroll="onScroll">
+				<!-- 右侧二级分类 -->
+				<view class="dflex-s dflex-wrap-w" v-if="mode == 1">
+					<view class="item padding-bottom-sm dflex dflex-flow-c" v-if="list.pid == cid"
+						v-for="(list, listindex) in sdatas" :key="listindex" @click="togoodslist(list)">
+						<image :lazy-load="true" :src="list.icon"></image>
+						<text class="tac clamp margin-top-sm">{{ list.name }}</text>
+					</view>
+				</view>
+
+				<!-- 右侧分类对应商品列表 -->
+				<view v-if="mode == 2">
+					<!-- 空白页 -->
+					<use-empty v-if="empty" e-style="round" tip="无商品数据"></use-empty>
+
+					<view v-else class="padding-lr" v-for="(list, listindex) in goodsDatas" :key="listindex"
+						@click="togoods(list)">
+						<view class="goods border-radius-sm padding margin-bottom-sm bg-main"
+							style="padding-bottom: 15rpx;">
+							<view class="goods-left">
+								<image mode="aspectFill" :lazy-load="true" :src="list.imgs"></image>
+							</view>
+							<view class="margin-left-sm pos-r">
+								<text class="clamp-2">{{ list.name }} {{ list.name_pw }}</text>
+								<view class="pos-a price-box w-full">
+									<text class="price">{{ list.price }}</text>
+									<text class="m-price">{{ list.marketPrice }}</text>
+								</view>
+							</view>
+						</view>
+					</view>
+
+					<!-- 上拉加载更多 -->
+					<use-loadmore v-if="!empty && hasmore" :type="loadmoreType"></use-loadmore>
+					<!-- 置顶 -->
+					<use-totop ref="usetop" bottom="150" :style="{ marginBottom: navHeight + 'px' }" @to="totop"></use-totop>
+				</view>
+			</scroll-view>
+		</view>
+
+		<!-- 切换模式 1二级分类 2商品列表 -->
+		<!-- <view class="fixed-top" :style="{ marginBottom: navHeight + 'px' }" @click="changeMode">
+			<text class="iconfont iconpailie" v-if="mode == 1"></text>
+			<text class="iconfont iconpailie02" v-if="mode == 2"></text>
+		</view> -->
+		<!-- <tabbar :current-page="1"></tabbar> -->
+	</view>
+</template>
+
+<script>
+	import {
+		goodsCate
+	} from '../../utils/api_category.js'
+	import {
+		goodslistlimit
+	} from '../../utils/api_home.js'
+	import useHeader from '../../components/use-header/use-header.vue'
+	import useEmpty from '../../components/use-empty/use-empty.vue'
+	import useLoadmore from '../../components/use-loadmore/use-loadmore.vue'
+	import useTotop from '../../components/use-totop/use-totop.vue'
+	// import tabbar from '../tabbar.vue'
+	const _goods = 'usemall-goods'
+	const _goodscategory = 'usemall-goods-category'
+	export default {
+		components:{
+			useHeader,
+			useEmpty,
+			useLoadmore,
+			useTotop,
+			// tabbar
+		},
+		data() {
+			return {
+				// 1分类列表 2商品列表
+				mode: 1,
+				// 兼容支付宝 height 显示 bug
+				scrollHeight: '100%',
+
+				// 头部参数
+				headerPlaceholder: 0,
+				headerFixed: !0,
+				searchAuto: !0,
+				searchTip: '请输入搜索关键字',
+
+				// 当前选中分类ID
+				cid: 0,
+				// 一级数据
+				fdatas: [
+				],
+				// 二级数据
+				sdatas: [],
+
+				// 商品列表
+				goodsDatas: [],
+				empty: false,
+				hasmore: 0,
+				loadmoreType: 'nomore',
+				// 商品请求数据
+				reqdata: {
+					rows: 20,
+					page: 1
+				},
+
+				top: 0,
+				scrollTop: 0,
+				navHeight: 0
+			};
+		},
+		watch: {
+			goodsDatas(e) {
+				// 监听数据,呈现空白页
+				let empty = e.length === 0;
+				if (this.empty !== empty) {
+					this.empty = empty;
+				}
+			}
+		},
+		onPageScroll(e) {
+			//this.scrollTop = e.scrollTop;
+			// this.$refs.usetop.change(e.scrollTop);
+		},
+		onLoad(option) {
+			// #ifdef MP-ALIPAY
+			// this.scrollHeight = (this.$env.windowHeight - this.$env.sis.titleBarHeight) + 'px';
+			// #endif
+
+			// 获取存储的模式
+			// this.mode = uni.getStorageSync('category.mode') || 1;
+
+			this.cid = option.cid;
+			this.loadData(() => {
+				if (this.mode == 2) {
+					// 加载商品数据
+					this.loadGoodsDatas()
+				}
+			});
+		},
+		// 下拉刷新
+		onPullDownRefresh() {
+			this.loadData(() => {
+				uni.stopPullDownRefresh();
+			});
+		},
+		methods: {
+			loadData(callback) {
+				//商品分类树形列表
+				let _self = this;
+				_self.fdatas = []
+				goodsCate().then((res) => {
+					if (res.success) {
+						res.data.forEach(data => {
+							_self.fdatas.push(data)
+						})		
+						if (res.data.length > 0) {
+							// _self.cid = _self.fdatas[0].id;
+							// _self.sdatas=_self.fdatas[0].childCategory
+							for(var i=0;i<_self.fdatas.length;i++){
+								if(_self.cid==_self.fdatas[i].id){
+									_self.sdatas=_self.fdatas[i].childCategory
+								}
+							}
+						}
+						if (typeof callback === 'function') {
+							// 数据加载完成回调函数
+							callback();
+						}
+					}else{
+						_self.$message.warning('没有符合条件的数据!')
+					}
+				})
+			},
+			// 加载商品数据
+			loadGoodsDatas() {
+				let _self = this;
+				if (_self.mode != 2) {
+					return;
+				}
+				// 根据当前 cid 加载商品数据列表
+				_self.reqdata.cid = _self.cid;
+				_self.goodsDatas=[]
+				var data='?categoryId='+_self.reqdata.cid+'&categoryLevel=1'
+				goodslistlimit(data).then((res) => {
+					if (res.success) {
+						var total=res.data.totalCount
+						data='categoryId='+_self.reqdata.cid+'&categoryLevel=1&pageSize='+total
+						goodslistlimit(data).then((res) => {
+							if (res.success) {
+								res.data.list.forEach(data => {
+									_self.goodsDatas.push(data)
+								})
+							}else{
+								_self.$message.warning('没有符合条件的数据!')
+							}
+						})
+					}
+				})
+			},
+			totop(e) {
+				this.top = e.scrollTop
+				this.$nextTick(function() {
+					this.top = 0
+				});
+			},
+			// 一级分类
+			fSelect(item) {
+				for(var i=0;i<this.fdatas.length;i++){
+					if(item.id==this.fdatas[i].id){
+						this.sdatas=this.fdatas[i].childCategory
+					}
+				}
+				this.cid = item.id;
+				this.loadGoodsDatas();
+			},
+			// 切换模式 1分类模式 2商品模式
+			changeMode() {
+				this.mode = this.mode == 1 ? 2 : 1;
+
+				this.loadGoodsDatas();
+			},
+			// 跳转商品详情
+			togoods(item) {
+				this.$api.togoods({
+					id: item.id
+				});
+			},
+			// 跳转商品列表
+			togoodslist(item) {
+				this.$api.togoodslist({
+					cid: item.id,
+					level:item.level,
+					keyword:item.name
+				});
+			},
+		},
+		mounted() {
+			// #ifdef H5 || MP-360
+			this.navHeight = 50;
+			// #endif
+		}
+	};
+</script>
+
+<style lang="scss">
+	page {
+		height: 100%;
+		background-color: $page-color-base;
+	}
+	// 顶部背景
+	.top-bg{
+		width: 100%;
+		height: 100rpx;
+		background: rgba(255, 255, 255, 1);
+		
+		.top-kuang{
+			width: 100%;
+			height: 176rpx;
+			background: rgba(255, 255, 255, 1);
+			box-shadow: 0px 2rpx 0px rgba(236, 236, 236, 1);
+			.top-title{
+				padding: 106rpx 0 0 0rpx;
+				text-align: center;
+				font-size: 36rpx;
+				font-weight: 500;
+				line-height: 52rpx;
+				color: rgba(0, 0, 0, 1);
+			}
+		}
+	}
+
+	.category {
+		overflow: hidden;
+
+		.left {
+			width: 200rpx;
+			background-color: $page-color-base;
+
+			.item {
+				height: 100rpx;
+				color: $font-color-base;
+				position: relative;
+
+				&.active {
+					color: $uni-color-primary;
+					background: #fff;
+
+					&:before {
+						content: '';
+						position: absolute;
+						left: 0;
+						top: 50%;
+						transform: translateY(-50%);
+						height: 36rpx;
+						width: 8rpx;
+						background-color: $uni-color-primary;
+						opacity: 0.8;
+					}
+				}
+			}
+		}
+
+		.right {
+			flex: 1;
+			overflow: hidden;
+			display: block;
+
+			.item {
+				flex-shrink: 0;
+				width: 33.33%;
+				font-size: $font-sm + 2upx;
+				color: #666;
+
+				image {
+					width: 130rpx;
+					height: 130rpx;
+				}
+			}
+		}
+	}
+
+	.goods {
+		display: flex;
+
+		.goods-left {
+			image {
+				width: 120rpx;
+				height: 120rpx;
+			}
+		}
+
+		.price-box {
+			bottom: 0;
+		}
+	}
+</style>

+ 340 - 0
packageShang/pages/tabbar/home.vue

@@ -0,0 +1,340 @@
+<template>
+	<view class="box-sizing-b w-full">
+		<!-- 最上层背景 -->
+		<view class="top-bg">
+			<view style="height: 54rpx;width: 100%;"></view>
+			<!-- 01. 头部组件 -->
+			<use-header :search-tip="searchTip" :search-auto="searchAuto" @search="search" style="margin-top: 54rpx"></use-header>
+			<!-- 02. 轮播区 -->
+			<view class="swiper-area pos-r" v-if="swiperDatas && swiperDatas.length > 0">
+				<!-- 轮播组件 -->
+				<swiper class="swiper w-full" autoplay indicator-dots indicator-color="#f7f7f7" indicator-active-color="rgba(36, 147, 241, 1)">
+					<swiper-item class="swiper-item padding-lr wh-full box-sizing-b" v-for="(item, index) in swiperDatas" :key="index">
+						<view class="wh-full" @click.stop="topage(item)">
+							<image class="border-radius wh-full" mode="aspectFill" :lazy-load="true" :src="item.img" />
+						</view>
+					</swiper-item>
+				</swiper>
+			</view>
+		
+
+			<!-- 03. 分类区 -->
+			<view class="category-area dflex dflex-wrap-w" v-if="categoryDatas && categoryDatas.length > 0">
+				<view class="category-item dflex dflex-flow-c margin-bottom-sm" v-for="(item, index) in categoryDatas"
+					:key="index" @click="topage2(item)">
+					<image class="margin-bottom-xs" lazy-load :src="item.icon"></image>
+					<text class="tac clamp" style="color: black;">{{ item.name }}</text>
+				</view>
+			</view>
+
+			<!-- 04. 限时精选 -->
+			<view class="xianshijing">
+				<use-list-title title="限时精选" size="32" fwt="600" color="#333" iconfont="icondaishouhuo-" @goto="limit">
+				</use-list-title>
+				<view class="limit-area">
+					<scroll-view class="padding-lr" scroll-x>
+						<view class="dflex padding-bottom">
+							<view class="item margin-right-sm" v-for="(item, index) in goodsLimitDatas" :key="index"
+								@click="togoods(item)">
+								<image class="border-radius-xs" v-if="((item.imgs).indexOf(',')) != -1"
+								 mode="aspectFill" :lazy-load="true" :src="((item.imgs).substring(0, ((item.imgs).indexOf(','))))"></image>
+								 <image class="border-radius-xs" v-else
+								  mode="aspectFill" :lazy-load="true" :src="item.imgs"></image>
+								<text class="title clamp padding-bottom-xs">{{ item.name }}</text>
+								<text class="price">{{ item.price  }}</text><text class="m-price">{{ item.marketPrice  }}</text>
+							</view>
+						</view>
+					</scroll-view>
+				</view>
+			</view>
+
+			<!-- 05. 热门推荐 -->
+			<use-hot-goods :datas="goodsHotDatas" autoload="none" title="热门推荐"></use-hot-goods>
+			<view style="height: 125px;width: 100%;"></view>
+
+			<!-- 置顶 -->
+			<use-totop ref="usetop" :style="{ marginBottom: navHeight + 'px' }"></use-totop>
+
+			<!-- #ifdef MP-WEIXIN -->
+			<official-account @bindload="wxOAccountLoad" @binderror="wxOAccountErr"></official-account>
+			<!-- #endif -->
+		</view>
+		<!-- <tabbar :current-page="0"></tabbar> -->
+	</view>
+</template>
+
+<script>
+	import { mapState } from 'vuex';
+	import {
+		getLunbo,
+		goodsgory,
+		goodslistlimit
+	} from '../../utils/api_home.js'
+	import useHeader from '../../components/use-header/use-header.vue'
+	import useListTitle from '../../components/use-list-title/use-list-title.vue'
+	import useHotGoods from '../../components/use-hot-goods/use-hot-goods.vue'
+	import useTotop from '../../components/use-totop/use-totop.vue'
+	// import tabbar from '../tabbar.vue'
+	export default {
+		computed: {
+			...mapState(['member'])
+		},
+		components:{
+			useHeader,
+			useListTitle,
+			useHotGoods,
+			useTotop,
+			// tabbar
+		},
+		data() {
+			return {
+				// 头部参数
+				searchAuto: !0,
+				searchTip: '万千商品,等你来采购',
+
+				// 轮播区
+				swiperDatas: [],
+				// 金刚区分类
+				categoryDatas: [],
+				// 限时精选
+				goodsLimitDatas: [],
+				// 热门推荐
+				goodsHotDatas: [],
+
+				scrollTop: 0,
+				navHeight: 50,
+			};
+		},
+		// 监听页面加载
+		onLoad() {
+		},
+		onPageScroll(e) {
+			// this.scrollTop = e.scrollTop
+			this.$refs.usetop.change(e.scrollTop);
+		},
+		// 监听页面显示。页面每次出现在屏幕上都触发,包括从下级页面点返回露出当前页面
+		onShow() {
+			this.get_lunbo()
+			this.get_goodsgory()
+			this.get_goodslist()
+			this.get_good_hot()
+		},
+		// 监听用户下拉刷新
+		onPullDownRefresh() {
+			this.get_lunbo(() => {
+				uni.stopPullDownRefresh();
+			});
+			this.get_goodsgory(() => {
+				uni.stopPullDownRefresh();
+			});
+			this.get_goodslist(() => {
+				uni.stopPullDownRefresh();
+			});
+			this.get_good_hot(() => {
+				uni.stopPullDownRefresh();
+			});
+		},
+		// 用户点击右上角分享
+		// https://uniapp.dcloud.io/api/plugins/share?id=showsharemenu
+		onShareAppMessage: function(ops) {
+			let _this = this,
+				mid = 0;
+
+			if (_this.member && _this.member._id) {
+				mid = _this.member._id;
+			}
+
+			return {
+				title: '特产商城',
+				path: `/pages/tabbar/home?mid=${mid}`,
+				// imageUrl: 'https://mall-os-api.use-cloud.com/files/upload/image/20200408/200408115587860242.jpg',
+				success: function(res) {
+					// 转发成功
+					console.log('转发成功', res);
+				},
+				fail: function(res) {
+					// 转发失败
+					console.log('转发失败', res);
+				}
+			};
+		},
+
+
+		methods: {
+			//轮播图列表
+			get_lunbo() {
+				let _self = this
+				_self.swiperDatas=[]
+				getLunbo().then((res) => {
+					if (res.success) {
+						res.data.list.forEach(data => {
+							_self.swiperDatas.push(data)
+						})
+					}
+				})
+			},
+			// 商品金刚区分类
+			get_goodsgory(){
+				let _self = this;
+				_self.categoryDatas=[]
+				goodsgory().then((res) => {
+					if (res.success) {
+						res.data.forEach(data => {
+							_self.categoryDatas.push(data)
+						})
+					}
+				})
+			},
+			//商品限时精选
+			get_goodslist(){
+				let _self = this;
+				_self.goodsLimitDatas=[]
+				var data='?limited=1'
+				goodslistlimit(data).then((res) => {
+					if (res.success) {
+						var total=res.data.totalCount
+						data='?limited=1&pageSize='+total
+						goodslistlimit(data).then((res) => {
+							if (res.success) {
+								res.data.list.forEach(data => {
+									_self.goodsLimitDatas.push(data)
+								})
+							}else{
+								_self.$message.warning('没有符合条件的数据!')
+							}
+						})
+					}
+				})
+			},
+			// 热门推荐
+			get_good_hot(){
+				let _self = this;
+				_self.goodsHotDatas=[]
+				var data='?hot=1'
+				goodslistlimit(data).then((res) => {
+					if (res.success) {
+						var total=res.data.totalCount
+						data='?hot=1&pageSize='+total
+						goodslistlimit(data).then((res) => {
+							if (res.success) {
+								res.data.list.forEach(data => {
+									_self.goodsHotDatas.push(data)
+								})
+							}else{
+								_self.$message.warning('没有符合条件的数据!')
+							}
+						})
+					}
+				})
+			},
+			// 搜索回调函数
+			search() {
+				console.log('home search');
+			},
+			// 跳转页面
+			topage(item) {
+			},
+			topage2(item){
+				uni.navigateTo({
+					url:'/packageShang/pages/tabbar/category?cid='+item.id
+				})
+				// this.$api.togoodslist({
+				// 	cid: item.id,
+				// 	level:item.level,
+				// 	keyword:item.name
+				// });
+			},
+			// 限时精选 -> 商品详情
+			togoods(item) {
+				// 跳转商品详情
+				this.$api.togoods({
+					id: item.id
+				});
+			},
+			// 限时精选
+			limit() {
+				// 跳转商品列表 - 限时精选类目
+				this.$api.togoodslist({
+					limited: 1
+				});
+			},
+		},
+		mounted() {
+			// #ifdef H5 || MP-360
+			this.navHeight = 50;
+			// #endif
+		}
+	};
+</script>
+
+<style lang="scss">
+	page {
+		min-height: 100%;
+		background: $page-color-base;
+	}
+	// 最上层背景
+	.top-bg{
+		width: 100%;
+		display: flow;
+		height: 500rpx;
+		background: linear-gradient(180deg, rgba(36, 147, 241, 1) 0%, rgba(36, 147, 241, 1) 64.28%, rgba(255, 255, 255, 0) 100%);
+		.top-title{
+			padding: 106rpx 0 0 30rpx;
+			font-size: 36rpx;
+			font-weight: 400;
+			line-height: 36rpx;
+			color: rgba(255, 255, 255, 1);
+		}
+	}
+	/* 轮播图区 */
+	.swiper-area {
+		padding-top: 20rpx;
+		.swiper {
+			height: 276rpx;
+		}
+	}
+
+	/* 分类区 */
+	.category-area {
+		width: 690rpx;
+		// height: 100%;
+		border-radius: 14rpx;
+		background: rgba(255, 255, 255, 1);
+		margin: 15rpx 0 0 30rpx;
+		padding: 15rpx 0 30rpx 0;
+
+		.category-item {
+			font-size: $font-sm + 2upx;
+			color: $font-color-dark;
+			width: 20%;
+		}
+
+		image {
+			width: 90rpx;
+			height: 90rpx;
+		}
+	}
+
+	/* 限时精选区 */
+	.xianshijing{
+		margin: 15rpx 0 0 30rpx;
+		width: 690rpx;
+		height: 428rpx;
+		border-radius: 14rpx;
+		background: rgba(255, 255, 255, 1);
+		.limit-area {
+			min-height: 221rpx;
+		
+			.item {
+				width: 221rpx;
+		
+				image {
+					width: 221rpx;
+					height: 221rpx;
+				}
+			}
+		}
+	}
+	
+
+</style>

+ 488 - 0
packageShang/pages/tabbar/user.vue

@@ -0,0 +1,488 @@
+<template>
+	<view class="user-area">
+		<view style="width: 100%;height: 320px;">
+		<view class="header-area padding-lr-sm" :class="(is_mp && !is_alipay) ? 'padding-top-big' : 'padding-top'">
+			<view>
+				<view class="member-area padding-top-use pos-r">
+					 <!-- @click="to('/packageShang/pages/user/setting/personal')" -->
+					<view>
+						<image class="headimg border-radius-c" :src="list.avatarUrl || '/static/images/user/default.png'"></image>
+					</view>
+					<view class="margin-left-user">
+						<view class="info-box">
+							<text class="fs-lg">{{ list.nickName || '大魏' }}</text>
+						</view>
+						<view v-if="member.member_city"><text class="fs-xxs">{{ member.member_city }}</text></view>
+					</view>
+				</view>
+				<!-- <view class="border-radius-big bg-base dflex-c padding-lr"
+				 style="z-index: 99;position: fixed;"
+				 @click="to('/packageShang/pages/user/integral/sign')">
+					<view class="iconfont fs-xl iconqiandao margin-right-xs"></view>
+					<view>签到</view>
+				</view> -->
+			</view>
+
+			<view class="border-radius">
+				<view class="stats-area dflex-c">
+					<view class="item dflex dflex-flow-c" @click="toOrder('/packageShang/pages/user/order/order', '全部')">
+						<text class="num">{{ member.member_monetary / 100 || 0 }}</text>
+						<text>我的余额</text>
+					</view>
+					<view class="item dflex dflex-flow-c" @click="toOrder('/packageShang/pages/user/order/order', '全部')">
+						<text class="num">{{ pointsAmount || 0 }}</text>
+						<text>我的积分</text>
+					</view>
+					<!-- <view class="item dflex dflex-flow-c" @click="to('/packageShang/pages/user/coupon/coupon')">
+						<text class="num">{{ member.member_coupon_cnt || 6 }}</text>
+						<text>优惠券</text>
+					</view> -->
+				</view>
+				
+				<view class="vip-card-area pos-r padding-lr padding-tb-sm">
+					<!-- <view style="margin: 28rpx 0 0 4rpx;width: 100%;height: 100%;">
+						<image class="vip" src="../../static/images/user/VIP.png"></image>
+						<text class="margin-left-sm" style="line-height: 42rpx;">会员立享5大权益</text>
+						<view class="margin-left-kai">立即开通</view>
+					</view> -->
+				</view>
+			</view>
+		</view>
+		</view>
+
+		<view class="container-area padding-lr-sm padding-bottom-sm">
+			<!-- 我的订单 -->
+			<view class="border-radius margin-top-sm bg-main">
+				<use-list-title title="我的订单" iconfont="none" fwt="600" tip="查看全部订单"
+					@goto="toOrder('/packageShang/pages/user/order/order', '全部')"></use-list-title>
+
+				<view class="order-area padding-bottom-sm padding-lr dflex-c">
+					<view class="item dflex dflex-flow-c" @click="toOrder('/packageShang/pages/user/order/order', '待付款')">
+						<view class="iconfont">&#xe6da;<view class="badge badge-small"
+								v-if="stats && stats.order_state && stats.order_state['待付款'] > 0">
+								{{stats.order_state['待付款']}}
+							</view>
+						</view>
+						<text>待付款</text>
+					</view>
+					<view class="item dflex dflex-flow-c" @click="toOrder('/packageShang/pages/user/order/order', '待发货')">
+						<view class="iconfont">&#xe6d9;<view class="badge badge-small"
+								v-if="stats && stats.order_state && stats.order_state['待发货'] > 0">
+								{{stats.order_state['待发货']}}
+							</view>
+						</view>
+						<text>待发货</text>
+					</view>
+					<view class="item dflex dflex-flow-c" @click="toOrder('/packageShang/pages/user/order/order', '待收货')">
+						<view class="iconfont">&#xe6d7;<view class="badge badge-small"
+								v-if="stats && stats.order_state && stats.order_state['待收货'] > 0">
+								{{stats.order_state['待收货']}}
+							</view>
+						</view>
+						<text>待收货</text>
+					</view>
+					<view class="item dflex dflex-flow-c" @click="toOrder('/packageShang/pages/user/order/order', '待评价')">
+						<view class="iconfont">&#xe6db;<view class="badge badge-small"
+								v-if="stats && stats.order_state && stats.order_state['待评价'] > 0">
+								{{stats.order_state['待评价']}}
+							</view>
+						</view>
+						<text>待评价</text>
+					</view>
+					<view class="item dflex dflex-flow-c" @click="toOrder('/packageShang/pages/user/order/order', '已完成')">
+						<view class="iconfont">&#xe6db;<view class="badge badge-small"
+								v-if="stats && stats.order_state && stats.order_state['已完成'] > 0">
+								{{stats.order_state['已完成']}}
+							</view>
+						</view>
+						<text>已完成</text>
+					</view>
+					<!-- <view class="item dflex dflex-flow-c" @click="toOrder('/packageShang/pages/user/order/order', '退款')">
+						<view class="iconfont">&#xe715;<view class="badge badge-small"
+								v-if="stats && stats.order_state && stats.order_state['退款'] > 0">
+								{{stats.order_state['退款']}}
+							</view>
+						</view>
+						<text>售后/退款</text>
+					</view> -->
+				</view>
+			</view>
+
+			<view class="border-radius margin-top-sm bg-main">
+				<!-- 我的足迹 -->
+				<use-list-title title="我的足迹" iconfont="iconzuji" color="rgba(36, 147, 241, 1)" fwt="600" :tip="stats.browsing"
+					@goto="to('/packageShang/pages/user/browsing/browsing')"></use-list-title>
+				<scroll-view scroll-x class="browsing-area padding-lr">
+					<view class="dflex">
+						<view v-for="(item, index) in historyDatas" :key="index">
+							<image class="border-radius-sm margin-right-sm" @click="togoods(item)" :src="item.goodsMainImageUrl"
+								mode="aspectFill"></image>
+						</view>
+					</view>
+				</scroll-view>
+
+				<use-list-title title="我的收藏" iconfont="iconshoucang-" color="#ff6a6c" fwt="600" :tip="stats.collect"
+					@goto="to('/packageShang/pages/user/collect/collect')"></use-list-title>
+				<!-- <use-list-title title="分销中心" iconfont="iconyixiaoshou" color="#ffab6c" fwt="600" tip="分享赚钱"
+					@goto="to('/packageShang/pages/user/distribution/distribution')"></use-list-title> -->
+				<use-list-title title="收货人" iconfont="icondizhi-" color="#5a9ded" fwt="600"
+					@goto="to('/packageShang/pages/user/address/address')"></use-list-title>
+				<!-- <use-list-title title="设置" iconfont="iconshezhi-" color="#58bc8a" fwt="600"
+					@goto="to('/packageShang/pages/user/setting/setting')"></use-list-title> -->
+			</view>
+
+			<view v-if="list.avatarUrl" class="border-radius margin-top-sm padding-sm dflex-c bg-main log-out-btn"
+				@click="openActionSheet">
+				<text class="cell-tit">退出登录</text>
+			</view>
+			<!-- <view v-else class="border-radius margin-top-sm padding-sm dflex-c bg-main log-out-btn"
+				@click="to('/pages/login/login')">
+				<text class="cell-tit">去登录</text>
+			</view> -->
+			<view style="width: 100%;height: 70px;"></view>
+
+			<!-- 操作菜单 -->
+			<!-- <use-action-sheet v-model="actionSheetShow" :list="actionSheetList" :tips="actionSheetTips"
+				@click="actionSheetClick" @close="actionSheetClose"></use-action-sheet> -->
+		</view>
+
+		<tabbar :current-page="3"></tabbar>
+	</view>
+</template>
+<script>
+	import {
+		orserlist,
+		footlist
+	} from '../../utils/api_user.js'
+	import tabbar from '../tabbar.vue'
+	import common from '../../../static/comon.js'
+	import useListTitle from '../../components/use-list-title/use-list-title.vue'
+	import useActionSheet from '../../components/use-action-sheet/use-action-sheet.vue'
+	const db = uniCloud.database();
+	
+	const _history = 'usemall-goods-history'
+	export default {
+		components:{
+			useListTitle,
+			useActionSheet,
+			tabbar
+		},
+		data() {
+			return {
+				list: {},
+				pointsAmount: '',//积分
+				isreq: false,
+				// 浏览历史
+				historyDatas: [],
+				// 统计数据
+				stats: {},
+
+				actionSheetShow: false,
+				actionSheetList: [],
+				actionSheetTips: {
+					text: "",
+					color: "#9a9a9a",
+					size: 24
+				},
+
+				is_mp: false,
+				is_alipay: false,
+			};
+		},
+		onLoad() {
+			var token = uni.getStorageSync('tokenId');
+			this.isReal(token)
+			var _self = this
+			if(token){
+				this.getIndex()
+				uni.getUserInfo({
+				  provider: 'weixin',
+				  success: function (infoRes) {
+					_self.list = infoRes.userInfo
+				  }
+				})
+			}else{
+				_self.list = {}
+			}
+			
+			_self.$nextTick(() => {
+				_self.is_mp = _self.$env.is_mp;
+				_self.is_alipay = _self.$env.platform == 'alipay';
+			})
+		},
+		onShow() {
+
+			this.loadData();
+		},
+		methods: {
+			// 获取积分
+			getIndex(){
+				var token = uni.getStorageSync('tokenId');
+				var _self = this;
+				uni.request({
+					url: common.url + 'integral/user/integralIndex',
+					header: {"User-Token": token},
+					success(res) {
+						res = res.data
+						if(res.status == 'success'){
+							_self.pointsAmount = res.data.pointsAmount
+						}else{
+							if (res.data.errCode == '10006'){
+								common.isOverdue()
+							}
+						}
+					}
+				})
+			},
+			// 获取用户实名信息进行判断是否已实名
+			isReal(token) {
+				var _self = this;
+				uni.request({
+					url: common.url + 'user/getUserRealName',
+					header: {
+					   'User-Token': token
+					},
+					success(res){
+						res = res.data
+						if (res.status == 'success'){
+							// _self.text = '已认证'
+							_self.isRight(token)
+						}else if(res.status == 'fail') {
+							// _self.text = '未认证'
+						} 
+					}
+				})
+			},
+			// 判断有无绑定房产
+			isRight(token) {
+				var _self = this;
+				uni.request({
+					url: common.url + 'user/getUserIsBindHouse',
+					header:{
+						"User-Token": token
+					},
+					success(res) {
+						res = res.data
+						if(res.status == 'success') {
+							_self.haveHouseInfo = res.data.haveHouseInfo
+						} else {
+							if (res.data.errCode == '10006'){
+								common.isOverdue()
+							}
+						}
+					}
+				})
+			},
+			
+			// 加载数据
+			loadData() {
+				var _self = this;
+				// 订单列表
+				var header={
+					"Mall-Token": uni.getStorageSync('tokenId')
+				}
+				orserlist(header).then((res) => {
+					if (res.success) {
+						this.stats = res.data;
+					}
+				})
+
+				// 浏览历史足迹
+				footlist().then((res) => {
+					if (res.success) {
+						res.data.list.forEach(x => {
+							this.historyDatas.push(x);
+						});
+					}
+				})
+			},
+
+			// 打开操作菜单
+			openActionSheet() {
+				try {
+				    uni.removeStorageSync('tokenId');
+					uni.removeStorageSync('houseInfo');
+					uni.removeStorageSync('address');
+					uni.removeStorageSync('latitude');
+					uni.removeStorageSync('longitude');
+					uni.removeStorageSync('communityName');
+					uni.removeStorageSync('communityId');
+					uni.removeStorageSync('streetOfficeId');
+					uni.removeStorageSync('streetOfficeName');
+					uni.removeStorageSync('centerId');
+					uni.navigateTo({
+						url: '/pages/login/login'
+					})
+				} catch (e) {
+				    // error
+				}
+			},
+			// 关闭操作菜单
+			actionSheetClose() {
+				console.log(this.actionSheetShow);
+			},
+			// 点击操作菜单
+			actionSheetClick(index) {
+				switch (index) {
+					case 0:
+						this.$api.msg('退出成功');
+						this.logout();
+						this.$api.timerout(() => {
+							this.$api.tohome();
+						}, 200);
+						break;
+					case 1:
+						this.$api.tologin();
+						break;
+				}
+			},
+
+			// 统一跳转接口,拦截未登录路由
+			to(url) {
+				// if (!this.list.avatarUrl) {
+				// 	this.$api.tologin()
+				// 	return;
+				// }
+				console.log(url,'0')
+
+				uni.navigateTo({
+					url
+				});
+			},
+			// 跳转到 订单
+			toOrder(url, state) {
+				// if (!this.list.avatarUrl) {
+				// 	this.$api.tologin()
+				// 	return;
+				// }
+
+				uni.setStorage({
+					key: '__order_state',
+					data: state,
+					success(res) {
+						console.log(res);
+					},
+					complete() {
+						uni.navigateTo({
+							url
+						});
+					}
+				});
+			},
+
+			// 跳转商品详情
+			togoods(item) {
+				this.$api.togoods({
+					id: item.goodsId
+				});
+			}
+		}
+	};
+</script>
+<style lang="scss">
+	page {
+		min-height: 100%;
+		background: $page-color-base;
+	}
+	.header-area{
+		padding: 0px;
+		width: 750rpx;
+		height: 560rpx;
+		background-image: url('../../static/images/user/user-bg.png');
+		background-size: 100%;
+	}
+
+	.member-area {
+		image {
+			width: 130rpx;
+			height: 130rpx;
+			border: 5rpx solid #fff;
+		}
+	}
+
+	.vip-card-area {
+		margin-left: 30rpx;
+		width: 690rpx;
+		height: 136rpx;
+		opacity: 1;
+		border-radius: 20rpx;
+		background: rgba(35, 39, 52, 1);
+		font-size: 32rpx;
+		color: rgba(255, 225, 192, 1);
+		.vip{
+			width: 98rpx;
+			height: 42rpx;
+			line-height: 50rpx;
+		}
+		.margin-left-kai{
+			float: right;
+			width: 136rpx;
+			height: 56rpx;
+			opacity: 1;
+			border-radius: 28rpx;
+			background: linear-gradient(270deg, rgba(226, 162, 92, 1) 0%, rgba(247, 229, 195, 1) 100%);
+			font-size: 26rpx;
+			line-height: 56rpx;
+			color: rgba(35, 39, 52, 1);
+			text-align: center;
+		}
+	}
+
+	.stats-area {
+		.item {
+			padding: 30rpx 0;
+			color: rgba(255, 255, 255, 1);
+			position: relative;
+			font-size: $font-sm;
+			flex: 1;
+		}
+
+		.num {
+			font-size: 40rpx;
+			color: $font-color-dark;
+			margin-bottom: 6rpx;
+		}
+	}
+
+	.order-area {
+		.item {}
+
+		.iconfont {
+			position: relative;
+			font-size: $font-lg + 8upx;
+
+			.badge {
+				right: initial;
+			}
+		}
+	}
+
+	
+	.order-area .item {
+		position: relative;
+		font-size: $font-sm;
+		color: $font-color-base;
+		flex: 1;
+	}
+
+	.browsing-area {
+		image {
+			width: 160rpx;
+			height: 160rpx;
+		}
+	}
+
+	::-webkit-scrollbar {
+		width: 0;
+		height: 0;
+		color: transparent;
+		display: none;
+	}
+
+	.log-out-btn {
+		color: $font-color-base;
+	}
+	.iconfont {
+	    font-size: 36rpx;
+	}
+</style>

+ 381 - 0
packageShang/pages/user/address/address-edit.vue

@@ -0,0 +1,381 @@
+<template>
+	<view class="content bg-drak ft-main">
+		<view class="gap"></view>
+		<view class="row dflex border-line padding-lr">
+			<text class="tit">收货人</text>
+			<input class="input" type="text" v-model="addrData.receiverName" placeholder="请输入收货人姓名"
+				placeholder-class="placeholder" />
+		</view>
+		<view class="row dflex border-line padding-lr">
+			<text class="tit">手机号</text>
+			<input class="input" type="number" v-model="addrData.phoneNum" placeholder="请输入手机号码"
+				placeholder-class="placeholder" />
+		</view>
+
+		<view class="row dflex border-line padding-left">
+			<text class="tit">所在地区</text>
+			<view class="dflex-b flex1">
+				<view class="flex1 input_t" @click="openAddress">{{ addressName }}</view>
+				<use-address ref="useAddress" @onConfirm="changeAddr" cancelColor="#bbb" themeColor="#FF6A6C">
+				</use-address>
+				<!-- <use-pickeraddr class="flex1" @change="changeAddr">
+					<view class="input_t">{{ addressName }}</view>
+				</use-pickeraddr> -->
+				<!-- #ifdef MP-WEIXIN || MP-BAIDU -->
+				<view class="iconfont icondizhi- input_t padding-lr-sm padding-tb-16" @click="choiceMapAddr"></view>
+				<!-- #endif -->
+				<!-- #ifdef MP-ALIPAY -->
+				<view class="iconfont icondizhi- input_t padding-lr-sm padding-tb-16" @click="choiceAlipayMapAddr">
+				</view>
+				<!-- #endif -->
+			</view>
+		</view>
+		<view class="row dflex border-line padding-lr">
+			<text class="tit">详细地址</text>
+			<input class="input" type="text" v-model="addrData.address" placeholder="请输入详细地址"
+				placeholder-class="placeholder" />
+		</view>
+		<view class="gap"></view>
+		<view class="row dflex-b padding-lr">
+			<text class="tit">设为默认</text>
+			<switch :checked="addrDefault" color="#FF6A6C" @change="switchChange" />
+		</view>
+
+		<view class="padding w-full margin-top">
+			<view class="dflex-b border-radius-big">
+				<view class="tac padding-tb-sm flex1 bg-base-a-tijiao" @click="submit">提交</view>
+			</view>
+		</view>
+	</view>
+</template>
+
+<script>
+	import {
+		addressinfo,
+		addressadd,
+		addressupdate
+	} from '../../../utils/api_user.js'
+	import useAddress from '../../../components/use-address/use-address.vue'
+	const __name = 'usemall-member-address';
+	export default {
+		components:{
+			useAddress,
+		},
+		data() {
+			return {
+				addrDefault: false,
+				addressName: '请选择地址 | 地图选择',
+				addrData: {
+					id:'',
+					receiverName:'',
+					phoneNum: '',
+					address1: '',
+					province: '',
+					city: '',
+					county: '',
+					address: '',
+					defaultFlag: 0,
+					addr_source: '录入',
+					remark: '家',
+					longitude: '',
+					latitude: '',
+				},
+				id: 0,
+				type: 'add',
+			};
+		},
+		onLoad(options) {
+			console.log(options)
+			let title = '新增收货人';
+			if (options.type === 'edit') {
+				title = '编辑收货人';
+				this.id = options.id;
+
+				// 收货人地址详情
+				var _self=this
+				var data=_self.id
+				addressinfo(data).then((res) => {
+					if(res.success){
+						for (let key in _self.addrData) {
+							_self.addrData[key] = res.data[key];
+						}
+						_self.addrDefault = _self.addrData.defaultFlag == '1';
+						_self.addressName = _self.addrData.province + '-' + _self.addrData.city +
+							'-' + _self.addrData.county;
+						return
+					}
+					_self.$api.msg(res.msg);
+				})
+
+			} else {
+				// #ifdef H5 || MP-360 || MP-QQ || MP-TOUTIAO
+				this.addressName = "请选择地址";
+				// #endif
+			}
+
+			this.type = options.type || 'add';
+			uni.setNavigationBarTitle({
+				title
+			});
+		},
+		methods: {
+			switchChange(e) {
+				this.addrDefault = e.detail.value;
+			},
+			openAddress() {
+				this.$refs.useAddress.open();
+			},
+			// 选择地址
+			changeAddr(res) {
+				let _this = this;
+
+				_this.addrData.province = res.labelArr[0] || '';
+				_this.addrData.city = res.labelArr[1] || '';
+				_this.addrData.county = res.labelArr[2] || '';
+
+				_this.addrData.address1 = res.label;
+				_this.addressName = _this.addrData.address1;
+			},
+			// 选择地图地址
+			choiceMapAddr(options) {
+				let _this = this;
+
+				uni.authorize({
+					scope: 'scope.userLocation',
+					success() {
+						uni.chooseLocation({
+							success: res => {
+								console.log('位置', res);
+								// console.log('位置名称:' + res.name);
+								// console.log('详细地址:' + res.address);
+								// console.log('纬度:' + res.latitude);
+								// console.log('经度:' + res.longitude);
+
+								if (res && res.name) {
+									let __addr = _this.resolveAddr(res);
+									_this.addrData.province = __addr.province || '';
+									_this.addrData.city = __addr.city || '';
+									_this.addrData.county = __addr.area || '';
+
+									_this.addrData.address1 = __addr.addr;
+									_this.addressName = _this.addrData.address1;
+
+									_this.addrData.longitude = res.longitude + '';
+									_this.addrData.latitude = res.latitude + '';
+
+									_this.addrData.address = res.name;
+								}
+							},
+							fail(err) {
+								console.log(err);
+							}
+						});
+					},
+					fail(err) {
+						uni.showModal({
+							title: '位置未授权,打开设置',
+							success: function(res) {
+								if (res.confirm) {
+									uni.openSetting({});
+								}
+							}
+						});
+					}
+				});
+			},
+			// #ifdef MP-ALIPAY
+			// 选择地图地址
+			choiceAlipayMapAddr(options) {
+				console.log('open-location', options);
+				let _this = this;
+
+				uni.chooseLocation({
+					success: res => {
+						console.log('位置', res);
+						if (res && res.address1) {
+							let __addr = _this.resolveAddr(res);
+							_this.addrData.province = res.provinceName || '';
+							_this.addrData.city = res.cityName || '';
+							_this.addrData.county = res.adName || '';
+							
+							_this.addrData.address1 = [_this.addrData.province, _this.addrData.city, _this.addrData.county]
+								.filter(x => x).join('-');
+							_this.addressName = _this.addrData.address1;
+
+							_this.addrData.longitude = res.longitude;
+							_this.addrData.latitude = res.latitude;
+
+							_this.addrData.address = res.name || res.address1;
+						}
+					},
+					fail(err) {
+						console.log(err);
+					}
+				});
+			},
+			// #endif
+			// 解析地址
+			resolveAddr(options) {
+				let _this = this;
+				let str = options.address;
+				let __addr = {
+					addr: ''
+				};
+				let __idx = 0;
+				let __idx_pro = str.indexOf('省');
+
+				if (__idx_pro == -1) {
+					__idx = str.indexOf('自治区');
+					if (__idx != -1) {
+						__addr.province = str.substring(0, __idx + 3);
+					} else {
+						__addr.province = str.substring(0, 0);
+						__idx = 0;
+					}
+				} else {
+					__addr.province = str.substring(0, __idx_pro + 1);
+				}
+
+				if (__addr.province) __addr.addr += __addr.province + '-';
+
+				let __idx_city = str.indexOf('市');
+				if (__idx_city == -1) {
+					__idx = str.indexOf('自治州');
+					if (__idx != -1) {
+						__addr.city = str.substring(__idx_pro + 1, __idx + 3);
+					} else {
+						__addr.city = str.substring(__idx + 1, __idx_city + 1);
+					}
+				} else {
+					if (__idx == 0) {
+						__addr.city = str.substring(__idx_pro + 1, __idx_city + 1);
+					} else {
+						__addr.city = str.substring(__idx + 3, __idx_city + 1);
+					}
+				}
+
+				if (__addr.city) __addr.addr += __addr.city + '-';
+
+				let __idx_area = str.lastIndexOf('区');
+				if (__idx_area == -1) {
+					__idx_area = str.indexOf('县');
+					if (__idx == 0) {
+						__addr.area = str.substring(__idx_city + 1, __idx_area + 1);
+					} else {
+						__addr.area = str.substring(__idx + 3, __idx_area + 1);
+					}
+				} else {
+					if (__idx == 0) {
+						__addr.area = str.substring(__idx_city + 1, __idx_area + 1);
+					} else {
+						__addr.area = str.substring(__idx + 3, __idx_area + 1);
+					}
+				}
+
+				if (__addr.area) __addr.addr += __addr.area;
+
+				return __addr;
+			},
+
+			//提交
+			submit() {
+				let _self=this
+				if (_self.addrDefault) {
+					_self.addrData.defaultFlag = '1';
+				} else {
+					_self.addrData.defaultFlag = '0';
+				}
+
+				let data = _self.addrData;
+				if (!data.receiverName) {
+					_self.$api.msg('请填写收货人');
+					return;
+				}
+				if (!/(^1[3|4|5|7|8|9][0-9]{9}$)/.test(data.phoneNum)) {
+					_self.$api.msg('请输入正确的手机号码');
+					return;
+				}
+
+				
+				if (!data.address) {
+					_self.$api.msg('请填写详细地址');
+					return;
+				}
+				
+				if (_self.addrDefault) {
+					// // 把默认为是的改成 否
+					// _self.$db[__name]
+					// 	.where('create_uid == $env.uid && defaultFlag == "1"')
+					// 	.update({
+					// 		defaultFlag: '0'
+					// 	});
+				}
+				
+				if (_self.type == 'add') {
+					if (!data.address1) {
+						_self.$api.msg('请选择地址');
+						return;
+					}
+					//添加收货地址
+					addressadd(data).then((res) => {
+						if(res.success){
+							_self.$api.msg('添加成功');
+							_self.$api.timerout(() => {
+								uni.navigateBack();
+							}, 100);
+							return
+						}
+						_self.$api.msg(res.msg);
+					})
+				} else {
+					if (!_self.addressName) {
+						_self.$api.msg('请选择地址');
+						return;
+					}
+					if (!_self.id) {
+						_self.$api.msg('当前ID异常,编辑失败');
+						return;
+					}
+					//修改收货地址
+					addressupdate(data).then((res) => {
+						if(res.success){
+							_self.$api.msg('编辑成功');
+							_self.$api.timerout(() => {
+								uni.navigateBack();
+							}, 100);
+							return
+						}
+						_self.$api.msg(res.msg);
+					})
+				}
+			}
+		}
+	};
+</script>
+
+<style lang="scss">
+	page {
+		background: $page-color-base;
+	}
+
+	.row {
+		background: #fff;
+		position: relative;
+		height: 110rpx;
+
+		.tit {
+			flex-shrink: 0;
+			width: 150rpx;
+		}
+
+		.input {
+			flex: 1;
+			font-size: 30rpx;
+			padding-left: 0;
+		}
+
+		.input_t {
+			color: #333;
+		}
+	}
+</style>

+ 244 - 0
packageShang/pages/user/address/address.vue

@@ -0,0 +1,244 @@
+<template>
+	<view class="padding-lr padding-bottom-big margin-bottom ft-main bg-drak">
+		<view class="bg-main padding-top padding-lr border-radius margin-top-sm" v-for="(item, index) in addressDatas"
+			:key="index" @click="selectAddr(item)">
+			<view class="w-full dflex-wrap-w border-line">
+				<view class="fwb margin-bottom-xs desc">
+					<text>{{item.province}}{{item.city}}{{item.county}}{{ item.address }}</text>
+				</view>
+				<view class="margin-bottom-sm">
+					<text>{{ item.receiverName }}</text>
+					<text class="margin-left">{{ item.phoneNum }}</text>
+				</view>
+			</view>
+			<view class="dflex-b">
+				<view v-if="item.defaultFlag == '1'" class="dflex active">
+					<text class="iconfont iconxuanzhongzhuangtai padding-tb-sm padding-right-sm"></text>
+					<text> 默认地址</text>
+				</view>
+				<view v-else class="dflex ft-dark" @tap.stop="setDefault(item)">
+					<text class="iconfont iconweixuanzhongzhuangtai padding-tb-sm padding-right-sm"></text>
+					<text> 设为默认</text>
+				</view>
+				<view v-if="source == 0 || source == 1" class="dflex">
+					<view class="padding-tb-sm padding-right-sm" @tap.stop="addAddr('edit', item)"><text
+							class="iconfont iconbianji-01 ft-dark"></text></view>
+					<view class="padding-tb-sm padding-left-sm" @tap.stop="removeAddr(item)"><text
+							class="iconfont iconlajitong-01 ft-dark"></text></view>
+				</view>
+			</view>
+		</view>
+
+		<view class="btn-container dflex-b pos-f border-radius-big">
+			<view v-if="is_mp" class="tac padding-tb-sm flex1 bg-main" @click="importAddr">{{ platform_name }}导入</view>
+			<view class="tac padding-tb-sm flex1 bg-base-address" @click="addAddr('add')">添加地址</view>
+		</view>
+	</view>
+</template>
+
+<script>
+	import {
+		addresslist,
+		addressadd,
+		addressupdate,
+		addressdelete
+	} from '../../../utils/api_user.js'
+	const __name = 'usemall-member-address';
+	export default {
+		data() {
+			return {
+				env: {},
+
+				is_mp: false,
+				platform: '',
+				platform_name: '',
+				platform_icon: '',
+
+				source: 0,
+				addressDatas: []
+			};
+		},
+		onLoad(option) {
+			this.$api.get_env((res) => {
+				this.env = res;
+				// console.log(this.env);
+				this.is_mp = this.env.is_mp;
+				this.platform = this.env.platform;
+				this.platform_icon = this.env.platform_icon;
+				this.platform_name = this.env.platform_name;
+			});
+
+			this.source = option.source || 0;
+		},
+		onShow() {
+			this.loadData();
+		},
+		methods: {
+			// 加载数据
+			loadData() {
+				let _self=this
+				// 收货人列表
+				addresslist().then((res) => {
+					if(res.success){
+						_self.addressDatas = res.data;
+						return
+					}
+					_self.$api.msg(res.msg);
+				})
+			},
+			// 导入地址
+			importAddr() {
+				let _this = this;
+
+				uni.chooseAddress({
+					success(res) {
+						let addr = {};
+
+						addr.defaultFlag = '0';
+						addr.receiverName = res.userName;
+						addr.phoneNum = res.telNumber;
+						// addr.addr_detail = res.detailInfo;
+						addr.province = res.provinceName;
+						addr.city = res.cityName;
+						addr.county = res.countyName || '';
+
+						// #ifdef MP-ALIPAY
+						addr.county = res.result.area;
+						// #endif
+						addr.address =address
+
+						// addr.address = `${addr.province}-${addr.city}-${addr.county}`;
+						// addr.addr_source = _this.$env.platform;
+
+						if (!addr.phoneNum) {
+							_this.$api.msg('收货人手机不存在')
+							return;
+						}
+						//添加收货地址
+						addressadd(addr).then((res) => {
+							if(res.success){
+								_this.$api.msg('导入成功');
+								_this.loadData();
+								return
+							}
+							_self.$api.msg(res.msg);
+						})
+					},
+					fail(err) {
+						if (err.errMsg.indexOf('cancel') !== -1) {
+							_this.$api.msg('已取消')
+						} else {
+							uni.showModal({
+								content: '打开授权',
+								success: e => {
+									if (e.confirm) {
+										uni.openSetting({
+
+										})
+									}
+								}
+							});
+						}
+					}
+				})
+			},
+			// 默认地址
+			setDefault(options) {
+				var _self=this
+				uni.showModal({
+					title: '提示',
+					content: '设为默认',
+					success: async (res) => {
+						if (res.confirm) {
+							if(options.defaultFlag=='0'){
+								var defaultFlag='1'
+							}else if(options.defaultFlag=='1'){
+								var defaultFlag='0'
+							}
+							//修改收货地址
+							var data={
+								"id": options.id,
+								"receiverName": options.receiverName,
+								"phoneNum": options.phoneNum,
+								"province": options.province,
+								"city": options.city,
+								"county": options.county,
+								"address": options.address,
+								"defaultFlag": defaultFlag,
+								"createBy": options.createBy,
+								"createTime": options.createTime,
+								"updateTime": options.updateTime,
+								"updateBy": options.updateBy
+							}
+							addressupdate(data).then((res) => {
+								if(res.success){
+									_self.loadData();
+									return
+								}
+								_self.$api.msg(res.msg);
+							})
+						} else if (res.cancel) {
+							console.log('用户点击取消');
+						}
+					}
+				});
+			},
+			// 选择地址
+			selectAddr(options) {
+				if (this.source == 1) {
+					uni.$emit('__event_choice_address', options);
+					uni.navigateBack();
+				}
+			},
+			// 添加|编辑 收货人
+			addAddr(type, options) {
+				options = options || {
+					id: 0
+				};
+				uni.navigateTo({
+					url: `/packageShang/pages/user/address/address-edit?type=${type}&id=${options.id}`
+				});
+			},
+			// 删除收货人
+			removeAddr(options) {
+				var data=[options.id]
+				let _this = this;
+				uni.showModal({
+					title: '提示',
+					content: '删除收货人',
+					success: function(res) {
+						if (res.confirm) {
+							//删除收货地址
+							addressdelete(data).then((res) => {
+								if(res.success){
+									_this.loadData();
+									return
+								}
+								_self.$api.msg(res.msg);
+							})
+						} else if (res.cancel) {
+							console.log('用户点击取消');
+						}
+					}
+				});
+
+			}
+		}
+	};
+</script>
+
+<style lang="scss">
+	page {
+		background: $page-color-base;
+	}
+
+	.desc {
+		font-size: $font-lg;
+	}
+
+	.btn-container {
+		left: 20rpx;
+		right: 20rpx;
+		bottom: 20rpx;
+	}
+</style>

+ 216 - 0
packageShang/pages/user/collect/collect.vue

@@ -0,0 +1,216 @@
+<template>
+	<view class="bg-drak" :class="[empty ? '' : 'padding-tb']">
+		<!-- 空白页 -->
+		<use-empty v-if="empty" e-style="round" tip="无收藏数据"></use-empty>
+
+		<view v-else class="padding-lr" v-for="(item, index) in datas" :key="index">
+			<view class="product border-radius-sm padding margin-bottom-sm bg-main" style="padding-bottom: 15rpx;">
+				<view class="left" @click="togoods(item)">
+					<image :src="item.goodsMainImageUrl" mode="aspectFill"></image>
+				</view>
+				<view class="margin-left-sm pos-r w-full">
+					<text class="clamp-2" @click="togoods(item)">{{ item.goodsName }} </text>
+					<view class="pos-a dflex-b price-box w-full">
+						<text class="price padding-tb-sm" @click="togoods(item)">{{ item.goodsPrice }}</text>
+						<view class="dflex-c ft-dark">
+							<button class="btn no-border padding-0 fs-sm ft-dark" open-type="share" :id="item.goodsId">
+								<view class="dflex-c fs-xs padding-tb-sm">
+									<text class="iconfont iconfenxiang margin-left-xs"></text>
+								</view>
+							</button>
+							<view @tap.stop="deleteCollect(item)" class="dflex-c margin-left-sm padding-tb-sm">
+								<text class="iconfont iconlajitong-01 margin-left-xs"></text>
+							</view>
+						</view>
+					</view>
+				</view>
+			</view>
+		</view>
+
+		<!-- 上拉加载更多 -->
+		<use-loadmore v-if="!empty && hasmore" :type="loadmoreType"></use-loadmore>
+		<!-- 置顶 -->
+		<use-totop ref="usetop" bottom="150"></use-totop>
+
+		<view v-if="!empty" class="fixed-top" @click="clear">
+			<text class="iconfont iconlajitong-01 fs-xl"></text>
+		</view>
+	</view>
+</template>
+
+<script>
+	import {
+		collectlist,
+		collectdelete,
+		collectclear
+	} from '../../../utils/api_user.js'
+	import useEmpty from '../../../components/use-empty/use-empty.vue'
+	import useLoadmore from '../../../components/use-loadmore/use-loadmore.vue'
+	import useTotop from '../../../components/use-totop/use-totop.vue'
+	const db = uniCloud.database();
+	const _collect = 'usemall-member-collect'
+	import { mapState } from 'vuex';
+	export default {
+		computed: {
+			...mapState(['islogin', 'member'])
+		},
+		components:{
+			useEmpty,
+			useLoadmore,
+			useTotop
+		},
+		data() {
+			return {
+				// 加载更多状态
+				loadmoreType: 'nomore',
+				// 数据源
+				datas: [],
+				empty: false,
+				hasmore: 0,
+				reqdata: {
+					rows: 20,
+					page: 1
+				},
+				scrollTop: 0,
+			};
+		},
+		watch: {
+			datas(e) {
+				let empty = e.length === 0;
+				if (this.empty !== empty) {
+					this.empty = empty;
+				}
+			}
+		},
+		onShareAppMessage: function(ops) {
+			let _this = this;
+			console.log(_this.member)
+			let mid = 0;
+			if (_this.member && _this.member.id) {
+				mid = _this.member.id;
+			}
+
+			let goods = _this.datas.find(x => x.goodsId == ops.target.id);
+			let share_img = '';
+			if (goods && goods.id > 0) {
+				share_img = goods.share_img;
+			}
+
+			return {
+				imageUrl: share_img,
+				bgImgUrl: share_img, //[${_this.member.user_name}] 的
+				title: `来自收藏夹`,
+				path: `/pages/goods/goods?id=${ops.target.id}&mid=${mid}`,
+				content: '大魏商城',
+				desc: '大魏商城',
+				success: function(res) {
+					// 转发成功
+					console.log(res);
+					console.log('转发成功', JSON.stringify(res));
+				},
+				fail: function(res) {
+					// 转发失败
+					console.log('转发失败', JSON.stringify(res));
+				}
+			};
+		},
+		onPageScroll(e) {
+			//this.scrollTop = e.scrollTop;
+			this.$refs.usetop.change(e.scrollTop);
+		},
+		onShow() {
+			this.loadData();
+		},
+		methods: {
+			loadData() {
+				let _self=this
+				// const goodsTemp = db.collection('usemall-goods').getTemp();
+				
+				//收藏数据
+				collectlist().then((res) => {
+					if(res.success){
+						let _historyDatas = [];
+						res.data.list.forEach(x => {
+							_historyDatas.push(x);
+						});
+						_self.datas = _historyDatas;
+						if (_self.datas.length === 0) {
+							_self.empty = true;
+						}
+						return
+					}
+					_self.$api.msg(res.msg);
+				})
+			},
+			// 删除收藏
+			deleteCollect(item) {
+				let _this = this
+				uni.showModal({
+					title: '提示',
+					content: '删除收藏',
+					success: function(res) {
+						if (res.confirm) {
+							var data=item.goodsId
+							collectdelete(data).then((res) => {
+								if(res.success){
+									_this.loadData();
+									return
+								}
+								_this.$api.msg(res.msg);
+							})
+						} else if (res.cancel) {
+							console.log('用户点击取消');
+						}
+					}
+				});
+			},
+			// 清空收藏
+			clear() {
+				let _this = this;
+				uni.showModal({
+					title: '提示',
+					content: '清空收藏',
+					success: function(res) {
+						if (res.confirm) {
+							collectclear().then((res) => {
+								if(res.success){
+									_this.datas = [];
+									return
+								}
+								_this.$api.msg(res.msg);
+							})
+						} else if (res.cancel) {
+							console.log('用户点击取消');
+						}
+					}
+				});
+			},
+			togoods(item) {
+				this.$api.togoods({
+					id: item.goodsId
+				});
+			}
+		}
+	};
+</script>
+
+<style lang="scss">
+	page {
+		background: $page-color-base;
+	}
+
+	.product {
+		display: flex;
+
+		.left {
+			image {
+				width: 180rpx;
+				height: 180rpx;
+			}
+		}
+
+		.price-box {
+			bottom: -20rpx;
+		}
+	}
+</style>

+ 48 - 0
packageShang/pages/user/integral/detail.vue

@@ -0,0 +1,48 @@
+<template>
+	<view class="padding">
+		
+		<view class="padding item border-radius dflex-b margin-bottom" v-for="(item, idx) in datas" :key="idx">
+			<view class="fs fwb">{{item.time}}</view>
+			<view class="tar">
+				<view class="fwb"><text class="fs-lg">{{item.symbol}}{{item.integral}}</text></view>
+				<view class="fs-xs">{{item.type}}</view>
+			</view>
+		</view>
+		
+	</view>
+</template>
+
+<script>
+	export default {
+		data() {
+			return {
+				datas: [],
+			}
+		},
+		onShow() {
+			this.loadData();
+		},
+		methods: {
+			loadData(){
+				this.datas = [
+					{time: '2021.06.10 12:00', integral: 10, symbol: '+', type: '签到'},
+					{time: '2021.06.11 12:00', integral: 10, symbol: '-', type: '支出'},
+					{time: '2021.06.12 12:00', integral: 10, symbol: '-', type: '支出'},
+					{time: '2021.06.13 12:00', integral: 10, symbol: '+', type: '签到'},
+					{time: '2021.06.14 12:00', integral: 10, symbol: '+', type: '签到'},
+					{time: '2021.06.16 12:00', integral: 10, symbol: '-', type: '消费'},
+					{time: '2021.06.15 12:00', integral: 10, symbol: '+', type: '支出'},
+					{time: '2021.06.17 12:00', integral: 10, symbol: '+', type: '签到'},
+					{time: '2021.06.18 12:00', integral: 10, symbol: '-', type: '消费'},
+					{time: '2021.06.18 12:00', integral: 10, symbol: '+', type: '注册'},
+				];
+			},
+		}
+	}
+</script>
+
+<style lang="scss">
+	.item {
+		box-shadow: 0px 0px 17px 3px #f0f0f0;;
+	}
+</style>

+ 119 - 0
packageShang/pages/user/integral/sign.vue

@@ -0,0 +1,119 @@
+<template>
+	<view class="sign-area">
+		<view class="sign-top pos-r bg-base dflex dflex-flow-c">
+			<view class="ft-white margin-top-sm fwb" style="font-size: 70rpx">{{data.member_integral || 750}}</view>
+			<view class="btn-line ft-black fs-xs border-radius-big" @click="toDetail">积分明细</view>
+		</view>
+		<view class="sign-content margin pos-a">
+			<view class="bg-main border-radius padding">
+				<view class="title">
+					<view class="fwb fs">
+						<text class="d">已累计签到</text>
+						<text class="ft-base margin-left-sm fwb">{{data.sign_cnt || 6}}天</text>
+					</view>
+				</view>
+				<view class="dflex-b margin-top">
+					<view class="dflex-c dflex-flow-c" v-for="(item, index) in weeks" :key="index">
+						<view :class="{ active: item.sign }" class="iconfont iconxuanzhong"></view>
+						<view>{{item.name}}</view>
+					</view>
+				</view>
+			</view>
+			
+			<view class="margin-top-xl">
+				<view class="tac margin fs-xs">
+					<text>连续签到{{data.sign_in_full || 7}}天</text>
+					<text class="margin-left-xs">可额外获得{{ data.sign_in_full_integral || '10' }}积分</text>
+				</view>
+				
+				<view class="w-full margin-top">
+					<view class="dflex-b border-radius-big">
+						<view class="tac padding-tb-sm flex1 bg-base" v-if="!signed" @click="toSign">立即签到</view>
+						<view class="tac padding-tb-sm flex1 bg-disabled" v-else>今日已签到</view>
+					</view>
+				</view>
+			</view>
+		</view>
+		
+		<view class="pos-f pos-bottom padding tac w-full bg-warn fs-xs">有200积分即将在2021.12.30过期</view>
+	</view>
+</template>
+
+<script>
+	export default {
+		data() {
+			return {
+				weeks: [],
+				data: {},
+				signed: false,
+			}
+		},
+		onShow() {
+			this.loadData();
+		},
+		methods: {
+			loadData(){
+				this.weeks = [];
+				this.weeks.push({ name: '周一', sign: true });
+				this.weeks.push({ name: '周二', sign: true });
+				this.weeks.push({ name: '周三', sign: true });
+				this.weeks.push({ name: '周四', sign: true });
+				this.weeks.push({ name: '周五', sign: false });
+				this.weeks.push({ name: '周六', sign: false });
+				this.weeks.push({ name: '周日', sign: false });
+			},
+			toDetail() {
+				uni.navigateTo({
+					url: '/pages/user/integral/detail'
+				});
+			},
+			toSign() {
+				if (this.signed) {
+					this.$api.msg('已签到,明天再来吧')
+					return;
+				}
+				
+				this.signed = true;
+				this.$api.msg('恭喜签到成功');
+			}
+		}
+	}
+</script>
+
+<style lang="less">
+	page {
+		overflow: hidden;
+		background-color: #f5f5f5;
+	}
+	
+	.sign-area {
+		.sign-top {
+			left: -10%;
+			width: 120%;
+			height: 360rpx;
+			padding: 0rpx 10%;
+			border-bottom-right-radius: 50%;
+			border-bottom-left-radius: 50%;
+
+			.btn-line {
+				padding: 10rpx 30rpx;
+				border: 1px solid #333;
+			}
+		}
+		
+		.bg-disabled {
+			background-color: #c0c4cd !important;
+			color: #fff !important;
+		}
+
+		.sign-content {
+			top: 240rpx; left: 0; right: 0; 
+			margin: 0 50rpx;
+			
+			.iconfont {
+				color: #c0c4cd;
+				font-size: 50rpx;
+			}
+		}
+	}
+</style>

+ 563 - 0
packageShang/pages/user/order/order-detail.vue

@@ -0,0 +1,563 @@
+<template>
+	<view class="order-detail">
+		<!-- 订单状态 -->
+		<view class="state-area padding-lr margin-tb-sm">
+			<view class="padding border-radius bg-base-zhi">
+
+				<view v-if="order_data.state == '1'">
+					<view class="dflex-c fs-lg">
+						<text class="iconfont icondaifukuan- fs-lg fwb"></text>
+						<text class="fwb margin-left-sm">订单待支付</text>
+					</view>
+					<!-- <view class="dflex-c">
+						剩余时间:
+						<use-count-down :show-days="false" separator="zh" separator-color="#fff" font-size="24"
+							:timestamp="time_remaining"></use-count-down>
+					</view> -->
+				</view>
+
+				<view v-if="order_data.state == '2'">
+					<view class="dflex-c fs-lg">
+						<text class="iconfont icondaifahuo- fs-lg fwb"></text>
+						<text class="fwb margin-left-sm">订单备货中</text>
+					</view>
+					<view class="dflex-c">预计1天后发货</view>
+				</view>
+
+				<view v-if="order_data.state == '3'">
+					<view class="dflex-c fs-lg">
+						<text class="iconfont icondaishouhuo- fs-lg fwb"></text>
+						<text class="fwb margin-left-sm">订单已发货</text>
+					</view>
+					<view class="dflex-c">还剩15天10时自动确认</view>
+				</view>
+
+				<view v-if="order_data.state == '4'">
+					<view class="dflex-c fs-lg">
+						<text class="iconfont iconyiwancheng- fs-lg fwb"></text>
+						<text class="fwb margin-left-sm">订单已收货</text>
+					</view>
+					<view class="dflex-c">感谢您的支持,评价送积分</view>
+				</view>
+				
+				<view v-if="order_data.state == '5'">
+					<view class="dflex-c fs-lg">
+						<text class="iconfont iconyiwancheng- fs-lg fwb"></text>
+						<text class="fwb margin-left-sm">订单已完成</text>
+					</view>
+					<view class="dflex-c">感谢您的支持,期待下次购买</view>
+				</view>
+
+				<view v-if="order_data.state == '6'">
+					<view class="dflex-c fs-lg">
+						<text class="iconfont icondaifukuan- fs-lg fwb"></text>
+						<text class="fwb margin-left-sm">订单已关闭</text>
+					</view>
+					<view class="dflex-c">感谢您的支持</view>
+				</view>
+				
+				<view v-if="order_data.state == '0'">
+					<view class="dflex-c fs-lg">
+						<text class="iconfont icondaifukuan- fs-lg fwb"></text>
+						<text class="fwb margin-left-sm">已申请售后</text>
+					</view>
+					<view class="dflex-c">请耐心等待工作人员处理</view>
+				</view>
+				<view v-if="order_data.state == '售后结束'">
+					<view class="dflex-c fs-lg">
+						<text class="iconfont iconyiwancheng- fs-lg fwb"></text>
+						<text class="fwb margin-left-sm">{{order_data.order_refund_state}}</text>
+					</view>
+					<view class="dflex-c">感谢您的支持</view>
+				</view>
+			</view>
+		</view>
+
+		<!-- 收货人 -->
+		<view class="address-area padding-lr margin-tb-sm" v-if="order_data.orderConsignee">
+			<view class="dflex padding border-radius bg-main">
+				<view class="iconfont icondizhi- margin-right ft-main"></view>
+				<view class="flex1">
+					<view class="w-full dflex-wrap-w">
+						<view class="margin-bottom-xs desc">
+							<text>{{ order_data.orderConsigneeProvince }}{{ order_data.orderConsigneeCity }}{{ order_data.orderConsigneeDistrict }}
+								{{ order_data.orderConsigneeAddrDetail }}</text>
+						</view>
+						<view>
+							<text>{{ order_data.orderConsignee }}</text>
+							<text class="margin-left">{{ order_data.orderConsigneeTel }}</text>
+						</view>
+					</view>
+				</view>
+			</view>
+		</view>
+
+		<!-- 订单商品明细 -->
+		<view class="padding margin-lr margin-tb-sm bg-main border-radius">
+			<view class="goods-area" :class="{ 'margin-top': index > 0 }" v-for="(item, index) in order_detail"
+				:key="index">
+				<view class="dflex">
+					<view class="img">
+						<image :src="item.goodsMasterImg"></image>
+					</view>
+					<view class="margin-left-sm">
+						<text class="clamp-2">{{ item.goodsName }}</text>
+						<view class="ft-dark fs-xs padding-top-xs">
+							<text class="margin-right">× {{item.goodsCount}}</text>
+							<!-- {{ item.goods_sku_name || '&nbsp;&nbsp;' }} -->
+						</view>
+						<view class="margin-top-sm">
+							<text class="price">{{ item.goodsActualPrice }}</text>
+						</view>
+					</view>
+				</view>
+			</view>
+		</view>
+
+		<!-- 订单数据 -->
+		<view class="order-area padding margin-lr margin-tb-sm bg-main border-radius">
+			<view class="item">
+				<text>订单编号:</text>
+				<text class="">{{ order_data.orderNo }}</text>
+				<text class="copy" @click="copy">复制</text>
+			</view>
+			<view class="item">
+				<text>下单时间:</text>
+				<text class="">{{ order_data.createTime }}</text>
+			</view>
+			<view class="item">
+				<text>支付方式:</text>
+				<text class="">
+					<!-- 1 微信支付 2余额支付 3支付宝 4网银 5其他 -->
+					<template v-if="order_data.payWay==1">微信支付</template>
+					<template v-if="order_data.payWay==2">余额支付</template>
+					<template v-if="order_data.payWay==3">支付宝</template>
+					<template v-if="order_data.payWay==4">网银</template>
+					<template v-if="order_data.payWay==5">其他</template>
+				</text>
+			</view>
+			<view class="item">
+				<text>支付编号:</text>
+				<text class="">{{ order_data.payNo }}</text>
+			</view>
+		</view>
+
+		<!-- 退款数据 -->
+		<view class="order-area padding margin-lr margin-tb-sm bg-main border-radius"
+			v-if="order_data.order_refund_state">
+			<view class="item">
+				<text>退款原因:</text>
+				<text class="">{{ order_data.order_refund_reason }}</text>
+			</view>
+			<view class="item">
+				<text>退款说明:</text>
+				<text class="">{{ order_data.order_refund_desc }}</text>
+			</view>
+			<view class="item">
+				<text>退款状态:</text>
+				<text>{{ order_data.order_refund_state }}</text>
+			</view>
+			<view class="item">
+				<text>退款情况:</text>
+				<text>{{ order_data.order_refund_remark || '' }}</text>
+			</view>
+		</view>
+
+		<!-- 统计数据 -->
+		<view class="total-area padding margin-lr margin-tb-sm bg-main border-radius" v-for="(item, index) in order_detail"
+				:key="index">
+			<view class="ft-dark">
+				<view class="item dflex-b">
+					<text>{{ goods_price_tip}}</text>
+					<text class="">¥{{ item.goodsTotalAmt }}</text>
+				</view>
+				<view class="item dflex-b">
+					<text>优惠</text>
+					<text class="">¥{{ order_data.order_coupon_price || 0}}</text>
+				</view>
+				<view class="item dflex-b">
+					<text>运费</text>
+					<text class="">¥0</text>
+				</view>
+			</view>
+			<view class="item dflex-b">
+				<text>实付款</text>
+				<text class="price">{{ order_data.orderActualPrice }}</text>
+			</view>
+		</view>
+		<view style="height: 100rpx;"></view>
+
+		<!-- 底部操作区 -->
+		<view class="oper-area dflex-b padding-right padding-left-sm">
+			<view class="dflex">
+				<view class="btn-area dflex dflex-flow-c" @click="tohome">
+					<text class="iconfont iconshouye-1"></text>
+					<text>首页</text>
+				</view>
+				<!-- #ifndef H5 || MP-360 || MP-ALIPAY -->
+				<button class="btn no-border" open-type="contact">
+					<view class="btn-area dflex dflex-flow-c">
+						<text class="iconfont iconkefu-01"></text>
+						<text>客服</text>
+					</view>
+				</button>
+				<!-- #endif -->
+			</view>
+
+			<view class="dflex-e">
+				<view class="dflex" v-if="order_data.state == '1'">
+					<button class="action-btn" @click="cancelOrder">取消订单</button>
+					<button v-if="order_data.state == '1'" class="action-btn main-btn"
+						@click="payment">立即支付</button>
+					<!-- <button v-if="order_data.order_pay_state == '待核实'" class="action-btn main-btn"
+						@click="payment">待核实</button> -->
+				</view>
+				<!-- <view class="dflex" v-if="order_data.state == '2'">
+					<button v-if="!order_data.order_refund_state" class="action-btn border-radius-big bg-main"
+						@click="refund">申请退款</button>
+				</view> -->
+				<view class="dflex" v-if="order_data.state == '2'">
+					<button class="action-btn border-radius-big bg-main"
+						@click="refund">申请退款</button>
+				</view>
+				<!-- <button v-if="!order_data.order_refund_state && 
+					['3', '4', '5'].includes(order_data.state)" class="action-btn" @click="toexpress">查看物流</button>
+				<view class="dflex" v-if="order_data.state == '3'">
+					<button v-if="!order_data.order_refund_state" class="action-btn main-btn" @click="toreceipt">确认收货</button>
+				</view> -->
+				<button v-if="['3',].includes(order_data.state)" class="action-btn" @click="toexpress">查看物流</button>
+				<view class="dflex" v-if="order_data.state == '3'">
+					<button class="action-btn main-btn" @click="toreceipt">确认收货</button>
+				</view>
+				<view class="dflex" v-if="order_data.state == '4'">
+					<button class="action-btn main-btn" @click="evaluate">我要评价</button>
+				</view>
+				<view v-if="order_data.state == '5' || order_data.state == '6'"><button @click="delorder" class="action-btn main-btn">删除订单</button></view>
+			</view>
+		</view>
+	</view>
+</template>
+
+<script>
+	import {
+		orderinfo,
+		shouhuo,
+		orserdelete,
+		orserclear
+	} from '../../../utils/api_order.js'
+	export default {
+		components: {
+			// share
+		},
+		data() {
+			return {
+				// 商品数据
+				order_detail: [],
+				// 订单数据
+				order_data: {},
+				addressData: [],
+				sharekefuList: [],
+				shareEmptyList: [],
+				orderId: '',
+				goods_price_tip: '产品总计',
+				time_remaining: 0,
+			};
+		},
+		onUnload() {
+			uni.$emit('__event_order', 'refresh');
+		},
+		onLoad(options) {
+
+			this.orderId = options.order_id;
+
+			this.loadData();
+		},
+		onShow() {
+			this.loadData();
+		},
+		methods: {
+			tohome() {
+				this.$api.tohome();
+			},
+			loadData() {
+				let _this = this;
+				// 订单详情
+				var data=_this.orderId
+				orderinfo(data).then((res) => {
+					if(res.success){
+						_this.order_data = res.data;
+						_this.order_detail = res.data.orderDetails;
+						return
+					}
+					_this.$api.msg(res.msg);
+				})
+				// this.$func.usemall.call('order/detail', {
+				// 	order_id: _this.order_id
+				// }).then(res => {
+				// 	if (res.code === 200) {
+				// 		res.datas.order.create_time = this.$api.format(res.datas.order.create_time);
+				// 		_this.order_data = res.datas.order;
+				// 		_this.order_detail = res.datas.order_detail;
+				// 		_this.addressData = res.datas.order_trip;
+				// 		if (res.datas.order && res.datas.order.state === '待付款') {
+				// 			_this.time_remaining = res.datas.time_remaining;
+				// 		}
+
+				// 		_this.order_detail.forEach(data => {
+				// 			if (data.goods_opt_id > 0) {
+				// 				let desc = '';
+				// 				if (data.goods_opt_desc) {
+				// 					desc = ' (' + data.goods_opt_desc + ')';
+				// 				}
+				// 				_this.goods_price_tip = data.goods_opt_name + desc;
+				// 			}
+				// 		});
+				// 	}
+				// })
+			},
+			// 立即支付
+			payment() {
+				if (this.order_data.order_pay_state == '待核实') {
+					this.$api.msg('订单已支付待核实状态');
+					return;
+				}
+
+				this.$api.topay({
+					order_id: this.order_data.orderId,
+					money: this.order_data.orderActualPrice,
+				});
+			},
+			// 查看物流
+			toexpress(item) {
+				// this.$api.msg('查看物流开发中');
+				uni.navigateTo({
+					url: `/packageShang/pages/user/order/order-express?order_id=${this.orderId}`
+				});
+			},
+			// 确认发货
+			toreceipt() {
+				let _this = this;
+
+				uni.showModal({
+					title: '提示',
+					content: '确认收货',
+					success: function(res) {
+						if (res.confirm) {
+							uni.showLoading({
+								title: '请稍后'
+							});
+							var data=_this.orderId
+							shouhuo(data).then((res) => {
+								if(res.success){
+									_this.loadData('refresh');
+									return
+								}
+								_this.$api.msg(res.msg);
+							})
+						} else if (res.cancel) {
+							console.log('用户点击取消');
+						}
+					},
+					complete() {
+						uni.hideLoading();
+					}
+				});
+			},
+			// 删除订单
+			delorder() {
+				let _this = this;
+
+				uni.showModal({
+					title: '提示',
+					content: '删除订单',
+					success: function(res) {
+						if (res.confirm) {
+							uni.showLoading({
+								title: '请稍后'
+							});
+							var data=_this.orderId
+							orserdelete(data).then((res) => {
+								if(res.success){
+									uni.navigateBack({});
+									return
+								}
+								_this.$api.msg(res.msg);
+							})
+						} else if (res.cancel) {
+							console.log('点击取消');
+						}
+					},
+					complete() {
+						uni.hideLoading();
+					}
+				});
+			},
+			// 取消订单
+			cancelOrder() {
+				let _this = this;
+
+				uni.showModal({
+					title: '提示',
+					content: '取消订单',
+					success: function(res) {
+						if (res.confirm) {
+							uni.showLoading({
+								title: '请稍后'
+							});
+							var data=_this.orderId
+							orserclear(data).then((res) => {
+								if(res.success){
+									_this.loadData('refresh');
+									return
+								}
+								_this.$api.msg(res.msg);
+							})
+						} else if (res.cancel) {
+							console.log('用户点击取消');
+						}
+					},
+					complete() {
+						uni.hideLoading();
+					}
+				});
+			},
+			// 点击复制
+			copy() {
+				let _this = this;
+
+				uni.setClipboardData({
+					data: _this.order_data.orderNo,
+					success: function(res) {
+						uni.getClipboardData({
+							success: function(res) {
+								uni.showToast({
+									title: '复制成功'
+								});
+							}
+						});
+					}
+				});
+			},
+			// 评价
+			evaluate() {
+				uni.navigateTo({
+					url: `/packageShang/pages/user/order/order-evaluate?id=${this.orderId}`
+				});
+			},
+			// 申请退款
+			refund() {
+				uni.navigateTo({
+					url: `/packageShang/pages/user/order/order-refund?order_id=${this.orderId}`
+				});
+			},
+		},
+	}
+</script>
+
+<style lang="scss">
+	page {
+		background: $page-color-base;
+	}
+
+	.order-detail .item text:first-child {
+		width: 152rpx;
+		text-align: right;
+		display: inline-block;
+	}
+
+	/* 状态区 */
+	.state-area {}
+
+	/* 收货人 */
+	.address-area {}
+
+	/* 商品区 */
+	.goods-area {
+		&:last-child {
+			margin-bottom: 0;
+		}
+
+		image {
+			width: 180rpx;
+			height: 180rpx;
+		}
+	}
+
+	/* 订单数据区 */
+	.order-area {
+		.item {
+			line-height: 66rpx;
+
+			.copy {
+				margin-left: 20rpx;
+				padding: 10rpx 40rpx;
+				background-color: #f1f1f1;
+				border-radius: 40rpx;
+				font-size: 24rpx;
+			}
+		}
+	}
+
+	/* 数据统计区 */
+	.total-area {
+		.item {
+			line-height: 48rpx;
+
+			text {
+				padding-right: 16rpx;
+			}
+		}
+	}
+
+	/* 操作区 */
+	.oper-area {
+		width: 100%;
+		height: 100rpx;
+		background-color: #fff;
+		position: fixed;
+		z-index: 1;
+		bottom: 0;
+		left: 0;
+		border-top: 1px solid #f0f0f0;
+
+		.btn-area {
+			font-size: $font-sm;
+			color: $font-color-base;
+			width: 96rpx;
+
+			.iconfont {
+				font-size: 40rpx;
+				line-height: 48rpx;
+			}
+		}
+
+		/* 操作按钮 */
+		.action-btn {
+			width: 156rpx;
+			height: inherit;
+			line-height: inherit;
+			margin: 0;
+			margin-left: 20rpx;
+			padding: 12rpx 0;
+			font-size: $font-sm + 2upx;
+			background: #fff;
+			border-radius: 100px;
+			/* #ifdef MP-QQ || MP-ALIPAY */
+			border: 1px solid;
+			/* #endif */
+
+			&:after {
+				border-radius: 100px;
+			}
+
+			&.main-btn {
+				background: #fff9f9;
+				color: $base-color;
+
+				&:after {
+					border-color: #f7bcc8;
+				}
+			}
+		}
+	}
+</style>

+ 240 - 0
packageShang/pages/user/order/order-evaluate.vue

@@ -0,0 +1,240 @@
+<template>
+	<view class="">
+		<!-- 商品区 -->
+		<view class="padding margin-lr margin-tb-sm bg-main border-radius">
+			<view class="goods-area" v-for="(item, index) in order_detail" :key="index">
+				<view class="dflex">
+					<view class="img">
+						<image :src="item.goodsMasterImg"></image>
+					</view>
+					<view class="margin-left-sm">
+						<text class="clamp-2">{{ item.goodsName }}</text>
+						<view class="ft-dark fs-xs padding-top-xs">
+							<text class="margin-right">× {{ item.goodsCount }}</text>
+							{{ item.goods_sku_name || '&nbsp;&nbsp;' }}
+						</view>
+						<view class="margin-top-sm">
+							<text class="price">{{ order_data.orderActualPrice }}</text>
+						</view>
+					</view>
+				</view>
+			</view>
+		</view>
+
+		<!-- 评分 -->
+		<view class="evaluate-kps">
+			<view class="padding margin-lr margin-tb-sm bg-main border-radius dflex-b">
+				<view>
+					<text>总体评分</text>
+					<text class="margin-left ft-base fs-xs">
+						{{postData.review_type}}
+					</text>
+				</view>
+				<use-rate @change="rateChange" value="5"></use-rate>
+			</view>
+		</view>
+
+		<!-- 评价区 -->
+		<view class="evaluate-area">
+			<view class="padding margin-lr margin-tb-sm bg-main border-radius">
+				<!-- 评价内容 -->
+				<textarea class="ft-black w-full fs-sm" v-model="postData.review_content"
+					placeholder="请输入评价内容"></textarea>
+
+				<!-- 上传图片 -->
+				<use-upload class="pos-r" @upload="uploadImgs"></use-upload>
+			</view>
+		</view>
+
+		<!-- 是否匿名评价 -->
+		<view>
+			<view class="padding margin-lr margin-tb-sm bg-main border-radius dflex-b">
+				<text>提交评价的图片{{ anonymity ? '不可见' : '可见' }}</text>
+				<view>
+					<!-- <text class="ft-dark">匿名</text> -->
+					<switch color="#FF6A6C" @change="switchChange" />
+				</view>
+			</view>
+		</view>
+
+		<!-- 提交操作 -->
+		<view class="padding w-full margin-top">
+			<view class="dflex-b border-radius-big">
+				<view class="tac padding-tb-sm flex1 bg-base-ping" @click="submit">提交评价</view>
+			</view>
+		</view>
+
+	</view>
+</template>
+<script>
+	import {
+		orderinfo,
+		comment,
+	} from '../../../utils/api_order.js'
+	import useUpload from '../../../components/use-upload/use-upload.vue'
+	import useRate from '../../../components/use-rate/use-rate.vue'
+	export default {
+		components:{
+			useUpload,
+			useRate
+		},
+		data() {
+			return {
+				// 商品数据
+				order_detail: [],
+				// 订单数据
+				order_data: {},
+				// 订单ID
+				order_id: '',
+				postData: {
+					orderId: '',
+					review_cnt: 5,
+					review_type: '好评',
+					review_content: '',
+					review_imgs: '',
+					review_anonymity: '0',
+					
+				},
+				now_date:'',//当前时间
+			};
+		},
+		onLoad(options) {
+			this.order_id = options.id;
+			if (!this.order_id) {
+				this.$api.msg('订单编号不存在');
+			}
+			this.loadData();
+		},
+		methods: {
+			loadData() {
+				let _this = this;
+				// 订单详情
+				var data=_this.order_id
+				orderinfo(data).then((res) => {
+					if(res.success){
+						_this.order_data = res.data;
+						_this.order_detail = res.data.orderDetails;
+						return
+					}
+					_this.$api.msg(res.msg);
+				})
+			},
+			uploadImgs(options) {
+				let imgs = [];
+
+				options.forEach((_) => {
+					imgs.push(_);
+				});
+				console.log(imgs,'imgsa')
+
+				if (imgs.length > 0) this.postData.review_imgs = imgs;
+
+				console.log('uploadImgs', this.postData.review_imgs);
+			},
+			submit() {
+				this.getNowDate()
+				let _this = this;
+				if (!_this.postData.review_content) {
+					_this.$api.msg('请填写评价内容');
+					return;
+				}
+				if (_this.issubmit) return;
+				if(_this.postData.review_type=='差评'){
+					var type=1
+				}else if(_this.postData.review_type=='中评'){
+					var type=2
+				}else if(_this.postData.review_type=='好评'){
+					var type=3
+				}
+
+				_this.issubmit = true;
+				_this.postData.orderId = _this.order_id;
+				var imgshu=this.postData.review_imgs.toString()
+				uni.showModal({
+					title: '提示',
+					content: '提交评价',
+					success: function(res) {
+						if (res.confirm) {
+							var data={
+								"orderId": _this.postData.orderId,
+								"goodsId": _this.order_detail[0].goodsId,
+								"reviewContent": _this.postData.review_content,
+								"reviewType": type,//1差评 2中评 3好评
+								"reviewCnt": _this.postData.review_cnt,//1-5
+								"reviewImgs": imgshu,
+								"state": _this.postData.review_anonymity,//0隐藏 1显示
+								"createTime": _this.now_date,
+							}
+							comment(data).then((res) => {
+								if(res.success){
+									_this.$api.msg('提交成功');
+									_this.issubmit = false;
+									uni.navigateBack({});
+									return
+								}
+								_this.$api.msg(res.msg);
+								_this.issubmit = false;
+							})
+						} else if (res.cancel) {
+							console.log('用户点击取消');
+						}
+					}
+				});
+
+			},
+			//获取当前时间
+			getNowDate() {
+			  var _this = this;
+			  // this.timer = setInterval(function() {
+				var aData = new Date();
+				var month = aData.getMonth() < 9 ? "0" + (aData.getMonth() + 1) : aData.getMonth() + 1;
+				var date = aData.getDate() <= 9 ? "0" + aData.getDate() : aData.getDate();
+				var date2 = aData.getDate() <= 9 ? "0" + (aData.getDate()-1) : (aData.getDate()-1);
+				var Hour = aData.getHours() <= 9 ? "0" + (aData.getHours()) : aData.getHours();
+				var Miunte = aData.getMinutes() <= 9 ? "0" + (aData.getMinutes()) : aData.getMinutes();
+				var Seconds = aData.getSeconds() <= 9 ? "0" + (aData.getSeconds()) : aData.getSeconds();
+				// console.log(aData.getTime())
+				_this.now_date = aData.getFullYear() + "-" + month + "-" + date + ' '+ Hour +":"+ Miunte +":"+ Seconds;
+				// console.log(aData.getFullYear() + "-" + month + "-" + date2)昨天
+			  // }, 86400000);
+			},
+			switchChange(options) {
+				this.postData.review_anonymity = options.detail.value ? '1' : '0';
+			},
+			rateChange(options) {
+				switch (options.value) {
+					case 1:
+						this.postData.review_type = '差评';
+						break;
+					case 2:
+					case 3:
+						this.postData.review_type = '中评';
+						break;
+					case 4:
+					case 5:
+						this.postData.review_type = '好评';
+						break;
+				}
+				this.postData.review_cnt = options.value;
+			}
+		}
+	};
+</script>
+
+<style lang="scss">
+	page {
+		background: $page-color-base;
+	}
+
+	/* 商品区 */
+	.goods-area {
+		&:last-child {
+			margin-bottom: 0;
+		}
+
+		image {
+			width: 180rpx;
+			height: 180rpx;
+		}
+	}
+</style>

+ 160 - 0
packageShang/pages/user/order/order-express.vue

@@ -0,0 +1,160 @@
+<template>
+	<view class="bg-drak" :class="[empty ? '' : 'padding-tb']">
+		<!-- 空白页 -->
+		<use-empty v-if="empty" e-style="round" tip="无物流数据"></use-empty>
+	
+		<view v-if="expressData" class="padding-lr">
+			<view class="border-radius padding margin-bottom-sm bg-main">
+				<view class="fs-lg fwb">{{expressData.expressName}}</view>
+				<view>物流单号:{{expressData.expressNo}}<text class="copy" @click="copy">复制</text></view>
+				<!-- <view class="fs-lg fwb">{{expressData.detail}}</view> -->
+			</view>
+		</view>
+		<view v-if="expressData" class="padding-lr">
+			<view class="product border-radius padding margin-bottom-sm bg-main" style="padding-bottom: 15rpx;">
+				<view :class="{ 'active': index == 0, 'fwb': index == 0 }" class="dflex item pos-r" v-for="(item, index) in detail" :key="index">
+					<view :class="{ 'active': index == 0 }" class="circle"></view>
+					<view :class="{ 'ft-dark': index > 0 }" class="margin-left-lg pos-r w-full margin-bottom">
+						<view>{{item.context}}</view>
+						<view class="margin-top-xs fs-xs">{{item.time}}</view>
+					</view>
+				</view>
+			</view>
+		</view>
+	
+		<!-- 置顶 -->
+		<use-totop ref="usetop"></use-totop>
+	</view>
+</template>
+
+<script>
+	import {
+		orderexpress
+	} from '../../../utils/api_order.js'
+	import useEmpty from '../../../components/use-empty/use-empty.vue'
+	import useTotop from '../../../components/use-totop/use-totop.vue'
+	export default {
+		components:{
+			useEmpty,
+			useTotop,
+		},
+		data() {
+			return {
+				empty: false,
+				order_id: '',
+				expressData: {},
+				detail:[],
+			}
+		},
+		watch: {
+			expressData(e) {
+				let empty = !e;
+				if (this.empty !== empty) {
+					this.empty = empty;
+				}
+			}
+		},
+		onPageScroll(e) {
+			//this.scrollTop = e.scrollTop;
+			this.$refs.usetop.change(e.scrollTop);
+		},
+		onLoad(options) {
+			this.order_id = options.order_id;
+			
+			if(!this.order_id) {
+				this.$api.msg('订单号不存在');
+			}
+			
+			this.loadData();
+		},
+		onShow() {
+			
+		},
+		methods: {
+			loadData() {
+				var _self=this
+				//根据订单id查物流信息
+				var data=_self.order_id
+				orderexpress(data).then((res) => {
+					if(res.success){
+						_self.expressData = res.data;
+						return
+					}
+					_self.detail=JSON.parse(_self.expressData.detail)
+					_self.$api.msg(res.message);
+				})
+			},
+			// 点击复制
+			copy() {
+			
+				uni.setClipboardData({
+					data: this.expressData.expressNo,
+					success: function(res) {
+						uni.getClipboardData({
+							success: function(res) {
+								uni.showToast({
+									title: '复制成功'
+								});
+							}
+						});
+					}
+				});
+			},
+		}
+	}
+</script>
+
+<style>
+	
+.copy {
+	margin-left: 30rpx;
+	padding: 10rpx 40rpx;
+	background-color: #f1f1f1;
+	border-radius: 40rpx;
+	font-size: 24rpx;
+}
+	
+.item {
+    align-items: baseline;
+}
+
+.item:not(:last-child)::before {
+	content: ' ';
+	border-left: 1px solid #d3d3d3;
+	position: absolute;
+	bottom: -14rpx;
+	top: 14rpx;
+	left: 10rpx;
+	border-left-width: 1px;
+	border-left-style: solid;
+	border-left-color: rgb(211, 211, 211);
+}
+.item.active::before{
+    border-left: 1px solid #ff6a6c;
+}
+
+.circle {
+	width: 20rpx;
+	height: 20rpx;
+	position: absolute;
+	background: #d3d3d3;
+	border-radius: 50%;
+	top: 14rpx;
+}
+
+.circle.active {
+	background: #ff6a6c !important;
+	transform: scale(1.1);
+}
+
+.circle.active::after {
+	content: ' ';
+	background: rgba(255, 106, 108, 0.5) !important;
+	-webkit-transform: scale(1.6);
+	transform: scale(1.6);
+	width: 20rpx;
+	height: 20rpx;
+	position: absolute;
+	border-radius: 50%;
+}
+</style>

+ 296 - 0
packageShang/pages/user/order/order-refund.vue

@@ -0,0 +1,296 @@
+<template>
+	<view>
+		<!-- 商品区 -->
+		<view class="padding margin-lr margin-tb-sm bg-main border-radius">
+			<view class="goods-area" v-for="(item, index) in order_detail" :key="index">
+				<view class="dflex">
+					<view class="img">
+						<image :src="item.goodsMasterImg"></image>
+					</view>
+					<view class="margin-left-sm">
+						<text class="clamp-2">{{ item.goodsName }}</text>
+						<view class="ft-dark fs-xs padding-top-xs">
+							<text class="margin-right">× {{ item.goodsCount }}</text>
+							{{ item.goods_sku_name || '&nbsp;&nbsp;' }}
+						</view>
+						<view class="margin-top-sm">
+							<text class="price">{{ item.goodsTotalAmt }}</text>
+						</view>
+					</view>
+				</view>
+			</view>
+		</view>
+
+		<!-- 退款区 -->
+		<view class="padding-lr-xs padding-bottom-sm">
+			<use-list-title title="货物状态" type="round" color="#333" :tip="goods_state" iconfont=" "
+				@goto="openActionSheet(1)"></use-list-title>
+		</view>
+		<view class="padding-lr-xs">
+			<use-list-title title="退款原因" type="round" color="#333" :tip="reason" iconfont=" "
+				@goto="openActionSheet(2)"></use-list-title>
+		</view>
+
+		<view class="refund-area padding margin-lr margin-tb-sm bg-main border-radius">
+			<view class="dflex-b">
+				<text class="item margin-right-sm">退款金额:</text>
+				<text class="price">{{refund_money}}</text>
+			</view>
+		</view>
+
+		<!-- 上传凭证 -->
+		<view class="padding margin-lr margin-tb-sm bg-main border-radius">
+			<!-- 退款说明 -->
+			<textarea v-model="desc" class="ft-black w-full margin-0 padding-0 fs-sm"
+				placeholder="请填写退款说明(选填)"></textarea>
+
+			<!-- 上传图片 -->
+			<!-- <use-upload class="pos-r" @upload="refundImgs"></use-upload> -->
+		</view>
+
+		<!-- 提交操作 -->
+		<view class="padding w-full margin-top">
+			<view class="dflex-b">
+				<view class="tac padding-tb-sm flex1 bg-base-tuikuan" @click="submit">提交申请</view>
+			</view>
+		</view>
+
+
+		<!-- 操作菜单 -->
+		<use-action-sheet v-model="actionSheetShow" :list="actionSheetList" :tips="actionSheetTips"
+			@click="actionSheetClick" @close="actionSheetClose"></use-action-sheet>
+	</view>
+</template>
+
+<script>
+	import {
+		orderinfo,
+		refund
+	} from '../../../utils/api_order.js'
+	import useListTitle from '../../../components/use-list-title/use-list-title.vue'
+	import useUpload from '../../../components/use-upload/use-upload.vue'
+	import useActionSheet from '../../../components/use-action-sheet/use-action-sheet.vue'
+	export default {
+		components:{
+			useListTitle,
+			useUpload,
+			useActionSheet
+		},
+		data() {
+			return {
+				issubmit: false,
+				goods_state: '请选择',
+				reason: '请选择',
+				desc: '', // 退款说明
+				// 订单ID
+				order_id: '',
+				// 退款金额
+				refund_money: 0,
+
+				// 商品数据
+				order_detail: [],
+				// 订单数据
+				order_data: {},
+
+				postData: {
+					order_id: '',
+					goods_state: '',
+					reason: '',
+					desc: '',
+					imgs: [],
+					refund_money: 0,
+				},
+
+				actionSheetShow: false,
+				actionSheetList: [],
+				actionSheetTips: {
+					// text: "退出登录 | 切换账号",
+					// color: "#9a9a9a",
+					// size: 24
+				},
+			};
+		},
+		onUnload() {
+			uni.$emit('__event_order', 'refresh');
+		},
+		onLoad(option) {
+			this.order_id = option.order_id;
+			if (!this.order_id) {
+				this.$api.msg('订单编号不存在');
+			}
+			this.loadData();
+		},
+		methods: {
+			loadData() {
+				let _this = this;
+				// 订单详情
+				var data=_this.order_id
+				orderinfo(data).then((res) => {
+					if(res.success){
+						_this.order_data = res.data;
+						_this.order_detail = res.data.orderDetails;
+						
+						// 退款金额为实付款金额
+						_this.refund_money = _this.order_data.orderActualPrice;
+						return
+					}
+					_this.$api.msg(res.msg);
+				})
+			},
+			refundImgs(options) {
+				let imgs = [];
+
+				options.forEach((_) => {
+					imgs.push(_.url);
+				});
+
+				if (imgs.length > 0) this.postData.imgs = imgs;
+
+				console.log('refundImgs', this.postData.imgs);
+			},
+			submit() {
+				if (!this.postData.goods_state) {
+					this.$api.msg('请选择货物状态');
+					return;
+				}
+				
+				if (!this.postData.reason) {
+					this.$api.msg('请选择退款原因');
+					return;
+				}
+
+				if (this.issubmit) return;
+
+				this.issubmit = true;
+
+				this.postData.order_id = this.order_id;
+				this.postData.refund_money = this.refund_money;
+				this.postData.desc = this.desc;
+
+				let _this = this;
+				uni.showModal({
+					title: '提示',
+					content: '申请退款',
+					success: function(res) {
+						if (res.confirm) {
+
+							//只有代发货状态才能申请退款
+							var data=_this.postData.order_id
+							refund(data).then((res) => {
+								if(res.success){
+									_this.$api.msg('提交成功');
+									_this.issubmit = false;
+									uni.navigateBack({});
+									return
+								}
+								_this.$api.msg(res.msg);
+								_this.issubmit = false;
+							})
+						} else if (res.cancel) {
+							console.log('用户点击取消');
+						}
+					}
+				});
+
+			},
+			// 打开操作菜单
+			openActionSheet(idx) {
+				console.log('ppp',idx)
+
+				let type = '';
+				let actionSheetList = [];
+
+				switch (idx) {
+					case 1:
+						type = "货物状态";
+						this.actionSheetTips.text = "请选择" + type;
+						actionSheetList = [
+						// 	{
+						// 	text: "已收到货",
+						// 	color: "#333",
+						// 	type: type
+						// }, 
+						{
+							text: "未收到货",
+							color: "#333",
+							type: type
+						}, ];
+
+						break;
+					case 2:
+						type = "退款原因";
+						this.actionSheetTips.text = "请选择" + type;
+						actionSheetList = [{
+							text: "未发货不要了",
+							color: "#333",
+							type: type
+						}, {
+							text: "拍错了,重新下单",
+							color: "#333",
+							type: type
+						}, {
+							text: "换一家,质量不好",
+							color: "#333",
+							type: type
+						}, {
+							text: "其他",
+							color: "#333",
+							type: type
+						}, ];
+
+						break;
+				}
+
+				this.actionSheetShow = true;
+				this.actionSheetList = actionSheetList;
+			},
+			// 关闭操作菜单
+			actionSheetClose() {
+				this.actionSheetShow = false;
+				console.log(this.actionSheetShow);
+			},
+			// 点击操作菜单
+			actionSheetClick(index) {
+				let item = this.actionSheetList[index];
+
+				switch (item.type) {
+					case '货物状态':
+						this.goods_state = item.text;
+						this.postData.goods_state = item.text;
+						break;
+					case '退款原因':
+						this.reason = item.text;
+						this.postData.reason = item.text;
+						break;
+				}
+			},
+		}
+	};
+</script>
+
+<style lang="scss">
+	page {
+		background: $page-color-base;
+	}
+
+	/* 商品区 */
+	.goods-area {
+		&:last-child {
+			margin-bottom: 0;
+		}
+
+		image {
+			width: 180rpx;
+			height: 180rpx;
+		}
+	}
+
+	/* 退款区 */
+	.refund-area {
+		line-height: 60rpx;
+
+		.desc {
+			line-height: 60rpx;
+		}
+	}
+</style>

+ 658 - 0
packageShang/pages/user/order/order.vue

@@ -0,0 +1,658 @@
+<template>
+	<view class="container">
+		<!-- #ifdef MP-ALIPAY -->
+		<!-- 订单状态区 -->
+		<view class="pos-f w-full state-area dflex navbar-area bg-main">
+			<view class="nav-item dflex-c pos-r fs h-full" :class="{ active: tabCurrentIndex === index }"
+				v-for="(item, index) in navList" :key="index" @click="tabClick(index)">
+				{{ item.state }}
+			</view>
+		</view>
+
+		<!-- 订单轮播区 -->
+		<view class="order-area w-full" style="margin-top: 7vh;">
+			<!-- 空白页 -->
+			<use-empty v-if="navData.orderList.length === 0 && navData.loaded" e-style="round" e-type="cart"
+				tip="订单数据为空" height="93vh"></use-empty>
+			<!-- 订单列表区 -->
+			<view class="padding-lr margin-bottom-sm" :class="index === 0 ? 'padding-top-sm' : ''"
+				v-for="(item, index) in navData.orderList" :key="index">
+				<!-- 订单项 -->
+				<view class="order-item padding bg-main border-radius">
+					<view @click="todetail(item)">
+						<!-- 订单商品明细 -->
+						<view class="goods-area" :class="{ 'margin-top': goodsIndex > 0 }"
+							v-for="(goodsItem, goodsIndex) in item.orderDetails" :key="goodsIndex">
+							<image :src="goodsItem.goodsMasterImg" mode="aspectFill"></image>
+							<view class="right flex1">
+								<text class="clamp-2">{{ goodsItem.goodsName }} {{ goodsItem.goods_name_pw }}</text>
+								<view class="ft-dark fs-xs padding-top-xs">
+									<text class="margin-right">× {{goodsItem.goodsCount}}</text>
+									<!-- {{ goodsItem.goods_sku_name || '&nbsp;&nbsp;' }} -->
+								</view>
+								<view class="margin-top-sm">
+									<text class="price ft-main fs-sm">{{ goodsItem.goodsActualPrice }}</text>
+								</view>
+							</view>
+							<!-- 实付款 -->
+							<view class="dflex-e" style="padding-top: 70px;">
+								<text class="fs-xs margin-right-xs">实付款</text>
+								<text class="price ft-main">{{ item.totalPrice }}</text>
+							</view>
+						</view>
+
+						
+					</view>
+
+					<!-- 订单操作区 -->
+					<view class="dflex-b margin-top-sm">
+						<view>
+							<!-- 当前状态 -->
+							<!-- <text class="ft-dark" v-if="item.order.order_refund_state == '处理中'">退款处理中</text>
+							<text class="ft-dark"
+								v-else-if="item.order.order_refund_state">{{ item.order.order_refund_state }}</text>
+							<text class="ft-dark" v-else-if="item.order.state == '待评价'">已发货</text>
+							<text class="ft-dark" v-else>{{ item.order.state }}</text> -->
+						</view>
+
+						<view class="dflex-e">
+							<view class="dflex" v-if="item.state == '1'">
+								<button class="action-btn border-radius-big bg-main"
+									@click="cancelOrder(item)">取消订单</button>
+
+								<button v-if="item.state == '1'"
+									class="action-btn border-radius-big bg-main main-btn"
+									@click="payment(item)">立即支付</button>
+								<!-- <button v-if="item.order.state == '待核实'"
+									class="action-btn border-radius-big bg-main main-btn"
+									@click="payment(item.order)">待核实</button> -->
+							</view>
+							<view class="dflex" v-if="item.state == '2'">
+								<button
+									class="action-btn border-radius-big bg-main" @click="torefund(item)">申请退款</button>
+								<!-- <button v-if="!item.order.order_refund_state"
+									class="action-btn border-radius-big bg-main" @click="torefund(item)">申请退款</button> -->
+							</view>
+							<button v-if="['3',].includes(item.state)" 
+								class="action-btn border-radius-big bg-main" @click="toexpress(item)">查看物流</button>
+							<!-- <button v-if="!item.order.order_refund_state && 
+										['待收货', '待评价', '已完成'].includes(item.order.state)" 
+								class="action-btn border-radius-big bg-main" @click="toexpress(item)">查看物流</button> -->
+							<view class="dflex" v-if="item.state == '3'">
+								<button
+									class="action-btn border-radius-big bg-main main-btn"
+									@click="toreceipt(item)">确认收货</button>
+								<!-- <button v-if="!item.order.order_refund_state"
+									class="action-btn border-radius-big bg-main main-btn"
+									@click="toreceipt(item)">确认收货</button> -->
+							</view>
+							<view class="dflex" v-if="item.state == '4'">
+								<button class="action-btn border-radius-big bg-main main-btn"
+									@click="toevaluate(item)">我要评价</button>
+							</view>
+							<view class="dflex" v-if="['6', '5'].includes(item.state)">
+								<button class="action-btn border-radius-big bg-main main-btn"
+									@click="delOrder(item)">删除订单</button>
+							</view>
+						</view>
+					</view>
+				</view>
+			</view>
+
+			<!-- 上拉加载更多 -->
+			<use-loadmore v-if="navData.orderList.length > 0 && navData.loaded && navData.hasmore"
+				:type="navData.loadingType"></use-loadmore>
+
+		</view>
+		<!-- #endif -->
+
+		<!-- #ifndef MP-ALIPAY -->
+		<!-- 订单状态区 -->
+		<!-- <scroll-view scroll-x="true" class="navbar-area bg-main" :scroll-left="scrollLeft"> -->
+		<view class="state-area dflex navbar-area bg-main">
+			<view class="nav-item dflex-c pos-r fs h-full" :class="{ active: tabCurrentIndex === index }"
+				v-for="(item, index) in navList" :key="index" @click="tabClick(index)">
+				{{ item.state }}
+			</view>
+		</view>
+		<!-- </scroll-view> -->
+
+		<!-- 订单轮播区 -->
+		<swiper class="swiper-area w-full" :duration="0" :current="tabCurrentIndex" @change="changeTab">
+			<!-- 轮播项对应订单状态 -->
+			<swiper-item class="tab-content wh-full" v-for="(tabItem, tabIndex) in navList" :key="tabIndex">
+				<!-- 滚动区 -->
+				<scroll-view class="h-full" scroll-y @scrolltolower="loadData">
+					<!-- 空白页 -->
+					<use-empty v-if="tabItem.orderList.length === 0 && tabItem.loaded" e-style="round" e-type="cart"
+						tip="订单数据为空" height="93vh"></use-empty>
+					<!-- 订单列表区 -->
+					<view class="padding-lr margin-bottom-sm" :class="index === 0 ? 'padding-top-sm' : ''"
+						v-for="(item, index) in tabItem.orderList" :key="index">
+						<!-- 订单项 -->
+						<view class="order-item padding bg-main border-radius">
+							<view @click="todetail(item)">
+								<!-- 订单商品明细 -->
+								<view class="goods-area" :class="{ 'margin-top': goodsIndex > 0 }"
+									v-for="(goodsItem, goodsIndex) in item.orderDetails" :key="goodsIndex">
+									<image :src="goodsItem.goodsMasterImg" mode="aspectFill"></image>
+									<view class="right flex1">
+										<text class="clamp-2">{{ goodsItem.goodsName }}
+											</text>
+										<view class="ft-dark fs-xs padding-top-xs">
+											<text class="margin-right">× {{goodsItem.goodsCount}}</text>
+											<!-- {{ goodsItem.goods_sku_name || '&nbsp;&nbsp;' }} -->
+										</view>
+										<view class="margin-top-sm">
+											<text class="price ft-main fs-sm">{{ goodsItem.goodsActualPrice}}</text>
+										</view>
+									</view>
+									<!-- 实付款 -->
+									<view class="dflex-e" style="padding-top: 70px;">
+										<text class="fs-xs margin-right-xs">实付款</text>
+										<text class="price ft-main">{{ item.totalPrice}}</text>
+									</view>
+								</view>
+
+								
+							</view>
+
+							<!-- 订单操作区 -->
+							<view class="dflex-b margin-top-sm">
+								<view>
+									<!-- 当前状态 -->
+									<!-- <text class="ft-dark" v-if="item.order.order_refund_state == '处理中'">退款处理中</text>
+									<text class="ft-dark"
+										v-else-if="item.order.order_refund_state">{{ item.order.order_refund_state }}</text>
+									<text class="ft-dark" v-else-if="item.order.state == '待评价'">已发货</text>
+									<text class="ft-dark" v-else>{{ item.order.state }}</text> -->
+								</view>
+
+								<view class="dflex-e">
+									<view class="dflex" v-if="item.state == '1'">
+										<button class="action-btn border-radius-big bg-main"
+											@click="cancelOrder(item)">取消订单</button>
+								
+										<button v-if="item.state == '1'"
+											class="action-btn border-radius-big bg-main main-btn"
+											@click="payment(item)">立即支付</button>
+										<!-- <button v-if="item.order.state == '待核实'"
+											class="action-btn border-radius-big bg-main main-btn"
+											@click="payment(item.order)">待核实</button> -->
+									</view>
+									<view class="dflex" v-if="item.state == '2'">
+										<button
+											class="action-btn border-radius-big bg-main" @click="torefund(item)">申请退款</button>
+										<!-- <button v-if="!item.order.order_refund_state"
+											class="action-btn border-radius-big bg-main" @click="torefund(item)">申请退款</button> -->
+									</view>
+									<button v-if="['3',].includes(item.state)" 
+										class="action-btn border-radius-big bg-main" @click="toexpress(item)">查看物流</button>
+									<!-- <button v-if="!item.order.order_refund_state && 
+												['待收货', '待评价', '已完成'].includes(item.order.state)" 
+										class="action-btn border-radius-big bg-main" @click="toexpress(item)">查看物流</button> -->
+									<view class="dflex" v-if="item.state == '3'">
+										<button
+											class="action-btn border-radius-big bg-main main-btn"
+											@click="toreceipt(item)">确认收货</button>
+										<!-- <button v-if="!item.order.order_refund_state"
+											class="action-btn border-radius-big bg-main main-btn"
+											@click="toreceipt(item)">确认收货</button> -->
+									</view>
+									<view class="dflex" v-if="item.state == '4'">
+										<button class="action-btn border-radius-big bg-main main-btn"
+											@click="toevaluate(item)">我要评价</button>
+									</view>
+									<view class="dflex" v-if="['6', '5'].includes(item.state)">
+										<button class="action-btn border-radius-big bg-main main-btn"
+											@click="delOrder(item)">删除订单</button>
+									</view>
+								</view>
+							</view>
+						</view>
+					</view>
+
+					<!-- 上拉加载更多 -->
+					<use-loadmore v-if="tabItem.orderList.length > 0 && tabItem.loaded && tabItem.hasmore"
+						:type="tabItem.loadingType"></use-loadmore>
+				</scroll-view>
+			</swiper-item>
+		</swiper>
+		<!-- #endif -->
+
+	</view>
+</template>
+
+
+<script>
+	import {
+		orserlist,
+		orserdelete,
+		orserclear,
+		shouhuo
+	} from '../../../utils/api_order.js'
+	const _order = 'usemall-order'
+	import {
+		mapState
+	} from 'vuex';
+
+	export default {
+		data() {
+			return {
+				tabCurrentIndex: 0,
+				navData: {},
+				navList: [{
+						id: 0,
+						state: '全部',
+						orderState:7,
+						loadingType: 'more',
+						orderList: []
+					},
+					{
+						id: 1,
+						state: '待付款',
+						orderState:1,
+						loadingType: 'more',
+						orderList: []
+					},
+					{
+						id: 2,
+						state: '待发货',
+						orderState:2,
+						loadingType: 'more',
+						orderList: []
+					},
+					{
+						id: 3,
+						state: '待收货',
+						orderState:3,
+						loadingType: 'more',
+						orderList: []
+					},
+					{
+						id: 4,
+						state: '待评价',
+						orderState:4,
+						loadingType: 'more',
+						orderList: []
+					},
+					{
+						id: 5,
+						state: '已完成',
+						orderState:5,
+						loadingType: 'more',
+						orderList: []
+					},
+					{
+						id: 6,
+						state: '已取消',
+						orderState:6,
+						loadingType: 'more',
+						orderList: []
+					},
+				],
+				reqdata: {
+					page: 1,
+					rows: 10
+				},
+				scrollLeft: 0,
+				title: '全部'
+			};
+		},
+		watch: {
+			tabCurrentIndex(nv, ov) {
+				this.loadData('tab_change', 1);
+			}
+		},
+		onShow(options) {
+			let _this = this;
+			
+
+			let state = '';
+
+			uni.getStorage({
+				key: '__order_state',
+				success(res) {
+					state = res.data;
+
+					let cur_nav = _this.navList.find(x => x.state == state);
+					if (cur_nav) {
+						_this.tabCurrentIndex = cur_nav.id;
+					}
+
+					uni.removeStorage({
+						key: '__order_state'
+					});
+				},
+				complete() {
+					_this.loadData('refresh');
+				}
+			});
+		},
+
+		onLoad(options) {
+			uni.$on('__event_order', res => {
+				if (res == 'refresh') {
+					this.loadData('refresh');
+				}
+			});
+		},
+		// 下拉刷新
+		onPullDownRefresh() {
+			this.loadData('refresh');
+		},
+		// 上拉加载更多
+		onReachBottom() {
+			this.loadData();
+		},
+		methods: {
+			// 获取订单列表
+			loadData(source = 'add', loading) {
+				// 获取当前 nav
+				let cur_nav = this.navList[this.tabCurrentIndex];
+				this.title = cur_nav.state;
+				console.log(this.title);
+				console.log('loadData cur_nav', cur_nav);
+
+				if (cur_nav.loadingType === 'loading') {
+					//防止重复加载
+					return;
+				}
+
+				this.reqdata.state = cur_nav.state;
+				var orderState=cur_nav.orderState
+				if (loading == 1 || source == 'refresh') {
+					this.reqdata.page = 1;
+				}
+				if (source.type) {
+					source.type = source.type.toLowerCase();
+				}
+				if (source === 'add' || source.type == 'scrolltolower') {
+					if (cur_nav.loadingType == 'nomore') {
+						return;
+					}
+					cur_nav.loadingType = 'loading';
+				} else {
+					cur_nav.loadingType = 'more';
+				}
+				var _self=this
+				// 1等待付款、2等待发货、3等待收货、4等待评论、5完成、7全部
+				var data='?curPage='+_self.reqdata.page+'&pageSize='+_self.reqdata.rows+'&orderState='+orderState
+				orserlist(data).then((res) => {
+					if(res.success){
+						cur_nav.loaded = true;
+						
+						if (loading == 1 || source == 'refresh') {
+							cur_nav.orderList = [];
+							cur_nav.hasmore = 0;
+						}
+										
+						if (res.data.list.length > 0) {
+							let __datas = [];
+							res.data.list.forEach(row => {
+								__datas.push(row);
+							});
+										
+							cur_nav.orderList = [...cur_nav.orderList, ...__datas];
+										
+							if (res.data.list.length >= _self.reqdata.rows) {
+								if (_self.reqdata.page == 1) {
+									cur_nav.hasmore = !0;
+								}
+								_self.reqdata.page++;
+								cur_nav.loadingType = 'more';
+							} else {
+								cur_nav.loadingType = 'nomore';
+							}
+						} else {
+							cur_nav.loadingType = 'nomore';
+						}
+						
+						if (loading == 1) {
+							uni.hideLoading();
+						} else if (source == 'refresh') {
+							uni.stopPullDownRefresh();
+						}
+											
+						_self.navData = cur_nav;
+						return
+					}
+					_self.$api.msg(res.msg);
+				})
+			},
+
+			// swiper 切换
+			changeTab(e) {
+				this.tabCurrentIndex = e.target.current;
+
+			},
+			//顶部tab点击
+			tabClick(index) {
+				this.tabCurrentIndex = index;
+			},
+
+			// 点击跳转详情页面
+			todetail(order) {
+				uni.navigateTo({
+					url: `/packageShang/pages/user/order/order-detail?order_id=${order.orderId}`
+				});
+			},
+			// 立即支付
+			payment(item) {
+				if (item.order_pay_state == '待核实') {
+					this.$api.msg('订单已支付待核实状态');
+					return;
+				}
+
+				this.$api.topay({
+					order_id: item.orderId,
+					money: item.totalPrice
+				});
+			},
+			// 删除订单
+			delOrder(item) {
+				let _this = this;
+
+				uni.showModal({
+					title: '提示',
+					content: '删除订单',
+					success: function(res) {
+						if (res.confirm) {
+							uni.showLoading({
+								title: '请稍后'
+							});
+							var data=item.orderId
+							orserdelete(data).then((res) => {
+								if(res.success){
+									_this.loadData('tab_change', 1);
+									return
+								}
+								_this.$api.msg(res.msg);
+							})
+						} else if (res.cancel) {
+							console.log('点击取消');
+						}
+					},
+					complete() {
+						uni.hideLoading();
+					}
+				});
+			},
+			// 取消订单
+			cancelOrder(item) {
+				let _this = this;
+
+				uni.showModal({
+					title: '提示',
+					content: '取消订单',
+					success: function(res) {
+						if (res.confirm) {
+							uni.showLoading({
+								title: '请稍后'
+							});
+							var data=item.orderId
+							orserclear(data).then((res) => {
+								if(res.success){
+									_this.loadData('tab_change', 1);
+									return
+								}
+								_this.$api.msg(res.msg);
+							})
+						} else if (res.cancel) {
+							console.log('用户点击取消');
+						}
+					},
+					complete() {
+						uni.hideLoading();
+					}
+				});
+			},
+			// 查看物流
+			toexpress(item) {
+				// this.$api.msg('查看物流开发中');
+				uni.navigateTo({
+					url: `/packageShang/pages/user/order/order-express?order_id=${item.orderId}`
+				});
+			},
+			// 收货
+			toreceipt(item) {
+				let _this = this;
+
+				uni.showModal({
+					title: '提示',
+					content: '确认收货',
+					success: function(res) {
+						if (res.confirm) {
+							uni.showLoading({
+								title: '请稍后'
+							});
+							var data=item.orderId
+							shouhuo(data).then((res) => {
+								if(res.success){
+									_this.loadData('tab_change', 1);
+									return
+								}
+								_this.$api.msg(res.msg);
+							})
+						} else if (res.cancel) {
+							console.log('用户点击取消');
+						}
+					},
+					complete() {
+						uni.hideLoading();
+					}
+				});
+			},
+			// 申请退款
+			torefund(data) {
+				uni.navigateTo({
+					url: `/packageShang/pages/user/order/order-refund?order_id=${data.orderId}`
+				});
+			},
+			// 评价
+			toevaluate(item) {
+				uni.navigateTo({
+					url: `/packageShang/pages/user/order/order-evaluate?id=${item.orderId}`
+				});
+			}
+		}
+	};
+</script>
+
+<style lang="scss">
+	page,
+	.container {
+		min-height: 100%;
+		background: $page-color-base;
+	}
+
+	/* 订单状态区 */
+	.navbar-area {
+		white-space: nowrap;
+	}
+
+	.state-area {
+		height: 7vh;
+		box-shadow: 0 1px 5px rgba(0, 0, 0, 0.06);
+		z-index: 10;
+		top: 0;
+	}
+
+	.nav-item {
+		flex: 1;
+
+		&.active {
+			&:after {
+				content: '';
+				position: absolute;
+				left: 50%;
+				transform: translate(-50%);
+				bottom: 0;
+				width: 44px;
+				height: 0;
+				border-bottom: 2px solid $base-color;
+			}
+		}
+	}
+
+	/* 订单轮播区 */
+	.swiper-area {
+		height: 93vh;
+	}
+
+	/* 订单区 */
+	.order-area {}
+
+
+	/* 订单项 */
+	.order-item {
+
+		/* 订单商品明细区 */
+		.goods-area {
+			display: flex;
+
+			image {
+				width: 180rpx;
+				height: 180rpx;
+			}
+
+			.right {
+				padding: 0 30upx 0 24upx;
+				overflow: hidden;
+
+				.attr-box {
+					font-size: $font-sm + 2upx;
+					color: $font-color-light;
+					padding: 10upx 12upx;
+				}
+			}
+		}
+
+		/* 操作按钮 */
+		.action-btn {
+			width: 156rpx;
+			height: inherit;
+			line-height: inherit;
+			margin: 0;
+			margin-left: 20rpx;
+			padding: 12rpx 0;
+			font-size: $font-sm + 2upx;
+			/* #ifdef MP-QQ || MP-ALIPAY */
+			border: 1px solid;
+			/* #endif */
+
+			&:after {
+				border-radius: 100px;
+			}
+
+			&.main-btn {
+				background: #fff9f9;
+				color: $base-color;
+
+				&:after {
+					border-color: #f7bcc8;
+				}
+			}
+		}
+	}
+</style>

+ 20 - 0
packageShang/static/customicons.css

@@ -0,0 +1,20 @@
+@font-face {
+  font-family: "customicons"; /* Project id 2878519 */
+  src:url('/static/customicons.ttf') format('truetype');
+}
+
+.customicons {
+  font-family: "customicons" !important;
+}
+
+.youxi:before {
+  content: "\e60e";
+}
+
+.wenjian:before {
+  content: "\e60f";
+}
+
+.zhuanfa:before {
+  content: "\e610";
+}

BIN
packageShang/static/customicons.ttf


BIN
packageShang/static/images/empty/cart.jpg


BIN
packageShang/static/images/empty/empty.jpg


BIN
packageShang/static/images/empty/search.jpg


BIN
packageShang/static/images/home/banner.png


BIN
packageShang/static/images/home/baobao.png


BIN
packageShang/static/images/home/fushi.png


BIN
packageShang/static/images/home/huiyuan.png


BIN
packageShang/static/images/home/jiadian.png


BIN
packageShang/static/images/home/jiaju.png


BIN
packageShang/static/images/home/phone.png


BIN
packageShang/static/images/home/shucai.png


BIN
packageShang/static/images/home/xiezi.png


BIN
packageShang/static/images/home/xihu.png


BIN
packageShang/static/images/home/youhui.png


BIN
packageShang/static/images/logo.png


BIN
packageShang/static/images/right_icon.png


BIN
packageShang/static/images/tabbar/cart-active.png


BIN
packageShang/static/images/tabbar/cart.png


BIN
packageShang/static/images/tabbar/category-active.png


BIN
packageShang/static/images/tabbar/category.png


BIN
packageShang/static/images/tabbar/home-active.png


BIN
packageShang/static/images/tabbar/home.png


BIN
packageShang/static/images/tabbar/shopping-active.png


BIN
packageShang/static/images/tabbar/shopping.png


BIN
packageShang/static/images/tabbar/user-active.png


BIN
packageShang/static/images/tabbar/user.png


BIN
packageShang/static/images/user/VIP.png


BIN
packageShang/static/images/user/default.png


BIN
packageShang/static/images/user/user-bg.png


BIN
packageShang/static/images/客服.png


BIN
packageShang/static/logo.png


BIN
packageShang/static/uni.png


+ 11 - 0
packageShang/uni_modules/lime-painter/changelog.md

@@ -0,0 +1,11 @@
+## 1.6.2(2021-05-31)
+- fix: 修复 报`adaptor is not a function`错误
+- fix: 修复 text 多行高度
+- fix: 优化 默认文字的基准线
+- feat: `@progress`事件,监听绘制进度
+## 1.6.1(2021-02-28)
+- 删除多余节点
+## 1.6.0(2021-02-26)
+- 调整为uni_modules目录规范
+- 修复:transform的rotate不能为负数问题
+- 新增:`pathType` 指定生成图片返回的路径类型,可选值有 `base64`、`url`

+ 57 - 0
packageShang/uni_modules/lime-painter/components/lime-painter/canvas.js

@@ -0,0 +1,57 @@
+const _expand = ctx => {
+	return {
+		setFonts({fontFamily: ff = 'sans-serif', fontSize: fs = 14, fontWeight: fw = 'normal' , textStyle: ts = 'normal'}) {
+			// 设置属性
+			// #ifdef MP-TOUTIAO
+			fw = fw == 'bold' ? 'bold' : ''
+			ts =  ts == 'italic' ? 'italic' : ''
+			// #endif
+			ctx.font = `${ts} ${fw} ${fs}px ${ff}`;
+		},
+	}
+};
+export function expand(ctx) {
+	return Object.assign(ctx, _expand(ctx))
+}
+export function adaptor(ctx) {
+	return Object.assign(ctx, _expand(ctx), {
+		setStrokeStyle(val) {
+			ctx.strokeStyle = val;
+		},
+		setLineWidth(val) {
+			ctx.lineWidth = val;
+		},
+		setLineCap(val) {
+			ctx.lineCap = val;
+		},
+		setFillStyle(val) {
+			ctx.fillStyle = val;
+		},
+		setFontSize(val) {
+			ctx.font = String(val);
+		},
+		setGlobalAlpha(val) {
+			ctx.globalAlpha = val;
+		},
+		setLineJoin(val) {
+			ctx.lineJoin = val;
+		},
+		setTextAlign(val) {
+			ctx.textAlign = val;
+		},
+		setMiterLimit(val) {
+			ctx.miterLimit = val;
+		},
+		setShadow(offsetX, offsetY, blur, color) {
+			ctx.shadowOffsetX = offsetX;
+			ctx.shadowOffsetY = offsetY;
+			ctx.shadowBlur = blur;
+			ctx.shadowColor = color;
+		},
+		setTextBaseline(val) {
+			ctx.textBaseline = val;
+		},
+		createCircularGradient() {},
+		draw() {},
+	});
+}

+ 790 - 0
packageShang/uni_modules/lime-painter/components/lime-painter/draw.js

@@ -0,0 +1,790 @@
+import {
+	toPx,
+	isNumber,
+	getImageInfo
+} from './utils'
+import {
+	GD
+} from './gradient'
+import QR from './qrcode'
+
+
+export class Draw {
+	constructor(context, canvas, use2dCanvas = false, isH5PathToBase64 = false, sleep) {
+		this.ctx = context
+		this.canvas = canvas || null
+		this.use2dCanvas = use2dCanvas
+		this.isH5PathToBase64 = isH5PathToBase64
+		this.sleep = sleep
+		this.count = 0
+		this.progress = 0
+	}
+	roundRect(x, y, w, h, r, fill = false, stroke = false, ) {
+		if (r < 0) return
+		const {
+			ctx
+		} = this
+		ctx.beginPath()
+		if (!r) {
+			ctx.rect(x, y, w, h)
+		} else {
+			let {
+				borderTopLeftRadius: tl = r || 0,
+				borderTopRightRadius: tr = r || 0,
+				borderBottomRightRadius: br = r || 0,
+				borderBottomLeftRadius: bl = r || 0
+			} = r || {}
+			// 右下角
+			ctx.arc(x + w - br, y + h - br, br, 0, Math.PI * 0.5)
+			ctx.lineTo(x + bl, y + h)
+			// 左下角
+			ctx.arc(x + bl, y + h - bl, bl, Math.PI * 0.5, Math.PI)
+			ctx.lineTo(x, y + tl)
+			// 左上角
+			ctx.arc(x + tl, y + tl, tl, Math.PI, Math.PI * 1.5)
+			ctx.lineTo(x + w - tr, y)
+			// 右上角
+			ctx.arc(x + w - tr, y + tr, tr, Math.PI * 1.5, Math.PI * 2)
+			ctx.lineTo(x + w, y + h - br)
+		}
+		ctx.closePath()
+		if (stroke) ctx.stroke()
+		if (fill) ctx.fill()
+	}
+	setTransform(box, {
+		transform,
+		transformOrigin: o = 'center center'
+	}) {
+		const {
+			ctx
+		} = this
+		let {
+			scaleX = 1,
+				scaleY = 1,
+				translateX = 0,
+				translateY = 0,
+				rotate = 0,
+				skewX = 0,
+				skewY = 0
+		} = transform || {}
+		let {
+			left: x,
+			top: y,
+			width: w,
+			height: h
+		} = box
+		translateX = toPx(translateX, w) || 0
+		translateY = toPx(translateY, h) || 0
+
+		const yMaps = {
+			"top": toPx('0%', 1),
+			"center": toPx('50%', 1, true),
+			"bottom": toPx('100%', 1),
+		}
+		const xMaps = {
+			"left": toPx('0%', 1),
+			"center": toPx('50%', 1, true),
+			"right": toPx('100%', 1)
+		}
+		o = o.split(' ').filter((v, i) => i < 2).reduce((c, v) => {
+			if (/\d+/.test(v)) {
+				let n = toPx(v, 1, true) / (/px|rpx$/.test(v) ? (isNumber(c.x) ? h : w) : 1)
+				return isNumber(c.x) ? Object.assign(c, {
+					y: n
+				}) : Object.assign(c, {
+					x: n
+				})
+			} else {
+				return isNumber(xMaps[v]) && !isNumber(c.x) ? Object.assign(c, {
+					x: xMaps[v]
+				}) : Object.assign(c, {
+					y: yMaps[v] || 0.5
+				})
+			}
+		}, {})
+		ctx.scale(scaleX, scaleY)
+		const offset = {
+			x: w * (scaleX > 0 ? 1 : -1) * o.x + (x + translateX) / scaleX,
+			y: h * (scaleY > 0 ? 1 : -1) * o.y + (y + translateY) / scaleY
+		}
+		ctx.translate(offset.x, offset.y)
+		if (rotate) {
+			ctx.rotate(rotate * Math.PI / 180)
+		}
+		if (skewX || skewY) {
+			ctx.transform(1, Math.tan(skewY * Math.PI / 180), Math.tan(skewX * Math.PI / 180), 1, 0, 0)
+		}
+		return {
+			x: -w * o.x,
+			y: -h * o.y,
+			w,
+			h
+		}
+	}
+	setBackground(bg, w, h) {
+		const {
+			ctx
+		} = this
+		if (!bg) {
+			// #ifndef MP-TOUTIAO || MP-BAIDU
+			ctx.setFillStyle('transparent')
+			// #endif
+			// #ifdef MP-TOUTIAO || MP-BAIDU
+			ctx.setFillStyle('rgba(0,0,0,0)')
+			// #endif
+		} else if (GD.isGradient(bg)) {
+			GD.doGradient(bg, w, h, ctx);
+		} else {
+			ctx.setFillStyle(bg)
+		}
+	}
+	setShadow({
+		boxShadow: bs = []
+	}) {
+		const {
+			ctx
+		} = this
+		if (bs.length) {
+			const [x, y, b, c] = bs
+			ctx.setShadow(x, y, b, c)
+		}
+	}
+	setBorder(box, style) {
+		const {
+			ctx
+		} = this
+		let {
+			width: w,
+			height: h
+		} = box
+		const {
+			border,
+			borderBottom,
+			borderTop,
+			borderRight,
+			borderLeft,
+			borderRadius: r
+		} = style;
+		const {
+			borderWidth: bw = 0,
+			borderStyle: bs,
+			borderColor: bc,
+		} = border || {}
+		const {
+			borderBottomWidth: bbw = bw,
+			borderBottomStyle: bbs = bs,
+			borderBottomColor: bbc = bc,
+		} = borderBottom || {}
+		const {
+			borderTopWidth: btw = bw,
+			borderTopStyle: bts = bs,
+			borderTopColor: btc = bc,
+		} = borderTop || {}
+		const {
+			borderRightWidth: brw = bw,
+			borderRightStyle: brs = bs,
+			borderRightColor: brc = bc,
+		} = borderRight || {}
+		const {
+			borderLeftWidth: blw = bw,
+			borderLeftStyle: bls = bs,
+			borderLeftColor: blc = bc,
+		} = borderLeft || {}
+
+		let {
+			borderTopLeftRadius: tl = r || 0,
+			borderTopRightRadius: tr = r || 0,
+			borderBottomRightRadius: br = r || 0,
+			borderBottomLeftRadius: bl = r || 0
+		} = r || {
+			r,
+			r,
+			r,
+			r
+		}
+		if (!borderBottom && !borderLeft && !borderTop && !borderRight && !border) return;
+		const _borderType = (w, s, c) => {
+			if (s == 'dashed') {
+				// #ifdef MP
+				ctx.setLineDash([Math.ceil(w * 4 / 3), Math.ceil(w * 4 / 3)])
+				// #endif
+				// #ifndef MP
+				ctx.setLineDash([Math.ceil(w * 6), Math.ceil(w * 6)])
+				// #endif
+			} else if (s == 'dotted') {
+				ctx.setLineDash([w, w])
+			}
+			ctx.setStrokeStyle(c)
+		}
+		const _setBorder = (x1, y1, x2, y2, x3, y3, r1, r2, p1, p2, p3, bw, bs, bc) => {
+			ctx.save()
+			// this.setOpacity(style)
+			// this.setTransform(box, style)
+			ctx.setLineWidth(bw)
+			_borderType(bw, bs, bc)
+			ctx.beginPath()
+			ctx.arc(x1, y1, r1, Math.PI * p1, Math.PI * p2)
+			ctx.lineTo(x2, y2)
+			ctx.arc(x3, y3, r2, Math.PI * p2, Math.PI * p3)
+			ctx.stroke()
+			ctx.restore()
+		}
+		ctx.save()
+		this.setOpacity(style)
+		let {
+			x,
+			y
+		} = this.setTransform(box, style)
+		if (border) {
+			ctx.setLineWidth(bw)
+			_borderType(bw, bs, bc)
+			this.roundRect(x, y, w, h, r, false, bc ? true : false)
+			ctx.restore()
+		}
+
+		if (borderBottom) {
+			_setBorder(x + w - br, y + h - br, x + bl, y + h, x + bl, y + h - bl, br, bl, 0.25, 0.5, 0.75, bbw, bbs,
+				bbc)
+		}
+		if (borderLeft) {
+			// 左下角
+			_setBorder(x + bl, y + h - bl, x, y + tl, x + tl, y + tl, bl, tl, 0.75, 1, 1.25, blw, bls, blc)
+		}
+		if (borderTop) {
+			// 左上角
+			_setBorder(x + tl, y + tl, x + w - tr, y, x + w - tr, y + tr, tl, tr, 1.25, 1.5, 1.75, btw, bts, btc)
+		}
+		if (borderRight) {
+			// 右上角
+			_setBorder(x + w - tr, y + tr, x + w, y + h - br, x + w - br, y + h - br, tr, br, 1.75, 2, 0.25, btw,
+				bts, btc)
+		}
+	}
+	setOpacity({
+		opacity = 1
+	}) {
+		this.ctx.setGlobalAlpha(opacity)
+	}
+	drawView(box, style) {
+		const {
+			ctx
+		} = this
+		const {
+			width: w,
+			height: h
+		} = box
+		let {
+			borderRadius: br = 0,
+			backgroundColor: bg,
+		} = style || {}
+		ctx.save()
+		this.setOpacity(style)
+		let {
+			x,
+			y
+		} = this.setTransform(box, style)
+		this.setShadow(style)
+		this.setBackground(bg, w, h)
+		this.roundRect(x, y, w, h, br, true, false)
+		ctx.restore()
+		this.setBorder(box, style)
+	}
+	async drawImage(img, box = {}, style = {}, custom = true) {
+		await new Promise(async (resolve, reject) => {
+			const {
+				ctx,
+				sleep
+			} = this
+			const canvas = this.canvas
+			let {
+				borderRadius = 0,
+					mode,
+					padding = {},
+					backgroundColor: bg,
+			} = style
+			const {
+				paddingTop: pt = 0,
+				paddingLeft: pl = 0,
+				paddingRight: pr = 0,
+				paddingBottom: pb = 0
+			} = padding;
+			let {
+				left: x,
+				top: y,
+				width: w,
+				height: h
+			} = box
+			ctx.save()
+			if (!custom) {
+				this.setOpacity(style)
+				let {
+					x: x1,
+					y: y1
+				} = this.setTransform(box, style)
+				if (bg) {
+					this.setBackground(bg, w, h)
+				}
+				x = x1
+				y = y1
+				this.setShadow(style)
+				this.roundRect(x, y, w, h, borderRadius, borderRadius ? true : false, false)
+			}
+			ctx.clip()
+			const _modeImage = (img) => {
+				x += pl
+				y += pt
+				w = w - pl - pr
+				h = h - pt - pb
+				// 获得图片原始大小
+				let {
+					width: rw,
+					height: rh,
+					src
+				} = img
+				let sX = 0
+				let sY = 0
+				// 绘画区域比例
+				const cp = w / h
+				// 原图比例
+				const op = rw / rh
+				if (!rw) {
+					mode = 'scaleToFill'
+				}
+				switch (mode) {
+					case 'aspectFit':
+						if (cp >= op) {
+							rw = h * op;
+							rh = h
+							sX = x + Math.round(w - rw) / 2
+							sY = y
+						} else {
+							rw = w
+							rh = w / op;
+							sX = x
+							sY = y + Math.round(h - rh) / 2
+						}
+						ctx.drawImage(src, sX, sY, rw, rh);
+						break;
+					case 'aspectFill':
+						if (cp >= op) {
+							rh = rw / cp;
+							// sY = Math.round((h - rh) / 2)
+						} else {
+							rw = rh * cp;
+							sX = Math.round(((img.width || w) - rw) / 2)
+						}
+						// 百度小程序
+						// #ifdef MP-BAIDU
+						ctx.drawImage(src, x, y, w, h, sX, sY, rw, rh)
+						// #endif
+						// #ifndef MP-BAIDU
+						ctx.drawImage(src, sX, sY, rw, rh, x, y, w, h)
+						// #endif
+						break;
+					default:
+						// scaleToFill
+						ctx.drawImage(src, x, y, w, h);
+				}
+			}
+			const _restore = () => {
+				ctx.restore()
+				this.setBorder(box, style)
+				setTimeout(resolve, sleep)
+			}
+			const _drawImage = (img, isReset = false) => {
+				if (this.use2dCanvas) {
+					const Image = canvas.createImage()
+					Image.onload = () => {
+						img.src = Image
+						_modeImage(img)
+						_restore()
+					}
+					Image.onerror = async () => {
+						if (isReset) {
+							console.error(`createImage fail: ${JSON.stringify(img)}`)
+						}
+						resolve(true)
+					}
+					Image.src = img.src
+				} else {
+					_modeImage(img)
+					_restore()
+				}
+			}
+			if (typeof img === 'string') {
+				const {
+					path: src,
+					width,
+					height
+				} = await getImageInfo(img, this.isH5PathToBase64)
+				_drawImage({
+					src,
+					width,
+					height
+				})
+			} else {
+				try {
+					_drawImage(img)
+				} catch (e) {
+					const {
+						path: src,
+						width,
+						height
+					} = await getImageInfo(img.originSrc, this.isH5PathToBase64, true)
+					_drawImage({
+						src,
+						width,
+						height
+					}, true)
+				}
+
+			}
+		})
+	}
+	drawText(text, box, style, rules) {
+		const {
+			ctx
+		} = this
+		let {
+			width: w,
+			height: h,
+			offsetLeft: ol = 0,
+			offsetTop: ot = 0,
+		} = box
+		let {
+			color = '#000000',
+				lineHeight = '1.4em',
+				fontSize = 14,
+				fontWeight,
+				fontFamily,
+				textStyle,
+				textAlign = 'left',
+				verticalAlign: va = 'top',
+				backgroundColor: bg,
+				maxLines,
+				display,
+				padding = {},
+				borderRadius = 0,
+				textDecoration: td
+		} = style
+		lineHeight = toPx(lineHeight, fontSize)
+		if (!text) return
+		ctx.save()
+		this.setOpacity(style)
+		let {
+			x,
+			y
+		} = this.setTransform(box, style)
+		ctx.setTextBaseline('middle')
+		ctx.setFonts({
+			fontFamily,
+			fontSize,
+			fontWeight,
+			textStyle
+		})
+		ctx.setTextAlign(textAlign)
+		ctx.translate(0, -(fontSize / 2));
+		// 垂直布局
+		y += fontSize
+		if (bg) {
+			this.setBackground(bg, w, h)
+			this.roundRect(x, y - fontSize / 2, w, h, borderRadius, 1, 0)
+		}
+		y += ot
+		this.setShadow(style)
+		ctx.setFillStyle(color)
+		let rulesObj = {};
+		if (rules) {
+			if (rules.word.length > 0) {
+				for (let i = 0; i < rules.word.length; i++) {
+					// let reg = new RegExp(rules.word[i], "g")
+					// let result;
+					// while(result=reg.exec(text)) {
+					// 		rulesObj[result.index] = {
+					// 			reset: true,
+					// 			char: result[0]
+					// 		};
+					// }
+					let startIndex = 0,
+						index;
+					while ((index = text.indexOf(rules.word[i], startIndex)) > -1) {
+						rulesObj[index] = {
+							reset: true
+						};
+						for (let j = 0; j < rules.word[i].length; j++) {
+							rulesObj[index + j] = {
+								reset: true
+							};
+						}
+						startIndex = index + 1;
+					}
+				}
+			}
+		}
+		// 水平布局
+		switch (textAlign) {
+			case 'left':
+				break
+			case 'center':
+				x += 0.5 * w
+				break
+			case 'right':
+				x += w
+				break
+			default:
+				break
+		}
+		const textWidth = ctx.measureText(text).width
+		const actualHeight = Math.ceil(textWidth / w) * lineHeight
+		let paddingTop = Math.ceil((h - actualHeight) / 2)
+		if (paddingTop < 0) paddingTop = 0
+
+		// 绘线
+		const _drawLine = (x, y, textWidth) => {
+			let to = x
+			switch (textAlign) {
+				case 'left':
+					x = x
+					to += textWidth
+					break
+				case 'center':
+					x = x - textWidth / 2
+					to = x + textWidth
+					break
+				case 'right':
+					to = x
+					x = x - textWidth
+					break
+				default:
+					break
+			}
+
+			if (td) {
+				ctx.setLineWidth(fontSize / 13);
+				ctx.beginPath();
+
+				if (/\bunderline\b/.test(td)) {
+					ctx.moveTo(x, y - fontSize * 0.5);
+					ctx.lineTo(to, y - fontSize * 0.5);
+				}
+
+				if (/\boverline\b/.test(td)) {
+					ctx.moveTo(x, y - fontSize * 1.5);
+					ctx.lineTo(to, y - fontSize * 1.5);
+				}
+				if (/\bline-through\b/.test(td)) {
+					ctx.moveTo(x, y - fontSize);
+					ctx.lineTo(to, y - fontSize);
+				}
+				ctx.closePath();
+				ctx.setStrokeStyle(color);
+				ctx.stroke();
+			}
+		}
+		const _reset = (text, x, y) => {
+			const rs = Object.keys(rulesObj)
+			for (let i = 0; i < rs.length; i++) {
+				const item = rulesObj[rs[i]]
+				ctx.save();
+				ctx.setFillStyle(rules.color);
+				if (item.char) {
+					ctx.fillText(item.char, item.x, item.y)
+				}
+				ctx.restore();
+			}
+		}
+		const _setText = (isReset, char) => {
+			if (isReset) {
+				const t1 = Math.round(ctx.measureText('\u0020').width)
+				const t2 = Math.round(ctx.measureText('\u3000').width)
+				const width = Math.round(ctx.measureText(char).width)
+				let _char = ''
+				let _num = 1
+				if (width == t2) {
+					_char = '\u3000'
+					_num = 1
+				} else {
+					_char = '\u0020'
+					_num = Math.ceil(width / t1)
+				}
+				return {
+					char: new Array(_num).fill(_char).join(''),
+					width
+				}
+			} else {
+				return {
+					char
+				}
+			}
+		}
+		const _setRulesObj = (text, index, x, y) => {
+			rulesObj[index].x = x
+			rulesObj[index].y = y
+			rulesObj[index].char = text
+		}
+		const _setRules = (x, rs, text, textWidth, {
+			startIndex = 0,
+			endIndex
+		}) => {
+			let clonetext = text
+			if (/·/.test(text)) {
+				clonetext = clonetext.replace(/·/g, '.')
+				textWidth = ctx.measureText(clonetext).width
+			}
+			let _text = text.split('')
+			let _x = x
+			for (let i = 0; i < rs.length; i++) {
+				const index = rs[i]
+				const key = index - startIndex
+				const t = _text[key]
+				if (t) {
+					let {
+						char,
+						width
+					} = _setText(rulesObj[index], t)
+					_text[key] = char
+					if (textAlign == 'center') {
+						_x = x - 0.5 * (textWidth - width)
+					}
+					if (textAlign == 'right') {
+						_x = x - textWidth + width
+					}
+					_setRulesObj(t, index, _x + ctx.measureText(clonetext.substring(0, key)).width, y +
+						inlinePaddingTop)
+				} else {
+					continue
+				}
+
+			}
+			return _text
+		}
+		const inlinePaddingTop = Math.ceil((lineHeight - fontSize) / 2) + 1
+		// 不超过一行
+		if (textWidth + ol <= w && !text.includes('\n')) {
+			x = x + ol
+			const rs = Object.keys(rulesObj)
+			let _text = ''
+			if (rs) {
+				_text = _setRules(x, rs, text, textWidth, {})
+				_reset()
+			}
+			ctx.fillText(_text.join(''), x, y + inlinePaddingTop)
+			y += lineHeight
+			_drawLine(x, y, textWidth)
+			ctx.restore()
+			this.setBorder(box, style)
+			return
+		}
+		// 多行文本
+		const chars = text.split('')
+		const _y = y
+		let _x = x
+		// 逐行绘制
+		let line = ''
+		let lineIndex = 0
+		let startIndex = 0
+		for (let index = 0; index <= chars.length; index++) {
+			let ch = chars[index] || ''
+			const isLine = ch === '\n'
+			const isRight = ch == '' // index == chars.length
+			ch = isLine ? '' : ch;
+			let textline = line + ch
+			let textWidth = ctx.measureText(textline).width
+			// 绘制行数大于最大行数,则直接跳出循环
+			if (lineIndex >= maxLines) {
+				break;
+			}
+			if (lineIndex == 0) {
+				textWidth = textWidth + ol
+				_x = x + ol
+			} else {
+				textWidth = textWidth
+				_x = x
+			}
+
+			if (textWidth > w || isLine || isRight) {
+				let endIndex = index
+				lineIndex++
+				line = isRight && textWidth <= w ? textline : line
+				if (lineIndex === maxLines && textWidth > w) {
+					while (ctx.measureText(`${line}...`).width > w) {
+						if (line.length <= 1) {
+							// 如果只有一个字符时,直接跳出循环
+							break;
+						}
+						line = line.substring(0, line.length - 1);
+					}
+					line += '...'
+				}
+				const rs = Object.keys(rulesObj)
+				let _text = ''
+				if (rs) {
+					_text = _setRules(x, rs, line, textWidth, {
+						startIndex,
+						endIndex
+					})
+					_reset()
+				}
+				ctx.fillText(_text.join(''), _x, y + inlinePaddingTop)
+				y += lineHeight
+				_drawLine(_x, y, textWidth)
+				line = ch
+				startIndex = endIndex + (isLine ? 1 : 0)
+				if ((y + lineHeight) > (_y + h)) break
+			} else {
+				line = textline
+			}
+		}
+		ctx.restore()
+		this.setBorder(box, style)
+	}
+	async drawNode(element) {
+		const {
+			layoutBox: box,
+			computedStyle: style,
+			attributes: attr,
+			name,
+			rules,
+			children,
+			parent
+		} = element
+		if (!parent.hasOwnProperty('id')) {
+			this.count = Math.max(element.count || 1, this.count)
+		}
+		const {
+			src,
+			text
+		} = element.attributes
+		if (name === 'view') {
+			this.drawView(box, style)
+		} else if (name === 'image' && src) {
+			await this.drawImage(attr, box, style, false)
+		} else if (name === 'text') {
+			this.drawText(text, box, style, rules)
+		} else if (name === 'qrcode') {
+			if (QR?.api) {
+				QR.api.draw(text, this, box, style)
+			}
+		}
+		this.progress = (this.progress || 0) + 1
+		if (!children) return
+		const childs = Object.values ? Object.values(children) : Object.keys(children).map((key) => children[key]);
+		for (const child of childs) {
+			await this.drawNode(child)
+		}
+	}
+	listen(type, callBack) {
+		if(type == 'progressChange') {
+			Object.defineProperty(this, 'progress', {
+				configurable: true,
+				set: (v) => {
+					this._progress = v;
+					callBack(v / this.count);
+				},
+				get: () => {
+					return this._progress;
+				}
+			})
+		}
+	}
+}

+ 107 - 0
packageShang/uni_modules/lime-painter/components/lime-painter/gradient.js

@@ -0,0 +1,107 @@
+/* eslint-disable */
+
+export const GD = {
+	isGradient(bg) {
+		if (bg && (bg.startsWith('linear') || bg.startsWith('radial'))) {
+			return true;
+		}
+		return false;
+	},
+	doGradient(bg, width, height, ctx) {
+		if (bg.startsWith('linear')) {
+			linearEffect(width, height, bg, ctx);
+		} else if (bg.startsWith('radial')) {
+			radialEffect(width, height, bg, ctx);
+		}
+	},
+}
+
+function analizeGrad(string) {
+	const colorPercents = string.substring(0, string.length - 1).split("%,");
+	const colors = [];
+	const percents = [];
+	for (let colorPercent of colorPercents) {
+		colors.push(colorPercent.substring(0, colorPercent.lastIndexOf(" ")).trim());
+		percents.push(colorPercent.substring(colorPercent.lastIndexOf(" "), colorPercent.length) / 100);
+	}
+	return {
+		colors: colors,
+		percents: percents
+	};
+}
+
+function radialEffect(width, height, bg, ctx) {
+	const colorPer = analizeGrad(bg.match(/radial-gradient\((.+)\)/)[1]);
+	const grd = ctx.createCircularGradient(0, 0, width < height ? height / 2 : width / 2);
+	for (let i = 0; i < colorPer.colors.length; i++) {
+		grd.addColorStop(colorPer.percents[i], colorPer.colors[i]);
+	}
+	ctx.setFillStyle(grd);
+}
+
+function analizeLinear(bg, width, height) {
+	const direction = bg.match(/([-]?\d{1,3})deg/);
+	const dir = direction && direction[1] ? parseFloat(direction[1]) : 0;
+	let coordinate;
+	switch (dir) {
+		case 0:
+			coordinate = [0, -height / 2, 0, height / 2];
+			break;
+		case 90:
+			coordinate = [width / 2, 0, -width / 2, 0];
+			break;
+		case -90:
+			coordinate = [-width / 2, 0, width / 2, 0];
+			break;
+		case 180:
+			coordinate = [0, height / 2, 0, -height / 2];
+			break;
+		case -180:
+			coordinate = [0, -height / 2, 0, height / 2];
+			break;
+		default:
+			let x1 = 0;
+			let y1 = 0;
+			let x2 = 0;
+			let y2 = 0;
+			if (direction[1] > 0 && direction[1] < 90) {
+				x1 = (width / 2) - ((width / 2) * Math.tan((90 - direction[1]) * Math.PI * 2 / 360) - height / 2) * Math.sin(2 * (
+					90 - direction[1]) * Math.PI * 2 / 360) / 2;
+				y2 = Math.tan((90 - direction[1]) * Math.PI * 2 / 360) * x1;
+				x2 = -x1;
+				y1 = -y2;
+			} else if (direction[1] > -180 && direction[1] < -90) {
+				x1 = -(width / 2) + ((width / 2) * Math.tan((90 - direction[1]) * Math.PI * 2 / 360) - height / 2) * Math.sin(2 * (
+					90 - direction[1]) * Math.PI * 2 / 360) / 2;
+				y2 = Math.tan((90 - direction[1]) * Math.PI * 2 / 360) * x1;
+				x2 = -x1;
+				y1 = -y2;
+			} else if (direction[1] > 90 && direction[1] < 180) {
+				x1 = (width / 2) + (-(width / 2) * Math.tan((90 - direction[1]) * Math.PI * 2 / 360) - height / 2) * Math.sin(2 * (
+					90 - direction[1]) * Math.PI * 2 / 360) / 2;
+				y2 = Math.tan((90 - direction[1]) * Math.PI * 2 / 360) * x1;
+				x2 = -x1;
+				y1 = -y2;
+			} else {
+				x1 = -(width / 2) - (-(width / 2) * Math.tan((90 - direction[1]) * Math.PI * 2 / 360) - height / 2) * Math.sin(2 *
+					(90 - direction[1]) * Math.PI * 2 / 360) / 2;
+				y2 = Math.tan((90 - direction[1]) * Math.PI * 2 / 360) * x1;
+				x2 = -x1;
+				y1 = -y2;
+			}
+			coordinate = [x1, y1, x2, y2];
+			break;
+	}
+	return coordinate;
+}
+
+function linearEffect(width, height, bg, ctx) {
+	const param = analizeLinear(bg, width, height);
+	const grd = ctx.createLinearGradient(param[0], param[1], param[2], param[3]);
+	const content = bg.match(/linear-gradient\((.+)\)/)[1];
+	const colorPer = analizeGrad(content.substring(content.indexOf(',') + 1));
+	for (let i = 0; i < colorPer.colors.length; i++) {
+		grd.addColorStop(colorPer.percents[i], colorPer.colors[i]);
+	}
+	ctx.setFillStyle(grd);
+}

+ 326 - 0
packageShang/uni_modules/lime-painter/components/lime-painter/index.vue

@@ -0,0 +1,326 @@
+<template>
+	<view v-if="canvasId">
+		<canvas v-if="use2dCanvas" :id="canvasId" type="2d" :style="size + ';' + customStyle"></canvas>
+		<canvas v-else :canvas-id="canvasId" :style="size + ';' + customStyle" :id="canvasId" :width="boardWidth * dpr" :height="boardHeight * dpr"></canvas>
+	</view>
+</template>
+
+
+<script>
+import { toPx, compareVersion, sleep, isNumber, base64ToPath, pathToBase64 } from './utils';
+import { Draw } from './draw';
+import { Layout } from './layout';
+import { adaptor, expand } from './canvas';
+export default {
+	// version: '1.6.2',
+	name: 'l-painter',
+	props: {
+		board: Object,
+		pathType: {
+			type: String,
+			//'base64'、'url'
+		},
+		fileType: {
+			type: String,
+			default: 'png'
+		},
+		quality: {
+			type: Number,
+			default: 1
+		},
+		width: [Number, String],
+		height: [Number, String],
+		pixelRatio: Number,
+		customStyle: String,
+		isRenderImage: Boolean,
+		isH5PathToBase64: Boolean,
+		sleep: {
+			type: Number,
+			default: 1000 / 60
+		},
+		beforeDelay: {
+			type: Number,
+			default: 1000 / 60
+		},
+		afterDelay: Number,
+		// #ifdef MP-WEIXIN || MP-TOUTIAO
+		type: {
+			type: String,
+			default: '2d'
+		},
+		// #endif
+	},
+	data() {
+		return {
+			// #ifdef MP-WEIXIN || MP-TOUTIAO
+			use2dCanvas: true,
+			// #endif
+			// #ifndef MP-WEIXIN || MP-TOUTIAO
+			use2dCanvas: false,
+			// #endif
+			draw: null,
+			ctx: null,
+			canvasHeight: 0,
+			canvasWidth: 0,
+			layout: new Layout(),
+			isDrawIng: false
+		};
+	},
+	computed: {
+		canvasId() {
+			return `l-painter${this._uid}`
+		},
+		size() {
+			return `width:${this.boardWidth}px; height: ${this.boardHeight}px;`;
+		},
+		dpr() {
+			return this.pixelRatio || uni.getSystemInfoSync().pixelRatio;
+		},
+		boardWidth() {
+			const { width: bw } = this.board || {};
+			return toPx(this.width || bw);
+		},
+		boardHeight() {
+			const { height: bh } = this.board || {};
+			const { height: h, canvasHeight: ch } = this;
+			return /^auto$/.test(h || bh) ? ch || 1 : toPx(h || bh);
+		}
+	},
+	watch: {
+		size() {
+			// #ifdef MP-WEIXIN
+			if (this.use2dCanvas) {
+				this.inited = false;
+			}
+			// #endif
+			// #ifdef MP-ALIPAY
+			this.inited = false;
+			// #endif
+		}
+	},
+	// #ifdef MP-WEIXIN || MP-TOUTIAO
+	created() {
+		const { SDKVersion, version, platform, environment } = uni.getSystemInfoSync();
+		// #ifdef MP-WEIXIN
+		// ios wx7.0.20 createImage bug
+		this.use2dCanvas = this.type === '2d' && compareVersion(SDKVersion, '2.9.2') >= 0 && !((/ios/i.test(platform) && /7.0.20/.test(version)) || /wxwork/i.test(environment));
+		// #endif
+		// #ifdef MP-TOUTIAO
+		this.use2dCanvas = this.type === '2d' && compareVersion(SDKVersion, '1.78.0') >= 0;
+		// #endif
+	},
+		// #endif
+	mounted() {
+		this.$watch(
+			'board',
+			async (val, old) => {
+				if (JSON.stringify(val) === '{}' || !val) return;
+				if (this.beforeDelay) {await sleep(this.beforeDelay)}
+				this.render();
+			},
+			{
+				deep: true,
+				immediate: true
+			}
+		);
+	},
+	methods: {
+		async update(args, single) {
+			this.draw = null;
+			// #ifdef MP-WEIXIN
+			if (this.use2dCanvas) {
+				this.ctx = null;
+				this.inited = false;
+			}
+			// #endif
+			// #ifdef MP-ALIPAY
+			this.inited = false;
+			// #endif
+			this.isDrawIng = false;
+			await new Promise(resolve => this.$nextTick(resolve));
+			await sleep(200);
+			return this.render(args, single);
+		},
+		async render(args = {}, single = false) {
+			if (this.isDrawIng) {
+				return this.update(args, single);
+			}
+			this.isDrawIng = true;
+			const isArg = JSON.stringify(args) != '{}';
+			const ctx = await this.getContext();
+			let { use2dCanvas, boardWidth, boardHeight, board, canvas, isH5PathToBase64, sleep: s, afterDelay } = this;
+			if (use2dCanvas && !canvas) {
+				return Promise.reject(new Error('render: fail canvas has not been created'));
+			}
+			this.boundary = {
+				top: 0,
+				left: 0,
+				width: boardWidth,
+				height: boardHeight
+			};
+			if (!single) {
+				ctx.clearRect(0, 0, boardWidth, boardHeight);
+			}
+			if (!this.draw || isArg) {
+				this.draw = new Draw(ctx, canvas, use2dCanvas, isH5PathToBase64, s);
+				this.draw.listen('progressChange', (v) => {
+					this.$emit('progress', v)
+				})
+			}
+			this.layout.init(ctx, this.boundary, isH5PathToBase64);
+			if (isArg || (board && JSON.stringify(board) != '{}')) {
+				this.node = await this.layout.calcNode(isArg ? args : Object.assign({}, board, { height: !board.type && board.height == 'auto' ? boardHeight : board.height }));
+			}
+			const { layoutHeight } = this.layout;
+			if (/auto/.test(this.height || board.height) && !this.canvasHeight) {
+				this.canvasHeight = layoutHeight;
+				return this.update(args, single);
+			}
+			if (this.node) {
+				await this.draw?.drawNode(this.node);
+			}
+			await new Promise(resolve => this.$nextTick(resolve));
+			if (!use2dCanvas && !single) {
+				await this.canvasDraw(ctx);
+			}
+			if (afterDelay) {
+				await sleep(afterDelay);
+			}
+			this.$emit('done');
+			if (this.isRenderImage && !single && this.isDrawIng) {
+				this.canvasToTempFilePath()
+					.then(async res => {
+						if(this.pathType == 'base64') {
+							if(/^data:image\/(\w+);base64/.test(res.tempFilePath)) {
+								this.$emit('success', res.tempFilePath);
+							} else {
+								const img = await pathToBase64(res.tempFilePath)
+								this.$emit('success', img)
+							}
+						} else if(this.pathType == 'url'){
+							let img = ''
+							if(/^data:image\/(\w+);base64/.test(res.tempFilePath)) {
+								img = await base64ToPath(res.tempFilePath)
+							} 
+							this.$emit('success', img);
+						} else {
+							this.$emit('success', res.tempFilePath);
+						}
+					})
+					.catch(err => {
+						this.$emit('fail', new Error(JSON.stringify(err)));
+					});
+			}
+			this.isDrawIng = false;
+			return Promise.resolve({ ctx, draw: this.draw, node: this.node });
+		},
+		async custom(cb) {
+			const { ctx, draw } = await this.render({}, true);
+			ctx.save();
+			await cb(ctx, draw);
+			ctx.restore();
+			return Promise.resolve(true);
+		},
+		async single(args = {}) {
+			const res = await this.render(args, true);
+			return Promise.resolve(res);
+		},
+		canvasDraw(flag = false) {
+			const { ctx } = this;
+			return new Promise(resolve => {
+				ctx.draw(flag, resolve);
+			});
+		},
+		async getContext() {
+			if (this.ctx && this.inited) {
+				return Promise.resolve(this.ctx);
+			}
+			const { type, use2dCanvas, dpr, boardWidth, boardHeight } = this;
+			const _getContext = () => {
+				return new Promise(resolve => {
+					uni.createSelectorQuery()
+						.in(this)
+						.select(`#${this.canvasId}`)
+						.boundingClientRect()
+						.exec(res => {
+							if (res) {
+								const ctx = uni.createCanvasContext(this.canvasId, this);
+								if (!this.inited) {
+									this.inited = true;
+									this.use2dCanvas = false;
+									this.canvas = res;
+								}
+								// #ifdef MP-ALIPAY
+								ctx.scale(dpr, dpr);
+								// #endif
+								this.ctx = expand(ctx);
+								resolve(this.ctx);
+							}
+						});
+				});
+			};
+			// #ifndef MP-WEIXIN
+			return _getContext();
+			// #endif
+
+			if (!use2dCanvas) {
+				return _getContext();
+			}
+			return new Promise(resolve => {
+				uni.createSelectorQuery()
+					.in(this)
+					.select(`#${this.canvasId}`)
+					.node()
+					.exec(res => {
+						const canvas = res[0].node;
+						if (!canvas) {
+							this.use2dCanvas = false;
+							resolve(this.getContext());
+						}
+						const ctx = canvas.getContext(type);
+						if (!this.inited) {
+							this.inited = true;
+							canvas.width = boardWidth * dpr;
+							canvas.height = boardHeight * dpr;
+							this.use2dCanvas = true;
+							this.canvas = canvas;
+							ctx.scale(dpr, dpr);
+						}
+						this.ctx = adaptor ? adaptor(ctx) : expand(ctx);
+						resolve(this.ctx);
+					});
+			});
+		},
+		canvasToTempFilePath(args = {}) {
+			const { use2dCanvas, canvasId, dpr, fileType, quality } = this;
+			return new Promise((resolve, reject) => {
+				let { top: y = 0, left: x = 0, width, height } = this.boundary || this;
+				let destWidth = width * dpr;
+				let destHeight = height * dpr;
+				// #ifdef MP-ALIPAY
+				width = destWidth;
+				height = destHeight;
+				// #endif
+				const copyArgs = Object.assign({
+					x,
+					y,
+					width,
+					height,
+					destWidth,
+					destHeight,
+					canvasId,
+					fileType,
+					quality,
+					success: resolve,
+					fail: reject
+				}, args);
+				if (use2dCanvas) {
+					delete copyArgs.canvasId;
+					copyArgs.canvas = this.canvas;
+				}
+				uni.canvasToTempFilePath(copyArgs, this);
+			});
+		}
+	}
+};
+</script>

+ 0 - 0
packageShang/uni_modules/lime-painter/components/lime-painter/layout.js


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