Roebin %!s(int64=2) %!d(string=hai) anos
achega
935b9d1c75
Modificáronse 100 ficheiros con 13418 adicións e 0 borrados
  1. 24 0
      .hbuilderx/launch.json
  2. 34 0
      App.vue
  3. 21 0
      LICENSE
  4. 34 0
      README.en.md
  5. 90 0
      README.md
  6. 3 0
      common/api.js
  7. 3 0
      common/config.js
  8. 58 0
      common/demo.scss
  9. 65 0
      common/flex.scss
  10. 7 0
      common/mixin.js
  11. 12 0
      common/pc.js
  12. 2 0
      common/props.js
  13. 210 0
      components/app-composition.vue
  14. 234 0
      components/app-dev/html2json.js
  15. 126 0
      components/app-dev/htmlparser.js
  16. 210 0
      components/app-dev/wxDiscode.js
  17. 59 0
      components/goods/bd-detail.vue
  18. 126 0
      components/goods/htmlparser.js
  19. 228 0
      components/goods/parse.scss
  20. 138 0
      components/goods/parse.vue
  21. 210 0
      components/goods/wxDiscode.js
  22. 28 0
      components/goods/wxParseAudio.vue
  23. 123 0
      components/goods/wxParseImg.vue
  24. 52 0
      components/goods/wxParseTable.vue
  25. 143 0
      components/goods/wxParseTemplate0.vue
  26. 139 0
      components/goods/wxParseTemplate1.vue
  27. 15 0
      components/goods/wxParseVideo.vue
  28. 441 0
      components/index/app-nav-bar.vue
  29. 77 0
      components/index/app-no-goods.vue
  30. 216 0
      components/lee-popup/lee-popup.vue
  31. 1 0
      components/px-suspen-button/iconBase64.js
  32. 394 0
      components/px-suspen-button/index.vue
  33. 48 0
      main.js
  34. 149 0
      manifest.json
  35. 18 0
      package.json
  36. 62 0
      pages.json
  37. 417 0
      pages/goods-details/goods-details.vue
  38. 130 0
      pages/index/components/scroll-list.vue
  39. 559 0
      pages/index/index.scss
  40. 415 0
      pages/index/index.vue
  41. 214 0
      pages/more/more.vue
  42. 454 0
      pages/program/program.vue
  43. 20 0
      responsive/left-window.vue
  44. 10 0
      siteInfo.js
  45. 224 0
      static/app-plus/mp-html/js/handler.js
  46. 19 0
      static/app-plus/mp-html/js/uni.webview.min.js
  47. 1 0
      static/app-plus/mp-html/local.html
  48. 363 0
      static/common/js/touch-emulator.js
  49. 13 0
      static/css/border-box.scss
  50. 266 0
      static/css/flex.scss
  51. 52 0
      static/css/text.scss
  52. BIN=BIN
      static/image/1.png
  53. BIN=BIN
      static/image/2.png
  54. BIN=BIN
      static/image/bz.png
  55. BIN=BIN
      static/image/bz1.png
  56. BIN=BIN
      static/image/couimgbg.png
  57. BIN=BIN
      static/image/fangan.png
  58. BIN=BIN
      static/image/goodsimg.png
  59. BIN=BIN
      static/image/hxj.jpg
  60. BIN=BIN
      static/image/programioc.png
  61. BIN=BIN
      static/image/viewmore.png
  62. BIN=BIN
      static/image/zu.png
  63. BIN=BIN
      static/uview/common/favicon.ico
  64. BIN=BIN
      static/uview/common/gray-logo.png
  65. BIN=BIN
      static/uview/common/logo.png
  66. BIN=BIN
      static/uview/example/component.png
  67. BIN=BIN
      static/uview/example/component_select.png
  68. BIN=BIN
      static/uview/example/js.png
  69. BIN=BIN
      static/uview/example/js_bak.png
  70. BIN=BIN
      static/uview/example/js_select.png
  71. BIN=BIN
      static/uview/example/template.png
  72. BIN=BIN
      static/uview/example/template_select.png
  73. 17 0
      store/index.js
  74. 42 0
      template.h5.html
  75. 6 0
      uni.scss
  76. 89 0
      uni_modules/uni-datetime-picker/changelog.md
  77. 185 0
      uni_modules/uni-datetime-picker/components/uni-datetime-picker/calendar-item.vue
  78. 898 0
      uni_modules/uni-datetime-picker/components/uni-datetime-picker/calendar.vue
  79. 19 0
      uni_modules/uni-datetime-picker/components/uni-datetime-picker/i18n/en.json
  80. 8 0
      uni_modules/uni-datetime-picker/components/uni-datetime-picker/i18n/index.js
  81. 19 0
      uni_modules/uni-datetime-picker/components/uni-datetime-picker/i18n/zh-Hans.json
  82. 19 0
      uni_modules/uni-datetime-picker/components/uni-datetime-picker/i18n/zh-Hant.json
  83. 45 0
      uni_modules/uni-datetime-picker/components/uni-datetime-picker/keypress.js
  84. 927 0
      uni_modules/uni-datetime-picker/components/uni-datetime-picker/time-picker.vue
  85. 997 0
      uni_modules/uni-datetime-picker/components/uni-datetime-picker/uni-datetime-picker.vue
  86. 410 0
      uni_modules/uni-datetime-picker/components/uni-datetime-picker/util.js
  87. 90 0
      uni_modules/uni-datetime-picker/package.json
  88. 21 0
      uni_modules/uni-datetime-picker/readme.md
  89. 17 0
      uni_modules/uni-fab/changelog.md
  90. 475 0
      uni_modules/uni-fab/components/uni-fab/uni-fab.vue
  91. 87 0
      uni_modules/uni-fab/package.json
  92. 9 0
      uni_modules/uni-fab/readme.md
  93. 22 0
      uni_modules/uni-icons/changelog.md
  94. 1169 0
      uni_modules/uni-icons/components/uni-icons/icons.js
  95. 96 0
      uni_modules/uni-icons/components/uni-icons/uni-icons.vue
  96. 663 0
      uni_modules/uni-icons/components/uni-icons/uniicons.css
  97. BIN=BIN
      uni_modules/uni-icons/components/uni-icons/uniicons.ttf
  98. 86 0
      uni_modules/uni-icons/package.json
  99. 8 0
      uni_modules/uni-icons/readme.md
  100. 37 0
      uni_modules/uni-nav-bar/changelog.md

+ 24 - 0
.hbuilderx/launch.json

xqd
@@ -0,0 +1,24 @@
+{ // 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": [{
+     	"app-plus" : 
+     	{
+     		"launchtype" : "local"
+     	},
+     	"default" : 
+     	{
+     		"launchtype" : "local"
+     	},
+     	"h5" : 
+     	{
+     		"launchtype" : "local"
+     	},
+     	"mp-weixin" : 
+     	{
+     		"launchtype" : "local"
+     	},
+     	"type" : "uniCloud"
+     }
+    ]
+}

+ 34 - 0
App.vue

xqd
@@ -0,0 +1,34 @@
+<script>
+	export default {
+		onLaunch: function() {
+		},
+		onShow: function() {
+			
+		},
+		onHide: function() {
+			
+		}
+	}
+</script>
+
+<style lang="scss">
+	/*每个页面公共css */
+	@import "@/uni_modules/uview-ui/index.scss";
+	// @import "common/demo.scss";
+	@import "static/css/flex.scss";
+	@import "static/css/border-box.scss";
+	page{
+		padding: 0;
+		margin: 0;
+	}
+	body{
+		padding: 0;
+		margin: 0;
+	}
+	/*  #ifdef  H5  */
+	body {
+	  max-width:calc(100vw);  //最大宽度自己可以调整
+	  margin: auto !important;
+	}
+	/*  #endif  */
+</style>

+ 21 - 0
LICENSE

xqd
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2021 www.uviewui.com
+
+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.

+ 34 - 0
README.en.md

xqd
@@ -0,0 +1,34 @@
+#### Description
+{**When you're done, you can delete the content in this README and update the file with details for others getting started with your repository**}
+
+#### Software Architecture
+Software architecture description
+
+#### Installation
+
+1.  xxxx
+2.  xxxx
+3.  xxxx
+
+#### Instructions
+
+1.  xxxx
+2.  xxxx
+3.  xxxx
+
+#### Contribution
+
+1.  Fork the repository
+2.  Create Feat_xxx branch
+3.  Commit your code
+4.  Create Pull Request
+
+
+#### Gitee Feature
+
+1.  You can use Readme\_XXX.md to support different languages, such as Readme\_en.md, Readme\_zh.md
+2.  Gitee blog [blog.gitee.com](https://blog.gitee.com)
+3.  Explore open source project [https://gitee.com/explore](https://gitee.com/explore)
+4.  The most valuable open source project [GVP](https://gitee.com/gvp)
+5.  The manual of Gitee [https://gitee.com/help](https://gitee.com/help)
+6.  The most popular members  [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/)

+ 90 - 0
README.md

xqd
@@ -0,0 +1,90 @@
+<p align="center">
+    <img alt="logo" src="https://uviewui.com/common/logo.png" width="120" height="120" style="margin-bottom: 10px;">
+</p>
+<h3 align="center" style="margin: 30px 0 30px;font-weight: bold;font-size:40px;">uView 2.0</h3>
+<h3 align="center">多平台快速开发的UI框架</h3>
+
+[![stars](https://img.shields.io/github/stars/umicro/uView2.0?style=flat-square&logo=GitHub)](https://github.com/umicro/uView2.0)
+[![forks](https://img.shields.io/github/forks/umicro/uView2.0?style=flat-square&logo=GitHub)](https://github.com/umicro/uView2.0)
+[![issues](https://img.shields.io/github/issues/umicro/uView2.0?style=flat-square&logo=GitHub)](https://github.com/umicro/uView2.0/issues)
+[![Website](https://img.shields.io/badge/uView-up-blue?style=flat-square)](https://uviewui.com)
+[![release](https://img.shields.io/github/v/release/umicro/uView2.0?style=flat-square)](https://gitee.com/umicro/uView2.0/releases)
+[![license](https://img.shields.io/github/license/umicro/uView2.0?style=flat-square)](https://en.wikipedia.org/wiki/MIT_License)
+
+## 说明
+
+uView UI,是[uni-app](https://uniapp.dcloud.io/)全面兼容nvue的uni-app生态框架,全面的组件和便捷的工具会让您信手拈来,如鱼得水
+
+## [官方文档:https://v2.uviewui.com](https://v2.uviewui.com)
+
+### 官方1群:1042987248(已满)
+### 官方2群:249718512(已满)
+### 官方3群:1129077272(已满)
+### 官方4群:1084514613(已满)
+### 官方5群:863820668(已满)
+### 官方6群:745721078(已满)
+### 官方7群:627867855(已满)
+### 官方8群:496409492(已满)
+### 官方9群:828504448(已满)
+### [点击加10群交流反馈:232041042](https://jq.qq.com/?_wv=1027&k=KnbeceDU)
+
+## 特性
+
+- 全面兼容nvue,原生渲染,高性能
+- 兼容安卓,iOS,微信小程序,H5,QQ小程序,百度小程序,支付宝小程序,头条小程序
+- 60+精选组件,功能丰富,多端兼容,让您快速集成,开箱即用
+- 众多贴心的JS利器,让您飞镖在手,召之即来,百步穿杨
+- 众多的常用页面和布局,让您专注逻辑,事半功倍
+- 详尽的文档支持,现代化的演示效果
+- 按需引入,精简打包体积
+
+
+## 预览
+
+您可以通过**微信**扫码,查看最佳的演示效果。
+<br>
+<br>
+<img src="https://uviewui.com/common/weixin_mini_qrcode.png" width="220" height="220" >
+
+
+## 链接
+
+- [官方文档](https://v2.uviewui.com/)
+- [更新日志](https://v2.uviewui.com/components/changelog.html)
+- [升级指南](https://v2.uviewui.com/components/changeGuide.html)
+- [关于我们](https://v2.uviewui.com/cooperation/about.html)
+
+## 交流反馈
+
+欢迎加入我们的QQ群交流反馈:[点此跳转](https://www.uviewui.com/components/addQQGroup.html)
+
+## 安装
+
+#### **下载地址** —— [https://ext.dcloud.net.cn/plugin?id=1593](https://ext.dcloud.net.cn/plugin?id=1593)
+
+## 快速上手
+
+请通过[官网安装文档](https://v2.uviewui.com/components/install.html)了解更详细的内容
+
+## 使用方法
+配置easycom规则后,自动按需引入,无需`import`组件,直接引用即可。
+
+```html
+<template>
+	<u-button>按钮</u-button>
+</template>
+```
+
+请通过[快速上手](https://v2.uviewui.com/components/quickstart.html)了解更详细的内容
+
+
+## 捐赠uView的研发
+
+uView文档内容和框架源码全部开源免费,如果您认为uView帮到了您的开发工作,您可以捐赠uView的研发工作,捐赠无门槛,哪怕是一杯可乐也好(相信这比打赏主播更有意义)。
+
+<img src="https://uviewui.com/common/wechat.png" width="220" >
+<img style="margin-left: 100px;" src="https://uviewui.com/common/alipay.png" width="220" >
+
+## 版权信息
+uView遵循[MIT](https://en.wikipedia.org/wiki/MIT_License)开源协议,意味着您无需支付任何费用,也无需授权,即可将uView应用到您的产品中。
+

+ 3 - 0
common/api.js

xqd
@@ -0,0 +1,3 @@
+const { http } = uni.$u
+// 获取菜单
+export const fetchMenu = (params, config = {}) => http.post('/ebapi/public_api/index', params, config)

+ 3 - 0
common/config.js

xqd
@@ -0,0 +1,3 @@
+module.exports = {
+    baseUrl: 'https://api.youzixy.com'
+}

+ 58 - 0
common/demo.scss

xqd
@@ -0,0 +1,58 @@
+.u-view {
+	padding: 40px 20px 0px 20px;
+	&__title {
+		font-size: 14px;
+		color: rgb(143, 156, 162);
+		margin-bottom: 10px;
+	}
+}
+
+.u-block{
+	padding: 14px;
+	&__section{
+		margin-bottom:10px;
+	}
+	&__title {
+		margin-top:10px;
+		font-size: 15px;
+		color: $u-content-color;
+		margin-bottom:10px;
+	}
+	&__flex{
+		/* #ifndef APP-NVUE */
+		display: flex;
+		/* #endif */
+	}
+}
+
+// 使用了cell组件的icon图片样式
+.u-cell-icon {
+	width: 36rpx;
+	height: 36rpx;
+	margin-right: 8rpx;
+}
+
+.u-page {
+	padding: 15px 15px 40px 15px;
+}
+
+.u-demo-block {
+	flex: 1;
+	margin-bottom: 23px;
+	
+	&__content {
+		/* #ifndef APP-NVUE */
+		display: flex;
+		/* #endif */
+		// flex-direction: row!important;
+		// align-items: center;
+		// flex-wrap: wrap;
+	}
+	
+	&__title {
+		font-size: 14px;
+		color: rgb(143, 156, 162);
+		margin-bottom: 8px;
+	}
+}
+

+ 65 - 0
common/flex.scss

xqd
@@ -0,0 +1,65 @@
+// 主轴方向 属性值
+$directionList: row , row-reverse , column , column-reverse;
+// 主轴对齐方式 属性值
+$justifyContentList: flex-start , flex-end , center , space-between , space-around;
+// 交叉轴对齐方式 属性值
+$alignItemsList: flex-start , flex-end , center , baseline , stretch;
+
+// 三层遍历,组合所有属性值
+@each $direction in $directionList {
+  
+  // 简化一些属性值
+  $dir: $direction;
+  @if $direction == 'row' {
+    $dir: 'x';
+  }
+  @if $direction == 'column' {
+    $dir: 'y';
+  }
+  @each $justifyContent in $justifyContentList {
+
+    // 简化一些属性值
+    $JC: $justifyContent;
+    @if $justifyContent == 'flex-start' {
+      $JC: 'start';
+    }
+    @if $justifyContent == 'flex-end' {
+      $JC: 'end';
+    }
+    @if $justifyContent == 'space-between' {
+      $JC: 'between';
+    }
+    @if $justifyContent == 'space-around' {
+      $JC: 'around';
+    }
+    @each $alignItems in $alignItemsList {
+
+      // 简化一些属性值
+      $AI: $alignItems;
+      @if $alignItems == 'flex-start' {
+        $AI: 'start';
+      }
+      @if $alignItems == 'flex-end' {
+        $AI: 'end';
+      }
+      
+      // 根据变量,组合为css代码
+      @if $AI == 'center' {
+        .flex-#{$dir}-#{$JC} {
+          display: flex;
+          flex-direction: $direction;
+          justify-content: $justifyContent;
+          align-items: center;
+        }
+      }
+      @else {
+        .flex-#{$dir}-#{$JC}-#{$AI} {
+          display: flex;
+          flex-direction: $direction;
+          justify-content: $justifyContent;
+          align-items: $alignItems;
+        }
+      }
+    }
+  }
+}

+ 7 - 0
common/mixin.js

xqd
@@ -0,0 +1,7 @@
+export default {
+    data() {
+        return {
+
+        }
+    }
+}

+ 12 - 0
common/pc.js

xqd
@@ -0,0 +1,12 @@
+// #ifdef H5
+(function () {
+  var u = navigator.userAgent,
+    w = window.innerWidth;
+  if (!u.match(/AppleWebKit.*Mobile.*/) || u.indexOf("iPad") > -1) {
+    window.innerWidth = 780 * (w / 1920);
+    window.onload = function () {
+      window.innerWidth = w;
+    };
+  }
+})();
+// #endif

+ 2 - 0
common/props.js

xqd
@@ -0,0 +1,2 @@
+uni.$u.props.gap.bgColor = '#f3f4f6'
+uni.$u.props.gap.height = '10'

+ 210 - 0
components/app-composition.vue

xqd
@@ -0,0 +1,210 @@
+<template>
+    <view class="app-composition dir-left-nowrap">
+        <view @click.stop="open">
+            <view class="app-composition-goods" :class="item.goods_list.length == 2 ? 'two' : item.goods_list.length == 3 ? 'three' : item.goods_list.length == 4 ? 'four': 'five'" v-if="item.type == 1">
+                <image mode="aspectFill" :src="goods.cover_pic" v-for="goods in item.goods_list" :key="goods.id"></image>
+            </view>
+            <view class="app-composition-goods" :class="item.goods_list.length == 1 ? 'two' : item.goods_list.length == 2 ? 'three' : item.goods_list.length == 3 ? 'four': 'five'" v-if="item.type == 2">
+                <image mode="aspectFill" :src="goods.cover_pic" v-for="goods in item.host_list" :key="goods.id"></image>
+                <image mode="aspectFill" :src="goods.cover_pic" v-for="goods in item.goods_list" :key="goods.id"></image>
+            </view>
+            <slot></slot>
+        </view>
+        <view @click.stop="toDetail" class="app-composition-right">
+            <view class="dir-left-nowrap cross-center app-composition-info">
+                <view class="app-composition-type" v-if="item.type == 1 && !large" :style="{'color':theme.color}">固定</view>
+                <view class="app-composition-type" v-if="item.type == 2 && !large" :style="{'color':theme.color}">搭配</view>
+                <view class="app-composition-name t-omit" :class="large ? 'long-name' :  ''">{{item.name}}</view>
+            </view>
+            <view class="app-composition-type" v-if="item.type == 1 && large" :style="{'color':theme.color}">固定套餐</view>
+            <view class="app-composition-type" v-if="item.type == 2 && large" :style="{'color':theme.color}">搭配套餐</view>
+            <view class="app-composition-price">
+                套餐价<text :style="{'color':theme.color}">¥{{item.actual_price}}</text>
+            </view>
+            <view class="app-composition-discount">
+                最多可省<text>¥{{item.max_discount}}</text>
+            </view>
+        </view>
+    </view>
+</template>
+
+<script>
+
+	import {mapState} from 'vuex';
+	
+    export default {
+        name: "app-composition",
+	    props: {
+            item: {
+                type: Object
+            },
+            large: Boolean,
+            theme: Object
+	    },
+        data() {
+            return {
+            }
+        },
+        created() {
+            let that = this;
+        },
+        methods: {
+            open(e) {
+                this.$emit('click', e);
+            },
+            toDetail(e) {
+                this.$emit('look', e);
+            }
+        }
+    }
+</script>
+
+<style scoped lang="scss">
+    .app-composition {
+        .app-composition-goods {
+            width: #{288rpx};
+            height: #{140rpx};
+            flex-shrink: 0;
+            margin-right: #{24rpx};
+            position: relative;
+            &.two {
+                image {
+                    position: absolute;
+                    width: #{140rpx};
+                    height: #{140rpx};
+                    border-radius: #{8rpx};
+                    top: 0;
+                }
+                image:first-of-type {
+                    left: 0;
+                }
+                image:last-of-type {
+                    right: 0;
+                }
+            }
+            &.three {
+                image {
+                    position: absolute;
+                    width: #{140rpx};
+                    height: #{66rpx};
+                    border-radius: #{8rpx};
+                }
+                image:first-of-type {
+                    left: 0;
+                    top: 0;
+                    height: #{140rpx};
+                    width: #{140rpx};
+                }
+                image:nth-child(2) {
+                    top: 0;
+                    right: 0;
+                }
+                image:last-of-type {
+                    bottom: 0;
+                    right: 0;
+                }
+            }
+            &.four {
+                image {
+                    position: absolute;
+                    width: #{66rpx};
+                    height: #{66rpx};
+                    border-radius: #{8rpx};
+                }
+                image:first-of-type {
+                    left: 0;
+                    top: 0;
+                    height: #{140rpx};
+                    width: #{140rpx};
+                }
+                image:nth-child(2) {
+                    top: 0;
+                    right: 0;
+                    width: #{140rpx};
+                }
+                image:nth-child(3) {
+                    right: #{74rpx};
+                    bottom: 0;
+                }
+                image:last-of-type {
+                    right: 0;
+                    bottom: 0;
+                }
+            }
+            &.five {
+                image {
+                    position: absolute;
+                    width: #{66rpx};
+                    height: #{66rpx};
+                    border-radius: #{8rpx};
+                }
+                image:first-of-type {
+                    left: 0;
+                    top: 0;
+                    height: #{140rpx};
+                    width: #{140rpx};
+                }
+                image:nth-child(2) {
+                    top: 0;
+                    right: #{74rpx};
+                }
+                image:nth-child(3) {
+                    right: 0;
+                    top: 0;
+                }
+                image:nth-child(4) {
+                    right: #{74rpx};
+                    bottom: 0;
+                }
+                image:last-of-type {
+                    right: 0;
+                    bottom: 0;
+                }
+            }
+        }
+        .app-composition-right {
+            width: #{342rpx};
+        }
+        .app-composition-info {
+            height: #{44rpx};
+            width: #{342rpx};
+            .app-composition-type {
+                font-size: #{22rpx};
+                display: block;
+                margin: 0 #{16rpx} 0 0;
+            }
+            .app-composition-name {
+                height: #{44rpx};
+                line-height: #{44rpx};
+                width: #{260rpx};
+                font-size: #{28rpx};
+                color: #353535;
+                &.long-name {
+                    width: #{342rpx};
+                }
+            }
+        }
+        .app-composition-type {
+            padding: #{2rpx 12rpx};
+            flex-shrink: 0;
+            border: #{2rpx} solid;
+            border-radius: #{20rpx};
+            margin: #{14rpx} 0 0;
+            font-size: #{22rpx};
+            display: inline-block;
+        }
+        .app-composition-price {
+            color: #999999;
+            font-size: #{24rpx};
+            margin: #{14rpx} 0 #{10rpx};
+            text {
+                font-size: #{28rpx};
+            }
+        }
+        .app-composition-discount {
+            color: #999999;
+            font-size: #{24rpx};
+
+        }
+    }
+</style>

+ 234 - 0
components/app-dev/html2json.js

xqd
@@ -0,0 +1,234 @@
+import wxDiscode from './wxDiscode';
+import HTMLParser from './htmlparser';
+
+function makeMap(str) {
+    const obj = {};
+    const items = str.split(',');
+    for (let i = 0; i < items.length; i += 1) obj[items[i]] = true;
+    return obj;
+}
+
+const block = makeMap('br,code,address,article,applet,aside,audio,blockquote,button,canvas,center,dd,del,dir,div,dl,dt,fieldset,figcaption,figure,footer,form,frameset,h1,h2,h3,h4,h5,h6,header,hgroup,hr,iframe,ins,isindex,li,map,menu,noframes,noscript,object,ol,output,p,pre,section,script,table,tbody,td,tfoot,th,thead,tr,ul,video');
+
+const inline = makeMap('a,abbr,acronym,applet,b,basefont,bdo,big,button,cite,del,dfn,em,font,i,iframe,img,input,ins,kbd,label,map,object,q,s,samp,script,select,small,span,strike,strong,sub,sup,textarea,tt,u,var');
+
+const closeSelf = makeMap('colgroup,dd,dt,li,options,p,td,tfoot,th,thead,tr');
+
+function removeDOCTYPE(html) {
+    return /<body.*>([^]*)<\/body>/.test(html) ? RegExp.$1 : html;
+}
+
+function trimHtml(html) {
+    return html.replace(/<!--.*?-->/gi, '').replace(/\/\*.*?\*\//gi, '').replace(/[ ]+</gi, '<').replace(/<script[^]*<\/script>/gi, '').replace(/<style[^]*<\/style>/gi, '');
+}
+
+function getScreenInfo() {
+    const screen = {};
+    wx.getSystemInfo({
+        success: (res) => {
+            screen.width = res.windowWidth;
+            screen.height = res.windowHeight;
+        },
+    });
+    return screen;
+}
+
+function html2json(html, customHandler, imageProp, host) {
+    // 处理字符串
+    html = removeDOCTYPE(html);
+    // 去除注释 样式 js
+    html = trimHtml(html);
+    html = wxDiscode.strDiscode(html);
+    const bufArray = [];
+
+    const results = {
+        nodes: [],
+        imageUrls: [],
+    };
+
+    const screen = getScreenInfo();
+    function Node(tag) {
+        this.node = 'element';
+        this.tag = tag;
+        this.$screen = screen;
+    }
+
+    HTMLParser(html, {
+        start(tag, attrs, unary) {
+            const node = new Node(tag);
+
+            if (bufArray.length !== 0) {
+                const parent = bufArray[0];
+                if (parent.nodes === undefined) {
+                    parent.nodes = [];
+                }
+            }
+
+            if (block[tag]) {
+                node.tagType = 'block';
+            } else if (inline[tag]) {
+                node.tagType = 'inline';
+            } else if (closeSelf[tag]) {
+                node.tagType = 'closeSelf';
+            }
+
+            node.attr = attrs.reduce((pre, attr) => {
+                const { name } = attr;
+                let { value } = attr;
+                if (name === 'class') {
+                    node.classStr = value;
+                }
+
+                if (name === 'style') {
+                    node.styleStr = value;
+                }
+
+                if (value.match(/ /)) {
+                    value = value.split(' ');
+                }
+
+                // merge it
+                if (pre[name]) {
+                    if (Array.isArray(pre[name])) {
+                        // already array, push to last
+                        pre[name].push(value);
+                    } else {
+                        // single value, make it array
+                        pre[name] = [pre[name], value];
+                    }
+                } else {
+                    // not exist, put it
+                    pre[name] = value;
+                }
+
+                return pre;
+            }, {});
+
+            // 优化样式相关属性
+            if (node.classStr) {
+                node.classStr += ` ${node.tag}`;
+            } else {
+                node.classStr = node.tag;
+            }
+            if (node.tagType === 'inline') {
+                node.classStr += ' inline';
+            }
+
+            // 对img添加额外数据
+            if (node.tag === 'img') {
+                let imgUrl = node.attr.src;
+                imgUrl = wxDiscode.urlToHttpUrl(imgUrl, imageProp.domain);
+                Object.assign(node.attr, imageProp, {
+                    src: imgUrl || '',
+                });
+                if (imgUrl) {
+                    results.imageUrls.push(imgUrl);
+                }
+            }
+            // 处理a标签属性
+            if (node.tag === 'a') {
+                node.attr.href = node.attr.href || '';
+            }
+
+            // 处理font标签样式属性
+            if (node.tag === 'font') {
+                const fontSize = [
+                    'x-small',
+                    'small',
+                    'medium',
+                    'large',
+                    'x-large',
+                    'xx-large',
+                    '-webkit-xxx-large',
+                ];
+                const styleAttrs = {
+                    color: 'color',
+                    face: 'font-family',
+                    size: 'font-size',
+                };
+                if (!node.styleStr) node.styleStr = '';
+                Object.keys(styleAttrs).forEach((key) => {
+                    if (node.attr[key]) {
+                        const value = key === 'size' ? fontSize[node.attr[key] - 1] : node.attr[key];
+                        node.styleStr += `${styleAttrs[key]}: ${value};`;
+                    }
+                });
+            }
+
+            // 临时记录source资源
+            if (node.tag === 'source') {
+                results.source = node.attr.src;
+            }
+
+            // #ifndef MP-BAIDU
+            if (customHandler.start) {
+                customHandler.start(node, results);
+            }
+            // #endif
+
+            if (unary) {
+                // if this tag doesn't have end tag
+                // like <img src="hoge.png"/>
+                // add to parents
+                const parent = bufArray[0] || results;
+                if (parent.nodes === undefined) {
+                    parent.nodes = [];
+                }
+                parent.nodes.push(node);
+            } else {
+                bufArray.unshift(node);
+            }
+        },
+        end(tag) {
+            const node = bufArray.shift();
+            if (node.tag !== tag) {
+                console.error('invalid state: mismatch end tag');
+            }
+
+            if (node.tag === 'video' && results.source) {
+                node.attr.src = results.source;
+                delete results.source;
+            }
+
+            if (customHandler.end) {
+                customHandler.end(node, results);
+            }
+
+            if (bufArray.length === 0) {
+                results.nodes.push(node);
+            } else {
+                const parent = bufArray[0];
+                if (!parent.nodes) {
+                    parent.nodes = [];
+                }
+                parent.nodes.push(node);
+            }
+        },
+        chars(text) {
+            if (!text.trim()) return;
+
+            const node = {
+                node: 'text',
+                text,
+            };
+
+            if (customHandler.chars) {
+                customHandler.chars(node, results);
+            }
+
+            if (bufArray.length === 0) {
+                results.nodes.push(node);
+            } else {
+                const parent = bufArray[0];
+                if (parent.nodes === undefined) {
+                    parent.nodes = [];
+                }
+                parent.nodes.push(node);
+            }
+        },
+    });
+
+    return results;
+}
+
+export default html2json;

+ 126 - 0
components/app-dev/htmlparser.js

xqd
@@ -0,0 +1,126 @@
+
+const startTag = /^<([-A-Za-z0-9_]+)((?:\s+[a-zA-Z0-9_:][-a-zA-Z0-9_:.]*(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)>/;
+const endTag = /^<\/([-A-Za-z0-9_]+)[^>]*>/;
+const attr = /([a-zA-Z0-9_:][-a-zA-Z0-9_:.]*)(?:\s*=\s*(?:(?:"((?:\\.|[^"])*)")|(?:'((?:\\.|[^'])*)')|([^>\s]+)))?/g;
+
+function makeMap(str) {
+    const obj = {};
+    const items = str.split(',');
+    for (let i = 0; i < items.length; i += 1) obj[items[i]] = true;
+    return obj;
+}
+
+const empty = makeMap('area,base,basefont,br,col,frame,hr,img,input,link,meta,param,embed,command,keygen,source,track,wbr');
+
+const block = makeMap('address,code,article,applet,aside,audio,blockquote,button,canvas,center,dd,del,dir,div,dl,dt,fieldset,figcaption,figure,footer,form,frameset,h1,h2,h3,h4,h5,h6,header,hgroup,hr,iframe,ins,isindex,li,map,menu,noframes,noscript,object,ol,output,p,pre,section,script,table,tbody,td,tfoot,th,thead,tr,ul,video');
+
+const inline = makeMap('a,abbr,acronym,applet,b,basefont,bdo,big,br,button,cite,del,dfn,em,font,i,iframe,img,input,ins,kbd,label,map,object,q,s,samp,script,select,small,span,strike,strong,sub,sup,textarea,tt,u,var');
+
+const closeSelf = makeMap('colgroup,dd,dt,li,options,p,td,tfoot,th,thead,tr');
+
+const fillAttrs = makeMap('checked,compact,declare,defer,disabled,ismap,multiple,nohref,noresize,noshade,nowrap,readonly,selected');
+
+function HTMLParser(html, handler) {
+    let index;
+    let chars;
+    let match;
+    let last = html;
+    const stack = [];
+
+    stack.last = () => stack[stack.length - 1];
+
+    function parseEndTag(tag, tagName) {
+        let pos;
+        if (!tagName) {
+            pos = 0;
+        } else {
+            tagName = tagName.toLowerCase();
+            for (pos = stack.length - 1; pos >= 0; pos -= 1) {
+                if (stack[pos] === tagName) break;
+            }
+        }
+        if (pos >= 0) {
+            for (let i = stack.length - 1; i >= pos; i -= 1) {
+                if (handler.end) handler.end(stack[i]);
+            }
+            stack.length = pos;
+        }
+    }
+
+    function parseStartTag(tag, tagName, rest, unary) {
+        tagName = tagName.toLowerCase();
+
+        if (block[tagName]) {
+            while (stack.last() && inline[stack.last()]) {
+                parseEndTag('', stack.last());
+            }
+        }
+
+        if (closeSelf[tagName] && stack.last() === tagName) {
+            parseEndTag('', tagName);
+        }
+
+        unary = empty[tagName] || !!unary;
+
+        if (!unary) stack.push(tagName);
+
+        if (handler.start) {
+            const attrs = [];
+
+            rest.replace(attr, function genAttr(matches, name) {
+                const value = arguments[2] || arguments[3] || arguments[4] || (fillAttrs[name] ? name : '');
+
+                attrs.push({
+                    name,
+                    value,
+                    escaped: value.replace(/(^|[^\\])"/g, '$1\\"'), // "
+                });
+            });
+
+            if (handler.start) {
+                handler.start(tagName, attrs, unary);
+            }
+        }
+    }
+
+    while (html) {
+        chars = true;
+
+        if (html.indexOf('</') === 0) {
+            match = html.match(endTag);
+            if (match) {
+                html = html.substring(match[0].length);
+                match[0].replace(endTag, parseEndTag);
+                chars = false;
+            }
+        } else if (html.indexOf('<') === 0) {
+            match = html.match(startTag);
+            if (match) {
+                html = html.substring(match[0].length);
+                match[0].replace(startTag, parseStartTag);
+                chars = false;
+            }
+        }
+
+        if (chars) {
+            index = html.indexOf('<');
+            let text = '';
+            while (index === 0) {
+                text += '<';
+                html = html.substring(1);
+                index = html.indexOf('<');
+            }
+            text += index < 0 ? html : html.substring(0, index);
+            html = index < 0 ? '' : html.substring(index);
+
+            if (handler.chars) handler.chars(text);
+        }
+
+        if (html === last) throw new Error(`Parse Error: ${html}`);
+        last = html;
+    }
+
+    parseEndTag();
+}
+
+export default HTMLParser;

+ 210 - 0
components/app-dev/wxDiscode.js

xqd
@@ -0,0 +1,210 @@
+// HTML 支持的数学符号
+function strNumDiscode(str) {
+    str = str.replace(/&forall;/g, '∀');
+    str = str.replace(/&part;/g, '∂');
+    str = str.replace(/&exist;/g, '∃');
+    str = str.replace(/&empty;/g, '∅');
+    str = str.replace(/&nabla;/g, '∇');
+    str = str.replace(/&isin;/g, '∈');
+    str = str.replace(/&notin;/g, '∉');
+    str = str.replace(/&ni;/g, '∋');
+    str = str.replace(/&prod;/g, '∏');
+    str = str.replace(/&sum;/g, '∑');
+    str = str.replace(/&minus;/g, '−');
+    str = str.replace(/&lowast;/g, '∗');
+    str = str.replace(/&radic;/g, '√');
+    str = str.replace(/&prop;/g, '∝');
+    str = str.replace(/&infin;/g, '∞');
+    str = str.replace(/&ang;/g, '∠');
+    str = str.replace(/&and;/g, '∧');
+    str = str.replace(/&or;/g, '∨');
+    str = str.replace(/&cap;/g, '∩');
+    str = str.replace(/&cup;/g, '∪');
+    str = str.replace(/&int;/g, '∫');
+    str = str.replace(/&there4;/g, '∴');
+    str = str.replace(/&sim;/g, '∼');
+    str = str.replace(/&cong;/g, '≅');
+    str = str.replace(/&asymp;/g, '≈');
+    str = str.replace(/&ne;/g, '≠');
+    str = str.replace(/&le;/g, '≤');
+    str = str.replace(/&ge;/g, '≥');
+    str = str.replace(/&sub;/g, '⊂');
+    str = str.replace(/&sup;/g, '⊃');
+    str = str.replace(/&nsub;/g, '⊄');
+    str = str.replace(/&sube;/g, '⊆');
+    str = str.replace(/&supe;/g, '⊇');
+    str = str.replace(/&oplus;/g, '⊕');
+    str = str.replace(/&otimes;/g, '⊗');
+    str = str.replace(/&perp;/g, '⊥');
+    str = str.replace(/&sdot;/g, '⋅');
+    return str;
+}
+
+// HTML 支持的希腊字母
+function strGreeceDiscode(str) {
+    str = str.replace(/&Alpha;/g, 'Α');
+    str = str.replace(/&Beta;/g, 'Β');
+    str = str.replace(/&Gamma;/g, 'Γ');
+    str = str.replace(/&Delta;/g, 'Δ');
+    str = str.replace(/&Epsilon;/g, 'Ε');
+    str = str.replace(/&Zeta;/g, 'Ζ');
+    str = str.replace(/&Eta;/g, 'Η');
+    str = str.replace(/&Theta;/g, 'Θ');
+    str = str.replace(/&Iota;/g, 'Ι');
+    str = str.replace(/&Kappa;/g, 'Κ');
+    str = str.replace(/&Lambda;/g, 'Λ');
+    str = str.replace(/&Mu;/g, 'Μ');
+    str = str.replace(/&Nu;/g, 'Ν');
+    str = str.replace(/&Xi;/g, 'Ν');
+    str = str.replace(/&Omicron;/g, 'Ο');
+    str = str.replace(/&Pi;/g, 'Π');
+    str = str.replace(/&Rho;/g, 'Ρ');
+    str = str.replace(/&Sigma;/g, 'Σ');
+    str = str.replace(/&Tau;/g, 'Τ');
+    str = str.replace(/&Upsilon;/g, 'Υ');
+    str = str.replace(/&Phi;/g, 'Φ');
+    str = str.replace(/&Chi;/g, 'Χ');
+    str = str.replace(/&Psi;/g, 'Ψ');
+    str = str.replace(/&Omega;/g, 'Ω');
+
+    str = str.replace(/&alpha;/g, 'α');
+    str = str.replace(/&beta;/g, 'β');
+    str = str.replace(/&gamma;/g, 'γ');
+    str = str.replace(/&delta;/g, 'δ');
+    str = str.replace(/&epsilon;/g, 'ε');
+    str = str.replace(/&zeta;/g, 'ζ');
+    str = str.replace(/&eta;/g, 'η');
+    str = str.replace(/&theta;/g, 'θ');
+    str = str.replace(/&iota;/g, 'ι');
+    str = str.replace(/&kappa;/g, 'κ');
+    str = str.replace(/&lambda;/g, 'λ');
+    str = str.replace(/&mu;/g, 'μ');
+    str = str.replace(/&nu;/g, 'ν');
+    str = str.replace(/&xi;/g, 'ξ');
+    str = str.replace(/&omicron;/g, 'ο');
+    str = str.replace(/&pi;/g, 'π');
+    str = str.replace(/&rho;/g, 'ρ');
+    str = str.replace(/&sigmaf;/g, 'ς');
+    str = str.replace(/&sigma;/g, 'σ');
+    str = str.replace(/&tau;/g, 'τ');
+    str = str.replace(/&upsilon;/g, 'υ');
+    str = str.replace(/&phi;/g, 'φ');
+    str = str.replace(/&chi;/g, 'χ');
+    str = str.replace(/&psi;/g, 'ψ');
+    str = str.replace(/&omega;/g, 'ω');
+    str = str.replace(/&thetasym;/g, 'ϑ');
+    str = str.replace(/&upsih;/g, 'ϒ');
+    str = str.replace(/&piv;/g, 'ϖ');
+    str = str.replace(/&middot;/g, '·');
+    return str;
+}
+
+function strcharacterDiscode(str) {
+    // 加入常用解析
+    str = str.replace(/&nbsp;/g, ' ');
+    str = str.replace(/&ensp;/g, ' ');
+    str = str.replace(/&emsp;/g, ' ');
+    str = str.replace(/&quot;/g, "'");
+    str = str.replace(/&amp;/g, '&');
+    str = str.replace(/&lt;/g, '<');
+    str = str.replace(/&gt;/g, '>');
+    str = str.replace(/&#8226;/g, '•');
+
+    return str;
+}
+
+// HTML 支持的其他实体
+function strOtherDiscode(str) {
+    str = str.replace(/&OElig;/g, 'Œ');
+    str = str.replace(/&oelig;/g, 'œ');
+    str = str.replace(/&Scaron;/g, 'Š');
+    str = str.replace(/&scaron;/g, 'š');
+    str = str.replace(/&Yuml;/g, 'Ÿ');
+    str = str.replace(/&fnof;/g, 'ƒ');
+    str = str.replace(/&circ;/g, 'ˆ');
+    str = str.replace(/&tilde;/g, '˜');
+    str = str.replace(/&ensp;/g, '');
+    str = str.replace(/&emsp;/g, '');
+    str = str.replace(/&thinsp;/g, '');
+    str = str.replace(/&zwnj;/g, '');
+    str = str.replace(/&zwj;/g, '');
+    str = str.replace(/&lrm;/g, '');
+    str = str.replace(/&rlm;/g, '');
+    str = str.replace(/&ndash;/g, '–');
+    str = str.replace(/&mdash;/g, '—');
+    str = str.replace(/&lsquo;/g, '‘');
+    str = str.replace(/&rsquo;/g, '’');
+    str = str.replace(/&sbquo;/g, '‚');
+    str = str.replace(/&ldquo;/g, '“');
+    str = str.replace(/&rdquo;/g, '”');
+    str = str.replace(/&bdquo;/g, '„');
+    str = str.replace(/&dagger;/g, '†');
+    str = str.replace(/&Dagger;/g, '‡');
+    str = str.replace(/&bull;/g, '•');
+    str = str.replace(/&hellip;/g, '…');
+    str = str.replace(/&permil;/g, '‰');
+    str = str.replace(/&prime;/g, '′');
+    str = str.replace(/&Prime;/g, '″');
+    str = str.replace(/&lsaquo;/g, '‹');
+    str = str.replace(/&rsaquo;/g, '›');
+    str = str.replace(/&oline;/g, '‾');
+    str = str.replace(/&euro;/g, '€');
+    str = str.replace(/&trade;/g, '™');
+
+    str = str.replace(/&larr;/g, '←');
+    str = str.replace(/&uarr;/g, '↑');
+    str = str.replace(/&rarr;/g, '→');
+    str = str.replace(/&darr;/g, '↓');
+    str = str.replace(/&harr;/g, '↔');
+    str = str.replace(/&crarr;/g, '↵');
+    str = str.replace(/&lceil;/g, '⌈');
+    str = str.replace(/&rceil;/g, '⌉');
+
+    str = str.replace(/&lfloor;/g, '⌊');
+    str = str.replace(/&rfloor;/g, '⌋');
+    str = str.replace(/&loz;/g, '◊');
+    str = str.replace(/&spades;/g, '♠');
+    str = str.replace(/&clubs;/g, '♣');
+    str = str.replace(/&hearts;/g, '♥');
+
+    str = str.replace(/&diams;/g, '♦');
+    str = str.replace(/&#39;/g, "'");
+    return str;
+}
+
+function strDiscode(str) {
+    str = strNumDiscode(str);
+    str = strGreeceDiscode(str);
+    str = strcharacterDiscode(str);
+    str = strOtherDiscode(str);
+    return str;
+}
+
+function urlToHttpUrl(url, domain) {
+    if (/^\/\//.test(url)) {
+        return `https:${url}`;
+    } else if (/^\//.test(url)) {
+        return `https://${domain}${url}`;
+    } else if (Array.isArray(url)) {
+        return arrUrl(url, domain);
+    }
+    return url;
+}
+
+function arrUrl(data, domain) {
+    for (let i = 0; i < data.length; i++) {
+        if (data[i] !== '') {
+            if (/^\/\//.test(data[i])) {
+                return `https:${data[i]}`;
+            } else if (/^\//.test(data[i])) {
+                return `https://${domain}${data[i]}`;
+            }
+            return data[i];
+        }
+    }
+}
+
+export default {
+    strDiscode,
+    urlToHttpUrl,
+};

+ 59 - 0
components/goods/bd-detail.vue

xqd
@@ -0,0 +1,59 @@
+<template>
+    <view class="bd-detail">
+        <view class="detail" v-if="newDetail">
+            <!-- <image src="https://shop.9026.com/web/statics/image/mall/static/icon/goods-detail.png"></image> -->
+            <app-rich-text
+                :content="newDetail"
+            ></app-rich-text>
+        </view>
+    </view>
+</template>
+
+<script>
+import appRichText from "./parse.vue";
+
+export default {
+    name: "bd-detail",
+
+    components: {
+        'app-rich-text': appRichText,
+    },
+
+    props: {
+        detail: {
+            type: String,
+            default() {
+                return '';
+            }
+        },
+    },
+    created() {
+        // this.$store.dispatch('gConfig/setImageWidth', 48);
+    },
+    computed: {
+        newDetail() {
+            let detail = '';
+            if (this.detail) {
+                detail = this.detail;
+            }
+            return detail;
+        },
+    }
+}
+</script>
+
+<style scoped lang="scss">
+
+.bd-detail {
+    width: 750upx;
+    // padding: 0upx 0 0 0;
+    background-color: #ffffff;
+    border-radius: 15upx;
+    // margin: 24upx 24upx 0 24upx;
+    image {
+        width: 100%;
+        height: #{80rpx};
+        display: block;
+    }
+}
+</style>

+ 126 - 0
components/goods/htmlparser.js

xqd
@@ -0,0 +1,126 @@
+
+const startTag = /^<([-A-Za-z0-9_]+)((?:\s+[a-zA-Z0-9_:][-a-zA-Z0-9_:.]*(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)>/;
+const endTag = /^<\/([-A-Za-z0-9_]+)[^>]*>/;
+const attr = /([a-zA-Z0-9_:][-a-zA-Z0-9_:.]*)(?:\s*=\s*(?:(?:"((?:\\.|[^"])*)")|(?:'((?:\\.|[^'])*)')|([^>\s]+)))?/g;
+
+function makeMap(str) {
+    const obj = {};
+    const items = str.split(',');
+    for (let i = 0; i < items.length; i += 1) obj[items[i]] = true;
+    return obj;
+}
+
+const empty = makeMap('area,base,basefont,br,col,frame,hr,img,input,link,meta,param,embed,command,keygen,source,track,wbr');
+
+const block = makeMap('address,code,article,applet,aside,audio,blockquote,button,canvas,center,dd,del,dir,div,dl,dt,fieldset,figcaption,figure,footer,form,frameset,h1,h2,h3,h4,h5,h6,header,hgroup,hr,iframe,ins,isindex,li,map,menu,noframes,noscript,object,ol,output,p,pre,section,script,table,tbody,td,tfoot,th,thead,tr,ul,video');
+
+const inline = makeMap('a,abbr,acronym,applet,b,basefont,bdo,big,br,button,cite,del,dfn,em,font,i,iframe,img,input,ins,kbd,label,map,object,q,s,samp,script,select,small,span,strike,strong,sub,sup,textarea,tt,u,var');
+
+const closeSelf = makeMap('colgroup,dd,dt,li,options,p,td,tfoot,th,thead,tr');
+
+const fillAttrs = makeMap('checked,compact,declare,defer,disabled,ismap,multiple,nohref,noresize,noshade,nowrap,readonly,selected');
+
+function HTMLParser(html, handler) {
+    let index;
+    let chars;
+    let match;
+    let last = html;
+    const stack = [];
+
+    stack.last = () => stack[stack.length - 1];
+
+    function parseEndTag(tag, tagName) {
+        let pos;
+        if (!tagName) {
+            pos = 0;
+        } else {
+            tagName = tagName.toLowerCase();
+            for (pos = stack.length - 1; pos >= 0; pos -= 1) {
+                if (stack[pos] === tagName) break;
+            }
+        }
+        if (pos >= 0) {
+            for (let i = stack.length - 1; i >= pos; i -= 1) {
+                if (handler.end) handler.end(stack[i]);
+            }
+            stack.length = pos;
+        }
+    }
+
+    function parseStartTag(tag, tagName, rest, unary) {
+        tagName = tagName.toLowerCase();
+
+        if (block[tagName]) {
+            while (stack.last() && inline[stack.last()]) {
+                parseEndTag('', stack.last());
+            }
+        }
+
+        if (closeSelf[tagName] && stack.last() === tagName) {
+            parseEndTag('', tagName);
+        }
+
+        unary = empty[tagName] || !!unary;
+
+        if (!unary) stack.push(tagName);
+
+        if (handler.start) {
+            const attrs = [];
+
+            rest.replace(attr, function genAttr(matches, name) {
+                const value = arguments[2] || arguments[3] || arguments[4] || (fillAttrs[name] ? name : '');
+
+                attrs.push({
+                    name,
+                    value,
+                    escaped: value.replace(/(^|[^\\])"/g, '$1\\"'), // "
+                });
+            });
+
+            if (handler.start) {
+                handler.start(tagName, attrs, unary);
+            }
+        }
+    }
+
+    while (html) {
+        chars = true;
+
+        if (html.indexOf('</') === 0) {
+            match = html.match(endTag);
+            if (match) {
+                html = html.substring(match[0].length);
+                match[0].replace(endTag, parseEndTag);
+                chars = false;
+            }
+        } else if (html.indexOf('<') === 0) {
+            match = html.match(startTag);
+            if (match) {
+                html = html.substring(match[0].length);
+                match[0].replace(startTag, parseStartTag);
+                chars = false;
+            }
+        }
+
+        if (chars) {
+            index = html.indexOf('<');
+            let text = '';
+            while (index === 0) {
+                text += '<';
+                html = html.substring(1);
+                index = html.indexOf('<');
+            }
+            text += index < 0 ? html : html.substring(0, index);
+            html = index < 0 ? '' : html.substring(index);
+
+            if (handler.chars) handler.chars(text);
+        }
+
+        if (html === last) throw new Error(`Parse Error: ${html}`);
+        last = html;
+    }
+
+    parseEndTag();
+}
+
+export default HTMLParser;

+ 228 - 0
components/goods/parse.scss

xqd
@@ -0,0 +1,228 @@
+.wxParse {
+	user-select:none;
+	width: 100%;
+	color: #333;
+	line-height: 1.5;
+	font-size: 1em;
+	text-align:justify;/* //左右两端对齐 */
+	white-space: pre-wrap
+}
+.wxParse view ,.wxParse uni-view{
+	word-break: break-word;
+}
+.wxParse .p {
+	padding-bottom: 0.5em;
+	clear: both;
+	/* letter-spacing: 0;//字间距 */
+}
+.wxParse .inline {
+  display: inline;
+  margin: 0;
+  padding: 0;
+}
+
+.wxParse .div {
+  margin: 0;
+  padding: 0;
+  display: block;
+}
+
+.wxParse .h1{
+  font-size: 2em;
+  line-height: 1.2em;
+  margin: 0.67em 0;
+}
+.wxParse .h2{
+  font-size: 1.5em;
+  margin: 0.83em 0;
+}
+.wxParse .h3{
+  font-size: 1.17em;
+  margin: 1em 0;
+}
+.wxParse .h4{
+  margin: 1.33em 0;
+}
+.wxParse .h5{
+  font-size: 0.83em;
+  margin: 1.67em 0;
+}
+.wxParse .h6{
+  font-size: 0.83em;
+  margin: 1.67em 0;
+}
+
+.wxParse .h1,
+.wxParse .h2,
+.wxParse .h3,
+.wxParse .h4,
+.wxParse .h5,
+.wxParse .h6,
+.wxParse .b,
+.wxParse .strong{
+  font-weight: bolder;
+}
+
+.wxParse .i,
+.wxParse .cite,
+.wxParse .em,
+.wxParse .var,
+.wxParse .address {
+  font-style: italic;
+}
+
+.wxParse .pre,
+.wxParse .tt,
+.wxParse .code,
+.wxParse .kbd,
+.wxParse .samp {
+  font-family: monospace;
+}
+.wxParse .pre {
+  overflow: auto;
+  background: #f5f5f5;
+  padding: 16rpx;
+  white-space: pre;
+  margin: 1em 0rpx;
+}
+.wxParse .code {
+  display: inline;
+  background: #f5f5f5;
+}
+
+.wxParse .big {
+  font-size: 1.17em;
+}
+
+.wxParse .small,
+.wxParse .sub,
+.wxParse .sup {
+  font-size: 0.83em;
+}
+
+.wxParse .sub {
+  vertical-align: sub;
+}
+.wxParse .sup {
+  vertical-align: super;
+}
+
+.wxParse .s,
+.wxParse .strike,
+.wxParse .del {
+  text-decoration: line-through;
+}
+
+.wxParse .strong,
+.wxParse .s {
+  display: inline;
+}
+
+.wxParse .a {
+  color: deepskyblue;
+}
+
+.wxParse .video {
+  text-align: center;
+  margin: 22rpx 0;
+}
+
+.wxParse .video-video {
+  width: 100%;
+}
+.wxParse .uni-image{
+	max-width: 100%;
+}
+.wxParse .img {
+  display: block;
+  max-width: 100%;
+  margin-bottom: 0em;/* //与p标签底部padding同时修改 */
+  overflow: hidden;
+}
+
+.wxParse .blockquote {
+  margin: 10rpx 0;
+  padding: 22rpx 0 22rpx 22rpx;
+  //font-family: Courier, Calibri, '宋体';
+  background: #f5f5f5;
+  border-left: 6rpx solid #dbdbdb;
+}
+.wxParse .blockquote .p {
+  margin: 0;
+}
+.wxParse .ul, .wxParse .ol {
+  display: block;
+  margin: 1em 0;
+  padding-left: 2em;
+}
+.wxParse .ol {
+  list-style-type: disc;
+}
+.wxParse .ol {
+  list-style-type: decimal;
+}
+.wxParse .ol>weixin-parse-template,.wxParse .ul>weixin-parse-template {
+  display: list-item;
+  align-items: baseline;
+  text-align: match-parent;
+}
+
+.wxParse .ol>.li,.wxParse .ul>.li {
+  display: list-item;
+  align-items: baseline;
+  text-align: match-parent;
+}
+.wxParse .ul .ul, .wxParse .ol .ul {
+  list-style-type: circle;
+}
+.wxParse .ol .ol .ul, .wxParse .ol .ul .ul, .wxParse .ul .ol .ul, .wxParse .ul .ul .ul {
+    list-style-type: square;
+}
+
+.wxParse .u {
+  text-decoration: underline;
+}
+.wxParse .hide {
+  display: none;
+}
+.wxParse .del {
+  display: inline;
+}
+.wxParse .figure {
+  overflow: hidden;
+}
+.wxParse .table .table{
+	border-collapse:collapse;
+	box-sizing: border-box;
+	/* 内边框 */
+	border: 1px solid #dadada;
+	width: 100%;
+}
+.wxParse .tbody{
+	border-collapse:collapse;
+	box-sizing: border-box;
+	/* 内边框 */
+	border: 1px solid #dadada;
+}
+.wxParse .thead, .wxParse .tfoot, .wxParse .th{
+	border-collapse:collapse;
+	box-sizing: border-box;
+	background: #ececec;
+	font-weight: 40;
+}
+.wxParse .tr {
+	border-collapse:collapse;
+	box-sizing: border-box;
+	border: 2px solid #F0AD4E; 
+	overflow:auto;
+}
+.wxParse .th,
+.wxParse .td{
+	border-collapse:collapse;
+	box-sizing: border-box;
+	border: 2rpx solid #dadada;
+	overflow:auto;
+}
+.wxParse .audio, .wxParse .uni-audio-default{
+	display: block;
+}

+ 138 - 0
components/goods/parse.vue

xqd
@@ -0,0 +1,138 @@
+
+<template>
+	<!--基础元素-->
+	<view class="wxParse" :class="className" :style="'user-select:' + userSelect + ';background-color: ' + background">
+		<template v-if="!loading">
+			<block v-for="(node, index) of nodes" :key="index"  >
+				<wxParseTemplate :node="node" :parent-node="nodes"/>
+			</block>
+		</template>
+	</view>
+</template>
+
+<script>
+import HtmlToJson from '@/components/app-dev/html2json.js';
+import wxParseTemplate from './wxParseTemplate0';
+
+export default {
+	name: 'wxParse',
+	props: {
+		userSelect:{
+			type:String,
+			default:'text'
+		},
+		imgOptions:{
+			type:[Object,Boolean],
+			default:function(){
+				return {
+					loop: false,
+					indicator:'number',
+					longPressActions:false
+				}
+			}
+		},
+		loading: {
+			type: Boolean,
+			default: false
+		},
+		background: {
+			type: String,
+			default: '#ffffff'
+		},
+		className: {
+			type: String,
+			default: ''
+		},
+		content: {
+			type: String,
+			default: ''
+		},
+		noData: {
+			type: String,
+			default: ''
+		},
+		startHandler: {
+			type: Function,
+			default() {
+				return node => {
+					node.attr.class = null;
+					node.attr.style = null;
+				};
+			}
+		},
+		endHandler: {
+			type: Function,
+			default: null
+		},
+		charsHandler: {
+			type: Function,
+			default: null
+		},
+		imageProp: {
+			type: Object,
+			default() {
+				return {
+					mode: 'aspectFit',
+					padding: 0,
+					lazyLoad: false,
+					domain: ''
+				};
+			}
+		},
+	},
+	components: {
+		wxParseTemplate,
+	},
+	data() {
+		return {
+			nodes:{},
+			imageUrls: [],
+			wxParseWidth:{
+				value:0
+			}
+		};
+	},
+	mounted() {
+		this.setHtml();
+	},
+	methods: {
+		setHtml(){
+			let { content, noData, imageProp, startHandler, endHandler, charsHandler } = this;
+			let parseData = content || noData;
+			let customHandler = {
+				start: startHandler,
+				end: endHandler,
+				chars: charsHandler
+			};
+			let results = HtmlToJson(parseData, customHandler, imageProp, this);
+			this.imageUrls = results.imageUrls;
+			this.nodes = results.nodes;
+		},
+		navigate(href, $event) {
+			this.$emit('navigate', href, $event);
+		},
+		preview(src, $event) {
+			if (!this.imageUrls.length || typeof this.imgOptions === 'boolean'){
+			} else {
+				uni.previewImage({
+					current: src,
+					urls: this.imageUrls,
+					loop: this.imgOptions.loop,
+					indicator: this.imgOptions.indicator,
+					longPressActions: this.imgOptions.longPressActions
+				});
+			}
+			this.$emit('preview', src, $event);
+		},
+		removeImageUrl(src) {
+			const { imageUrls } = this;
+			imageUrls.splice(imageUrls.indexOf(src), 1);
+		}
+	},
+	watch: {
+      content() {
+        this.setHtml();
+      }
+    }
+};
+</script>

+ 210 - 0
components/goods/wxDiscode.js

xqd
@@ -0,0 +1,210 @@
+// HTML 支持的数学符号
+function strNumDiscode(str) {
+    str = str.replace(/&forall;/g, '∀');
+    str = str.replace(/&part;/g, '∂');
+    str = str.replace(/&exist;/g, '∃');
+    str = str.replace(/&empty;/g, '∅');
+    str = str.replace(/&nabla;/g, '∇');
+    str = str.replace(/&isin;/g, '∈');
+    str = str.replace(/&notin;/g, '∉');
+    str = str.replace(/&ni;/g, '∋');
+    str = str.replace(/&prod;/g, '∏');
+    str = str.replace(/&sum;/g, '∑');
+    str = str.replace(/&minus;/g, '−');
+    str = str.replace(/&lowast;/g, '∗');
+    str = str.replace(/&radic;/g, '√');
+    str = str.replace(/&prop;/g, '∝');
+    str = str.replace(/&infin;/g, '∞');
+    str = str.replace(/&ang;/g, '∠');
+    str = str.replace(/&and;/g, '∧');
+    str = str.replace(/&or;/g, '∨');
+    str = str.replace(/&cap;/g, '∩');
+    str = str.replace(/&cup;/g, '∪');
+    str = str.replace(/&int;/g, '∫');
+    str = str.replace(/&there4;/g, '∴');
+    str = str.replace(/&sim;/g, '∼');
+    str = str.replace(/&cong;/g, '≅');
+    str = str.replace(/&asymp;/g, '≈');
+    str = str.replace(/&ne;/g, '≠');
+    str = str.replace(/&le;/g, '≤');
+    str = str.replace(/&ge;/g, '≥');
+    str = str.replace(/&sub;/g, '⊂');
+    str = str.replace(/&sup;/g, '⊃');
+    str = str.replace(/&nsub;/g, '⊄');
+    str = str.replace(/&sube;/g, '⊆');
+    str = str.replace(/&supe;/g, '⊇');
+    str = str.replace(/&oplus;/g, '⊕');
+    str = str.replace(/&otimes;/g, '⊗');
+    str = str.replace(/&perp;/g, '⊥');
+    str = str.replace(/&sdot;/g, '⋅');
+    return str;
+}
+
+// HTML 支持的希腊字母
+function strGreeceDiscode(str) {
+    str = str.replace(/&Alpha;/g, 'Α');
+    str = str.replace(/&Beta;/g, 'Β');
+    str = str.replace(/&Gamma;/g, 'Γ');
+    str = str.replace(/&Delta;/g, 'Δ');
+    str = str.replace(/&Epsilon;/g, 'Ε');
+    str = str.replace(/&Zeta;/g, 'Ζ');
+    str = str.replace(/&Eta;/g, 'Η');
+    str = str.replace(/&Theta;/g, 'Θ');
+    str = str.replace(/&Iota;/g, 'Ι');
+    str = str.replace(/&Kappa;/g, 'Κ');
+    str = str.replace(/&Lambda;/g, 'Λ');
+    str = str.replace(/&Mu;/g, 'Μ');
+    str = str.replace(/&Nu;/g, 'Ν');
+    str = str.replace(/&Xi;/g, 'Ν');
+    str = str.replace(/&Omicron;/g, 'Ο');
+    str = str.replace(/&Pi;/g, 'Π');
+    str = str.replace(/&Rho;/g, 'Ρ');
+    str = str.replace(/&Sigma;/g, 'Σ');
+    str = str.replace(/&Tau;/g, 'Τ');
+    str = str.replace(/&Upsilon;/g, 'Υ');
+    str = str.replace(/&Phi;/g, 'Φ');
+    str = str.replace(/&Chi;/g, 'Χ');
+    str = str.replace(/&Psi;/g, 'Ψ');
+    str = str.replace(/&Omega;/g, 'Ω');
+
+    str = str.replace(/&alpha;/g, 'α');
+    str = str.replace(/&beta;/g, 'β');
+    str = str.replace(/&gamma;/g, 'γ');
+    str = str.replace(/&delta;/g, 'δ');
+    str = str.replace(/&epsilon;/g, 'ε');
+    str = str.replace(/&zeta;/g, 'ζ');
+    str = str.replace(/&eta;/g, 'η');
+    str = str.replace(/&theta;/g, 'θ');
+    str = str.replace(/&iota;/g, 'ι');
+    str = str.replace(/&kappa;/g, 'κ');
+    str = str.replace(/&lambda;/g, 'λ');
+    str = str.replace(/&mu;/g, 'μ');
+    str = str.replace(/&nu;/g, 'ν');
+    str = str.replace(/&xi;/g, 'ξ');
+    str = str.replace(/&omicron;/g, 'ο');
+    str = str.replace(/&pi;/g, 'π');
+    str = str.replace(/&rho;/g, 'ρ');
+    str = str.replace(/&sigmaf;/g, 'ς');
+    str = str.replace(/&sigma;/g, 'σ');
+    str = str.replace(/&tau;/g, 'τ');
+    str = str.replace(/&upsilon;/g, 'υ');
+    str = str.replace(/&phi;/g, 'φ');
+    str = str.replace(/&chi;/g, 'χ');
+    str = str.replace(/&psi;/g, 'ψ');
+    str = str.replace(/&omega;/g, 'ω');
+    str = str.replace(/&thetasym;/g, 'ϑ');
+    str = str.replace(/&upsih;/g, 'ϒ');
+    str = str.replace(/&piv;/g, 'ϖ');
+    str = str.replace(/&middot;/g, '·');
+    return str;
+}
+
+function strcharacterDiscode(str) {
+    // 加入常用解析
+    str = str.replace(/&nbsp;/g, ' ');
+    str = str.replace(/&ensp;/g, ' ');
+    str = str.replace(/&emsp;/g, ' ');
+    str = str.replace(/&quot;/g, "'");
+    str = str.replace(/&amp;/g, '&');
+    str = str.replace(/&lt;/g, '<');
+    str = str.replace(/&gt;/g, '>');
+    str = str.replace(/&#8226;/g, '•');
+
+    return str;
+}
+
+// HTML 支持的其他实体
+function strOtherDiscode(str) {
+    str = str.replace(/&OElig;/g, 'Œ');
+    str = str.replace(/&oelig;/g, 'œ');
+    str = str.replace(/&Scaron;/g, 'Š');
+    str = str.replace(/&scaron;/g, 'š');
+    str = str.replace(/&Yuml;/g, 'Ÿ');
+    str = str.replace(/&fnof;/g, 'ƒ');
+    str = str.replace(/&circ;/g, 'ˆ');
+    str = str.replace(/&tilde;/g, '˜');
+    str = str.replace(/&ensp;/g, '');
+    str = str.replace(/&emsp;/g, '');
+    str = str.replace(/&thinsp;/g, '');
+    str = str.replace(/&zwnj;/g, '');
+    str = str.replace(/&zwj;/g, '');
+    str = str.replace(/&lrm;/g, '');
+    str = str.replace(/&rlm;/g, '');
+    str = str.replace(/&ndash;/g, '–');
+    str = str.replace(/&mdash;/g, '—');
+    str = str.replace(/&lsquo;/g, '‘');
+    str = str.replace(/&rsquo;/g, '’');
+    str = str.replace(/&sbquo;/g, '‚');
+    str = str.replace(/&ldquo;/g, '“');
+    str = str.replace(/&rdquo;/g, '”');
+    str = str.replace(/&bdquo;/g, '„');
+    str = str.replace(/&dagger;/g, '†');
+    str = str.replace(/&Dagger;/g, '‡');
+    str = str.replace(/&bull;/g, '•');
+    str = str.replace(/&hellip;/g, '…');
+    str = str.replace(/&permil;/g, '‰');
+    str = str.replace(/&prime;/g, '′');
+    str = str.replace(/&Prime;/g, '″');
+    str = str.replace(/&lsaquo;/g, '‹');
+    str = str.replace(/&rsaquo;/g, '›');
+    str = str.replace(/&oline;/g, '‾');
+    str = str.replace(/&euro;/g, '€');
+    str = str.replace(/&trade;/g, '™');
+
+    str = str.replace(/&larr;/g, '←');
+    str = str.replace(/&uarr;/g, '↑');
+    str = str.replace(/&rarr;/g, '→');
+    str = str.replace(/&darr;/g, '↓');
+    str = str.replace(/&harr;/g, '↔');
+    str = str.replace(/&crarr;/g, '↵');
+    str = str.replace(/&lceil;/g, '⌈');
+    str = str.replace(/&rceil;/g, '⌉');
+
+    str = str.replace(/&lfloor;/g, '⌊');
+    str = str.replace(/&rfloor;/g, '⌋');
+    str = str.replace(/&loz;/g, '◊');
+    str = str.replace(/&spades;/g, '♠');
+    str = str.replace(/&clubs;/g, '♣');
+    str = str.replace(/&hearts;/g, '♥');
+
+    str = str.replace(/&diams;/g, '♦');
+    str = str.replace(/&#39;/g, "'");
+    return str;
+}
+
+function strDiscode(str) {
+    str = strNumDiscode(str);
+    str = strGreeceDiscode(str);
+    str = strcharacterDiscode(str);
+    str = strOtherDiscode(str);
+    return str;
+}
+
+function urlToHttpUrl(url, domain) {
+    if (/^\/\//.test(url)) {
+        return `https:${url}`;
+    } else if (/^\//.test(url)) {
+        return `https://${domain}${url}`;
+    } else if (Array.isArray(url)) {
+        return arrUrl(url, domain);
+    }
+    return url;
+}
+
+function arrUrl(data, domain) {
+    for (let i = 0; i < data.length; i++) {
+        if (data[i] !== '') {
+            if (/^\/\//.test(data[i])) {
+                return `https:${data[i]}`;
+            } else if (/^\//.test(data[i])) {
+                return `https://${domain}${data[i]}`;
+            }
+            return data[i];
+        }
+    }
+}
+
+export default {
+    strDiscode,
+    urlToHttpUrl,
+};

+ 28 - 0
components/goods/wxParseAudio.vue

xqd
@@ -0,0 +1,28 @@
+<template>
+	<!-- '<audio/>' 组件不再维护,建议使用能力更强的 'uni.createInnerAudioContext' 接口 有时间再改-->
+  <!--增加audio标签支持-->
+  <audio
+    :id="node.attr.id"
+    :class="node.classStr"
+    :style="node.styleStr"
+    :src="node.attr.src"
+    :loop="node.attr.loop"
+    :poster="node.attr.poster"
+    :name="node.attr.name"
+    :author="node.attr.author"
+    controls></audio>
+</template>
+
+<script>
+export default {
+  name: 'wxParseAudio',
+  props: {
+    node: {
+      type: Object,
+      default() {
+        return {};
+      },
+    },
+  },
+};
+</script>

+ 123 - 0
components/goods/wxParseImg.vue

xqd
@@ -0,0 +1,123 @@
+<template>
+    <image
+        :lazy-load="newNode.attr.lazyLoad"
+        :class="newNode.classStr"
+        :style="newStyleStr || newNode.styleStr"
+        :data-src="newNode.attr.src"
+        :src="newNode.attr.src"
+        @tap="wxParseImgTap"
+        @load="wxParseImgLoad"
+    />
+</template>
+
+<script>
+    import {mapState} from 'vuex';
+
+    export default {
+        name: 'wxParseImg',
+
+        data() {
+            return {
+                style: '',
+                preview: true,
+				windowWidth:'',
+            };
+        },
+
+        props: {
+            node: {
+                type: Object,
+                default() {
+                    return {};
+                }
+            },
+            parentNode: {}
+        },
+
+        computed: {
+            newStyleStr: function() {
+                if (this.parentNode.styleStr && this.parentNode.styleStr.indexOf('text-align: center') > -1) {
+                    this.style += 'margin: 0 auto';
+                }
+                return this.style;
+            },
+            // ...mapState('gConfig', {
+            //     windowWidth: (state) => {
+            //         return state.imageWidth;
+            //     },
+            // }),
+            newNode: function() {
+                return this.node;
+            }
+        },
+        methods: {
+            wxParseImgTap(e) {
+                if (!this.preview) return;
+                const { src } = e.currentTarget.dataset;
+                if (!src) return;
+                let parent = this.$parent;
+                while (!parent.preview || typeof parent.preview !== 'function') {
+                    parent = parent.$parent;
+                }
+                parent.preview(src, e);
+            },
+            // 图片视觉宽高计算函数区
+            wxParseImgLoad(e) {
+                const { src } = e.currentTarget.dataset;
+                if (!src) return;
+                let { width, height } = e.mp.detail;
+                const recal = this.wxAutoImageCal(width, height);
+                const { imageheight, imageWidth } = recal;
+                const { padding, mode } = this.newNode.attr;//删除padding
+                const { styleStr } = this.newNode;
+                const imageHeightStyle = mode === 'widthFix' ? '' : `height: ${imageheight}px;`;
+                this.$nextTick().then(() => {
+                    this.style = `${styleStr ? styleStr: null}; ${imageHeightStyle}; width: ${imageWidth}; padding: 0 ${+padding}px;display:block;`;//删除padding
+                });
+            },
+            wxAutoImageCal(originalWidth, originalHeight) {
+				try {
+					const res = uni.getSystemInfoSync();
+					this.windowWidth=res.windowWidth
+					// console.log(this.windowWidth);
+				} catch (e) {
+					console.log(e)
+				}
+				
+                // 获取图片的原始长宽
+                const windowWidth = this.windowWidth;
+
+                const results = {};
+
+                if (originalWidth < 60 || originalHeight < 60) {
+                    const { src } = this.newNode.attr;
+                    let parent = this.$parent;
+                    while (!parent.preview || typeof parent.preview !== 'function') {
+                        parent = parent.$parent;
+                    }
+                    parent.removeImageUrl(src);
+                    this.preview = false;
+                }
+                results.imageWidth = originalWidth;
+                results.imageheight = originalHeight;
+                // 判断按照那种方式进行缩放
+                if (originalWidth  > windowWidth) {
+                    // 在图片width大于手机屏幕width时候
+                    results.imageWidth = `100%`;
+                    results.imageheight = windowWidth * (uni.upx2px(originalHeight) / uni.upx2px(originalWidth));
+                } else {
+                    // 否则展示原来的数据
+                    results.imageWidth = `100vw`;
+                    results.imageheight =windowWidth * (uni.upx2px(originalHeight) / uni.upx2px(originalWidth));
+                }
+                return results;
+            }
+        }
+    };
+</script>
+
+<style lang="scss">
+    image:host{
+        width: 702upx;
+    }
+</style>

+ 52 - 0
components/goods/wxParseTable.vue

xqd
@@ -0,0 +1,52 @@
+<template>
+	<rich-text style="width: 100%" :nodes="nodes" :class="node.classStr"></rich-text>
+</template>
+<script>
+export default {
+	name: 'wxParseTable',
+	props: {
+		node: {
+			type: Object,
+			default() {
+				return {};
+			},
+		},
+	},
+	data() {
+		return {
+			nodes:[]
+		};
+	},
+	mounted() {
+		this.nodes=this.loadNode([this.node]);
+	},
+	methods: {
+		loadNode(node) {
+			let obj = [];
+			for (let children of node) {
+				if (children.node=='element') {
+					let t = {
+						name:children.tag,
+						attrs: {
+							class: children.classStr,
+							style: children.styleStr,
+						},
+						children: children.nodes?this.loadNode(children.nodes):[]
+					};
+					
+					obj.push(t)
+				} else if(children.node=='text'){
+					obj.push({
+						type: 'text',
+						text: children.text
+					})
+				}
+			}
+			return obj
+		}
+	}
+};
+</script>
+<style>
+	@import url("./parse.scss");
+</style>

+ 143 - 0
components/goods/wxParseTemplate0.vue

xqd
@@ -0,0 +1,143 @@
+<template>
+	<!--判断是否是标签节点-->
+	<block v-if="node.node === 'element'">
+
+		<!--button类型-->
+		<button v-if="node.tag === 'button'" type="default" size="mini" :class="node.classStr" :style="node.styleStr">
+			<we-parse-template-1 :node="node" />
+		</button>
+		
+		<!--a类型-->
+		<view v-else-if="node.tag === 'a'" @click="wxParseATap(node.attr,$event)" :class="node.classStr" :data-href="node.attr.href" :style="node.styleStr">
+			<block v-for="(item, index) of node.nodes" :key="index">
+				<we-parse-template-1 :node="item" :parent-node="node"/>
+			</block>
+		</view>
+		
+		<!--div类型-->
+		<view v-else-if="node.tag === 'div'" :class="node.classStr" :style="node.styleStr">
+			<block v-for="(item, index) of node.nodes" :key="index">
+				<we-parse-template-1 :node="item" :parent-node="node"/>
+			</block>
+		</view>
+		
+		<!--span类型-->
+		<view v-else-if="node.tag === 'span'" :class="node.classStr" :style="node.styleStr">
+			<block v-for="(item, index) of node.nodes" :key="index">
+				<we-parse-template-1 :node="item" :parent-node="node"/>
+			</block>
+		</view>
+		
+		<!--p类型-->
+		<view v-else-if="node.tag === 'p'" :class="node.classStr" :style="node.styleStr">
+			<block v-for="(item, index) of node.nodes" :key="index">
+				<we-parse-template-1 :node="item" :parent-node="node"/>
+			</block>
+		</view>
+		
+		<!--ul类型-->
+		<view v-else-if="node.tag === 'ul'" :class="node.classStr" :style="node.styleStr">
+			<block v-for="(item, index) of node.nodes" :key="index">
+				<we-parse-template-1 :node="item" :parent-node="node"/>
+			</block>
+		</view>
+		
+		<!--li类型-->
+		<view v-else-if="node.tag === 'li'" :class="node.classStr" :style="node.styleStr">
+			<block v-for="(item, index) of node.nodes" :key="index">
+				<we-parse-template-1 :node="item" :parent-node="node"/>
+			</block>
+		</view>
+		
+		<!--table类型-->
+		<wx-parse-table v-else-if="node.tag === 'table'" :style="node.styleStr" :node="node" />
+		
+		<!--br类型-->
+		<text v-else-if="node.tag === 'br'">\n</text>
+
+		<!--video类型-->
+		<wx-parse-video :node="node" v-else-if="node.tag === 'video'"/>
+	
+		<!--audio类型-->
+		<wx-parse-audio :node="node" v-else-if="node.tag === 'audio'"/>
+	
+		<!--img类型-->
+		<wx-parse-img :parent-node="parentNode" :node="node" v-else-if="node.tag === 'img'" :style="node.styleStr"/>
+		
+		<!-- strong标签 -->
+		<view v-else-if="node.tag === 'strong'" style="font-weight: bold;display: inline;">
+			<block v-for="(item, index) of node.nodes" :key="index">
+				<we-parse-template-1 :node="item" :parent-node="node"/>
+			</block>
+		</view>
+		
+		<!-- em标签 -->
+		<view v-else-if="node.tag === 'em'" style="font-style: italic">
+			<block v-for="(node, index) of node.nodes" :key="index">
+				<we-parse-template-1 :node="node" />
+			</block>
+		</view>
+		
+		<!-- section标签 -->
+		<view v-else-if="node.tag === 'section'" >
+			<block v-for="(node, index) of node.nodes" :key="index">
+				<we-parse-template-1 :node="node" />
+			</block>
+		</view>
+		
+		<!--其他标签-->
+		<view v-else :class="node.classStr" :style="node.styleStr">
+			<block v-for="(item, index) of node.nodes" :key="index">
+				<we-parse-template-1 :node="item" :parent-node="node"/>
+			</block>
+		</view>
+	</block>
+	
+	<!--判断是否是文本节点-->
+	<block v-else-if="node.node === 'text'">
+		<text>{{node.text}}</text>
+	</block>
+</template>
+
+<script>
+	
+	import wxParseTemplate from './wxParseTemplate1';
+	import wxParseImg from './wxParseImg';
+	import wxParseVideo from './wxParseVideo';
+	import wxParseAudio from './wxParseAudio';
+	import wxParseTable from './wxParseTable';
+
+	export default {
+		name: 'wxParseTemplate0',
+
+		props: {
+			node: {
+			    type: Object,
+				default() {
+			        return {}
+				}
+			},
+            parentNode: {}
+		},
+		components: {
+			'we-parse-template-1': wxParseTemplate,
+			wxParseImg,
+			wxParseVideo,
+			wxParseAudio,
+			wxParseTable
+		},
+		methods: {
+			wxParseATap(attr,e) {
+				const {
+					href
+				} = e.currentTarget.dataset;// TODO currentTarget才有dataset
+				if (!href) return;
+				let parent = this.$parent;
+				while(!parent.preview || typeof parent.preview !== 'function') {// TODO 遍历获取父节点执行方法
+					parent = parent.$parent;
+				}
+				parent.navigate(href, e, attr);
+			},
+		}
+	};
+</script>

+ 139 - 0
components/goods/wxParseTemplate1.vue

xqd
@@ -0,0 +1,139 @@
+<template>
+	<!--判断是否是标签节点-->
+	<block v-if="node.node === 'element'">
+		<!--button类型-->
+		<button v-if="node.tag === 'button'" type="default" size="mini" :class="node.classStr" :style="node.styleStr">
+			<wx-parse-template :node="node" />
+		</button>
+		
+		<!--a类型-->
+		<view v-else-if="node.tag === 'a'" @click="wxParseATap(node.attr,$event)" :class="node.classStr" :data-href="node.attr.href" :style="node.styleStr">
+			<block v-for="(item, index) of node.nodes" :key="index">
+				<wx-parse-template :node="item" :parent-node="node"/>
+			</block>
+		</view>
+		
+		<!--div类型-->
+		<view v-else-if="node.tag === 'div'" :class="node.classStr" :style="node.styleStr">
+			<block v-for="(item, index) of node.nodes" :key="index">
+				<wx-parse-template :node="item" :parent-node="node"/>
+			</block>
+		</view>
+		
+		<!--span类型-->
+		<view v-else-if="node.tag === 'span'" :class="node.classStr" :style="node.styleStr">
+            <block v-for="(item, index) of node.nodes" :key="index">
+                <wx-parse-template :node="item" :parent-node="node"/>
+            </block>
+		</view>
+		
+		<!--p类型-->
+		<view v-else-if="node.tag === 'p'" :class="node.classStr" :style="node.styleStr">
+            <block v-for="(item, index) of node.nodes" :key="index">
+                <wx-parse-template :node="item" :parent-node="node"/>
+            </block>
+		</view>
+		
+		<!--ul类型-->
+		<view v-else-if="node.tag === 'ul'" :class="node.classStr" :style="node.styleStr">
+            <block v-for="(item, index) of node.nodes" :key="index">
+                <wx-parse-template :node="item" :parent-node="node"/>
+            </block>
+		</view>
+		
+		<!--li类型-->
+		<view v-else-if="node.tag === 'li'" :class="node.classStr" :style="node.styleStr">
+            <block v-for="(item, index) of node.nodes" :key="index">
+                <wx-parse-template :node="item" :parent-node="node"/>
+            </block>
+		</view>
+		
+		<!--table类型-->
+		<wx-parse-table v-else-if="node.tag === 'table'" :class="node.classStr" :style="node.styleStr" :node="node" />
+		
+		<!--br类型-->
+		<text v-else-if="node.tag === 'br'">\n</text>
+		
+		<!--video类型-->
+		<wx-parse-video :node="node" v-else-if="node.tag === 'video'"/>
+	
+		<!--audio类型-->
+		<wx-parse-audio :node="node" v-else-if="node.tag === 'audio'"/>
+	
+		<!--img类型-->
+		<wx-parse-img :parent-node="parentNode" :node="node" v-else-if="node.tag === 'img'" :style="node.styleStr"/>
+		
+		<!-- strong标签 -->
+		<view v-else-if="node.tag === 'strong'" style="display: inline;font-weight: bold;">
+            <block v-for="(item, index) of node.nodes" :key="index">
+                <wx-parse-template :node="item" :parent-node="node"/>
+            </block>
+		</view>
+		
+		<!-- em标签 -->
+		<view v-else-if="node.tag === 'em'" style="font-style: italic">
+            <block v-for="(item, index) of node.nodes" :key="index">
+                <wx-parse-template :node="item" :parent-node="node"/>
+            </block>
+		</view>
+		
+		<!-- section标签 -->
+		<view v-else-if="node.tag === 'section'" >
+            <block v-for="(item, index) of node.nodes" :key="index">
+                <wx-parse-template :node="item" :parent-node="node"/>
+            </block>
+		</view>
+		
+<!--		&lt;!&ndash;其他标签&ndash;&gt;-->
+		<view v-else :class="node.classStr" :style="node.styleStr">
+			<block v-for="(item, index) of node.nodes" :key="index">
+				<wx-parse-template :node="item" :parent-node="node"/>
+			</block>
+		</view>
+	</block>
+	
+	<!--判断是否是文本节点-->
+	<block v-else-if="node.node === 'text'">
+		<text>{{node.text}}</text>
+	</block>
+</template>
+
+<script>
+    // #ifdef MP
+	import wxParseTemplate from './wxParseTemplate0';
+	// #endif
+	import wxParseImg from './wxParseImg';
+	import wxParseVideo from './wxParseVideo';
+	import wxParseAudio from './wxParseAudio';
+	import wxParseTable from './wxParseTable';
+	
+	export default {
+		name: 'wxParseTemplate1',
+		props: {
+			node: {},
+            parentNode: {}
+		},
+		components: {
+		    // #ifdef MP
+			wxParseTemplate,
+            // #endif
+			wxParseImg,
+			wxParseVideo,
+			wxParseAudio,
+			wxParseTable
+		},
+		methods: {
+			wxParseATap(attr,e) {
+				const {
+					href
+				} = e.currentTarget.dataset;
+				if (!href) return;
+				let parent = this.$parent;
+				while(!parent.preview || typeof parent.preview !== 'function') {
+					parent = parent.$parent;
+				}
+				parent.navigate(href, e, attr);
+			},
+		},
+	};
+</script>

+ 15 - 0
components/goods/wxParseVideo.vue

xqd
@@ -0,0 +1,15 @@
+<template>
+    <!--增加video标签支持,并循环添加-->
+    <view :class="node.classStr" :style="node.styleStr">
+        <video :class="node.classStr" :style="node.styleStr" class="video-video" :src="node.attr.src"></video>
+    </view>
+</template>
+
+<script>
+    export default {
+        name: 'wxParseVideo',
+        props: {
+            node: {},
+        },
+    };
+</script>

+ 441 - 0
components/index/app-nav-bar.vue

xqd
@@ -0,0 +1,441 @@
+<template>
+    <view class="app-nav-bar">
+        <view v-if="hasHeight" class="hidden-height" :style="[hiddenHeight()]"></view>
+        <view
+            :class="{ 'app-navbar--fixed': fixed, 'app-navbar--shadow': shadow,'app-navbar--border': border, 'app-navbar--content':true}">
+            <view :style="[ordinaryStyle()]" class="app-navbar__bar cross-center">
+                <view class="dir-left-nowrap app-navbar__box">
+                    <view class="app-navbar__back cross-center box-grow-0" @click="onClickBack" v-if="pagesLength > 1">
+                        <view class="icon-back"></view>
+                    </view>
+                    <view
+                        :class="{'main-center app-navbar__right-center': position === 'center' && xStyle== 1, 'right__back': pagesLength > 1}"
+                        class="box-grow-1 dir-left-nowrap app-navbar__right">
+                        <view v-if="showLeftIcon">
+							<template v-if="xStyle==5">
+								<view class="main-left cross-center hxj-area" :style="{color:color}"  @click="leftClick">
+									<view class="tt t-omit" style="width:110rpx ;overflow: hidden;text-overflow:clip">{{selectedProperties.name?selectedProperties.name:'绑定楼盘'}}</view>
+									
+									<image src="https://t17.9026.com/web/statics/image/index/arrow-down.png" mode=""></image>
+								</view>
+							</template>
+							<template v-else>
+								<app-jump-button form :open_type="link.openType"
+								                 :url="link.url"
+								                 :params="link.params"> 
+								    <view class="cross-center box-grow-0 left-icon">
+								        <image :style="[hw_style]" :src="leftIcon"></image>
+								    </view>
+								</app-jump-button>
+							</template>
+                        </view> 
+                        <view v-if="showTitle" class="cross-center box-grow-0 title">
+                            <app-jump-button v-if="hasJump" form :open_type="link.openType"
+                                             :url="link.url"
+                                             :params="link.params">
+                                <view class="t-omit" :style="[maxWidth()]">{{ title }}</view>
+                            </app-jump-button>
+                            <view v-else class="t-omit" :style="[maxWidth()]">{{ title }}</view>
+                        </view>
+                        <view v-if="showLink" class="cross-center box-grow-0 link">
+                            <view class="link-search" @click="navGoodsSearch"
+                                  :style="{color: placeholderColor}">
+                                <view class="link-box cross-center main-between">
+                                    <view class="search-placeholder t-omit">{{ placeholder }}</view>
+                                    <view class="search-icon"></view>
+                                </view>
+                            </view>
+                        </view>
+                    </view>
+                </view>
+            </view>
+        </view>
+    </view>
+</template>
+
+<script>
+
+import {mapState} from "vuex";
+
+export default {
+    name: 'app-nav-bar',
+    data() {
+        return {
+            hw_style: {},
+        }
+    },
+    watch: {
+        'leftIcon'(newValue, oldValue) {
+            this.doSomething();
+        }
+    },
+    props: {
+        fixed: {
+            type: [Boolean, String],
+            default: true
+        },
+        shadow: {
+            type: [String, Boolean],
+            default: false
+        },
+		hasHeight: {
+		    type: Boolean,
+		    default: true,
+		},
+        border: {
+            type: [String, Boolean],
+            default: false
+        },
+        backgroundColor: {
+            type: String,
+            default: "#FFFFFF"
+        },
+		backgroundImage:{
+			type:String,
+			default:''
+		},
+        leftIcon: {
+            type: String,
+            default: ""
+        },
+        link: {
+            type: [Object,Array]
+        },
+        title: {
+            type: String,
+            default: ""
+        },
+        xStyle: {
+            type: [Number, String],
+            default: 1
+        },
+        hasMallSetting: {
+            type: [Number, String],
+            default: 1
+        },
+        color: {
+            type: String,
+            default: "#000000"
+        },
+        position: {
+            type: String,
+            default: "center"
+        },
+        placeholder: {
+            type: String,
+            default: "搜索"
+        },
+        placeholderColor: {
+            type: String,
+            default: "#666666"
+        }
+    },
+    computed: {
+        ...mapState({
+            statusBarHeight: state => state.gConfig.systemInfo.statusBarHeight,
+            mBarHeight: state => state.gConfig.mBarHeight,
+            appImg: state => state.mallConfig.__wxapp_img.mall,
+            mallNavbar: state => state.mallConfig.navbar,
+			selectedProperties: state => state.user.selectedProperties
+        }),
+        maxWidth() {
+            return () => {
+                let xstyle = parseInt(this.xStyle);
+                let width = 0;
+
+                switch (xstyle) {
+                    case 1:
+                        if (this.position === 'center') {
+                            width = uni.upx2px(360);
+                        } else {
+                            width = uni.upx2px(500);
+                        }
+                        break;
+                    case 2:
+                        width = uni.upx2px(400);
+                        break;
+                    case 4:
+                        width = uni.upx2px(200);
+                        break;
+                    default:
+                        break;
+                }
+                if (width && this.pagesLength > 1) {
+                    width -= uni.upx2px(42);
+                }
+                return Object.assign({}, {
+                    'max-width': width + 'px',
+                });
+            }
+        },
+        hiddenHeight() {
+            return () => {
+                let barHeight;
+                // #ifdef MP-WEIXIN || MP-BAIDU || MP-TOUTIAO
+                barHeight = this.statusBarHeight;
+                // #endif
+                barHeight = barHeight || 0;
+				let height = barHeight + this.mBarHeight;
+				this.$emit('headHeight', height);
+                return {
+                    height: height + 'px',
+                }
+            }
+        },
+        ordinaryStyle() {
+            return () => {
+                let color = '';
+                let backgroundColor = '';
+				let backgroundImage=''
+                if (this.hasMallSetting == 1) {
+                    color = this.mallNavbar.top_text_color;
+                    backgroundColor = this.mallNavbar.top_background_color;
+                } else {
+                    color = this.color;
+                    backgroundColor = this.backgroundColor;
+					backgroundImage =this.backgroundImage
+                }
+                let barHeight;
+                // #ifdef MP
+                barHeight = this.statusBarHeight;
+                // #endif
+                barHeight = barHeight || 0;
+                color = color || '#000000';
+                backgroundColor = backgroundColor || '#FFFFFF';
+                return Object.assign({}, {
+                    color, backgroundColor,backgroundImage,'background-size': '100% 386rpx',
+                    height: (barHeight + this.mBarHeight) + 'px',
+                    paddingTop: barHeight + 'px',
+                })
+            }
+        },
+        hasJump() {
+            return [2, 4].indexOf(parseInt(this.xStyle)) !== -1
+        },
+        showLeftIcon() {
+            return [2, 3,5].indexOf(parseInt(this.xStyle)) !== -1 && this.leftIcon
+        },
+        showTitle() {
+            return [1, 2, 4].indexOf(parseInt(this.xStyle)) !== -1 && this.title
+        },
+        showLink() {
+            return [3, 4].indexOf(parseInt(this.xStyle)) !== -1 && this.link
+        },
+        pagesLength() {
+            return getCurrentPages().length;
+        },
+    },
+    mounted() {
+        this.doSomething();
+    },
+    methods: {
+        doSomething() {
+            const maxHeight = 54;
+            const maxWidth = 100;
+            const self = this;
+            uni.getImageInfo({
+                src: self.leftIcon,
+                success: function (res) {
+                    let {height, width} = res;
+                    if (height <= maxHeight && width <= maxWidth) {
+                        // height = height;
+                        // width = width;
+                    }
+                    if (height <= maxHeight && width >= maxWidth) {
+                        height = height / (width / maxWidth);
+                        width = maxWidth;
+                    }
+                    if (height >= maxHeight && width <= maxWidth) {
+                        height = maxHeight;
+                        width = width / (height / maxHeight);
+                    }
+
+                    if (height > maxHeight && width >= maxWidth) {
+                        if (maxWidth / maxHeight > width / height) {
+                            width = width / (height / maxHeight);
+                            height = maxHeight;
+                        } else {
+                            height = height / (width / maxWidth);
+                            width = maxWidth;
+                        }
+                    }
+                    self.hw_style = {
+                        height: uni.upx2px(height) + 'px',
+                        width: uni.upx2px(width) + 'px',
+                    };
+                }
+            })
+        },
+        navGoodsSearch() {
+            uni.navigateTo({
+                url: '/pages/search/search',
+            })
+        },
+        onClickBack() {
+            uni.navigateBack({
+                delta: 1
+            })
+        },
+		leftClick(){
+			this.$emit('leftClick')
+		}
+    },
+}
+</script>
+
+<style lang="scss" scoped>
+	.hxj-area{
+		width:194rpx;
+		height: 50rpx;
+		font-size: 28rpx;
+		font-weight: 500;
+		color: #ebe4e1;
+		// border:1px solid rgba($color: #000, $alpha: 0.22);
+		background-color: rgba($color: #000, $alpha: 0.22);
+		border-radius:25px;
+		display: flex;
+		align-items: center;
+		padding: 0 20rpx;
+		box-sizing: border-box;
+		justify-content:space-between; ;
+		.tt{
+			max-width: 240rpx;
+		}
+		image{
+			width: 24rpx;
+			height: 20rpx;
+			margin-left: 6rpx;
+		}
+	}
+/* #ifdef H5 */
+$bar-height: 0;
+/* #endif */
+/* #ifdef MP */
+$bar-height: 20px;
+/* #endif */
+$app-height: 44px + $bar-height;
+$app-bg-color: #FFFFFF;
+.app-nav-bar {
+    .hidden-height {
+        height: $app-height;
+    }
+
+    .app-navbar--fixed {
+        position: fixed;
+        z-index: 9998;
+        top: 0;
+    }
+
+    .app-navbar--shadow {
+        box-shadow: 0 1px 6px #ccc;
+    }
+
+    .app-navbar--border {
+        border-bottom-width: #{1rpx};
+        border-bottom-style: solid;
+        border-bottom-color: $app-bg-color;
+    }
+
+    .app-navbar--content {
+        width: 100vw;
+        // background-color: $app-bg-color;
+        overflow: hidden;
+    }
+}
+
+.app-navbar__bar {
+    height: $app-height;
+    padding-top: $bar-height;
+
+    .app-navbar__box {
+        position: relative;
+        width: 100%;
+
+        .app-navbar__back {
+            padding-left: #{26rpx};
+
+            .icon-back {
+                background-image: url("https://shop.9026.com/web/statics/image/mall/static/icon/h5-back-2.png");
+				// background-image: url("https://t17.9026.com/web/statics/image/index/back.png");
+                background-repeat: no-repeat;
+                background-size: 100% 100%;
+				height: #{30rpx};
+				width: #{20rpx};
+				
+                // height: #{27rpx};
+                // width: #{16rpx};
+				
+				// height: #{33rpx};
+				// width: #{33rpx};
+                display: block;
+            }
+        }
+
+        .app-navbar__right {
+            > view {
+                padding-left: #{26rpx};
+            }
+
+            .left-icon image {
+                max-height: #{54rpx};
+                max-width: #{100rpx};
+                height: 0;
+                width: 0;
+                display: block;
+            }
+
+            .title {
+                font-size: #{32rpx};
+                font-weight: bold;
+
+                > view {
+                    max-width: 50vw;
+                }
+            }
+
+            .link-search {
+                width: #{298rpx};
+                height: #{55rpx};
+                background-color: #FFFFFF;
+                border-radius: #{40rpx};
+                position: relative;
+
+                .link-box {
+                    position: absolute;
+                    top: 0;
+                    left: 0;
+                    font-size: #{22rpx};
+                    height: #{55rpx};
+                    padding: 0 #{24rpx};
+                    width: 100%;
+                    border: 1upx solid #e2e2e2;
+                    border-radius: #{40rpx};
+                    .search-placeholder {
+                        max-width: #{218rpx};
+                    }
+
+                    .search-icon {
+                        background-image: url("https://shop.9026.com/web/statics/image/mall/static/icon/icon-search.png");
+                        background-repeat: no-repeat;
+                        background-size: 100% 100%;
+                        height: #{26rpx};
+                        width: #{26rpx};
+                    }
+                }
+            }
+        }
+
+        .app-navbar__right-center {
+            margin-right: 0;
+
+            .title {
+                padding-left: 0;
+            }
+        }
+
+        .app-navbar__right-center.right__back {
+            margin-right: calc(#{26rpx} + #{16rpx});
+        }
+    }
+}
+</style>

+ 77 - 0
components/index/app-no-goods.vue

xqd
@@ -0,0 +1,77 @@
+<template>
+	<view class="app-no-goods dir-top-nowrap main-center cross-center bor" :class="{'fixed':fixed}" :style="{'backgroundColor': background}">
+		
+		<image class="icon" v-if="icon" :src="icon"></image>
+		<image class="icon" v-if="is_image === 0 && !icon" src="https://shop.9026.com/web/statics/img/mall/static/no-goods.png"></image>
+		<image class="icon" v-else-if="is_image === 1 && !icon" src="https://shop.9026.com/web/statics/img/mall/static/order-empty.png"></image>
+		<text class="text" :style="{'color': color}">{{title}}</text>
+	</view>
+</template>
+
+<script>
+    export default {
+        name: "app-no-goods",
+	    
+	    props: {
+			fixed:{
+				type:Boolean,
+				default(){
+					return false
+				}
+			},
+            background: {
+                type: String,
+                default() {
+                    return '#ffffff';
+                }
+            },
+            color: {
+                type: String,
+                default() {
+                    return '#666666';
+                }
+            },
+		   	title: {
+			   type: String,
+			   default() {
+				   return '没有任何商品哦~';
+			   }
+		   },
+		   icon:{
+			 type:String,
+			   default(){
+				   return ''
+			   }
+		   },
+			is_image: {
+				type: Number,
+				default() {
+					return  0;
+				}
+			},
+	    },
+    }
+</script>
+
+<style scoped lang="scss">
+	.bor{
+		border-top: 20rpx;
+		overflow: hidden;
+	}
+	.app-no-goods {
+		width: 100%;
+		height: #{400upx};
+		.icon {
+			width: #{240upx};
+			height: #{240upx};
+		}
+		.text {
+			font-size: #{24upx};
+			line-height: 1;
+			margin-top: #{25upx};
+		}
+	}
+	.fixed{
+		position: fixed;top: 37%;left:50%;transform: translate(-50%,-50%);
+	}
+</style>

+ 216 - 0
components/lee-popup/lee-popup.vue

xqd
@@ -0,0 +1,216 @@
+<template>
+	<view class="lee-popup-mask" :class="maskClass" @click="maskClickHandler" data-role="mask">
+		<view class="lee-popup" :style="popupStyle" @transitionend="onAnimationEnd">
+			<slot></slot>
+		</view>
+	</view>
+</template>
+
+<script>
+	// 弹出层状态
+	const Status = {
+		OPEN: 'open',
+		CLOSE: 'close',
+		OPENED: 'opened',
+		CLOSED: 'closed'
+	}
+	
+	/**
+	 * LeePopup弹出层
+	 * @property {String}  type = [top|left|right|bottom] 弹出方向
+	 * 		@value top 从顶部弹出
+	 * 		@value left 从左侧弹出
+	 * 		@value right 从右侧弹出
+	 * 		@value bottom 从底部弹出
+	 * @property {String} width 横向弹出时,指定弹框宽度
+	 * @property {String} height 纵向弹出时,指定弹出高度
+	 * @property {Object} customStyle 自定义弹出层样式
+	 * @property {Boolean} animation 是否开启动画 
+	 * @property {Boolean} round 是否使用圆角
+	 * @property {String} padding 定义边距
+	 * @event {Function(status: String)} change 状态更新事件
+	 * @event {Function} open 弹层打开事件,动画未结束
+	 * @event {Function} opened 弹层打开事件,动画结束时
+	 * @event {Function} close 弹层关闭事件,动画未结束
+	 * @event {Function} closed 弹层关闭事件,动画结束时
+	 */
+	export default {
+		props: {
+			
+			// 弹窗方向
+			type: {
+				type: String,
+				default: 'top'
+			},
+			
+			// 横向弹出时,指定弹框宽度
+			width: {
+				type: String,
+				default: '440rpx'
+			},
+			
+			// 纵向弹出时,指定弹出高度
+			height: {
+				type: String,
+				default: 'auto'
+			},
+			
+			// 自定义弹出层样式
+			customStyle: {
+				type: Object,
+				default: () => {}
+			},
+			
+			// 是否开启动画
+			animation: {
+				type: Boolean,
+				default: true
+			},
+			
+			// 圆角模式
+			round: {
+				type: Boolean,
+				default: false
+			},
+			
+			// 边距
+			padding: {
+				type: String,
+				default: '30rpx'
+			}
+		},
+		data() {
+			return {
+				status: Status.CLOSED
+			}
+		},
+		computed: {
+			
+			// 是否为纵向弹出层
+			isVertical() {
+				return ['top', 'bottom'].includes(this.type)
+			},
+			
+			// 弹出层样式计算
+			popupStyle() {
+				const style = Object.assign({
+					width: this.isVertical ? '100%' : this.width,
+					height: this.isVertical ? this.height : '100%',
+					padding: this.padding
+				}, this.customStyle)
+				if (this.animation) {
+					style.transition = 'all .5s'
+				}
+				return Object.entries(style).map(item => `${item[0]}: ${item[1]}`).join(';')
+			},
+			
+			// 遮罩层类计算
+			maskClass() {
+				const list = [
+					`lee-popup-type_${this.type}`,
+					`lee-popup-status_${this.status}`,
+				]
+				if (this.round) {
+					list.push('lee-popup-round')
+				}
+				return list
+			}
+		},
+		watch: {
+			// 派发状态更新事件
+			status(status) {
+				this.$emit(status)
+				this.$emit('change', status)
+			}
+		},
+		methods: {
+			
+			// 打开弹出层
+			open() {
+				this.status = this.animation ? Status.OPEN : Status.OPENED
+			},
+			// 关闭弹出层
+			close() {
+				this.status = this.animation ? Status.CLOSE : Status.CLOSED
+			},
+			
+			// 点击遮罩层
+			maskClickHandler(e) {
+				if (e.target.dataset.role === 'mask') {
+					this.close()
+				}
+			},
+			
+			// 动画结束
+			onAnimationEnd() {
+				if (this.status === Status.OPEN) {
+					this.status = Status.OPENED
+				} else if (this.status === Status.CLOSE) {
+					this.status = Status.CLOSED
+				}
+			}
+		}
+	}
+</script>
+
+<style lang="scss">
+	$z-index: 3000;
+	
+	.lee-popup-mask {
+		top: 0;
+		left: 0;
+		width: 100%;
+		height: 100%;
+		position: fixed;
+		z-index: $z-index;
+		background-color: $uni-bg-color-mask;
+		
+		&.lee-popup-status_closed {
+			visibility: hidden;
+		}
+	}
+	
+	.lee-popup {
+		box-sizing: border-box;
+		background-color: $uni-bg-color;
+		position: absolute;
+		
+		&-round > & {
+			border-radius: $uni-border-radius-lg;
+		}
+		
+		&-type_top > & {
+			top: 0;
+			left: 0;
+			transform: translate(0, -100%);
+			border-top-left-radius: 0;
+			border-top-right-radius: 0;
+		}
+		&-type_left > & {
+			top: 0;
+			left: 0;
+			transform: translate(-100%, 0);
+			border-top-left-radius: 0;
+			border-bottom-left-radius: 0;
+		}
+		&-type_right > & {
+			top: 0;
+			right: 0;
+			transform: translate(100%, 0);
+			border-top-right-radius: 0;
+			border-bottom-right-radius: 0;
+		}
+		&-type_bottom > & {
+			left: 0;
+			bottom: 0;
+			transform: translate(0, 100%);
+			border-bottom-left-radius: 0;
+			border-bottom-right-radius: 0;
+		}
+		
+		&-status_open > &,
+		&-status_opened > & {
+			transform: translate(0, 0);
+		}
+	}
+</style>

A diferenza do arquivo foi suprimida porque é demasiado grande
+ 1 - 0
components/px-suspen-button/iconBase64.js


+ 394 - 0
components/px-suspen-button/index.vue

xqd
@@ -0,0 +1,394 @@
+<template>
+	<view class="px-suspen-button" :style="{'top':elTop,'left':elLeft,'zIndex':zindex}"
+		@touchmove.stop.prevent="onTouchMove">
+		<view :class="['center-button','px-button',{'active':open}]" @click.stop="onOpen"
+			:style="{'backgroundColor':bgColor,'height':_size,'width':_size,'zIndex':zindex+1,'opacity':opacity}">
+			<image v-if="!$slots.center" mode="widthFix" :style="{'width':_iconSize}"
+				:src="centerIcon||defaultCenterIcon">
+			</image>
+			<slot name="center"></slot>
+		</view>
+		<view :class="['top-button','px-button','ot-button',{'active-top':open,'hidden-top':!first&&!open}]"
+			:style="{'backgroundColor':bgColor,'height':_size,'width':_size,'zIndex':zindex}" @click.stop="onClickTop">
+			<image v-if="!$slots.top" mode="widthFix" :style="{'width':_iconSize}" :src="topIcon||defaultTopIcon">
+			</image>
+			<slot name="top"></slot>
+		</view>
+		<view :class="['left-button','px-button','ot-button',{'active-left':open,'hidden-left':!first&&!open}]"
+			:style="{'backgroundColor':bgColor,'height':_size,'width':_size,'zIndex':zindex}" @click.stop="onClickLeft">
+			<image v-if="!$slots.left" mode="widthFix" :style="{'width':_iconSize}" :src="leftIcon||defaultLeftIcon">
+			</image>
+			<slot name="left"></slot>
+		</view>
+		<view :class="['bottom-button','px-button','ot-button',{'active-bottom':open,'hidden-bottom':!first&&!open}]"
+			:style="{'backgroundColor':bgColor,'height':_size,'width':_size,'zIndex':zindex}"
+			@click.stop="onClickBottom">
+			<image v-if="!$slots.bottom" mode="widthFix" :style="{'width':_iconSize}"
+				:src="bottomIcon||defaultBottomIcon">
+			</image>
+			<slot name="bottom"></slot>
+		</view>
+	</view>
+</template>
+
+<script>
+	import {
+		centerIcon as defaultCenterIcon,
+		leftIcon as defaultLeftIcon,
+		topIcon as defaultTopIcon,
+		bottomIcon as defaultBottomIcon
+	} from './iconBase64.js'
+	export default {
+		props: {
+			top: {
+				type: [Number, String],
+				default: "70%"
+			},
+			left: {
+				type: [Number, String],
+				default: "85%"
+			},
+			bgColor: { //按钮底色
+				type: String,
+				default: "rgb(235, 155, 50)"
+			},
+			size: { //按钮直径大小
+				type: [Number, String],
+				default: 80
+			},
+			opacity: {
+				type: Number,
+				default: 0.8
+			},
+			move: { //可移动
+				type: Boolean,
+				default: true
+			},
+			centerIcon: {
+				type: String,
+				default: ""
+			},
+			topIcon: {
+				type: String,
+				default: ""
+			},
+			leftIcon: {
+				type: String,
+				default: ""
+			},
+			bottomIcon: {
+				type: String,
+				default: ""
+			},
+			zindex: {
+				type: Number,
+				default: 999
+			},
+			iconSize: { //图标尺寸
+				type: [Number, String],
+				default: '100%'
+			},
+			turn: { //是否可以展开
+				type: Boolean,
+				default: true
+			}
+		},
+		data() {
+			return {
+				first: true,
+				open: false,
+				elTop: '',
+				elLeft: '',
+				_size: '',
+				_iconSize: '',
+				defaultCenterIcon,
+				defaultLeftIcon,
+				defaultTopIcon,
+				defaultBottomIcon,
+				windowHeight: '',
+				windowWidth: '',
+				rate: '',
+				//#ifdef H5||APP
+				windowTop: '',
+				//#endif
+
+
+			}
+		},
+		watch: {
+			top: {
+				handler(newval) {
+					this.elTop = this.unitCheck(newval);
+
+				},
+				immediate: true
+			},
+			left: {
+				handler(newval) {
+					this.elLeft = this.unitCheck(newval);
+
+				},
+				immediate: true
+			},
+			size: {
+				handler(newval) {
+					this._size = this.unitCheck(newval);
+
+				},
+				immediate: true
+			},
+			iconSize: {
+				handler(newval) {
+					this._iconSize = this.unitCheck(newval);
+
+				},
+				immediate: true
+			},
+		},
+		mounted() {
+			uni.getSystemInfo({
+				success: res => {
+					let width = res.windowWidth;
+					let rate = 750.00 / width;
+					this.windowHeight = res.windowHeight;
+					this.windowWidth = width;
+					this.rate = rate;
+					//#ifdef H5||APP
+					this.windowTop = res.windowTop;
+					//#endif
+				},
+			});
+		},
+		methods: {
+			unitCheck(value) {
+				const val = String(value);
+				if (!val.includes('px') && !val.includes('%')) {
+					return `${val}rpx`;
+				}
+				return val;
+
+			},
+			onOpen() {
+				if (this.turn) {
+					this.open = !this.open;
+					this.first = false;
+					 this.$emit( this.open ? 'open':'close')
+				}
+				else{
+					this.$emit('click')
+				}
+			},
+			onTouchMove(e) {
+				if (!this.move) {
+					return
+				}
+
+				let x = e.touches[0].clientX;
+				let y = e.touches[0].clientY;
+
+				if (x < 0) {
+					x = 0;
+				} else if (x > (this.windowWidth - this.size / this.rate)) {
+					x = this.windowWidth - this.size / this.rate;
+				}
+
+				//#ifndef H5||APP-PLUS
+				if (y < 0) {
+					y = 0;
+
+				} else if (y > (this.windowHeight - this.size / this.rate)) {
+					y = this.windowHeight - this.size / this.rate;
+
+				}
+				//#endif
+
+				//#ifdef H5||APP-PLUS
+				if (y < this.windowTop) {
+					y = this.windowTop;
+
+				} else if (y > (this.windowHeight - this.size / this.rate + this.windowTop)) {
+					y = this.windowHeight - this.size / this.rate + this.windowTop;
+
+				}
+				//#endif
+
+
+				this.elLeft = x + "px";
+				this.elTop = y + "px";
+			},
+			onClickTop() {
+				this.$emit('top')
+			},
+			onClickLeft() {
+				this.$emit('left')
+			},
+			onClickBottom() {
+				this.$emit('bottom')
+			}
+		}
+	}
+</script>
+
+<style lang="scss" scoped>
+	.px-suspen-button {
+		position: fixed;
+		z-index: 999;
+	}
+
+	.px-button {
+		position: absolute;
+		border-radius: 50%;
+		top: 0rpx;
+		left: 0rpx;
+		height: 80rpx;
+		width: 80rpx;
+		background: rgb(235, 155, 50);
+		display: flex;
+		flex-direction: column;
+		justify-content: center;
+		align-items: center;
+		opacity: 0.8;
+		z-index: 999;
+		overflow: hidden;
+
+		&.center-button {
+			z-index: 1000;
+		}
+
+		&.active {
+			animation: 0.5s ease center forwards;
+		}
+
+		&.ot-button {
+			display: none;
+			opacity: 0;
+
+			&.active-top {
+				animation: 0.5s ease top forwards;
+				display: flex;
+			}
+
+			&.hidden-top {
+				animation: 0.5s ease hidetop forwards;
+				display: flex;
+			}
+
+			&.active-left {
+				animation: 0.5s ease left forwards;
+				display: flex;
+			}
+
+			&.hidden-left {
+				animation: 0.5s ease hideleft forwards;
+				display: flex;
+			}
+
+			&.active-bottom {
+				animation: 0.5s ease bottom forwards;
+				display: flex;
+			}
+
+			&.hidden-bottom {
+				animation: 0.5s ease hidebottom forwards;
+				display: flex;
+			}
+		}
+
+
+	}
+
+	@keyframes center {
+		0% {}
+
+		100% {
+			transform: rotate(315deg);
+			opacity: 1
+		}
+	}
+
+	@keyframes bottom {
+		0% {
+			opacity: 0
+		}
+
+		100% {
+			top: 100rpx;
+			left: -120rpx;
+			transform: rotate(360deg);
+			opacity: 1
+		}
+	}
+
+	@keyframes left {
+		0% {
+			opacity: 0
+		}
+
+		100% {
+			top: 0rpx;
+			left: -180rpx;
+			transform: rotate(360deg);
+			opacity: 1
+		}
+	}
+
+	@keyframes top {
+		0% {
+			opacity: 0
+		}
+
+		100% {
+			top: -100rpx;
+			left: -120rpx;
+			transform: rotate(360deg);
+			opacity: 1
+		}
+	}
+
+	@keyframes hideleft {
+		0% {
+			top: 0rpx;
+			left: -180rpx;
+			opacity: 1
+		}
+
+		100% {
+			top: 0rpx;
+			left: 0rpx;
+			transform: rotate(360deg);
+			opacity: 0
+		}
+
+	}
+
+	@keyframes hidebottom {
+		0% {
+			top: 100rpx;
+			left: -120rpx;
+			opacity: 1
+		}
+
+		100% {
+			top: 0rpx;
+			left: 0rpx;
+			transform: rotate(360deg);
+			opacity: 0
+		}
+
+	}
+
+	@keyframes hidetop {
+		0% {
+			top: -100rpx;
+			left: -120rpx;
+			opacity: 1
+		}
+
+		100% {
+			top: 0rpx;
+			left: 0rpx;
+			transform: rotate(360deg);
+			opacity: 0
+		}
+	}
+</style>

+ 48 - 0
main.js

xqd
@@ -0,0 +1,48 @@
+import Vue from 'vue'
+import App from './App'
+import storge from 'utils/storge.js'
+// vuex
+import store from './store'
+import '@/common/pc.js'
+// 引入全局uView
+import uView from '@/uni_modules/uview-ui'
+import {
+	post
+} from 'utils/request.post.js'
+import mixin from './common/mixin'
+
+Vue.prototype.$store = store
+
+Vue.config.productionTip = false
+Vue.prototype.$post = post;
+Vue.prototype.$storge = storge;
+App.mpType = 'app'
+Vue.use(uView)
+Vue.prototype.$toast = function(msg, callback = false) {
+	uni.showToast({
+		icon: 'none',
+		title: msg,
+	})
+	if (callback !== false) {
+		setTimeout(() => {
+			callback()
+		}, 2000)
+	}
+}
+// #ifdef MP
+// 引入uView对小程序分享的mixin封装
+const mpShare = require('@/uni_modules/uview-ui/libs/mixin/mpShare.js')
+Vue.mixin(mpShare)
+// #endif
+
+Vue.mixin(mixin)
+
+const app = new Vue({
+    store,
+    ...App
+})
+
+// // 引入请求封装
+require('./util/request/index')(app)
+
+app.$mount()

+ 149 - 0
manifest.json

xqd
@@ -0,0 +1,149 @@
+{
+    "name" : "hxj_android",
+    "appid" : "__UNI__ECB31DD",
+    "description" : "多平台快速开发的UI框架",
+    "versionName" : "1.0.0",
+    "versionCode" : 1,
+    "transformPx" : false,
+    "app-plus" : {
+        "optimization" : {
+            "subPackages" : true
+        },
+        "safearea" : {
+            "bottom" : {
+                "offset" : "none"
+            }
+        },
+        "splashscreen" : {
+            "alwaysShowBeforeRender" : true,
+            "waiting" : true,
+            "autoclose" : true,
+            "delay" : 0
+        },
+        "usingComponents" : true,
+        "nvueCompiler" : "uni-app",
+        "compilerVersion" : 3,
+        "modules" : {
+            "Webview-x5" : {}
+        },
+        "distribute" : {
+            "android" : {
+                "permissions" : [
+                    "<uses-feature android:name=\"android.hardware.camera\"/>",
+                    "<uses-feature android:name=\"android.hardware.camera.autofocus\"/>",
+                    "<uses-permission android:name=\"android.permission.ACCESS_COARSE_LOCATION\"/>",
+                    "<uses-permission android:name=\"android.permission.ACCESS_FINE_LOCATION\"/>",
+                    "<uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\"/>",
+                    "<uses-permission android:name=\"android.permission.ACCESS_WIFI_STATE\"/>",
+                    "<uses-permission android:name=\"android.permission.CALL_PHONE\"/>",
+                    "<uses-permission android:name=\"android.permission.CAMERA\"/>",
+                    "<uses-permission android:name=\"android.permission.CHANGE_NETWORK_STATE\"/>",
+                    "<uses-permission android:name=\"android.permission.CHANGE_WIFI_STATE\"/>",
+                    "<uses-permission android:name=\"android.permission.FLASHLIGHT\"/>",
+                    "<uses-permission android:name=\"android.permission.GET_ACCOUNTS\"/>",
+                    "<uses-permission android:name=\"android.permission.MODIFY_AUDIO_SETTINGS\"/>",
+                    "<uses-permission android:name=\"android.permission.MOUNT_UNMOUNT_FILESYSTEMS\"/>",
+                    "<uses-permission android:name=\"android.permission.READ_LOGS\"/>",
+                    "<uses-permission android:name=\"android.permission.READ_PHONE_STATE\"/>",
+                    "<uses-permission android:name=\"android.permission.RECORD_AUDIO\"/>",
+                    "<uses-permission android:name=\"android.permission.VIBRATE\"/>",
+                    "<uses-permission android:name=\"android.permission.WAKE_LOCK\"/>",
+                    "<uses-permission android:name=\"android.permission.WRITE_CONTACTS\"/>",
+                    "<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>"
+                ],
+                "abiFilters" : [ "armeabi-v7a", "arm64-v8a" ]
+            },
+            "ios" : {
+                "idfa" : false
+            },
+            "sdkConfigs" : {
+                "ad" : {}
+            },
+            "icons" : {
+                "android" : {
+                    "hdpi" : "",
+                    "xhdpi" : "",
+                    "xxhdpi" : "",
+                    "xxxhdpi" : ""
+                },
+                "ios" : {
+                    "appstore" : "",
+                    "ipad" : {
+                        "app" : "",
+                        "app@2x" : "",
+                        "notification" : "",
+                        "notification@2x" : "",
+                        "proapp@2x" : "",
+                        "settings" : "",
+                        "settings@2x" : "",
+                        "spotlight" : "",
+                        "spotlight@2x" : ""
+                    },
+                    "iphone" : {
+                        "app@2x" : "",
+                        "app@3x" : "",
+                        "notification@2x" : "",
+                        "notification@3x" : "",
+                        "settings@2x" : "",
+                        "settings@3x" : "",
+                        "spotlight@2x" : "",
+                        "spotlight@3x" : ""
+                    }
+                }
+            }
+        }
+    },
+    "quickapp" : {},
+    "mp-weixin" : {
+        "appid" : "",
+        "setting" : {
+            "urlCheck" : false,
+            "es6" : false,
+            "minified" : false,
+            "postcss" : false
+        },
+        "optimization" : {
+            "subPackages" : true
+        },
+        "usingComponents" : true
+    },
+    "mp-alipay" : {
+        "usingComponents" : true,
+        "component2" : true
+    },
+    "mp-qq" : {
+        "optimization" : {
+            "subPackages" : true
+        },
+        "appid" : "15646153"
+    },
+    "mp-baidu" : {
+        "usingComponents" : true,
+        "appid" : ""
+    },
+    "mp-toutiao" : {
+        "usingComponents" : true,
+        "appid" : ""
+    },
+    "h5" : {
+        "template" : "template.h5.html",
+        "router" : {
+            "mode" : "history",
+            "base" : ""
+        },
+        "optimization" : {
+            "treeShaking" : {
+                "enable" : false
+            }
+        },
+        "title" : "uView UI",
+        "sdkConfigs" : {
+            "maps" : {
+                "qqmap" : {
+                    "key" : ""
+                }
+            }
+        },
+        "domain" : ""
+    }
+}

+ 18 - 0
package.json

xqd
@@ -0,0 +1,18 @@
+{
+    "id": "px-suspen-button",
+    "name": "suspensionButton 悬浮按钮",
+    "version": "1.0.1",
+    "description": "悬浮按钮组件,扇形展开关闭,自由移动,多样式自定义",
+    "keywords": [
+        "suspenButton",
+        "悬浮按钮",
+        "扇形展开",
+        "自定义"
+    ],
+    "dcloudext": {
+        "category": [
+            "前端组件",
+            "通用组件"
+        ]
+    }
+}

+ 62 - 0
pages.json

xqd
@@ -0,0 +1,62 @@
+{
+	// "condition": { //模式配置,仅开发期间生效
+	// 	"current": 0, //当前激活的模式(list 的索引项)
+	// 	"list": [{
+	// 		"name": "test", //模式名称
+	// 		"path": "pages/componentsB/dropdown/dropdown", //启动页面,必选
+	// 		"query": "" //启动参数,在页面的onLoad函数里面得到
+	// 	}]
+	// },
+	"pages": [ //pages数组中第一项表示应用启动页,参考:https://uniapp.dcloud.io/collocation/pages
+		{
+			"path": "pages/index/index",
+			"style": {
+				"navigationBarTitleText": "",
+				"enablePullDownRefresh": false,
+				"navigationStyle":"custom"
+			}
+
+		}
+	    ,{
+            "path" : "pages/goods-details/goods-details",
+            "style" :                                                                                    
+            {
+                "navigationBarTitleText": "",
+                "enablePullDownRefresh": false
+            }
+            
+        }
+        ,{
+            "path" : "pages/program/program",
+            "style" :                                                                                    
+            {
+                "navigationBarTitleText": "",
+                "enablePullDownRefresh": false
+            }
+            
+        }
+        ,{
+            "path" : "pages/more/more",
+            "style" :                                                                                    
+            {
+                "navigationBarTitleText": "",
+                "enablePullDownRefresh": false
+            }
+            
+        }
+    ],
+	"globalStyle": {
+		"navigationBarTextStyle": "black",
+		"navigationBarTitleText": "荟享家大屏",
+		"navigationBarBackgroundColor": "#FFFFFF",
+		"backgroundColor": "#FFFFFF",
+		"titleNView":false
+	}
+	// "tabBar": {
+	// 	"color": "#7A7E83",
+	// 	"selectedColor": "#3cc51f",
+	// 	"borderStyle": "black",
+	// 	"backgroundColor": "#ffffff",
+	// 	"list": []
+	// }
+}

+ 417 - 0
pages/goods-details/goods-details.vue

xqd
@@ -0,0 +1,417 @@
+<template>
+	<view class="">
+		<uni-nav-bar left-icon="left"  :title="goodsdetais.name" color="" :statusBar="true" :border="false" :size="60" @clickLeft="back"/>
+		<view class="goods-details">
+			<view class="goods-image">
+				<image :src="goodsdetais.cover_pic" mode="widthFix"></image>
+			</view>
+			<view class="infogoods">
+				<text class="goods-name">{{goodsdetais.name}}</text>
+				<view class="goods-price">
+					<view class="all-price">
+						<text class="tag">¥</text>
+						<text class="pricenum">{{goodsdetais.original_price}}</text>
+					</view>
+					<view class="coup-price">
+						<text class="couptag">券后价 ¥</text>
+						<text class="couprice">{{goodsdetais.price}}</text>
+					</view>
+				
+				</view>
+				<view class="totalnum">
+					<text>销量{{goodsdetais.sales}}{{goodsdetais.unit}}</text>
+				</view>
+			</view>
+		
+			
+		</view>
+		<view class="goods-specific">
+			<view class="specific-detail" >
+				<view class="one">
+					<text class="specific-title">商品规格</text>
+					<view class="kindof" v-for="(item,inedx) in goodsdetais.attr_groups" >
+					
+							<text class="kindoftitle">{{item.attr_group_name}}</text>		
+							<view v-for="i in item.attr_list"  class="kindofspec" >
+							<text>{{i.attr_name}}<text v-if="item.attr_list.length>1">/</text></text>
+							</view>
+		
+					</view>
+				</view>
+				
+		
+				
+			</view>
+			
+		</view>
+		<view class="goods-webview">
+			<view class="goodsmsg">
+				<text class="tah" style="padding-right: 24rpx;" id="gang">— </text>
+				<text>商品详情</text>
+				<text class="tah" style="padding-left: 24rpx;" id="ang">—</text>
+			</view>
+			
+		</view>
+		<view class="addshop" @click="open">
+			<uni-icons type="cart-filled" size="40" color="#fff"></uni-icons>
+		</view>
+		<uni-popup ref="popup" type="center">
+			<view class="icode">
+				<text style="font-size: 28rpx;">扫描二维码,去微信商城购买</text>
+				<view class="codeimg">
+					<image src="/static/image/hxj.jpg" mode="aspectFill"></image>
+				</view>
+			</view>
+		</uni-popup>
+		<!-- <view class="webview">
+			<view v-html="goodsdetais.detail"></view>
+		</view> -->
+		<view >
+			<bd-detail :detail="goodsdetais.detail" ></bd-detail>
+		</view>
+	</view>
+</template>
+
+<script>
+	let _this
+	import bdDetail from "@/components/goods/bd-detail.vue"
+	
+	export default {
+		components: {
+			bdDetail
+		},
+		data() {
+			
+			return {
+				id:'',
+				goodsdetais:[],
+				webview:'/static/image/zu.png',
+				speclist:[
+					{
+						"name":"颜色",
+						"kind":"黄色/绿色/蓝色/紫色"
+					},
+					{
+						"name":"尺码",
+						"kind":"xs/s/m/xl"
+					},
+				],
+				webview:''
+			}
+		},
+		onLoad(option) {
+			_this=this
+			"id" in option?this.id=JSON.parse(option.id):''
+			this.getGoods()
+		},
+		methods: {
+			open() {
+			           this.$refs.popup.open()
+			       },
+			back(){
+				uni.navigateBack({
+					delta:1
+				})
+			},
+			getGoods(){
+				uni.showLoading({
+					title:"加载中",
+					marsk:true
+				})
+				_this.$post('api/goods/detail',{
+					data:{
+						id:_this.id,
+						plugin:"mall",
+					},
+					success(res){
+						uni.hideLoading()
+						if(res.code==1){
+							_this.$toast(res.msg)
+						}else{
+							_this.goodsdetais=res.data.goods
+							console.log(_this.goodsdetais,55)
+						}
+					},
+					error(err) {
+						_this.$toast(err)
+					}
+				},'GET')
+			}
+		}
+	}
+</script>
+
+<style lang="scss">
+	.canshu{
+		text-align: center;
+		
+		.canshutitle{
+			color: #222222;
+			display: flex;
+			flex-direction: column;
+			margin: 45rpx 0;
+		}
+		.td-form{
+			padding:0 40rpx;
+			box-sizing: border-box;
+			padding-bottom: 80rpx;
+		}
+	}
+	.addshop{
+		position: fixed;
+		bottom: 80rpx;
+		z-index: 999;
+		right: 70rpx;
+		width: 120rpx;
+		height: 120rpx;
+		border-radius: 50%;
+		background-color:  #AE8445;
+		display: flex;
+		align-items: center;
+		justify-content: center;
+	}
+	.icode{
+		width: 600rpx;
+		height: 600rpx;
+		background-color: #fff;
+		border-radius: 15rpx;
+		display: flex;
+		flex-direction: column;
+		justify-content: center;
+		align-items: center;
+		.codeimg{
+			margin-top: 10rpx;
+			width: 450rpx;
+			height: 450rpx;
+			image{
+				width: 100%;
+				height: 100%;
+			}
+		}
+	}
+	.infogoods{
+		box-sizing: border-box;
+		padding:0 30rpx;
+		padding-bottom: 40rpx;
+		.goods-name{
+			font-size: 35rpx;
+			font-weight: 600;
+			display: block;
+			padding-right: 20rpx;
+		}
+		.coup-price{
+			background-image: url(../../static/image/couimgbg.png);
+			background-size:100% 100%;
+			// background-position: center;
+			// width: 250rpx;
+			height: 60rpx;
+			line-height: 50rpx;
+			box-sizing: border-box;
+			padding: 0 20rpx;
+			text-align: center;
+			box-sizing: border-box;
+			.couptag{
+				font-size: 24rpx;
+			}
+			.couprice{
+				font-size: 30rpx;
+				
+			}
+		}
+		.goods-price{
+			font-size: 40rpx;
+			color: #F95353;
+			display: flex;
+			margin-top: 24rpx;
+			}
+			.totalnum{
+				margin-top: 24rpx;
+				font-size: 22rpx;
+				padding-left: 7rpx;
+				color: #A2A4AB;
+				box-sizing: border-box;
+			}
+	}
+	page{
+		background-color: #F8F8F8;
+		padding: 0;
+		margin: 0;
+		font-family: PingFang-SC-Heavy;
+	}
+	html{
+		padding: 0;
+		margin: 0;
+	}
+	body{
+		padding: 0;
+		margin: 0;
+		
+	}
+	.webview{		
+		width: 750rpx;
+		// height: 1000rpx;
+		image{
+			// display: block;			
+			width:750rpx;
+			height: 100%;
+		}
+	}
+.goods-details{
+	background-color: #FFFFFF;
+	// display: flex;	
+	flex-direction: column;
+	// justify-content: center;
+	align-items: center;
+	.goods-image{
+		// margin-top: 30rpx;
+		width: 100%;
+		// height: 664rpx;
+		image{
+			width: 100%;
+			height: 100%;
+		}
+	}
+	// .infogoods{
+	// 	.goods-name{
+	// 		font-size: 35rpx;
+	// 		font-weight: 600;
+	// 	}
+		
+	// }
+	.goods-title{
+		width: 690rpx;
+		margin: 40rpx 0;
+		
+		.goods-name{
+			font-size: 32rpx;
+			font-weight: 600;
+		}
+		.goods-price{
+			color: #F95353;
+			display: flex;
+			margin-top: 24rpx;
+			
+			.all-price{
+				font-weight: bold;
+				margin-right: 12rpx;
+				
+				
+				.tag{
+					font-size: 28rpx;
+				}
+				.pricenum{
+					font-size: 44rpx;
+				}
+			}
+			.coup-price{
+				background-image: url(../../static/image/couimgbg.png);
+				background-size:100% 100%;
+				// background-position: center;
+				// width: 250rpx;
+				height: 60rpx;
+				line-height: 60rpx;
+				box-sizing: border-box;
+				padding: 0 20rpx;
+				text-align: center;
+				box-sizing: border-box;
+				.couptag{
+					font-size: 24rpx;
+				}
+				.couprice{
+					font-size: 30rpx;
+					
+				}
+			}
+		}
+		.totalnum{
+			margin-top: 24rpx;
+			font-size: 22rpx;
+			padding-left: 7rpx;
+			color: #A2A4AB;
+			box-sizing: border-box;
+		}
+	}
+}
+.goods-specific{
+	margin-top: 18rpx;
+	background-color: #FFFFFF;
+	display: flex;
+	justify-content: center;
+	padding: 36rpx 34rpx 0 34rpx;
+	box-sizing: border-box;
+	.specific-detail{
+		box-sizing: border-box;
+		
+		width: 690rpx;
+		// padding-bottom: 50rpx;
+		.specific-title{
+			font-size:18rpx;
+			font-weight: bold;
+			// padding-top: 35rpx;
+			display: block;
+			padding-bottom: 24rpx;
+			box-sizing: border-box;
+		}
+		
+		.kindof{
+			padding-bottom: 12rpx;
+			display: flex;
+			box-sizing: border-box;
+			.kindoftitle{	
+				width: 150rpx;
+				font-size:16rpx;
+				font-weight: 500;
+				color: #222222;
+			}
+			.kindofspec{
+				width: 480rpx;
+				display: block;
+				overflow:hidden; //超出的文本隐藏
+				text-overflow:ellipsis; //溢出用省略号显示
+				white-space:nowrap; //溢出不换行
+				font-size: 16rpx;
+				margin-left: 40rpx;
+				color: #666666;
+				box-sizing: border-box;
+			}
+			
+		}
+		.one{
+			margin-bottom: 30rpx;
+		}
+		.two{
+			margin-bottom: 50rpx;
+		}
+		
+	}
+	
+}
+.goods-webview{
+		background-color: #FFFFFF;
+		// width: 750rpx;
+		.goodsmsg{
+			font-size: 26rpx;
+			color: #666666;
+			text-align: center;
+			padding-bottom: 30rpx;
+			box-sizing: border-box;
+			.tah{
+				
+			}
+			@media  screen and (-webkit-min-device-pixel-ratio:0) {
+			    #gang {
+			    background: -webkit-gradient(linear,left top,right bottom,from(#FFFFFF),to(#666666));
+			    -webkit-background-clip: text;
+			    -webkit-text-fill-color: transparent;
+			    }
+			}
+			@media  screen and (-webkit-min-device-pixel-ratio:0) {
+			    #ang {
+			    background: -webkit-gradient(linear,left top,right bottom,from(#666666),to(#FFFFFF));
+			    -webkit-background-clip: text;
+			    -webkit-text-fill-color: transparent;
+			    }
+			}
+		}
+	}
+</style>

+ 130 - 0
pages/index/components/scroll-list.vue

xqd
@@ -0,0 +1,130 @@
+<template>
+	<view>
+		<scroll-view class="scroll_list" :style="{'background': background}" scroll-x="true">
+			<view class="main-left" style="padding-left: 40rpx;">
+				<view v-for="(item,index) in list" :key="index" @click="goPage(item.id)">
+					<view class="box" :style="{'width':itemWidth,'height':itemHeight}" >
+						<image class="image-bg" :src="item.cover_img" mode="aspectFill"></image>
+						<view class="text t-omit">{{item.title?item.title:''}}</view>
+					</view>
+					<view class="tag_list" v-if="item.tag">
+						<text class="tag">{{item.tag}}</text>
+					</view>
+				</view>
+			</view>
+		</scroll-view>
+	</view>
+</template>
+
+<script>
+	let defaultList = [{
+			image: 'https://t17.9026.com/web/statics/image/index/2.png',
+			title: '定制产品',
+			tag: '定制',
+			link:''
+		},
+		{
+			image: 'https://t17.9026.com/web/statics/image/index/2.png',
+			title: '热销方案',
+			tag: '热销',
+			link:'/pages/case/hot_sale_project'
+		},
+		{
+			image: 'https://t17.9026.com/web/statics/image/index/2.png',
+			title: '主题专区',
+			tag: '主题',
+			link:'/pages/case/themeArea'
+		},
+		{
+			image: 'https://t17.9026.com/web/statics/image/index/2.png',
+			title: '定制产品',
+			tag: '定制'
+		}
+	];
+	export default {
+		name: "app-scroll-list",
+		props: {
+			list: {
+				type: Array,
+				default: () => {
+					return defaultList
+				}
+			},
+			background: {
+				type: String,
+				default: ''
+			},
+			itemWidth:{
+				type:[String,Number],
+				default:'207rpx'
+			},
+			itemHeight:{
+				type:[String,Number],
+				default:'250rpx'
+			}
+		},
+		data() {
+			return {
+
+			}
+		},
+		methods: {
+			goPage(id){
+				uni.navigateTo({
+					url:"/pages/program/program?id="+id
+				})
+				
+			}
+		}
+	}
+</script>
+
+<style lang="scss" scoped>
+	.scroll_list {
+		padding: 15rpx 0 28rpx;
+
+		.box {
+			border-radius: 20rpx;
+			overflow: hidden;
+			position: relative;
+			z-index: 2;
+			margin-right: 20rpx;
+
+			.image-bg {
+				position: absolute;
+				z-index: -1;
+				left: 0;
+				right: 0;
+				bottom: 0;
+				right: 0;
+				width: 100%;
+				height: 100%;
+			}
+
+			.text {
+				width: 115rpx;
+				font-size: 28rpx;
+				font-weight: 600;
+				color: #FEFFFE;
+				text-shadow: 0px 0px 8px rgba(0, 0, 0, 0.17);
+				position: absolute;
+				top: 30rpx;
+				left: 50%;
+				transform: translateX(-50%);
+				z-index: 1;
+			}
+		}
+
+		.tag_list {
+			margin-top: 20rpx;
+
+			.tag {
+				background-color: rgba(238, 235, 222, 1);
+				color: rgb(167, 133, 79);
+				border-radius: 0 50% 50% 50%;
+				font-size: 24rpx;
+				padding: 10rpx 20rpx;
+			}
+		}
+	}
+</style>

+ 559 - 0
pages/index/index.scss

xqd
@@ -0,0 +1,559 @@
+.link {
+	padding: 30rpx 40rpx;
+	position: relative;
+	font-family: PingFangSC-Semibold, PingFang SC;
+	.hxj {
+		font-size: 40rpx;
+		font-weight: 600;
+		color: #e7dac1;
+		background: angular-gradient(180deg, #e1cfaf 0%, #fefffe 100%);
+		-webkit-background-clip: text;
+		// -webkit-text-fill-color: transparent;
+	}
+
+	.title {
+		font-weight: 500;
+		color: #121212;
+		font-size: 32rpx;
+		position: relative;
+		z-index: 2;
+		.hxjImg {
+			width: 249rpx;
+			height: 31rpx;
+			position: absolute;
+			top: -9rpx;
+			left: 1rpx;
+			z-index: -1;
+		}
+	}
+	.title1 {
+		font-weight: 500;
+		font-size: 26rpx;
+		position: relative;
+		color: #121212;
+		z-index: 2;
+	}
+	.tyx {
+		width: 37rpx;
+		height: 37rpx;
+		position: absolute;
+		top: 26rpx;
+		left: 24rpx;
+		z-index: 1;
+	}
+	.d6 {
+		width: 32rpx;
+		height: 17rpx;
+		position: absolute;
+		top: 40rpx;
+		left: 234rpx;
+		z-index: 1;
+	}
+	.subtitle {
+		height: 34rpx;
+		font-size: 24rpx;
+		font-weight: 400;
+		color: #a7a7a7;
+		line-height: 34rpx;
+	}
+	.coupon_1 {
+		width: 15rpx;
+		height: 15rpx;
+		margin-left: 25rpx;
+	}
+	.coupon_sub {
+		font-size: 20rpx;
+		font-weight: 400;
+		color: #999999;
+		line-height: 28rpx;
+		letter-spacing: 3rpx;
+		margin-left: 25rpx;
+	}
+	.link-more{
+		font-size: 25rpx;
+		font-weight: 500;
+		color: #989898;
+		image{
+			width: 12rpx;
+			height: 19rpx;
+			margin-left: 6rpx;
+		}
+	}
+}
+.pd_list {
+	padding: 0 0 0 40rpx;
+	.box {
+		height: 250rpx;
+		width: 207rpx;
+		border-radius: 20rpx;
+		overflow: hidden;
+		position: relative;
+		z-index: 2;
+		margin-right: 20rpx;
+		.image-bg {
+			position: absolute;
+			z-index: -1;
+			left: 0;
+			right: 0;
+			bottom: 0;
+			right: 0;
+			width: 100%;
+			height: 100%;
+		}
+		.text {
+			width: 87rpx;
+			height: 33rpx;
+			background: #b19d60;
+			border-radius: 0px 70rpx 70rpx 17rpx;
+			opacity: 0.2;
+			font-size: 24rpx;
+			font-family: PingFangSC-Regular, PingFang SC;
+			font-weight: 400;
+			color: #a7824b;
+			line-height: 33rpx;
+
+			text-shadow: 0px 0px 8rpx rgba(0, 0, 0, 0.17);
+		}
+	}
+	.tag_list {
+		margin-top: 20rpx;
+		.tag {
+			background-color: rgba(238, 235, 222, 1);
+			color: rgb(167, 133, 79);
+			border-radius: 0 50% 50% 50%;
+			font-size: 24rpx;
+			padding: 10rpx 20rpx;
+		}
+	}
+}
+.lg_list {
+	.item {
+		padding: 0 40rpx;
+		padding-bottom: 40rpx;
+		position: relative;
+		.imgBox {
+			width: 100%;
+			height: 500rpx;
+			border-radius: 40rpx 0 0 0;
+			z-index: 1;
+		}
+
+		.b_card {
+			width: 650rpx;
+			height: 130rpx;
+			position: absolute;
+			bottom: 25rpx;
+			left: 50%;
+			transform: translateX(-50%);
+			z-index: 2;
+			background-color: #fff;
+			border-radius: 20rpx;
+			padding: 20rpx 30rpx;
+			box-shadow: 6px 40px 100px 4px #eaeef0;
+			.left {
+				display: flex;
+				flex-flow: column;
+				justify-content: space-around;
+				.title {
+					font-size: 24rpx;
+					font-weight: 500;
+					color: #0e0e0e;
+					line-height: 34rpx;
+				}
+				.desc {
+					font-size: 24rpx;
+					font-weight: 400;
+					color: #a7a7a7;
+					line-height: 34rpx;
+				}
+			}
+			.right {
+				display: flex;
+				align-items: center;
+				.price {
+					display: flex;
+					align-items: flex-end;
+					color: #000;
+					font-size: 40rpx;
+					font-family: Helvetica;
+					color: #0e0e0e;
+					.rmb {
+						padding-bottom: 10rpx;
+						font-size: 18rpx;
+						font-family: Helvetica;
+						color: #0e0e0e;
+					}
+					.dw {
+						padding-bottom: 10rpx;
+						font-size: 18rpx;
+						font-weight: 500;
+						color: #a7a7a7;
+					}
+				}
+			}
+		}
+	}
+}
+.more {
+	width: 167rpx;
+	height: auto;
+	padding: 30rpx 0;
+	font-size: 20rpx;
+	font-family: PingFangSC-Regular, PingFang SC;
+	font-weight: 400;
+	color: #999999;
+	line-height: 28rpx;
+	letter-spacing: 3rpx;
+	margin: 0 auto;
+	text-align: center;
+	image {
+		width: 167rpx;
+		height: 11rpx;
+	}
+	.viewmore1 {
+		width: 51rpx;
+		height: 51rpx;
+	}
+	.viewmore1_color {
+		color: #a7824b;
+		line-height: 34rpx;
+		letter-spacing: 5rpx;
+	}
+	.viewmore2 {
+		width: 28rpx;
+		height: 28rpx;
+	}
+}
+.more1 {
+	width: 320rpx;
+	height: 85rpx;
+	border-radius: 43rpx;
+	border: 1rpx solid #b39978;
+	margin: 65rpx auto 100rpx;
+	font-size: 24rpx;
+	font-family: PingFangSC-Regular, PingFang SC;
+	font-weight: 400;
+	color: #a7824b;
+	line-height: 85rpx;
+	image {
+		width: 28rpx;
+		height: 28rpx;
+		margin-left: 12rpx;
+	}
+}
+
+.coupon {
+	padding: 0 45rpx 0 40rpx;
+	.item {
+		width: 320rpx;
+		height: 200rpx;
+		overflow: hidden;
+		border-radius: 20rpx;
+		margin-right: 25rpx;
+		position: relative;
+		background-image: url(https://t17.9026.com/web/statics/image/index/coupon_masking.png);
+		background-size: 310rpx 200rpx;
+		.Wb {
+			display: flex;
+			flex-flow: column;
+			justify-content: space-around;
+			padding: 17rpx 0 17rpx 37rpx;
+			.price {
+				font-size: 40rpx;
+				font-family: PingFangSC-Medium, PingFang SC;
+				font-weight: 500;
+				color: #b19d60;
+				text {
+					font-size: 24rpx;
+				}
+			}
+			.yxq {
+				font-size: 18rpx;
+				font-family: PingFangSC-Regular, PingFang SC;
+				font-weight: 400;
+				color: #c1c2c1;
+			}
+		}
+	}
+}
+
+.hxdz {
+	.q {
+		height: 385rpx;
+		padding: 0 45rpx 0 40rpx;
+		.l {
+			margin-right: 25rpx;
+			width: 320rpx;
+			height: 385rpx;
+			padding: 27rpx 31rpx;
+			border-radius: 20rpx;
+			position: relative;
+			overflow: hidden;
+			z-index: 1;
+			image {
+				width: 100%;
+				height: 100%;
+				position: absolute;
+				left: 0;
+				top: 0;
+				bottom: 0;
+				right: 0;
+				z-index: -1;
+			}
+		}
+		.r1 {
+			width: 320rpx;
+			height: 180rpx;
+			padding: 27rpx 31rpx;
+			border-radius: 20rpx;
+			position: relative;
+			overflow: hidden;
+			z-index: 1;
+			image {
+				width: 100%;
+				height: 100%;
+				position: absolute;
+				left: 0;
+				top: 0;
+				bottom: 0;
+				right: 0;
+				z-index: -1;
+			}
+		}
+		.r2 {
+			width: 320rpx;
+			height: 180rpx;
+			padding: 27rpx 31rpx;
+			border-radius: 20rpx;
+			position: relative;
+			overflow: hidden;
+			z-index: 1;
+			image {
+				width: 100%;
+				height: 100%;
+				position: absolute;
+				left: 0;
+				top: 0;
+				bottom: 0;
+				right: 0;
+				z-index: -1;
+			}
+		}
+	}
+
+	.w {
+		padding: 0 0 0 40rpx;
+		margin-top: 36rpx;
+		.item {
+			width: 320rpx;
+			margin-right: 25rpx;
+			image {
+				width: 320rpx;
+				height: 250rpx;
+			}
+		}
+	}
+}
+
+.group_list {
+	.item {
+		padding: 0 15rpx;
+		margin-bottom: 40rpx;
+		.imgBox {
+			width: 100%;
+			height: 404rpx;
+			position: relative;
+			.img {
+				position: absolute;
+				z-index: -1;
+				left: 0;
+				right: 0;
+				bottom: 0;
+				right: 0;
+				width: 100%;
+				height: 100%;
+				border-radius: 20rpx;
+			}
+		}
+
+		.b_card {
+			width: 500rpx;
+			height: 130rpx;
+			margin: -65rpx auto 0;
+			background-color: #fff;
+			border-radius: 20rpx;
+			padding: 20rpx 30rpx;
+			box-shadow: 6px 40px 100px 4px #eaeef0;
+			.left {
+				.i {
+					width: 49rpx;
+					height: 52rpx;
+					border-radius: 8rpx;
+					border: 1rpx solid #0e0e0e;
+					font-size: 24rpx;
+					font-family: PingFangSC-Medium, PingFang SC;
+					font-weight: 500;
+					color: #0e0e0e;
+					line-height: 52rpx;
+					text-align: center;
+				}
+				.q {
+					font-size: 24rpx;
+					font-family: PingFangSC-Medium, PingFang SC;
+					font-weight: 500;
+					color: #0e0e0e;
+					line-height: 52rpx;
+					margin: 0 7rpx;
+				}
+			}
+			.right {
+				display: flex;
+				align-items: center;
+				.price {
+					display: flex;
+					align-items: flex-end;
+					color: #000;
+					font-size: 40rpx;
+					font-family: Helvetica;
+					color: #0e0e0e;
+					.rmb {
+						padding-bottom: 10rpx;
+						font-size: 18rpx;
+						font-family: Helvetica;
+						color: #0e0e0e;
+					}
+					.dw {
+						padding-bottom: 10rpx;
+						font-size: 18rpx;
+						font-weight: 500;
+						color: #a7a7a7;
+					}
+				}
+			}
+		}
+	}
+}
+
+.hxzy {
+	padding: 0 40rpx;
+	.top {
+		margin-top: 20rpx;
+		height: 296rpx;
+		position: relative;
+		.dbo {
+			width: 379rpx;
+			height: 204rpx;
+			background: #fefffe;
+			padding: 31rpx 37rpx;
+			position: absolute;
+			bottom: 0;
+			left: 0;
+			.text {
+				font-size: 26rpx;
+				font-family: PingFangSC-Regular, PingFang SC;
+				font-weight: 400;
+				color: #252526;
+			}
+			.category {
+				font-size: 24rpx;
+				font-family: PingFangSC-Regular, PingFang SC;
+				font-weight: 400;
+				color: #b39978;
+			}
+		}
+	}
+	.bottom {
+		margin-top: 40rpx;
+		.item {
+			width: 320rpx;
+			margin-bottom: 35rpx;
+			border-radius: 20rpx;
+			.cover {
+				width: 320rpx;
+				height: 250rpx;
+			}
+		}
+		& > .item:nth-child(2n-1) {
+			margin-right: 25rpx;
+		}
+	}
+}
+.bottom_logo {
+	text-align: center;
+	padding: 50rpx 0;
+	image {
+		width: 135rpx;
+		height: 46rpx;
+	}
+	.text {
+		font-variant: small-caps;
+		font-size: 16rpx;
+		font-family: PingFangSC-Regular, PingFang SC;
+		font-weight: 400;
+		color: #999999;
+		line-height: 22px;
+		letter-spacing: 10rpx;
+	}
+}
+.title_2 {
+	font-weight: 600;
+	font-family: PingFangSC-Regular, PingFang SC;
+	color: #000;
+	font-size: 24rpx;
+	font-weight: 500;
+	color: #0e0e0e;
+}
+.desc_2 {
+	color: #5a5a5a;
+	font-size: 24rpx;
+	font-family: PingFangSC-Regular, PingFang SC;
+	font-weight: 400;
+	color: #a7a7a7;
+}
+.borradu_20 {
+	border-radius: 20rpx;
+}
+.mt_20 {
+	margin-top: 20rpx;
+}
+
+.price {
+	display: flex;
+	align-items: flex-end;
+	color: #000;
+	font-size: 40rpx;
+	font-family: Helvetica;
+	color: #0e0e0e;
+	.rmb {
+		padding-bottom: 10rpx;
+		font-size: 18rpx;
+		font-family: Helvetica;
+		color: #0e0e0e;
+	}
+	.dw {
+		padding-bottom: 10rpx;
+		font-size: 18rpx;
+		font-weight: 500;
+		color: #a7a7a7;
+	}
+}
+
+.price20 {
+	display: flex;
+	align-items: center;
+	font-size: 20rpx;
+	font-weight: 500;
+	color: #222;
+	.rmb {
+		padding-bottom: 0;
+		font-size: 14rpx;
+		color: #222;
+		font-weight: 400;
+	}
+	.dw {
+		padding-bottom: 10rpx;
+		font-size: 14rpx;
+		font-weight: 500;
+		color: #a7a7a7;
+	}
+}

+ 415 - 0
pages/index/index.vue

xqd
@@ -0,0 +1,415 @@
+<template>
+	<view style="background-color: #FEFFFE;">
+		<view class="swiper_cent">
+			<swiper style="height: 900rpx;" :indicator-dots="false" :autoplay="true" :interval="5000" :duration="1000" @change="swiperChange" :circular="true">
+				<swiper-item v-for="(item,index) in banners" :key="index" @click="goPage(item.url)">
+					<image :src="item.picUrl" style="width: 100%;height: 900rpx;" mode="aspectFill"></image>
+				</swiper-item>
+			</swiper>
+			<view class="swiper_zhishi">
+				<view :class="swiperKey===i?'view_active':''" v-for="(item1,i) in 3" :key="i"></view>
+			</view>
+		</view>
+		<!-- <view class="main-center cross-center" style="height: 132rpx;">
+			<image style="width: 32rpx;height: 17rpx;margin-right: 22rpx;" src="/static/</image>
+							<image class="hxjImg" src="https://t17.9026.com/web/statics/image/index/HUIXIANGJIA.png" mode=""></image>
+		</view>
+						<view class="more-an">
+							<view class="text" style="position: relative;" @click="goMore">了解更多
+							<image style="width: 105rpx;height: 9rpx;position: absolute;left: 0;bottom: 0;" src="/static/image/viewmore.png" mode=""></image>
+							</view>image/bz1.png" mode=""></image>
+			<view style="font-size: 40rpx;">热卖套装</view>
+			<image style="width: 32rpx;height: 17rpx;margin-left: 22rpx;" src="/static/image/bz.png" mode=""></image>
+		</view> -->
+		<view class="list">
+			
+			<view class="item" v-for="(item,index) in allList" :key="index">
+				<view class="index">
+					<text>0{{index+1}}</text>
+				</view>
+				<view class="main-between cross-center link">
+						<view class="title">
+							{{item[0].cat_name}}
+						</view>
+				</view>
+				<app-scroll-list :list="item" :itemWidth="'200rpx'" :itemHeight="'278rpx'"></app-scroll-list>
+			</view>
+		</view>
+		
+		<!-- <view class="link">
+			<view class="title1">本期家点灵感</view>
+			<image class="tyx" src="https://t17.9026.com/web/statics/image/index/tyx.png" mode=""></image>
+			<image class="d6" src="https://t17.9026.com/web/statics/image/index/6d.png" mode=""></image>
+			<view class="subtitle">臻选整装 · 安全无甲醛 · 7天入住</view>
+		</view>
+		<view class="lg_list">
+			<view class="item" v-for="(item,index) in homePages[5].data.list" :key="index"
+				@click="goPage(item.type=='goods'?`/pages/goods/goods?id=${item.id}`:`/pages/case/projectInfo?id=${item.id}`)">
+				<image class="imgBox" :src="item.cover_pic">
+				</image>
+				<view class="b_card main-between">
+					<view class="left">
+						<view class="title t-omit" style="width: 420rpx;">{{item.name}}</view>
+						<view class="desc t-omit" style="width: 420rpx;">{{item.subtitle}}</view>
+					</view>
+					<view class="right">
+						<view class="price"><text class="rmb">¥</text><text>{{item.price}}</text><text
+								class="dw">元</text></view>
+					</view>
+				</view>
+			</view>
+			<view class="more">
+				<view>VIEW MORE</view>
+				<view>
+					<image src="https://t17.9026.com/web/statics/image/index/viewmore.png" mode=""></image>
+				</view>
+			</view>
+		</view>
+		<view class="link ">
+			<view class="title1 main-left cross-center">优惠券<image class="coupon_1"
+					src="https://t17.9026.com/web/statics/image/index/coupon_1.png" mode=""></image><text
+					class="coupon_sub">RECOMMEND</text></view>
+
+		</view>
+		<view class="coupon main-left">
+			<view class="item main-left" v-for="(item,index) in homePages[6].data.coupon_list" :key="index"
+				@click="goPage(`/pages/coupon/list/list`)">
+				<view class="Wb">
+					<view>
+						<view class="title_2">定制优享礼券 <text class="hjx-tc-B19D60 hjx-ts-21 hxj-ml-10">立即领取</text></view>
+						<view class="desc_2">新用户礼遇</view>
+					</view>
+					<view>
+						<view class="price"><text>¥</text>{{item.sub_price}}</view>
+						<view class="yxq">有效期至{{item.end_time.split(' ')[0]}}</view>
+					</view>
+				</view>
+			</view>
+		</view>
+		<view class="link mt_20">
+			<view class="title1">荟享定制<image class="coupon_1"
+					src="https://t17.9026.com/web/statics/image/index/coupon_1.png" mode="">
+				</image><text class="coupon_sub">HUIXIANG</text></view>
+
+		</view>
+		<view class="hxdz">
+			<view class="main-left q">
+				<view class="l">
+					<view class="title_2">明星套装</view>
+					<view class="desc_2">自然高端好家居</view>
+					<image src="https://t17.9026.com/web/statics/image/index/4.png" mode=""></image>
+				</view>
+				<view class="main-between-y">
+					<view class="r1" @click="goPage('/pages/case/selectedCases')">
+						<view class="title_2">优选</view>
+						<view class="desc_2">优选家居</view>
+						<image src="https://t17.9026.com/web/statics/image/index/5.png" mode=""></image>
+					</view>
+					<view class="r2">
+						<view class="title_2">专属</view>
+						<view class="desc_2">定制精品</view>
+						<image src="https://t17.9026.com/web/statics/image/index/6.png" mode=""></image>
+					</view>
+				</view>
+			</view>
+			<scroll-view scroll-x="true">
+				<view class="main-left w">
+					<view class="item" v-for="(item,index) in qwe" :key="index">
+						<image class="borradu_20"
+							:src="`https://t17.9026.com/web/statics/image/index/temporary/${item.cover_image}.png`"
+							mode=""></image>
+						<view class="title_2">{{item.title}}</view>
+						<view class="desc_2">独到品味的代表备份</view>
+					</view>
+				</view>
+			</scroll-view>
+			<view class="more">
+				<view class="viewmore1_color">查看更多</view>
+				<view class="viewmore1_color">VIEW MORE</view>
+				<view>
+					<image class="viewmore1" src="https://t17.9026.com/web/statics/image/index/viewmore1.png" mode=""></image>
+				</view>
+			</view>
+		</view>
+		<view class="main-between link mt_20">
+			<view class="title1">
+				限时团购
+				<image class="coupon_1" src="https://t17.9026.com/web/statics/image/index/coupon_1.png" mode="">
+				</image>
+				<text class="coupon_sub">TUANGOU</text>
+			</view>
+			<view class="link-more" @click="goPage('/plugins/pt/index/index')">
+				查看更多<image src="https://t17.9026.com/web/statics/image/index/arrow-right-gray.png" mode=""></image>
+			</view>
+		</view>
+
+		<view class="group_list">
+			<swiper style="height: 606rpx;" :indicator-dots="false" :autoplay="false" :interval="3000" :duration="1000"
+				previous-margin="75rpx" next-margin="75rpx">
+				<swiper-item class="" v-for="(item,index) in 3" :key="index"
+					@click="goPage('/plugins/pt/goods/goods?goods_id=29')">
+					<view class="item">
+						<view class="imgBox">
+							<image class="img" src="https://t17.9026.com/web/statics/image/index/3.png"></image>
+						</view>
+						<view class="b_card main-between">
+							<view class="main-left cross-center left">
+								<view class="i">02</view>
+								<view class="q">:</view>
+								<view class="i">24</view>
+								<view class="q">:</view>
+								<view class="i">45</view>
+							</view>
+							<view class="right">
+								<view class="price"><text class="rmb">¥</text><text>56800</text><text
+										class="dw">元</text></view>
+							</view>
+						</view>
+					</view>
+				</swiper-item>
+			</swiper>
+		</view>
+		<view class="link main-left">
+			<view class="title1" style="margin-right: 61rpx;" :style="{'color':bottom_goods_index===index?'':'#A7A7A7'}"
+				v-for="(item,index) in homePages[11].data.catList" :key="index" @click="selectCat(index)">
+				{{item.menuName}}</view>
+			<image class="tyx" src="https://t17.9026.com/web/statics/image/index/tyx.png" mode=""></image>
+		</view>
+		<view class="hxzy">
+			<view class="top">
+				<image
+					style="width: 560rpx;height: 270rpx;border-radius: 20rpx 0 0 0;position: absolute;top: 0;right: 0;"
+					src="https://t17.9026.com/web/statics/image/index/9.png" mode=""></image>
+				<view class="dbo main-between-y">
+					<view>
+						<view class="text">decoration荟享自营</view>
+						<view class="text">心至所向·渐入家境</view>
+					</view>
+					<view class="category">2930人已收藏</view>
+				</view>
+			</view>
+			<view class="dir-left-wrap bottom">
+				<view class="item" v-for="(item,index) in homePages[11].data.catList[bottom_goods_index].goodsList"
+					:key="index" @click="goPage(`/pages/goods/goods?id=${item.id}`)">
+					<view>
+						<image class="cover" :src="item.cover_pic" mode=""></image>
+					</view>
+					<view class="title_2 t-omit" style="width: 320rpx;">{{item.name}}</view>
+					<view class="desc_2">{{item.sales}}</view>
+					<view class="price" style="margin-top: 27rpx;"><text
+							class="rmb">¥</text><text>{{item.price}}</text><text class="dw">元</text></view>
+				</view>
+			</view>
+			<view class="more1" @click="goPage('/pages/cats/cats')">
+				<view class="viewmore1_color main-center cross-center">更多荟享<image class=""
+						src="https://t17.9026.com/web/statics/image/index/viewmore1.png" mode=""></image>
+				</view>
+			</view>
+		</view>
+
+		<view class="bottom_logo">
+			<image src="https://t17.9026.com/web/statics/image/index/bottom_logo.png" mode=""></image>
+			<view class="text">home shopping mall</view>
+		</view> -->
+	</view>
+</template>
+
+<script>
+	let _this
+	
+	import appScrollList from './components/scroll-list.vue'
+	export default {
+		components: {
+			appScrollList
+		},
+		data() {
+			return {
+				allList:[],
+				pageData: '',
+				swiperKey: 0, //轮播位置
+				bottom_goods_index: 0, //底部商品分类索引
+				banners: [{
+						"link": null,
+						"openType": "",
+						"open_type": "",
+						"page_url": "",
+						"picUrl": "https://swdzshopv4.oss-cn-chengdu.aliyuncs.com/uploads/mall10000/20210811/ea8f12c9fac0ef1353ef10025bce982e.jpg",
+						"pic_url": "https://swdzshopv4.oss-cn-chengdu.aliyuncs.com/uploads/mall10000/20210811/ea8f12c9fac0ef1353ef10025bce982e.jpg",
+						"url": ""
+					},
+					{
+						"link": null,
+						"openType": "",
+						"open_type": "",
+						"page_url": "",
+						"picUrl": "https://swdzshopv4.oss-cn-chengdu.aliyuncs.com/uploads/mall10000/20210811/3c14220516b0d47808484f72442edbbf.jpg",
+						"pic_url": "https://swdzshopv4.oss-cn-chengdu.aliyuncs.com/uploads/mall10000/20210811/3c14220516b0d47808484f72442edbbf.jpg",
+						"url": ""
+					},
+					{
+						"link": null,
+						"openType": "",
+						"open_type": "",
+						"page_url": "",
+						"picUrl": "https://swdzshopv4.oss-cn-chengdu.aliyuncs.com/uploads/mall10000/20210811/7e56eaf80af09b51d18ec93259cd5b4b.jpg",
+						"pic_url": "https://swdzshopv4.oss-cn-chengdu.aliyuncs.com/uploads/mall10000/20210811/7e56eaf80af09b51d18ec93259cd5b4b.jpg",
+						"url": ""
+					}
+				],
+				list: [
+
+					{
+						image: 'https://t17.9026.com/web/statics/image/index/temporary/jhk-1634283687206.png',
+						// title: '热销方案',
+						// tag: '热销',
+						link: '/pages/case/hot_sale_project'
+					},
+					{
+						image: 'https://t17.9026.com/web/statics/image/index/temporary/jhk-1634284372084.png',
+						// title: '主题专区',
+						// tag: '主题',
+						link: '/pages/case/themeArea'
+					},
+					{
+						image: 'https://t17.9026.com/web/statics/image/index/temporary/jhk-1634283667210.png',
+						// title: '定制产品',
+						// tag: '定制',
+						link: ''
+					},
+					{
+						image: 'https://t17.9026.com/web/statics/image/index/2.png',
+						// title: '明星套装',
+						// tag: '套装'
+					}
+				],
+				jdlg: [{
+						title: '兴城人居ins风套装',
+						sub_title: '细节控·轻奢风',
+						price: '65622',
+						cover_image: '3',
+					},
+					{
+						title: '风创造「最大坪效」85m²MUJI',
+						sub_title: '细节控·轻奢风',
+						price: '7486',
+						cover_image: 'temporary/jhk-1634283447190'
+					},
+					{
+						title: '85m²MUJI风创造「最大坪效」',
+						sub_title: '细节控·轻奢风',
+						price: '27853',
+						cover_image: 'temporary/jhk-1634284367308'
+					}
+				],
+				qwe: [{
+						'title': '兴城人居',
+						'cover_image': 'jhk-1634283667210'
+					}, {
+						'title': '世龙广场',
+						'cover_image': 'jhk-1634284372084'
+					}, {
+						'title': '新中式',
+						'cover_image': 'jhk-1634283687206'
+					}, 
+				]
+
+			}
+		},
+		onLoad(){
+			_this=this
+			this.getList()
+			
+		},
+		methods: {
+			goMore(){
+				uni.navigateTo({
+					url:'/pages/more/more'
+				})
+			},
+			getList(){
+				// console.log(8888)
+				_this.$post('api/big-screen/compositions',{
+					data:{
+						estate_id:3,
+					},
+					success(res){	
+						_this.allList=res.data.list
+					},
+					error(res) {
+						t.$toast(res)
+					}
+				},'GET')
+			},
+			selectCat(index) {
+				this.bottom_goods_index = index
+			},
+			swiperChange(e) {
+				// console.log(e.detail.current)
+				this.swiperKey = e.detail.current
+			},
+			goPage(url) {
+				uni.navigateTo({
+					url: url
+				})
+			}
+		}
+	}
+</script>
+
+<style lang="scss" scoped>
+	@import './index.scss';
+.list{
+	
+	.item{
+		width: 100%;
+		height: 596rpx;
+		background: #F9F6F4;
+		opacity: 0.76;
+		border-radius: 0px 40rpx 0px 0px;
+		.index{
+			padding: 43rpx 42rpx;
+			font-size: 40rpx;
+			font-family: DIN Medium;
+			font-weight: 400;
+			color: #8D6D5D;
+			opacity: 0.23;
+		}
+		.more-an{
+			width: 105rpx;
+			height: 43rpx;
+			padding-top: 6rpx;
+			.text{
+				height: 43rpx;
+				width: 105rpx;
+				font-size: 22rpx;
+				font-family: PingFang SC;
+				font-weight: 300;
+				color: #999999;
+				line-height: 30rpx;
+			}
+		}
+	}
+}
+	.swiper_cent {
+		position: relative;
+
+		.swiper_zhishi {
+			display: flex;
+			align-items: center;
+			width: 100%;
+			justify-content: center;
+			position: absolute;
+			bottom: 39rpx;
+
+			view {
+				width: 81rpx;
+				height: 4rpx;
+				background: #c8c8c8;
+			}
+
+			.view_active {
+				background: #f4f4f4;
+			}
+		}
+	}
+</style>

+ 214 - 0
pages/more/more.vue

xqd
@@ -0,0 +1,214 @@
+<template>
+	<view class="page">
+		<view class="tpbg">
+			<uni-nav-bar left-icon="back" backgroundColor="transparent" :border="false" color="#555555" @clickLeft="back" :statusBar="true"></uni-nav-bar>
+		</view>
+
+		<view class="center">
+			
+			<view class="main-left tagf">
+				<view class="tag" :class="{'active':activeIndex===index?true:false}" v-for="(item,index) in classList" :key="index" @click="selectTag(index)">
+					{{item.name}}
+				</view>
+			</view>
+			<view class="item" v-for="(item,index) in CompositionList" :key="index" @click="goPage(item.id)">
+				<view class="main-between img">
+					<view style="width: 419rpx;height: 281rpx;">
+						<image style="width: 420rpx;height: 420rpx;" :src="item.cover_img" mode=""></image>
+					</view>
+					<view style="width: 206rpx;">
+						<image style="width: 206rpx;height: 206rpx;" :src="item.banner_imgs[0].banner_imgs" mode=""></image>
+						<image style="width: 206rpx;height: 206rpx;" :src="item.banner_imgs[1].banner_imgs" mode=""></image>
+					</view>
+				</view>
+				<view class="main-between pl" >
+					<view class="main-between-y">
+						<view class="title t-omit">{{item.name}}</view>
+						<view class="desc">已售:{{item.sale_num}}套 | {{item.collect_num}}人收藏</view>
+						<view class="price"><text>¥</text>{{item.actual_price}}</view>
+					</view>
+					<view class="cross-bottom arrow-right">
+						<image src="https://t17.9026.com/web/statics/image/index/arrow-right-gray.png" mode=""></image>
+					</view>
+				</view>
+			</view>
+			<view class="no-more" v-if="noMore && CompositionList.length>0">没有更多了...</view>
+			<appNoData v-if="CompositionList.length==0" :background="'#f8f8f8'" :title="'该分类暂无套装'"></appNoData>
+		</view>
+	</view>
+</template>
+
+<script>
+	let _this
+	// import appNavBar from '@/components/index/app-nav-bar.vue';
+	import appNoData from '@/components/index/app-no-goods.vue';
+	export default {
+		components:{
+			// appNavBar,
+			appNoData
+		},
+		data() {
+			return {
+				activeIndex:0,
+				navbarHeight:'',
+				
+				classList:[],
+				CompositionList:[],
+				noMore:false,
+			};
+		},
+		onLoad(option) {
+			_this=this
+			this.getCompositionClass(option.cat_id)
+			this.getCompositionList(option.cat_id)
+		},
+		methods:{
+			selectTag(index){
+				this.activeIndex=index
+				this.getCompositionList(this.classList[index].id)
+			},
+			headHeight(e){
+				this.navbarHeight=e+'px'
+			},
+			goPage(id){
+				uni.navigateTo({
+					url:'/pages/program/program?id='+id
+				})
+			},
+			getCompositionClass(cat_id){
+				_this.$post('plugin/composition/api/index/cat-list',{
+					success(res){
+						if(res.code===0){
+							this.classList=res.data.list
+							this.classList.splice(0,0,{id:'',name:'全部'})
+						}
+						for (let i = 0; i < this.classList.length; i++) {
+							if(this.classList[i].id==cat_id){
+								this.activeIndex=i
+							}
+						}
+					}
+				})	
+			},
+			back(){
+				uni.navigateBack({
+					delta:1
+				})
+			},
+			getCompositionList(cat_id=''){
+				let dk_estate_id = uni.getStorageSync('dk_estate_id') ? uni.getStorageSync('dk_estate_id') : 0;
+				_this.$post('plugin/composition/api/index/cat-list',{
+					data:{
+						cat_id:cat_id,
+						estate_id: 0
+					},
+					success(res){
+						if(res.code===0){
+							this.CompositionList=res.data.list
+						}
+					}
+				})		
+			}
+		}
+	}
+</script>
+
+<style lang="scss" scoped>
+	.no-more {
+		font-size: 24rpx;
+		font-weight: 500;
+		color: #666666;
+		margin-top: 37rpx;
+		text-align: center;
+	}
+	.page{
+		position: relative;
+		min-height: 100vh;
+	}
+	.tpbg{
+		width: 750rpx;
+		height: 368rpx;
+		position: fixed;
+		top: 0;
+		left: 0;
+		z-index: -1;
+		background-image: url("https://t17.9026.com/web/statics/image/index/3.png");
+		background-size: 100%;
+	}
+	.w{
+		width: 750rpx;
+	}
+	.center{
+		border-radius: 30rpx;
+		overflow: hidden;
+		margin-top: 368rpx;
+		width: 750rpx;
+		height: auto;
+		background: #F8F8F8;
+		border-radius: 20rpx;
+		overflow: hidden;
+		padding: 36rpx;
+		.tagf{
+			padding-bottom: 26rpx;
+		}
+		.tag{
+			min-width: 131rpx;
+			height: 51rpx;
+			background: #EFEFEF;
+			border-radius: 10rpx;
+			font-size: 24rpx;
+			font-weight: 500;
+			color: #999999;
+			text-align: center;
+			line-height: 51rpx;
+			padding: 0 15rpx;
+			margin-right: 20rpx;
+		}
+		.active{
+			background: #A18353;
+			color: #FFFFFF;
+		}
+		.item{
+			width: 678rpx;
+			height: auto;
+			background: #FFFFFF;
+			border-radius: 8rpx;
+			margin:0 auto 24rpx;
+			padding: 27rpx 22rpx;
+			.img{
+				height: 420rpx;
+				border-radius: 10rpx;
+				overflow: hidden;
+			}
+			.pl{
+				margin-top: 25rpx;
+				.title{
+					width: 620rpx;
+					font-size: 32rpx;
+					font-weight: bold;
+					color: #222222;
+				}
+				.desc{
+					font-size: 22rpx;
+					font-weight: 500;
+					color: #999999;
+					margin: 10rpx 0 13rpx;
+				}
+				.price{
+					text{
+						font-size: 28rpx;
+					}
+					font-size: 38rpx;
+					font-weight: 500;
+					color: #F93F3F;
+				}
+				.arrow-right{
+					image{
+						width: 15rpx;
+						height: 26rpx;
+					}
+				}
+			}
+		}
+	}
+</style>

+ 454 - 0
pages/program/program.vue

xqd
@@ -0,0 +1,454 @@
+<template>
+	<view>
+		<view class="swiper_cent" style="position: relative;">
+			<view style="position: absolute; z-index: 999;">
+				<uni-nav-bar left-icon="back" backgroundColor="transparent" :border="false" color="#555555" @clickLeft="back" :statusBar="true"></uni-nav-bar>
+			</view>
+			
+		<swiper style="height: 900rpx;" :indicator-dots="false" :autoplay="true" :interval="5000" :duration="1000" @change="swiperChange" :circular="true">
+			
+			<swiper-item v-for="(item,index) in goodsdetais.banner_imgs" :key="index" @click="goPage(item.url)">
+				<image :src="item.banner_imgs" style="width: 100%;height: 900rpx;" mode="aspectFill"></image>
+			</swiper-item>
+		</swiper>
+		<view class="swiper_zhishi">
+			<view :class="swiperKey===i?'view_active':''" v-for="(item1,i) in goodsdetais.banner_imgs" :key="i"></view>
+		</view>
+		</view>
+		<view class="suit">
+			<view class="suititle">
+				<text class="suitname">套装内商品</text>
+				<view class="suitimg">
+					<image src="../../static/image/programioc.png" mode="aspectFill"></image>
+				</view>
+				<text class="suiteng">IN SET GOODS</text>	
+			</view>
+			<view class="suitsall">
+				<view class="suitgoods" v-for="(i,index) in goodsdetais.compositionGoods" :key="index" @click="goGoods(i.goods.id)">
+					<image :src="i.goods.cover_pic" mode="aspectFill"></image>
+				</view>
+			</view>
+		</view>
+		<view class="magcard">
+			<view class="cardtitle">
+				<view class="titleone">
+					<text class="suitname">{{goodsdetais.name}}</text>
+					<text class="nametag">{{goodsdetais.tag}}</text>
+					<view style="overflow: visible;">
+						<text class="coutall">已售{{goodsdetais.sale_num}}</text>
+					</view>
+					
+				</view>
+				<view class="titlewo">
+					<text class="qing">{{exampleHouse_name}}  </text>
+					<text class="qing" style="padding-left: 10rpx;padding-right: 0;">{{goodsdetais.estate_name?goodsdetais.estate_name:''}}</text>
+					<text class="daprice">{{goodsdetais.actual_price}}元</text>	
+				</view>
+				<view class="pricecop"><text>券后价</text></view>
+				<view class="price">
+					<text style="font-size: 18rpx;padding-right: 8rpx;font-weight: 600;">¥ </text>
+					<text style="font-size: 46rpx;font-weight: 800;">{{goodsdetais.discount_amount}}</text>
+					<text style="font-size: 18rpx;padding-left: 12rpx;">元</text>
+				</view>
+			</view>
+		</view>
+		<view class="total">
+			<view class="opentotal">
+				<view class="you">
+					<text class="">套装内商品</text>
+					<view class="zhan" >
+						<text class="">数量</text>
+						<view class="openbutton" v-show="!opensuit">
+							
+						</view>
+					</view>
+				</view>
+				<view class="gan" :style="{'margin-right':!opensuit?'90rpx':'0'}"></view>
+			</view>
+			<view class="opentotal" v-for="(item,index) in goodsdetais.compositionGoods" :key="index" v-show="!opensuit?index<2:gotoal.length-1">
+				<view class="you">
+					<text class="" style="width: 450rpx;">{{item.goods.name}}</text>
+					<view class="zhan">
+						<text class="">x1</text>
+						<view class="openbutton" v-if="index==1&&!opensuit&&vesuit" @click="opemSuit()">
+							<text>展开</text>
+							<uni-icons type="bottom" size="15" color="#A18353;"></uni-icons>	
+						</view>
+						
+						<view class="openbutton" v-else v-show="!opensuit"></view>
+					</view>
+				</view>
+				<view class="gan" :style="{'margin-right':!opensuit?'90rpx':'0'}"></view>
+			</view>
+		
+		</view>
+		<!-- <view class="webview">
+			<view v-html="goodsdetais.details" class="detailsd"></view>
+		</view> -->
+		<view style="margin-top: 20rpx;">
+			<bd-detail :detail="goodsdetais.details" ></bd-detail>
+		</view>
+		<view class="addshop" @click="open">
+			<uni-icons type="cart-filled" size="40" color="#fff"></uni-icons>
+		</view>
+		<uni-popup ref="popup" type="center">
+			<view class="icode">
+				<text style="font-size: 28rpx;">扫描二维码,去微信商城购买</text>
+				<view class="codeimg">
+					<image src="/static/image/hxj.jpg" mode="aspectFill"></image>
+				</view>
+			</view>
+		</uni-popup>
+		
+		<!-- <view class="canshu">
+			<view class="canshutitle">
+				<text style="font-size: 24rpx;font-weight: 500;">Product parameters</text>
+				<text style="font-size: 46rpx;font-weight: 800;">产品参数</text>
+			</view>
+			<view class="td-form">
+					<uni-table border :stripe="false" emptyText="暂无更多数据" >
+			
+						
+						<uni-tr v-for="(item,index) in 5" :key="index">
+							<uni-td width="90" align="center"  color="#303030" bold="bold">产品名称</uni-td>
+							<uni-td color="#666666" bold="500">休闲沙发</uni-td>
+						</uni-tr>
+					
+					</uni-table>
+				</view>
+			</view>
+		</view> -->
+	</view>
+</template>
+
+<script>
+	let _this
+	import bdDetail from "@/components/goods/bd-detail.vue"
+	export default {
+		components: {
+			bdDetail,
+		},
+		data() {
+			return {
+				id:'',
+				content:[
+					
+				],
+				goodsdetais:[],
+				exampleHouse_name:'',
+				opensuit:false,
+				vesuit:false,
+				webview:'/static/image/zu.png',
+				swiperKey: 0, //轮播位置
+				gotoal:[
+					{
+						"name":"ins风高档皮质沙发",
+						"total":"x1"
+					},
+					{
+						"name":"ins风高档吊灯",
+						"total":"x1"
+					},
+					{
+						"name":"ins风高档吊灯",
+						"total":"x1"
+					},
+				],
+				
+			}
+		},
+		onLoad(option) {
+			_this=this
+			"id" in option?this.id=JSON.parse(option.id):''
+			if(this.gotoal.length>2){
+				this.vesuit=true
+			}
+			this.getGoods()
+		},
+		methods: {
+			 open() {
+			            this.$refs.popup.open()
+			        },
+			back(){
+				uni.navigateBack({
+					delta:1
+				})
+			},
+			opemSuit(){
+				this.opensuit=true
+			},
+			swiperChange(e) {
+				this.swiperKey = e.detail.current
+			},
+			goGoods(id){
+				uni.navigateTo({
+					url: "/pages/goods-details/goods-details?id="+id
+				})
+			},
+			goPage(url) {
+				
+				uni.navigateTo({
+					url: url
+				})
+			},
+			getGoods(){
+				uni.showLoading({
+					title:"加载中"
+				})
+				_this.$post('plugin/composition/api/index/info',{
+					data:{
+						composition_id:_this.id
+					},
+					success(res){
+						
+						if(res.code==1){
+							_this.$toast(res.msg)
+						}else{
+							_this.goodsdetais=res.data
+							_this.exampleHouse_name=res.data.exampleHouse.name
+							uni.hideLoading()
+						}
+					},
+					error(err) {
+						_this.$toast(err)
+					}
+				},'POST')
+			}
+		}
+	}
+</script>
+
+<style lang="scss">
+	.icode{
+		width: 600rpx;
+		height: 600rpx;
+		background-color: #fff;
+		border-radius: 15rpx;
+		display: flex;
+		flex-direction: column;
+		justify-content: center;
+		align-items: center;
+		.codeimg{
+			margin-top: 10rpx;
+			width: 450rpx;
+			height: 450rpx;
+			image{
+				width: 100%;
+				height: 100%;
+			}
+		}
+	}
+	.addshop{
+		position: fixed;
+		bottom: 80rpx;
+		z-index: 999;
+		right: 70rpx;
+		width: 120rpx;
+		height: 120rpx;
+		border-radius: 50%;
+		background-color:  #AE8445;
+		display: flex;
+		align-items: center;
+		justify-content: center;
+	}
+	.detailsd{
+		p{
+			font-size: 20rpx;
+		}
+	}
+	.webview{
+		margin-top: 10rpx;
+		width: 100%;
+		height: 100%;
+		
+		image{
+			width: 100%;
+			height: 100%;
+		}
+	}
+	page{
+		font-family: PingFang-SC-Heavy;
+	}
+.swiper_cent {
+		position: relative;
+
+		.swiper_zhishi {
+			display: flex;
+			align-items: center;
+			width: 100%;
+			justify-content: center;
+			position: absolute;
+			bottom: 39rpx;
+
+			view {
+				width: 81rpx;
+				height: 4rpx;
+				background: #c8c8c8;
+			}
+
+			.view_active {
+				background: #f4f4f4;
+			}
+		}
+	}
+	.suit{
+		padding: 50rpx 0rpx 60rpx 40rpx;
+		.suititle{
+			display: flex;
+			align-items: center;
+			// .suitname{
+			// font-size: 35rpx;
+			// font-weight: 500;
+			// display: block;
+			// }
+			.suitimg{
+				width: 15rpx;
+				height: 15rpx;
+				margin: 0 20rpx;
+				margin-bottom: 35rpx;
+				
+				image{
+					width: 100%;
+					height: 100%;
+				}
+			}
+			.suiteng{
+				display: block;
+				font-size: 25rpx;
+				color: #999999;
+				// margin-top: 6rpx;
+			}
+		}
+	}
+	.suitsall{
+		display: flex;
+		flex-wrap: nowrap;
+		flex-direction: row;
+		// display: inline-block;
+		overflow-x: auto;
+		overflow-y: hidden;
+		// overflow: hidden;
+		.suitgoods{
+			flex-shrink: 0;
+			margin-top: 30rpx;
+			margin-right: 25rpx;
+			width: 168rpx;
+			height: 168rpx;
+			border-radius: 25rpx;
+			display: inline-block;
+			image{
+				width: 100%;
+				height: 100%;
+				border-radius: 25rpx;
+			}
+		}
+	}
+	.suitname{
+	font-size: 30rpx;
+	font-weight: bold;
+	display: block;
+	}
+	.magcard{
+		padding: 60rpx 40rpx 30rpx 40rpx;
+		background-color: #F6F6F6;
+		border-radius: 60rpx 0 0 0;
+		.cardtitle{
+			
+			.titleone{
+				display: flex;
+				align-items: center;
+				height: 40rpx;
+				overflow: visible;
+				.nametag{
+				display: block;
+				margin-left: 14rpx;
+				font-size: 20rpx;
+				padding: 5rpx 10rpx;
+				background-color: #040404;
+				color: #FFFFFF;
+				box-sizing: border-box;
+				// border-radius: 5rpx;
+				}
+				.coutall{
+					color: #AE8445;
+					display: block;
+					overflow: visible;
+					margin-left: 14rpx;
+					font-size: 22rpx;
+					padding: 5rpx 10rpx;
+					border-radius: 5rpx;
+					width: 130rpx;
+					text-align: center;
+					border: 1rpx solid #AE8445;
+				}
+			}
+			.titlewo{
+				font-size: 22rpx;
+				color: #999999;
+				padding-top: 15rpx;
+				.qing{
+					
+				}
+				.daprice{
+					text-decoration: line-through;
+					padding-left: 30rpx;
+				}
+				
+			}
+			.pricecop{
+				color: #AE8445;
+				padding-top: 25rpx;
+				font-size: 22rpx;
+			}
+			.price{
+				display: block;
+				color: #AE8445;
+				padding-top: 18rpx;
+			}
+		}
+		
+	}
+	.total{
+		padding: 25rpx 50rpx;
+		.opentotal{
+			font-size: 25rpx;
+			
+			.you{
+				display: flex;
+				justify-content: space-between;
+				align-items: center;
+				padding: 18rpx 0;
+				.zhan{
+					display: flex;
+				}
+				.openbutton{
+						width: 120rpx;
+						text-align: right;
+						color: #A18353;
+				}
+			}
+			.gan{
+				margin-left: 62rpx;
+				// margin-right: 90rpx;
+				padding: 5rpx 0;
+				border-bottom: 1rpx solid #EFEFEF;
+				
+			}
+		}
+	}
+	.canshu{
+		text-align: center;
+		
+		.canshutitle{
+			color: #222222;
+			display: flex;
+			flex-direction: column;
+			margin: 45rpx 0;
+		}
+		.td-form{
+			padding:0 40rpx;
+			box-sizing: border-box;
+			padding-bottom: 80rpx;
+		}
+	}
+</style>

+ 20 - 0
responsive/left-window.vue

xqd
@@ -0,0 +1,20 @@
+<template>  
+  <view>  
+    <!-- 将原来的详情页(/pages/detail/detail.vue),作为一个组件(pages-detail-detail)使用 -->  
+    <!-- <pages-detail-detail ref="detailPage"></pages-detail-detail> -->
+  </view>  
+</template>  
+
+<script>  
+  export default {  
+    created(e) {  
+      //监听自定义事件,该事件由左侧列表页的点击触发  
+      uni.$on('updateDetail', (e) => {  
+        // 执行 detailPage组件,即:/pages/detail/detail.vue 页面的load方法  
+        this.$refs.detailPage.load(e.detail);  
+      })  
+    }  
+  }  
+
+<style>
+</style>

+ 10 - 0
siteInfo.js

xqd
@@ -0,0 +1,10 @@
+
+module.exports = {
+	acid: -1,
+	version: "1.0.0",
+	siteroot: "https://wechat.xcrjhuixiangjia.com",
+	apiroot: "https://wechat.xcrjhuixiangjia.com/web/index.php?_mall_id=10000&r=api/big-screen/compositions&estate_id=3"
+	// siteroot: "https://t17.9026.com",
+	// apiroot: "https://t17.9026.com//web/index.php?_mall_id=10000"
+
+};

+ 224 - 0
static/app-plus/mp-html/js/handler.js

xqd
@@ -0,0 +1,224 @@
+'use strict'
+
+// 等待初始化完毕
+document.addEventListener('UniAppJSBridgeReady', () => {
+    document.body.onclick = function () {
+        return uni.postMessage({
+            data: {
+                action: 'onClick'
+            }
+        })
+    }
+
+    uni.postMessage({
+        data: {
+            action: 'onJSBridgeReady'
+        }
+    })
+})
+let options
+let medias = []
+/**
+ * @description 获取标签的所有属性
+ * @param {Element} ele
+ */
+
+function getAttrs(ele) {
+    const attrs = Object.create(null)
+
+    for (let i = ele.attributes.length; i--;) {
+        attrs[ele.attributes[i].name] = ele.attributes[i].value
+    }
+
+    return attrs
+}
+/**
+ * @description 图片加载出错
+ */
+
+function onImgError() {
+    if (options[1]) {
+        this.src = options[1]
+        this.onerror = null
+    } // 取消监听点击
+
+    this.onclick = null
+    this.ontouchstart = null
+    uni.postMessage({
+        data: {
+            action: 'onError',
+            source: 'img',
+            attrs: getAttrs(this)
+        }
+    })
+}
+/**
+ * @description 创建 dom 结构
+ * @param {object[]} nodes 节点数组
+ * @param {Element} parent 父节点
+ * @param {string} namespace 命名空间
+ */
+
+function createDom(nodes, parent, namespace) {
+    const _loop = function _loop(i) {
+        const node = nodes[i]
+        let ele = void 0
+
+        if (!node.type || node.type == 'node') {
+            let { name } = node // svg 需要设置 namespace
+
+            if (name == 'svg') namespace = 'http://www.w3.org/2000/svg'
+            if (name == 'html' || name == 'body') name = 'div' // 创建标签
+
+            if (!namespace) ele = document.createElement(name); else ele = document.createElementNS(namespace, name) // 设置属性
+
+            for (const item in node.attrs) {
+                ele.setAttribute(item, node.attrs[item])
+            } // 递归创建子节点
+
+            if (node.children) createDom(node.children, ele, namespace) // 处理图片
+
+            if (name == 'img') {
+                if (!ele.src && ele.getAttribute('data-src')) ele.src = ele.getAttribute('data-src')
+
+                if (!node.attrs.ignore) {
+                    // 监听图片点击事件
+                    ele.onclick = function (e) {
+                        e.stopPropagation()
+                        uni.postMessage({
+                            data: {
+                                action: 'onImgTap',
+                                attrs: getAttrs(this)
+                            }
+                        })
+                    }
+                }
+
+                if (options[2]) {
+                    image = new Image()
+                    image.src = ele.src
+                    ele.src = options[2]
+
+                    image.onload = function () {
+                        ele.src = this.src
+                    }
+
+                    image.onerror = function () {
+                        ele.onerror()
+                    }
+                }
+
+                ele.onerror = onImgError
+            } // 处理链接
+            else if (name == 'a') {
+                ele.addEventListener('click', function (e) {
+                    e.stopPropagation()
+                    e.preventDefault() // 阻止默认跳转
+
+                    const href = this.getAttribute('href')
+                    let offset
+                    if (href && href[0] == '#') offset = (document.getElementById(href.substr(1)) || {}).offsetTop
+                    uni.postMessage({
+                        data: {
+                            action: 'onLinkTap',
+                            attrs: getAttrs(this),
+                            offset
+                        }
+                    })
+                }, true)
+            } // 处理音视频
+            else if (name == 'video' || name == 'audio') {
+                medias.push(ele)
+
+                if (!node.attrs.autoplay) {
+                    if (!node.attrs.controls) ele.setAttribute('controls', 'true') // 空白图占位
+
+                    if (!node.attrs.poster) ele.setAttribute('poster', "data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg'/>")
+                }
+
+                if (options[3]) {
+                    ele.onplay = function () {
+                        for (let _i = 0; _i < medias.length; _i++) {
+                            if (medias[_i] != this) medias[_i].pause()
+                        }
+                    }
+                }
+
+                ele.onerror = function () {
+                    uni.postMessage({
+                        data: {
+                            action: 'onError',
+                            source: name,
+                            attrs: getAttrs(this)
+                        }
+                    })
+                }
+            } // 处理表格
+            else if (name == 'table' && options[4] && !ele.style.cssText.includes('inline')) {
+                const div = document.createElement('div')
+                div.style.overflow = 'auto'
+                div.appendChild(ele)
+                ele = div
+            } else if (name == 'svg') namespace = void 0
+        } else ele = document.createTextNode(node.text.replace(/&amp;/g, '&'))
+
+        parent.appendChild(ele)
+    }
+
+    for (let i = 0; i < nodes.length; i++) {
+        var image
+
+        _loop(i)
+    }
+} // 设置 html 内容
+
+window.setContent = function (nodes, opts, append) {
+    const ele = document.getElementById('content') // 背景颜色
+
+    if (opts[0]) document.body.bgColor = opts[0] // 长按复制
+
+    if (!opts[5]) ele.style.userSelect = 'none'
+
+    if (!append) {
+        ele.innerHTML = '' // 不追加则先清空
+
+        medias = []
+    }
+
+    options = opts
+    const fragment = document.createDocumentFragment()
+    createDom(nodes, fragment)
+    ele.appendChild(fragment) // 触发事件
+
+    let height = ele.scrollHeight
+    uni.postMessage({
+        data: {
+            action: 'onLoad',
+            height
+        }
+    })
+    clearInterval(window.timer)
+    let ready = false
+    window.timer = setInterval(() => {
+        if (ele.scrollHeight != height) {
+            height = ele.scrollHeight
+            uni.postMessage({
+                data: {
+                    action: 'onHeightChange',
+                    height
+                }
+            })
+        } else if (!ready) {
+            ready = true
+            uni.postMessage({
+                data: {
+                    action: 'onReady'
+                }
+            })
+        }
+    }, 350)
+} // 回收计时器
+
+window.onunload = function () {
+    clearInterval(window.timer)
+}

+ 19 - 0
static/app-plus/mp-html/js/uni.webview.min.js

xqd
@@ -0,0 +1,19 @@
+!(function (e, n) { typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = n() : typeof define === 'function' && define.amd ? define(n) : (e = e || self).uni = n() }(this, (() => {
+    'use strict'
+
+    try { const e = {}; Object.defineProperty(e, 'passive', { get() { !0 } }), window.addEventListener('test-passive', null, e) } catch (e) {} const n = Object.prototype.hasOwnProperty; function t(e, t) { return n.call(e, t) } const i = []; const a = function (e, n) { const t = { options: { timestamp: +new Date() }, name: e, arg: n }; if (window.__dcloud_weex_postMessage || window.__dcloud_weex_) { if (e === 'postMessage') { const a = { data: [n] }; return window.__dcloud_weex_postMessage ? window.__dcloud_weex_postMessage(a) : window.__dcloud_weex_.postMessage(JSON.stringify(a)) } const o = { type: 'WEB_INVOKE_APPSERVICE', args: { data: t, webviewIds: i } }; window.__dcloud_weex_postMessage ? window.__dcloud_weex_postMessageToService(o) : window.__dcloud_weex_.postMessageToService(JSON.stringify(o)) } if (!window.plus) return window.parent.postMessage({ type: 'WEB_INVOKE_APPSERVICE', data: t, pageId: '' }, '*'); if (i.length === 0) { const r = plus.webview.currentWebview(); if (!r) throw new Error('plus.webview.currentWebview() is undefined'); const d = r.parent(); let s = ''; s = d ? d.id : r.id, i.push(s) } if (plus.webview.getWebviewById('__uniapp__service'))plus.webview.postMessageToUniNView({ type: 'WEB_INVOKE_APPSERVICE', args: { data: t, webviewIds: i } }, '__uniapp__service'); else { const w = JSON.stringify(t); plus.webview.getLaunchWebview().evalJS('UniPlusBridge.subscribeHandler("'.concat('WEB_INVOKE_APPSERVICE', '",').concat(w, ',').concat(JSON.stringify(i), ');')) } }; const o = {
+        navigateTo() { const e = arguments.length > 0 && void 0 !== arguments[0] ? arguments[0] : {}; const n = e.url; a('navigateTo', { url: encodeURI(n) }) }, navigateBack() { const e = arguments.length > 0 && void 0 !== arguments[0] ? arguments[0] : {}; const n = e.delta; a('navigateBack', { delta: parseInt(n) || 1 }) }, switchTab() { const e = arguments.length > 0 && void 0 !== arguments[0] ? arguments[0] : {}; const n = e.url; a('switchTab', { url: encodeURI(n) }) }, reLaunch() { const e = arguments.length > 0 && void 0 !== arguments[0] ? arguments[0] : {}; const n = e.url; a('reLaunch', { url: encodeURI(n) }) }, redirectTo() { const e = arguments.length > 0 && void 0 !== arguments[0] ? arguments[0] : {}; const n = e.url; a('redirectTo', { url: encodeURI(n) }) }, getEnv(e) { window.plus ? e({ plus: !0 }) : e({ h5: !0 }) }, postMessage() { const e = arguments.length > 0 && void 0 !== arguments[0] ? arguments[0] : {}; a('postMessage', e.data || {}) }
+    }; const r = /uni-app/i.test(navigator.userAgent); const d = /Html5Plus/i.test(navigator.userAgent); const s = /complete|loaded|interactive/; const w = window.my && navigator.userAgent.indexOf('AlipayClient') > -1; const u = window.swan && window.swan.webView && /swan/i.test(navigator.userAgent); const c = window.qq && window.qq.miniProgram && /QQ/i.test(navigator.userAgent) && /miniProgram/i.test(navigator.userAgent); const g = window.tt && window.tt.miniProgram && /toutiaomicroapp/i.test(navigator.userAgent); const v = window.wx && window.wx.miniProgram && /micromessenger/i.test(navigator.userAgent) && /miniProgram/i.test(navigator.userAgent); const p = window.qa && /quickapp/i.test(navigator.userAgent); for (var l, _ = function () { window.UniAppJSBridge = !0, document.dispatchEvent(new CustomEvent('UniAppJSBridgeReady', { bubbles: !0, cancelable: !0 })) }, f = [function (e) { if (r || d) return window.__dcloud_weex_postMessage || window.__dcloud_weex_ ? document.addEventListener('DOMContentLoaded', e) : window.plus && s.test(document.readyState) ? setTimeout(e, 0) : document.addEventListener('plusready', e), o }, function (e) { if (v) return window.WeixinJSBridge && window.WeixinJSBridge.invoke ? setTimeout(e, 0) : document.addEventListener('WeixinJSBridgeReady', e), window.wx.miniProgram }, function (e) { if (c) return window.QQJSBridge && window.QQJSBridge.invoke ? setTimeout(e, 0) : document.addEventListener('QQJSBridgeReady', e), window.qq.miniProgram }, function (e) {
+            if (w) {
+                document.addEventListener('DOMContentLoaded', e); const n = window.my; return {
+                    navigateTo: n.navigateTo, navigateBack: n.navigateBack, switchTab: n.switchTab, reLaunch: n.reLaunch, redirectTo: n.redirectTo, postMessage: n.postMessage, getEnv: n.getEnv
+                }
+            }
+        }, function (e) { if (u) return document.addEventListener('DOMContentLoaded', e), window.swan.webView }, function (e) { if (g) return document.addEventListener('DOMContentLoaded', e), window.tt.miniProgram }, function (e) {
+            if (p) {
+                window.QaJSBridge && window.QaJSBridge.invoke ? setTimeout(e, 0) : document.addEventListener('QaJSBridgeReady', e); const n = window.qa; return {
+                    navigateTo: n.navigateTo, navigateBack: n.navigateBack, switchTab: n.switchTab, reLaunch: n.reLaunch, redirectTo: n.redirectTo, postMessage: n.postMessage, getEnv: n.getEnv
+                }
+            }
+        }, function (e) { return document.addEventListener('DOMContentLoaded', e), o }], m = 0; m < f.length && !(l = f[m](_)); m++);l || (l = {}); const E = typeof uni !== 'undefined' ? uni : {}; if (!E.navigateTo) for (const b in l)t(l, b) && (E[b] = l[b]); return E.webView = l, E
+})))

+ 1 - 0
static/app-plus/mp-html/local.html

xqd
@@ -0,0 +1 @@
+<head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no"><style>body,html{width:100%;height:100%;overflow:hidden}body{margin:0}video{width:300px;height:225px}img{max-width:100%;-webkit-touch-callout:none}@keyframes show{0%{opacity:0}100%{opacity:1}}</style></head><body><div id="content"></div><script type="text/javascript" src="./js/uni.webview.min.js"></script><script type="text/javascript" src="./js/handler.js"></script></body>

+ 363 - 0
static/common/js/touch-emulator.js

xqd
@@ -0,0 +1,363 @@
+(function (window, document, exportName, undefined) {
+    "use strict";
+
+    var isMultiTouch = false;
+    var multiTouchStartPos;
+    var eventTarget;
+    var touchElements = {};
+
+    // polyfills
+    if (!document.createTouch) {
+        document.createTouch = function (view, target, identifier, pageX, pageY, screenX, screenY, clientX, clientY) {
+            // auto set
+            if (clientX == undefined || clientY == undefined) {
+                clientX = pageX - window.pageXOffset;
+                clientY = pageY - window.pageYOffset;
+            }
+
+            return new Touch(target, identifier, {
+                pageX: pageX,
+                pageY: pageY,
+                screenX: screenX,
+                screenY: screenY,
+                clientX: clientX,
+                clientY: clientY
+            });
+        };
+    }
+
+    if (!document.createTouchList) {
+        document.createTouchList = function () {
+            var touchList = new TouchList();
+            for (var i = 0; i < arguments.length; i++) {
+                touchList[i] = arguments[i];
+            }
+            touchList.length = arguments.length;
+            return touchList;
+        };
+    }
+
+    /**
+     * create an touch point
+     * @constructor
+     * @param target
+     * @param identifier
+     * @param pos
+     * @param deltaX
+     * @param deltaY
+     * @returns {Object} touchPoint
+     */
+    function Touch(target, identifier, pos, deltaX, deltaY) {
+        deltaX = deltaX || 0;
+        deltaY = deltaY || 0;
+
+        this.identifier = identifier;
+        this.target = target;
+        this.clientX = pos.clientX + deltaX;
+        this.clientY = pos.clientY + deltaY;
+        this.screenX = pos.screenX + deltaX;
+        this.screenY = pos.screenY + deltaY;
+        this.pageX = pos.pageX + deltaX;
+        this.pageY = pos.pageY + deltaY;
+    }
+
+    /**
+     * create empty touchlist with the methods
+     * @constructor
+     * @returns touchList
+     */
+    function TouchList() {
+        var touchList = [];
+
+        touchList.item = function (index) {
+            return this[index] || null;
+        };
+
+        // specified by Mozilla
+        touchList.identifiedTouch = function (id) {
+            return this[id + 1] || null;
+        };
+
+        return touchList;
+    }
+
+
+    /**
+     * Simple trick to fake touch event support
+     * this is enough for most libraries like Modernizr and Hammer
+     */
+    function fakeTouchSupport() {
+        var objs = [window, document.documentElement];
+        var props = ['ontouchstart', 'ontouchmove', 'ontouchcancel', 'ontouchend'];
+
+        for (var o = 0; o < objs.length; o++) {
+            for (var p = 0; p < props.length; p++) {
+                if (objs[o] && objs[o][props[p]] == undefined) {
+                    objs[o][props[p]] = null;
+                }
+            }
+        }
+    }
+
+    /**
+     * we don't have to emulate on a touch device
+     * @returns {boolean}
+     */
+    function hasTouchSupport() {
+        return ("ontouchstart" in window) || // touch events
+            (window.Modernizr && window.Modernizr.touch) || // modernizr
+            (navigator.msMaxTouchPoints || navigator.maxTouchPoints) > 2; // pointer events
+    }
+
+    /**
+     * disable mouseevents on the page
+     * @param ev
+     */
+    function preventMouseEvents(ev) {
+        // 注释启用默认事件
+        // ev.preventDefault();
+        // ev.stopPropagation();
+    }
+
+    /**
+     * only trigger touches when the left mousebutton has been pressed
+     * @param touchType
+     * @returns {Function}
+     */
+    function onMouse(touchType) {
+        return function (ev) {
+            // prevent mouse events
+            preventMouseEvents(ev);
+
+            if (ev.which !== 1) {
+                return;
+            }
+
+            // The EventTarget on which the touch point started when it was first placed on the surface,
+            // even if the touch point has since moved outside the interactive area of that element.
+            // also, when the target doesnt exist anymore, we update it
+            if (ev.type == 'mousedown' || !eventTarget || (eventTarget && !eventTarget.dispatchEvent)) {
+                eventTarget = ev.target;
+            }
+
+            // shiftKey has been lost, so trigger a touchend
+            if (isMultiTouch && !ev.shiftKey) {
+                triggerTouch('touchend', ev);
+                isMultiTouch = false;
+            }
+
+            triggerTouch(touchType, ev);
+
+            // we're entering the multi-touch mode!
+            if (!isMultiTouch && ev.shiftKey) {
+                isMultiTouch = true;
+                multiTouchStartPos = {
+                    pageX: ev.pageX,
+                    pageY: ev.pageY,
+                    clientX: ev.clientX,
+                    clientY: ev.clientY,
+                    screenX: ev.screenX,
+                    screenY: ev.screenY
+                };
+                triggerTouch('touchstart', ev);
+            }
+
+            // reset
+            if (ev.type == 'mouseup') {
+                multiTouchStartPos = null;
+                isMultiTouch = false;
+                eventTarget = null;
+            }
+        }
+    }
+
+    /**
+     * trigger a touch event
+     * @param eventName
+     * @param mouseEv
+     */
+    function triggerTouch(eventName, mouseEv) {
+        var touchEvent = document.createEvent('Event');
+        touchEvent.initEvent(eventName, true, true);
+
+        touchEvent.altKey = mouseEv.altKey;
+        touchEvent.ctrlKey = mouseEv.ctrlKey;
+        touchEvent.metaKey = mouseEv.metaKey;
+        touchEvent.shiftKey = mouseEv.shiftKey;
+
+        touchEvent.touches = getActiveTouches(mouseEv, eventName);
+        touchEvent.targetTouches = getActiveTouches(mouseEv, eventName);
+        touchEvent.changedTouches = getChangedTouches(mouseEv, eventName);
+
+        eventTarget.dispatchEvent(touchEvent);
+    }
+
+    /**
+     * create a touchList based on the mouse event
+     * @param mouseEv
+     * @returns {TouchList}
+     */
+    function createTouchList(mouseEv) {
+        var touchList = new TouchList();
+
+        if (isMultiTouch) {
+            var f = TouchEmulator.multiTouchOffset;
+            var deltaX = multiTouchStartPos.pageX - mouseEv.pageX;
+            var deltaY = multiTouchStartPos.pageY - mouseEv.pageY;
+
+            touchList.push(new Touch(eventTarget, 1, multiTouchStartPos, (deltaX * -1) - f, (deltaY * -1) + f));
+            touchList.push(new Touch(eventTarget, 2, multiTouchStartPos, deltaX + f, deltaY - f));
+        } else {
+            touchList.push(new Touch(eventTarget, 1, mouseEv, 0, 0));
+        }
+
+        return touchList;
+    }
+
+    /**
+     * receive all active touches
+     * @param mouseEv
+     * @returns {TouchList}
+     */
+    function getActiveTouches(mouseEv, eventName) {
+        // empty list
+        if (mouseEv.type == 'mouseup') {
+            return new TouchList();
+        }
+
+        var touchList = createTouchList(mouseEv);
+        if (isMultiTouch && mouseEv.type != 'mouseup' && eventName == 'touchend') {
+            touchList.splice(1, 1);
+        }
+        return touchList;
+    }
+
+    /**
+     * receive a filtered set of touches with only the changed pointers
+     * @param mouseEv
+     * @param eventName
+     * @returns {TouchList}
+     */
+    function getChangedTouches(mouseEv, eventName) {
+        var touchList = createTouchList(mouseEv);
+
+        // we only want to return the added/removed item on multitouch
+        // which is the second pointer, so remove the first pointer from the touchList
+        //
+        // but when the mouseEv.type is mouseup, we want to send all touches because then
+        // no new input will be possible
+        if (isMultiTouch && mouseEv.type != 'mouseup' &&
+            (eventName == 'touchstart' || eventName == 'touchend')) {
+            touchList.splice(0, 1);
+        }
+
+        return touchList;
+    }
+
+    /**
+     * show the touchpoints on the screen
+     */
+    function showTouches(ev) {
+        var touch, i, el, styles;
+
+        // first all visible touches
+        for (i = 0; i < ev.touches.length; i++) {
+            touch = ev.touches[i];
+            el = touchElements[touch.identifier];
+            if (!el) {
+                el = touchElements[touch.identifier] = document.createElement("div");
+                document.body.appendChild(el);
+            }
+
+            styles = TouchEmulator.template(touch);
+            for (var prop in styles) {
+                el.style[prop] = styles[prop];
+            }
+        }
+
+        // remove all ended touches
+        if (ev.type == 'touchend' || ev.type == 'touchcancel') {
+            for (i = 0; i < ev.changedTouches.length; i++) {
+                touch = ev.changedTouches[i];
+                el = touchElements[touch.identifier];
+                if (el) {
+                    el.parentNode.removeChild(el);
+                    delete touchElements[touch.identifier];
+                }
+            }
+        }
+    }
+
+    /**
+     * TouchEmulator initializer
+     */
+    function TouchEmulator() {
+        if (hasTouchSupport()) {
+            return;
+        }
+
+        fakeTouchSupport();
+
+        window.addEventListener("mousedown", onMouse('touchstart'), true);
+        window.addEventListener("mousemove", onMouse('touchmove'), true);
+        window.addEventListener("mouseup", onMouse('touchend'), true);
+
+        window.addEventListener("mouseenter", preventMouseEvents, true);
+        window.addEventListener("mouseleave", preventMouseEvents, true);
+        window.addEventListener("mouseout", preventMouseEvents, true);
+        window.addEventListener("mouseover", preventMouseEvents, true);
+
+        // it uses itself!
+        window.addEventListener("touchstart", showTouches, true);
+        window.addEventListener("touchmove", showTouches, true);
+        window.addEventListener("touchend", showTouches, true);
+        window.addEventListener("touchcancel", showTouches, true);
+    }
+
+    // start distance when entering the multitouch mode
+    TouchEmulator.multiTouchOffset = 75;
+
+    /**
+     * css template for the touch rendering
+     * @param touch
+     * @returns object
+     */
+    TouchEmulator.template = function (touch) {
+        var size = 0;
+        var transform = 'translate(' + (touch.clientX - (size / 2)) + 'px, ' + (touch.clientY - (size / 2)) + 'px)';
+        return {
+            position: 'fixed',
+            left: 0,
+            top: 0,
+            background: '#fff',
+            border: 'solid 1px #999',
+            opacity: .6,
+            borderRadius: '100%',
+            height: size + 'px',
+            width: size + 'px',
+            padding: 0,
+            margin: 0,
+            display: 'block',
+            overflow: 'hidden',
+            pointerEvents: 'none',
+            webkitUserSelect: 'none',
+            mozUserSelect: 'none',
+            userSelect: 'none',
+            webkitTransform: transform,
+            mozTransform: transform,
+            transform: transform,
+            zIndex: 100
+        }
+    };
+
+    // export
+    if (typeof define == "function" && define.amd) {
+        define(function () {
+            return TouchEmulator;
+        });
+    } else if (typeof module != "undefined" && module.exports) {
+        module.exports = TouchEmulator;
+    } else {
+        window[exportName] = TouchEmulator;
+    }
+})(window, document, "TouchEmulator");

+ 13 - 0
static/css/border-box.scss

xqd
@@ -0,0 +1,13 @@
+// #ifndef MP-TOUTIAO
+page, block, view, scroll-view, swiper, movable-view, icon, text, progress,
+button, checkbox, form, input, label, picker, picker-view, radio, slider, switch,
+textarea, navigator, audio, image, video, map, canvas, contact-button, cover-image, cover-view{
+  box-sizing: border-box;
+}
+// #endif
+
+// #ifdef MP-TOUTIAO
+* {
+  box-sizing: border-box;
+}
+// #endif

+ 266 - 0
static/css/flex.scss

xqd
@@ -0,0 +1,266 @@
+.dir-left-nowrap {
+  /* 主轴 排列方式从左侧开始 不换行*/
+  display: -webkit-box;
+  display: -webkit-flex;
+  display: flex;
+  -webkit-flex-direction: row;
+  flex-direction: row;
+  flex-wrap: nowrap;
+}
+
+.dir-left-wrap {
+  /* 主轴 排列方式从左侧开始 换行*/
+  display: -webkit-box;
+  display: -webkit-flex;
+  display: flex;
+  -webkit-flex-direction: row;
+  flex-direction: row;
+  flex-wrap: wrap;
+}
+
+.dir-left-wrap-reverse {
+  /* 主轴 排列方式从左侧开始 换行 第一行在下方*/
+  display: -webkit-box;
+  display: -webkit-flex;
+  display: flex;
+  -webkit-flex-direction: row;
+  flex-direction: row;
+  flex-wrap: wrap-reverse;
+}
+
+.dir-right-nowrap {
+  /* 主轴 排列方式从 右侧开始 */
+  display: -webkit-box;
+  display: -webkit-flex;
+  display: flex;
+  -webkit-flex-direction: row-reverse;
+  flex-direction: row-reverse;
+  flex-wrap: nowrap;
+}
+
+.dir-right-wrap {
+  /* 主轴 排列方式从 右侧开始 换行*/
+  display: -webkit-box;
+  display: -webkit-flex;
+  display: flex;
+  -webkit-flex-direction: row;
+  flex-direction: row;
+  flex-wrap: wrap;
+}
+
+.dir-right-wrap-reverse {
+  /* 主轴 排列方式从 右侧开始 换行 第一行在下方*/
+  display: -webkit-box;
+  display: -webkit-flex;
+  display: flex;
+  -webkit-flex-direction: row;
+  flex-direction: row;
+  flex-wrap: wrap-reverse;
+}
+
+.dir-top-nowrap {
+  /* 主轴 排列方式从顶部开始 不换行 */
+  display: -webkit-box;
+  display: -webkit-flex;
+  display: flex;
+  -webkit-box-orient: vertical;
+  -webkit-flex-direction: column;
+  flex-direction: column;
+  flex-wrap: nowrap;
+}
+
+.dir-top-wrap {
+  /* 主轴 排列方式从顶部开始  换行*/
+  display: -webkit-box;
+  display: -webkit-flex;
+  display: flex;
+  -webkit-box-orient: vertical;
+  -webkit-flex-direction: column;
+  flex-direction: column;
+  flex-wrap: wrap;
+}
+
+.dir-top-wrap-reverse {
+  /* 主轴 排列方式从顶部开始 不换行换行 第一行在下方*/
+  display: -webkit-box;
+  display: -webkit-flex;
+  display: flex;
+  -webkit-box-orient: vertical;
+  -webkit-flex-direction: column;
+  flex-direction: column;
+  flex-wrap: wrap-reverse;
+}
+
+.dir-bottom-nowrap {
+  /* 主轴 排列方式从底部开始 */
+  display: -webkit-box;
+  display: -webkit-flex;
+  display: flex;
+  -webkit-flex-direction: column-reverse;
+  flex-direction: column-reverse;
+  flex-wrap: nowrap;
+}
+
+.dir-bottom-wrap {
+  /* 主轴 排列方式从底部开始 不换行 换行*/
+  display: -webkit-box;
+  display: -webkit-flex;
+  display: flex;
+  -webkit-box-orient: vertical;
+  -webkit-flex-direction: column;
+  flex-direction: column;
+  flex-wrap: wrap;
+}
+
+.dir-bottom-wrap-reverse {
+  /* 主轴 排列方式从底部开始 不换行换行 第一行在下方*/
+  display: -webkit-box;
+  display: -webkit-flex;
+  display: flex;
+  -webkit-box-orient: vertical;
+  -webkit-flex-direction: column;
+  flex-direction: column;
+  flex-wrap: wrap-reverse;
+}
+
+.main-left {
+  /* 主轴 左对齐 */
+  display: -webkit-box;
+  display: -webkit-flex;
+  display: flex;
+  -webkit-justify-content: flex-start;
+  justify-content: flex-start;
+}
+
+.main-right {
+  /* 主轴 右对齐 */
+  display: -webkit-box;
+  display: -webkit-flex;
+  display: flex;
+  -webkit-justify-content: flex-end;
+  justify-content: flex-end;
+}
+
+.main-between {
+  /* 主轴 两端对齐 */
+  display: -webkit-box;
+  display: -webkit-flex;
+  display: flex;
+  -webkit-justify-content: space-between;
+  justify-content: space-between;
+}
+
+.main-center {
+  /* 主轴 居中对齐 */
+  display: -webkit-box;
+  display: -webkit-flex;
+  display: flex;
+  -webkit-box-pack: center;
+  -webkit-justify-content: center;
+  -ms-flex-pack: center;
+  justify-content: center;
+}
+
+.main-around {
+  /* 主轴 项目位于各行之前、之间、之后都留有空白的容器内*/
+  display: -webkit-box;
+  display: -webkit-flex;
+  display: flex;
+  -webkit-justify-content: space-around;
+  justify-content: space-around;
+}
+
+.cross-top {
+  /* 交叉轴 起点对齐 */
+  display: -webkit-box;
+  display: -webkit-flex;
+  display: flex;
+  -webkit-align-items: flex-start;
+  align-items: flex-start;
+}
+
+.cross-bottom {
+  /* 交叉轴 终点对齐 */
+  display: -webkit-box;
+  display: -webkit-flex;
+  display: flex;
+  -webkit-box-align: end;
+  -webkit-align-items: flex-end;
+  -ms-flex-align: end;
+  -ms-grid-row-align: flex-end;
+  align-items: flex-end;
+}
+
+.cross-baseline {
+  /* 交叉轴 第一行文字基线对齐 */
+  display: -webkit-box;
+  display: -webkit-flex;
+  display: flex;
+  -webkit-align-items: baseline;
+  align-items: baseline;
+}
+
+.cross-center {
+  /* 交叉轴 居中对齐 */
+  display: -webkit-box;
+  display: -webkit-flex;
+  display: flex;
+  -webkit-align-items: center;
+  align-items: center;
+}
+
+.cross-stretch {
+  /* 交叉轴 高度并排铺满 高度不固定*/
+  display: -webkit-box;
+  display: -webkit-flex;
+  display: flex;
+  -webkit-align-items: stretch;
+  align-items: stretch;
+}
+
+.flex-wrap {
+  /* 流模式 第一行在上方 */
+  display: -webkit-box;
+  display: -webkit-flex;
+  display: flex;
+  -webkit-flex-wrap: wrap;
+  flex-wrap: wrap;
+}
+
+.flex-wrap-reverse {
+  /* 流模式 第一行在下方 */
+  display: -webkit-box;
+  display: -webkit-flex;
+  display: flex;
+  -webkit-flex-wrap: wrap-reverse;
+  flex-wrap: wrap-reverse;
+}
+
+.box-grow-0 {
+  /* flex 子元素固定宽度*/
+  min-width: 0;
+  -webkit-box-flex: 0;
+  -webkit-flex-grow: 0;
+  -ms-flex-positive: 0;
+  flex-grow: 0;
+  -webkit-flex-shrink: 0;
+  -ms-flex-negative: 0;
+  flex-shrink: 0;
+}
+
+.box-grow-1 {
+  /* flex 子元素等分 */
+  min-width: 0;
+  -webkit-box-flex: 1;
+  -webkit-flex-grow: 1;
+  -ms-flex-positive: 1;
+  flex-grow: 1;
+  -webkit-flex-shrink: 1;
+  -ms-flex-negative: 1;
+  flex-shrink: 1;
+}
+
+.inline-flex {
+  display: -webkit-inline-flex;
+  display: inline-flex;
+}

+ 52 - 0
static/css/text.scss

xqd
@@ -0,0 +1,52 @@
+.t-omit {
+    /* 
+        单行文本显示、超出省略 
+        注意:在flex部分布局下使用可能会冲突  
+    */
+    word-break: break-all;
+    text-overflow: ellipsis;
+    display: -webkit-box;
+    -webkit-box-orient: vertical;
+    -webkit-line-clamp: 1;
+    overflow: hidden;
+	white-space: nowrap;
+}
+
+.t-omit-two {
+    /* 
+        两行文本超出省略显示  
+        注意:在flex部分布局下使用可能会冲突
+    */
+    word-break: break-all;
+    text-overflow: ellipsis;
+    display: -webkit-box;
+    -webkit-box-orient: vertical;
+    -webkit-line-clamp: 2;
+    overflow: hidden;
+    white-space: normal !important;
+}
+
+.t-omit-three {
+    /* 
+        三行文本超出省略显示  
+        注意:在flex部分布局下使用可能会冲突
+    */
+    word-break: break-all;
+    text-overflow: ellipsis;
+    display: -webkit-box;
+    -webkit-box-orient: vertical;
+    -webkit-line-clamp: 3;
+    overflow: hidden;
+}
+.gray {
+    color: #999999;
+}
+  
+.black {
+    color: #353535;
+}
+  
+button::after {
+    border: 0;
+}
+

BIN=BIN
static/image/1.png


BIN=BIN
static/image/2.png


BIN=BIN
static/image/bz.png


BIN=BIN
static/image/bz1.png


BIN=BIN
static/image/couimgbg.png


BIN=BIN
static/image/fangan.png


BIN=BIN
static/image/goodsimg.png


BIN=BIN
static/image/hxj.jpg


BIN=BIN
static/image/programioc.png


BIN=BIN
static/image/viewmore.png


BIN=BIN
static/image/zu.png


BIN=BIN
static/uview/common/favicon.ico


BIN=BIN
static/uview/common/gray-logo.png


BIN=BIN
static/uview/common/logo.png


BIN=BIN
static/uview/example/component.png


BIN=BIN
static/uview/example/component_select.png


BIN=BIN
static/uview/example/js.png


BIN=BIN
static/uview/example/js_bak.png


BIN=BIN
static/uview/example/js_select.png


BIN=BIN
static/uview/example/template.png


BIN=BIN
static/uview/example/template_select.png


+ 17 - 0
store/index.js

xqd
@@ -0,0 +1,17 @@
+import Vue from 'vue'
+import Vuex from 'vuex'
+
+Vue.use(Vuex) // vue的插件机制
+
+// Vuex.Store 构造器选项
+const store = new Vuex.Store({
+    // 为了不和页面或组件的data中的造成混淆,state中的变量前面建议加上$符号
+    state: {
+        // 用户信息
+        $userInfo: {
+            id: 1
+        }
+    }
+})
+
+export default store

+ 42 - 0
template.h5.html

xqd
@@ -0,0 +1,42 @@
+<!DOCTYPE html>
+<html lang="zh-CN">
+	<head>
+		<meta charset="utf-8">
+		<meta http-equiv="X-UA-Compatible" content="IE=edge">
+		<link rel="shortcut icon" type="image/x-icon" href="https://cdn.uviewui.com/uview/common/favicon.ico">
+		<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
+		<title>
+			<%= htmlWebpackPlugin.options.title %>
+		</title>
+		<!-- 正式发布的时候使用,开发期间不启用。↓ -->
+        <script src="/static/common/js/touch-emulator.js"></script>
+		<script>
+            TouchEmulator();
+		</script>
+        <style>
+            ::-webkit-scrollbar{
+                display: none;
+            }
+        </style>
+        <!-- 正式发布的时候使用,开发期间不启用。↑ -->
+		<script>
+			document.addEventListener('DOMContentLoaded', function() {
+				document.documentElement.style.fontSize = document.documentElement.clientWidth / 20 + 'px'
+			})
+		</script>
+		<link rel="stylesheet" href="<%= BASE_URL %>static/index.css" />
+	</head>
+	<body>
+		<!-- 该文件为 H5 平台的模板 HTML,并非应用入口。 -->
+		<!-- 请勿在此文件编写页面代码或直接运行此文件。 -->
+		<!-- 详见文档:https://uniapp.dcloud.io/collocation/manifest?id=h5-template -->
+		<noscript>
+			<strong>本站点必须要开启JavaScript才能运行</strong>
+		</noscript>
+		<div id="app"></div>
+		<!-- built files will be auto injected -->
+		<script>
+			/*BAIDU_STAT*/
+		</script>
+	</body>
+</html>

+ 6 - 0
uni.scss

xqd
@@ -0,0 +1,6 @@
+/**
+ * 下方引入的为uView UI的集成样式文件,为scss预处理器,其中包含了一些"u-"开头的自定义变量
+ * 使用的时候,请将下面的一行复制到您的uniapp项目根目录的uni.scss中即可
+ * uView自定义的css类名和scss变量,均以"u-"开头,不会造成冲突,请放心使用 
+ */
+@import '@/uni_modules/uview-ui/theme.scss';

+ 89 - 0
uni_modules/uni-datetime-picker/changelog.md

xqd
@@ -0,0 +1,89 @@
+## 2.2.4(2022-03-31)
+- 修复 Vue3 下动态赋值,单选类型未响应的 bug
+## 2.2.3(2022-03-28)
+- 修复 Vue3 下动态赋值未响应的 bug
+## 2.2.2(2021-12-10)
+- 修复 clear-icon 属性在小程序平台不生效的 bug
+## 2.2.1(2021-12-10)
+- 修复 日期范围选在小程序平台,必须多点击一次才能取消选中状态的 bug
+## 2.2.0(2021-11-19)
+- 优化 组件UI,并提供设计资源,详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource)
+- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-datetime-picker](https://uniapp.dcloud.io/component/uniui/uni-datetime-picker)
+## 2.1.5(2021-11-09) 
+- 新增 提供组件设计资源,组件样式调整
+## 2.1.4(2021-09-10)
+- 修复 hide-second 在移动端的 bug
+- 修复 单选赋默认值时,赋值日期未高亮的 bug
+- 修复 赋默认值时,移动端未正确显示时间的 bug
+## 2.1.3(2021-09-09)
+- 新增 hide-second 属性,支持只使用时分,隐藏秒
+## 2.1.2(2021-09-03)
+- 优化 取消选中时(范围选)直接开始下一次选择, 避免多点一次
+- 优化 移动端支持清除按钮,同时支持通过 ref 调用组件的 clear 方法
+- 优化 调整字号大小,美化日历界面
+- 修复 因国际化导致的 placeholder 失效的 bug
+## 2.1.1(2021-08-24)
+- 新增 支持国际化
+- 优化 范围选择器在 pc 端过宽的问题
+## 2.1.0(2021-08-09)
+- 新增 适配 vue3
+## 2.0.19(2021-08-09)
+- 新增 支持作为 uni-forms 子组件相关功能
+- 修复 在 uni-forms 中使用时,选择时间报 NAN 错误的 bug
+## 2.0.18(2021-08-05)
+- 修复 type 属性动态赋值无效的 bug
+- 修复 ‘确认’按钮被 tabbar 遮盖 bug
+- 修复 组件未赋值时范围选左、右日历相同的 bug
+## 2.0.17(2021-08-04)
+- 修复 范围选未正确显示当前值的 bug
+- 修复 h5 平台(移动端)报错 'cale' of undefined 的 bug
+## 2.0.16(2021-07-21)
+- 新增 return-type 属性支持返回 date 日期对象
+## 2.0.15(2021-07-14)
+- 修复 单选日期类型,初始赋值后不在当前日历的 bug
+- 新增 clearIcon 属性,显示框的清空按钮可配置显示隐藏(仅 pc 有效)
+- 优化 移动端移除显示框的清空按钮,无实际用途
+## 2.0.14(2021-07-14)
+- 修复 组件赋值为空,界面未更新的 bug
+- 修复 start 和 end 不能动态赋值的 bug
+- 修复 范围选类型,用户选择后再次选择右侧日历(结束日期)显示不正确的 bug
+## 2.0.13(2021-07-08)
+- 修复 范围选择不能动态赋值的 bug
+## 2.0.12(2021-07-08)
+- 修复 范围选择的初始时间在一个月内时,造成无法选择的bug
+## 2.0.11(2021-07-08)
+- 优化 弹出层在超出视窗边缘定位不准确的问题
+## 2.0.10(2021-07-08)
+- 修复 范围起始点样式的背景色与今日样式的字体前景色融合,导致日期字体看不清的 bug
+- 优化 弹出层在超出视窗边缘被遮盖的问题
+## 2.0.9(2021-07-07)
+- 新增 maskClick 事件
+- 修复 特殊情况日历 rpx 布局错误的 bug,rpx -> px
+- 修复 范围选择时清空返回值不合理的bug,['', ''] -> []
+## 2.0.8(2021-07-07)
+- 新增 日期时间显示框支持插槽
+## 2.0.7(2021-07-01)
+- 优化 添加 uni-icons 依赖
+## 2.0.6(2021-05-22)
+- 修复 图标在小程序上不显示的 bug
+- 优化 重命名引用组件,避免潜在组件命名冲突
+## 2.0.5(2021-05-20)
+- 优化 代码目录扁平化
+## 2.0.4(2021-05-12)
+- 新增 组件示例地址
+## 2.0.3(2021-05-10)
+- 修复 ios 下不识别 '-' 日期格式的 bug
+- 优化 pc 下弹出层添加边框和阴影
+## 2.0.2(2021-05-08)
+- 修复 在 admin 中获取弹出层定位错误的bug
+## 2.0.1(2021-05-08)
+- 修复 type 属性向下兼容,默认值从 date 变更为 datetime
+## 2.0.0(2021-04-30)
+- 支持日历形式的日期+时间的范围选择
+ > 注意:此版本不向后兼容,不再支持单独时间选择(type=time)及相关的 hide-second 属性(时间选可使用内置组件 picker)
+## 1.0.6(2021-03-18)
+- 新增 hide-second 属性,时间支持仅选择时、分
+- 修复 选择跟显示的日期不一样的 bug
+- 修复 chang事件触发2次的 bug
+- 修复 分、秒 end 范围错误的 bug
+- 优化 更好的 nvue 适配

+ 185 - 0
uni_modules/uni-datetime-picker/components/uni-datetime-picker/calendar-item.vue

xqd
@@ -0,0 +1,185 @@
+<template>
+	<view class="uni-calendar-item__weeks-box" :class="{
+		'uni-calendar-item--disable':weeks.disable,
+		'uni-calendar-item--before-checked-x':weeks.beforeMultiple,
+		'uni-calendar-item--multiple': weeks.multiple,
+		'uni-calendar-item--after-checked-x':weeks.afterMultiple,
+		}" @click="choiceDate(weeks)" @mouseenter="handleMousemove(weeks)">
+		<view class="uni-calendar-item__weeks-box-item" :class="{
+				'uni-calendar-item--checked':calendar.fullDate === weeks.fullDate && (calendar.userChecked || !checkHover),
+				'uni-calendar-item--checked-range-text': checkHover,
+				'uni-calendar-item--before-checked':weeks.beforeMultiple,
+				'uni-calendar-item--multiple': weeks.multiple,
+				'uni-calendar-item--after-checked':weeks.afterMultiple,
+				'uni-calendar-item--disable':weeks.disable,
+				}">
+			<text v-if="selected&&weeks.extraInfo" class="uni-calendar-item__weeks-box-circle"></text>
+			<text class="uni-calendar-item__weeks-box-text uni-calendar-item__weeks-box-text-disable uni-calendar-item--checked-text">{{weeks.date}}</text>
+		</view>
+		<view :class="{'uni-calendar-item--isDay': weeks.isDay}"></view>
+	</view>
+</template>
+
+<script>
+	export default {
+		props: {
+			weeks: {
+				type: Object,
+				default () {
+					return {}
+				}
+			},
+			calendar: {
+				type: Object,
+				default: () => {
+					return {}
+				}
+			},
+			selected: {
+				type: Array,
+				default: () => {
+					return []
+				}
+			},
+			lunar: {
+				type: Boolean,
+				default: false
+			},
+			checkHover: {
+				type: Boolean,
+				default: false
+			}
+		},
+		methods: {
+			choiceDate(weeks) {
+				this.$emit('change', weeks)
+			},
+			handleMousemove(weeks) {
+				this.$emit('handleMouse', weeks)
+			}
+		}
+	}
+</script>
+
+<style lang="scss" >
+	.uni-calendar-item__weeks-box {
+		flex: 1;
+		/* #ifndef APP-NVUE */
+		display: flex;
+		/* #endif */
+		flex-direction: column;
+		justify-content: center;
+		align-items: center;
+		margin: 1px 0;
+		position: relative;
+	}
+
+	.uni-calendar-item__weeks-box-text {
+		font-size: 14px;
+		// font-family: Lato-Bold, Lato;
+		font-weight: bold;
+		color: #455997;
+	}
+
+	.uni-calendar-item__weeks-lunar-text {
+		font-size: 12px;
+		color: #333;
+	}
+
+	.uni-calendar-item__weeks-box-item {
+		position: relative;
+		/* #ifndef APP-NVUE */
+		display: flex;
+		/* #endif */
+		flex-direction: column;
+		justify-content: center;
+		align-items: center;
+		width: 40px;
+		height: 40px;
+		/* #ifdef H5 */
+		cursor: pointer;
+		/* #endif */
+	}
+
+
+	.uni-calendar-item__weeks-box-circle {
+		position: absolute;
+		top: 5px;
+		right: 5px;
+		width: 8px;
+		height: 8px;
+		border-radius: 8px;
+		background-color: #dd524d;
+
+	}
+
+	.uni-calendar-item__weeks-box .uni-calendar-item--disable {
+		// background-color: rgba(249, 249, 249, $uni-opacity-disabled);
+		cursor: default;
+	}
+
+	.uni-calendar-item--disable .uni-calendar-item__weeks-box-text-disable {
+		color: #D1D1D1;
+	}
+
+	.uni-calendar-item--isDay {
+		position: absolute;
+		top: 10px;
+		right: 17%;
+		background-color: #dd524d;
+		width:6px;
+		height: 6px;
+		border-radius: 50%;
+	}
+
+	.uni-calendar-item--extra {
+		color: #dd524d;
+		opacity: 0.8;
+	}
+
+	.uni-calendar-item__weeks-box .uni-calendar-item--checked {
+		background-color: #007aff;
+		border-radius: 50%;
+		box-sizing: border-box;
+		border: 3px solid #fff;
+	}
+
+	.uni-calendar-item--checked .uni-calendar-item--checked-text {
+		color: #fff;
+	}
+
+	.uni-calendar-item--multiple .uni-calendar-item--checked-range-text {
+		color: #333;
+	}
+
+	.uni-calendar-item--multiple {
+		background-color:  #F6F7FC;
+		// color: #fff;
+	}
+
+	.uni-calendar-item--multiple .uni-calendar-item--before-checked,
+	.uni-calendar-item--multiple .uni-calendar-item--after-checked {
+		background-color: #409eff;
+		border-radius: 50%;
+		box-sizing: border-box;
+		border: 3px solid #F6F7FC;
+	}
+
+	.uni-calendar-item--before-checked .uni-calendar-item--checked-text,
+	.uni-calendar-item--after-checked .uni-calendar-item--checked-text {
+		color: #fff;
+	}
+
+	.uni-calendar-item--before-checked-x {
+		border-top-left-radius: 50px;
+		border-bottom-left-radius: 50px;
+		box-sizing: border-box;
+		background-color: #F6F7FC;
+	}
+
+	.uni-calendar-item--after-checked-x {
+		border-top-right-radius: 50px;
+		border-bottom-right-radius: 50px;
+		background-color: #F6F7FC;
+	}
+</style>

+ 898 - 0
uni_modules/uni-datetime-picker/components/uni-datetime-picker/calendar.vue

xqd
@@ -0,0 +1,898 @@
+<template>
+	<view class="uni-calendar" @mouseleave="leaveCale">
+		<view v-if="!insert&&show" class="uni-calendar__mask" :class="{'uni-calendar--mask-show':aniMaskShow}"
+			@click="clean"></view>
+		<view v-if="insert || show" class="uni-calendar__content"
+			:class="{'uni-calendar--fixed':!insert,'uni-calendar--ani-show':aniMaskShow, 'uni-calendar__content-mobile': aniMaskShow}">
+			<view class="uni-calendar__header" :class="{'uni-calendar__header-mobile' :!insert}">
+				<view v-if="left" class="uni-calendar__header-btn-box" @click.stop="pre">
+					<view class="uni-calendar__header-btn uni-calendar--left"></view>
+				</view>
+				<picker mode="date" :value="date" fields="month" @change="bindDateChange">
+					<text
+						class="uni-calendar__header-text">{{ (nowDate.year||'') + ' 年 ' + ( nowDate.month||'') +' 月'}}</text>
+				</picker>
+				<view v-if="right" class="uni-calendar__header-btn-box" @click.stop="next">
+					<view class="uni-calendar__header-btn uni-calendar--right"></view>
+				</view>
+				<view v-if="!insert" class="dialog-close" @click="clean">
+					<view class="dialog-close-plus" data-id="close"></view>
+					<view class="dialog-close-plus dialog-close-rotate" data-id="close"></view>
+				</view>
+
+				<!-- <text class="uni-calendar__backtoday" @click="backtoday">回到今天</text> -->
+			</view>
+			<view class="uni-calendar__box">
+				<view v-if="showMonth" class="uni-calendar__box-bg">
+					<text class="uni-calendar__box-bg-text">{{nowDate.month}}</text>
+				</view>
+				<view class="uni-calendar__weeks" style="padding-bottom: 7px;">
+					<view class="uni-calendar__weeks-day">
+						<text class="uni-calendar__weeks-day-text">{{SUNText}}</text>
+					</view>
+					<view class="uni-calendar__weeks-day">
+						<text class="uni-calendar__weeks-day-text">{{monText}}</text>
+					</view>
+					<view class="uni-calendar__weeks-day">
+						<text class="uni-calendar__weeks-day-text">{{TUEText}}</text>
+					</view>
+					<view class="uni-calendar__weeks-day">
+						<text class="uni-calendar__weeks-day-text">{{WEDText}}</text>
+					</view>
+					<view class="uni-calendar__weeks-day">
+						<text class="uni-calendar__weeks-day-text">{{THUText}}</text>
+					</view>
+					<view class="uni-calendar__weeks-day">
+						<text class="uni-calendar__weeks-day-text">{{FRIText}}</text>
+					</view>
+					<view class="uni-calendar__weeks-day">
+						<text class="uni-calendar__weeks-day-text">{{SATText}}</text>
+					</view>
+				</view>
+				<view class="uni-calendar__weeks" v-for="(item,weekIndex) in weeks" :key="weekIndex">
+					<view class="uni-calendar__weeks-item" v-for="(weeks,weeksIndex) in item" :key="weeksIndex">
+						<calendar-item class="uni-calendar-item--hook" :weeks="weeks" :calendar="calendar"
+							:selected="selected" :lunar="lunar" :checkHover="range" @change="choiceDate"
+							@handleMouse="handleMouse">
+						</calendar-item>
+					</view>
+				</view>
+			</view>
+			<view v-if="!insert && !range && typeHasTime" class="uni-date-changed uni-calendar--fixed-top"
+				style="padding: 0 80px;">
+				<view class="uni-date-changed--time-date">{{tempSingleDate ? tempSingleDate : selectDateText}}</view>
+				<time-picker type="time" :start="reactStartTime" :end="reactEndTime" v-model="time"
+					:disabled="!tempSingleDate" :border="false" :hide-second="hideSecond" class="time-picker-style">
+				</time-picker>
+			</view>
+
+			<view v-if="!insert && range && typeHasTime" class="uni-date-changed uni-calendar--fixed-top">
+				<view class="uni-date-changed--time-start">
+					<view class="uni-date-changed--time-date">{{tempRange.before ? tempRange.before : startDateText}}
+					</view>
+					<time-picker type="time" :start="reactStartTime" v-model="timeRange.startTime" :border="false"
+						:hide-second="hideSecond" :disabled="!tempRange.before" class="time-picker-style">
+					</time-picker>
+				</view>
+				<uni-icons type="arrowthinright" color="#999" style="line-height: 50px;"></uni-icons>
+				<view class="uni-date-changed--time-end">
+					<view class="uni-date-changed--time-date">{{tempRange.after ? tempRange.after : endDateText}}</view>
+					<time-picker type="time" :end="reactEndTime" v-model="timeRange.endTime" :border="false"
+						:hide-second="hideSecond" :disabled="!tempRange.after" class="time-picker-style">
+					</time-picker>
+				</view>
+			</view>
+			<view v-if="!insert" class="uni-date-changed uni-date-btn--ok">
+				<!-- <view class="uni-calendar__header-btn-box">
+					<text class="uni-calendar__button-text uni-calendar--fixed-width">{{okText}}</text>
+				</view> -->
+				<view class="uni-datetime-picker--btn" @click="confirm">确认</view>
+			</view>
+		</view>
+	</view>
+</template>
+
+<script>
+	import Calendar from './util.js';
+	import calendarItem from './calendar-item.vue'
+	import timePicker from './time-picker.vue'
+	import {
+		initVueI18n
+	} from '@dcloudio/uni-i18n'
+	import messages from './i18n/index.js'
+	const {
+		t
+	} = initVueI18n(messages)
+	/**
+	 * Calendar 日历
+	 * @description 日历组件可以查看日期,选择任意范围内的日期,打点操作。常用场景如:酒店日期预订、火车机票选择购买日期、上下班打卡等
+	 * @tutorial https://ext.dcloud.net.cn/plugin?id=56
+	 * @property {String} date 自定义当前时间,默认为今天
+	 * @property {Boolean} lunar 显示农历
+	 * @property {String} startDate 日期选择范围-开始日期
+	 * @property {String} endDate 日期选择范围-结束日期
+	 * @property {Boolean} range 范围选择
+	 * @property {Boolean} insert = [true|false] 插入模式,默认为false
+	 * 	@value true 弹窗模式
+	 * 	@value false 插入模式
+	 * @property {Boolean} clearDate = [true|false] 弹窗模式是否清空上次选择内容
+	 * @property {Array} selected 打点,期待格式[{date: '2019-06-27', info: '签到', data: { custom: '自定义信息', name: '自定义消息头',xxx:xxx... }}]
+	 * @property {Boolean} showMonth 是否选择月份为背景
+	 * @event {Function} change 日期改变,`insert :ture` 时生效
+	 * @event {Function} confirm 确认选择`insert :false` 时生效
+	 * @event {Function} monthSwitch 切换月份时触发
+	 * @example <uni-calendar :insert="true":lunar="true" :start-date="'2019-3-2'":end-date="'2019-5-20'"@change="change" />
+	 */
+	export default {
+		components: {
+			calendarItem,
+			timePicker
+		},
+		props: {
+			date: {
+				type: String,
+				default: ''
+			},
+			defTime: {
+				type: [String, Object],
+				default: ''
+			},
+			selectableTimes: {
+				type: [Object],
+				default () {
+					return {}
+				}
+			},
+			selected: {
+				type: Array,
+				default () {
+					return []
+				}
+			},
+			lunar: {
+				type: Boolean,
+				default: false
+			},
+			startDate: {
+				type: String,
+				default: ''
+			},
+			endDate: {
+				type: String,
+				default: ''
+			},
+			range: {
+				type: Boolean,
+				default: false
+			},
+			typeHasTime: {
+				type: Boolean,
+				default: false
+			},
+			insert: {
+				type: Boolean,
+				default: true
+			},
+			showMonth: {
+				type: Boolean,
+				default: true
+			},
+			clearDate: {
+				type: Boolean,
+				default: true
+			},
+			left: {
+				type: Boolean,
+				default: true
+			},
+			right: {
+				type: Boolean,
+				default: true
+			},
+			checkHover: {
+				type: Boolean,
+				default: true
+			},
+			hideSecond: {
+				type: [Boolean],
+				default: false
+			},
+			pleStatus: {
+				type: Object,
+				default () {
+					return {
+						before: '',
+						after: '',
+						data: [],
+						fulldate: ''
+					}
+				}
+			}
+		},
+		data() {
+			return {
+				show: false,
+				weeks: [],
+				calendar: {},
+				nowDate: '',
+				aniMaskShow: false,
+				firstEnter: true,
+				time: '',
+				timeRange: {
+					startTime: '',
+					endTime: ''
+				},
+				tempSingleDate: '',
+				tempRange: {
+					before: '',
+					after: ''
+				}
+			}
+		},
+		watch: {
+			date: {
+				immediate: true,
+				handler(newVal, oldVal) {
+					if (!this.range) {
+						this.tempSingleDate = newVal
+						setTimeout(() => {
+							this.init(newVal)
+						}, 100)
+					}
+				}
+			},
+			defTime: {
+				immediate: true,
+				handler(newVal, oldVal) {
+					if (!this.range) {
+						this.time = newVal
+					} else {
+						// console.log('-----', newVal);
+						this.timeRange.startTime = newVal.start
+						this.timeRange.endTime = newVal.end
+					}
+				}
+			},
+			startDate(val) {
+				this.cale.resetSatrtDate(val)
+				this.cale.setDate(this.nowDate.fullDate)
+				this.weeks = this.cale.weeks
+			},
+			endDate(val) {
+				this.cale.resetEndDate(val)
+				this.cale.setDate(this.nowDate.fullDate)
+				this.weeks = this.cale.weeks
+			},
+			selected(newVal) {
+				this.cale.setSelectInfo(this.nowDate.fullDate, newVal)
+				this.weeks = this.cale.weeks
+			},
+			pleStatus: {
+				immediate: true,
+				handler(newVal, oldVal) {
+					const {
+						before,
+						after,
+						fulldate,
+						which
+					} = newVal
+					this.tempRange.before = before
+					this.tempRange.after = after
+					setTimeout(() => {
+						if (fulldate) {
+							this.cale.setHoverMultiple(fulldate)
+							if (before && after) {
+								this.cale.lastHover = true
+								if (this.rangeWithinMonth(after, before)) return
+								this.setDate(before)
+							} else {
+								this.cale.setMultiple(fulldate)
+								this.setDate(this.nowDate.fullDate)
+								this.calendar.fullDate = ''
+								this.cale.lastHover = false
+							}
+						} else {
+							this.cale.setDefaultMultiple(before, after)
+							if (which === 'left') {
+								this.setDate(before)
+								this.weeks = this.cale.weeks
+							} else {
+								this.setDate(after)
+								this.weeks = this.cale.weeks
+							}
+							this.cale.lastHover = true
+						}
+					}, 16)
+				}
+			}
+		},
+		computed: {
+			reactStartTime() {
+				const activeDate = this.range ? this.tempRange.before : this.calendar.fullDate
+				const res = activeDate === this.startDate ? this.selectableTimes.start : ''
+				return res
+			},
+			reactEndTime() {
+				const activeDate = this.range ? this.tempRange.after : this.calendar.fullDate
+				const res = activeDate === this.endDate ? this.selectableTimes.end : ''
+				return res
+			},
+			/**
+			 * for i18n
+			 */
+			selectDateText() {
+				return t("uni-datetime-picker.selectDate")
+			},
+			startDateText() {
+				return this.startPlaceholder || t("uni-datetime-picker.startDate")
+			},
+			endDateText() {
+				return this.endPlaceholder || t("uni-datetime-picker.endDate")
+			},
+			okText() {
+				return t("uni-datetime-picker.ok")
+			},
+			monText() {
+				return t("uni-calender.MON")
+			},
+			TUEText() {
+				return t("uni-calender.TUE")
+			},
+			WEDText() {
+				return t("uni-calender.WED")
+			},
+			THUText() {
+				return t("uni-calender.THU")
+			},
+			FRIText() {
+				return t("uni-calender.FRI")
+			},
+			SATText() {
+				return t("uni-calender.SAT")
+			},
+			SUNText() {
+				return t("uni-calender.SUN")
+			},
+		},
+		created() {
+			// 获取日历方法实例
+			this.cale = new Calendar({
+				// date: new Date(),
+				selected: this.selected,
+				startDate: this.startDate,
+				endDate: this.endDate,
+				range: this.range,
+				// multipleStatus: this.pleStatus
+			})
+			// 选中某一天
+			// this.cale.setDate(this.date)
+			this.init(this.date)
+			// this.setDay
+		},
+		methods: {
+			leaveCale() {
+				this.firstEnter = true
+			},
+			handleMouse(weeks) {
+				if (weeks.disable) return
+				if (this.cale.lastHover) return
+				let {
+					before,
+					after
+				} = this.cale.multipleStatus
+				if (!before) return
+				this.calendar = weeks
+				// 设置范围选
+				this.cale.setHoverMultiple(this.calendar.fullDate)
+				this.weeks = this.cale.weeks
+				// hover时,进入一个日历,更新另一个
+				if (this.firstEnter) {
+					this.$emit('firstEnterCale', this.cale.multipleStatus)
+					this.firstEnter = false
+				}
+			},
+			rangeWithinMonth(A, B) {
+				const [yearA, monthA] = A.split('-')
+				const [yearB, monthB] = B.split('-')
+				return yearA === yearB && monthA === monthB
+			},
+
+			// 取消穿透
+			clean() {
+				this.close()
+			},
+
+			clearCalender() {
+				if (this.range) {
+					this.timeRange.startTime = ''
+					this.timeRange.endTime = ''
+					this.tempRange.before = ''
+					this.tempRange.after = ''
+					this.cale.multipleStatus.before = ''
+					this.cale.multipleStatus.after = ''
+					this.cale.multipleStatus.data = []
+					this.cale.lastHover = false
+				} else {
+					this.time = ''
+					this.tempSingleDate = ''
+				}
+				this.calendar.fullDate = ''
+				this.setDate()
+			},
+
+			bindDateChange(e) {
+				const value = e.detail.value + '-1'
+				this.init(value)
+			},
+			/**
+			 * 初始化日期显示
+			 * @param {Object} date
+			 */
+			init(date) {
+				this.cale.setDate(date)
+				this.weeks = this.cale.weeks
+				this.nowDate = this.calendar = this.cale.getInfo(date)
+			},
+			// choiceDate(weeks) {
+			// 	if (weeks.disable) return
+			// 	this.calendar = weeks
+			// 	// 设置多选
+			// 	this.cale.setMultiple(this.calendar.fullDate, true)
+			// 	this.weeks = this.cale.weeks
+			// 	this.tempSingleDate = this.calendar.fullDate
+			// 	this.tempRange.before = this.cale.multipleStatus.before
+			// 	this.tempRange.after = this.cale.multipleStatus.after
+			// 	this.change()
+			// },
+			/**
+			 * 打开日历弹窗
+			 */
+			open() {
+				// 弹窗模式并且清理数据
+				if (this.clearDate && !this.insert) {
+					this.cale.cleanMultipleStatus()
+					// this.cale.setDate(this.date)
+					this.init(this.date)
+				}
+				this.show = true
+				this.$nextTick(() => {
+					setTimeout(() => {
+						this.aniMaskShow = true
+					}, 50)
+				})
+			},
+			/**
+			 * 关闭日历弹窗
+			 */
+			close() {
+				this.aniMaskShow = false
+				this.$nextTick(() => {
+					setTimeout(() => {
+						this.show = false
+						this.$emit('close')
+					}, 300)
+				})
+			},
+			/**
+			 * 确认按钮
+			 */
+			confirm() {
+				this.setEmit('confirm')
+				this.close()
+			},
+			/**
+			 * 变化触发
+			 */
+			change() {
+				if (!this.insert) return
+				this.setEmit('change')
+			},
+			/**
+			 * 选择月份触发
+			 */
+			monthSwitch() {
+				let {
+					year,
+					month
+				} = this.nowDate
+				this.$emit('monthSwitch', {
+					year,
+					month: Number(month)
+				})
+			},
+			/**
+			 * 派发事件
+			 * @param {Object} name
+			 */
+			setEmit(name) {
+				let {
+					year,
+					month,
+					date,
+					fullDate,
+					lunar,
+					extraInfo
+				} = this.calendar
+				this.$emit(name, {
+					range: this.cale.multipleStatus,
+					year,
+					month,
+					date,
+					time: this.time,
+					timeRange: this.timeRange,
+					fulldate: fullDate,
+					lunar,
+					extraInfo: extraInfo || {}
+				})
+			},
+			/**
+			 * 选择天触发
+			 * @param {Object} weeks
+			 */
+			choiceDate(weeks) {
+				if (weeks.disable) return
+				this.calendar = weeks
+				this.calendar.userChecked = true
+				// 设置多选
+				this.cale.setMultiple(this.calendar.fullDate, true)
+				this.weeks = this.cale.weeks
+				this.tempSingleDate = this.calendar.fullDate
+				this.tempRange.before = this.cale.multipleStatus.before
+				this.tempRange.after = this.cale.multipleStatus.after
+				this.change()
+			},
+			/**
+			 * 回到今天
+			 */
+			backtoday() {
+				let date = this.cale.getDate(new Date()).fullDate
+				// this.cale.setDate(date)
+				this.init(date)
+				this.change()
+			},
+			/**
+			 * 比较时间大小
+			 */
+			dateCompare(startDate, endDate) {
+				// 计算截止时间
+				startDate = new Date(startDate.replace('-', '/').replace('-', '/'))
+				// 计算详细项的截止时间
+				endDate = new Date(endDate.replace('-', '/').replace('-', '/'))
+				if (startDate <= endDate) {
+					return true
+				} else {
+					return false
+				}
+			},
+			/**
+			 * 上个月
+			 */
+			pre() {
+				const preDate = this.cale.getDate(this.nowDate.fullDate, -1, 'month').fullDate
+				this.setDate(preDate)
+				this.monthSwitch()
+
+			},
+			/**
+			 * 下个月
+			 */
+			next() {
+				const nextDate = this.cale.getDate(this.nowDate.fullDate, +1, 'month').fullDate
+				this.setDate(nextDate)
+				this.monthSwitch()
+			},
+			/**
+			 * 设置日期
+			 * @param {Object} date
+			 */
+			setDate(date) {
+				this.cale.setDate(date)
+				this.weeks = this.cale.weeks
+				this.nowDate = this.cale.getInfo(date)
+			}
+		}
+	}
+</script>
+
+<style lang="scss" >
+	.uni-calendar {
+		/* #ifndef APP-NVUE */
+		display: flex;
+		/* #endif */
+		flex-direction: column;
+	}
+
+	.uni-calendar__mask {
+		position: fixed;
+		bottom: 0;
+		top: 0;
+		left: 0;
+		right: 0;
+		background-color: rgba(0, 0, 0, 0.4);
+		transition-property: opacity;
+		transition-duration: 0.3s;
+		opacity: 0;
+		/* #ifndef APP-NVUE */
+		z-index: 99;
+		/* #endif */
+	}
+
+	.uni-calendar--mask-show {
+		opacity: 1
+	}
+
+	.uni-calendar--fixed {
+		position: fixed;
+		bottom: calc(var(--window-bottom));
+		left: 0;
+		right: 0;
+		transition-property: transform;
+		transition-duration: 0.3s;
+		transform: translateY(460px);
+		/* #ifndef APP-NVUE */
+		z-index: 99;
+		/* #endif */
+	}
+
+	.uni-calendar--ani-show {
+		transform: translateY(0);
+	}
+
+	.uni-calendar__content {
+		background-color: #fff;
+	}
+
+	.uni-calendar__content-mobile {
+		border-top-left-radius: 10px;
+		border-top-right-radius: 10px;
+		box-shadow: 0px 0px 5px 3px rgba(0, 0, 0, 0.1);
+	}
+
+	.uni-calendar__header {
+		position: relative;
+		/* #ifndef APP-NVUE */
+		display: flex;
+		/* #endif */
+		flex-direction: row;
+		justify-content: center;
+		align-items: center;
+		height: 50px;
+	}
+
+	.uni-calendar__header-mobile {
+		padding: 10px;
+		padding-bottom: 0;
+	}
+
+	.uni-calendar--fixed-top {
+		/* #ifndef APP-NVUE */
+		display: flex;
+		/* #endif */
+		flex-direction: row;
+		justify-content: space-between;
+		border-top-color: rgba(0, 0, 0, 0.4);
+		border-top-style: solid;
+		border-top-width: 1px;
+	}
+
+	.uni-calendar--fixed-width {
+		width: 50px;
+	}
+
+	.uni-calendar__backtoday {
+		position: absolute;
+		right: 0;
+		top: 25rpx;
+		padding: 0 5px;
+		padding-left: 10px;
+		height: 25px;
+		line-height: 25px;
+		font-size: 12px;
+		border-top-left-radius: 25px;
+		border-bottom-left-radius: 25px;
+		color: #fff;
+		background-color: #f1f1f1;
+	}
+
+	.uni-calendar__header-text {
+		text-align: center;
+		width: 100px;
+		font-size: 15px;
+		color: #666;
+	}
+
+	.uni-calendar__button-text {
+		text-align: center;
+		width: 100px;
+		font-size: 14px;
+		color: #007aff;
+		/* #ifndef APP-NVUE */
+		letter-spacing: 3px;
+		/* #endif */
+	}
+
+	.uni-calendar__header-btn-box {
+		/* #ifndef APP-NVUE */
+		display: flex;
+		/* #endif */
+		flex-direction: row;
+		align-items: center;
+		justify-content: center;
+		width: 50px;
+		height: 50px;
+	}
+
+	.uni-calendar__header-btn {
+		width: 9px;
+		height: 9px;
+		border-left-color: #808080;
+		border-left-style: solid;
+		border-left-width: 1px;
+		border-top-color: #555555;
+		border-top-style: solid;
+		border-top-width: 1px;
+	}
+
+	.uni-calendar--left {
+		transform: rotate(-45deg);
+	}
+
+	.uni-calendar--right {
+		transform: rotate(135deg);
+	}
+
+
+	.uni-calendar__weeks {
+		position: relative;
+		/* #ifndef APP-NVUE */
+		display: flex;
+		/* #endif */
+		flex-direction: row;
+	}
+
+	.uni-calendar__weeks-item {
+		flex: 1;
+	}
+
+	.uni-calendar__weeks-day {
+		flex: 1;
+		/* #ifndef APP-NVUE */
+		display: flex;
+		/* #endif */
+		flex-direction: column;
+		justify-content: center;
+		align-items: center;
+		height: 40px;
+		border-bottom-color: #F5F5F5;
+		border-bottom-style: solid;
+		border-bottom-width: 1px;
+	}
+
+	.uni-calendar__weeks-day-text {
+		font-size: 12px;
+		color: #B2B2B2;
+	}
+
+	.uni-calendar__box {
+		position: relative;
+		// padding: 0 10px;
+		padding-bottom: 7px;
+	}
+
+	.uni-calendar__box-bg {
+		/* #ifndef APP-NVUE */
+		display: flex;
+		/* #endif */
+		justify-content: center;
+		align-items: center;
+		position: absolute;
+		top: 0;
+		left: 0;
+		right: 0;
+		bottom: 0;
+	}
+
+	.uni-calendar__box-bg-text {
+		font-size: 200px;
+		font-weight: bold;
+		color: #999;
+		opacity: 0.1;
+		text-align: center;
+		/* #ifndef APP-NVUE */
+		line-height: 1;
+		/* #endif */
+	}
+
+	.uni-date-changed {
+		padding: 0 10px;
+		// line-height: 50px;
+		text-align: center;
+		color: #333;
+		border-top-color: #DCDCDC;
+		;
+		border-top-style: solid;
+		border-top-width: 1px;
+		flex: 1;
+	}
+
+	.uni-date-btn--ok {
+		padding: 20px 15px;
+	}
+
+	.uni-date-changed--time-start {
+		/* #ifndef APP-NVUE */
+		display: flex;
+		/* #endif */
+		align-items: center;
+	}
+
+	.uni-date-changed--time-end {
+		/* #ifndef APP-NVUE */
+		display: flex;
+		/* #endif */
+		align-items: center;
+	}
+
+	.uni-date-changed--time-date {
+		color: #999;
+		line-height: 50px;
+		margin-right: 5px;
+		// opacity: 0.6;
+	}
+
+	.time-picker-style {
+		// width: 62px;
+		/* #ifndef APP-NVUE */
+		display: flex;
+		/* #endif */
+		justify-content: center;
+		align-items: center
+	}
+
+	.mr-10 {
+		margin-right: 10px;
+	}
+
+	.dialog-close {
+		position: absolute;
+		top: 0;
+		right: 0;
+		bottom: 0;
+		/* #ifndef APP-NVUE */
+		display: flex;
+		/* #endif */
+		flex-direction: row;
+		align-items: center;
+		padding: 0 25px;
+		margin-top: 10px;
+	}
+
+	.dialog-close-plus {
+		width: 16px;
+		height: 2px;
+		background-color: #737987;
+		border-radius: 2px;
+		transform: rotate(45deg);
+	}
+
+	.dialog-close-rotate {
+		position: absolute;
+		transform: rotate(-45deg);
+	}
+
+	.uni-datetime-picker--btn {
+		border-radius: 100px;
+		height: 40px;
+		line-height: 40px;
+		background-color: #007aff;
+		color: #fff;
+		font-size: 16px;
+		letter-spacing: 5px;
+	}
+
+	/* #ifndef APP-NVUE */
+	.uni-datetime-picker--btn:active {
+		opacity: 0.7;
+	}
+	/* #endif */
+</style>

+ 19 - 0
uni_modules/uni-datetime-picker/components/uni-datetime-picker/i18n/en.json

xqd
@@ -0,0 +1,19 @@
+{
+	"uni-datetime-picker.selectDate": "select date",
+	"uni-datetime-picker.selectTime": "select time",
+	"uni-datetime-picker.selectDateTime": "select datetime",
+	"uni-datetime-picker.startDate": "start date",
+	"uni-datetime-picker.endDate": "end date",
+	"uni-datetime-picker.startTime": "start time",
+	"uni-datetime-picker.endTime": "end time",
+	"uni-datetime-picker.ok": "ok",
+	"uni-datetime-picker.clear": "clear",
+	"uni-datetime-picker.cancel": "cancel",
+	"uni-calender.MON": "MON",
+	"uni-calender.TUE": "TUE",
+	"uni-calender.WED": "WED",
+	"uni-calender.THU": "THU",
+	"uni-calender.FRI": "FRI",
+	"uni-calender.SAT": "SAT",
+	"uni-calender.SUN": "SUN"
+}

+ 8 - 0
uni_modules/uni-datetime-picker/components/uni-datetime-picker/i18n/index.js

xqd
@@ -0,0 +1,8 @@
+import en from './en.json'
+import zhHans from './zh-Hans.json'
+import zhHant from './zh-Hant.json'
+export default {
+	en,
+	'zh-Hans': zhHans,
+	'zh-Hant': zhHant
+}

+ 19 - 0
uni_modules/uni-datetime-picker/components/uni-datetime-picker/i18n/zh-Hans.json

xqd
@@ -0,0 +1,19 @@
+{
+	"uni-datetime-picker.selectDate": "选择日期",
+	"uni-datetime-picker.selectTime": "选择时间",
+	"uni-datetime-picker.selectDateTime": "选择日期时间",
+	"uni-datetime-picker.startDate": "开始日期",
+	"uni-datetime-picker.endDate": "结束日期",
+	"uni-datetime-picker.startTime": "开始时间",
+	"uni-datetime-picker.endTime": "结束时间",
+	"uni-datetime-picker.ok": "确定",
+	"uni-datetime-picker.clear": "清除",
+	"uni-datetime-picker.cancel": "取消",
+	"uni-calender.SUN": "日",
+	"uni-calender.MON": "一",
+	"uni-calender.TUE": "二",
+	"uni-calender.WED": "三",
+	"uni-calender.THU": "四",
+	"uni-calender.FRI": "五",
+	"uni-calender.SAT": "六"
+}

+ 19 - 0
uni_modules/uni-datetime-picker/components/uni-datetime-picker/i18n/zh-Hant.json

xqd
@@ -0,0 +1,19 @@
+{
+	"uni-datetime-picker.selectDate": "選擇日期",
+	"uni-datetime-picker.selectTime": "選擇時間",
+	"uni-datetime-picker.selectDateTime": "選擇日期時間",
+	"uni-datetime-picker.startDate": "開始日期",
+	"uni-datetime-picker.endDate": "結束日期",
+	"uni-datetime-picker.startTime": "開始时间",
+	"uni-datetime-picker.endTime": "結束时间",
+	"uni-datetime-picker.ok": "確定",
+	"uni-datetime-picker.clear": "清除",
+	"uni-datetime-picker.cancel": "取消",
+	"uni-calender.SUN": "日",
+	"uni-calender.MON": "一",
+	"uni-calender.TUE": "二",
+	"uni-calender.WED": "三",
+	"uni-calender.THU": "四",
+	"uni-calender.FRI": "五",
+	"uni-calender.SAT": "六"
+}

+ 45 - 0
uni_modules/uni-datetime-picker/components/uni-datetime-picker/keypress.js

xqd
@@ -0,0 +1,45 @@
+// #ifdef H5
+export default {
+  name: 'Keypress',
+  props: {
+    disable: {
+      type: Boolean,
+      default: false
+    }
+  },
+  mounted () {
+    const keyNames = {
+      esc: ['Esc', 'Escape'],
+      tab: 'Tab',
+      enter: 'Enter',
+      space: [' ', 'Spacebar'],
+      up: ['Up', 'ArrowUp'],
+      left: ['Left', 'ArrowLeft'],
+      right: ['Right', 'ArrowRight'],
+      down: ['Down', 'ArrowDown'],
+      delete: ['Backspace', 'Delete', 'Del']
+    }
+    const listener = ($event) => {
+      if (this.disable) {
+        return
+      }
+      const keyName = Object.keys(keyNames).find(key => {
+        const keyName = $event.key
+        const value = keyNames[key]
+        return value === keyName || (Array.isArray(value) && value.includes(keyName))
+      })
+      if (keyName) {
+        // 避免和其他按键事件冲突
+        setTimeout(() => {
+          this.$emit(keyName, {})
+        }, 0)
+      }
+    }
+    document.addEventListener('keyup', listener)
+    this.$once('hook:beforeDestroy', () => {
+      document.removeEventListener('keyup', listener)
+    })
+  },
+	render: () => {}
+}
+// #endif

+ 927 - 0
uni_modules/uni-datetime-picker/components/uni-datetime-picker/time-picker.vue

xqd
@@ -0,0 +1,927 @@
+<template>
+	<view class="uni-datetime-picker">
+		<view @click="initTimePicker">
+			<slot>
+				<view class="uni-datetime-picker-timebox-pointer"
+					:class="{'uni-datetime-picker-disabled': disabled, 'uni-datetime-picker-timebox': border}">
+					<text class="uni-datetime-picker-text">{{time}}</text>
+					<view v-if="!time" class="uni-datetime-picker-time">
+						<text class="uni-datetime-picker-text">{{selectTimeText}}</text>
+					</view>
+				</view>
+			</slot>
+		</view>
+		<view v-if="visible" id="mask" class="uni-datetime-picker-mask" @click="tiggerTimePicker"></view>
+		<view v-if="visible" class="uni-datetime-picker-popup" :class="[dateShow && timeShow ? '' : 'fix-nvue-height']"
+			:style="fixNvueBug">
+			<view class="uni-title">
+				<text class="uni-datetime-picker-text">{{selectTimeText}}</text>
+			</view>
+			<view v-if="dateShow" class="uni-datetime-picker__container-box">
+				<picker-view class="uni-datetime-picker-view" :indicator-style="indicatorStyle" :value="ymd"
+					@change="bindDateChange">
+					<picker-view-column>
+						<view class="uni-datetime-picker-item" v-for="(item,index) in years" :key="index">
+							<text class="uni-datetime-picker-item">{{lessThanTen(item)}}</text>
+						</view>
+					</picker-view-column>
+					<picker-view-column>
+						<view class="uni-datetime-picker-item" v-for="(item,index) in months" :key="index">
+							<text class="uni-datetime-picker-item">{{lessThanTen(item)}}</text>
+						</view>
+					</picker-view-column>
+					<picker-view-column>
+						<view class="uni-datetime-picker-item" v-for="(item,index) in days" :key="index">
+							<text class="uni-datetime-picker-item">{{lessThanTen(item)}}</text>
+						</view>
+					</picker-view-column>
+				</picker-view>
+				<!-- 兼容 nvue 不支持伪类 -->
+				<text class="uni-datetime-picker-sign sign-left">-</text>
+				<text class="uni-datetime-picker-sign sign-right">-</text>
+			</view>
+			<view v-if="timeShow" class="uni-datetime-picker__container-box">
+				<picker-view class="uni-datetime-picker-view" :class="[hideSecond ? 'time-hide-second' : '']"
+					:indicator-style="indicatorStyle" :value="hms" @change="bindTimeChange">
+					<picker-view-column>
+						<view class="uni-datetime-picker-item" v-for="(item,index) in hours" :key="index">
+							<text class="uni-datetime-picker-item">{{lessThanTen(item)}}</text>
+						</view>
+					</picker-view-column>
+					<picker-view-column>
+						<view class="uni-datetime-picker-item" v-for="(item,index) in minutes" :key="index">
+							<text class="uni-datetime-picker-item">{{lessThanTen(item)}}</text>
+						</view>
+					</picker-view-column>
+					<picker-view-column v-if="!hideSecond">
+						<view class="uni-datetime-picker-item" v-for="(item,index) in seconds" :key="index">
+							<text class="uni-datetime-picker-item">{{lessThanTen(item)}}</text>
+						</view>
+					</picker-view-column>
+				</picker-view>
+				<!-- 兼容 nvue 不支持伪类 -->
+				<text class="uni-datetime-picker-sign" :class="[hideSecond ? 'sign-center' : 'sign-left']">:</text>
+				<text v-if="!hideSecond" class="uni-datetime-picker-sign sign-right">:</text>
+			</view>
+			<view class="uni-datetime-picker-btn">
+				<view @click="clearTime">
+					<text class="uni-datetime-picker-btn-text">{{clearText}}</text>
+				</view>
+				<view class="uni-datetime-picker-btn-group">
+					<view class="uni-datetime-picker-cancel" @click="tiggerTimePicker">
+						<text class="uni-datetime-picker-btn-text">{{cancelText}}</text>
+					</view>
+					<view @click="setTime">
+						<text class="uni-datetime-picker-btn-text">{{okText}}</text>
+					</view>
+				</view>
+			</view>
+		</view>
+		<!-- #ifdef H5 -->
+		<!-- <keypress v-if="visible" @esc="tiggerTimePicker" @enter="setTime" /> -->
+		<!-- #endif -->
+	</view>
+</template>
+
+<script>
+	// #ifdef H5
+	import keypress from './keypress'
+	// #endif
+	import {
+		initVueI18n
+	} from '@dcloudio/uni-i18n'
+	import messages from './i18n/index.js'
+	const {	t	} = initVueI18n(messages)
+
+	/**
+	 * DatetimePicker 时间选择器
+	 * @description 可以同时选择日期和时间的选择器
+	 * @tutorial https://ext.dcloud.net.cn/plugin?id=xxx
+	 * @property {String} type = [datetime | date | time] 显示模式
+	 * @property {Boolean} multiple = [true|false] 是否多选
+	 * @property {String|Number} value 默认值
+	 * @property {String|Number} start 起始日期或时间
+	 * @property {String|Number} end 起始日期或时间
+	 * @property {String} return-type = [timestamp | string]
+	 * @event {Function} change  选中发生变化触发
+	 */
+
+	export default {
+		name: 'UniDatetimePicker',
+		components: {
+			// #ifdef H5
+			keypress
+			// #endif
+		},
+		data() {
+			return {
+				indicatorStyle: `height: 50px;`,
+				visible: false,
+				fixNvueBug: {},
+				dateShow: true,
+				timeShow: true,
+				title: '日期和时间',
+				// 输入框当前时间
+				time: '',
+				// 当前的年月日时分秒
+				year: 1920,
+				month: 0,
+				day: 0,
+				hour: 0,
+				minute: 0,
+				second: 0,
+				// 起始时间
+				startYear: 1920,
+				startMonth: 1,
+				startDay: 1,
+				startHour: 0,
+				startMinute: 0,
+				startSecond: 0,
+				// 结束时间
+				endYear: 2120,
+				endMonth: 12,
+				endDay: 31,
+				endHour: 23,
+				endMinute: 59,
+				endSecond: 59,
+			}
+		},
+		props: {
+			type: {
+				type: String,
+				default: 'datetime'
+			},
+			value: {
+				type: [String, Number],
+				default: ''
+			},
+			modelValue: {
+				type: [String, Number],
+				default: ''
+			},
+			start: {
+				type: [Number, String],
+				default: ''
+			},
+			end: {
+				type: [Number, String],
+				default: ''
+			},
+			returnType: {
+				type: String,
+				default: 'string'
+			},
+			disabled: {
+				type: [Boolean, String],
+				default: false
+			},
+			border: {
+				type: [Boolean, String],
+				default: true
+			},
+			hideSecond: {
+				type: [Boolean, String],
+				default: false
+			}
+		},
+		watch: {
+			value: {
+				handler(newVal, oldVal) {
+					if (newVal) {
+						this.parseValue(this.fixIosDateFormat(newVal)) //兼容 iOS、safari 日期格式
+						this.initTime(false)
+					} else {
+						this.time = ''
+						this.parseValue(Date.now())
+					}
+				},
+				immediate: true
+			},
+			type: {
+				handler(newValue) {
+					if (newValue === 'date') {
+						this.dateShow = true
+						this.timeShow = false
+						this.title = '日期'
+					} else if (newValue === 'time') {
+						this.dateShow = false
+						this.timeShow = true
+						this.title = '时间'
+					} else {
+						this.dateShow = true
+						this.timeShow = true
+						this.title = '日期和时间'
+					}
+				},
+				immediate: true
+			},
+			start: {
+				handler(newVal) {
+					this.parseDatetimeRange(this.fixIosDateFormat(newVal), 'start') //兼容 iOS、safari 日期格式
+				},
+				immediate: true
+			},
+			end: {
+				handler(newVal) {
+					this.parseDatetimeRange(this.fixIosDateFormat(newVal), 'end') //兼容 iOS、safari 日期格式
+				},
+				immediate: true
+			},
+
+			// 月、日、时、分、秒可选范围变化后,检查当前值是否在范围内,不在则当前值重置为可选范围第一项
+			months(newVal) {
+				this.checkValue('month', this.month, newVal)
+			},
+			days(newVal) {
+				this.checkValue('day', this.day, newVal)
+			},
+			hours(newVal) {
+				this.checkValue('hour', this.hour, newVal)
+			},
+			minutes(newVal) {
+				this.checkValue('minute', this.minute, newVal)
+			},
+			seconds(newVal) {
+				this.checkValue('second', this.second, newVal)
+			}
+		},
+		computed: {
+			// 当前年、月、日、时、分、秒选择范围
+			years() {
+				return this.getCurrentRange('year')
+			},
+
+			months() {
+				return this.getCurrentRange('month')
+			},
+
+			days() {
+				return this.getCurrentRange('day')
+			},
+
+			hours() {
+				return this.getCurrentRange('hour')
+			},
+
+			minutes() {
+				return this.getCurrentRange('minute')
+			},
+
+			seconds() {
+				return this.getCurrentRange('second')
+			},
+
+			// picker 当前值数组
+			ymd() {
+				return [this.year - this.minYear, this.month - this.minMonth, this.day - this.minDay]
+			},
+			hms() {
+				return [this.hour - this.minHour, this.minute - this.minMinute, this.second - this.minSecond]
+			},
+
+			// 当前 date 是 start
+			currentDateIsStart() {
+				return this.year === this.startYear && this.month === this.startMonth && this.day === this.startDay
+			},
+
+			// 当前 date 是 end
+			currentDateIsEnd() {
+				return this.year === this.endYear && this.month === this.endMonth && this.day === this.endDay
+			},
+
+			// 当前年、月、日、时、分、秒的最小值和最大值
+			minYear() {
+				return this.startYear
+			},
+			maxYear() {
+				return this.endYear
+			},
+			minMonth() {
+				if (this.year === this.startYear) {
+					return this.startMonth
+				} else {
+					return 1
+				}
+			},
+			maxMonth() {
+				if (this.year === this.endYear) {
+					return this.endMonth
+				} else {
+					return 12
+				}
+			},
+			minDay() {
+				if (this.year === this.startYear && this.month === this.startMonth) {
+					return this.startDay
+				} else {
+					return 1
+				}
+			},
+			maxDay() {
+				if (this.year === this.endYear && this.month === this.endMonth) {
+					return this.endDay
+				} else {
+					return this.daysInMonth(this.year, this.month)
+				}
+			},
+			minHour() {
+				if (this.type === 'datetime') {
+					if (this.currentDateIsStart) {
+						return this.startHour
+					} else {
+						return 0
+					}
+				}
+				if (this.type === 'time') {
+					return this.startHour
+				}
+			},
+			maxHour() {
+				if (this.type === 'datetime') {
+					if (this.currentDateIsEnd) {
+						return this.endHour
+					} else {
+						return 23
+					}
+				}
+				if (this.type === 'time') {
+					return this.endHour
+				}
+			},
+			minMinute() {
+				if (this.type === 'datetime') {
+					if (this.currentDateIsStart && this.hour === this.startHour) {
+						return this.startMinute
+					} else {
+						return 0
+					}
+				}
+				if (this.type === 'time') {
+					if (this.hour === this.startHour) {
+						return this.startMinute
+					} else {
+						return 0
+					}
+				}
+			},
+			maxMinute() {
+				if (this.type === 'datetime') {
+					if (this.currentDateIsEnd && this.hour === this.endHour) {
+						return this.endMinute
+					} else {
+						return 59
+					}
+				}
+				if (this.type === 'time') {
+					if (this.hour === this.endHour) {
+						return this.endMinute
+					} else {
+						return 59
+					}
+				}
+			},
+			minSecond() {
+				if (this.type === 'datetime') {
+					if (this.currentDateIsStart && this.hour === this.startHour && this.minute === this.startMinute) {
+						return this.startSecond
+					} else {
+						return 0
+					}
+				}
+				if (this.type === 'time') {
+					if (this.hour === this.startHour && this.minute === this.startMinute) {
+						return this.startSecond
+					} else {
+						return 0
+					}
+				}
+			},
+			maxSecond() {
+				if (this.type === 'datetime') {
+					if (this.currentDateIsEnd && this.hour === this.endHour && this.minute === this.endMinute) {
+						return this.endSecond
+					} else {
+						return 59
+					}
+				}
+				if (this.type === 'time') {
+					if (this.hour === this.endHour && this.minute === this.endMinute) {
+						return this.endSecond
+					} else {
+						return 59
+					}
+				}
+			},
+
+			/**
+			 * for i18n
+			 */
+			selectTimeText() {
+				return t("uni-datetime-picker.selectTime")
+			},
+			okText() {
+				return t("uni-datetime-picker.ok")
+			},
+			clearText() {
+				return t("uni-datetime-picker.clear")
+			},
+			cancelText() {
+				return t("uni-datetime-picker.cancel")
+			}
+		},
+
+		mounted() {
+			// #ifdef APP-NVUE
+			const res = uni.getSystemInfoSync();
+			this.fixNvueBug = {
+				top: res.windowHeight / 2,
+				left: res.windowWidth / 2
+			}
+			// #endif
+		},
+
+		methods: {
+			/**
+			 * @param {Object} item
+			 * 小于 10 在前面加个 0
+			 */
+
+			lessThanTen(item) {
+				return item < 10 ? '0' + item : item
+			},
+
+			/**
+			 * 解析时分秒字符串,例如:00:00:00
+			 * @param {String} timeString
+			 */
+			parseTimeType(timeString) {
+				if (timeString) {
+					let timeArr = timeString.split(':')
+					this.hour = Number(timeArr[0])
+					this.minute = Number(timeArr[1])
+					this.second = Number(timeArr[2])
+				}
+			},
+
+			/**
+			 * 解析选择器初始值,类型可以是字符串、时间戳,例如:2000-10-02、'08:30:00'、 1610695109000
+			 * @param {String | Number} datetime
+			 */
+			initPickerValue(datetime) {
+				let defaultValue = null
+				if (datetime) {
+					defaultValue = this.compareValueWithStartAndEnd(datetime, this.start, this.end)
+				} else {
+					defaultValue = Date.now()
+					defaultValue = this.compareValueWithStartAndEnd(defaultValue, this.start, this.end)
+				}
+				this.parseValue(defaultValue)
+			},
+
+			/**
+			 * 初始值规则:
+			 * - 用户设置初始值 value
+			 * 	- 设置了起始时间 start、终止时间 end,并 start < value < end,初始值为 value, 否则初始值为 start
+			 * 	- 只设置了起始时间 start,并 start < value,初始值为 value,否则初始值为 start
+			 * 	- 只设置了终止时间 end,并 value < end,初始值为 value,否则初始值为 end
+			 * 	- 无起始终止时间,则初始值为 value
+			 * - 无初始值 value,则初始值为当前本地时间 Date.now()
+			 * @param {Object} value
+			 * @param {Object} dateBase
+			 */
+			compareValueWithStartAndEnd(value, start, end) {
+				let winner = null
+				value = this.superTimeStamp(value)
+				start = this.superTimeStamp(start)
+				end = this.superTimeStamp(end)
+
+				if (start && end) {
+					if (value < start) {
+						winner = new Date(start)
+					} else if (value > end) {
+						winner = new Date(end)
+					} else {
+						winner = new Date(value)
+					}
+				} else if (start && !end) {
+					winner = start <= value ? new Date(value) : new Date(start)
+				} else if (!start && end) {
+					winner = value <= end ? new Date(value) : new Date(end)
+				} else {
+					winner = new Date(value)
+				}
+
+				return winner
+			},
+
+			/**
+			 * 转换为可比较的时间戳,接受日期、时分秒、时间戳
+			 * @param {Object} value
+			 */
+			superTimeStamp(value) {
+				let dateBase = ''
+				if (this.type === 'time' && value && typeof value === 'string') {
+					const now = new Date()
+					const year = now.getFullYear()
+					const month = now.getMonth() + 1
+					const day = now.getDate()
+					dateBase = year + '/' + month + '/' + day + ' '
+				}
+				if (Number(value) && typeof value !== NaN) {
+					value = parseInt(value)
+					dateBase = 0
+				}
+				return this.createTimeStamp(dateBase + value)
+			},
+
+			/**
+			 * 解析默认值 value,字符串、时间戳
+			 * @param {Object} defaultTime
+			 */
+			parseValue(value) {
+				if (!value) {
+					return
+				}
+				if (this.type === 'time' && typeof value === "string") {
+					this.parseTimeType(value)
+				} else {
+					let defaultDate = null
+					defaultDate = new Date(value)
+					if (this.type !== 'time') {
+						this.year = defaultDate.getFullYear()
+						this.month = defaultDate.getMonth() + 1
+						this.day = defaultDate.getDate()
+					}
+					if (this.type !== 'date') {
+						this.hour = defaultDate.getHours()
+						this.minute = defaultDate.getMinutes()
+						this.second = defaultDate.getSeconds()
+					}
+				}
+				if (this.hideSecond) {
+					this.second = 0
+				}
+			},
+
+			/**
+			 * 解析可选择时间范围 start、end,年月日字符串、时间戳
+			 * @param {Object} defaultTime
+			 */
+			parseDatetimeRange(point, pointType) {
+				// 时间为空,则重置为初始值
+				if (!point) {
+					if (pointType === 'start') {
+						this.startYear = 1920
+						this.startMonth = 1
+						this.startDay = 1
+						this.startHour = 0
+						this.startMinute = 0
+						this.startSecond = 0
+					}
+					if (pointType === 'end') {
+						this.endYear = 2120
+						this.endMonth = 12
+						this.endDay = 31
+						this.endHour = 23
+						this.endMinute = 59
+						this.endSecond = 59
+					}
+					return
+				}
+				if (this.type === 'time') {
+					const pointArr = point.split(':')
+					this[pointType + 'Hour'] = Number(pointArr[0])
+					this[pointType + 'Minute'] = Number(pointArr[1])
+					this[pointType + 'Second'] = Number(pointArr[2])
+				} else {
+					if (!point) {
+						pointType === 'start' ? this.startYear = this.year - 60 : this.endYear = this.year + 60
+						return
+					}
+					if (Number(point) && Number(point) !== NaN) {
+						point = parseInt(point)
+					}
+					// datetime 的 end 没有时分秒, 则不限制
+					const hasTime = /[0-9]:[0-9]/
+					if (this.type === 'datetime' && pointType === 'end' && typeof point === 'string' && !hasTime.test(
+							point)) {
+						point = point + ' 23:59:59'
+					}
+					const pointDate = new Date(point)
+					this[pointType + 'Year'] = pointDate.getFullYear()
+					this[pointType + 'Month'] = pointDate.getMonth() + 1
+					this[pointType + 'Day'] = pointDate.getDate()
+					if (this.type === 'datetime') {
+						this[pointType + 'Hour'] = pointDate.getHours()
+						this[pointType + 'Minute'] = pointDate.getMinutes()
+						this[pointType + 'Second'] = pointDate.getSeconds()
+					}
+				}
+			},
+
+			// 获取 年、月、日、时、分、秒 当前可选范围
+			getCurrentRange(value) {
+				const range = []
+				for (let i = this['min' + this.capitalize(value)]; i <= this['max' + this.capitalize(value)]; i++) {
+					range.push(i)
+				}
+				return range
+			},
+
+			// 字符串首字母大写
+			capitalize(str) {
+				return str.charAt(0).toUpperCase() + str.slice(1)
+			},
+
+			// 检查当前值是否在范围内,不在则当前值重置为可选范围第一项
+			checkValue(name, value, values) {
+				if (values.indexOf(value) === -1) {
+					this[name] = values[0]
+				}
+			},
+
+			// 每个月的实际天数
+			daysInMonth(year, month) { // Use 1 for January, 2 for February, etc.
+				return new Date(year, month, 0).getDate();
+			},
+
+			//兼容 iOS、safari 日期格式
+			fixIosDateFormat(value) {
+				if (typeof value === 'string') {
+					value = value.replace(/-/g, '/')
+				}
+				return value
+			},
+
+			/**
+			 * 生成时间戳
+			 * @param {Object} time
+			 */
+			createTimeStamp(time) {
+				if (!time) return
+				if (typeof time === "number") {
+					return time
+				} else {
+					time = time.replace(/-/g, '/')
+					if (this.type === 'date') {
+						time = time + ' ' + '00:00:00'
+					}
+					return Date.parse(time)
+				}
+			},
+
+			/**
+			 * 生成日期或时间的字符串
+			 */
+			createDomSting() {
+				const yymmdd = this.year +
+					'-' +
+					this.lessThanTen(this.month) +
+					'-' +
+					this.lessThanTen(this.day)
+
+				let hhmmss = this.lessThanTen(this.hour) +
+					':' +
+					this.lessThanTen(this.minute)
+
+				if (!this.hideSecond) {
+					hhmmss = hhmmss + ':' + this.lessThanTen(this.second)
+				}
+
+				if (this.type === 'date') {
+					return yymmdd
+				} else if (this.type === 'time') {
+					return hhmmss
+				} else {
+					return yymmdd + ' ' + hhmmss
+				}
+			},
+
+			/**
+			 * 初始化返回值,并抛出 change 事件
+			 */
+			initTime(emit = true) {
+				this.time = this.createDomSting()
+				if (!emit) return
+				if (this.returnType === 'timestamp' && this.type !== 'time') {
+					this.$emit('change', this.createTimeStamp(this.time))
+					this.$emit('input', this.createTimeStamp(this.time))
+					this.$emit('update:modelValue', this.createTimeStamp(this.time))
+				} else {
+					this.$emit('change', this.time)
+					this.$emit('input', this.time)
+					this.$emit('update:modelValue', this.time)
+				}
+			},
+
+			/**
+			 * 用户选择日期或时间更新 data
+			 * @param {Object} e
+			 */
+			bindDateChange(e) {
+				const val = e.detail.value
+				this.year = this.years[val[0]]
+				this.month = this.months[val[1]]
+				this.day = this.days[val[2]]
+			},
+			bindTimeChange(e) {
+				const val = e.detail.value
+				this.hour = this.hours[val[0]]
+				this.minute = this.minutes[val[1]]
+				this.second = this.seconds[val[2]]
+			},
+
+			/**
+			 * 初始化弹出层
+			 */
+			initTimePicker() {
+				if (this.disabled) return
+				const value = this.fixIosDateFormat(this.value)
+				this.initPickerValue(value)
+				this.visible = !this.visible
+			},
+
+			/**
+			 * 触发或关闭弹框
+			 */
+			tiggerTimePicker(e) {
+				this.visible = !this.visible
+			},
+
+			/**
+			 * 用户点击“清空”按钮,清空当前值
+			 */
+			clearTime() {
+				this.time = ''
+				this.$emit('change', this.time)
+				this.$emit('input', this.time)
+				this.$emit('update:modelValue', this.time)
+				this.tiggerTimePicker()
+			},
+
+			/**
+			 * 用户点击“确定”按钮
+			 */
+			setTime() {
+				this.initTime()
+				this.tiggerTimePicker()
+			}
+		}
+	}
+</script>
+
+<style>
+	.uni-datetime-picker {
+		/* #ifndef APP-NVUE */
+		/* width: 100%; */
+		/* #endif */
+	}
+
+	.uni-datetime-picker-view {
+		height: 130px;
+		width: 270px;
+		/* #ifndef APP-NVUE */
+		cursor: pointer;
+		/* #endif */
+	}
+
+	.uni-datetime-picker-item {
+		height: 50px;
+		line-height: 50px;
+		text-align: center;
+		font-size: 14px;
+	}
+
+	.uni-datetime-picker-btn {
+		margin-top: 60px;
+		/* #ifndef APP-NVUE */
+		display: flex;
+		cursor: pointer;
+		/* #endif */
+		flex-direction: row;
+		justify-content: space-between;
+	}
+
+	.uni-datetime-picker-btn-text {
+		font-size: 14px;
+		color: #007AFF;
+	}
+
+	.uni-datetime-picker-btn-group {
+		/* #ifndef APP-NVUE */
+		display: flex;
+		/* #endif */
+		flex-direction: row;
+	}
+
+	.uni-datetime-picker-cancel {
+		margin-right: 30px;
+	}
+
+	.uni-datetime-picker-mask {
+		position: fixed;
+		bottom: 0px;
+		top: 0px;
+		left: 0px;
+		right: 0px;
+		background-color: rgba(0, 0, 0, 0.4);
+		transition-duration: 0.3s;
+		z-index: 998;
+	}
+
+	.uni-datetime-picker-popup {
+		border-radius: 8px;
+		padding: 30px;
+		width: 270px;
+		/* #ifdef APP-NVUE */
+		height: 500px;
+		/* #endif */
+		/* #ifdef APP-NVUE */
+		width: 330px;
+		/* #endif */
+		background-color: #fff;
+		position: fixed;
+		top: 50%;
+		left: 50%;
+		transform: translate(-50%, -50%);
+		transition-duration: 0.3s;
+		z-index: 999;
+	}
+
+	.fix-nvue-height {
+		/* #ifdef APP-NVUE */
+		height: 330px;
+		/* #endif */
+	}
+
+	.uni-datetime-picker-time {
+		color: grey;
+	}
+
+	.uni-datetime-picker-column {
+		height: 50px;
+	}
+
+	.uni-datetime-picker-timebox {
+
+		border: 1px solid #E5E5E5;
+		border-radius: 5px;
+		padding: 7px 10px;
+		/* #ifndef APP-NVUE */
+		box-sizing: border-box;
+		cursor: pointer;
+		/* #endif */
+	}
+
+	.uni-datetime-picker-timebox-pointer {
+		/* #ifndef APP-NVUE */
+		cursor: pointer;
+		/* #endif */
+	}
+
+
+	.uni-datetime-picker-disabled {
+		opacity: 0.4;
+		/* #ifdef H5 */
+		cursor: not-allowed !important;
+		/* #endif */
+	}
+
+	.uni-datetime-picker-text {
+		font-size: 14px;
+	}
+
+	.uni-datetime-picker-sign {
+		position: absolute;
+		top: 53px;
+		/* 减掉 10px 的元素高度,兼容nvue */
+		color: #999;
+		/* #ifdef APP-NVUE */
+		font-size: 16px;
+		/* #endif */
+	}
+
+	.sign-left {
+		left: 86px;
+	}
+
+	.sign-right {
+		right: 86px;
+	}
+
+	.sign-center {
+		left: 135px;
+	}
+
+	.uni-datetime-picker__container-box {
+		position: relative;
+		display: flex;
+		align-items: center;
+		justify-content: center;
+		margin-top: 40px;
+	}
+
+	.time-hide-second {
+		width: 180px;
+	}
+</style>

+ 997 - 0
uni_modules/uni-datetime-picker/components/uni-datetime-picker/uni-datetime-picker.vue

xqd
@@ -0,0 +1,997 @@
+<template>
+	<view class="uni-date">
+		<view class="uni-date-editor" @click="show">
+			<slot>
+				<view class="uni-date-editor--x" :class="{'uni-date-editor--x__disabled': disabled,
+		'uni-date-x--border': border}">
+					<view v-if="!isRange" class="uni-date-x uni-date-single">
+						<uni-icons type="calendar" color="#e1e1e1" size="22"></uni-icons>
+						<input class="uni-date__x-input" type="text" v-model="singleVal"
+							:placeholder="singlePlaceholderText" :disabled="true" />
+					</view>
+					<view v-else class="uni-date-x uni-date-range">
+						<uni-icons type="calendar" color="#e1e1e1" size="22"></uni-icons>
+						<input class="uni-date__x-input t-c" type="text" v-model="range.startDate"
+							:placeholder="startPlaceholderText" :disabled="true" />
+						<slot>
+							<view class="">{{rangeSeparator}}</view>
+						</slot>
+						<input class="uni-date__x-input t-c" type="text" v-model="range.endDate"
+							:placeholder="endPlaceholderText" :disabled="true" />
+					</view>
+					<view v-if="showClearIcon" class="uni-date__icon-clear" @click.stop="clear">
+						<uni-icons type="clear" color="#e1e1e1" size="18"></uni-icons>
+					</view>
+				</view>
+			</slot>
+		</view>
+
+		<view v-show="popup" class="uni-date-mask" @click="close"></view>
+		<view v-if="!isPhone" ref="datePicker" v-show="popup" class="uni-date-picker__container">
+			<view v-if="!isRange" class="uni-date-single--x" :style="popover">
+				<view class="uni-popper__arrow"></view>
+				<view v-if="hasTime" class="uni-date-changed popup-x-header">
+					<input class="uni-date__input t-c" type="text" v-model="tempSingleDate"
+						:placeholder="selectDateText" />
+					<time-picker type="time" v-model="time" :border="false" :disabled="!tempSingleDate"
+						:start="reactStartTime" :end="reactEndTime" :hideSecond="hideSecond" style="width: 100%;">
+						<input class="uni-date__input t-c" type="text" v-model="time" :placeholder="selectTimeText"
+							:disabled="!tempSingleDate" />
+					</time-picker>
+				</view>
+				<calendar ref="pcSingle" :showMonth="false" :start-date="caleRange.startDate"
+					:end-date="caleRange.endDate" :date="defSingleDate" @change="singleChange"
+					style="padding: 0 8px;" />
+				<view v-if="hasTime" class="popup-x-footer">
+					<!-- <text class="">此刻</text> -->
+					<text class="confirm" @click="confirmSingleChange">{{okText}}</text>
+				</view>
+				<view class="uni-date-popper__arrow"></view>
+			</view>
+
+			<view v-else class="uni-date-range--x" :style="popover">
+				<view class="uni-popper__arrow"></view>
+				<view v-if="hasTime" class="popup-x-header uni-date-changed">
+					<view class="popup-x-header--datetime">
+						<input class="uni-date__input uni-date-range__input" type="text" v-model="tempRange.startDate"
+							:placeholder="startDateText" />
+						<time-picker type="time" v-model="tempRange.startTime" :start="reactStartTime" :border="false"
+							:disabled="!tempRange.startDate" :hideSecond="hideSecond">
+							<input class="uni-date__input uni-date-range__input" type="text"
+								v-model="tempRange.startTime" :placeholder="startTimeText"
+								:disabled="!tempRange.startDate" />
+						</time-picker>
+					</view>
+					<uni-icons type="arrowthinright" color="#999" style="line-height: 40px;"></uni-icons>
+					<view class="popup-x-header--datetime">
+						<input class="uni-date__input uni-date-range__input" type="text" v-model="tempRange.endDate"
+							:placeholder="endDateText" />
+						<time-picker type="time" v-model="tempRange.endTime" :end="reactEndTime" :border="false"
+							:disabled="!tempRange.endDate" :hideSecond="hideSecond">
+							<input class="uni-date__input uni-date-range__input" type="text" v-model="tempRange.endTime"
+								:placeholder="endTimeText" :disabled="!tempRange.endDate" />
+						</time-picker>
+					</view>
+				</view>
+				<view class="popup-x-body">
+					<calendar ref="left" :showMonth="false" :start-date="caleRange.startDate"
+						:end-date="caleRange.endDate" :range="true" @change="leftChange" :pleStatus="endMultipleStatus"
+						@firstEnterCale="updateRightCale" @monthSwitch="leftMonthSwitch" style="padding: 0 8px;" />
+					<calendar ref="right" :showMonth="false" :start-date="caleRange.startDate"
+						:end-date="caleRange.endDate" :range="true" @change="rightChange"
+						:pleStatus="startMultipleStatus" @firstEnterCale="updateLeftCale"
+						@monthSwitch="rightMonthSwitch" style="padding: 0 8px;border-left: 1px solid #F1F1F1;" />
+				</view>
+				<view v-if="hasTime" class="popup-x-footer">
+					<text class="" @click="clear">{{clearText}}</text>
+					<text class="confirm" @click="confirmRangeChange">{{okText}}</text>
+				</view>
+			</view>
+		</view>
+		<calendar v-show="isPhone" ref="mobile" :clearDate="false" :date="defSingleDate" :defTime="reactMobDefTime"
+			:start-date="caleRange.startDate" :end-date="caleRange.endDate" :selectableTimes="mobSelectableTime"
+			:pleStatus="endMultipleStatus" :showMonth="false" :range="isRange" :typeHasTime="hasTime" :insert="false"
+			:hideSecond="hideSecond" @confirm="mobileChange" />
+	</view>
+</template>
+<script>
+	/**
+	 * DatetimePicker 时间选择器
+	 * @description 同时支持 PC 和移动端使用日历选择日期和日期范围
+	 * @tutorial https://ext.dcloud.net.cn/plugin?id=3962
+	 * @property {String} type 选择器类型
+	 * @property {String|Number|Array|Date} value 绑定值
+	 * @property {String} placeholder 单选择时的占位内容
+	 * @property {String} start 起始时间
+	 * @property {String} end 终止时间
+	 * @property {String} start-placeholder 范围选择时开始日期的占位内容
+	 * @property {String} end-placeholder 范围选择时结束日期的占位内容
+	 * @property {String} range-separator 选择范围时的分隔符
+	 * @property {Boolean} border = [true|false] 是否有边框
+	 * @property {Boolean} disabled = [true|false] 是否禁用
+	 * @property {Boolean} clearIcon = [true|false] 是否显示清除按钮(仅PC端适用)
+	 * @event {Function} change 确定日期时触发的事件
+	 * @event {Function} show 打开弹出层
+	 * @event {Function} close 关闭弹出层
+	 * @event {Function} clear 清除上次选中的状态和值
+	 **/
+	import calendar from './calendar.vue'
+	import timePicker from './time-picker.vue'
+	import {
+		initVueI18n
+	} from '@dcloudio/uni-i18n'
+	import messages from './i18n/index.js'
+	const {
+		t
+	} = initVueI18n(messages)
+
+	export default {
+		name: 'UniDatetimePicker',
+		components: {
+			calendar,
+			timePicker
+		},
+		data() {
+			return {
+				isRange: false,
+				hasTime: false,
+				mobileRange: false,
+				// 单选
+				singleVal: '',
+				tempSingleDate: '',
+				defSingleDate: '',
+				time: '',
+				// 范围选
+				caleRange: {
+					startDate: '',
+					startTime: '',
+					endDate: '',
+					endTime: ''
+				},
+				range: {
+					startDate: '',
+					// startTime: '',
+					endDate: '',
+					// endTime: ''
+				},
+				tempRange: {
+					startDate: '',
+					startTime: '',
+					endDate: '',
+					endTime: ''
+				},
+				// 左右日历同步数据
+				startMultipleStatus: {
+					before: '',
+					after: '',
+					data: [],
+					fulldate: ''
+				},
+				endMultipleStatus: {
+					before: '',
+					after: '',
+					data: [],
+					fulldate: ''
+				},
+				visible: false,
+				popup: false,
+				popover: null,
+				isEmitValue: false,
+				isPhone: false,
+				isFirstShow: true,
+			}
+		},
+		props: {
+			type: {
+				type: String,
+				default: 'datetime'
+			},
+			value: {
+				type: [String, Number, Array, Date],
+				default: ''
+			},
+			modelValue: {
+				type: [String, Number, Array, Date],
+				default: ''
+			},
+			start: {
+				type: [Number, String],
+				default: ''
+			},
+			end: {
+				type: [Number, String],
+				default: ''
+			},
+			returnType: {
+				type: String,
+				default: 'string'
+			},
+			placeholder: {
+				type: String,
+				default: ''
+			},
+			startPlaceholder: {
+				type: String,
+				default: ''
+			},
+			endPlaceholder: {
+				type: String,
+				default: ''
+			},
+			rangeSeparator: {
+				type: String,
+				default: '-'
+			},
+			border: {
+				type: [Boolean],
+				default: true
+			},
+			disabled: {
+				type: [Boolean],
+				default: false
+			},
+			clearIcon: {
+				type: [Boolean],
+				default: true
+			},
+			hideSecond: {
+				type: [Boolean],
+				default: false
+			}
+		},
+		watch: {
+			type: {
+				immediate: true,
+				handler(newVal, oldVal) {
+					if (newVal.indexOf('time') !== -1) {
+						this.hasTime = true
+					} else {
+						this.hasTime = false
+					}
+					if (newVal.indexOf('range') !== -1) {
+						this.isRange = true
+					} else {
+						this.isRange = false
+					}
+				}
+			},
+			// #ifndef VUE3
+			value: {
+				immediate: true,
+				handler(newVal, oldVal) {
+					if (this.isEmitValue) {
+						this.isEmitValue = false
+						return
+					}
+					this.initPicker(newVal)
+				}
+			},
+			// #endif
+			// #ifdef VUE3
+			modelValue: {
+				immediate: true,
+				handler(newVal, oldVal) {
+					if (this.isEmitValue) {
+						this.isEmitValue = false
+						return
+					}
+					this.initPicker(newVal)
+				}
+			},
+			// #endif
+			start: {
+				immediate: true,
+				handler(newVal, oldVal) {
+					if (!newVal) return
+					const {
+						defDate,
+						defTime
+					} = this.parseDate(newVal)
+					this.caleRange.startDate = defDate
+					if (this.hasTime) {
+						this.caleRange.startTime = defTime
+					}
+				}
+			},
+			end: {
+				immediate: true,
+				handler(newVal, oldVal) {
+					if (!newVal) return
+					const {
+						defDate,
+						defTime
+					} = this.parseDate(newVal)
+					this.caleRange.endDate = defDate
+					if (this.hasTime) {
+						this.caleRange.endTime = defTime
+					}
+				}
+			},
+		},
+		computed: {
+			reactStartTime() {
+				const activeDate = this.isRange ? this.tempRange.startDate : this.tempSingleDate
+				const res = activeDate === this.caleRange.startDate ? this.caleRange.startTime : ''
+				return res
+			},
+			reactEndTime() {
+				const activeDate = this.isRange ? this.tempRange.endDate : this.tempSingleDate
+				const res = activeDate === this.caleRange.endDate ? this.caleRange.endTime : ''
+				return res
+			},
+			reactMobDefTime() {
+				const times = {
+					start: this.tempRange.startTime,
+					end: this.tempRange.endTime
+				}
+				return this.isRange ? times : this.time
+			},
+			mobSelectableTime() {
+				return {
+					start: this.caleRange.startTime,
+					end: this.caleRange.endTime
+				}
+			},
+			datePopupWidth() {
+				// todo
+				return this.isRange ? 653 : 301
+			},
+
+			/**
+			 * for i18n
+			 */
+			singlePlaceholderText() {
+				return this.placeholder || (this.type === 'date' ? this.selectDateText : t(
+					"uni-datetime-picker.selectDateTime"))
+			},
+			startPlaceholderText() {
+				return this.startPlaceholder || this.startDateText
+			},
+			endPlaceholderText() {
+				return this.endPlaceholder || this.endDateText
+			},
+			selectDateText() {
+				return t("uni-datetime-picker.selectDate")
+			},
+			selectTimeText() {
+				return t("uni-datetime-picker.selectTime")
+			},
+			startDateText() {
+				return this.startPlaceholder || t("uni-datetime-picker.startDate")
+			},
+			startTimeText() {
+				return t("uni-datetime-picker.startTime")
+			},
+			endDateText() {
+				return this.endPlaceholder || t("uni-datetime-picker.endDate")
+			},
+			endTimeText() {
+				return t("uni-datetime-picker.endTime")
+			},
+			okText() {
+				return t("uni-datetime-picker.ok")
+			},
+			clearText() {
+				return t("uni-datetime-picker.clear")
+			},
+			showClearIcon() {
+				const {
+					clearIcon,
+					disabled,
+					singleVal,
+					range
+				} = this
+				const bool = clearIcon && !disabled && (singleVal || (range.startDate && range.endDate))
+				return bool
+			}
+		},
+		created() {
+			this.form = this.getForm('uniForms')
+			this.formItem = this.getForm('uniFormsItem')
+
+			// if (this.formItem) {
+			// 	if (this.formItem.name) {
+			// 		this.rename = this.formItem.name
+			// 		this.form.inputChildrens.push(this)
+			// 	}
+			// }
+		},
+		mounted() {
+			this.platform()
+		},
+		methods: {
+			/**
+			 * 获取父元素实例
+			 */
+			getForm(name = 'uniForms') {
+				let parent = this.$parent;
+				let parentName = parent.$options.name;
+				while (parentName !== name) {
+					parent = parent.$parent;
+					if (!parent) return false
+					parentName = parent.$options.name;
+				}
+				return parent;
+			},
+			initPicker(newVal) {
+				if (!newVal || Array.isArray(newVal) && !newVal.length) {
+					this.$nextTick(() => {
+						this.clear(false)
+					})
+					return
+				}
+				if (!Array.isArray(newVal) && !this.isRange) {
+					const {
+						defDate,
+						defTime
+					} = this.parseDate(newVal)
+					this.singleVal = defDate
+					this.tempSingleDate = defDate
+					this.defSingleDate = defDate
+					if (this.hasTime) {
+						this.singleVal = defDate + ' ' + defTime
+						this.time = defTime
+					}
+				} else {
+					const [before, after] = newVal
+					if (!before && !after) return
+					const defBefore = this.parseDate(before)
+					const defAfter = this.parseDate(after)
+					const startDate = defBefore.defDate
+					const endDate = defAfter.defDate
+					this.range.startDate = this.tempRange.startDate = startDate
+					this.range.endDate = this.tempRange.endDate = endDate
+
+					if (this.hasTime) {
+						this.range.startDate = defBefore.defDate + ' ' + defBefore.defTime
+						this.range.endDate = defAfter.defDate + ' ' + defAfter.defTime
+						this.tempRange.startTime = defBefore.defTime
+						this.tempRange.endTime = defAfter.defTime
+					}
+					const defaultRange = {
+						before: defBefore.defDate,
+						after: defAfter.defDate
+					}
+					this.startMultipleStatus = Object.assign({}, this.startMultipleStatus, defaultRange, {
+						which: 'right'
+					})
+					this.endMultipleStatus = Object.assign({}, this.endMultipleStatus, defaultRange, {
+						which: 'left'
+					})
+				}
+			},
+			updateLeftCale(e) {
+				const left = this.$refs.left
+				// 设置范围选
+				left.cale.setHoverMultiple(e.after)
+				left.setDate(this.$refs.left.nowDate.fullDate)
+			},
+			updateRightCale(e) {
+				const right = this.$refs.right
+				// 设置范围选
+				right.cale.setHoverMultiple(e.after)
+				right.setDate(this.$refs.right.nowDate.fullDate)
+			},
+			platform() {
+				const systemInfo = uni.getSystemInfoSync()
+				this.isPhone = systemInfo.windowWidth <= 500
+				this.windowWidth = systemInfo.windowWidth
+			},
+			show(event) {
+				if (this.disabled) {
+					return
+				}
+				this.platform()
+				if (this.isPhone) {
+					this.$refs.mobile.open()
+					return
+				}
+				this.popover = {
+					top: '10px'
+				}
+				const dateEditor = uni.createSelectorQuery().in(this).select(".uni-date-editor")
+				dateEditor.boundingClientRect(rect => {
+					if (this.windowWidth - rect.left < this.datePopupWidth) {
+						this.popover.right = 0
+					}
+				}).exec()
+				setTimeout(() => {
+					this.popup = !this.popup
+					if (!this.isPhone && this.isRange && this.isFirstShow) {
+						this.isFirstShow = false
+						const {
+							startDate,
+							endDate
+						} = this.range
+						if (startDate && endDate) {
+							if (this.diffDate(startDate, endDate) < 30) {
+								this.$refs.right.next()
+							}
+						} else {
+							this.$refs.right.next()
+							this.$refs.right.cale.lastHover = false
+						}
+					}
+
+				}, 50)
+			},
+
+			close() {
+				setTimeout(() => {
+					this.popup = false
+					this.$emit('maskClick', this.value)
+				}, 20)
+			},
+			setEmit(value) {
+				if (this.returnType === "timestamp" || this.returnType === "date") {
+					if (!Array.isArray(value)) {
+						if (!this.hasTime) {
+							value = value + ' ' + '00:00:00'
+						}
+						value = this.createTimestamp(value)
+						if (this.returnType === "date") {
+							value = new Date(value)
+						}
+					} else {
+						if (!this.hasTime) {
+							value[0] = value[0] + ' ' + '00:00:00'
+							value[1] = value[1] + ' ' + '00:00:00'
+						}
+						value[0] = this.createTimestamp(value[0])
+						value[1] = this.createTimestamp(value[1])
+						if (this.returnType === "date") {
+							value[0] = new Date(value[0])
+							value[1] = new Date(value[1])
+						}
+					}
+				}
+				this.formItem && this.formItem.setValue(value)
+				this.$emit('change', value)
+				this.$emit('input', value)
+				this.$emit('update:modelValue', value)
+				this.isEmitValue = true
+			},
+			createTimestamp(date) {
+				date = this.fixIosDateFormat(date)
+				return Date.parse(new Date(date))
+			},
+			singleChange(e) {
+				this.tempSingleDate = e.fulldate
+				if (this.hasTime) return
+				this.confirmSingleChange()
+			},
+
+			confirmSingleChange() {
+				if (!this.tempSingleDate) {
+					this.popup = false
+					return
+				}
+				if (this.hasTime) {
+					this.singleVal = this.tempSingleDate + ' ' + (this.time ? this.time : '00:00:00')
+				} else {
+					this.singleVal = this.tempSingleDate
+				}
+				this.setEmit(this.singleVal)
+				this.popup = false
+			},
+
+			leftChange(e) {
+				const {
+					before,
+					after
+				} = e.range
+				this.rangeChange(before, after)
+				const obj = {
+					before: e.range.before,
+					after: e.range.after,
+					data: e.range.data,
+					fulldate: e.fulldate
+				}
+				this.startMultipleStatus = Object.assign({}, this.startMultipleStatus, obj)
+			},
+
+			rightChange(e) {
+				const {
+					before,
+					after
+				} = e.range
+				this.rangeChange(before, after)
+				const obj = {
+					before: e.range.before,
+					after: e.range.after,
+					data: e.range.data,
+					fulldate: e.fulldate
+				}
+				this.endMultipleStatus = Object.assign({}, this.endMultipleStatus, obj)
+			},
+
+			mobileChange(e) {
+				if (this.isRange) {
+					const {
+						before,
+						after
+					} = e.range
+					this.handleStartAndEnd(before, after, true)
+					if (this.hasTime) {
+						const {
+							startTime,
+							endTime
+						} = e.timeRange
+						this.tempRange.startTime = startTime
+						this.tempRange.endTime = endTime
+					}
+					this.confirmRangeChange()
+
+				} else {
+					if (this.hasTime) {
+						this.singleVal = e.fulldate + ' ' + e.time
+					} else {
+						this.singleVal = e.fulldate
+					}
+					this.setEmit(this.singleVal)
+				}
+				this.$refs.mobile.close()
+			},
+
+			rangeChange(before, after) {
+				if (!(before && after)) return
+				this.handleStartAndEnd(before, after, true)
+				if (this.hasTime) return
+				this.confirmRangeChange()
+			},
+
+			confirmRangeChange() {
+				if (!this.tempRange.startDate && !this.tempRange.endDate) {
+					this.popup = false
+					return
+				}
+				let start, end
+				if (!this.hasTime) {
+					start = this.range.startDate = this.tempRange.startDate
+					end = this.range.endDate = this.tempRange.endDate
+				} else {
+					start = this.range.startDate = this.tempRange.startDate + ' ' +
+						(this.tempRange.startTime ? this.tempRange.startTime : '00:00:00')
+					end = this.range.endDate = this.tempRange.endDate + ' ' +
+						(this.tempRange.endTime ? this.tempRange.endTime : '00:00:00')
+				}
+				const displayRange = [start, end]
+				this.setEmit(displayRange)
+				this.popup = false
+			},
+
+			handleStartAndEnd(before, after, temp = false) {
+				if (!(before && after)) return
+				const type = temp ? 'tempRange' : 'range'
+				if (this.dateCompare(before, after)) {
+					this[type].startDate = before
+					this[type].endDate = after
+				} else {
+					this[type].startDate = after
+					this[type].endDate = before
+				}
+			},
+
+			/**
+			 * 比较时间大小
+			 */
+			dateCompare(startDate, endDate) {
+				// 计算截止时间
+				startDate = new Date(startDate.replace('-', '/').replace('-', '/'))
+				// 计算详细项的截止时间
+				endDate = new Date(endDate.replace('-', '/').replace('-', '/'))
+				if (startDate <= endDate) {
+					return true
+				} else {
+					return false
+				}
+			},
+
+			/**
+			 * 比较时间差
+			 */
+			diffDate(startDate, endDate) {
+				// 计算截止时间
+				startDate = new Date(startDate.replace('-', '/').replace('-', '/'))
+				// 计算详细项的截止时间
+				endDate = new Date(endDate.replace('-', '/').replace('-', '/'))
+				const diff = (endDate - startDate) / (24 * 60 * 60 * 1000)
+				return Math.abs(diff)
+			},
+
+			clear(needEmit = true) {
+				if (!this.isRange) {
+					this.singleVal = ''
+					this.tempSingleDate = ''
+					this.time = ''
+					if (this.isPhone) {
+						this.$refs.mobile && this.$refs.mobile.clearCalender()
+					} else {
+						this.$refs.pcSingle && this.$refs.pcSingle.clearCalender()
+					}
+					if (needEmit) {
+						this.formItem && this.formItem.setValue('')
+						this.$emit('change', '')
+						this.$emit('input', '')
+						this.$emit('update:modelValue', '')
+					}
+				} else {
+					this.range.startDate = ''
+					this.range.endDate = ''
+					this.tempRange.startDate = ''
+					this.tempRange.startTime = ''
+					this.tempRange.endDate = ''
+					this.tempRange.endTime = ''
+					if (this.isPhone) {
+						this.$refs.mobile && this.$refs.mobile.clearCalender()
+					} else {
+						this.$refs.left && this.$refs.left.clearCalender()
+						this.$refs.right && this.$refs.right.clearCalender()
+						this.$refs.right && this.$refs.right.next()
+					}
+					if (needEmit) {
+						this.formItem && this.formItem.setValue([])
+						this.$emit('change', [])
+						this.$emit('input', [])
+						this.$emit('update:modelValue', [])
+					}
+				}
+			},
+
+			parseDate(date) {
+				date = this.fixIosDateFormat(date)
+				const defVal = new Date(date)
+				const year = defVal.getFullYear()
+				const month = defVal.getMonth() + 1
+				const day = defVal.getDate()
+				const hour = defVal.getHours()
+				const minute = defVal.getMinutes()
+				const second = defVal.getSeconds()
+				const defDate = year + '-' + this.lessTen(month) + '-' + this.lessTen(day)
+				const defTime = this.lessTen(hour) + ':' + this.lessTen(minute) + (this.hideSecond ? '' : (':' + this
+					.lessTen(second)))
+				return {
+					defDate,
+					defTime
+				}
+			},
+
+			lessTen(item) {
+				return item < 10 ? '0' + item : item
+			},
+
+			//兼容 iOS、safari 日期格式
+			fixIosDateFormat(value) {
+				if (typeof value === 'string') {
+					value = value.replace(/-/g, '/')
+				}
+				return value
+			},
+
+			leftMonthSwitch(e) {
+				// console.log('leftMonthSwitch 返回:', e)
+			},
+			rightMonthSwitch(e) {
+				// console.log('rightMonthSwitch 返回:', e)
+			}
+		}
+	}
+</script>
+
+<style>
+	.uni-date-x {
+		display: flex;
+		flex-direction: row;
+		align-items: center;
+		justify-content: center;
+		padding: 0 10px;
+		border-radius: 4px;
+		background-color: #fff;
+		color: #666;
+		font-size: 14px;
+	}
+
+	.uni-date-x--border {
+		box-sizing: border-box;
+		border-radius: 4px;
+		border: 1px solid #dcdfe6;
+	}
+
+	.uni-date-editor--x {
+		position: relative;
+	}
+
+	.uni-date-editor--x .uni-date__icon-clear {
+		position: absolute;
+		top: 0;
+		right: 0;
+		display: inline-block;
+		box-sizing: border-box;
+		border: 9px solid transparent;
+		/* #ifdef H5 */
+		cursor: pointer;
+		/* #endif */
+	}
+
+	.uni-date__x-input {
+		padding: 0 8px;
+		height: 40px;
+		width: 100%;
+		line-height: 40px;
+		font-size: 14px;
+	}
+
+	.t-c {
+		text-align: center;
+	}
+
+	.uni-date__input {
+		height: 40px;
+		width: 100%;
+		line-height: 40px;
+		font-size: 14px;
+	}
+
+	.uni-date-range__input {
+		text-align: center;
+		max-width: 142px;
+	}
+
+	.uni-date-picker__container {
+		position: relative;
+		/* 		position: fixed;
+		left: 0;
+		right: 0;
+		top: 0;
+		bottom: 0;
+		box-sizing: border-box;
+		z-index: 996;
+		font-size: 14px; */
+	}
+
+	.uni-date-mask {
+		position: fixed;
+		bottom: 0px;
+		top: 0px;
+		left: 0px;
+		right: 0px;
+		background-color: rgba(0, 0, 0, 0);
+		transition-duration: 0.3s;
+		z-index: 996;
+	}
+
+	.uni-date-single--x {
+		/* padding: 0 8px; */
+		background-color: #fff;
+		position: absolute;
+		top: 0;
+		z-index: 999;
+		border: 1px solid #EBEEF5;
+		box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
+		border-radius: 4px;
+	}
+
+	.uni-date-range--x {
+		/* padding: 0 8px; */
+		background-color: #fff;
+		position: absolute;
+		top: 0;
+		z-index: 999;
+		border: 1px solid #EBEEF5;
+		box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
+		border-radius: 4px;
+	}
+
+	.uni-date-editor--x__disabled {
+		opacity: 0.4;
+		cursor: default;
+	}
+
+	.uni-date-editor--logo {
+		width: 16px;
+		height: 16px;
+		vertical-align: middle;
+	}
+
+	/* 添加时间 */
+	.popup-x-header {
+		/* #ifndef APP-NVUE */
+		display: flex;
+		/* #endif */
+		flex-direction: row;
+		/* justify-content: space-between; */
+	}
+
+	.popup-x-header--datetime {
+		/* #ifndef APP-NVUE */
+		display: flex;
+		/* #endif */
+		flex-direction: row;
+		flex: 1;
+	}
+
+	.popup-x-body {
+		display: flex;
+	}
+
+	.popup-x-footer {
+		padding: 0 15px;
+		border-top-color: #F1F1F1;
+		border-top-style: solid;
+		border-top-width: 1px;
+		/* background-color: #fff; */
+		line-height: 40px;
+		text-align: right;
+		color: #666;
+	}
+
+	.popup-x-footer text:hover {
+		color: #007aff;
+		cursor: pointer;
+		opacity: 0.8;
+	}
+
+	.popup-x-footer .confirm {
+		margin-left: 20px;
+		color: #007aff;
+	}
+
+	.uni-date-changed {
+		/* background-color: #fff; */
+		text-align: center;
+		color: #333;
+		border-bottom-color: #F1F1F1;
+		border-bottom-style: solid;
+		border-bottom-width: 1px;
+		/* padding: 0 50px; */
+	}
+
+	.uni-date-changed--time text {
+		/* padding: 0 20px; */
+		height: 50px;
+		line-height: 50px;
+	}
+
+	.uni-date-changed .uni-date-changed--time {
+		/* display: flex; */
+		flex: 1;
+	}
+
+	.uni-date-changed--time-date {
+		color: #333;
+		opacity: 0.6;
+	}
+
+	.mr-50 {
+		margin-right: 50px;
+	}
+
+	/* picker 弹出层通用的指示小三角, todo:扩展至上下左右方向定位 */
+	.uni-popper__arrow,
+	.uni-popper__arrow::after {
+		position: absolute;
+		display: block;
+		width: 0;
+		height: 0;
+		border-color: transparent;
+		border-style: solid;
+		border-width: 6px;
+	}
+
+	.uni-popper__arrow {
+		filter: drop-shadow(0 2px 12px rgba(0, 0, 0, 0.03));
+		top: -6px;
+		left: 10%;
+		margin-right: 3px;
+		border-top-width: 0;
+		border-bottom-color: #EBEEF5;
+	}
+
+	.uni-popper__arrow::after {
+		content: " ";
+		top: 1px;
+		margin-left: -6px;
+		border-top-width: 0;
+		border-bottom-color: #fff;
+	}
+</style>

+ 410 - 0
uni_modules/uni-datetime-picker/components/uni-datetime-picker/util.js

xqd
@@ -0,0 +1,410 @@
+class Calendar {
+	constructor({
+		date,
+		selected,
+		startDate,
+		endDate,
+		range,
+		// multipleStatus
+	} = {}) {
+		// 当前日期
+		this.date = this.getDate(new Date()) // 当前初入日期
+		// 打点信息
+		this.selected = selected || [];
+		// 范围开始
+		this.startDate = startDate
+		// 范围结束
+		this.endDate = endDate
+		this.range = range
+		// 多选状态
+		this.cleanMultipleStatus()
+		// 每周日期
+		this.weeks = {}
+		// this._getWeek(this.date.fullDate)
+		// this.multipleStatus = multipleStatus
+		this.lastHover = false
+	}
+	/**
+	 * 设置日期
+	 * @param {Object} date
+	 */
+	setDate(date) {
+		this.selectDate = this.getDate(date)
+		this._getWeek(this.selectDate.fullDate)
+	}
+
+	/**
+	 * 清理多选状态
+	 */
+	cleanMultipleStatus() {
+		this.multipleStatus = {
+			before: '',
+			after: '',
+			data: []
+		}
+	}
+
+	/**
+	 * 重置开始日期
+	 */
+	resetSatrtDate(startDate) {
+		// 范围开始
+		this.startDate = startDate
+
+	}
+
+	/**
+	 * 重置结束日期
+	 */
+	resetEndDate(endDate) {
+		// 范围结束
+		this.endDate = endDate
+	}
+
+	/**
+	 * 获取任意时间
+	 */
+	getDate(date, AddDayCount = 0, str = 'day') {
+		if (!date) {
+			date = new Date()
+		}
+		if (typeof date !== 'object') {
+			date = date.replace(/-/g, '/')
+		}
+		const dd = new Date(date)
+		switch (str) {
+			case 'day':
+				dd.setDate(dd.getDate() + AddDayCount) // 获取AddDayCount天后的日期
+				break
+			case 'month':
+				if (dd.getDate() === 31) {
+					dd.setDate(dd.getDate() + AddDayCount)
+				} else {
+					dd.setMonth(dd.getMonth() + AddDayCount) // 获取AddDayCount天后的日期
+				}
+				break
+			case 'year':
+				dd.setFullYear(dd.getFullYear() + AddDayCount) // 获取AddDayCount天后的日期
+				break
+		}
+		const y = dd.getFullYear()
+		const m = dd.getMonth() + 1 < 10 ? '0' + (dd.getMonth() + 1) : dd.getMonth() + 1 // 获取当前月份的日期,不足10补0
+		const d = dd.getDate() < 10 ? '0' + dd.getDate() : dd.getDate() // 获取当前几号,不足10补0
+		return {
+			fullDate: y + '-' + m + '-' + d,
+			year: y,
+			month: m,
+			date: d,
+			day: dd.getDay()
+		}
+	}
+
+
+	/**
+	 * 获取上月剩余天数
+	 */
+	_getLastMonthDays(firstDay, full) {
+		let dateArr = []
+		for (let i = firstDay; i > 0; i--) {
+			const beforeDate = new Date(full.year, full.month - 1, -i + 1).getDate()
+			dateArr.push({
+				date: beforeDate,
+				month: full.month - 1,
+				disable: true
+			})
+		}
+		return dateArr
+	}
+	/**
+	 * 获取本月天数
+	 */
+	_currentMonthDys(dateData, full) {
+		let dateArr = []
+		let fullDate = this.date.fullDate
+		for (let i = 1; i <= dateData; i++) {
+			let isinfo = false
+			let nowDate = full.year + '-' + (full.month < 10 ?
+				full.month : full.month) + '-' + (i < 10 ?
+				'0' + i : i)
+			// 是否今天
+			let isDay = fullDate === nowDate
+			// 获取打点信息
+			let info = this.selected && this.selected.find((item) => {
+				if (this.dateEqual(nowDate, item.date)) {
+					return item
+				}
+			})
+
+			// 日期禁用
+			let disableBefore = true
+			let disableAfter = true
+			if (this.startDate) {
+				// let dateCompBefore = this.dateCompare(this.startDate, fullDate)
+				// disableBefore = this.dateCompare(dateCompBefore ? this.startDate : fullDate, nowDate)
+				disableBefore = this.dateCompare(this.startDate, nowDate)
+			}
+
+			if (this.endDate) {
+				// let dateCompAfter = this.dateCompare(fullDate, this.endDate)
+				// disableAfter = this.dateCompare(nowDate, dateCompAfter ? this.endDate : fullDate)
+				disableAfter = this.dateCompare(nowDate, this.endDate)
+			}
+			let multiples = this.multipleStatus.data
+			let checked = false
+			let multiplesStatus = -1
+			if (this.range) {
+				if (multiples) {
+					multiplesStatus = multiples.findIndex((item) => {
+						return this.dateEqual(item, nowDate)
+					})
+				}
+				if (multiplesStatus !== -1) {
+					checked = true
+				}
+			}
+			let data = {
+				fullDate: nowDate,
+				year: full.year,
+				date: i,
+				multiple: this.range ? checked : false,
+				beforeMultiple: this.isLogicBefore(nowDate, this.multipleStatus.before, this.multipleStatus.after),
+				afterMultiple: this.isLogicAfter(nowDate, this.multipleStatus.before, this.multipleStatus.after),
+				month: full.month,
+				disable: !(disableBefore && disableAfter),
+				isDay,
+				userChecked: false
+			}
+			if (info) {
+				data.extraInfo = info
+			}
+
+			dateArr.push(data)
+		}
+		return dateArr
+	}
+	/**
+	 * 获取下月天数
+	 */
+	_getNextMonthDays(surplus, full) {
+		let dateArr = []
+		for (let i = 1; i < surplus + 1; i++) {
+			dateArr.push({
+				date: i,
+				month: Number(full.month) + 1,
+				disable: true
+			})
+		}
+		return dateArr
+	}
+
+	/**
+	 * 获取当前日期详情
+	 * @param {Object} date
+	 */
+	getInfo(date) {
+		if (!date) {
+			date = new Date()
+		}
+		const dateInfo = this.canlender.find(item => item.fullDate === this.getDate(date).fullDate)
+		return dateInfo
+	}
+
+	/**
+	 * 比较时间大小
+	 */
+	dateCompare(startDate, endDate) {
+		// 计算截止时间
+		startDate = new Date(startDate.replace('-', '/').replace('-', '/'))
+		// 计算详细项的截止时间
+		endDate = new Date(endDate.replace('-', '/').replace('-', '/'))
+		if (startDate <= endDate) {
+			return true
+		} else {
+			return false
+		}
+	}
+
+	/**
+	 * 比较时间是否相等
+	 */
+	dateEqual(before, after) {
+		// 计算截止时间
+		before = new Date(before.replace('-', '/').replace('-', '/'))
+		// 计算详细项的截止时间
+		after = new Date(after.replace('-', '/').replace('-', '/'))
+		if (before.getTime() - after.getTime() === 0) {
+			return true
+		} else {
+			return false
+		}
+	}
+
+	/**
+	 *  比较真实起始日期
+	 */
+
+	isLogicBefore(currentDay, before, after) {
+		let logicBefore = before
+		if (before && after) {
+			logicBefore = this.dateCompare(before, after) ? before : after
+		}
+		return this.dateEqual(logicBefore, currentDay)
+	}
+
+	isLogicAfter(currentDay, before, after) {
+		let logicAfter = after
+		if (before && after) {
+			logicAfter = this.dateCompare(before, after) ? after : before
+		}
+		return this.dateEqual(logicAfter, currentDay)
+	}
+
+	/**
+	 * 获取日期范围内所有日期
+	 * @param {Object} begin
+	 * @param {Object} end
+	 */
+	geDateAll(begin, end) {
+		var arr = []
+		var ab = begin.split('-')
+		var ae = end.split('-')
+		var db = new Date()
+		db.setFullYear(ab[0], ab[1] - 1, ab[2])
+		var de = new Date()
+		de.setFullYear(ae[0], ae[1] - 1, ae[2])
+		var unixDb = db.getTime() - 24 * 60 * 60 * 1000
+		var unixDe = de.getTime() - 24 * 60 * 60 * 1000
+		for (var k = unixDb; k <= unixDe;) {
+			k = k + 24 * 60 * 60 * 1000
+			arr.push(this.getDate(new Date(parseInt(k))).fullDate)
+		}
+		return arr
+	}
+
+	/**
+	 *  获取多选状态
+	 */
+	setMultiple(fullDate) {
+		let {
+			before,
+			after
+		} = this.multipleStatus
+		if (!this.range) return
+		if (before && after) {
+			if (!this.lastHover) {
+				this.lastHover = true
+				return
+			}
+			this.multipleStatus.before = fullDate
+			this.multipleStatus.after = ''
+			this.multipleStatus.data = []
+			this.multipleStatus.fulldate = ''
+			this.lastHover = false
+		} else {
+			if (!before) {
+				this.multipleStatus.before = fullDate
+				this.lastHover = false
+			} else {
+				this.multipleStatus.after = fullDate
+				if (this.dateCompare(this.multipleStatus.before, this.multipleStatus.after)) {
+					this.multipleStatus.data = this.geDateAll(this.multipleStatus.before, this.multipleStatus
+						.after);
+				} else {
+					this.multipleStatus.data = this.geDateAll(this.multipleStatus.after, this.multipleStatus
+						.before);
+				}
+				this.lastHover = true
+			}
+		}
+		this._getWeek(fullDate)
+	}
+
+	/**
+	 *  鼠标 hover 更新多选状态
+	 */
+	setHoverMultiple(fullDate) {
+		let {
+			before,
+			after
+		} = this.multipleStatus
+
+		if (!this.range) return
+		if (this.lastHover) return
+
+		if (!before) {
+			this.multipleStatus.before = fullDate
+		} else {
+			this.multipleStatus.after = fullDate
+			if (this.dateCompare(this.multipleStatus.before, this.multipleStatus.after)) {
+				this.multipleStatus.data = this.geDateAll(this.multipleStatus.before, this.multipleStatus.after);
+			} else {
+				this.multipleStatus.data = this.geDateAll(this.multipleStatus.after, this.multipleStatus.before);
+			}
+		}
+		this._getWeek(fullDate)
+	}
+
+	/**
+	 * 更新默认值多选状态
+	 */
+	setDefaultMultiple(before, after) {
+		this.multipleStatus.before = before
+		this.multipleStatus.after = after
+		if (before && after) {
+			if (this.dateCompare(before, after)) {
+				this.multipleStatus.data = this.geDateAll(before, after);
+				this._getWeek(after)
+			} else {
+				this.multipleStatus.data = this.geDateAll(after, before);
+				this._getWeek(before)
+			}
+		}
+	}
+
+	/**
+	 * 获取每周数据
+	 * @param {Object} dateData
+	 */
+	_getWeek(dateData) {
+		const {
+			fullDate,
+			year,
+			month,
+			date,
+			day
+		} = this.getDate(dateData)
+		let firstDay = new Date(year, month - 1, 1).getDay()
+		let currentDay = new Date(year, month, 0).getDate()
+		let dates = {
+			lastMonthDays: this._getLastMonthDays(firstDay, this.getDate(dateData)), // 上个月末尾几天
+			currentMonthDys: this._currentMonthDys(currentDay, this.getDate(dateData)), // 本月天数
+			nextMonthDays: [], // 下个月开始几天
+			weeks: []
+		}
+		let canlender = []
+		const surplus = 42 - (dates.lastMonthDays.length + dates.currentMonthDys.length)
+		dates.nextMonthDays = this._getNextMonthDays(surplus, this.getDate(dateData))
+		canlender = canlender.concat(dates.lastMonthDays, dates.currentMonthDys, dates.nextMonthDays)
+		let weeks = {}
+		// 拼接数组  上个月开始几天 + 本月天数+ 下个月开始几天
+		for (let i = 0; i < canlender.length; i++) {
+			if (i % 7 === 0) {
+				weeks[parseInt(i / 7)] = new Array(7)
+			}
+			weeks[parseInt(i / 7)][i % 7] = canlender[i]
+		}
+		this.canlender = canlender
+		this.weeks = weeks
+	}
+
+	//静态方法
+	// static init(date) {
+	// 	if (!this.instance) {
+	// 		this.instance = new Calendar(date);
+	// 	}
+	// 	return this.instance;
+	// }
+}
+
+
+export default Calendar

+ 90 - 0
uni_modules/uni-datetime-picker/package.json

xqd
@@ -0,0 +1,90 @@
+{
+  "id": "uni-datetime-picker",
+  "displayName": "uni-datetime-picker 日期选择器",
+  "version": "2.2.4",
+  "description": "uni-datetime-picker 日期时间选择器,支持日历,支持范围选择",
+  "keywords": [
+    "uni-datetime-picker",
+    "uni-ui",
+    "uniui",
+    "日期时间选择器",
+    "日期时间"
+],
+  "repository": "https://github.com/dcloudio/uni-ui",
+  "engines": {
+    "HBuilderX": ""
+  },
+  "directories": {
+    "example": "../../temps/example_temps"
+  },
+  "dcloudext": {
+    "category": [
+      "前端组件",
+      "通用组件"
+    ],
+    "sale": {
+      "regular": {
+        "price": "0.00"
+      },
+      "sourcecode": {
+        "price": "0.00"
+      }
+    },
+    "contact": {
+      "qq": ""
+    },
+    "declaration": {
+      "ads": "无",
+      "data": "无",
+      "permissions": "无"
+    },
+    "npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui"
+  },
+  "uni_modules": {
+    "dependencies": [
+			"uni-scss",
+			"uni-icons"
+		],
+    "encrypt": [],
+    "platforms": {
+      "cloud": {
+        "tcb": "y",
+        "aliyun": "y"
+      },
+      "client": {
+        "App": {
+          "app-vue": "y",
+          "app-nvue": "n"
+        },
+        "H5-mobile": {
+          "Safari": "y",
+          "Android Browser": "y",
+          "微信浏览器(Android)": "y",
+          "QQ浏览器(Android)": "y"
+        },
+        "H5-pc": {
+          "Chrome": "y",
+          "IE": "y",
+          "Edge": "y",
+          "Firefox": "y",
+          "Safari": "y"
+        },
+        "小程序": {
+          "微信": "y",
+          "阿里": "y",
+          "百度": "y",
+          "字节跳动": "y",
+          "QQ": "y"
+        },
+        "快应用": {
+          "华为": "u",
+          "联盟": "u"
+        },
+        "Vue": {
+            "vue2": "y",
+            "vue3": "y"
+        }
+      }
+    }
+  }
+}

+ 21 - 0
uni_modules/uni-datetime-picker/readme.md

xqd
@@ -0,0 +1,21 @@
+
+
+> `重要通知:组件升级更新 2.0.0 后,支持日期+时间范围选择,组件 ui 将使用日历选择日期,ui 变化较大,同时支持 PC 和 移动端。此版本不向后兼容,不再支持单独的时间选择(type=time)及相关的 hide-second 属性(时间选可使用内置组件 picker)。若仍需使用旧版本,可在插件市场下载*非uni_modules版本*,旧版本将不再维护`
+
+## DatetimePicker 时间选择器
+
+> **组件名:uni-datetime-picker**
+> 代码块: `uDatetimePicker`
+
+
+该组件的优势是,支持**时间戳**输入和输出(起始时间、终止时间也支持时间戳),可**同时选择**日期和时间。
+
+若只是需要单独选择日期和时间,不需要时间戳输入和输出,可使用原生的 picker 组件。
+
+**_点击 picker 默认值规则:_**
+
+- 若设置初始值 value, 会显示在 picker 显示框中
+- 若无初始值 value,则初始值 value 为当前本地时间 Date.now(), 但不会显示在 picker 显示框中
+
+### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-datetime-picker)
+#### 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839 

+ 17 - 0
uni_modules/uni-fab/changelog.md

xqd
@@ -0,0 +1,17 @@
+## 1.2.2(2021-12-29)
+- 更新 组件依赖
+## 1.2.1(2021-11-19)
+- 修复 阴影颜色不正确的bug
+## 1.2.0(2021-11-19)
+- 优化 组件UI,并提供设计资源,详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource)
+- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-fab](https://uniapp.dcloud.io/component/uniui/uni-fab)
+## 1.1.1(2021-11-09) 
+- 新增 提供组件设计资源,组件样式调整
+## 1.1.0(2021-07-30)
+- 组件兼容 vue3,如何创建vue3项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834)
+## 1.0.7(2021-05-12)
+- 新增 组件示例地址
+## 1.0.6(2021-02-05)
+- 调整为uni_modules目录规范
+- 优化 按钮背景色调整
+- 优化 兼容pc端

+ 475 - 0
uni_modules/uni-fab/components/uni-fab/uni-fab.vue

xqd
@@ -0,0 +1,475 @@
+<template>
+	<view class="uni-cursor-point">
+		<view v-if="popMenu && (leftBottom||rightBottom||leftTop||rightTop) && content.length > 0" :class="{
+        'uni-fab--leftBottom': leftBottom,
+        'uni-fab--rightBottom': rightBottom,
+        'uni-fab--leftTop': leftTop,
+        'uni-fab--rightTop': rightTop
+      }" class="uni-fab">
+			<view :class="{
+          'uni-fab__content--left': horizontal === 'left',
+          'uni-fab__content--right': horizontal === 'right',
+          'uni-fab__content--flexDirection': direction === 'vertical',
+          'uni-fab__content--flexDirectionStart': flexDirectionStart,
+          'uni-fab__content--flexDirectionEnd': flexDirectionEnd,
+		  'uni-fab__content--other-platform': !isAndroidNvue
+        }" :style="{ width: boxWidth, height: boxHeight, backgroundColor: styles.backgroundColor }"
+				class="uni-fab__content" elevation="5">
+				<view v-if="flexDirectionStart || horizontalLeft" class="uni-fab__item uni-fab__item--first" />
+				<view v-for="(item, index) in content" :key="index" :class="{ 'uni-fab__item--active': isShow }"
+					class="uni-fab__item" @click="_onItemClick(index, item)">
+					<image :src="item.active ? item.selectedIconPath : item.iconPath" class="uni-fab__item-image"
+						mode="aspectFit" />
+					<text class="uni-fab__item-text"
+						:style="{ color: item.active ? styles.selectedColor : styles.color }">{{ item.text }}</text>
+				</view>
+				<view v-if="flexDirectionEnd || horizontalRight" class="uni-fab__item uni-fab__item--first" />
+			</view>
+		</view>
+		<view :class="{
+		  'uni-fab__circle--leftBottom': leftBottom,
+		  'uni-fab__circle--rightBottom': rightBottom,
+		  'uni-fab__circle--leftTop': leftTop,
+		  'uni-fab__circle--rightTop': rightTop,
+		  'uni-fab__content--other-platform': !isAndroidNvue
+		}" class="uni-fab__circle uni-fab__plus" :style="{ 'background-color': styles.buttonColor }" @click="_onClick">
+			<uni-icons class="fab-circle-icon" type="plusempty" :color="styles.iconColor" size="32"
+				:class="{'uni-fab__plus--active': isShow && content.length > 0}"></uni-icons>
+			<!-- <view class="fab-circle-v"  :class="{'uni-fab__plus--active': isShow && content.length > 0}"></view>
+			<view class="fab-circle-h" :class="{'uni-fab__plus--active': isShow  && content.length > 0}"></view> -->
+		</view>
+	</view>
+</template>
+
+<script>
+	let platform = 'other'
+	// #ifdef APP-NVUE
+	platform = uni.getSystemInfoSync().platform
+	// #endif
+
+	/**
+	 * Fab 悬浮按钮
+	 * @description 点击可展开一个图形按钮菜单
+	 * @tutorial https://ext.dcloud.net.cn/plugin?id=144
+	 * @property {Object} pattern 可选样式配置项
+	 * @property {Object} horizontal = [left | right] 水平对齐方式
+	 * 	@value left 左对齐
+	 * 	@value right 右对齐
+	 * @property {Object} vertical = [bottom | top] 垂直对齐方式
+	 * 	@value bottom 下对齐
+	 * 	@value top 上对齐
+	 * @property {Object} direction = [horizontal | vertical] 展开菜单显示方式
+	 * 	@value horizontal 水平显示
+	 * 	@value vertical 垂直显示
+	 * @property {Array} content 展开菜单内容配置项
+	 * @property {Boolean} popMenu 是否使用弹出菜单
+	 * @event {Function} trigger 展开菜单点击事件,返回点击信息
+	 * @event {Function} fabClick 悬浮按钮点击事件
+	 */
+	export default {
+		name: 'UniFab',
+		emits: ['fabClick', 'trigger'],
+		props: {
+			pattern: {
+				type: Object,
+				default () {
+					return {}
+				}
+			},
+			horizontal: {
+				type: String,
+				default: 'left'
+			},
+			vertical: {
+				type: String,
+				default: 'bottom'
+			},
+			direction: {
+				type: String,
+				default: 'horizontal'
+			},
+			content: {
+				type: Array,
+				default () {
+					return []
+				}
+			},
+			show: {
+				type: Boolean,
+				default: false
+			},
+			popMenu: {
+				type: Boolean,
+				default: true
+			}
+		},
+		data() {
+			return {
+				fabShow: false,
+				isShow: false,
+				isAndroidNvue: platform === 'android',
+				styles: {
+					color: '#3c3e49',
+					selectedColor: '#007AFF',
+					backgroundColor: '#fff',
+					buttonColor: '',
+					iconColor: '#fff'
+				}
+			}
+		},
+		computed: {
+			contentWidth(e) {
+				return (this.content.length + 1) * 55 + 15 + 'px'
+			},
+			contentWidthMin() {
+				return '55px'
+			},
+			// 动态计算宽度
+			boxWidth() {
+				return this.getPosition(3, 'horizontal')
+			},
+			// 动态计算高度
+			boxHeight() {
+				return this.getPosition(3, 'vertical')
+			},
+			// 计算左下位置
+			leftBottom() {
+				return this.getPosition(0, 'left', 'bottom')
+			},
+			// 计算右下位置
+			rightBottom() {
+				return this.getPosition(0, 'right', 'bottom')
+			},
+			// 计算左上位置
+			leftTop() {
+				return this.getPosition(0, 'left', 'top')
+			},
+			rightTop() {
+				return this.getPosition(0, 'right', 'top')
+			},
+			flexDirectionStart() {
+				return this.getPosition(1, 'vertical', 'top')
+			},
+			flexDirectionEnd() {
+				return this.getPosition(1, 'vertical', 'bottom')
+			},
+			horizontalLeft() {
+				return this.getPosition(2, 'horizontal', 'left')
+			},
+			horizontalRight() {
+				return this.getPosition(2, 'horizontal', 'right')
+			}
+		},
+		watch: {
+			pattern: {
+				handler(val, oldVal) {
+					this.styles = Object.assign({}, this.styles, val)
+				},
+				deep: true
+			}
+		},
+		created() {
+			this.isShow = this.show
+			if (this.top === 0) {
+				this.fabShow = true
+			}
+			// 初始化样式
+			this.styles = Object.assign({}, this.styles, this.pattern)
+		},
+		methods: {
+			_onClick() {
+				this.$emit('fabClick')
+				if (!this.popMenu) {
+					return
+				}
+				this.isShow = !this.isShow
+			},
+			open() {
+				this.isShow = true
+			},
+			close() {
+				this.isShow = false
+			},
+			/**
+			 * 按钮点击事件
+			 */
+			_onItemClick(index, item) {
+				this.$emit('trigger', {
+					index,
+					item
+				})
+			},
+			/**
+			 * 获取 位置信息
+			 */
+			getPosition(types, paramA, paramB) {
+				if (types === 0) {
+					return this.horizontal === paramA && this.vertical === paramB
+				} else if (types === 1) {
+					return this.direction === paramA && this.vertical === paramB
+				} else if (types === 2) {
+					return this.direction === paramA && this.horizontal === paramB
+				} else {
+					return this.isShow && this.direction === paramA ? this.contentWidth : this.contentWidthMin
+				}
+			}
+		}
+	}
+</script>
+
+<style lang="scss" >
+	$uni-shadow-base:0 1px 5px 2px rgba($color: #000000, $alpha: 0.3) !default;
+
+	.uni-fab {
+		position: fixed;
+		/* #ifndef APP-NVUE */
+		display: flex;
+		/* #endif */
+		justify-content: center;
+		align-items: center;
+		z-index: 10;
+		border-radius: 45px;
+		box-shadow: $uni-shadow-base;
+	}
+
+	.uni-cursor-point {
+		/* #ifdef H5 */
+		cursor: pointer;
+		/* #endif */
+	}
+
+	.uni-fab--active {
+		opacity: 1;
+	}
+
+	.uni-fab--leftBottom {
+		left: 15px;
+		bottom: 30px;
+		/* #ifdef H5 */
+		left: calc(15px + var(--window-left));
+		bottom: calc(30px + var(--window-bottom));
+		/* #endif */
+		// padding: 10px;
+	}
+
+	.uni-fab--leftTop {
+		left: 15px;
+		top: 30px;
+		/* #ifdef H5 */
+		left: calc(15px + var(--window-left));
+		top: calc(30px + var(--window-top));
+		/* #endif */
+		// padding: 10px;
+	}
+
+	.uni-fab--rightBottom {
+		right: 15px;
+		bottom: 30px;
+		/* #ifdef H5 */
+		right: calc(15px + var(--window-right));
+		bottom: calc(30px + var(--window-bottom));
+		/* #endif */
+		// padding: 10px;
+	}
+
+	.uni-fab--rightTop {
+		right: 15px;
+		top: 30px;
+		/* #ifdef H5 */
+		right: calc(15px + var(--window-right));
+		top: calc(30px + var(--window-top));
+		/* #endif */
+		// padding: 10px;
+	}
+
+	.uni-fab__circle {
+		position: fixed;
+		/* #ifndef APP-NVUE */
+		display: flex;
+		/* #endif */
+		justify-content: center;
+		align-items: center;
+		width: 55px;
+		height: 55px;
+		background-color: #3c3e49;
+		border-radius: 45px;
+		z-index: 11;
+		// box-shadow: $uni-shadow-base;
+	}
+
+	.uni-fab__circle--leftBottom {
+		left: 15px;
+		bottom: 30px;
+		/* #ifdef H5 */
+		left: calc(15px + var(--window-left));
+		bottom: calc(30px + var(--window-bottom));
+		/* #endif */
+	}
+
+	.uni-fab__circle--leftTop {
+		left: 15px;
+		top: 30px;
+		/* #ifdef H5 */
+		left: calc(15px + var(--window-left));
+		top: calc(30px + var(--window-top));
+		/* #endif */
+	}
+
+	.uni-fab__circle--rightBottom {
+		right: 15px;
+		bottom: 30px;
+		/* #ifdef H5 */
+		right: calc(15px + var(--window-right));
+		bottom: calc(30px + var(--window-bottom));
+		/* #endif */
+	}
+
+	.uni-fab__circle--rightTop {
+		right: 15px;
+		top: 30px;
+		/* #ifdef H5 */
+		right: calc(15px + var(--window-right));
+		top: calc(30px + var(--window-top));
+		/* #endif */
+	}
+
+	.uni-fab__circle--left {
+		left: 0;
+	}
+
+	.uni-fab__circle--right {
+		right: 0;
+	}
+
+	.uni-fab__circle--top {
+		top: 0;
+	}
+
+	.uni-fab__circle--bottom {
+		bottom: 0;
+	}
+
+	.uni-fab__plus {
+		font-weight: bold;
+	}
+
+	// .fab-circle-v {
+	// 	position: absolute;
+	// 	width: 2px;
+	// 	height: 24px;
+	// 	left: 0;
+	// 	top: 0;
+	// 	right: 0;
+	// 	bottom: 0;
+	// 	/* #ifndef APP-NVUE */
+	// 	margin: auto;
+	// 	/* #endif */
+	// 	background-color: white;
+	// 	transform: rotate(0deg);
+	// 	transition: transform 0.3s;
+	// }
+
+	// .fab-circle-h {
+	// 	position: absolute;
+	// 	width: 24px;
+	// 	height: 2px;
+	// 	left: 0;
+	// 	top: 0;
+	// 	right: 0;
+	// 	bottom: 0;
+	// 	/* #ifndef APP-NVUE */
+	// 	margin: auto;
+	// 	/* #endif */
+	// 	background-color: white;
+	// 	transform: rotate(0deg);
+	// 	transition: transform 0.3s;
+	// }
+
+	.fab-circle-icon {
+		transform: rotate(0deg);
+		transition: transform 0.3s;
+		font-weight: 200;
+	}
+
+	.uni-fab__plus--active {
+		transform: rotate(135deg);
+	}
+
+	.uni-fab__content {
+		/* #ifndef APP-NVUE */
+		box-sizing: border-box;
+		display: flex;
+		/* #endif */
+		flex-direction: row;
+		border-radius: 55px;
+		overflow: hidden;
+		transition-property: width, height;
+		transition-duration: 0.2s;
+		width: 55px;
+		border-color: #DDDDDD;
+		border-width: 1rpx;
+		border-style: solid;
+	}
+
+	.uni-fab__content--other-platform {
+		border-width: 0px;
+		box-shadow: $uni-shadow-base;
+	}
+
+	.uni-fab__content--left {
+		justify-content: flex-start;
+	}
+
+	.uni-fab__content--right {
+		justify-content: flex-end;
+	}
+
+	.uni-fab__content--flexDirection {
+		flex-direction: column;
+		justify-content: flex-end;
+	}
+
+	.uni-fab__content--flexDirectionStart {
+		flex-direction: column;
+		justify-content: flex-start;
+	}
+
+	.uni-fab__content--flexDirectionEnd {
+		flex-direction: column;
+		justify-content: flex-end;
+	}
+
+	.uni-fab__item {
+		/* #ifndef APP-NVUE */
+		display: flex;
+		/* #endif */
+		flex-direction: column;
+		justify-content: center;
+		align-items: center;
+		width: 55px;
+		height: 55px;
+		opacity: 0;
+		transition: opacity 0.2s;
+	}
+
+	.uni-fab__item--active {
+		opacity: 1;
+	}
+
+	.uni-fab__item-image {
+		width: 20px;
+		height: 20px;
+		margin-bottom: 4px;
+	}
+
+	.uni-fab__item-text {
+		color: #FFFFFF;
+		font-size: 12px;
+		line-height: 12px;
+		margin-top: 2px;
+	}
+
+	.uni-fab__item--first {
+		width: 55px;
+	}
+</style>

+ 87 - 0
uni_modules/uni-fab/package.json

xqd
@@ -0,0 +1,87 @@
+{
+  "id": "uni-fab",
+  "displayName": "uni-fab 悬浮按钮",
+  "version": "1.2.2",
+  "description": "悬浮按钮 fab button ,点击可展开一个图标按钮菜单。",
+  "keywords": [
+    "uni-ui",
+    "uniui",
+    "按钮",
+    "悬浮按钮",
+    "fab"
+],
+  "repository": "https://github.com/dcloudio/uni-ui",
+  "engines": {
+    "HBuilderX": ""
+  },
+  "directories": {
+    "example": "../../temps/example_temps"
+  },
+  "dcloudext": {
+    "category": [
+      "前端组件",
+      "通用组件"
+    ],
+    "sale": {
+      "regular": {
+        "price": "0.00"
+      },
+      "sourcecode": {
+        "price": "0.00"
+      }
+    },
+    "contact": {
+      "qq": ""
+    },
+    "declaration": {
+      "ads": "无",
+      "data": "无",
+      "permissions": "无"
+    },
+    "npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui"
+  },
+  "uni_modules": {
+    "dependencies": ["uni-scss","uni-icons"],
+    "encrypt": [],
+    "platforms": {
+      "cloud": {
+        "tcb": "y",
+        "aliyun": "y"
+      },
+      "client": {
+        "App": {
+          "app-vue": "y",
+          "app-nvue": "y"
+        },
+        "H5-mobile": {
+          "Safari": "y",
+          "Android Browser": "y",
+          "微信浏览器(Android)": "y",
+          "QQ浏览器(Android)": "y"
+        },
+        "H5-pc": {
+          "Chrome": "y",
+          "IE": "y",
+          "Edge": "y",
+          "Firefox": "y",
+          "Safari": "y"
+        },
+        "小程序": {
+          "微信": "y",
+          "阿里": "y",
+          "百度": "y",
+          "字节跳动": "y",
+          "QQ": "y"
+        },
+        "快应用": {
+          "华为": "u",
+          "联盟": "u"
+        },
+        "Vue": {
+            "vue2": "y",
+            "vue3": "y"
+        }
+      }
+    }
+  }
+}

+ 9 - 0
uni_modules/uni-fab/readme.md

xqd
@@ -0,0 +1,9 @@
+## Fab 悬浮按钮
+> **组件名:uni-fab**
+> 代码块: `uFab`
+
+
+点击可展开一个图形按钮菜单
+
+### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-fab)
+#### 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839 

+ 22 - 0
uni_modules/uni-icons/changelog.md

xqd
@@ -0,0 +1,22 @@
+## 1.3.5(2022-01-24)
+- 优化 size 属性可以传入不带单位的字符串数值
+## 1.3.4(2022-01-24)
+- 优化 size 支持其他单位
+## 1.3.3(2022-01-17)
+- 修复 nvue 有些图标不显示的bug,兼容老版本图标
+## 1.3.2(2021-12-01)
+- 优化 示例可复制图标名称
+## 1.3.1(2021-11-23)
+- 优化 兼容旧组件 type 值
+## 1.3.0(2021-11-19)
+- 新增 更多图标
+- 优化 自定义图标使用方式
+- 优化 组件UI,并提供设计资源,详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource)
+- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-icons](https://uniapp.dcloud.io/component/uniui/uni-icons)
+## 1.1.7(2021-11-08)
+## 1.2.0(2021-07-30)
+- 组件兼容 vue3,如何创建vue3项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834)
+## 1.1.5(2021-05-12)
+- 新增 组件示例地址
+## 1.1.4(2021-02-05)
+- 调整为uni_modules目录规范

+ 1169 - 0
uni_modules/uni-icons/components/uni-icons/icons.js

xqd
@@ -0,0 +1,1169 @@
+export default {
+  "id": "2852637",
+  "name": "uniui图标库",
+  "font_family": "uniicons",
+  "css_prefix_text": "uniui-",
+  "description": "",
+  "glyphs": [
+    {
+      "icon_id": "25027049",
+      "name": "yanse",
+      "font_class": "color",
+      "unicode": "e6cf",
+      "unicode_decimal": 59087
+    },
+    {
+      "icon_id": "25027048",
+      "name": "wallet",
+      "font_class": "wallet",
+      "unicode": "e6b1",
+      "unicode_decimal": 59057
+    },
+    {
+      "icon_id": "25015720",
+      "name": "settings-filled",
+      "font_class": "settings-filled",
+      "unicode": "e6ce",
+      "unicode_decimal": 59086
+    },
+    {
+      "icon_id": "25015434",
+      "name": "shimingrenzheng-filled",
+      "font_class": "auth-filled",
+      "unicode": "e6cc",
+      "unicode_decimal": 59084
+    },
+    {
+      "icon_id": "24934246",
+      "name": "shop-filled",
+      "font_class": "shop-filled",
+      "unicode": "e6cd",
+      "unicode_decimal": 59085
+    },
+    {
+      "icon_id": "24934159",
+      "name": "staff-filled-01",
+      "font_class": "staff-filled",
+      "unicode": "e6cb",
+      "unicode_decimal": 59083
+    },
+    {
+      "icon_id": "24932461",
+      "name": "VIP-filled",
+      "font_class": "vip-filled",
+      "unicode": "e6c6",
+      "unicode_decimal": 59078
+    },
+    {
+      "icon_id": "24932462",
+      "name": "plus_circle_fill",
+      "font_class": "plus-filled",
+      "unicode": "e6c7",
+      "unicode_decimal": 59079
+    },
+    {
+      "icon_id": "24932463",
+      "name": "folder_add-filled",
+      "font_class": "folder-add-filled",
+      "unicode": "e6c8",
+      "unicode_decimal": 59080
+    },
+    {
+      "icon_id": "24932464",
+      "name": "yanse-filled",
+      "font_class": "color-filled",
+      "unicode": "e6c9",
+      "unicode_decimal": 59081
+    },
+    {
+      "icon_id": "24932465",
+      "name": "tune-filled",
+      "font_class": "tune-filled",
+      "unicode": "e6ca",
+      "unicode_decimal": 59082
+    },
+    {
+      "icon_id": "24932455",
+      "name": "a-rilidaka-filled",
+      "font_class": "calendar-filled",
+      "unicode": "e6c0",
+      "unicode_decimal": 59072
+    },
+    {
+      "icon_id": "24932456",
+      "name": "notification-filled",
+      "font_class": "notification-filled",
+      "unicode": "e6c1",
+      "unicode_decimal": 59073
+    },
+    {
+      "icon_id": "24932457",
+      "name": "wallet-filled",
+      "font_class": "wallet-filled",
+      "unicode": "e6c2",
+      "unicode_decimal": 59074
+    },
+    {
+      "icon_id": "24932458",
+      "name": "paihangbang-filled",
+      "font_class": "medal-filled",
+      "unicode": "e6c3",
+      "unicode_decimal": 59075
+    },
+    {
+      "icon_id": "24932459",
+      "name": "gift-filled",
+      "font_class": "gift-filled",
+      "unicode": "e6c4",
+      "unicode_decimal": 59076
+    },
+    {
+      "icon_id": "24932460",
+      "name": "fire-filled",
+      "font_class": "fire-filled",
+      "unicode": "e6c5",
+      "unicode_decimal": 59077
+    },
+    {
+      "icon_id": "24928001",
+      "name": "refreshempty",
+      "font_class": "refreshempty",
+      "unicode": "e6bf",
+      "unicode_decimal": 59071
+    },
+    {
+      "icon_id": "24926853",
+      "name": "location-ellipse",
+      "font_class": "location-filled",
+      "unicode": "e6af",
+      "unicode_decimal": 59055
+    },
+    {
+      "icon_id": "24926735",
+      "name": "person-filled",
+      "font_class": "person-filled",
+      "unicode": "e69d",
+      "unicode_decimal": 59037
+    },
+    {
+      "icon_id": "24926703",
+      "name": "personadd-filled",
+      "font_class": "personadd-filled",
+      "unicode": "e698",
+      "unicode_decimal": 59032
+    },
+    {
+      "icon_id": "24923351",
+      "name": "back",
+      "font_class": "back",
+      "unicode": "e6b9",
+      "unicode_decimal": 59065
+    },
+    {
+      "icon_id": "24923352",
+      "name": "forward",
+      "font_class": "forward",
+      "unicode": "e6ba",
+      "unicode_decimal": 59066
+    },
+    {
+      "icon_id": "24923353",
+      "name": "arrowthinright",
+      "font_class": "arrow-right",
+      "unicode": "e6bb",
+      "unicode_decimal": 59067
+    },
+		{
+		  "icon_id": "24923353",
+		  "name": "arrowthinright",
+		  "font_class": "arrowthinright",
+		  "unicode": "e6bb",
+		  "unicode_decimal": 59067
+		},
+    {
+      "icon_id": "24923354",
+      "name": "arrowthinleft",
+      "font_class": "arrow-left",
+      "unicode": "e6bc",
+      "unicode_decimal": 59068
+    },
+		{
+		  "icon_id": "24923354",
+		  "name": "arrowthinleft",
+		  "font_class": "arrowthinleft",
+		  "unicode": "e6bc",
+		  "unicode_decimal": 59068
+		},
+    {
+      "icon_id": "24923355",
+      "name": "arrowthinup",
+      "font_class": "arrow-up",
+      "unicode": "e6bd",
+      "unicode_decimal": 59069
+    },
+		{
+		  "icon_id": "24923355",
+		  "name": "arrowthinup",
+		  "font_class": "arrowthinup",
+		  "unicode": "e6bd",
+		  "unicode_decimal": 59069
+		},
+    {
+      "icon_id": "24923356",
+      "name": "arrowthindown",
+      "font_class": "arrow-down",
+      "unicode": "e6be",
+      "unicode_decimal": 59070
+    },{
+      "icon_id": "24923356",
+      "name": "arrowthindown",
+      "font_class": "arrowthindown",
+      "unicode": "e6be",
+      "unicode_decimal": 59070
+    },
+    {
+      "icon_id": "24923349",
+      "name": "arrowdown",
+      "font_class": "bottom",
+      "unicode": "e6b8",
+      "unicode_decimal": 59064
+    },{
+      "icon_id": "24923349",
+      "name": "arrowdown",
+      "font_class": "arrowdown",
+      "unicode": "e6b8",
+      "unicode_decimal": 59064
+    },
+    {
+      "icon_id": "24923346",
+      "name": "arrowright",
+      "font_class": "right",
+      "unicode": "e6b5",
+      "unicode_decimal": 59061
+    },
+		{
+		  "icon_id": "24923346",
+		  "name": "arrowright",
+		  "font_class": "arrowright",
+		  "unicode": "e6b5",
+		  "unicode_decimal": 59061
+		},
+    {
+      "icon_id": "24923347",
+      "name": "arrowup",
+      "font_class": "top",
+      "unicode": "e6b6",
+      "unicode_decimal": 59062
+    },
+		{
+		  "icon_id": "24923347",
+		  "name": "arrowup",
+		  "font_class": "arrowup",
+		  "unicode": "e6b6",
+		  "unicode_decimal": 59062
+		},
+    {
+      "icon_id": "24923348",
+      "name": "arrowleft",
+      "font_class": "left",
+      "unicode": "e6b7",
+      "unicode_decimal": 59063
+    },
+		{
+		  "icon_id": "24923348",
+		  "name": "arrowleft",
+		  "font_class": "arrowleft",
+		  "unicode": "e6b7",
+		  "unicode_decimal": 59063
+		},
+    {
+      "icon_id": "24923334",
+      "name": "eye",
+      "font_class": "eye",
+      "unicode": "e651",
+      "unicode_decimal": 58961
+    },
+    {
+      "icon_id": "24923335",
+      "name": "eye-filled",
+      "font_class": "eye-filled",
+      "unicode": "e66a",
+      "unicode_decimal": 58986
+    },
+    {
+      "icon_id": "24923336",
+      "name": "eye-slash",
+      "font_class": "eye-slash",
+      "unicode": "e6b3",
+      "unicode_decimal": 59059
+    },
+    {
+      "icon_id": "24923337",
+      "name": "eye-slash-filled",
+      "font_class": "eye-slash-filled",
+      "unicode": "e6b4",
+      "unicode_decimal": 59060
+    },
+    {
+      "icon_id": "24923305",
+      "name": "info-filled",
+      "font_class": "info-filled",
+      "unicode": "e649",
+      "unicode_decimal": 58953
+    },
+    {
+      "icon_id": "24923299",
+      "name": "reload-01",
+      "font_class": "reload",
+      "unicode": "e6b2",
+      "unicode_decimal": 59058
+    },
+    {
+      "icon_id": "24923195",
+      "name": "mic_slash_fill",
+      "font_class": "micoff-filled",
+      "unicode": "e6b0",
+      "unicode_decimal": 59056
+    },
+    {
+      "icon_id": "24923165",
+      "name": "map-pin-ellipse",
+      "font_class": "map-pin-ellipse",
+      "unicode": "e6ac",
+      "unicode_decimal": 59052
+    },
+    {
+      "icon_id": "24923166",
+      "name": "map-pin",
+      "font_class": "map-pin",
+      "unicode": "e6ad",
+      "unicode_decimal": 59053
+    },
+    {
+      "icon_id": "24923167",
+      "name": "location",
+      "font_class": "location",
+      "unicode": "e6ae",
+      "unicode_decimal": 59054
+    },
+    {
+      "icon_id": "24923064",
+      "name": "starhalf",
+      "font_class": "starhalf",
+      "unicode": "e683",
+      "unicode_decimal": 59011
+    },
+    {
+      "icon_id": "24923065",
+      "name": "star",
+      "font_class": "star",
+      "unicode": "e688",
+      "unicode_decimal": 59016
+    },
+    {
+      "icon_id": "24923066",
+      "name": "star-filled",
+      "font_class": "star-filled",
+      "unicode": "e68f",
+      "unicode_decimal": 59023
+    },
+    {
+      "icon_id": "24899646",
+      "name": "a-rilidaka",
+      "font_class": "calendar",
+      "unicode": "e6a0",
+      "unicode_decimal": 59040
+    },
+    {
+      "icon_id": "24899647",
+      "name": "fire",
+      "font_class": "fire",
+      "unicode": "e6a1",
+      "unicode_decimal": 59041
+    },
+    {
+      "icon_id": "24899648",
+      "name": "paihangbang",
+      "font_class": "medal",
+      "unicode": "e6a2",
+      "unicode_decimal": 59042
+    },
+    {
+      "icon_id": "24899649",
+      "name": "font",
+      "font_class": "font",
+      "unicode": "e6a3",
+      "unicode_decimal": 59043
+    },
+    {
+      "icon_id": "24899650",
+      "name": "gift",
+      "font_class": "gift",
+      "unicode": "e6a4",
+      "unicode_decimal": 59044
+    },
+    {
+      "icon_id": "24899651",
+      "name": "link",
+      "font_class": "link",
+      "unicode": "e6a5",
+      "unicode_decimal": 59045
+    },
+    {
+      "icon_id": "24899652",
+      "name": "notification",
+      "font_class": "notification",
+      "unicode": "e6a6",
+      "unicode_decimal": 59046
+    },
+    {
+      "icon_id": "24899653",
+      "name": "staff",
+      "font_class": "staff",
+      "unicode": "e6a7",
+      "unicode_decimal": 59047
+    },
+    {
+      "icon_id": "24899654",
+      "name": "VIP",
+      "font_class": "vip",
+      "unicode": "e6a8",
+      "unicode_decimal": 59048
+    },
+    {
+      "icon_id": "24899655",
+      "name": "folder_add",
+      "font_class": "folder-add",
+      "unicode": "e6a9",
+      "unicode_decimal": 59049
+    },
+    {
+      "icon_id": "24899656",
+      "name": "tune",
+      "font_class": "tune",
+      "unicode": "e6aa",
+      "unicode_decimal": 59050
+    },
+    {
+      "icon_id": "24899657",
+      "name": "shimingrenzheng",
+      "font_class": "auth",
+      "unicode": "e6ab",
+      "unicode_decimal": 59051
+    },
+    {
+      "icon_id": "24899565",
+      "name": "person",
+      "font_class": "person",
+      "unicode": "e699",
+      "unicode_decimal": 59033
+    },
+    {
+      "icon_id": "24899566",
+      "name": "email-filled",
+      "font_class": "email-filled",
+      "unicode": "e69a",
+      "unicode_decimal": 59034
+    },
+    {
+      "icon_id": "24899567",
+      "name": "phone-filled",
+      "font_class": "phone-filled",
+      "unicode": "e69b",
+      "unicode_decimal": 59035
+    },
+    {
+      "icon_id": "24899568",
+      "name": "phone",
+      "font_class": "phone",
+      "unicode": "e69c",
+      "unicode_decimal": 59036
+    },
+    {
+      "icon_id": "24899570",
+      "name": "email",
+      "font_class": "email",
+      "unicode": "e69e",
+      "unicode_decimal": 59038
+    },
+    {
+      "icon_id": "24899571",
+      "name": "personadd",
+      "font_class": "personadd",
+      "unicode": "e69f",
+      "unicode_decimal": 59039
+    },
+    {
+      "icon_id": "24899558",
+      "name": "chatboxes-filled",
+      "font_class": "chatboxes-filled",
+      "unicode": "e692",
+      "unicode_decimal": 59026
+    },
+    {
+      "icon_id": "24899559",
+      "name": "contact",
+      "font_class": "contact",
+      "unicode": "e693",
+      "unicode_decimal": 59027
+    },
+    {
+      "icon_id": "24899560",
+      "name": "chatbubble-filled",
+      "font_class": "chatbubble-filled",
+      "unicode": "e694",
+      "unicode_decimal": 59028
+    },
+    {
+      "icon_id": "24899561",
+      "name": "contact-filled",
+      "font_class": "contact-filled",
+      "unicode": "e695",
+      "unicode_decimal": 59029
+    },
+    {
+      "icon_id": "24899562",
+      "name": "chatboxes",
+      "font_class": "chatboxes",
+      "unicode": "e696",
+      "unicode_decimal": 59030
+    },
+    {
+      "icon_id": "24899563",
+      "name": "chatbubble",
+      "font_class": "chatbubble",
+      "unicode": "e697",
+      "unicode_decimal": 59031
+    },
+    {
+      "icon_id": "24881290",
+      "name": "upload-filled",
+      "font_class": "upload-filled",
+      "unicode": "e68e",
+      "unicode_decimal": 59022
+    },
+    {
+      "icon_id": "24881292",
+      "name": "upload",
+      "font_class": "upload",
+      "unicode": "e690",
+      "unicode_decimal": 59024
+    },
+    {
+      "icon_id": "24881293",
+      "name": "weixin",
+      "font_class": "weixin",
+      "unicode": "e691",
+      "unicode_decimal": 59025
+    },
+    {
+      "icon_id": "24881274",
+      "name": "compose",
+      "font_class": "compose",
+      "unicode": "e67f",
+      "unicode_decimal": 59007
+    },
+    {
+      "icon_id": "24881275",
+      "name": "qq",
+      "font_class": "qq",
+      "unicode": "e680",
+      "unicode_decimal": 59008
+    },
+    {
+      "icon_id": "24881276",
+      "name": "download-filled",
+      "font_class": "download-filled",
+      "unicode": "e681",
+      "unicode_decimal": 59009
+    },
+    {
+      "icon_id": "24881277",
+      "name": "pengyouquan",
+      "font_class": "pyq",
+      "unicode": "e682",
+      "unicode_decimal": 59010
+    },
+    {
+      "icon_id": "24881279",
+      "name": "sound",
+      "font_class": "sound",
+      "unicode": "e684",
+      "unicode_decimal": 59012
+    },
+    {
+      "icon_id": "24881280",
+      "name": "trash-filled",
+      "font_class": "trash-filled",
+      "unicode": "e685",
+      "unicode_decimal": 59013
+    },
+    {
+      "icon_id": "24881281",
+      "name": "sound-filled",
+      "font_class": "sound-filled",
+      "unicode": "e686",
+      "unicode_decimal": 59014
+    },
+    {
+      "icon_id": "24881282",
+      "name": "trash",
+      "font_class": "trash",
+      "unicode": "e687",
+      "unicode_decimal": 59015
+    },
+    {
+      "icon_id": "24881284",
+      "name": "videocam-filled",
+      "font_class": "videocam-filled",
+      "unicode": "e689",
+      "unicode_decimal": 59017
+    },
+    {
+      "icon_id": "24881285",
+      "name": "spinner-cycle",
+      "font_class": "spinner-cycle",
+      "unicode": "e68a",
+      "unicode_decimal": 59018
+    },
+    {
+      "icon_id": "24881286",
+      "name": "weibo",
+      "font_class": "weibo",
+      "unicode": "e68b",
+      "unicode_decimal": 59019
+    },
+    {
+      "icon_id": "24881288",
+      "name": "videocam",
+      "font_class": "videocam",
+      "unicode": "e68c",
+      "unicode_decimal": 59020
+    },
+    {
+      "icon_id": "24881289",
+      "name": "download",
+      "font_class": "download",
+      "unicode": "e68d",
+      "unicode_decimal": 59021
+    },
+    {
+      "icon_id": "24879601",
+      "name": "help",
+      "font_class": "help",
+      "unicode": "e679",
+      "unicode_decimal": 59001
+    },
+    {
+      "icon_id": "24879602",
+      "name": "navigate-filled",
+      "font_class": "navigate-filled",
+      "unicode": "e67a",
+      "unicode_decimal": 59002
+    },
+    {
+      "icon_id": "24879603",
+      "name": "plusempty",
+      "font_class": "plusempty",
+      "unicode": "e67b",
+      "unicode_decimal": 59003
+    },
+    {
+      "icon_id": "24879604",
+      "name": "smallcircle",
+      "font_class": "smallcircle",
+      "unicode": "e67c",
+      "unicode_decimal": 59004
+    },
+    {
+      "icon_id": "24879605",
+      "name": "minus-filled",
+      "font_class": "minus-filled",
+      "unicode": "e67d",
+      "unicode_decimal": 59005
+    },
+    {
+      "icon_id": "24879606",
+      "name": "micoff",
+      "font_class": "micoff",
+      "unicode": "e67e",
+      "unicode_decimal": 59006
+    },
+    {
+      "icon_id": "24879588",
+      "name": "closeempty",
+      "font_class": "closeempty",
+      "unicode": "e66c",
+      "unicode_decimal": 58988
+    },
+    {
+      "icon_id": "24879589",
+      "name": "clear",
+      "font_class": "clear",
+      "unicode": "e66d",
+      "unicode_decimal": 58989
+    },
+    {
+      "icon_id": "24879590",
+      "name": "navigate",
+      "font_class": "navigate",
+      "unicode": "e66e",
+      "unicode_decimal": 58990
+    },
+    {
+      "icon_id": "24879591",
+      "name": "minus",
+      "font_class": "minus",
+      "unicode": "e66f",
+      "unicode_decimal": 58991
+    },
+    {
+      "icon_id": "24879592",
+      "name": "image",
+      "font_class": "image",
+      "unicode": "e670",
+      "unicode_decimal": 58992
+    },
+    {
+      "icon_id": "24879593",
+      "name": "mic",
+      "font_class": "mic",
+      "unicode": "e671",
+      "unicode_decimal": 58993
+    },
+    {
+      "icon_id": "24879594",
+      "name": "paperplane",
+      "font_class": "paperplane",
+      "unicode": "e672",
+      "unicode_decimal": 58994
+    },
+    {
+      "icon_id": "24879595",
+      "name": "close",
+      "font_class": "close",
+      "unicode": "e673",
+      "unicode_decimal": 58995
+    },
+    {
+      "icon_id": "24879596",
+      "name": "help-filled",
+      "font_class": "help-filled",
+      "unicode": "e674",
+      "unicode_decimal": 58996
+    },
+    {
+      "icon_id": "24879597",
+      "name": "plus-filled",
+      "font_class": "paperplane-filled",
+      "unicode": "e675",
+      "unicode_decimal": 58997
+    },
+    {
+      "icon_id": "24879598",
+      "name": "plus",
+      "font_class": "plus",
+      "unicode": "e676",
+      "unicode_decimal": 58998
+    },
+    {
+      "icon_id": "24879599",
+      "name": "mic-filled",
+      "font_class": "mic-filled",
+      "unicode": "e677",
+      "unicode_decimal": 58999
+    },
+    {
+      "icon_id": "24879600",
+      "name": "image-filled",
+      "font_class": "image-filled",
+      "unicode": "e678",
+      "unicode_decimal": 59000
+    },
+    {
+      "icon_id": "24855900",
+      "name": "locked-filled",
+      "font_class": "locked-filled",
+      "unicode": "e668",
+      "unicode_decimal": 58984
+    },
+    {
+      "icon_id": "24855901",
+      "name": "info",
+      "font_class": "info",
+      "unicode": "e669",
+      "unicode_decimal": 58985
+    },
+    {
+      "icon_id": "24855903",
+      "name": "locked",
+      "font_class": "locked",
+      "unicode": "e66b",
+      "unicode_decimal": 58987
+    },
+    {
+      "icon_id": "24855884",
+      "name": "camera-filled",
+      "font_class": "camera-filled",
+      "unicode": "e658",
+      "unicode_decimal": 58968
+    },
+    {
+      "icon_id": "24855885",
+      "name": "chat-filled",
+      "font_class": "chat-filled",
+      "unicode": "e659",
+      "unicode_decimal": 58969
+    },
+    {
+      "icon_id": "24855886",
+      "name": "camera",
+      "font_class": "camera",
+      "unicode": "e65a",
+      "unicode_decimal": 58970
+    },
+    {
+      "icon_id": "24855887",
+      "name": "circle",
+      "font_class": "circle",
+      "unicode": "e65b",
+      "unicode_decimal": 58971
+    },
+    {
+      "icon_id": "24855888",
+      "name": "checkmarkempty",
+      "font_class": "checkmarkempty",
+      "unicode": "e65c",
+      "unicode_decimal": 58972
+    },
+    {
+      "icon_id": "24855889",
+      "name": "chat",
+      "font_class": "chat",
+      "unicode": "e65d",
+      "unicode_decimal": 58973
+    },
+    {
+      "icon_id": "24855890",
+      "name": "circle-filled",
+      "font_class": "circle-filled",
+      "unicode": "e65e",
+      "unicode_decimal": 58974
+    },
+    {
+      "icon_id": "24855891",
+      "name": "flag",
+      "font_class": "flag",
+      "unicode": "e65f",
+      "unicode_decimal": 58975
+    },
+    {
+      "icon_id": "24855892",
+      "name": "flag-filled",
+      "font_class": "flag-filled",
+      "unicode": "e660",
+      "unicode_decimal": 58976
+    },
+    {
+      "icon_id": "24855893",
+      "name": "gear-filled",
+      "font_class": "gear-filled",
+      "unicode": "e661",
+      "unicode_decimal": 58977
+    },
+    {
+      "icon_id": "24855894",
+      "name": "home",
+      "font_class": "home",
+      "unicode": "e662",
+      "unicode_decimal": 58978
+    },
+    {
+      "icon_id": "24855895",
+      "name": "home-filled",
+      "font_class": "home-filled",
+      "unicode": "e663",
+      "unicode_decimal": 58979
+    },
+    {
+      "icon_id": "24855896",
+      "name": "gear",
+      "font_class": "gear",
+      "unicode": "e664",
+      "unicode_decimal": 58980
+    },
+    {
+      "icon_id": "24855897",
+      "name": "smallcircle-filled",
+      "font_class": "smallcircle-filled",
+      "unicode": "e665",
+      "unicode_decimal": 58981
+    },
+    {
+      "icon_id": "24855898",
+      "name": "map-filled",
+      "font_class": "map-filled",
+      "unicode": "e666",
+      "unicode_decimal": 58982
+    },
+    {
+      "icon_id": "24855899",
+      "name": "map",
+      "font_class": "map",
+      "unicode": "e667",
+      "unicode_decimal": 58983
+    },
+    {
+      "icon_id": "24855825",
+      "name": "refresh-filled",
+      "font_class": "refresh-filled",
+      "unicode": "e656",
+      "unicode_decimal": 58966
+    },
+    {
+      "icon_id": "24855826",
+      "name": "refresh",
+      "font_class": "refresh",
+      "unicode": "e657",
+      "unicode_decimal": 58967
+    },
+    {
+      "icon_id": "24855808",
+      "name": "cloud-upload",
+      "font_class": "cloud-upload",
+      "unicode": "e645",
+      "unicode_decimal": 58949
+    },
+    {
+      "icon_id": "24855809",
+      "name": "cloud-download-filled",
+      "font_class": "cloud-download-filled",
+      "unicode": "e646",
+      "unicode_decimal": 58950
+    },
+    {
+      "icon_id": "24855810",
+      "name": "cloud-download",
+      "font_class": "cloud-download",
+      "unicode": "e647",
+      "unicode_decimal": 58951
+    },
+    {
+      "icon_id": "24855811",
+      "name": "cloud-upload-filled",
+      "font_class": "cloud-upload-filled",
+      "unicode": "e648",
+      "unicode_decimal": 58952
+    },
+    {
+      "icon_id": "24855813",
+      "name": "redo",
+      "font_class": "redo",
+      "unicode": "e64a",
+      "unicode_decimal": 58954
+    },
+    {
+      "icon_id": "24855814",
+      "name": "images-filled",
+      "font_class": "images-filled",
+      "unicode": "e64b",
+      "unicode_decimal": 58955
+    },
+    {
+      "icon_id": "24855815",
+      "name": "undo-filled",
+      "font_class": "undo-filled",
+      "unicode": "e64c",
+      "unicode_decimal": 58956
+    },
+    {
+      "icon_id": "24855816",
+      "name": "more",
+      "font_class": "more",
+      "unicode": "e64d",
+      "unicode_decimal": 58957
+    },
+    {
+      "icon_id": "24855817",
+      "name": "more-filled",
+      "font_class": "more-filled",
+      "unicode": "e64e",
+      "unicode_decimal": 58958
+    },
+    {
+      "icon_id": "24855818",
+      "name": "undo",
+      "font_class": "undo",
+      "unicode": "e64f",
+      "unicode_decimal": 58959
+    },
+    {
+      "icon_id": "24855819",
+      "name": "images",
+      "font_class": "images",
+      "unicode": "e650",
+      "unicode_decimal": 58960
+    },
+    {
+      "icon_id": "24855821",
+      "name": "paperclip",
+      "font_class": "paperclip",
+      "unicode": "e652",
+      "unicode_decimal": 58962
+    },
+    {
+      "icon_id": "24855822",
+      "name": "settings",
+      "font_class": "settings",
+      "unicode": "e653",
+      "unicode_decimal": 58963
+    },
+    {
+      "icon_id": "24855823",
+      "name": "search",
+      "font_class": "search",
+      "unicode": "e654",
+      "unicode_decimal": 58964
+    },
+    {
+      "icon_id": "24855824",
+      "name": "redo-filled",
+      "font_class": "redo-filled",
+      "unicode": "e655",
+      "unicode_decimal": 58965
+    },
+    {
+      "icon_id": "24841702",
+      "name": "list",
+      "font_class": "list",
+      "unicode": "e644",
+      "unicode_decimal": 58948
+    },
+    {
+      "icon_id": "24841489",
+      "name": "mail-open-filled",
+      "font_class": "mail-open-filled",
+      "unicode": "e63a",
+      "unicode_decimal": 58938
+    },
+    {
+      "icon_id": "24841491",
+      "name": "hand-thumbsdown-filled",
+      "font_class": "hand-down-filled",
+      "unicode": "e63c",
+      "unicode_decimal": 58940
+    },
+    {
+      "icon_id": "24841492",
+      "name": "hand-thumbsdown",
+      "font_class": "hand-down",
+      "unicode": "e63d",
+      "unicode_decimal": 58941
+    },
+    {
+      "icon_id": "24841493",
+      "name": "hand-thumbsup-filled",
+      "font_class": "hand-up-filled",
+      "unicode": "e63e",
+      "unicode_decimal": 58942
+    },
+    {
+      "icon_id": "24841494",
+      "name": "hand-thumbsup",
+      "font_class": "hand-up",
+      "unicode": "e63f",
+      "unicode_decimal": 58943
+    },
+    {
+      "icon_id": "24841496",
+      "name": "heart-filled",
+      "font_class": "heart-filled",
+      "unicode": "e641",
+      "unicode_decimal": 58945
+    },
+    {
+      "icon_id": "24841498",
+      "name": "mail-open",
+      "font_class": "mail-open",
+      "unicode": "e643",
+      "unicode_decimal": 58947
+    },
+    {
+      "icon_id": "24841488",
+      "name": "heart",
+      "font_class": "heart",
+      "unicode": "e639",
+      "unicode_decimal": 58937
+    },
+    {
+      "icon_id": "24839963",
+      "name": "loop",
+      "font_class": "loop",
+      "unicode": "e633",
+      "unicode_decimal": 58931
+    },
+    {
+      "icon_id": "24839866",
+      "name": "pulldown",
+      "font_class": "pulldown",
+      "unicode": "e632",
+      "unicode_decimal": 58930
+    },
+    {
+      "icon_id": "24813798",
+      "name": "scan",
+      "font_class": "scan",
+      "unicode": "e62a",
+      "unicode_decimal": 58922
+    },
+    {
+      "icon_id": "24813786",
+      "name": "bars",
+      "font_class": "bars",
+      "unicode": "e627",
+      "unicode_decimal": 58919
+    },
+    {
+      "icon_id": "24813788",
+      "name": "cart-filled",
+      "font_class": "cart-filled",
+      "unicode": "e629",
+      "unicode_decimal": 58921
+    },
+    {
+      "icon_id": "24813790",
+      "name": "checkbox",
+      "font_class": "checkbox",
+      "unicode": "e62b",
+      "unicode_decimal": 58923
+    },
+    {
+      "icon_id": "24813791",
+      "name": "checkbox-filled",
+      "font_class": "checkbox-filled",
+      "unicode": "e62c",
+      "unicode_decimal": 58924
+    },
+    {
+      "icon_id": "24813794",
+      "name": "shop",
+      "font_class": "shop",
+      "unicode": "e62f",
+      "unicode_decimal": 58927
+    },
+    {
+      "icon_id": "24813795",
+      "name": "headphones",
+      "font_class": "headphones",
+      "unicode": "e630",
+      "unicode_decimal": 58928
+    },
+    {
+      "icon_id": "24813796",
+      "name": "cart",
+      "font_class": "cart",
+      "unicode": "e631",
+      "unicode_decimal": 58929
+    }
+  ]
+}

+ 96 - 0
uni_modules/uni-icons/components/uni-icons/uni-icons.vue

xqd
@@ -0,0 +1,96 @@
+<template>
+	<!-- #ifdef APP-NVUE -->
+	<text :style="{ color: color, 'font-size': iconSize }" class="uni-icons" @click="_onClick">{{unicode}}</text>
+	<!-- #endif -->
+	<!-- #ifndef APP-NVUE -->
+	<text :style="{ color: color, 'font-size': iconSize }" class="uni-icons" :class="['uniui-'+type,customPrefix,customPrefix?type:'']" @click="_onClick"></text>
+	<!-- #endif -->
+</template>
+
+<script>
+	import icons from './icons.js';
+	const getVal = (val) => {
+		const reg = /^[0-9]*$/g
+		return (typeof val === 'number' || reg.test(val) )? val + 'px' : val;
+	} 
+	// #ifdef APP-NVUE
+	var domModule = weex.requireModule('dom');
+	import iconUrl from './uniicons.ttf'
+	domModule.addRule('fontFace', {
+		'fontFamily': "uniicons",
+		'src': "url('"+iconUrl+"')"
+	});
+	// #endif
+
+	/**
+	 * Icons 图标
+	 * @description 用于展示 icons 图标
+	 * @tutorial https://ext.dcloud.net.cn/plugin?id=28
+	 * @property {Number} size 图标大小
+	 * @property {String} type 图标图案,参考示例
+	 * @property {String} color 图标颜色
+	 * @property {String} customPrefix 自定义图标
+	 * @event {Function} click 点击 Icon 触发事件
+	 */
+	export default {
+		name: 'UniIcons',
+		emits:['click'],
+		props: {
+			type: {
+				type: String,
+				default: ''
+			},
+			color: {
+				type: String,
+				default: '#333333'
+			},
+			size: {
+				type: [Number, String],
+				default: 16
+			},
+			customPrefix:{
+				type: String,
+				default: ''
+			}
+		},
+		data() {
+			return {
+				icons: icons.glyphs
+			}
+		},
+		computed:{
+			unicode(){
+				let code = this.icons.find(v=>v.font_class === this.type)
+				if(code){
+					return unescape(`%u${code.unicode}`)
+				}
+				return ''
+			},
+			iconSize(){
+				return getVal(this.size)
+			}
+		},
+		methods: {
+			_onClick() {
+				this.$emit('click')
+			}
+		}
+	}
+</script>
+
+<style lang="scss">
+	/* #ifndef APP-NVUE */
+	@import './uniicons.css';
+	@font-face {
+		font-family: uniicons;
+		src: url('./uniicons.ttf') format('truetype');
+	}
+
+	/* #endif */
+	.uni-icons {
+		font-family: uniicons;
+		text-decoration: none;
+		text-align: center;
+	}
+
+</style>

+ 663 - 0
uni_modules/uni-icons/components/uni-icons/uniicons.css

xqd
@@ -0,0 +1,663 @@
+.uniui-color:before {
+  content: "\e6cf";
+}
+
+.uniui-wallet:before {
+  content: "\e6b1";
+}
+
+.uniui-settings-filled:before {
+  content: "\e6ce";
+}
+
+.uniui-auth-filled:before {
+  content: "\e6cc";
+}
+
+.uniui-shop-filled:before {
+  content: "\e6cd";
+}
+
+.uniui-staff-filled:before {
+  content: "\e6cb";
+}
+
+.uniui-vip-filled:before {
+  content: "\e6c6";
+}
+
+.uniui-plus-filled:before {
+  content: "\e6c7";
+}
+
+.uniui-folder-add-filled:before {
+  content: "\e6c8";
+}
+
+.uniui-color-filled:before {
+  content: "\e6c9";
+}
+
+.uniui-tune-filled:before {
+  content: "\e6ca";
+}
+
+.uniui-calendar-filled:before {
+  content: "\e6c0";
+}
+
+.uniui-notification-filled:before {
+  content: "\e6c1";
+}
+
+.uniui-wallet-filled:before {
+  content: "\e6c2";
+}
+
+.uniui-medal-filled:before {
+  content: "\e6c3";
+}
+
+.uniui-gift-filled:before {
+  content: "\e6c4";
+}
+
+.uniui-fire-filled:before {
+  content: "\e6c5";
+}
+
+.uniui-refreshempty:before {
+  content: "\e6bf";
+}
+
+.uniui-location-filled:before {
+  content: "\e6af";
+}
+
+.uniui-person-filled:before {
+  content: "\e69d";
+}
+
+.uniui-personadd-filled:before {
+  content: "\e698";
+}
+
+.uniui-back:before {
+  content: "\e6b9";
+}
+
+.uniui-forward:before {
+  content: "\e6ba";
+}
+
+.uniui-arrow-right:before {
+  content: "\e6bb";
+}
+
+.uniui-arrowthinright:before {
+  content: "\e6bb";
+}
+
+.uniui-arrow-left:before {
+  content: "\e6bc";
+}
+
+.uniui-arrowthinleft:before {
+  content: "\e6bc";
+}
+
+.uniui-arrow-up:before {
+  content: "\e6bd";
+}
+
+.uniui-arrowthinup:before {
+  content: "\e6bd";
+}
+
+.uniui-arrow-down:before {
+  content: "\e6be";
+}
+
+.uniui-arrowthindown:before {
+  content: "\e6be";
+}
+
+.uniui-bottom:before {
+  content: "\e6b8";
+}
+
+.uniui-arrowdown:before {
+  content: "\e6b8";
+}
+
+.uniui-right:before {
+  content: "\e6b5";
+}
+
+.uniui-arrowright:before {
+  content: "\e6b5";
+}
+
+.uniui-top:before {
+  content: "\e6b6";
+}
+
+.uniui-arrowup:before {
+  content: "\e6b6";
+}
+
+.uniui-left:before {
+  content: "\e6b7";
+}
+
+.uniui-arrowleft:before {
+  content: "\e6b7";
+}
+
+.uniui-eye:before {
+  content: "\e651";
+}
+
+.uniui-eye-filled:before {
+  content: "\e66a";
+}
+
+.uniui-eye-slash:before {
+  content: "\e6b3";
+}
+
+.uniui-eye-slash-filled:before {
+  content: "\e6b4";
+}
+
+.uniui-info-filled:before {
+  content: "\e649";
+}
+
+.uniui-reload:before {
+  content: "\e6b2";
+}
+
+.uniui-micoff-filled:before {
+  content: "\e6b0";
+}
+
+.uniui-map-pin-ellipse:before {
+  content: "\e6ac";
+}
+
+.uniui-map-pin:before {
+  content: "\e6ad";
+}
+
+.uniui-location:before {
+  content: "\e6ae";
+}
+
+.uniui-starhalf:before {
+  content: "\e683";
+}
+
+.uniui-star:before {
+  content: "\e688";
+}
+
+.uniui-star-filled:before {
+  content: "\e68f";
+}
+
+.uniui-calendar:before {
+  content: "\e6a0";
+}
+
+.uniui-fire:before {
+  content: "\e6a1";
+}
+
+.uniui-medal:before {
+  content: "\e6a2";
+}
+
+.uniui-font:before {
+  content: "\e6a3";
+}
+
+.uniui-gift:before {
+  content: "\e6a4";
+}
+
+.uniui-link:before {
+  content: "\e6a5";
+}
+
+.uniui-notification:before {
+  content: "\e6a6";
+}
+
+.uniui-staff:before {
+  content: "\e6a7";
+}
+
+.uniui-vip:before {
+  content: "\e6a8";
+}
+
+.uniui-folder-add:before {
+  content: "\e6a9";
+}
+
+.uniui-tune:before {
+  content: "\e6aa";
+}
+
+.uniui-auth:before {
+  content: "\e6ab";
+}
+
+.uniui-person:before {
+  content: "\e699";
+}
+
+.uniui-email-filled:before {
+  content: "\e69a";
+}
+
+.uniui-phone-filled:before {
+  content: "\e69b";
+}
+
+.uniui-phone:before {
+  content: "\e69c";
+}
+
+.uniui-email:before {
+  content: "\e69e";
+}
+
+.uniui-personadd:before {
+  content: "\e69f";
+}
+
+.uniui-chatboxes-filled:before {
+  content: "\e692";
+}
+
+.uniui-contact:before {
+  content: "\e693";
+}
+
+.uniui-chatbubble-filled:before {
+  content: "\e694";
+}
+
+.uniui-contact-filled:before {
+  content: "\e695";
+}
+
+.uniui-chatboxes:before {
+  content: "\e696";
+}
+
+.uniui-chatbubble:before {
+  content: "\e697";
+}
+
+.uniui-upload-filled:before {
+  content: "\e68e";
+}
+
+.uniui-upload:before {
+  content: "\e690";
+}
+
+.uniui-weixin:before {
+  content: "\e691";
+}
+
+.uniui-compose:before {
+  content: "\e67f";
+}
+
+.uniui-qq:before {
+  content: "\e680";
+}
+
+.uniui-download-filled:before {
+  content: "\e681";
+}
+
+.uniui-pyq:before {
+  content: "\e682";
+}
+
+.uniui-sound:before {
+  content: "\e684";
+}
+
+.uniui-trash-filled:before {
+  content: "\e685";
+}
+
+.uniui-sound-filled:before {
+  content: "\e686";
+}
+
+.uniui-trash:before {
+  content: "\e687";
+}
+
+.uniui-videocam-filled:before {
+  content: "\e689";
+}
+
+.uniui-spinner-cycle:before {
+  content: "\e68a";
+}
+
+.uniui-weibo:before {
+  content: "\e68b";
+}
+
+.uniui-videocam:before {
+  content: "\e68c";
+}
+
+.uniui-download:before {
+  content: "\e68d";
+}
+
+.uniui-help:before {
+  content: "\e679";
+}
+
+.uniui-navigate-filled:before {
+  content: "\e67a";
+}
+
+.uniui-plusempty:before {
+  content: "\e67b";
+}
+
+.uniui-smallcircle:before {
+  content: "\e67c";
+}
+
+.uniui-minus-filled:before {
+  content: "\e67d";
+}
+
+.uniui-micoff:before {
+  content: "\e67e";
+}
+
+.uniui-closeempty:before {
+  content: "\e66c";
+}
+
+.uniui-clear:before {
+  content: "\e66d";
+}
+
+.uniui-navigate:before {
+  content: "\e66e";
+}
+
+.uniui-minus:before {
+  content: "\e66f";
+}
+
+.uniui-image:before {
+  content: "\e670";
+}
+
+.uniui-mic:before {
+  content: "\e671";
+}
+
+.uniui-paperplane:before {
+  content: "\e672";
+}
+
+.uniui-close:before {
+  content: "\e673";
+}
+
+.uniui-help-filled:before {
+  content: "\e674";
+}
+
+.uniui-paperplane-filled:before {
+  content: "\e675";
+}
+
+.uniui-plus:before {
+  content: "\e676";
+}
+
+.uniui-mic-filled:before {
+  content: "\e677";
+}
+
+.uniui-image-filled:before {
+  content: "\e678";
+}
+
+.uniui-locked-filled:before {
+  content: "\e668";
+}
+
+.uniui-info:before {
+  content: "\e669";
+}
+
+.uniui-locked:before {
+  content: "\e66b";
+}
+
+.uniui-camera-filled:before {
+  content: "\e658";
+}
+
+.uniui-chat-filled:before {
+  content: "\e659";
+}
+
+.uniui-camera:before {
+  content: "\e65a";
+}
+
+.uniui-circle:before {
+  content: "\e65b";
+}
+
+.uniui-checkmarkempty:before {
+  content: "\e65c";
+}
+
+.uniui-chat:before {
+  content: "\e65d";
+}
+
+.uniui-circle-filled:before {
+  content: "\e65e";
+}
+
+.uniui-flag:before {
+  content: "\e65f";
+}
+
+.uniui-flag-filled:before {
+  content: "\e660";
+}
+
+.uniui-gear-filled:before {
+  content: "\e661";
+}
+
+.uniui-home:before {
+  content: "\e662";
+}
+
+.uniui-home-filled:before {
+  content: "\e663";
+}
+
+.uniui-gear:before {
+  content: "\e664";
+}
+
+.uniui-smallcircle-filled:before {
+  content: "\e665";
+}
+
+.uniui-map-filled:before {
+  content: "\e666";
+}
+
+.uniui-map:before {
+  content: "\e667";
+}
+
+.uniui-refresh-filled:before {
+  content: "\e656";
+}
+
+.uniui-refresh:before {
+  content: "\e657";
+}
+
+.uniui-cloud-upload:before {
+  content: "\e645";
+}
+
+.uniui-cloud-download-filled:before {
+  content: "\e646";
+}
+
+.uniui-cloud-download:before {
+  content: "\e647";
+}
+
+.uniui-cloud-upload-filled:before {
+  content: "\e648";
+}
+
+.uniui-redo:before {
+  content: "\e64a";
+}
+
+.uniui-images-filled:before {
+  content: "\e64b";
+}
+
+.uniui-undo-filled:before {
+  content: "\e64c";
+}
+
+.uniui-more:before {
+  content: "\e64d";
+}
+
+.uniui-more-filled:before {
+  content: "\e64e";
+}
+
+.uniui-undo:before {
+  content: "\e64f";
+}
+
+.uniui-images:before {
+  content: "\e650";
+}
+
+.uniui-paperclip:before {
+  content: "\e652";
+}
+
+.uniui-settings:before {
+  content: "\e653";
+}
+
+.uniui-search:before {
+  content: "\e654";
+}
+
+.uniui-redo-filled:before {
+  content: "\e655";
+}
+
+.uniui-list:before {
+  content: "\e644";
+}
+
+.uniui-mail-open-filled:before {
+  content: "\e63a";
+}
+
+.uniui-hand-down-filled:before {
+  content: "\e63c";
+}
+
+.uniui-hand-down:before {
+  content: "\e63d";
+}
+
+.uniui-hand-up-filled:before {
+  content: "\e63e";
+}
+
+.uniui-hand-up:before {
+  content: "\e63f";
+}
+
+.uniui-heart-filled:before {
+  content: "\e641";
+}
+
+.uniui-mail-open:before {
+  content: "\e643";
+}
+
+.uniui-heart:before {
+  content: "\e639";
+}
+
+.uniui-loop:before {
+  content: "\e633";
+}
+
+.uniui-pulldown:before {
+  content: "\e632";
+}
+
+.uniui-scan:before {
+  content: "\e62a";
+}
+
+.uniui-bars:before {
+  content: "\e627";
+}
+
+.uniui-cart-filled:before {
+  content: "\e629";
+}
+
+.uniui-checkbox:before {
+  content: "\e62b";
+}
+
+.uniui-checkbox-filled:before {
+  content: "\e62c";
+}
+
+.uniui-shop:before {
+  content: "\e62f";
+}
+
+.uniui-headphones:before {
+  content: "\e630";
+}
+
+.uniui-cart:before {
+  content: "\e631";
+}

BIN=BIN
uni_modules/uni-icons/components/uni-icons/uniicons.ttf


+ 86 - 0
uni_modules/uni-icons/package.json

xqd
@@ -0,0 +1,86 @@
+{
+  "id": "uni-icons",
+  "displayName": "uni-icons 图标",
+  "version": "1.3.5",
+  "description": "图标组件,用于展示移动端常见的图标,可自定义颜色、大小。",
+  "keywords": [
+    "uni-ui",
+    "uniui",
+    "icon",
+    "图标"
+],
+  "repository": "https://github.com/dcloudio/uni-ui",
+  "engines": {
+    "HBuilderX": "^3.2.14"
+  },
+  "directories": {
+    "example": "../../temps/example_temps"
+  },
+  "dcloudext": {
+    "category": [
+      "前端组件",
+      "通用组件"
+    ],
+    "sale": {
+      "regular": {
+        "price": "0.00"
+      },
+      "sourcecode": {
+        "price": "0.00"
+      }
+    },
+    "contact": {
+      "qq": ""
+    },
+    "declaration": {
+      "ads": "无",
+      "data": "无",
+      "permissions": "无"
+    },
+    "npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui"
+  },
+  "uni_modules": {
+    "dependencies": ["uni-scss"],
+    "encrypt": [],
+    "platforms": {
+      "cloud": {
+        "tcb": "y",
+        "aliyun": "y"
+      },
+      "client": {
+        "App": {
+          "app-vue": "y",
+          "app-nvue": "y"
+        },
+        "H5-mobile": {
+          "Safari": "y",
+          "Android Browser": "y",
+          "微信浏览器(Android)": "y",
+          "QQ浏览器(Android)": "y"
+        },
+        "H5-pc": {
+          "Chrome": "y",
+          "IE": "y",
+          "Edge": "y",
+          "Firefox": "y",
+          "Safari": "y"
+        },
+        "小程序": {
+          "微信": "y",
+          "阿里": "y",
+          "百度": "y",
+          "字节跳动": "y",
+          "QQ": "y"
+        },
+        "快应用": {
+          "华为": "u",
+          "联盟": "u"
+        },
+        "Vue": {
+            "vue2": "y",
+            "vue3": "y"
+        }
+      }
+    }
+  }
+}

+ 8 - 0
uni_modules/uni-icons/readme.md

xqd
@@ -0,0 +1,8 @@
+## Icons 图标
+> **组件名:uni-icons**
+> 代码块: `uIcons`
+
+用于展示 icons 图标 。
+
+### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-icons)
+#### 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839 

+ 37 - 0
uni_modules/uni-nav-bar/changelog.md

xqd
@@ -0,0 +1,37 @@
+## 1.3.4(2022-01-24)
+- 更新 组件示例
+## 1.3.3(2022-01-24)
+- 新增 left-width/right-width属性 ,可修改左右两侧的宽度
+## 1.3.2(2022-01-18)
+- 修复 在vue下,标题不垂直居中的bug
+## 1.3.1(2022-01-18)
+- 修复 height 属性类型错误
+## 1.3.0(2022-01-18)
+- 新增 height 属性,可修改组件高度
+- 新增 dark 属性可可开启暗黑模式
+- 优化 标题字数过多显示省略号
+- 优化 插槽,插入内容可完全覆盖
+## 1.2.1(2022-01-10)
+- 修复 color 属性不生效的bug
+## 1.2.0(2021-11-19)
+- 优化 组件UI,并提供设计资源,详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource)
+- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-nav-bar](https://uniapp.dcloud.io/component/uniui/uni-nav-bar)
+## 1.1.0(2021-07-30)
+- 组件兼容 vue3,如何创建vue3项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834)
+## 1.0.11(2021-05-12)
+- 新增 组件示例地址
+## 1.0.10(2021-04-30)
+- 修复 在nvue下fixed为true,宽度不能撑满的Bug
+## 1.0.9(2021-04-21)
+- 优化 添加依赖 uni-icons, 导入后自动下载依赖
+## 1.0.8(2021-04-14)
+- uni-ui 修复 uni-nav-bar 当 fixed 属性为 true 时铺不满屏幕的 bug
+
+## 1.0.7(2021-02-25)
+- 修复 easycom 下,找不到 uni-status-bar 的bug
+
+## 1.0.6(2021-02-05)
+- 优化 组件引用关系,通过uni_modules引用组件
+
+## 1.0.5(2021-02-05)
+- 调整为uni_modules目录规范

Algúns arquivos non se mostraron porque demasiados arquivos cambiaron neste cambio