laosan2382995021@163.com před 4 roky
revize
5357833071
100 změnil soubory, kde provedl 8230 přidání a 0 odebrání
  1. 253 0
      api.js
  2. 2 0
      uni-shop/.gitignore
  3. 11 0
      uni-shop/.hbuilderx/launch.json
  4. 5 0
      uni-shop/.idea/.gitignore
  5. 32 0
      uni-shop/.idea/inspectionProfiles/Project_Default.xml
  6. 8 0
      uni-shop/.idea/modules.xml
  7. 12 0
      uni-shop/.idea/uni-shop.iml
  8. 245 0
      uni-shop/App.vue
  9. 21 0
      uni-shop/LICENSE
  10. 44 0
      uni-shop/README.md
  11. 270 0
      uni-shop/components/Progress-Bar/Progress-Bar.css
  12. 70 0
      uni-shop/components/Progress-Bar/Progress-Bar.vue
  13. 0 0
      uni-shop/components/address-picker/data/address.js
  14. 62 0
      uni-shop/components/address-picker/handleData.js
  15. 57 0
      uni-shop/components/address-picker/main.vue
  16. 214 0
      uni-shop/components/address-picker/mixins/handle.js
  17. 3 0
      uni-shop/components/address-picker/mixins/index.js
  18. 9 0
      uni-shop/components/address-picker/props.js
  19. 75 0
      uni-shop/components/address-picker/style.scss
  20. 24 0
      uni-shop/components/dot/main.vue
  21. 18 0
      uni-shop/components/dot/props.js
  22. 14 0
      uni-shop/components/dot/style.scss
  23. 18 0
      uni-shop/components/empty.vue
  24. 192 0
      uni-shop/components/flat-list/export.js
  25. 5 0
      uni-shop/components/flat-list/mixins/handle.js
  26. 4 0
      uni-shop/components/flat-list/mixins/index.js
  27. 276 0
      uni-shop/components/flat-list/mixins/status.js
  28. 112 0
      uni-shop/components/flat-list/props.js
  29. 106 0
      uni-shop/components/flat-list/src/main.vue
  30. 81 0
      uni-shop/components/flat-list/style.scss
  31. 119 0
      uni-shop/components/mix-list-cell.vue
  32. 6 0
      uni-shop/components/mix-loading/mix-loading.vue
  33. 74 0
      uni-shop/components/modal-select/main.vue
  34. 15 0
      uni-shop/components/modal-select/props.js
  35. 26 0
      uni-shop/components/modal-select/style.scss
  36. 34 0
      uni-shop/components/modal/main.vue
  37. 178 0
      uni-shop/components/modal/mixins/handle.js
  38. 3 0
      uni-shop/components/modal/mixins/index.js
  39. 130 0
      uni-shop/components/modal/props.js
  40. 146 0
      uni-shop/components/modal/style.scss
  41. 32 0
      uni-shop/components/refresh-view/main.vue
  42. 69 0
      uni-shop/components/refresh-view/mixins/handle.js
  43. 3 0
      uni-shop/components/refresh-view/mixins/index.js
  44. 54 0
      uni-shop/components/refresh-view/props.js
  45. 28 0
      uni-shop/components/refresh-view/style.scss
  46. 202 0
      uni-shop/components/share.vue
  47. 199 0
      uni-shop/components/uni-countdown/uni-countdown.vue
  48. 96 0
      uni-shop/components/uni-icons/icons.js
  49. 10 0
      uni-shop/components/uni-icons/uni-icons.vue
  50. 194 0
      uni-shop/components/uni-load-more/uni-load-more.vue
  51. 205 0
      uni-shop/components/uni-number-box.vue
  52. binární
      uni-shop/components/uni-rate/images/rate-active.png
  53. binární
      uni-shop/components/uni-rate/images/rate.png
  54. 137 0
      uni-shop/components/uni-rate/uni-rate.vue
  55. 245 0
      uni-shop/components/uni-swipe-action-item/bindingx.js
  56. 204 0
      uni-shop/components/uni-swipe-action-item/index.wxs
  57. 160 0
      uni-shop/components/uni-swipe-action-item/mpalipay.js
  58. 158 0
      uni-shop/components/uni-swipe-action-item/mpother.js
  59. 118 0
      uni-shop/components/uni-swipe-action-item/mpwxs.js
  60. 265 0
      uni-shop/components/uni-swipe-action-item/uni-swipe-action-item.vue
  61. 58 0
      uni-shop/components/uni-swipe-action/uni-swipe-action.vue
  62. 226 0
      uni-shop/components/upload-images.vue
  63. binární
      uni-shop/components/v-header/images/back-white.png
  64. binární
      uni-shop/components/v-header/images/back.png
  65. 89 0
      uni-shop/components/v-header/main.vue
  66. 200 0
      uni-shop/components/v-header/mixins/handle.js
  67. 4 0
      uni-shop/components/v-header/mixins/index.js
  68. 46 0
      uni-shop/components/v-header/mixins/scroll.js
  69. 94 0
      uni-shop/components/v-header/props.js
  70. 120 0
      uni-shop/components/v-header/style.scss
  71. 39 0
      uni-shop/components/v-image/main.vue
  72. 58 0
      uni-shop/components/v-image/mixins/handle.js
  73. 3 0
      uni-shop/components/v-image/mixins/index.js
  74. 30 0
      uni-shop/components/v-image/props.js
  75. 34 0
      uni-shop/components/v-image/style.scss
  76. 42 0
      uni-shop/components/v-step/main.vue
  77. 128 0
      uni-shop/components/v-step/mixins/handle.js
  78. 3 0
      uni-shop/components/v-step/mixins/index.js
  79. 67 0
      uni-shop/components/v-step/props.js
  80. 49 0
      uni-shop/components/v-step/style.scss
  81. 13 0
      uni-shop/components/z-table/table-render.js
  82. 782 0
      uni-shop/components/z-table/z-table.vue
  83. 24 0
      uni-shop/config/config.js
  84. 59 0
      uni-shop/config/lib.config.d.ts
  85. 14 0
      uni-shop/config/plugins/compatible.js
  86. 5 0
      uni-shop/config/plugins/index.js
  87. 13 0
      uni-shop/layout/layout-commet/data/control.js
  88. 154 0
      uni-shop/layout/layout-commet/main.vue
  89. 20 0
      uni-shop/layout/layout-commet/props.js
  90. 85 0
      uni-shop/layout/layout-commet/style.scss
  91. binární
      uni-shop/layout/layout-coupon/images/background.png
  92. 23 0
      uni-shop/layout/layout-coupon/index.vue
  93. 52 0
      uni-shop/layout/layout-coupon/style.scss
  94. 15 0
      uni-shop/layout/layout-shop-screen/data/screen.js
  95. 149 0
      uni-shop/layout/layout-shop-screen/main.vue
  96. 3 0
      uni-shop/layout/layout-shop-screen/mixins/index.js
  97. 52 0
      uni-shop/layout/layout-shop-screen/style.scss
  98. 48 0
      uni-shop/layout/layout-shop/main.vue
  99. 10 0
      uni-shop/layout/layout-shop/props.js
  100. 66 0
      uni-shop/layout/layout-shop/style.scss

+ 253 - 0
api.js

@@ -0,0 +1,253 @@
+
+import store from '../../store'
+const _config = {
+	baseUrl: "http://www.lychwl.com.cn"
+}
+// 提示
+const msg = (title, duration = 3000, mask = false, icon = 'none') => {
+	//统一提示方便全局修改
+	if (Boolean(title) === false) {
+		return;
+	}
+	uni.showToast({
+		title,
+		duration,
+		mask,
+		icon
+	});
+	setTimeout(function() {
+		uni.hideToast();
+	}, duration)
+}
+//等待框
+function showLoading(title='请稍候..', mask=false){
+	// 弹出系统等待对话框
+	// #ifdef APP-PLUS
+	_loading = plus.nativeUI.showWaiting( title, {
+		width: '76px',
+		height: '76px',
+		size: '12px',
+		modal: mask,
+		back: 'none', //安卓截获返回键 none:截获不做响应 close:截获后关闭等待框 none:不截获
+		loading: {
+			type: 'snow'
+		}
+	});
+	setTimeout(()=>{
+		hideLoading();
+	}, 10000)
+	
+	return _loading;
+	// #endif
+	// #ifndef APP-PLUS
+	uni.showLoading({
+		title,
+		mask
+	})
+	// #endif
+}
+function inAction(fn, that, timeout = 2000){
+	if(that['in' + fn]){
+		return true
+	}
+	that['in' + fn] = true;
+	setTimeout(()=>{
+		that['in' + fn] = false;
+	}, timeout)
+	return false;
+}
+// 返回上一页
+const prePage = () => {
+	let pages = getCurrentPages();
+	let prePage = pages[pages.length - 2];
+	// #ifdef H5
+	return prePage;
+	// #endif
+	return prePage.$vm;
+}
+
+
+// 上传图片--注按需要对res处理 JSON.parse()
+function  uploadImg(formData){
+	return new Promise((resolve,reject)=>{
+		uni.chooseImage({
+			count:1,
+			success(chooseImageRes){
+				// resolve(chooseImageRes.tempFilePaths[0]);
+				const tempFilePaths = chooseImageRes.tempFilePaths[0];
+				let token = uni.getStorageSync("token")
+				uni.showLoading();
+				uni.uploadFile({
+					url:_config.consoleBaseUrl+'api/common/upload',
+					filePath: tempFilePaths,
+					name: 'file',
+					formData:formData,
+					header: {
+						'token':token,
+					},
+					success:(res)=>{
+						resolve(res.data);
+					},
+					fail:(err)=>{
+						msg('上传失败');
+						reject();
+					},
+					complete:()=> {
+						uni.hideLoading();
+					},
+				})
+			},
+			fail(err){
+				// msg('打开相册失败');
+				reject(err)
+			}
+		})
+	})
+}
+
+// 深拷贝
+const deepCopy = (p, c) => {
+	var c = c || {};
+	for (var i in p) {
+		if (typeof p[i] === "object") {
+			c[i] = (p[i].constructor === Array) ? [] : {};
+			deepCopy(p[i], c[i])
+		} else {
+			c[i] = p[i]
+		}
+	}
+	return c;
+}
+
+function request(url, data, options, method="POST") {
+	options = Object.assign({showLoading: false,loadingText: '加载中',loadingMask: false}, options);
+	
+	const header = {
+	 	'Content-Type': 'application/x-www-form-urlencoded'
+	};
+	const token = uni.getStorageSync('token') || '';
+	if(token && typeof token == "string"){
+		data={...data,token};
+	}
+	let params = {
+		 url: _config.consoleBaseUrl + url,
+		//url,
+		data,
+		method,
+		header,
+	}
+	return new Promise((resolve, reject) => {
+		options.showLoading && showLoading(options.loadingText, options.loadingMask);
+		params.complete = (response) => {
+			options.showLoading && hideLoading();	
+			if (response.statusCode === 200) {
+				 if(response.data.code != 1){
+					msg(response.data.msg);
+					reject();
+				}else{
+					resolve(response.data);
+				}
+			}else if(response.statusCode === 401){
+				msg("401,开发中。。。")
+				reject();
+			}else{
+				msg('网络繁忙 ' + response.statusCode, 1500, true);
+				reject();
+			}
+		}
+		uni.request(params);
+	})
+}
+
+function get(url, data={}, options={}) {
+	return request(url, data, options, 'GET');
+}
+function post(url, data={}, options={}) {
+	return request(url, data, options, 'POST');
+}
+
+
+// 处理时间
+const handleStrLength=(num)=> {
+	return String(num).length < 2 ? '0' + num : num;
+}
+const handleDatetime=(date)=> {
+	let m = handleStrLength(date.getMonth() + 1);
+	let d = handleStrLength(date.getDate());
+	let h = handleStrLength(date.getHours());
+	let mm = handleStrLength(date.getMinutes());
+	let dateStr = date.getFullYear() + "-" + m + "-" + d + " " + h + ":" + mm;
+	return dateStr;
+}
+const getCurTime=()=> {
+	let date = new Date();
+	return  handleDatetime(date);
+}
+const getEndTime=()=> {
+	let date = new Date();
+	date.setFullYear(date.getFullYear() + 1);
+	return handleDatetime(date);
+}
+
+
+function match(str, type, showMsg=true){
+	let exp = '';
+	
+	switch(type){
+		case 'mobile':
+			if(str === ''){
+				showMsg && msg('请填写手机号码');
+				return false;
+			}
+			exp = /(^1[3|4|5|6|7|8|9][0-9]{9}$)/; //手机号
+			if(!exp.test(str)){
+				showMsg && msg('手机号码格式不正确');
+				return false;
+			}
+			break;
+		case 'pwd':
+			if(str === ''){
+				showMsg && msg('请填写密码');
+				return false;
+			}
+		case 'code':
+				if(str === ''){
+					showMsg && msg('请填写验证码');
+					return false;
+				}
+				if(!/^[0-9]+$/.test(str)){
+					showMsg && msg('验证码不正确');
+					return false;
+				}
+			break;
+	}
+	return true;
+}
+///**详情页处理富文本 details仅是变化替换部分a.replace(/<text>(abc)<\/text>/,function(match,$1) $1=abc---------------------------------------------------------------------- */
+const replaceDetail = (details = '') => {
+	//newContent仅是details替换后内容;
+  let newContent = details.replace(/<img[^>]*>/gi, function (match, capture) { //去除三标签
+    match = match.replace(/style="[^"]+"/gi, '').replace(/style='[^']+'/gi, '');
+    match = match.replace(/width="[^"]+"/gi, '').replace(/width='[^']+'/gi, '');
+    match = match.replace(/height="[^"]+"/gi, '').replace(/height='[^']+'/gi, '');
+    return match;
+  });
+  newContent = newContent.replace(/<br[^>]*\/>/gi, '');
+  newContent = newContent.replace(/<img/gi, '<img style="max-width:100%;height:auto;display:block;margin:0 auto;"');
+  return newContent;
+}
+
+export default {
+	    _config,
+		msg,
+		prePage,
+		get,
+		post,
+		deepCopy,
+		inAction,
+		replaceDetail,
+		getCurTime,
+		getEndTime,
+		match,
+		uploadImg,
+	};

+ 2 - 0
uni-shop/.gitignore

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

+ 11 - 0
uni-shop/.hbuilderx/launch.json

@@ -0,0 +1,11 @@
+{ // launch.json 配置了启动调试时相关设置,configurations下节点名称可为 app-plus/h5/mp-weixin/mp-baidu/mp-alipay/mp-qq/mp-toutiao/mp-360/
+  // launchtype项可配置值为local或remote, local代表前端连本地云函数,remote代表前端连云端云函数
+    "version": "0.0",
+    "configurations": [{
+            "type": "uniCloud",
+            "default": {
+                "launchtype": "local"
+            }
+        }
+    ]
+}

+ 5 - 0
uni-shop/.idea/.gitignore

@@ -0,0 +1,5 @@
+# 默认忽略的文件
+/shelf/
+/workspace.xml
+# 基于编辑器的 HTTP 客户端请求
+/httpRequests/

+ 32 - 0
uni-shop/.idea/inspectionProfiles/Project_Default.xml

@@ -0,0 +1,32 @@
+<component name="InspectionProjectProfileManager">
+  <profile version="1.0">
+    <option name="myName" value="Project Default" />
+    <inspection_tool class="CssFloatPxLength" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
+    <inspection_tool class="CssInvalidAtRule" enabled="false" level="ERROR" enabled_by_default="false" />
+    <inspection_tool class="CssInvalidCharsetRule" enabled="false" level="WARNING" enabled_by_default="false" />
+    <inspection_tool class="CssInvalidElement" enabled="false" level="ERROR" enabled_by_default="false" />
+    <inspection_tool class="CssInvalidFunction" enabled="false" level="ERROR" enabled_by_default="false" />
+    <inspection_tool class="CssInvalidHtmlTagReference" enabled="false" level="WARNING" enabled_by_default="false" />
+    <inspection_tool class="CssInvalidImport" enabled="false" level="WARNING" enabled_by_default="false" />
+    <inspection_tool class="CssInvalidMediaFeature" enabled="false" level="ERROR" enabled_by_default="false" />
+    <inspection_tool class="CssInvalidPropertyValue" enabled="false" level="ERROR" enabled_by_default="false" />
+    <inspection_tool class="CssInvalidPseudoSelector" enabled="false" level="ERROR" enabled_by_default="false" />
+    <inspection_tool class="CssMissingComma" enabled="false" level="WARNING" enabled_by_default="false" />
+    <inspection_tool class="CssNegativeValue" enabled="false" level="ERROR" enabled_by_default="false" />
+    <inspection_tool class="CssNoGenericFontName" enabled="false" level="WARNING" enabled_by_default="false" />
+    <inspection_tool class="CssUnitlessNumber" enabled="false" level="WARNING" enabled_by_default="false" />
+    <inspection_tool class="CssUnknownProperty" enabled="false" level="WARNING" enabled_by_default="false">
+      <option name="myCustomPropertiesEnabled" value="false" />
+      <option name="myIgnoreVendorSpecificProperties" value="false" />
+      <option name="myCustomPropertiesList">
+        <value>
+          <list size="0" />
+        </value>
+      </option>
+    </inspection_tool>
+    <inspection_tool class="CssUnknownTarget" enabled="false" level="ERROR" enabled_by_default="false" />
+    <inspection_tool class="CssUnresolvedClass" enabled="false" level="ERROR" enabled_by_default="false" />
+    <inspection_tool class="CssUnresolvedCustomProperty" enabled="false" level="ERROR" enabled_by_default="false" />
+    <inspection_tool class="Eslint" enabled="true" level="WARNING" enabled_by_default="true" />
+  </profile>
+</component>

+ 8 - 0
uni-shop/.idea/modules.xml

@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="ProjectModuleManager">
+    <modules>
+      <module fileurl="file://$PROJECT_DIR$/.idea/uni-shop.iml" filepath="$PROJECT_DIR$/.idea/uni-shop.iml" />
+    </modules>
+  </component>
+</project>

+ 12 - 0
uni-shop/.idea/uni-shop.iml

@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module type="WEB_MODULE" version="4">
+  <component name="NewModuleRootManager">
+    <content url="file://$MODULE_DIR$">
+      <excludeFolder url="file://$MODULE_DIR$/temp" />
+      <excludeFolder url="file://$MODULE_DIR$/.tmp" />
+      <excludeFolder url="file://$MODULE_DIR$/tmp" />
+    </content>
+    <orderEntry type="inheritedJdk" />
+    <orderEntry type="sourceFolder" forTests="false" />
+  </component>
+</module>

+ 245 - 0
uni-shop/App.vue

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

+ 21 - 0
uni-shop/LICENSE

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

+ 44 - 0
uni-shop/README.md

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

Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 270 - 0
uni-shop/components/Progress-Bar/Progress-Bar.css


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

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

Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 0 - 0
uni-shop/components/address-picker/data/address.js


+ 62 - 0
uni-shop/components/address-picker/handleData.js

@@ -0,0 +1,62 @@
+// import useData from './useData';
+//
+// // 取出级别为 1 的
+// let firstData = [];
+// let globalUseData = {};
+// let parseData = {};
+//
+//
+// for (let i=0,count=useData.length;i<count;i++) {
+//
+//     if (useData[i].level === 1) {
+//         firstData.push(useData[i]);
+//     } else {
+//         if (globalUseData[useData[i].pid] === undefined) globalUseData[useData[i].pid] = [];
+//
+//         globalUseData[useData[i].pid].push(useData[i]);
+//     }
+//
+// }
+//
+// function handleItem(item){
+//
+//     return {
+//         code:item.id,
+//         name:item.name
+//     };
+//
+// }
+//
+//
+// function getChildren(data,level) {
+//
+//
+// 	data.map((item,index)=>{
+//         parseData[level+':'+item.code] = handleData(globalUseData[item.code],level===1?item.code+':city':item.code+':area');
+//         if (level===1) {
+//             return getChildren(parseData[level+':'+item.code],2);
+//         }
+// 	});
+//
+//
+// }
+//
+// parseData['0:0'] = handleData(firstData);
+//
+// function handleData(data,type='province') {
+//
+// 	let arr = [];
+// 	data = data.map((item)=>{
+// 		arr.push(item.name);
+// 		return handleItem(item);
+// 	});
+//
+// 	parseData[type] = arr;
+//
+// 	return data;
+//
+// }
+//
+// getChildren(parseData['0:0'],1);
+//
+// console.log(parseData);

+ 57 - 0
uni-shop/components/address-picker/main.vue

@@ -0,0 +1,57 @@
+<template>
+  <view class="screen_all">
+    <view class="screen_all" @click="open">
+      <slot></slot>
+    </view>
+    <view
+        class="address-picker flex"
+        :class="[show?'picker-show':'picker-hide']"
+    >
+      <view @click="cancel" class="flex-1"></view>
+      <view class="address-picker-bottom"
+            :class="[show?'bottom-show':'bottom-hide']"
+      >
+        <view class="address-picker-header row">
+          <view @click="cancel" class="address-picker-control address-picker-cancel center">取消</view>
+          <view class="flex-all-1"></view>
+          <view @click="confirm" class="address-picker-control address-picker-confirm center">确定</view>
+        </view>
+        <view class="address-picker-content">
+          <picker-view
+              :value="vValue"
+              class="picker-view"
+              @change="changeColumn"
+          >
+            <picker-view-column>
+              <view class="picker-column flex jCenter" v-for="(item,index) in province" :key="index"><view class="picker-view-line-1">{{item.name}}</view></view>
+            </picker-view-column>
+            <picker-view-column>
+              <view  class="picker-column flex jCenter" v-for="(item,index) in city" :key="index"><view class="picker-view-line-1">{{item.name}}</view></view>
+            </picker-view-column>
+            <picker-view-column>
+              <view  class="picker-column flex jCenter" v-for="(item,index) in area" :key="index"><view class="picker-view-line-1">{{item.name}}</view></view>
+            </picker-view-column>
+          </picker-view>
+        </view>
+        <!-- iphoneX的兼容 -->
+        <view :style="{height: footerHeight+'px'}"></view>
+
+      </view>
+
+    </view>
+  </view>
+</template>
+
+<script>
+import mixins from './mixins/index';
+import props from "./props";
+export default {
+  name: "address-picker",
+  mixins,
+  props
+}
+</script>
+
+<style scoped src="./style.scss" lang="scss">
+
+</style>

+ 214 - 0
uni-shop/components/address-picker/mixins/handle.js

@@ -0,0 +1,214 @@
+import config from "../../../config/config";
+import address from "../data/address";
+
+export default {
+
+    data(){
+        return {
+            province:[],
+            city:[],
+            area:[],
+            vValue:[],
+            show: false,  // 是否显示
+            use:['province','city','area'],
+            footerHeight: config.phoneFooterHeight
+        }
+    },
+
+    watch:{
+        value:function (value) {
+            return this.setValue(value);
+        }
+    },
+
+
+
+    methods:{
+
+        open:function(){
+
+            if (!this.show) {
+
+                if (this.storeValueTarget && this.storeValueTarget.join('') !== this.vValue.join('')) {
+                    this.vValue = this.storeValueTarget;
+                    this.changeLevel('0',0);
+                }
+
+                this.show =true;
+            }
+        },
+
+        /* 点击取消触发 */
+        cancel:function(){
+            if (this.show) {
+                this.show =false;
+            }
+        },
+
+        /* 点击确认触发 */
+        confirm:function(){
+
+            let code = '0';
+            let cCode = [];
+            let cAddress = [];
+            this.vValue.map((item,index)=>{
+
+                code = this.getCode(index,code);
+                if (address[code]&&address[code][item]) {
+                    cAddress.push(address[code][item].name);
+                    code = address[code][item].code;
+                    cCode.push(code);
+                }
+
+            });
+            this.storeValue = cAddress.join('');
+            this.storeValueTarget = [...this.vValue];
+            this.$emit('change',{
+                postcode: code,
+                code: cCode,
+                value: cAddress
+            });
+
+            return this.cancel();
+
+        },
+
+        /* 更改展示数据 */
+        changeLevel:function(code = '0',level=0,draw={}){
+
+            let parentCode = code;
+            let nowCode = '';
+            for (let i=level;i<3;i++) {
+                nowCode = this.getCode(i,parentCode);
+
+                if (address[nowCode]) {
+                    draw[this.use[i]] = address[nowCode] || [];
+                    if (address[nowCode][this.vValue[i] || 0]){
+                        parentCode = address[nowCode][this.vValue[i] || 0].code || '';
+                    }else {
+                        parentCode = '';
+                    }
+                } else {
+                    draw[this.use[i]] = [];
+                }
+            }
+
+            return this.setUseData(draw);
+
+        },
+
+        setUseData(draw){
+            for (let key in draw) {
+                if (draw.hasOwnProperty(key)) {
+                    this[key] = draw[key]
+                }
+            }
+        },
+
+        /* 获取默认选择的地址 */
+        getAddressValue:function (value) {
+
+            let newValue = [];
+            let key = 0;
+            let parentItem = address;
+            for(let i=0;i<3;i++) {
+
+
+                if (value[i]) {
+                    key = i===0?this.use[i]:parentItem.code+':'+this.use[i];
+
+                    newValue[i] = address[key].indexOf(value[i]);
+                    if (newValue[i] < 0) newValue[i] = 0;
+                } else {
+                    newValue[i] = 0;
+                }
+
+                if (i === 0) {
+                    parentItem = address['0:0'][newValue[i]];
+                } else {
+                    parentItem = address[this.getCode(i,parentItem.code)][newValue[i]];
+                }
+
+            }
+
+            return newValue;
+        },
+
+        changeColumn:function (e) {
+
+            let newValue = e.detail.value;
+
+            let item ={
+                code:'0'
+            };
+
+            let change = false;
+
+            let storageI = 0;
+
+            let oldValue = [...this.vValue];
+
+
+            for (let i=0;i<3;i++) {
+
+                if (change) {
+                    newValue[i] = 0;
+                }else {
+                    item = this.getItem(this.getCode(i,item.code),newValue[i]);
+                }
+
+                if (oldValue[i] !== newValue[i]) {
+                    if (!change) {
+                        storageI = i;
+                    }
+                    change = true;
+                }
+            }
+            this.vValue = newValue;
+            if (change) {
+                this.changeLevel(item.code,storageI+1);
+            }
+
+        },
+
+        getCode:function(level,code){
+            return level+':'+code;
+        },
+
+        getItem:function (code='0:0',index) {
+
+            if (address[code]) {
+                return address[code][index];
+            } else {
+                return {
+                    code:-1
+                };
+            }
+
+
+        },
+
+        setValue:function (value) {
+            let storeValue = value.join('');
+            if (this.storeValue !== storeValue) {
+                this.vValue = this.getAddressValue(value);
+                let useValue = this.vValue;
+                this.storeValue = storeValue;
+                this.changeLevel('0',0);
+                this.vValue = [];
+
+                setTimeout(()=>{
+                    this.vValue = useValue;
+                });
+
+            }
+
+        }
+
+    },
+
+    mounted(){
+        return this.setValue(this.value || []);
+    }
+
+}

+ 3 - 0
uni-shop/components/address-picker/mixins/index.js

@@ -0,0 +1,3 @@
+import handle from "./handle";
+
+export default [handle];

+ 9 - 0
uni-shop/components/address-picker/props.js

@@ -0,0 +1,9 @@
+export default {
+
+	/* 中文提示 */
+	value:{
+		type: Array,
+		value: []
+	},
+
+}

+ 75 - 0
uni-shop/components/address-picker/style.scss

@@ -0,0 +1,75 @@
+.address-picker{
+  position: fixed;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  background-color: rgba(0,0,0,0.5);
+  z-index: 999999;
+  transition: .3s;
+}
+.address-picker-header{
+  height: 100upx;
+  border-bottom: 1upx solid #eee;
+  padding: 0 30upx;
+}
+.flex{
+  display: flex;
+  flex-direction: column;
+}
+.flex-1{
+  flex: 1;
+}
+.jCenter{
+  justify-content: center;
+}
+.address-picker-bottom{
+  background-color: #fff;
+  transition: .3s;
+  border-radius: 20upx 20upx 0 0;
+}
+.address-picker-control{
+  width: 100upx;
+  height: 100upx;
+  font-size: 30upx;
+}
+.address-picker-cancel{
+  color: #999;
+}
+.address-picker-confirm{
+  color: #41AE3C;
+}
+.address-picker-content{
+  padding: 30upx 0;
+}
+.picker-column{
+  height: 72upx;
+  padding: 0 15upx;
+  font-size: 32upx;
+  line-height: 40upx;
+}
+.picker-view{
+  height: 504upx;
+}
+.picker-view-line-1{
+  white-space: nowrap;
+  overflow: hidden;
+  display: block;
+  width: 100%;
+  text-overflow: ellipsis;
+  text-align: center;
+}
+.picker-show{
+  opacity: 1;
+  visibility: inherit;
+}
+.picker-hide{
+  opacity: 0;
+  visibility: hidden;
+}
+.bottom-show{
+  transform: translateY(0);
+}
+.bottom-hide{
+  transform: translateY(100%);
+}

+ 24 - 0
uni-shop/components/dot/main.vue

@@ -0,0 +1,24 @@
+<template>
+  <view v-if="dotNumber" class="dot-number center"><text>{{dotNumber}}</text></view>
+</template>
+
+<script>
+import props from "./props";
+export default {
+  name: "dot",
+  computed:{
+    dotNumber(){
+      if (this.value) {
+        return this.value>this.maxValue ? this.maxValue+this.format:this.value;
+      } else {
+        return '';
+      }
+    }
+  },
+  props
+}
+</script>
+
+<style scoped lang="scss" src="./style.scss">
+
+</style>

+ 18 - 0
uni-shop/components/dot/props.js

@@ -0,0 +1,18 @@
+export default {
+
+    value:{
+        type:Number,
+        default:0
+    },
+
+    maxValue:{
+        type:Number,
+        default:99
+    },
+
+    format:{
+        type:String,
+        default:'+'
+    }
+
+}

+ 14 - 0
uni-shop/components/dot/style.scss

@@ -0,0 +1,14 @@
+.dot-number{
+  height: 28upx;
+  min-width:28upx;
+  background-color: #FF3C3C;
+  border-radius: 14upx;
+  font-size: 22upx;
+  line-height: 24upx;
+  color: #fff;
+  padding: 0 8upx;
+}
+
+.dot-number text{
+  transform: scale(0.9);
+}

Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 18 - 0
uni-shop/components/empty.vue


+ 192 - 0
uni-shop/components/flat-list/export.js

@@ -0,0 +1,192 @@
+/* 该组件对外提供的 扩展功能 */
+export default {
+
+    data:function () {
+        return {
+            base_flat_data:{
+                data:[],
+                skeleton: false
+            },
+            base_flat_page_size: 10,
+            base_flat_remove:{length:0},  //  被删除的 data 因为 setData 不能 一次性渲染 非常多的 数据 所以 会提供一个 暂时删除的 数据表 请更具该对象自行处理
+            base_flat_key:'id',   // 被删除的记录的唯一值
+            base_flat_id:'flatIds',
+            base_flat_keys_collect: false,
+            base_flat_keys_unique:{},
+            base_flat_keys:undefined,
+            base_flat_row:1
+        }
+    },
+
+    /* 页面初始化的时候触发 */
+    mounted:function(){
+        this.__base_flat_ref = this.$refs[this.base_flat_id];
+    },
+
+    methods:{
+        flatIdGETIndex:function (id){
+            return this.base_flat_keys_unique && this.base_flat_keys_unique[id];
+        },
+
+        /* 修改data */
+        flatChangeData:function (data) {
+
+            /* 如果 骨架模式不想等 将执行 重置数据 */
+            if (this.base_flat_data.skeleton !== data.skeleton || data.clear) {
+
+                let resultData = {...data};
+                if (this.base_flat_row > 1) {
+                    resultData.data = [];
+                    for (let i=0,iCount = data.data.length;i<iCount;i++) {
+                        let row = i%this.base_flat_row;
+                        if (resultData.data[row] === undefined) resultData.data[row] = [];
+                        resultData.data[row].push(data.data[i]);
+                    }
+                } else {
+                    resultData = data;
+                }
+
+                this.base_flat_data = resultData;
+
+                if (this.base_flat_remove.length > 0) {
+                    /* 重置删除列表 */
+                    this.base_flat_remove = {
+                        length: 0
+                    };
+                }
+
+                if (data.data.length <= 0 && this.__base_flat_ref) {
+                    this.__base_flat_ref.setStatusApi(true);
+                }
+
+                data.data.map((item,index)=>{
+                    this.base_flat_keys_unique[item[this.base_flat_key]] = index;
+                    if (this.base_flat_keys_collect) {
+                        if (!this.base_flat_keys) this.base_flat_keys = {};
+                        this.base_flat_keys[item[this.base_flat_key]] = 1
+                    }
+                })
+
+            } else {
+                /* 否则 将为 添加数据 */
+                let count = data.position;
+                if (this.base_flat_row <= 1) {
+                    for (let i=0,iCount = data.data.length;i<iCount;i++) {
+                        this.$set(this.base_flat_data.data,(count + i),data.data[i]);
+                        this.base_flat_keys_unique[data.data[i][this.base_flat_key]] = (count + i);
+                    }
+                } else {
+                    for (let i=0,iCount = data.data.length;i<iCount;i++) {
+                        let row = (count + i)%this.base_flat_row;
+                        if (this.base_flat_data.data[row] === undefined) this.base_flat_data.data[row] = [];
+                        this.$set(this.base_flat_data.data[row],(this.data.base_flat_data.data[row].length),data.data[i]);
+                    }
+                }
+
+                if (this.base_flat_keys_collect) {
+                    data.data.map((item)=> this.base_flat_keys[item[this.base_flat_key]] = 1);
+                }
+
+
+            }
+
+        },
+
+        /* 鉴定ID是否存在 */
+        flatHasOwnPropertyID(id){
+            if (this.base_flat_keys) {
+                return  this.base_flat_keys[id];
+            } else {
+                return false;
+            }
+        },
+
+        /* 移除某一个数据 */
+        flatSplice:function (index,id) {
+
+            if (id) {
+                if (!this.base_flat_data[index]) {
+                    /* 记录数量增加 */
+                    this.base_flat_remove.length++;
+                    if ((this.base_flat_data.data.length - this.base_flat_remove.length) <= 0){
+                        this.base_flat_remove[index] = true;
+                        return this.flatReload(1,true);
+                    } else {
+                        this.base_flat_remove[index] = true;
+                    }
+                }
+            } else {
+                if (this.base_flat_data.data[index] && this.base_flat_data.data[index][this.base_flat_key]) {
+                    let key = this.base_flat_data.data[index][this.base_flat_key];
+                    /* 记录数量增加 */
+                    this.base_flat_remove.length++;
+
+                    if ((this.base_flat_data.data.length - this.base_flat_remove.length) <= 0) {
+                        this.base_flat_remove[key] = true;
+                        return this.flatReload(1,true);
+                    } else {
+                        /* 执行删除标记 */
+                        this.base_flat_remove[key] = true;
+                    }
+
+
+                }
+            }
+
+        },
+
+        /* 数据位置互换 */
+        flatDataExchange:function(index,toIndex){
+            if (index !== toIndex && this.base_flat_data.data[index] && this.base_flat_data.data[toIndex]) {
+                // 交换数据位置
+                [this.base_flat_data.data[index],this.base_flat_data.data[toIndex]] = [this.base_flat_data.data[toIndex],this.base_flat_data.data[index]]
+            }
+        },
+
+        /* 获取某一条数据 */
+        flatGetData:function (index){
+            if (this.base_flat_data.data[index]) {
+                // 交换数据位置
+                return this.base_flat_data.data[index];
+            }
+        },
+
+        /* 更新到某一条数据 */
+        flatSaveData:function(index,item){
+            if (this.base_flat_data.data[index] && item !== undefined) {
+                return this.$set(this.base_flat_data.data,index,item);
+            }
+        },
+
+        /* 向头部添加一条数据 并会从第一页重新开始 */
+        flatUnshift:function(item){
+            if (this.__base_flat_ref){
+                let maxCount = this.base_flat_page_size - 1;
+                let otherData = [];
+                for (let i=0,count = this.base_flat_data.data.length;i<count;i++) {
+                    if (!this.base_flat_remove[this.base_flat_data.data[i][this.base_flat_key]]) {
+                        if (otherData.length < maxCount) {
+                            otherData.push(this.base_flat_data.data[i]);
+                        } else {
+                            break;
+                        }
+                    }
+                }
+                otherData.unshift(item);
+                this.__base_flat_ref.setSuccessPageApi(1);
+                return this.base_flat_data.data =otherData;
+            }
+
+        },
+
+        /*
+        *  刷新操作
+        * */
+        flatReload: function (state,must) {
+            if (this.__base_flat_ref) {
+                return this.__base_flat_ref.reload(state,must);
+            }
+        },
+    }
+
+}

+ 5 - 0
uni-shop/components/flat-list/mixins/handle.js

@@ -0,0 +1,5 @@
+export default {
+
+
+
+}

+ 4 - 0
uni-shop/components/flat-list/mixins/index.js

@@ -0,0 +1,4 @@
+import handle from './handle';
+import status from './status';
+
+export default [handle,status];

+ 276 - 0
uni-shop/components/flat-list/mixins/status.js

@@ -0,0 +1,276 @@
+export default {
+
+    data(){
+        return {
+            start: true,   // 初始化
+            state: 0,      //  0 无状态 1 加载中 2 加载成功 3 暂无更多  4 错误
+            pagingStatus: true,  // 是否 显示 底部分页器
+            page: 1,      // 页码
+            _state: 0,		// 状态 副本  设置 状态 以 副本为主 的 检定  正本 作为 视图渲染
+            _requestTime: 0,  // 发起请求的 时间
+            _unique:undefined,       // 请求的唯一标示
+            _time:undefined,         // 发起 请求的 时间 记录器
+        }
+    },
+
+    methods:{
+
+        /* 下拉刷新 */
+        refreshHandle:function(e){
+            this.__pull = e;
+            return this.reload(2,true);
+        },
+
+        /* 滚动到底部 */
+        bottom:function(){
+            if ((this._state === 1
+                ||
+                ((this._state===3) && !this.start && this.page !== 1)
+                ||
+                ((this._state===4) && this.start && this.page === 1)
+            )
+            ) return undefined;
+            return this.reload(this._state===4?5:4);
+        },
+
+        /* 重试 */
+        retry:function(e){
+            return this.reload(Number(e.currentTarget.dataset.type));
+        },
+
+        /*
+         * 提供刷新的函数
+         * 	 1  代表 从头开始重新载入
+         * 	 2  代表 从 第一页 开始重新载入 也就是 下拉刷新
+         * 	 3  代表 无状态的 刷新 从 头导入
+         * 	 4  代表 加载下一页
+         * 	 5  代表 刷新当前页
+        * */
+        reload:function(state,must){
+
+            if (state === 1) {
+                this.page = 1;
+                this.setState(0);
+                if (this.mode === 'skeleton') {
+                    this.setSkeletonData();
+                } else {
+                    this.changeData([],false);
+                    if (!this.start) {
+                        this.setStatusApi(true);
+                    }
+                }
+
+            }  else if ( state === 2 || state === 3) {
+                this.page = 1;
+            } else if (state === 4) {
+                this.page++;
+            }
+
+            this._state = 0;
+
+            return this.fetch(must);
+
+        },
+
+        /* 设置page */
+        setPageApi:function(page=1){
+            this.page = page;
+        },
+
+        /* 设置 status */
+        setStatusApi:function(status){
+            if (this.start !== status) {
+                return this.start = status;
+            }
+        },
+
+        /* 设置状态为成功 并重置分页 */
+        setSuccessPageApi:function(page){
+
+            this.page = page;
+            if (this.data.state !== 2) {
+                this.state = 2;
+            }
+
+            if (this.start) {
+                this.start = false;
+            }
+
+
+
+        },
+
+        /* 请求 */
+        fetch:function(must){
+            // 查看当前是否在请求中 和 是否 已经到底了
+            if ((this._state === 1 || (this._state===3 && !this.start && this.page !== 1)) && must !== true ) return undefined;
+
+            /* 设置请求时间 */
+            this._requestTime = this.getNowTime();
+            /* 设置唯一标示 */
+            this._unique = this.getUnique();
+            /* 暂存唯一标示 */
+            let unique = this._unique;
+            if (!(this.start && ( (this._state === 3 || this._state === 4) ) && this.page === 1) || (this._state === 3 || this._state === 4)) {
+                /* 设置状态为加载中 */
+                this.setState(1);
+            } else {
+                this._state = 1;
+            }
+
+
+            return  this.$emit('fetch',{
+                page: this.page,
+                pageSize: this.pageSize,
+                success:(data)=>{
+                    /* 唯一表示保持一致 表示响应 */
+                    if (unique === this._unique) {
+                        unique = null;
+                        return this.success(data);
+                    } else {
+                        unique = null;
+                    }
+                },
+                fail:(error)=>{
+                    /* 唯一表示保持一致 表示响应 */
+                    if (unique === this._unique) {
+                        unique = null;
+                        return this.failHandle(error);
+                    } else {
+                        unique = null;
+                    }
+                }
+            })
+
+        },
+
+        /* 请求成功的函数 */
+        success: function(data=[]){
+
+            let state = 2;
+
+            /* 记录暂无更多 */
+            if (data.length < this.pageSize) {
+                state = 3;
+            }
+
+            if (state=== 2 && !this.pagingStatus) {
+                this.pagingStatus = true;
+            } else if (state === 3 && this.pagingStatus && this.page===1) {
+                this.pagingStatus = false;
+            }
+
+            if (this.start && data.length > 0) {
+                this.start = false;
+            }
+
+            this.changeData(data,false);
+
+            return this.setState(state);
+        },
+
+        // 获取 changeData
+        changeData: function(data,skeleton){
+            /* 设置当前data */
+            return this.$emit('changeData',{
+                data,
+                skeleton,
+                clear: this.page <= 1,
+                position: (this.page - 1) * this.pageSize
+            });
+        },
+
+        /* 失败的函数 */
+        failHandle: function(){
+            return this.setState(4);
+        },
+
+        /* 设置状态
+        * 	渲染规则 注意
+        *  		从 其他 状态 ->  加载中 	需要 延长 async
+        *  		从 加载中  -> 其他 状态    如果 目前处于 加载中 且时长不超过timeout 需要 延长 async 如果 不处于 加载中 将 直接 渲染
+        *
+        * */
+        setState:function (state,draw={}) {
+
+            /* 设置状态 */
+            if (state !== this._state) {
+                /* 清除 */
+                clearTimeout(this._time);
+
+                let beforeState = this._state;
+                this._state = state;
+
+                /* 如果设置加载状态 */
+                if (state === 1) {
+
+                    if (beforeState === 4) {
+                        this.state = 1;
+                    } else {
+                        /* 设置为 请求状态 */
+                        this._time = setTimeout(()=>{
+                            this.state = 1;
+                        },this.async);
+                    }
+
+                } else {
+                    this.state = this._state;
+                    /* 设置其他状态 */
+                    let diff = this.getNowTime() - this._requestTime;
+
+                    /* 如果请求间隔时长 大于 async 且 请求时长 不超过 timeout */
+                    if (diff >= this.async && diff < this.timeout) {
+
+                        this._time = setTimeout(()=>{
+                            if (this.__pull) {
+                                this.__pull();
+                                this.__pull = null;
+                            }
+                        },this.async);
+                    } else {
+                        if (this.__pull) {
+                            this.__pull();
+                            this.__pull = null;
+                        }
+                    }
+
+
+                }
+            }
+
+        },
+
+        /* 获取 当前时间戳 */
+        getNowTime:function () {
+            return new Date().getTime();
+        },
+
+        /* 获取唯一标示 */
+        getUnique: function () {
+            return this._requestTime + '<->' + Math.ceil(Math.random() * 1000);
+        },
+
+        /* 设置骨架模式的数量 */
+        setSkeletonData:function () {
+            let data =[];
+            let count = this.skeleton<0?this.pageSize:this.skeleton;
+            for (let i=0;i< count;i++ ){
+                data.push({});
+            }
+            return this.changeData(data,true);
+        }
+
+    },
+
+    mounted(){
+        if (this.first) {
+            if (this.mode === 'skeleton') {
+                this.setSkeletonData();
+            }
+
+            /* 执行请求 */
+            return this.fetch(true);
+        }
+    }
+
+}

+ 112 - 0
uni-shop/components/flat-list/props.js

@@ -0,0 +1,112 @@
+export default {
+
+	/* 分页器是否存在 */
+	paging: {
+		type: Boolean,
+		default : true
+	},
+
+	/* 一页的数量 */
+	pageSize:{
+		type: Number,
+		default: 10
+	},
+
+	/* 加载模式
+	* 	如果启用 骨架模式 async 默认为 300
+	* 	如果启用 加载模式 async 默认为 300
+	* 	启用 async 代表 设置 状态 将 延迟 设置 防止 请求过快 的 闪现操作
+	*   除非本次请求 发起 到 结束 超过了 timeout 时间 会 自动 注销 async 操作
+	*   注意 async 是为了 防止 程序 渲染 节奏过快 同步 处理 过于 依赖 减少 渲染 频率 降低 绘制次数 减少 请求过快 发生的 诸多问题
+	* */
+	mode:{
+		type: String,
+		default:'loading',  // skeleton 骨架模式 loading 加载模式 none 无加载模式
+	},
+
+	/* 骨架模式默认渲染的数据 默认根据 pageSize 作为数量 渲染 */
+	skeleton:{
+		type: Number,
+		default: -1
+	},
+
+	/* async */
+	async:{
+		type: Number,
+		default: 2000
+	},
+
+	/* 最大请求延长时间 */
+	timeout:{
+		type: Number,
+		default: 1200
+	},
+
+	/* 背景颜色 */
+	background:{
+		type: String,
+		default: '#fff'
+	},
+
+	/* 展示模块的背景颜色 */
+	refresherBackground:{
+		type: String
+	},
+
+	/* 其他的背景颜色 默认使用 background */
+	otherBackground:{
+		type: String
+	},
+
+	/* 是否自定义为空视图 */
+	empty:{
+		type: Boolean,
+		default: false
+	},
+
+	/* 视图为空的提醒 */
+	emptyText:{
+		type: String,
+		default:'暂无记录'
+	},
+
+	/* 二次分页加载 */
+	emptyPaging:{
+		type: String,
+		default:'我也是有底线的'
+	},
+
+	/* 是否自定义错误视图 */
+	fail:{
+		type: Boolean,
+		default: false
+	},
+
+	/* 视图为空的提醒 */
+	failText:{
+		type: String,
+		default:'网络错误,点击重试'
+	},
+
+
+	/* 第一次是否触发 */
+	first:{
+		type: Boolean,
+		default: true
+	},
+
+	/* 下拉刷新 */
+	refresh:{
+		type: Boolean,
+		default: true
+	},
+
+	/* 加载的颜色 */
+	loadingColor:{
+		type:String,
+		default:'#41AE3C'
+	}
+
+
+
+}

+ 106 - 0
uni-shop/components/flat-list/src/main.vue

@@ -0,0 +1,106 @@
+<template>
+  <view class="screen_all relative">
+    <view class="flat-list-absolute" >
+      <!-- 加载模式 -->
+      <view
+          class="flat-list-all absolute center"
+          :style="{
+              'background-color': otherBackground || background
+          }"
+          v-if="start&&mode==='loading'&&state===1"
+      >
+        <view class="flat-load flat-load-animate" :style="{'border-color':loadingColor}"></view>
+      </view>
+      <!-- 视图为空 -->
+      <view
+          class="flat-list-all absolute center"
+          :style="{'background-color':otherBackground || background}"
+          v-else-if="start&&state===3"
+      >
+        <!-- 头部 -->
+        <slot name="header"></slot>
+        <view class="flex-all-1 center">
+          <slot name="empty" v-if="empty"></slot>
+          <template v-else>
+<!--            <icon type="warn" color="rgb(201, 201, 201)" size="80"></icon>-->
+            <text class="flat-empty-text">{{emptyText}}</text>
+          </template>
+        </view>
+        <!-- 最底部 -->
+        <slot name="end-footer"></slot>
+
+      </view>
+      <!-- 网络错误 -->
+      <view
+          class="flat-list-all absolute center"
+          :style="{'background-color':otherBackground || background}"
+          v-else-if="start&&state===4"
+      >
+        <!-- 头部 -->
+        <slot name="header"></slot>
+        <view class="flex-all-1 center">
+          <slot name="fail" v-if="fail"></slot>
+          <template v-else>
+<!--            <icon type="info" color="red" size="80"></icon>-->
+            <text @click="retry" data-type="1" class="flat-empty-text flat-fail-text">{{failText}}</text>
+          </template>
+        </view>
+        <!-- 最底部 -->
+        <slot name="end-footer"></slot>
+
+      </view>
+      <view class="screen_all flex">
+        <view class="flex-all-1 overflow">
+          <refresh-view
+              @refresh="refreshHandle"
+              @bottom="bottom"
+              :refresh="refresh && !start"
+              :paging="paging"
+              :refresherBackground="refresherBackground || background"
+              :background="background"
+          >
+            <view class="flat-list-wrap">
+              <!-- 头部 -->
+              <slot name="header"></slot>
+              <view class="flex-1 relative flex-shrink-0">
+                <!-- 默认展示的插槽 -->
+                <view class="flex-1 relative flex-shrink-0 flat-list-slot">
+                  <slot></slot>
+
+                </view>
+                <!-- 底部 -->
+                <slot name="footer"></slot>
+                <!-- 否则在首次加载完成之后触发 -->
+                <view v-if="paging&&!start&&pagingStatus&&(state===3 && emptyPaging || state!==3)" class="flat-footer center">
+                  <view v-if="state===3" class="flat-footer-text">{{emptyPaging}}</view>
+                  <view v-else-if="state===4" data-type="5" @click="retry" class="flat-footer-text">{{failText}}</view>
+                  <view v-else-if="state===1" class="flat-load flat-load-animate flat-small"></view>
+                </view>
+
+              </view>
+            </view>
+          </refresh-view>
+        </view>
+        <!-- 最底部 -->
+        <slot name="end-footer"></slot>
+      </view>
+
+    </view>
+  </view>
+</template>
+
+<script>
+import props from "../props";
+import mixins from '../mixins';
+import refreshView from '@/components/refresh-view/main.vue';
+export default {
+  name: "flat-list",
+  props,
+  mixins,
+  components:{
+    refreshView
+  }
+}
+</script>
+
+<style scoped lang="scss" src="../style.scss"></style>

+ 81 - 0
uni-shop/components/flat-list/style.scss

@@ -0,0 +1,81 @@
+.flat-list-wrap{
+  display: flex;
+  flex-direction: column;
+  width: 100%;
+  min-height: 100%;
+}
+.flat-list-slot{
+  min-height: 300upx;
+}
+.flat-list-all,.flat-list-absolute{
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  z-index: 9999999;
+}
+.flat-list-absolute{
+  position: absolute;
+}
+.flat-empty-text{
+  font-size: 32upx;
+  margin-top: 20upx;
+  color: #666;
+}
+.flat-fail-text{
+  color: red;
+}
+.flat-load{
+  width: 50upx;
+  height: 50upx;
+  border: 2px solid #41AE3C;
+  border-left-color: transparent !important;
+  border-top-color: transparent !important;
+  border-radius: 50%;
+}
+.flat-small{
+  width: 40upx;
+  height: 40upx;
+}
+.flat-load-animate{
+  animation: rotate .7s infinite linear forwards;
+}
+.flat-footer{
+  height: 80upx;
+}
+.flat-footer-text{
+  font-size: 30upx;
+  color: #666;
+}
+.flex-1{
+  flex-grow: 1;
+}
+
+.flex-shrink-0{
+  flex-shrink: 0;
+}
+.aCenter,.center{
+  align-items: center;
+}
+
+.jCenter,.center{
+  justify-content: center;
+}
+
+.relative{
+  position: relative;
+}
+
+.absolute{
+  position: absolute;
+}
+@keyframes rotate {
+
+  0%{
+    transform: rotate(0);
+  }
+  100%{
+    transform: rotate(360deg);
+  }
+
+}

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

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

Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 6 - 0
uni-shop/components/mix-loading/mix-loading.vue


+ 74 - 0
uni-shop/components/modal-select/main.vue

@@ -0,0 +1,74 @@
+<template>
+  <modal
+    v-model="actualValue"
+    animateContent="translateYR"
+  >
+    <view class="flex modal-select-container">
+      <view  @click="cancel" class="flex-all-1"></view>
+      <view class="modal-select-footer overflow">
+        <template
+            v-for="(item,index) in data"
+        >
+          <button :key="index" class="clear-button modal-select-item modal-select-for-item center"
+                @click.stop="trigger(item)"
+                :open-type="item.openType"
+                  v-if="item.type === 'button'"
+          >
+            {{item.label}}
+          </button>
+          <view v-else class="modal-select-item modal-select-for-item center"
+
+                :key="index"
+                @click.stop="trigger(item)"
+          >{{item.label}}</view>
+        </template>
+
+        <view class="modal-select-margin"></view>
+        <view @click="cancel" class="modal-select-item center">取消</view>
+      </view>
+    </view>
+  </modal>
+</template>
+
+<script>
+import modal from '@/components/modal/main.vue';
+import props from './props';
+export default {
+  name: "modal-select",
+  components:{
+    modal
+  },
+  data(){
+    return {
+      actualValue:false
+    }
+  },
+
+  watch:{
+    value:function () {
+      this.setValue(this.value);
+    }
+  },
+
+  methods:{
+    setValue(value){
+      if (this.actualValue !== value) {
+        this.actualValue = value;
+        if (this.value !== value) {
+          this.$emit('input',value);
+        }
+      }
+    },
+    cancel(){
+      return this.setValue(false);
+    },
+    trigger(item){
+      this.$emit('trigger',item);
+    }
+  },
+
+  props
+}
+</script>
+
+<style scoped lang="scss" src="./style.scss"></style>

+ 15 - 0
uni-shop/components/modal-select/props.js

@@ -0,0 +1,15 @@
+export default {
+
+    value:{
+      type:Boolean,
+      default: false
+    },
+
+    data:{
+        type:Array,
+        default:function () {
+            return [];
+        }
+    }
+
+}

+ 26 - 0
uni-shop/components/modal-select/style.scss

@@ -0,0 +1,26 @@
+/* 容器 */
+.modal-select-container{
+  width: 100%;
+  height: 100%;
+}
+.modal-select-footer{
+  background-color: #fff;
+  border-radius: 30upx 30upx 0 0;
+}
+.modal-select-margin{
+  height: 10upx;
+  background-color: #F1F1F1;
+}
+.modal-select-for-item{
+  border-bottom: 1upx solid #EEEEEE !important;
+}
+.modal-select-for-item:last-of-type{
+  border-bottom: none;
+}
+.modal-select-item{
+  height: 87upx !important;
+  font-size: 30upx;
+  line-height: 40upx;
+  color: #333;
+}
+/* 容器 */

+ 34 - 0
uni-shop/components/modal/main.vue

@@ -0,0 +1,34 @@
+<template>
+  <view
+      class="modal"
+      :class="[animateClass]"
+      :hidden="!actualValue"
+      :style="{
+        'pointer-events':pointerEvents?'none':'',
+        transition: duration+'ms ' +animateType,
+        'background-color': transparent?'transparent':background
+      }"
+  >
+    <view
+        class="modal-content"
+        :class="[animateContentClass]"
+        :style="{
+          'transition':(contentDuration || duration)+'ms '+(animateContentType || animateType),
+        }"
+    >
+      <slot></slot>
+    </view>
+  </view>
+</template>
+
+<script>
+import mixins from './mixins/index';
+import props from "./props";
+export default {
+  name: "modal",
+  mixins,
+  props
+}
+</script>
+
+<style scoped lang="scss" src="./style.scss"></style>

+ 178 - 0
uni-shop/components/modal/mixins/handle.js

@@ -0,0 +1,178 @@
+export default {
+
+    data:function (){
+        return {
+            animateClass:'',
+            animateContentClass:'',
+
+            actualValue: false,
+
+            _animateClass:[],
+            _animateContentClass:[],
+            drawDiffTime: 5,
+            _value_set:false
+        }
+    },
+
+    watch:{
+        /* 每次value 变更 触发修改class */
+        'value':function (value,oldValue) {
+            if (this._value_set) {
+                return this.createDrawAnimateClass();
+            }
+        },
+        /* 每次修改发生变化的类型触发 变更 触发修改class */
+        'animate':function () {
+            return this.installAnimateClass();
+        },
+        'animateContent':function () {
+            return this.installAnimateClass();
+        }
+    },
+
+    methods:{
+
+        toggle:function () {
+            this.value = !this.value;
+        },
+
+        /* 构造渲染class */
+        createDrawAnimateClass:function(){
+
+
+            if (this.animate){
+
+                /*
+                * 	如果存在 计时器 清理掉
+                * */
+                if ('childrenDrawCallback' in this)  clearTimeout(this.childrenDrawCallback);
+                if ('watchEndTime' in this) clearTimeout(this.watchEndTime);
+
+                let draw = {};
+                let state = this.value ?'-show':'-hide';
+                let status = this.value;
+                let hasContentStatus = this.animateContent || this.animateExtend;
+                draw['animateClass'] = this._animateClass.map((item)=> item+state).join(',');
+                if (hasContentStatus) {
+                    draw['animateContentClass'] = this._animateContentClass.map((item)=> item+state).join(',');
+                }
+
+
+                this.actualValue = true;
+
+                setTimeout(()=>{
+                    if (this.async) {
+
+                        // 根据当前设置默认渲染的 情况
+                        let key = hasContentStatus? status?'animateClass':'animateContentClass':'animateClass';
+
+                        this[key] = draw[key];
+
+                        let nextChildren = hasContentStatus ? status ? 'animateContentClass' : 'animateClass' : false;
+
+                        // 如果存在下一个 子级
+                        if (nextChildren) {
+                            /*
+                            * 	统计过渡时间
+                            * */
+                            let contentDuration = this.contentDuration || this.duration;
+                            if (this.async && this.asyncDiff) contentDuration = this.asyncDiff;
+
+                            let duration = status ? this.duration : contentDuration;
+
+                            this.childrenDrawCallback = setTimeout( () => {
+                                this[nextChildren] = draw[nextChildren];
+                            },duration + this.drawDiffTime);
+                        }
+                    } else {
+
+                        for (let key in draw) {
+                            if (draw.hasOwnProperty(key)) {
+                                this[key] = draw[key];
+                            }
+                        }
+                    }
+                    /* 装载 */
+                    this.animateEndWatch();
+                },13);
+
+
+            }
+
+        },
+
+        /* 装载记录结束瞬间 */
+        animateEndWatch:function(){
+
+            if (!this.value) {
+
+                let hasContentStatus = this.animateContent || this.animateExtend;
+                let contentDuration = this.contentDuration || this.duration;
+
+                if (this.async && this.asyncDiff) contentDuration = this.asyncDiff;
+
+                let duration = (hasContentStatus ? ( this.duration + contentDuration ) : this.duration) + this.drawDiffTime;
+
+                if (hasContentStatus)  {
+                    duration += this.drawDiffTime;
+                }
+
+
+                this.watchEndTime = setTimeout(()=>{
+                    if (this.actualValue === true) this.actualValue = false;
+                },duration);
+            }
+
+
+        },
+
+        /* 安装class */
+        installAnimateClass:function(){
+            if (this.animate) {
+                this._animateClass = this.createAnimateClass(this.animate);
+                if (this.animateContent || this.animateExtend) {
+                    this._animateContentClass = this.createAnimateClass(this.animateContent || this.animateExtend && this.animate);
+                }
+            }
+        },
+        /* 创建动画名称 class */
+        createAnimateClass:function (string) {
+            if (string) {
+                return (string.split(',') || []).map((item)=> 'animate-'+item);
+            } else {
+                return [];
+            }
+
+        },
+
+        /* 初始化 */
+        install:function () {
+            this.installAnimateClass();
+            if (this.actualValue !== this.value) {
+                this.createDrawAnimateClass();
+            } else {
+                let draw = {};
+                let state = this.value ?'-show':'-hide';
+                let hasContentStatus = this.animateContent || this.animateExtend;
+                draw['animateClass'] = this._animateClass.map((item)=> item+state).join(',');
+                if (hasContentStatus) {
+                    draw['animateContentClass'] = this._animateContentClass.map((item)=> item+state).join(',');
+                }
+
+                for (let key in draw) {
+                    if (draw.hasOwnProperty(key)) {
+                        this[key] = draw[key];
+                    }
+                }
+
+            }
+            this._value_set = true;
+        }
+
+    },
+
+    mounted(){
+        this.install();
+    }
+
+}

+ 3 - 0
uni-shop/components/modal/mixins/index.js

@@ -0,0 +1,3 @@
+import handle from "./handle";
+
+export default [handle];

+ 130 - 0
uni-shop/components/modal/props.js

@@ -0,0 +1,130 @@
+export default {
+
+	value:{
+		type:Boolean,
+		default:false
+	},
+
+	/*
+	* 	过渡时间
+	* */
+	duration:{
+		type: Number,
+		default: 300
+	},
+
+	/*
+	* 	内部元素过渡动画时间
+	* 		默认启用 	duration
+	* */
+	contentDuration:{
+		type: Number
+	},
+
+
+	/*
+	* 	背景色
+	* */
+	background:{
+		type: String,
+		default:'rgba(0,0,0,0.5)'
+	},
+
+	/*
+	* 	是否透明
+	* */
+	transparent:{
+		type: Boolean,
+		default: false
+	},
+
+
+	/*
+	* 	动画类型
+	* 			opacity
+	* 			scale
+	* 			translateX
+	* 			translateXR
+	* 			translateY
+	* 			translateYR
+	* 			skew
+	* 			skewR
+	* 			skewY
+	* 			skewYR
+	* 			rotate
+	* 			rotateX
+	* 			rotateY
+	* 		允许指定多个动画
+	*			scale,opacity 例如
+	* */
+	animate:{
+		type: String,
+		default: 'opacity'
+	},
+
+	/*
+	* 	动画类型
+	* */
+	animateType:{
+		type:String,
+		default:'linear'
+	},
+
+	/*
+	* 	内部动画
+	* */
+	animateContent:{
+		type: String,
+		default:'scale'
+	},
+
+	/*
+	* 	内部动画类型 默认 启用 animateType
+	* */
+	animateContentType:{
+		type:String
+	},
+
+	/*
+	* 	如果不填写 animateContent 默认不继承动画
+	* */
+	animateExtend:{
+		type: Boolean,
+		default: true
+	},
+
+	/*
+	* 	弹窗
+	* */
+	default:{
+		type: Boolean,
+		default: false
+	},
+
+	/*
+	* 	同步或者异步渲染 默认为 同步
+	* */
+	async:{
+		type: Boolean,
+		default:false
+	},
+
+	/*
+	* 	如果为 异步 延迟 多久 便可以执行下一个动画
+	* */
+	asyncDiff:{
+		type: Number
+	},
+
+	/*
+	* 是否允许事件穿透
+	* */
+	pointerEvents:{
+		type: Boolean,
+		default: false
+	}
+
+
+};
+
+

+ 146 - 0
uni-shop/components/modal/style.scss

@@ -0,0 +1,146 @@
+.modal{
+    position: absolute;
+    top: 0;
+    bottom: 0;
+    right: 0;
+    left: 0;
+    overflow: hidden;
+    z-index: 9999999999999999;
+}
+
+.modal-content{
+    width: 100%;
+    height: 100%;
+}
+
+/*
+    opacity 配置
+*/
+.animate-opacity-show {
+    opacity: 1;
+    visibility: initial;
+}
+.animate-opacity-hide {
+    opacity: 0;
+    visibility: hidden;
+}
+
+/*
+    scale 配置
+*/
+.animate-scale-show {
+    transform: scale(1);
+}
+.animate-scale-hide {
+    transform: scale(0);
+}
+/*
+    translateX  配置
+*/
+.animate-translateX-show,.animate-translateXR-show {
+    transform: translateX(0);
+}
+
+.animate-translateX-hide {
+    transform: translateX(-100%);
+}
+
+/*
+    translateXR  配置
+*/
+.animate-translateXR-hide {
+    transform: translateX(100%);
+}
+
+
+/*
+    translateY  配置
+*/
+.animate-translateX-show,.animate-translateYR-show {
+    transform: translateY(0);
+}
+
+.animate-translateY-hide {
+    transform: translateY(-100%);
+}
+
+/*
+    translateYR  配置
+*/
+.animate-translateYR-hide {
+    transform: translateY(100%);
+}
+
+/*
+    skew
+*/
+.animate-skew-show,.animate-skewR-show {
+    transform: skew(0);
+}
+
+.animate-skew-hide {
+    transform: skew(90deg);
+}
+
+
+/*
+    skewR
+*/
+
+.animate-skewR-hide {
+    transform: skew(-90deg);
+}
+
+
+/*
+    skewY
+*/
+.animate-skewY-show,.animate-skewYR-show {
+    transform: skewY(0);
+}
+
+.animate-skewY-hide {
+    transform: skewY(90deg);
+}
+
+
+/*
+    skewY
+*/
+
+.animate-skewYR-hide {
+    transform: skewY(-90deg);
+}
+
+
+/*
+    rotate
+*/
+.animate-rotate-show {
+    transform: rotate(0);
+}
+.animate-rotate-hide {
+    transform: rotate(360deg);
+}
+
+/*
+    rotateX
+*/
+.animate-rotateX-show {
+    transform: rotateX(0);
+}
+.animate-rotateX-hide {
+    transform: rotateX(90deg);
+}
+
+/*
+    rotateY
+*/
+.animate-rotateY-show {
+    transform: rotateY(0);
+}
+.animate-rotateY-hide {
+    transform: rotateY(90deg);
+}
+
+

+ 32 - 0
uni-shop/components/refresh-view/main.vue

@@ -0,0 +1,32 @@
+<template>
+  <view class="refresh-view-screen relative" :style="{backgroundColor:background}">
+    <view class="refresh-view-screen-absolute" id="scroll-height">
+      <scroll-view
+          class="refresh-view-screen"
+          :scroll-y="true"
+          refresher-threshold="threshold"
+          :refresher-background="refresherBackground"
+          :refresher-enabled="refresh"
+          :refresher-triggered="triggered"
+          @scroll="scroll"
+          @refresherrefresh="refreshHandle"
+      >
+        <slot></slot>
+      </scroll-view>
+    </view>
+  </view>
+</template>
+
+<script>
+import mixins from './mixins';
+import props from './props';
+export default {
+  name: "refresh-view",
+  mixins,
+  props
+}
+</script>
+
+<style scoped lang="scss" src="./style.scss">
+
+</style>

+ 69 - 0
uni-shop/components/refresh-view/mixins/handle.js

@@ -0,0 +1,69 @@
+export default {
+
+    data(){
+        return {
+            triggered: false,
+            scrollTop:0,
+            look:false
+        }
+    },
+
+    methods:{
+
+        /* 开启下拉刷新的时候触发 */
+        refreshHandle:function () {
+            this.triggered = true;
+            return this.$emit('refresh',this.stop);
+        },
+
+        /* 停止下拉刷新 */
+        stop:function () {
+            if (this.triggered) {
+                return this.triggered = false;
+            }
+        },
+
+        getHeight(){
+            if(this.look) return;
+            this.look = true;
+
+            var query = uni.createSelectorQuery().in(this);
+            query.select('#scroll-height').boundingClientRect();
+            query.exec((res) => {
+                if (res && res[0]) {
+                    this.height = res[0].height
+                }
+                this.look = false;
+            })
+        },
+
+        scroll(e) {
+            if (!this.paging || this.bottomLoading) return;
+
+            let detail = e.detail;
+            let diff = detail.scrollHeight - detail.scrollTop - this.height;
+            if (this.height <= 0) { this.getHeight() }
+
+            if (diff <= this.threshold)  {
+                this.bottomLoading = true;
+                this.$emit('bottom');
+                return setTimeout(()=>{
+                    this.bottomLoading = false;
+                },500);
+            }
+        }
+
+
+    },
+
+    created() {
+        this.height = 0;
+        this.stop = this.stop.bind(this);
+    },
+    mounted() {
+
+        this.getHeight();
+
+    }
+
+}

+ 3 - 0
uni-shop/components/refresh-view/mixins/index.js

@@ -0,0 +1,3 @@
+import handle from "./handle";
+
+export default [handle];

+ 54 - 0
uni-shop/components/refresh-view/props.js

@@ -0,0 +1,54 @@
+import unit from '../../utils/tool/unit';
+export default {
+
+
+	/* 刷新模块的高度 */
+	threshold:{
+		type: Number,
+		default: 150
+	},
+
+	/* 全局的 背景颜色 注意 下拉刷新也使用了 该颜色 */
+	background:{
+		type: String,
+		default: '#fff'
+	},
+
+	/* 展示模块的背景颜色 */
+	refresherBackground:{
+		type: String,
+		default:'#fff'
+	},
+
+	/* 样式类型 */
+	style:{
+		type: String,
+		default:'black'
+	},
+
+	/* 距离底部多远触发更新 */
+	lower:{
+		type: Number,
+		default: unit(150),
+	},
+
+	/*  */
+	absolute:{
+		type: Boolean,
+		default: false
+	},
+
+	/* 是否支持下拉刷新 */
+	refresh:{
+		type: Boolean,
+		default: true
+	},
+
+	/*  是否到达底部通知  */
+	paging:{
+		type: Boolean,
+		default: true
+	}
+
+
+}

+ 28 - 0
uni-shop/components/refresh-view/style.scss

@@ -0,0 +1,28 @@
+.flex-1{
+  flex: 1;
+}
+.relative{
+  position: relative;
+  z-index: 5;
+}
+.refresh-view-screen{
+  width: 100%;
+  height: 100%;
+}
+.refresh-view-screen-absolute{
+  position: absolute;
+  top: 0;
+  bottom: 0;
+  left: 0;
+  right: 0;
+}
+
+:host{
+  flex:1;
+  display: flex;
+  flex-direction: column;
+}
+.flex{
+  display: flex;
+  flex-direction: column;
+}

+ 202 - 0
uni-shop/components/share.vue

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

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

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

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

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

Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 10 - 0
uni-shop/components/uni-icons/uni-icons.vue


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

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

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

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

binární
uni-shop/components/uni-rate/images/rate-active.png


binární
uni-shop/components/uni-rate/images/rate.png


+ 137 - 0
uni-shop/components/uni-rate/uni-rate.vue

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

+ 245 - 0
uni-shop/components/uni-swipe-action-item/bindingx.js

@@ -0,0 +1,245 @@
+const BindingX = uni.requireNativePlugin('bindingx');
+const dom = uni.requireNativePlugin('dom');
+const animation = uni.requireNativePlugin('animation');
+
+export default {
+	data() {
+		return {
+			right: 0,
+			button: [],
+			preventGesture: false
+		}
+	},
+
+	watch: {
+		show(newVal) {
+			if (!this.position || JSON.stringify(this.position) === '{}') return;
+			if (this.autoClose) return
+			if (this.isInAnimation) return
+			if (newVal) {
+				this.open()
+			} else {
+				this.close()
+			}
+		},
+	},
+	created() {
+		if (this.swipeaction.children !== undefined) {
+			this.swipeaction.children.push(this)
+		}
+	},
+	mounted() {
+		this.boxSelector = this.getEl(this.$refs['selector-box-hock']);
+		this.selector = this.getEl(this.$refs['selector-content-hock']);
+		this.buttonSelector = this.getEl(this.$refs['selector-button-hock']);
+		this.position = {}
+		this.x = 0
+		setTimeout(() => {
+			this.getSelectorQuery()
+		}, 200)
+	},
+	beforeDestroy() {
+		if (this.timing) {
+			BindingX.unbind({
+				token: this.timing.token,
+				eventType: 'timing'
+			})
+		}
+		if (this.eventpan) {
+			BindingX.unbind({
+				token: this.eventpan.token,
+				eventType: 'pan'
+			})
+		} 
+		this.swipeaction.children.forEach((item, index) => {
+			if (item === this) {
+				this.swipeaction.children.splice(index, 1)
+			}
+		})
+	},
+	methods: {
+		onClick(index, item) {
+			this.$emit('click', {
+				content: item,
+				index
+			})
+		},
+		touchstart(e) {
+			if (this.isInAnimation) return
+			if (this.stop) return
+			this.stop = true
+			if (this.autoClose) {
+				this.swipeaction.closeOther(this)
+			}
+			let endWidth = this.right
+			let boxStep = `(x+${this.x})`
+			let pageX = `${boxStep}> ${-endWidth} && ${boxStep} < 0?${boxStep}:(x+${this.x} < 0? ${-endWidth}:0)`
+
+			let props = [{
+				element: this.selector,
+				property: 'transform.translateX',
+				expression: pageX
+			}]
+
+			let left = 0
+			for (let i = 0; i < this.options.length; i++) {
+				let buttonSelectors = this.getEl(this.$refs['button-hock'][i]);
+				if (this.button.length === 0 || !this.button[i] || !this.button[i].width) return
+				let moveMix = endWidth - left
+				left += this.button[i].width
+				let step = `(${this.x}+x)/${endWidth}`
+				let moveX = `(${step}) * ${moveMix}`
+				let pageButtonX = `${moveX}&& (x+${this.x} > ${-endWidth})?${moveX}:${-moveMix}`
+				props.push({
+					element: buttonSelectors,
+					property: 'transform.translateX',
+					expression: pageButtonX
+				})
+			}
+
+			this.eventpan = this._bind(this.boxSelector, props, 'pan', (e) => {
+				if (e.state === 'end') {
+					this.x = e.deltaX + this.x;
+					if (this.x < -endWidth) {
+						this.x = -endWidth
+					}
+					if (this.x > 0) {
+						this.x = 0
+					}
+					this.stop = false
+					this.bindTiming();
+				}
+			})
+		},
+		touchend(e) {
+			this.$nextTick(() => {
+				if (this.isopen && !this.isDrag && !this.isInAnimation) {
+					this.close()
+				}
+			})
+		},
+		bindTiming() {
+			if (this.isopen) {
+				this.move(this.x, -this.right)
+			} else {
+				this.move(this.x, -40)
+			}
+		},
+		move(left, value) {
+			if (left >= value) {
+				this.close()
+			} else {
+				this.open()
+			}
+		},
+		/**
+		 * 开启swipe
+		 */
+		open() {
+			this.animation(true)
+		},
+		/**
+		 * 关闭swipe
+		 */
+		close() {
+			this.animation(false)
+		},
+		/**
+		 * 开启关闭动画
+		 * @param {Object} type
+		 */
+		animation(type) {
+			this.isDrag = true
+			let endWidth = this.right
+			let time = 200
+			this.isInAnimation = true;
+
+			let exit = `t>${time}`;
+			let translate_x_expression = `easeOutExpo(t,${this.x},${type?(-endWidth-this.x):(-this.x)},${time})`
+			let props = [{
+				element: this.selector,
+				property: 'transform.translateX',
+				expression: translate_x_expression
+			}]
+
+			let left = 0
+			for (let i = 0; i < this.options.length; i++) {
+				let buttonSelectors = this.getEl(this.$refs['button-hock'][i]);
+				if (this.button.length === 0 || !this.button[i] || !this.button[i].width) return
+				let moveMix = endWidth - left
+				left += this.button[i].width
+				let step = `${this.x}/${endWidth}`
+				let moveX = `(${step}) * ${moveMix}`
+				let pageButtonX = `easeOutExpo(t,${moveX},${type ? -moveMix + '-' + moveX: 0 + '-' + moveX},${time})`
+				props.push({
+					element: buttonSelectors,
+					property: 'transform.translateX',
+					expression: pageButtonX
+				})
+			}
+
+			this.timing = BindingX.bind({
+				eventType: 'timing',
+				exitExpression: exit,
+				props: props
+			}, (e) => {
+				if (e.state === 'end' || e.state === 'exit') {
+					this.x = type ? -endWidth : 0
+					this.isInAnimation = false;
+
+					this.isopen = this.isopen || false
+					if (this.isopen !== type) {
+						this.$emit('change', type)
+					}
+					this.isopen = type
+					this.isDrag = false
+				}
+			});
+		},
+		/**
+		 * 绑定  BindingX
+		 * @param {Object} anchor
+		 * @param {Object} props
+		 * @param {Object} fn
+		 */
+		_bind(anchor, props, eventType, fn) {
+			return BindingX.bind({
+				anchor,
+				eventType,
+				props
+			}, (e) => {
+				typeof(fn) === 'function' && fn(e)
+			});
+		},
+		/**
+		 * 获取ref
+		 * @param {Object} el
+		 */
+		getEl(el) {
+			return el.ref
+		},
+		/**
+		 * 获取节点信息
+		 */
+		getSelectorQuery() {
+			dom.getComponentRect(this.$refs['selector-content-hock'], (data) => {
+				if (this.position.content) return
+				this.position.content = data.size
+			})
+			for (let i = 0; i < this.options.length; i++) {
+				dom.getComponentRect(this.$refs['button-hock'][i], (data) => {
+					if (!this.button) {
+						this.button = []
+					}
+					if (this.options.length === this.button.length) return
+					this.button.push(data.size)
+					this.right += data.size.width
+					if (this.autoClose) return
+					if (this.show) {
+						this.open()
+					}
+				})
+			}
+		}
+	}
+}

+ 204 - 0
uni-shop/components/uni-swipe-action-item/index.wxs

@@ -0,0 +1,204 @@
+/**
+ * 监听页面内值的变化,主要用于动态开关swipe-action
+ * @param {Object} newValue
+ * @param {Object} oldValue
+ * @param {Object} ownerInstance
+ * @param {Object} instance
+ */
+function sizeReady(newValue, oldValue, ownerInstance, instance) {
+	var state = instance.getState()
+	state.position = JSON.parse(newValue)
+	if (!state.position || state.position.length === 0) return
+	var show = state.position[0].show
+	state.left = state.left || state.position[0].left;
+	// 通过用户变量,开启或关闭
+	if (show) {
+		openState(true, instance, ownerInstance)
+	} else {
+		openState(false, instance, ownerInstance)
+	}
+}
+
+/**
+ * 开始触摸操作
+ * @param {Object} e
+ * @param {Object} ins
+ */
+function touchstart(e, ins) {
+	var instance = e.instance;
+	var state = instance.getState();
+	var pageX = e.touches[0].pageX;
+	// 开始触摸时移除动画类
+	instance.removeClass('ani');
+	var owner = ins.selectAllComponents('.button-hock')
+	for (var i = 0; i < owner.length; i++) {
+		owner[i].removeClass('ani');
+	}
+	// state.position = JSON.parse(instance.getDataset().position);
+	state.left = state.left || state.position[0].left;
+	// 获取最终按钮组的宽度
+	state.width = pageX - state.left;
+	ins.callMethod('closeSwipe')
+}
+
+/**
+ * 开始滑动操作
+ * @param {Object} e
+ * @param {Object} ownerInstance
+ */
+function touchmove(e, ownerInstance) {
+	var instance = e.instance;
+	var disabled = instance.getDataset().disabled
+	var state = instance.getState()
+	// fix by mehaotian, TODO 兼容 app-vue 获取dataset为字符串 , h5 获取 为 undefined 的问题,待框架修复
+	disabled = (typeof(disabled) === 'string' ? JSON.parse(disabled) : disabled) || false;
+
+	if (disabled) return
+	var pageX = e.touches[0].pageX;
+	move(pageX - state.width, instance, ownerInstance)
+}
+
+/**
+ * 结束触摸操作
+ * @param {Object} e
+ * @param {Object} ownerInstance
+ */
+function touchend(e, ownerInstance) {
+	var instance = e.instance;
+	var disabled = instance.getDataset().disabled
+	var state = instance.getState()
+
+	// fix by mehaotian, TODO 兼容 app-vue 获取dataset为字符串 , h5 获取 为 undefined 的问题,待框架修复
+	disabled = (typeof(disabled) === 'string' ? JSON.parse(disabled) : disabled) || false;
+
+	if (disabled) return
+	// 滑动过程中触摸结束,通过阙值判断是开启还是关闭
+	// fixed by mehaotian 定时器解决点击按钮,touchend 触发比 click 事件时机早的问题 ,主要是 ios13
+	moveDirection(state.left, -40, instance, ownerInstance)
+}
+
+/**
+ * 设置移动距离
+ * @param {Object} value
+ * @param {Object} instance
+ * @param {Object} ownerInstance
+ */
+function move(value, instance, ownerInstance) {
+	var state = instance.getState()
+	// 获取可滑动范围
+	var x = Math.max(-state.position[1].width, Math.min((value), 0));
+	state.left = x;
+	instance.setStyle({
+		transform: 'translateX(' + x + 'px)',
+		'-webkit-transform': 'translateX(' + x + 'px)'
+	})
+	// 折叠按钮动画
+	buttonFold(x, instance, ownerInstance)
+}
+
+/**
+ * 移动方向判断
+ * @param {Object} left
+ * @param {Object} value
+ * @param {Object} ownerInstance
+ * @param {Object} ins
+ */
+function moveDirection(left, value, ins, ownerInstance) {
+	var state = ins.getState()
+	var position = state.position
+	var isopen = state.isopen
+	if (!position[1].width) {
+		openState(false, ins, ownerInstance)
+		return
+	}
+	// 如果已经是打开状态,进行判断是否关闭,还是保留打开状态
+	if (isopen) {
+		if (-left <= position[1].width) {
+			openState(false, ins, ownerInstance)
+		} else {
+			openState(true, ins, ownerInstance)
+		}
+		return
+	}
+	// 如果是关闭状态,进行判断是否打开,还是保留关闭状态
+	if (left <= value) {
+		openState(true, ins, ownerInstance)
+	} else {
+		openState(false, ins, ownerInstance)
+	}
+}
+
+/**
+ * 设置按钮移动距离
+ * @param {Object} value
+ * @param {Object} instance
+ * @param {Object} ownerInstance
+ */
+function buttonFold(value, instance, ownerInstance) {
+	var ins = ownerInstance.selectAllComponents('.button-hock');
+	var state = instance.getState();
+	var position = state.position;
+	var arr = [];
+	var w = 0;
+	for (var i = 0; i < ins.length; i++) {
+		if (!ins[i].getDataset().button) return
+		var btnData = JSON.parse(ins[i].getDataset().button)
+
+		// fix by mehaotian TODO 在 app-vue 中,字符串转对象,需要转两次,这里先这么兼容
+		if (typeof(btnData) === 'string') {
+			btnData = JSON.parse(btnData)
+		}
+
+		var button = btnData[i] && btnData[i].width || 0
+		w += button
+		arr.push(-w)
+		// 动态计算按钮组每个按钮的折叠动画移动距离
+		var distance = arr[i - 1] + value * (arr[i - 1] / position[1].width)
+		if (i != 0) {
+			ins[i].setStyle({
+				transform: 'translateX(' + distance + 'px)',
+			})
+		}
+	}
+}
+
+/**
+ * 开启状态
+ * @param {Boolean} type
+ * @param {Object} ins
+ * @param {Object} ownerInstance
+ */
+function openState(type, ins, ownerInstance) {
+	var state = ins.getState()
+	var position = state.position
+	if (state.isopen === undefined) {
+		state.isopen = false
+	}
+	// 只有状态有改变才会通知页面改变状态
+	if (state.isopen !== type) {
+		// 通知页面,已经打开
+		ownerInstance.callMethod('change', {
+			open: type
+		})
+	}
+	// 设置打开和移动状态
+	state.isopen = type
+
+
+	// 添加动画类
+	ins.addClass('ani');
+	var owner = ownerInstance.selectAllComponents('.button-hock')
+	for (var i = 0; i < owner.length; i++) {
+		owner[i].addClass('ani');
+	}
+	// 设置最终移动位置
+	move(type ? -position[1].width : 0, ins, ownerInstance)
+
+}
+
+module.exports = {
+	sizeReady: sizeReady,
+	touchstart: touchstart,
+	touchmove: touchmove,
+	touchend: touchend
+}

+ 160 - 0
uni-shop/components/uni-swipe-action-item/mpalipay.js

@@ -0,0 +1,160 @@
+export default {
+	data() {
+		return {
+			isshow: false,
+			viewWidth: 0,
+			buttonWidth: 0,
+			disabledView: false,
+			x: 0,
+			transition: false
+		}
+	},
+	watch: {
+		show(newVal) {
+			if (this.autoClose) return
+			if (newVal) {
+				this.open()
+			} else {
+				this.close()
+			}
+		},
+	},
+	created() {
+		if (this.swipeaction.children !== undefined) {
+			this.swipeaction.children.push(this)
+		}
+	},
+	beforeDestroy() {
+		this.swipeaction.children.forEach((item, index) => {
+			if (item === this) {
+				this.swipeaction.children.splice(index, 1)
+			}
+		})
+	},
+	mounted() {
+		this.isopen = false
+		this.transition = true
+		setTimeout(() => {
+			this.getQuerySelect()
+		}, 50)
+
+	},
+	methods: {
+		onClick(index, item) {
+			this.$emit('click', {
+				content: item,
+				index
+			})
+		},
+		touchstart(e) {
+			let {
+				pageX,
+				pageY
+			} = e.changedTouches[0]
+			this.transition = false
+			this.startX = pageX
+			if (this.autoClose) {
+				this.swipeaction.closeOther(this)
+			}
+		},
+		touchmove(e) {
+			let {
+				pageX,
+			} = e.changedTouches[0]
+			this.slide = this.getSlide(pageX)
+			if (this.slide === 0) {
+				this.disabledView = false
+			}
+
+		},
+		touchend(e) {
+			this.stop = false
+			this.transition = true
+			if (this.isopen) {
+				if (this.moveX === -this.buttonWidth) {
+					this.close()
+					return
+				}
+				this.move()
+			} else {
+				if (this.moveX === 0) {
+					this.close()
+					return
+				}
+				this.move()
+			}
+		},
+		open() {
+			this.x = this.moveX
+			this.$nextTick(() => {
+				this.x = -this.buttonWidth
+				this.moveX = this.x
+				
+				if(!this.isopen){
+					this.isopen = true
+					this.$emit('change', true)
+				}
+			})
+		},
+		close() {
+			this.x = this.moveX
+			this.$nextTick(() => {
+				this.x = 0
+				this.moveX = this.x
+				if(this.isopen){
+					this.isopen = false
+					this.$emit('change', false)
+				}
+			})
+		},
+		move() {
+			if (this.slide === 0) {
+				this.open()
+			} else {
+				this.close()
+			}
+		},
+		onChange(e) {
+			let x = e.detail.x
+			this.moveX = x
+			if (x >= this.buttonWidth) {
+				this.disabledView = true
+				this.$nextTick(() => {
+					this.x = this.buttonWidth
+				})
+			}
+		},
+		getSlide(x) {
+			if (x >= this.startX) {
+				this.startX = x
+				return 1
+			} else {
+				this.startX = x
+				return 0
+			}
+
+		},
+		getQuerySelect() {
+			const query = uni.createSelectorQuery().in(this);
+			query.selectAll('.viewWidth-hook').boundingClientRect(data => {
+
+				this.viewWidth = data[0].width
+				this.buttonWidth = data[1].width
+				this.transition = false
+				this.$nextTick(() => {
+					this.transition = true
+				})
+
+				if (!this.buttonWidth) {
+					this.disabledView = true
+				}
+
+				if (this.autoClose) return
+				if (this.show) {
+					this.open()
+				}
+			}).exec();
+
+		}
+	}
+}

+ 158 - 0
uni-shop/components/uni-swipe-action-item/mpother.js

@@ -0,0 +1,158 @@
+// #ifdef APP-NVUE
+const dom = weex.requireModule('dom');
+// #endif
+export default {
+	data() {
+		return {
+			uniShow: false,
+			left: 0
+		}
+	},
+	computed: {
+		moveLeft() {
+			return `translateX(${this.left}px)`
+		}
+	},
+	watch: {
+		show(newVal) {
+			if (!this.position || JSON.stringify(this.position) === '{}') return;
+			if (this.autoClose) return
+			if (newVal) {
+				this.$emit('change', true)
+				this.open()
+			} else {
+				this.$emit('change', false)
+				this.close()
+			}
+		}
+	},
+	mounted() {
+		this.position = {}
+		if (this.swipeaction.children !== undefined) {
+			this.swipeaction.children.push(this)
+		}
+		setTimeout(() => {
+			this.getSelectorQuery()
+		}, 100)
+	},
+	beforeDestoy() {
+		this.swipeaction.children.forEach((item, index) => {
+			if (item === this) {
+				this.swipeaction.children.splice(index, 1)
+			}
+		})
+	},
+	methods: {
+		onClick(index, item) {
+			this.$emit('click', {
+				content: item,
+				index
+			})
+			this.close()
+		},
+		touchstart(e) {
+			const {
+				pageX
+			} = e.touches[0]
+			if (this.disabled) return
+			const left = this.position.content.left
+			if (this.autoClose) {
+				this.swipeaction.closeOther(this)
+			}
+			this.width = pageX - left
+			if (this.isopen) return
+			if (this.uniShow) {
+				this.uniShow = false
+				this.isopen = true
+				this.openleft = this.left + this.position.button.width
+			}
+		},
+		touchmove(e, index) {
+			if (this.disabled) return
+			const {
+				pageX
+			} = e.touches[0]
+			this.setPosition(pageX)
+		},
+		touchend() {
+			if (this.disabled) return
+			if (this.isopen) {
+				this.move(this.openleft, 0)
+				return
+			}
+			this.move(this.left, -40)
+		},
+		setPosition(x, y) {
+			if (!this.position.button.width) {
+				return
+			}
+			// this.left = x - this.width
+			this.setValue(x - this.width)
+		},
+		setValue(value) {
+			// 设置最大最小值
+			this.left = Math.max(-this.position.button.width, Math.min(parseInt(value), 0))
+			this.position.content.left = this.left
+			if (this.isopen) {
+				this.openleft = this.left + this.position.button.width
+			}
+		},
+		move(left, value) {
+			if (left >= value) {
+				this.$emit('change', false)
+				this.close()
+			} else {
+				this.$emit('change', true)
+				this.open()
+			}
+		},
+		open() {
+			this.uniShow = true
+			this.left = -this.position.button.width
+			this.setValue(-this.position.button.width)
+		},
+		close() {
+			this.uniShow = true
+			this.setValue(0)
+			setTimeout(() => {
+				this.uniShow = false
+				this.isopen = false
+			}, 300)
+		},
+		getSelectorQuery() {
+			// #ifndef APP-NVUE
+			const views = uni.createSelectorQuery().in(this);
+			views
+				.selectAll('.selector-query-hock')
+				.boundingClientRect(data => {
+					console.log(data)
+					this.position.content = data[1]
+					this.position.button = data[0]
+					if (this.autoClose) return
+					if (this.show) {
+						this.open()
+					} else {
+						this.close()
+					}
+				})
+				.exec()
+			// #endif
+			// #ifdef APP-NVUE
+			dom.getComponentRect(this.$refs['selector-content-hock'], (data) => {
+				if (this.position.content) return
+				this.position.content = data.size
+			})
+			dom.getComponentRect(this.$refs['selector-button-hock'], (data) => {
+				if (this.position.button) return
+				this.position.button = data.size
+				if (this.autoClose) return
+				if (this.show) {
+					this.open()
+				} else {
+					this.close()
+				}
+			})
+			// #endif
+		}
+	}
+}

+ 118 - 0
uni-shop/components/uni-swipe-action-item/mpwxs.js

@@ -0,0 +1,118 @@
+export default {
+	data() {
+		return {
+			position: [],
+			button: []
+		}
+	},
+	computed: {
+		pos() {
+			return JSON.stringify(this.position)
+		},
+		btn() {
+			return JSON.stringify(this.button)
+		}
+	},
+	watch: {
+		show(newVal) {
+			if (this.autoClose) return
+			let valueObj = this.position[0]
+			if (!valueObj) {
+				this.init()
+				return
+			}
+			valueObj.show = newVal
+			this.$set(this.position, 0, valueObj)
+		}
+	},
+	created() {
+		if (this.swipeaction.children !== undefined) {
+			this.swipeaction.children.push(this)
+		}
+	},
+	mounted() {
+		this.init()
+
+	},
+	beforeDestroy() {
+		this.swipeaction.children.forEach((item, index) => {
+			if (item === this) {
+				this.swipeaction.children.splice(index, 1)
+			}
+		})
+	},
+	methods: {
+		init() {
+
+			setTimeout(() => {
+				this.getSize()
+				this.getButtonSize()
+			}, 50)
+		},
+		closeSwipe(e) {
+			if (!this.autoClose) return
+			this.swipeaction.closeOther(this)
+		},
+
+		change(e) {
+			this.$emit('change', e.open)
+			let valueObj = this.position[0]
+			if (valueObj.show !== e.open) {
+				valueObj.show = e.open
+				this.$set(this.position, 0, valueObj)
+			}
+		},
+		onClick(index, item) {
+			this.$emit('click', {
+				content: item,
+				index
+			})
+		},
+		appTouchStart(e) {
+			const {
+				clientX
+			} = e.changedTouches[0]
+			this.clientX = clientX
+			this.timestamp = new Date().getTime()
+		},
+		appTouchEnd(e, index, item) {
+			const {
+				clientX
+			} = e.changedTouches[0]
+			// fixed by xxxx 模拟点击事件,解决 ios 13 点击区域错位的问题
+			let diff = Math.abs(this.clientX - clientX)
+			let time = (new Date().getTime()) - this.timestamp
+			console.log(diff);
+			if (diff < 40 && time < 300) {
+				// console.log('点击');
+				this.$emit('click', {
+					content: item,
+					index
+				})
+			}
+		},
+		getSize() {
+			const views = uni.createSelectorQuery().in(this)
+			views
+				.selectAll('.selector-query-hock')
+				.boundingClientRect(data => {
+					if (this.autoClose) {
+						data[0].show = false
+					} else {
+						data[0].show = this.show
+					}
+					this.position = data
+				})
+				.exec()
+		},
+		getButtonSize() {
+			const views = uni.createSelectorQuery().in(this)
+			views
+				.selectAll('.button-hock')
+				.boundingClientRect(data => {
+					this.button = data
+				})
+				.exec()
+		}
+	}
+}

+ 265 - 0
uni-shop/components/uni-swipe-action-item/uni-swipe-action-item.vue

@@ -0,0 +1,265 @@
+<template>
+	<view class="uni-swipe">
+		<!-- 在微信小程序 app vue端 h5 使用wxs 实现-->
+		<!-- #ifdef APP-VUE || MP-WEIXIN || H5 -->
+		<view class="uni-swipe_content">
+			<view :data-disabled="disabled" :data-position="pos" :change:prop="swipe.sizeReady" :prop="pos" class="uni-swipe_move-box selector-query-hock move-hock" @touchstart="swipe.touchstart" @touchmove="swipe.touchmove" @touchend="swipe.touchend" @change="change">
+				<view class="uni-swipe_box">
+					<slot />
+				</view>
+				<view ref="selector-button-hock" class="uni-swipe_button-group selector-query-hock move-hock">
+					<!-- 使用 touchend 解决 ios 13 不触发按钮事件的问题-->
+					<view v-for="(item,index) in options" :data-button="btn" :key="index" :style="{
+		          backgroundColor: item.style && item.style.backgroundColor ? item.style.backgroundColor : '#C7C6CD',
+		          fontSize: item.style && item.style.fontSize ? item.style.fontSize : '16px'
+		        }" class="uni-swipe_button button-hock" @touchstart="appTouchStart" @touchend="appTouchEnd($event,index,item)"><text class="uni-swipe_button-text" :style="{color: item.style && item.style.color ? item.style.color : '#FFFFFF',}">{{ item.text }}</text></view>
+				</view>
+			</view>
+		</view>
+		<!-- #endif -->
+
+		<!--  app nvue端 使用 bindingx -->
+		<!-- #ifdef APP-NVUE -->
+		<view ref="selector-box-hock" class="uni-swipe_content" @horizontalpan="touchstart" @touchend="touchend">
+			<view ref="selector-button-hock" class="uni-swipe_button-group selector-query-hock move-hock" :style="{width:right+'px'}">
+				<view ref="button-hock" v-for="(item,index) in options" :key="index" :style="{
+		  backgroundColor: item.style && item.style.backgroundColor ? item.style.backgroundColor : '#C7C6CD',left: right+'px'}" class="uni-swipe_button " @click.stop="onClick(index,item)"><text class="uni-swipe_button-text" :style="{color: item.style && item.style.color ? item.style.color : '#FFFFFF',fontSize: item.style && item.style.fontSize ? item.style.fontSize : '16px'}">{{ item.text }}</text></view>
+			</view>
+			<view ref='selector-content-hock' class="uni-swipe_move-box selector-query-hock">
+				<view class="uni-swipe_box">
+					<slot />
+				</view>
+			</view>
+		</view>
+		<!-- #endif -->
+
+		<!-- 在非 app 端、非微信小程序、支付宝小程序、h5端使用 js -->
+		<!-- #ifndef APP-PLUS || MP-WEIXIN || MP-ALIPAY || H5 -->
+		<view class="uni-swipe_content">
+			<view ref="selector-button-hock" class="uni-swipe_button-group selector-query-hock move-hock">
+				<view v-for="(item,index) in options" :data-button="btn" :key="index" :style="{
+		    backgroundColor: item.style && item.style.backgroundColor ? item.style.backgroundColor : '#C7C6CD',
+		    fontSize: item.style && item.style.fontSize ? item.style.fontSize : '16px'
+		  }" class="uni-swipe_button button-hock" @click.stop="onClick(index,item)"><text class="uni-swipe_button-text" :style="{color: item.style && item.style.color ? item.style.color : '#FFFFFF',}">{{ item.text }}</text></view>
+			</view>
+			<view ref='selector-content-hock' class="selector-query-hock" @touchstart="touchstart" @touchmove="touchmove" @touchend="touchend" :class="{'ani':uniShow}" :style="{transform:moveLeft}">
+				<view class="uni-swipe_move-box">
+					<view class="uni-swipe_box">
+						<slot />
+					</view>
+				</view>
+			</view>
+		</view>
+		<!-- #endif -->
+		<!-- #ifdef MP-ALIPAY -->
+		<view class="uni-swipe-box" @touchstart="touchstart" @touchmove="touchmove" @touchend="touchend">
+			<view class="viewWidth-hook">
+				<movable-area v-if="viewWidth !== 0" class="movable-area" :style="{width:(viewWidth-buttonWidth)+'px'}">
+					<movable-view class="movable-view" direction="horizontal" :animation="!transition" :style="{width:viewWidth+'px'}" :class="[transition?'transition':'']" :x="x" :disabled="disabledView" @change="onChange">
+						<view class="movable-view-box">
+							<slot></slot>
+						</view>
+					</movable-view>
+				</movable-area>
+			</view>
+			<view ref="selector-button-hock" class="uni-swipe_button-group viewWidth-hook">
+				<view v-for="(item,index) in options" :data-button="btn" :key="index" :style="{
+				  backgroundColor: item.style && item.style.backgroundColor ? item.style.backgroundColor : '#C7C6CD',
+				  fontSize: item.style && item.style.fontSize ? item.style.fontSize : '16px'
+				}" class="uni-swipe_button button-hock" @click.stop="onClick(index,item)"><text class="uni-swipe_button-text" :style="{color: item.style && item.style.color ? item.style.color : '#FFFFFF',}">{{ item.text }}</text></view>
+			</view>
+		</view>
+		<!-- #endif -->
+	</view>
+</template>
+<script src="./index.wxs" module="swipe" lang="wxs"></script>
+<script>
+	// #ifdef APP-VUE|| MP-WEIXIN || H5
+	import mpwxs from './mpwxs'
+	// #endif
+
+	// #ifdef APP-NVUE
+	import bindingx from './bindingx.js'
+	// #endif
+
+	// #ifndef APP-PLUS|| MP-WEIXIN || MP-ALIPAY ||  H5
+	import mixins from './mpother'
+	// #endif
+
+	// #ifdef MP-ALIPAY
+	import mpalipay from './mpalipay'
+	// #endif
+
+	/**
+	 * SwipeActionItem 滑动操作子组件
+	 * @description 通过滑动触发选项的容器
+	 * @tutorial https://ext.dcloud.net.cn/plugin?id=181
+	 * @property {Boolean} show = [true|false] 开启关闭组件,auto-close = false 时生效
+	 * @property {Boolean} disabled = [true|false] 是否禁止滑动
+	 * @property {Boolean} autoClose = [true|false] 其他组件开启的时候,当前组件是否自动关闭
+	 * @property {Array} options 组件选项内容及样式
+	 * @event {Function} click 点击选项按钮时触发事件,e = {content,index} ,content(点击内容)、index(下标)
+	 * @event {Function} change 组件打开或关闭时触发,true:开启状态;false:关闭状态
+	 */
+
+	export default {
+		// #ifdef APP-VUE|| MP-WEIXIN||H5
+		mixins: [mpwxs],
+		// #endif
+
+		// #ifdef APP-NVUE
+		mixins: [bindingx],
+		// #endif
+
+		// #ifndef APP-PLUS|| MP-WEIXIN || MP-ALIPAY ||  H5
+		mixins: [mixins],
+		// #endif
+
+		// #ifdef MP-ALIPAY
+		mixins: [mpalipay],
+		// #endif
+
+		props: {
+			/**
+			 * 按钮内容
+			 */
+			options: {
+				type: Array,
+				default () {
+					return []
+				}
+			},
+			/**
+			 * 禁用
+			 */
+			disabled: {
+				type: Boolean,
+				default: false
+			},
+			/**
+			 * 变量控制开关
+			 */
+			show: {
+				type: Boolean,
+				default: false
+			},
+			/**
+			 * 是否自动关闭
+			 */
+			autoClose: {
+				type: Boolean,
+				default: true
+			}
+		},
+		inject: ['swipeaction']
+
+
+	}
+</script>
+<style scoped>
+	.uni-swipe {
+		overflow: hidden;
+	}
+
+	.uni-swipe-box {
+		position: relative;
+		width: 100%;
+	}
+
+	.uni-swipe_content {
+		flex: 1;
+		position: relative;
+	}
+
+	.uni-swipe_move-box {
+		/* #ifndef APP-NVUE */
+		display: flex;
+		/* #endif */
+		position: relative;
+		flex-direction: row;
+	}
+
+	.uni-swipe_box {
+		/* #ifndef APP-NVUE */
+		display: flex;
+		flex-direction: row;
+		width: 100%;
+		flex-shrink: 0;
+		/* #endif */
+		/* #ifdef APP-NVUE */
+		flex: 1;
+		/* #endif */
+		font-size: 14px;
+		background-color: #fff;
+	}
+
+	.uni-swipe_button-group {
+		/* #ifndef APP-VUE|| MP-WEIXIN||H5 */
+		position: absolute;
+		top: 0;
+		right: 0;
+		bottom: 0;
+		z-index: 0;
+		/* #endif */
+		/* #ifndef APP-NVUE */
+		display: flex;
+		flex-shrink: 0;
+		/* #endif */
+		flex-direction: row;
+	}
+
+	.uni-swipe_button {
+		/* #ifdef APP-NVUE */
+		position: absolute;
+		left: 0;
+		top: 0;
+		bottom: 0;
+		/* #endif */
+		/* #ifndef APP-NVUE */
+		display: flex;
+		/* #endif */
+		flex-direction: row;
+		justify-content: center;
+		align-items: center;
+		padding: 0 20px;
+	}
+
+	.uni-swipe_button-text {
+		/* #ifndef APP-NVUE */
+		flex-shrink: 0;
+		/* #endif */
+		font-size: 14px;
+	}
+
+	.ani {
+		transition-property: transform;
+		transition-duration: 0.3s;
+		transition-timing-function: cubic-bezier(0.165, 0.84, 0.44, 1);
+	}
+
+	/* #ifdef MP-ALIPAY */
+	.movable-area {
+		width: 300px;
+		height: 100%;
+		height: 45px;
+	}
+
+	.movable-view {
+		position: relative;
+		width: 160%;
+		height: 45px;
+		z-index: 2;
+	}
+
+	.transition {
+		transition: all 0.3s;
+	}
+
+	.movable-view-box {
+		width: 100%;
+		height: 100%;
+		background-color: #fff;
+	}
+
+	/* #endif */
+</style>

+ 58 - 0
uni-shop/components/uni-swipe-action/uni-swipe-action.vue

@@ -0,0 +1,58 @@
+<template>
+	<view>
+		<slot></slot>
+	</view>
+</template>
+
+<script>
+	/**
+	 * SwipeAction 滑动操作
+	 * @description 通过滑动触发选项的容器
+	 * @tutorial https://ext.dcloud.net.cn/plugin?id=181
+	 */
+	export default {
+		data() {
+			return {};
+		},
+		provide() {
+			return {
+				swipeaction: this
+			}
+		},
+		created() {
+			this.children = []
+		},
+		methods: {
+			closeOther(vm) {
+				let children = this.children
+				children.forEach((item, index) => {
+					if (vm === item) return
+					// 支付宝执行以下操作
+					// #ifdef MP-ALIPAY
+					if (item.isopen) {
+						item.close()
+					}
+					// #endif
+
+					// app vue 端、h5 、微信、支付宝  执行以下操作
+					// #ifdef APP-VUE || H5 || MP-WEIXIN
+					let position = item.position[0]
+					let show = position.show
+					if (show) {
+						position.show = false
+					}
+					// #endif
+
+					// nvue 执行以下操作
+					// #ifdef APP-NVUE || MP-BAIDU || MP-QQ || MP-TOUTIAO
+					item.close()
+					// #endif
+				})
+			}
+		}
+	}
+</script>
+
+<style scoped>
+
+</style>

+ 226 - 0
uni-shop/components/upload-images.vue

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

binární
uni-shop/components/v-header/images/back-white.png


binární
uni-shop/components/v-header/images/back.png


+ 89 - 0
uni-shop/components/v-header/main.vue

@@ -0,0 +1,89 @@
+<template>
+  <view class="header_screen relative flex">
+
+    <!-- 头部组件 -->
+    <view
+        :style="{
+          'padding-top': statusBarHeight+'px',
+          'height': (statusBarHeight + headerHeight)+'px',
+          'background-color': headerBackgroundColor
+        }"
+        class="header_header flex-shrink-0"
+        :class="[immersion ? 'absolute immersion-header':'relative',border?'header-border':'']"
+    >
+      <view class="absolute header-watch-scroll" :style="{background:transitionBackgroundColor,opacity: watchOpacity}" v-if="watchScroll"></view>
+      <!-- 内容包裹 -->
+      <view class="relative flex-1 flex row" >
+        <!-- 左侧 -->
+        <view class="relative header-left flex row">
+          <block v-if="control">
+            <view v-if="headerIcon[mode]" @click="goBack" class="header-back flex jCenter" :style="{height:headerHeight+'px'}">
+              <image class="header-back-image" :src="headerIcon[mode][headerStyle]"></image>
+            </view>
+          </block>
+
+          <slot name="header-left"></slot>
+
+        </view>
+        <view v-if="!alignCenter" @click="goBack"  class="header-title flex center" :style="{color:headerStyle==='black'?'#000':'#fff'}">
+          <view v-if="state===1" class="header-load flat-load-animate" :style="{'border-color':headerStyle==='black'?'#000':'#fff'}"></view>
+          <text v-else-if="state===3" @click="reload" class="header-fail relative" :class="['header-fail-'+headerStyle]">网络繁忙,点击重试</text>
+          <text v-else>{{title}}</text>
+        </view>
+        <!--标题文字-->
+        <view v-if="alignCenter" class="header-title-model absolute flex center">
+          <view class="header-title flex center" :style="{color:headerStyle==='black'?'#000':'#fff'}">
+            <view v-if="state===1" class="header-load flat-load-animate" :style="{'border-color':headerStyle==='black'?'#000':'#fff'}"></view>
+            <text v-else-if="state===3" @click="reload" :class="['header-fail-'+headerStyle]" class="header-fail relative">网络繁忙,点击重试</text>
+            <text v-else>{{title}}</text>
+          </view>
+        </view>
+      </view>
+
+    </view>
+
+    <slot name="background"></slot>
+
+    <!-- 其他位置 -->
+    <view class="flex-1 flex relative" >
+      <slot name="header-background"></slot>
+      <view class="flex-all-1 overflow" :style="{'background-color':backgroundColor}">
+        <view v-if="scroll"
+              class="header-title-model absolute flex"
+              :style="{'padding-top': (immersion&&immersionFixed?(statusBarHeight + headerHeight):0)+'px'}"
+        >
+          <scroll-view v-if="watchScroll" @scroll="watchScrollEvent" scroll-y class="header_screen">
+            <slot></slot>
+          </scroll-view>
+          <scroll-view wx:else scroll-y class="header_screen">
+            <slot></slot>
+          </scroll-view>
+        </view>
+        <view v-else
+              :style="{'padding-top': (immersion&&immersionFixed?(statusBarHeight + headerHeight):0)+'px'}"
+              class="header-title-model absolute flex">
+          <slot></slot>
+        </view>
+      </view>
+    </view>
+
+    <!-- 其他内容 -->
+    <slot name="modal"></slot>
+    <!-- 间距 -->
+    <view v-if="phoneX&&mode==='back'" :style="{height:(footerHeight + vFooterHeight)+'px','background-color':backgroundColor}"></view>
+
+  </view>
+
+</template>
+
+<script>
+import mixins from './mixins';
+import props from './props';
+export default {
+  name: "v-header",
+  mixins,
+  props
+}
+</script>
+
+<style scoped lang="scss" src="./style.scss"></style>

+ 200 - 0
uni-shop/components/v-header/mixins/handle.js

@@ -0,0 +1,200 @@
+import config from "../../../config/config";
+import router from "../../../router";
+
+export default {
+
+    data(){
+        return {
+            state: 0,   // 0 无状态 1 加载中  2 加载完成 3 加载失败
+            vFooterHeight: config.phoneFooterHeight,
+            useModal:undefined,
+
+            headerIcon:{
+                back:{
+                    black: require('../images/back.png'),
+                    white: require('../images/back-white.png')
+                }
+            },
+
+            _state: 0,		// 状态 副本  设置 状态 以 副本为主 的 检定  正本 作为 视图渲染
+            _requestTime: 0,  // 发起请求的 时间
+            _unique:undefined,       // 请求的唯一标示
+            _time:undefined,  // 发起 请求的 时间 记录器
+        }
+    },
+
+    methods:{
+
+        openModal:function(obj = {},type){
+            if (type === undefined || !this.useModal[type])  type = this.modal[0];
+            return this._modal[type] && this._modal[type].openModal(obj);
+        },
+
+        goBack:function(){
+            return router.navigateBack({
+                fail:function () {
+                    return router.switchTab('index');
+                }
+            });
+        },
+
+        /* 重新载入 */
+        reload:function(state=1){
+            return this.fetch(true,state);
+        },
+
+        /* 请求 */
+        fetch:function(must,state=1){
+            // 查看当前是否在请求中 和 是否 已经到底了
+            if (this._state === 1  && must !== true ) return undefined;
+
+            /* 设置请求时间 */
+            this._requestTime = this.getNowTime();
+            /* 设置唯一标示 */
+            this._unique = this.getUnique();
+            /* 暂存唯一标示 */
+            let unique = this._unique;
+
+            if (state === 1) {
+                /* 设置状态为加载中 */
+                this.setState(1);
+            }
+
+            return  this.$emit('fetch',{
+                success:(data)=>{
+                    /* 唯一表示保持一致 表示响应 */
+                    if (unique === this._unique) {
+                        unique = null;
+                        return this.success(data);
+                    } else {
+                        unique = null;
+                    }
+                },
+                fail:(error)=>{
+                    /* 唯一表示保持一致 表示响应 */
+                    if (unique === this._unique) {
+                        unique = null;
+                        return this.fail(error);
+                    } else {
+                        unique = null;
+                    }
+                }
+            })
+
+        },
+
+        /* 设置成功 */
+        success:function(){
+            return this.setState(2);
+        },
+
+        /* 失败的函数 */
+        fail: function(fail){
+            console.log(fail);
+            return this.setState(3);
+        },
+
+        /* 设置状态
+        * 	渲染规则 注意
+        *  		从 其他 状态 ->  加载中 	需要 延长 async
+        *  		从 加载中  -> 其他 状态    如果 目前处于 加载中 且时长不超过timeout 需要 延长 async 如果 不处于 加载中 将 直接 渲染
+        *
+        * */
+        setState:function (state,draw={}) {
+
+            /* 设置状态 */
+            if (state !== this._state) {
+                /* 清除 */
+                clearTimeout(this._time);
+
+                let beforeState = this._state;
+                this._state = state;
+
+                /* 如果设置加载状态 */
+                if (state === 1) {
+                    draw.state = 1;
+                    if (beforeState === 3) {
+                        return this.setUseData(draw);
+                    } else {
+                        /* 设置为 请求状态 */
+                        this._time = setTimeout(()=>{
+                            return this.setUseData(draw);
+                        },this.asyncTime);
+                    }
+                } else {
+                    draw.state = this._state;
+                    /* 设置其他状态 */
+                    let diff = this.getNowTime() - this._requestTime;
+                    /* 如果请求间隔时长 大于 async 且 请求时长 不超过 timeout */
+                    if (diff >= this.asyncTime && diff < this.timeout) {
+
+                        this._time = setTimeout(()=>{
+                            return this.setUseData(draw);
+                        },this.asyncTime);
+                    } else {
+                        return this.setUseData(draw);
+                    }
+
+
+                }
+            }
+
+        },
+
+        setUseData(draw){
+            for (let key in draw) {
+                if (draw.hasOwnProperty(key)) {
+                    this[key] = draw[key];
+                }
+            }
+        },
+
+        /* 获取 当前时间戳 */
+        getNowTime:function () {
+            return new Date().getTime();
+        },
+
+        /* 获取唯一标示 */
+        getUnique: function () {
+            return this._requestTime + '<->' + Math.ceil(Math.random() * 1000);
+        },
+
+        /* 设置modal */
+        installModal(){
+
+            if (this.modal && this.modal.length > 0) {
+
+                this.useModal = {};
+                this.modal.map((item)=> {
+                    this.$set(this.useModal,item,true);
+                });
+
+                // 页面渲染完成后执行构造
+                return setTimeout(()=>{
+
+                    this._modal = {};
+                    this.modal.map((item)=>{
+                        this._modal[item] = uni.selectComponent('#'+item,true);
+                    });
+                },16);
+
+            }
+
+        }
+
+    },
+
+    // pageLifetimes:{
+    //     show(){
+    //         // 子组件
+    //         config.component = this;
+    //     }
+    // },
+
+    mounted(){
+        this.installModal();
+        if (this.async) {
+            return this.fetch();
+        }
+    },
+}

+ 4 - 0
uni-shop/components/v-header/mixins/index.js

@@ -0,0 +1,4 @@
+import handle from './handle';
+import scroll from "./scroll";
+
+export default [handle,scroll];

+ 46 - 0
uni-shop/components/v-header/mixins/scroll.js

@@ -0,0 +1,46 @@
+export default {
+
+    data(){
+        return {
+            watchScroll:false,
+            watchOpacity:'0.00'
+        }
+    },
+
+    methods:{
+
+        installScroll(){
+            if (this.scroll && this.transitionBackgroundColor && this._transitionDistance) {
+                this.watchScroll = true;
+            }
+        },
+
+        watchScrollEvent(e){
+            let scrollTop = e.detail.scrollTop;
+            scrollTop = scrollTop > this._transitionDistance?this._transitionDistance:scrollTop;
+            let opacity= scrollTop / this._transitionDistance;
+
+            opacity = opacity.toFixed(2);
+
+            if (this.watchOpacity !== opacity) {
+                this.watchOpacity = opacity;
+            }
+
+        }
+
+    },
+
+    mounted(){
+        if (this.transitionScroll) {
+            this._transitionDistance = this.transitionDistance || (this.statusBarHeight + this.headerHeight)
+        } else if (this.transitionAdd){
+            this._transitionDistance = this.transitionAdd + (this.statusBarHeight + this.headerHeight);
+        } else {
+            this._transitionDistance = this.transitionDistance;
+        }
+        return this.installScroll();
+    }
+
+
+
+}

+ 94 - 0
uni-shop/components/v-header/props.js

@@ -0,0 +1,94 @@
+import config from '@/config/config';
+export default {
+    statusBarHeight: {
+        type: Number,
+        default: config.system.statusBarHeight
+    },
+    border: {
+        type: Boolean,
+        default: false
+    },
+    scroll: {
+        type: Boolean,
+        default: false
+    },
+    transitionScroll:{
+        type: Boolean,
+        default: false
+    },
+    transitionAdd:{
+        type:Number,
+        default:0
+    },
+    transitionBackgroundColor: {
+        type: String,
+        default: "#FE4431"
+    },
+    transitionDistance: {
+        type: Number
+    },
+    backgroundColor: {
+        type: String,
+        default: '#F7F7F7'
+    },
+    headerHeight: {
+        type: Number,
+        default: config.headerHeight
+    },
+    headerBackgroundColor: {
+        type: String,
+        default: "#fff"
+    },
+    immersion: {
+        type: Boolean,
+        default: false
+    },
+    immersionFixed:{
+        type: Boolean,
+        default: false
+    },
+    headerStyle: {
+        type: String,
+        default: 'black'
+    },
+    title: {
+        type: String,
+        default: '首页'
+    },
+    mode: {
+        type: String,
+        default: 'back'
+    },
+    alignCenter: {
+        type: Boolean,
+        default: true
+    },
+    control: {
+        type: Boolean,
+        default: true
+    },
+    async: {
+        type: Boolean,
+        default: false
+    },
+    asyncTime: {
+        type: Number,
+        default: 3000
+    },
+    timeout: {
+        type: Number,
+        default: 1200
+    },
+    phoneX: {
+        type: Boolean,
+        default: true
+    },
+    footerHeight: {
+        type: Number,
+        default: 0
+    },
+    modal: {
+        type: Array,
+        default: []
+    }
+}

+ 120 - 0
uni-shop/components/v-header/style.scss

@@ -0,0 +1,120 @@
+.header_screen{
+    width: 100%;
+    height: 100%;
+}
+.flex {
+    display: flex;
+    flex-direction: column;
+    box-sizing: border-box;
+}
+:host{
+    width: 100%;
+    height: 100%;
+    overflow: hidden;
+}
+.flex-1{
+    flex-grow: 1;
+}
+.flex-shrink-0{
+    flex-shrink: 0;
+}
+.header_header{
+    padding-left: 15px;
+    padding-right: 15px;
+    z-index: 999;
+}
+
+.relative{
+    position: relative;
+}
+
+.absolute{
+    position: absolute;
+}
+.header-title-model{
+    top: 0;
+    left: 0;
+    right: 0;
+    bottom: 0;
+}
+.header-border{
+    border-bottom: 1rpx solid #eee;
+}
+.flex-all-1{
+    flex: 1;
+}
+.header-left{
+    z-index: 10;
+}
+.aCenter,.center{
+    align-items: center;
+}
+
+.jCenter,.center{
+    justify-content: center;
+}
+.header-watch-scroll{
+    top: 0;
+    left: 0;
+    right: 0;
+    bottom: 0;
+}
+.header-title{
+    font-size: 16px;
+    line-height: 24px;
+    color: #000;
+    max-width: 180px;
+    white-space: nowrap;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    font-family: PingFang SC;
+}
+.header-back{
+    width: 20px;
+    margin-left: 4px;
+}
+.header-load{
+    width: 16px;
+    height: 16px;
+    border: 1.2px solid #000;
+    border-top-color: transparent !important;
+    border-radius: 50%;
+}
+.flat-load-animate{
+    animation: rotate .7s infinite linear forwards;
+}
+.row{
+    flex-direction: row;
+    width: auto;
+}
+@keyframes rotate {
+
+    0%{
+        transform: rotate(0);
+    }
+    100%{
+        transform: rotate(360deg);
+    }
+
+}
+.immersion-header{
+    top: 0;
+    left: 0;
+    width: 100%;
+    z-index: 9999999;
+}
+.header-fail{
+    color: red;
+}
+.header-fail-white{
+    color: #fff;
+}
+.header-back-image{
+    width: 9px;
+    height: 16px;
+}
+.header-logo-image{
+    width: 100px;
+    height: 0;
+    /*height: 25px;*/
+}

+ 39 - 0
uni-shop/components/v-image/main.vue

@@ -0,0 +1,39 @@
+<template>
+  <view
+      class="image_model"
+      :style="{
+        'background-color':(state===1||state===3)?backgroundColor:successBackgroundColor,
+        'height': mode==='heightFix'?'auto':'100%'
+      }"
+  >
+    <view class="relative image_screen">
+      <image
+          :lazy-load="lazyLoad"
+          class="image_model-image"
+          :show-menu-by-longpress="showMenuByLongpress"
+          :style="{'height': mode==='heightFix'?'auto':'100%'}"
+          :mode="mode"
+          :src="src"
+          @load="success"
+      ></image>
+      <view class="absolute image-fixed">
+        <slot></slot>
+      </view>
+    </view>
+
+
+  </view>
+
+</template>
+
+<script>
+import mixins from './mixins';
+import props from "./props";
+export default {
+  name: "v-image",
+  mixins,
+  props
+}
+</script>
+
+<style scoped lang="scss" src="./style.scss"></style>

+ 58 - 0
uni-shop/components/v-image/mixins/handle.js

@@ -0,0 +1,58 @@
+export default {
+
+    data(){
+        return {
+            state: 0,   // 0 无状态 1 加载中  2 加载完成 3 加载失败
+            _state: 0,		// 状态 副本  设置 状态 以 副本为主 的 检定  正本 作为 视图渲染
+        }
+    },
+
+    observers:{
+        src:function (value) {
+            if (this._src !== value) {
+                this._src = value;
+                return this.setState(1);
+            }
+        }
+    },
+
+    methods:{
+
+        success:function(){
+            this.setState(2);
+            return this.$emit('load');
+        },
+        /* 设置状态
+        * 	渲染规则 注意
+        *  		从 其他 状态 ->  加载中 	需要 延长 async
+        *  		从 加载中  -> 其他 状态    如果 目前处于 加载中 且时长不超过timeout 需要 延长 async 如果 不处于 加载中 将 直接 渲染
+        *
+        * */
+        setState:function (state,draw={}) {
+
+            /* 设置状态 */
+            if (state !== this._state) {
+                /* 清除 */
+                clearTimeout(this._time);
+                this._state = state;
+
+                if (state === 1) {
+                    this._time = setTimeout(()=>{
+                        return this.state = 1;
+                    },this.async);
+                } else {
+                    this.state = state;
+                }
+
+            }
+
+        }
+
+    },
+
+    mounted(){
+        this._src = this.src;
+        return this.setState(1);
+    }
+
+}

+ 3 - 0
uni-shop/components/v-image/mixins/index.js

@@ -0,0 +1,3 @@
+import handle from "./handle";
+
+export default [handle];

+ 30 - 0
uni-shop/components/v-image/props.js

@@ -0,0 +1,30 @@
+export default {
+    async: {
+        type: Number,
+        default: 100
+    },
+    src: {
+        type: String,
+        default: ''
+    },
+    mode: {
+        type: String,
+        default: 'aspectFill'
+    },
+    lazyLoad: {
+        type: Boolean,
+        default: true
+    },
+    showMenuByLongpress: {
+        type: Boolean,
+        default: false
+    },
+    successBackgroundColor: {
+        type: String,
+        default: 'transparent'
+    },
+    backgroundColor: {
+        type: String,
+        default: "#f2f2f2"
+    }
+}

+ 34 - 0
uni-shop/components/v-image/style.scss

@@ -0,0 +1,34 @@
+.image_screen{
+    width: 100%;
+    height: 100%;
+}
+:host{
+    overflow: hidden;
+}
+.image_model{
+    background-color: #666666;
+    overflow: hidden;
+}
+.image_model-image{
+    width: 100%;
+    height: 100%;
+}
+.header-title-model{
+    top: 0;
+    left: 0;
+    right: 0;
+    bottom: 0;
+}
+.header-left{
+    z-index: 10;
+}
+.image-fixed{
+    top: 0;
+    bottom: 0;
+    left: 0;
+    right: 0;
+    z-index: 10;
+    position: absolute;
+    background-color: transparent;
+}
+

+ 42 - 0
uni-shop/components/v-step/main.vue

@@ -0,0 +1,42 @@
+<template>
+  <view class="step-model"
+    :class="[small?'step-model-small':'',disabled&&disablesStyle?'step-disabled':'']"
+  >
+    <view class="step-control center"
+          :style="{color: color}"
+          :class="[small?'step-control-small':'',(disabled||showValue<=minValue)&&disablesStyle?'step-disabled':'']"
+          @click="reduce"
+    >
+      <text class="iconfont iconfont-sign"></text>
+    </view>
+    <input
+        type="number"
+        class="step-input"
+        :class="[small?'step-input-small':'',disabled&&disablesStyle?'step-disabled-input':'']"
+        :value="showValue"
+        @blur="blur"
+        :disabled="disabled || showValue>=maxValue && showValue<=minValue"
+        :style="{color: textColor || color}"
+    />
+    <view class="step-control center"
+          :style="{color: color}"
+          :class="[small?'step-control-small':'',(disabled||showValue>=maxValue)&&disablesStyle?'step-disabled':'']"
+          @click="add">
+      <text class="iconfont iconfont-puls"></text>
+    </view>
+
+
+  </view>
+</template>
+
+<script>
+import mixins from './mixins';
+import props from "./props";
+export default {
+  name: "v-step",
+  mixins,
+  props
+}
+</script>
+
+<style scoped lang="scss" src="./style.scss"></style>

+ 128 - 0
uni-shop/components/v-step/mixins/handle.js

@@ -0,0 +1,128 @@
+import throttle from "../../../utils/tool/throttle";
+
+export default {
+
+    data(){
+        return {
+            showValue: 1
+        }
+    },
+
+    watch:{
+        value:function (value) {
+            if (this.showValue !== value)  {
+                return this.setValue(value,true);
+            }
+        },
+        maxValue:function (value) {
+            return this.changeValue(this.showValue,true);
+        }
+    },
+
+    methods:{
+
+        // 设置value
+        setValue:function (value,draw = false,change=false) {
+
+            if (typeof value === 'number') {
+
+                if ( value !== this.showValue) {
+                    return this.changeValue(value,draw,change);
+                } else {
+                    return undefined;
+                }
+
+            } else {
+
+                let newValue = Number(value);
+
+                if(!newValue){
+                    newValue = parseInt(value);
+                }
+
+                if (newValue && newValue !== this.showValue) {
+                    return this.changeValue(parseInt(newValue),draw,change);
+                } else {
+                    return this.changeValue(this.minValue, draw, change);
+                }
+            }
+
+        },
+
+        // change
+        changeValue:function(value,draw,change){
+
+            value = this.getValue(value);
+
+            if (this.showValue !== value){this.showValue = value}
+            if (this.autoChange && !change && this.showValue !== this.value) {
+                change = true;
+            }
+
+
+            if(change) {
+                this.changeEvent(this.showValue);
+                this.$emit('input',{
+                    value: this.showValue
+                });
+            }
+        },
+
+        changeEvent:function(){
+            return this.$emit('change',{
+                value: this.showValue
+            });
+        },
+
+        // 获取value
+        getValue: function (value) {
+
+            if (value < this.minValue) {
+                return this.minValue;
+            } else if (value > this.maxValue) {
+                return this.maxValue;
+            } else {
+                return value;
+            }
+        },
+
+        // 添加
+        add:function () {
+            if (this.showValue < this.maxValue && !this.disabled) {
+                return this.setValue(this.showValue+this.step,true,true);
+            }
+        },
+
+        // 减少
+        reduce:function () {
+            if (this.showValue > this.minValue && !this.disabled) {
+                return this.setValue(this.showValue - this.step,true,true);
+            }
+        },
+
+        // 失去焦点的时候触发
+        blur:function (e) {
+            return this.setValue(Number(e.detail.value),true,true);
+        }
+
+    },
+
+    mounted(){
+        if (this.throttle) {
+            this.throttle = throttle({
+                handle: this.changeEvent,
+                target: this,
+                time: this.throttle
+            });
+            this.changeEvent = this.throttle.supper;
+        }
+
+        return this.setValue(this.value,true);
+    },
+
+    beforeDestroy(){
+        this.throttle && this.throttle.destroy();
+        this.throttle = null;
+    }
+
+}

+ 3 - 0
uni-shop/components/v-step/mixins/index.js

@@ -0,0 +1,3 @@
+import handle from "./handle";
+
+export default [handle];

+ 67 - 0
uni-shop/components/v-step/props.js

@@ -0,0 +1,67 @@
+export default {
+
+	/* 当前数量 */
+	value:{
+		type: Number,
+		default: 1
+	},
+
+	/* 最少选择的数量 */
+	minValue:{
+		type: Number,
+		default: 1
+	},
+
+	/* 最大选择的数量 */
+	maxValue:{
+		type: Number,
+		default: 999
+	},
+
+	/* 每次添加的数量 */
+	step:{
+		type: Number,
+		default:1
+	},
+
+	/* 禁用 */
+	disabled:{
+		type: Boolean,
+		default: false
+	},
+
+	/* 是否开启禁用的样式 */
+	disablesStyle:{
+		type:Boolean,
+		default: false
+	},
+
+	/* 被自动转换后是否需要同步 */
+	autoChange:{
+		type:Boolean,
+		default: false
+	},
+
+	/* 节流 300毫秒触发 */
+	throttle:{
+		type: Number,
+		default: 0
+	},
+
+	/* 显示的颜色 */
+	color:{
+		type: String,
+		default: '#9F9F9F'
+	},
+
+	textColor:{
+		type:String,
+		default:'#4B4B4B'
+	},
+
+	small:{
+		type: Boolean,
+		default: false
+	}
+
+}

+ 49 - 0
uni-shop/components/v-step/style.scss

@@ -0,0 +1,49 @@
+.step-model{
+    height: 60rpx;
+    border: 2rpx solid #f4f4f4;
+    display: flex;
+    flex-direction: row;
+    box-sizing: border-box;
+}
+.step-model-small {
+    /*height: 38rpx;*/
+}
+.flex{
+    display: flex;
+}
+.center{
+    justify-content: center;
+    align-items: center;
+}
+.step-control{
+    width: 52rpx;
+    height: 100%;
+    font-size: 28rpx;
+    line-height: 28rpx;
+    font-weight: bold;
+}
+.step-control-small {
+    width: 48rpx;
+    font-size: 24rpx;
+}
+.step-input{
+    width: 84rpx;
+    border-left: 2rpx solid #f4f4f4;
+    border-right: 2rpx solid #f4f4f4;
+    text-align: center;
+    padding: 1rpx 10rpx;
+    margin: 0;
+    box-sizing: border-box;
+    font-size: 32rpx;
+    height: 100%;
+    font-weight: 400;
+    min-height: auto;
+}
+.step-input-small{
+    font-size: 22rpx;
+    width: 74rpx;
+}
+.step-disabled,.step-disabled-input{
+    background-color: #f7f7f7;
+    color: #bebebe !important;
+}

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

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

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

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

Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 24 - 0
uni-shop/config/config.js


+ 59 - 0
uni-shop/config/lib.config.d.ts

@@ -0,0 +1,59 @@
+interface Config{
+
+    // 路由前缀
+    baseURL:string,
+
+    // 设备信息
+    system: WechatMiniprogram.SystemInfo,
+
+    // 头部高度
+    headerHeight: number,
+
+    // iphoneX的底部兼容策略
+    phoneFooterHeight?:number,
+
+    // 是否为iphoneX
+    isPhoneX?:boolean,
+
+    // 插件列表
+    plugins:Array<ConfigPlugin>,
+
+    // 安装
+    install:(system:WechatMiniprogram.SystemInfo)=> void,
+
+    // 根据状态码定义错误类型
+    failMessage:Record<number | string, string>,
+
+    page:undefined | PageOptions,
+
+    // 地图信息
+    map:{
+        // 唯一标识 key
+        key:string,
+        // app 应用名称
+        referer:string
+    }
+
+
+}
+
+// 颜色
+declare const enum ConfigColorEnum {
+    main='#D90B1F',
+    skeleton='#f2f2f2',
+    white='#fff'
+}
+
+
+interface ConfigPlugin {
+
+    // 安装
+    install:(config:Config)=>void,
+
+    // 注册
+    register?:(config:Config)=>void,
+
+    // 其他的属性可以任意设置
+    [propName: string]: any
+
+}

+ 14 - 0
uni-shop/config/plugins/compatible.js

@@ -0,0 +1,14 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+exports.default = {
+    xModels: ["iPhone X", 'iPhone XR', "iPhone XS", "iPhone XS MAX", 'iPhone 11', 'iPhone 11 Pro', 'iPhone 12'],
+    install: function (config) {
+        for (var i = 0; i < this.xModels.length; i++) {
+            if (config.system.model.indexOf(this.xModels[i]) >= 0) {
+                config.isPhoneX = true;
+            }
+        }
+        config.phoneFooterHeight = config.isPhoneX ? config.phoneFooterHeight : 0;
+    }
+};
+//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY29tcGF0aWJsZS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbImNvbXBhdGlibGUudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7QUFBQSxrQkFBNkI7SUFHNUIsT0FBTyxFQUFDLENBQUMsVUFBVSxFQUFFLFdBQVcsRUFBRSxXQUFXLEVBQUUsZUFBZSxFQUFDLFdBQVcsRUFBQyxlQUFlLEVBQUMsV0FBVyxDQUFDO0lBRXZHLE9BQU8sRUFBQyxVQUFVLE1BQU07UUFDdkIsS0FBSyxJQUFJLENBQUMsR0FBRyxDQUFDLEVBQUUsQ0FBQyxHQUFHLElBQUksQ0FBQyxPQUFPLENBQUMsTUFBTSxFQUFFLENBQUMsRUFBRSxFQUFFO1lBRTdDLElBQUssTUFBTSxDQUFDLE1BQU0sQ0FBQyxLQUFLLENBQUMsT0FBTyxDQUFDLElBQUksQ0FBQyxPQUFPLENBQUMsQ0FBQyxDQUFDLENBQUMsSUFBSSxDQUFDLEVBQUU7Z0JBQ3ZELE1BQU0sQ0FBQyxRQUFRLEdBQUcsSUFBSSxDQUFBO2FBQ3RCO1NBQ0Q7UUFHRCxNQUFNLENBQUMsaUJBQWlCLEdBQUcsTUFBTSxDQUFDLFFBQVEsQ0FBQSxDQUFDLENBQUMsTUFBTSxDQUFDLGlCQUFpQixDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUM7SUFFMUUsQ0FBQztDQUVELENBQUEiLCJzb3VyY2VzQ29udGVudCI6WyJleHBvcnQgZGVmYXVsdCA8Q29uZmlnUGx1Z2luPntcblxuXHQvLyB45py65Z6L55qEIOWIpOaWreS+neaNrlxuXHR4TW9kZWxzOltcImlQaG9uZSBYXCIsICdpUGhvbmUgWFInLCBcImlQaG9uZSBYU1wiLCBcImlQaG9uZSBYUyBNQVhcIiwnaVBob25lIDExJywnaVBob25lIDExIFBybycsJ2lQaG9uZSAxMiddLFxuXG5cdGluc3RhbGw6ZnVuY3Rpb24gKGNvbmZpZykge1xuXHRcdGZvciAobGV0IGkgPSAwOyBpIDwgdGhpcy54TW9kZWxzLmxlbmd0aDsgaSsrKSB7XG5cdFx0XHQvL+aooeeziuWIpOaWreaYr+WQpuaYr21vZGVsSW5jbHVkZSDkuK3nmoTmnLrlnoss5Zug5Li655yf5py65LiK5rWL6K+V5pi+56S655qEbW9kZWzmnLrlnovkv6Hmga/mr5TovoPplb/ml6Dms5XkuIDkuIDnsr7noa7ljLnphY1cblx0XHRcdGlmICggY29uZmlnLnN5c3RlbS5tb2RlbC5pbmRleE9mKHRoaXMueE1vZGVsc1tpXSkgPj0gMCkge1xuXHRcdFx0XHRjb25maWcuaXNQaG9uZVggPSB0cnVlXG5cdFx0XHR9XG5cdFx0fVxuXG5cdFx0Ly9pcG9uZVjku6Xlj4ppcGhvbmV45Lul5LiK55qE5py65Z6LXG5cdFx0Y29uZmlnLnBob25lRm9vdGVySGVpZ2h0ID0gY29uZmlnLmlzUGhvbmVYPyBjb25maWcucGhvbmVGb290ZXJIZWlnaHQgOiAwO1xuXG5cdH1cblxufVxuIl19

+ 5 - 0
uni-shop/config/plugins/index.js

@@ -0,0 +1,5 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+var compatible_1 = require("./compatible");
+exports.default = [compatible_1.default];
+//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJpbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOztBQUFBLDJDQUFzQztBQUV0QyxrQkFBZSxDQUFDLG9CQUFVLENBQUMsQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCBjb21wYXRpYmxlIGZyb20gXCIuL2NvbXBhdGlibGVcIjtcblxuZXhwb3J0IGRlZmF1bHQgW2NvbXBhdGlibGVdOyJdfQ==

+ 13 - 0
uni-shop/layout/layout-commet/data/control.js

@@ -0,0 +1,13 @@
+export default [
+    {
+        icon:'message',
+        key:'replay'
+    },
+    {
+        icon:'live',
+        activeIcon:'live-active',
+        key:'tutop',
+        active:'is_tutop',
+        trigger:'give'
+    }
+]

+ 154 - 0
uni-shop/layout/layout-commet/main.vue

@@ -0,0 +1,154 @@
+<template>
+  <view class="layout-comment-container row"
+    :class="['layout-comment-'+size]"
+  >
+    <image :src="item.user_avatar" class="comment-avatar"></image>
+    <view class="flex-all-1 layout-comment-content">
+      <view class="row">
+        <view class="flex-all-1">
+          <view v-if="skeleton" class="layout-comment-skeleton layout-comment-skeleton-title"></view>
+          <view v-else class="line-1">{{item.user_nickname}}</view>
+
+          <view v-if="skeleton" class="layout-comment-skeleton layout-comment-skeleton-time comment-time"></view>
+          <view v-else class="line-1 comment-time">{{item.createtime}}</view>
+        </view>
+        <uni-rate :disabled="true" :value="item.rate"></uni-rate>
+      </view>
+
+      <view v-if="skeleton" class="layout-comment-skeleton layout-comment-skeleton-context layout-comment-context"></view>
+      <view v-else class="layout-comment-context">{{item.comment}}</view>
+      <view class="layout-comment-images row wrap aCenter">
+        <view class="layout-comment-image overflow"
+          v-for="(item,index) in skeleton?3:item.images"
+              :key="index"
+        >
+          <v-image :src="skeleton?item:''" class="screen_all"></v-image>
+        </view>
+      </view>
+      <view v-if="control" class="layout-comment-control-footer row jEnd">
+        <view v-for="(cItem,cIndex) in controlData" :key="cIndex"
+              class="row aCenter layout-comment-control-item"
+              @click.stop="triggerControl(cItem)"
+        >
+          <text>{{(vItem[cItem.key] <= 0 ? 0:vItem[cItem.key]) || 0}}</text>
+          <image class="layout-control-icon" :src="'/static/images/'+(cItem.active && vItem[cItem.active]?cItem.activeIcon:cItem.icon)+'.png'"></image>
+        </view>
+      </view>
+    </view>
+
+  </view>
+</template>
+
+<script>
+import props from './props';
+import uniRate from '@/components/uni-rate/uni-rate';
+import vImage from '@/components/v-image/main.vue';
+import controlData from './data/control';
+import login from '@/mixins/login';
+import Throttle from "../../utils/tool/throttle";
+export default {
+  name: "layout-comment",
+  mixins:[login],
+  data(){
+    return {
+      controlData,
+      vItem:{}
+    }
+  },
+  props,
+
+  watch:{
+    item:function (){
+      this.setVItem();
+    }
+  },
+
+  methods:{
+
+    setVItem(item){
+      if (item) {
+        if (item.is_tutop && item.tutop<=0) {
+          item.tutop = 1;
+        }
+
+        this.vItem = item;
+      }
+    },
+
+    triggerControl(cItem){
+      return this[cItem.trigger] && this[cItem.trigger]();
+    },
+
+    give(){
+      this.routerLogin(()=>{
+
+        if (this.vItem.is_storage_tutop === undefined) {
+          this.vItem.is_storage_tutop = this.vItem.is_tutop;
+        }
+
+        this.$set(this.vItem,'is_tutop',this.vItem.is_tutop === 1 ? 0 :1);
+
+        let useValue = this.vItem.tutop <= 0 ? 0 : this.vItem.tutop;
+        let value = useValue + (this.vItem.is_tutop=== 1 ? 1 : -1);
+        this.$set(this.vItem,'tutop',value < 0 ? 0 :value);
+
+        this.triggerGive();
+      });
+    },
+
+    triggerGive(){
+      if(this.vItem.is_storage_tutop !== this.vItem.is_tutop){
+        this.vItem.is_storage_tutop = this.vItem.is_tutop;
+        return this.$request({
+          url:'product/addCommondTutop',
+          data:{
+            commond_id:this.vItem.id
+          },
+          token:true
+        });
+      }
+    },
+
+    goDetail(){
+      if (this.item.id) {
+        return this.$router.navigateTo({
+          name:'product',
+          params:{
+            id:this.item.id,
+            title:this.item.title
+          }
+        })
+      }
+
+    }
+  },
+
+  computed:{
+    skeleton(){
+      return this.item.id === undefined;
+    }
+  },
+
+  created(){
+    this.setVItem(this.item);
+    this.givThrottle = new Throttle({
+      first:false,
+      call:this,
+      delay:300,
+      handle: this.triggerGive
+    });
+    this.triggerGive = this.givThrottle.supper;
+  },
+
+  beforeDestroy(){
+    this.givThrottle && this.givThrottle.destroy();
+  },
+
+  components:{
+    uniRate,
+    vImage
+  }
+}
+</script>
+
+<style scoped lang="scss" src="./style.scss"></style>

+ 20 - 0
uni-shop/layout/layout-commet/props.js

@@ -0,0 +1,20 @@
+export default {
+
+    item:{
+        type:Object,
+        default:function () {
+            return {};
+        }
+    },
+
+    size:{
+        type:String,
+        default:'default'
+    },
+
+    control:{
+        type:Boolean,
+        default:false
+    }
+
+}

+ 85 - 0
uni-shop/layout/layout-commet/style.scss

@@ -0,0 +1,85 @@
+/*  */
+.layout-comment-container{
+  padding: 30upx 0 25rpx;
+}
+/* 容器 */
+
+/* 头像 */
+.comment-avatar{
+  width: 40upx;
+  height: 40upx;
+  border-radius: 50%;
+  background-color: #EEEEEE;
+  margin-right: 10upx;
+}
+/* 头像 */
+
+/* 内容 */
+.layout-comment-content{
+  font-size: 24upx;
+  line-height: 30upx;
+  color: #333;
+  padding-top: 6upx;
+}
+.comment-time{
+  font-size: 22upx;
+  color: #999999;
+  margin-top: 10upx;
+}
+.layout-comment-context{
+  margin-top: 20upx;
+}
+$comment-image-size:192upx;
+.layout-comment-image{
+  width: $comment-image-size;
+  height: $comment-image-size;
+  margin-right: 10upx;
+  border-radius: 10upx;
+  background-color: #EEEEEE;
+  margin-top: 16upx;
+}
+.layout-comment-image:nth-of-type(3n){
+  margin-right: 0;
+}
+/* 内容 */
+
+/* 骨架 */
+.layout-comment-skeleton{
+  background-color: #f2f2f2;
+}
+.layout-comment-skeleton-title,.layout-comment-skeleton-time,.layout-comment-skeleton-context{
+  height: 30upx;
+  width: 100upx;
+}
+.layout-comment-skeleton-time{
+  height: 30upx;
+  width: 150upx;
+}
+.layout-comment-skeleton-context{
+  width: 50%;
+}
+/* 骨架 */
+
+/* 图片 */
+.layout-comment-big .layout-comment-image{
+  width: 204upx;
+  height: 204upx;
+}
+/* 图片 */
+
+/* 控件 */
+.layout-comment-control-footer{
+  padding-bottom: 15upx;
+}
+.layout-comment-control-item{
+  font-size: 28upx;
+  line-height: 30upx;
+  color: #999;
+  margin-left: 40upx;
+}
+.layout-control-icon{
+  width: 22upx;
+  height: 22upx;
+  margin-left: 8upx;
+}
+/* 控件 */

binární
uni-shop/layout/layout-coupon/images/background.png


+ 23 - 0
uni-shop/layout/layout-coupon/index.vue

@@ -0,0 +1,23 @@
+<template>
+  <view class="layout-coupon-container">
+    <image :src="require('./images/background.png')" class="layout-coupon-image"></image>
+    <!--  优惠券内容部分  -->
+    <view class="absolute layout-coupon-content row aCenter">
+      <view class="center price-group row">
+        <text><text>¥</text><text class="price-int">10</text></text>
+      </view>
+      <view class="flex-1 center">
+        <view class="line-1 layout-coupon-message">新人优惠券满88元可用</view>
+        <view class="coupon-button center">立即领取</view>
+      </view>
+    </view>
+  </view>
+</template>
+
+<script>
+export default {
+  name: "layout-coupon"
+}
+</script>
+
+<style lang="scss" src="./style.scss"></style>

+ 52 - 0
uni-shop/layout/layout-coupon/style.scss

@@ -0,0 +1,52 @@
+/* 容器 */
+.layout-coupon-container{
+  width: 630upx;
+  height: 170upx;
+  position: relative;
+}
+/* 容器 */
+
+/* 图片 */
+.layout-coupon-image{
+  width: 100%;
+  height: 100%;
+}
+/* 图片 */
+
+/* 内容部分 */
+.layout-coupon-content{
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  font-size: 32upx;
+  color: #EDEDED;
+}
+.price-group{
+  font-size:34upx;
+  line-height: 82upx;
+  height: 100%;
+  width: 200upx;
+  margin-right:20upx;
+  position: relative;
+  top: 4upx;
+  color: #fff;
+}
+.price-int{
+  font-size: 78upx;
+  margin-left: 2upx;
+}
+.layout-coupon-message{
+  text-align: center;
+  width: 100%;
+}
+.coupon-button{
+  width: 140upx;
+  height: 42upx;
+  background-color: #fff;
+  border-radius: 21upx;
+  font-size:26upx;
+  color: #1D9B53;
+  margin-top: 17upx;
+}
+/* 内容部分 */

+ 15 - 0
uni-shop/layout/layout-shop-screen/data/screen.js

@@ -0,0 +1,15 @@
+export default [
+    {
+        label:'综合',
+    },
+    {
+        label: '价格',
+        type:'order',
+        key:'price'
+    },
+    {
+        label: '销量',
+        key:'sales_volume',
+        type:'order'
+    }
+]

+ 149 - 0
uni-shop/layout/layout-shop-screen/main.vue

@@ -0,0 +1,149 @@
+<template>
+  <view class="screen_all flex container">
+    <view class="header row">
+      <view class="header-aside flex-all-1"
+            v-for="(item,index) in screenData"
+            :key="index"
+            :class="{
+          'header-aside-active': screenOption.select === index
+        }"
+            @click="triggerScreen(index)"
+      >
+        <!--    排序    -->
+        <view v-if="item.type==='order'" class="screen_all row center">
+          <view>{{item.label}}</view>
+          <view class="header-aside-order flex center"
+                :class="['header-aside-'+screenOption.value]"
+          >
+            <text class="iconfont iconfont-up header-aside-icon"></text>
+            <text class="iconfont iconfont-down header-aside-icon"></text>
+          </view>
+        </view>
+        <view v-else class="screen_all center relative">{{item.label}}
+          <view class="absolute header-aside-line center">
+            <view></view>
+          </view>
+        </view>
+      </view>
+    </view>
+    <view class="flex-all-1 shop-container">
+      <flat-list
+          @changeData="flatChangeData"
+          background="#F6F6F6"
+          @fetch="fetch"
+          :mode="skeleton?'skeleton':undefined"
+          :skeleton="skeleton"
+          :ref="base_flat_id"
+      >
+        <view class="row wrap jSpaceBetween shop-padding">
+          <layout-shop
+              v-for="(item,index) in base_flat_data.data"
+              :key="index"
+              :item="item"
+          ></layout-shop>
+        </view>
+      </flat-list>
+
+    </view>
+
+  </view>
+</template>
+
+<script>
+import screenData from './data/screen';
+import layoutShop from '@/layout/layout-shop/main.vue';
+import flatList from '@/components/flat-list/src/main.vue';
+import mixins from './mixins';
+import Throttle from '@/utils/tool/throttle';
+export default {
+
+  data(){
+    return {
+      screenData,
+      screenOption:{
+        select:0,
+        value:0
+      },
+      skeleton:undefined
+    }
+  },
+
+  mixins,
+
+  methods:{
+    triggerScreen(index){
+      if (this.screenOption.select === index) {
+
+        if (this.screenData[index].type === 'order') {
+          this.screenOption.value = this.screenOption.value === 1 ? 2 : 1;
+          this.reloadFetch();
+        }
+
+      } else {
+
+        this.screenOption.select = index;
+
+
+        if (this.screenData[index] && this.screenData[index].type === 'order') {
+          this.screenOption.value = 1;
+        }
+        this.reloadFetch();
+      }
+
+    },
+    fetch(obj){
+
+      let params = {};
+
+      let item = this.screenData[this.screenOption.select];
+
+      if (item && item.key ) {
+        params[item.key] = this.screenOption.value;
+      }
+
+      return this.$request({
+        url:'product/getProductList',
+        data:{
+          is_recommend:0,
+          ...params
+        },
+        page:obj
+      }).then((data)=>{
+
+        if (this.skeleton === undefined) {
+          this.skeleton = data.data.length;
+        }
+
+        return obj.success(data.data);
+      }).catch(obj.fail);
+    },
+
+    reloadFetch(){
+      return this.flatReload(1,true);
+    }
+
+  },
+
+  components:{
+    layoutShop,
+    flatList
+  },
+
+  created(){
+    this.throttle = new Throttle({
+      delay:200,
+      first:false,
+      handle: this.reloadFetch,
+      call: this
+    });
+    this.reloadFetch = this.throttle.supper;
+  },
+
+  beforeDestroy(){
+    this.throttle && this.throttle.destroy();
+  }
+
+}
+</script>
+
+<style scoped lang="scss" src="./style.scss"></style>

+ 3 - 0
uni-shop/layout/layout-shop-screen/mixins/index.js

@@ -0,0 +1,3 @@
+import flatMixins from '@/components/flat-list/export';
+
+export default [flatMixins];

+ 52 - 0
uni-shop/layout/layout-shop-screen/style.scss

@@ -0,0 +1,52 @@
+
+/* 容器 */
+.container {
+  background-color: #F6F6F6;
+}
+/* 容器 */
+
+/* 头部 */
+.header{
+  background-color: #fff;
+  height: 80upx;
+}
+.header-aside{
+  font-size: 28upx;
+  line-height: 36upx;
+}
+.header-aside-order,.header-aside{
+  color: #999;
+}
+.header-aside-active,.header-aside-active .header-aside-1 .iconfont-up,,.header-aside-active .header-aside-2 .iconfont-down{
+                                                                          color:#41AE3C
+                                                                        }
+.header-aside-line{
+  left: 0;
+  right: 0;
+  bottom: 0;
+  display: none;
+}
+.header-aside-line view{
+  width:60upx;
+  height: 4upx;
+  border-radius: 2upx;
+  background-color: #41AE3C;
+}
+.header-aside-active .header-aside-line{
+  display: flex;
+}
+.header-aside-icon{
+  font-size:20upx;
+  line-height: 17upx;
+}
+.header-aside-order{
+  margin-left: 6upx;
+}
+/* 头部 */
+
+/* 商品 */
+.shop-padding{
+  padding: 0 30upx;
+  margin-top: 20upx;
+}
+/* 商品 */

+ 48 - 0
uni-shop/layout/layout-shop/main.vue

@@ -0,0 +1,48 @@
+<template>
+  <view class="layout-shop flex" @click="goDetail">
+    <view class="layout-shop-image relative">
+      <image src="/static/images/shop-background.png" class="layout-shop-background relative"></image>
+      <view class="absolute row aCenter shop-logo-warp">
+        <image src="/static/images/logo.jpg" class="layout-shop-logo"></image>
+        <text>{{config.name}}</text>
+      </view>
+      <view class="absolute shop-image">
+        <image mode="aspectFill" :src="item.image" class="screen_all"></image>
+      </view>
+    </view>
+    <view class="flex-all-1 shop-content">
+      <view class="shop-title line-2">{{item.title}}</view>
+      <view class="line-1 shop-price-color" v-if="item.id"><text class="shop-price"><text>¥</text><text class="shop-price-target">{{item.sales_price}}</text></text></view>
+    </view>
+  </view>
+</template>
+
+<script>
+import {
+  mapState
+} from 'vuex';
+import props from './props';
+export default {
+  name: "layout-shop",
+  computed:{
+    ...mapState(['config'])
+  },
+  props,
+  methods:{
+    goDetail(){
+      if (this.item.id) {
+        return this.$router.navigateTo({
+          name:'product',
+          params:{
+            id:this.item.id,
+            title:this.item.title
+          }
+        })
+      }
+
+    }
+  }
+}
+</script>
+
+<style scoped lang="scss" src="./style.scss"></style>

+ 10 - 0
uni-shop/layout/layout-shop/props.js

@@ -0,0 +1,10 @@
+export default {
+
+    item:{
+        type:Object,
+        default:function () {
+            return {};
+        }
+    }
+
+}

+ 66 - 0
uni-shop/layout/layout-shop/style.scss

@@ -0,0 +1,66 @@
+/* 容器 */
+$size:335upx;
+.layout-shop{
+  width:$size;
+  height: 510upx;
+  background-color: #fff;
+  margin-bottom: 20upx;
+  border-radius: 10upx;
+}
+/* 容器 */
+
+/* 图片 */
+.layout-shop-image{
+  width: $size;
+  height: $size;
+}
+.layout-shop-background{
+  width: 100%;
+  height: 100%;
+  z-index: 999;
+}
+.shop-logo-warp{
+  left: 12upx;
+  top: 6upx;
+  font-size: 20upx;
+  line-height: 24upx;
+  color: #967010;
+  z-index: 9999;
+}
+.layout-shop-logo{
+  width:40upx;
+  height:40upx;
+  margin-right:8upx;
+}
+.shop-image{
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+}
+/* 图片 */
+
+/* 内容 */
+.shop-content{
+  padding: 0 20upx;
+}
+.shop-title,.shop-price{
+  font-size: 28upx;
+  color: #333;
+  line-height: 40upx;
+}
+.shop-title{
+  height: 80upx;
+  margin-top: 17upx;
+}
+.shop-price-color,.shop-price{
+  margin-top:20upx;
+  color:#FF002B;
+}
+.shop-price{
+  font-weight: 400;
+}
+.shop-price-target{
+  font-size: 38upx;
+}
+/* 内容 */

Některé soubory nejsou zobrazeny, neboť je v těchto rozdílových datech změněno mnoho souborů