lhl 2 年之前
當前提交
f54de984d9
共有 100 個文件被更改,包括 10197 次插入0 次删除
  1. 二進制
      .DS_Store
  2. 2 0
      .gitignore
  3. 16 0
      .hbuilderx/launch.json
  4. 13 0
      1.html
  5. 二進制
      3a58dbfd0e0f1cbdf0c5bbf941934920.keystore
  6. 56 0
      App.vue
  7. 36 0
      README.en.md
  8. 1 0
      README.md
  9. 177 0
      api/Socket.js
  10. 251 0
      api/assets.js
  11. 25 0
      api/college.js
  12. 126 0
      api/contract.js
  13. 17 0
      api/convert.js
  14. 16 0
      api/donation.js
  15. 41 0
      api/exchange.js
  16. 25 0
      api/home.js
  17. 56 0
      api/market.js
  18. 137 0
      api/member.js
  19. 115 0
      api/option.js
  20. 95 0
      api/order.js
  21. 56 0
      api/otc.js
  22. 35 0
      api/pledge.js
  23. 73 0
      api/profile.js
  24. 101 0
      api/record.js
  25. 31 0
      api/resIntercept.js
  26. 247 0
      api/serve/index.js
  27. 200 0
      api/serve/market-socket.js
  28. 62 0
      api/serve/webaxios.js
  29. 197 0
      api/setting.js
  30. 29 0
      api/subscride.js
  31. 172 0
      api/wallet.js
  32. 73 0
      app.js
  33. 二進制
      assets/fontIcon/iconfont.eot
  34. 43 0
      assets/fontIcon/iconfont.svg
  35. 二進制
      assets/fontIcon/iconfont.ttf
  36. 二進制
      assets/fontIcon/iconfont.woff
  37. 二進制
      assets/fontIcon/iconfont.woff2
  38. 二進制
      assets/img/7coin_qidongye.gif
  39. 二進制
      assets/img/Bitmap3x.png
  40. 二進制
      assets/img/Fill13x.png
  41. 二進制
      assets/img/Upload_File3x.png
  42. 二進制
      assets/img/auth_fanmian.png
  43. 二進制
      assets/img/auth_shouchi.png
  44. 二進制
      assets/img/auth_zhengmain.png
  45. 二進制
      assets/img/fenzu23x.png
  46. 二進制
      assets/img/fenzu_73x.png
  47. 二進制
      assets/img/initve.png
  48. 二進制
      assets/img/invite-1.png
  49. 二進制
      assets/img/invite-2.png
  50. 二進制
      assets/img/invite-3.png
  51. 二進制
      assets/img/invite-4.png
  52. 二進制
      assets/img/invite-5.png
  53. 二進制
      assets/img/invite-6.png
  54. 二進制
      assets/img/invite-bg.png
  55. 二進制
      assets/img/invite-fy.png
  56. 二進制
      assets/img/invite-sy.png
  57. 二進制
      assets/img/invite-tg.png
  58. 二進制
      assets/img/invite-yq.png
  59. 二進制
      assets/img/shengji.png
  60. 609 0
      assets/scss/app.scss
  61. 3 0
      assets/scss/base.scss
  62. 22 0
      assets/scss/mixin.scss
  63. 23 0
      assets/scss/size.scss
  64. 72 0
      assets/scss/theme.scss
  65. 235 0
      assets/scss/vant.scss
  66. 二進制
      base_info/ww.gsr.keystore
  67. 二進制
      base_info/wwlogo.png
  68. 70 0
      components/bing-progress/bing-progress.css
  69. 727 0
      components/bing-progress/bing-progress.vue
  70. 454 0
      components/lb-picker/README.md
  71. 91 0
      components/lb-picker/index.vue
  72. 46 0
      components/lb-picker/mixins/index.js
  73. 94 0
      components/lb-picker/pickers/multi-selector-picker.vue
  74. 67 0
      components/lb-picker/pickers/selector-picker.vue
  75. 75 0
      components/lb-picker/pickers/unlinked-selector-picker.vue
  76. 23 0
      components/lb-picker/style/picker-item.scss
  77. 179 0
      components/lb-picker/style/picker.scss
  78. 110 0
      components/lb-picker/utils.js
  79. 148 0
      components/uni-badge/uni-badge.vue
  80. 546 0
      components/uni-calendar/calendar.js
  81. 151 0
      components/uni-calendar/uni-calendar-item.vue
  82. 431 0
      components/uni-calendar/uni-calendar.vue
  83. 327 0
      components/uni-calendar/util.js
  84. 403 0
      components/uni-card/uni-card.vue
  85. 217 0
      components/uni-collapse-item/uni-collapse-item.vue
  86. 59 0
      components/uni-collapse/uni-collapse.vue
  87. 212 0
      components/uni-combox/uni-combox.vue
  88. 200 0
      components/uni-countdown/uni-countdown.vue
  89. 170 0
      components/uni-drawer/uni-drawer.vue
  90. 428 0
      components/uni-fab/uni-fab.vue
  91. 136 0
      components/uni-fav/uni-fav.vue
  92. 230 0
      components/uni-goods-nav/uni-goods-nav.vue
  93. 167 0
      components/uni-grid-item/uni-grid-item copy.vue
  94. 125 0
      components/uni-grid-item/uni-grid-item.vue
  95. 142 0
      components/uni-grid/uni-grid.vue
  96. 132 0
      components/uni-icons/icons.js
  97. 10 0
      components/uni-icons/uni-icons.vue
  98. 142 0
      components/uni-indexed-list/uni-indexed-list-item.vue
  99. 317 0
      components/uni-indexed-list/uni-indexed-list.vue
  100. 80 0
      components/uni-link/uni-link.vue

二進制
.DS_Store


+ 2 - 0
.gitignore

@@ -0,0 +1,2 @@
+dist
+unpackage

+ 16 - 0
.hbuilderx/launch.json

@@ -0,0 +1,16 @@
+{ // 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"
+     	},
+     	"type" : "uniCloud"
+     }
+    ]
+}

+ 13 - 0
1.html

@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+	<meta charset="UTF-8">
+	<meta name="viewport" content="width=device-width, initial-scale=1.0">
+	<meta http-equiv="X-UA-Compatible" content="ie=edge">
+	<meta name="apple-mobile-web-app-capable" content="yes" /> 
+	<title></title>
+</head>
+<body>
+	
+</body>
+</html>

二進制
3a58dbfd0e0f1cbdf0c5bbf941934920.keystore


+ 56 - 0
App.vue

@@ -0,0 +1,56 @@
+<script>
+import Member from "@/api/member";
+import { mapActions } from "vuex";
+export default {
+  onLaunch: function () {
+    setInterval(() => {
+      this.$navFontColor();
+    }, 1000);
+    this.mobileLogo();
+  },
+  methods: {
+    ...mapActions({
+      setLogoMap: "logoMap",
+    }),
+    mobileLogo() {
+      Member.mobileLogo().then((res) => {
+        let data = res.data;
+        this.setLogoMap({
+          home_logo: data.home_logo,
+          login_logo: data.login_logo,
+          title_logo: data.title_logo,
+          share_logo: data.share_logo,
+          name: data.name,
+        });
+      });
+    },
+  },
+
+  onShow: function () {
+    //uni.$emit("appShow");
+    this.$navFontColor();
+  },
+  onHide: function () {},
+};
+</script>
+
+<style lang="scss">
+.layout-page {
+  height: auto;
+  min-height: 100vh;
+  font-size: 14px;
+  background: $panel-1;
+  color: $text-color;
+}
+@import "./assets/scss/app.scss";
+/* 解决头条小程序组件内引入字体不生效的问题 */
+/* #ifdef MP-TOUTIAO */
+@font-face {
+  font-family: uniicons;
+  src: url("/static/uni.ttf");
+}
+/* #endif */
+.flex-1 {
+    flex: 1;
+}
+</style>

+ 36 - 0
README.en.md

@@ -0,0 +1,36 @@
+# h5.GSR.name
+
+#### 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/)

+ 1 - 0
README.md

@@ -0,0 +1 @@
+初始化

+ 177 - 0
api/Socket.js

@@ -0,0 +1,177 @@
+class Socket {
+    constructor(link) {
+        // 初始化socket
+        if (link.constructor === WebSocket) {
+            this.socket = link;
+        } else {
+            this.socket = new WebSocket(link);
+        }
+
+        // this.socket.binaryType = 'arraybuffer';
+
+        this.doOpen();
+
+        // 连接状态的标识符
+        this.readyState = this.socket.readyState;
+
+        // 订阅/发布模型
+        this._events = {
+            // 订阅的事件 : 发布的方法
+
+        };
+
+        // 定时验证的标识符
+        this.heartBeatTimer = null;
+
+    }
+
+    // 执行socket并发布事件
+    doOpen() {
+
+        this.afterOpenEmit = [];
+
+        // 执行socket连接 并初始化验证请求
+        this.socket.addEventListener("open", evt => this.onOpen(evt));
+
+        // 接收socket数据
+        this.socket.addEventListener("message", evt => this.onMessage(evt));
+
+        // 关闭socket连接
+        this.socket.addEventListener("close", evt => this.onClose(evt));
+
+        // 请求发生错误
+        this.socket.addEventListener("error", err => this.onError(err));
+
+    }
+
+    // 发布后通知订阅者
+    Notify(entry) {
+        // 检查是否有订阅者 返回队列
+        const cbQueue = this._events[entry.Event];
+        if (cbQueue && cbQueue.length) {
+            for (let callback of cbQueue) {
+                if (callback instanceof Function) callback(entry.Data);
+            }
+        }
+    }
+
+    // 请求数据的方法
+    onOpen(evt) {
+
+        // 每隔20s检查连接
+        // this.heartBeatTimer = setInterval(() => this.send({
+        //     'cmd': 'ping',
+        //     'args': ''
+        // }), 20000);
+
+        // 通知订阅
+        this.Notify({Event: 'open', Data : evt});
+    }
+
+    /**
+     * 订阅所有的数据
+     * @param {array|object} datas 订阅参数集合
+     */
+    send(datas) {
+        if (datas.constructor != Array) {
+			datas = [datas];
+        }
+        
+        for (let item of datas) {
+            this.socket.send(JSON.stringify(item));
+        }
+    }
+
+
+    onMessage(evt) {
+
+        try {
+
+            // 解析推送的数据
+            const data = JSON.parse(evt.data);
+
+            // 通知订阅者
+            this.Notify({
+                Event: 'message',
+                Data: data
+            });
+
+        } catch (err) {
+            console.error(' >> Data parsing error:', err);
+
+            // 通知订阅者
+            this.Notify({
+                Event: 'error',
+                Data: err
+            });
+        }
+    }
+
+    // 添加事件监听
+    on(name, handler) {
+        this.subscribe(name, handler);
+    }
+
+    // 取消订阅事件
+    off(name, handler) {
+        this.unsubscribe(name, handler);
+    }
+
+    // 订阅事件的方法
+    subscribe(name, handler) {
+        if (this._events[name]) {
+            this._events[name].push(handler); // 追加事件
+        } else {
+            this._events[name] = [handler]; // 添加事件
+        }
+    }
+
+    // 取消订阅事件
+    unsubscribe(name, handler) {
+
+        let start = this._events[name].findIndex(item => item === handler);
+
+        // 删除该事件
+        this._events[name].splice(start, 1);
+
+    }
+
+    checkOpen() {
+        return this.readyState >= 2;
+    }
+
+    onClose(evt) {
+        this.Notify({Event: 'close', Data : evt});
+    }
+
+
+    onError(err) {
+        this.Notify({Event: 'error', Data : err});
+    }
+
+    emit(data) {
+        return new Promise((resolve) => {
+            this.send(JSON.stringify(data));
+            this.on('message', function (data) {
+                resolve(data);
+            });
+        });
+    }
+
+    doClose() {
+        this.socket.close();
+    }
+
+    destroy() {
+        if (this.heartBeatTimer) {
+            clearInterval(this.heartBeatTimer);
+            this.heartBeatTimer = null;
+        }
+        this.doClose();
+        this._events = {};
+        this.readyState = 0;
+        this.socket = null;
+    }
+}
+
+export default Socket

+ 251 - 0
api/assets.js

@@ -0,0 +1,251 @@
+import {$get,$post,$postFile} from '@/api'
+
+
+class Assets {
+    /**
+     * 数字货币提现
+     * @param {Object} data 
+    */
+    static cryptocurrenciesWithdrawal(data) {
+
+        return $post(`/withdraw/cryptocurrenciesWithdrawal`, data);
+        
+    }
+
+
+
+    /**
+     * assets页面
+     * @param {Object} data 
+    */
+    static assets(data) {
+
+        return $post(`/userCoin/assets`, data);
+
+    }
+
+
+    /**
+     * assets页面
+     * @param {Object} data 
+    */
+    static getAllList(data) {
+
+        return $get(`coin/getAllList`, data);
+
+    }
+
+    /**
+     * 币币用户历史委托
+     * @param {Object} data 
+    */
+    static history(data) {
+
+        return $post(`/coin/orders/history`, data);
+
+    }
+
+    /**
+     * 资金划转记录
+     * @param {Object} data 
+    */
+    static fundsTransferRecordPageList(data) {
+
+        return $post(`/fundsTransferRecord/pageList`, data);
+
+    }
+
+    /**
+     * 资金划转
+     * @param {Object} data 
+    */
+    static transfer(data) {
+
+        return $post(`/userCoin/transfer`, data);
+
+    }
+
+    /**
+     * 数字货币充值
+     * @param {int} params 
+    */
+    static cryptocurrenciesRecharge(coinId) {
+
+        return $get(`/recharge/cryptocurrenciesRecharge/${coinId}`);
+
+    }
+
+    /**
+     * 数字货币充值记录
+     * @param {Object} data 
+    */
+    static cryptocurrenciesRechargeRecords(data) {
+
+        return $post(`/recharge/cryptocurrenciesRechargeRecords`, data);
+
+    }
+
+
+    /**
+     * 用户数字货币提现记录
+     * @param {Object} data 
+    */
+    static cryptocurrenciesWithdrawRecords(data) {
+
+        return $post(`/withdraw/cryptocurrenciesWithdrawRecords`, data);
+
+    }
+
+    /**
+     * 删除提现地址
+     * @param {Object} data 
+    */
+    static deleteById(data) {
+
+        return $post(`/withdrawAddress/deleteById`, data);
+
+    }
+
+    /**
+     * 编辑提现地址
+     * @param {Object} data 
+    */
+    static editById(data) {
+
+        return $post(`/withdrawAddress/editById`, data);
+
+    }
+
+    /**
+    * 移除添加地址
+    * @param {Object} data 
+   */
+    static addRemove(data) {
+
+        return $post(`/withdrawAddress/addOrRemoveWhiteList`, data);
+
+    }
+
+
+    /**
+     * 提现地址分页列表
+     * @param {Object} data 
+    */
+    static pageList(data) {
+
+        return $post(`/withdrawAddress/pageList`, data);
+
+    }
+
+
+    /**
+     * 添加提现地址
+     * @param {Object} data 
+    */
+    static save(data) {
+
+        return $post(`/withdrawAddress/save`, data);
+
+    }
+
+
+    /**
+     * 费率列表(手续费)
+     * @param {Object} data 
+    */
+    static getList(data) {
+
+        return $get(`/transferFee/getList`, data);
+
+    }
+
+    /**
+     * 用户合约资金
+     * @param {Object} data 
+    */
+    static contractsAccount(data) {
+
+        return $get(`/futuresUserCoin/contractsAccount`, data);
+
+    }
+    /**
+     * 用户合约资金(详情)
+     * @param {number} accountId 
+    */
+    static contractsAccountDetail(accountId) {
+
+        return $get(`/futuresUserCoin/contractsAccountDetail/${accountId}`);
+
+    }
+
+    /**
+     * 用户资金历史记录
+     * @param {Object} data 
+    */
+    static transactionHistory(data) {
+
+        return $post(`/futuresUserCoin/transactionHistory`, data);
+
+    }
+    /**
+         * 确认是否白名单地址
+         * @param {Object} data 
+        */
+    static checkIsWhiteList(data) {
+
+        return $post(`/withdrawAddress/checkIsWhiteList`, data);
+
+    }
+
+    /**
+         * 用户已实现盈亏列表
+         * @param {Object} data 
+        */
+    static realisedPnlLog(data) {
+
+        return $post(`/realisedPnlLog/list`, data);
+
+    }
+
+    /**
+        * 定期宝列表
+        * @param {Object} data 
+    */
+    static financeList(data) {
+
+        return $post(`/finance/list`, data);
+
+    }
+    /**
+        * 理财订单列表
+        * @param {Object} data 
+    */
+    static financeOrderList(data) {
+
+        return $post(`/finance/order/list`, data);
+
+    }
+    /**
+        * 定期宝列表
+        * @param {Object} data 
+    */
+    static financeApply(data) {
+
+        return $post(`/finance/apply`, data);
+
+    }
+    /**
+        * 定期理财账户资产
+        * @param {Object} data 
+    */
+    static financeAccount(data) {
+
+        return $post(`/finance/account`, data);
+
+    }
+
+
+
+}
+
+export default Assets;

+ 25 - 0
api/college.js

@@ -0,0 +1,25 @@
+import Serve from '@/api/serve'
+
+class College {
+    static college() {
+        return Serve.get(`/college`);
+    }
+
+    static getArticleList(data) {
+        return Serve.get(`/articleList`,data);
+    }
+
+    static getCategoryList() {
+        return Serve.get(`/categoryList`);
+    }
+
+    static getArticleDetail(data) {
+        return Serve.get(`/article/detail`,data);
+    }
+
+    static getRecommend() {
+        return Serve.get(`/recommend`);
+    }
+}
+
+export default College;

+ 126 - 0
api/contract.js

@@ -0,0 +1,126 @@
+import Serve from '@/api/serve'
+
+class Contract {
+    /**
+     * 合约初始化面板数据
+     * @param {Object} data
+    */
+    static getMarketInfo(data) {
+        return Serve.get(`/contract/getMarketInfo`, data);
+    }
+
+    /**
+     * 获取合约市场
+    */
+    static getMarketList(data) {
+        return Serve.get('/contract/getMarketList', data)
+    }
+
+    /**
+     * 获取合约账户信息
+    */
+    static contractAccount(data, config) {
+        return Serve.get('/contract/contractAccount', data, config)
+    }
+
+    /**
+     * 获取合约详情
+    */
+    static getSymbolDetail(data) {
+        return Serve.get('/contract/getSymbolDetail', data)
+    }
+    /**
+     * 可开张数(合约上限)
+     * */
+    static openNum(data,config) {
+        return Serve.get('/contract/openNum', data,config)
+    }
+    /**
+     * 合约开仓
+    */
+    static openPosition(data, config) {
+        return Serve.post('/contract/openPosition', data, config)
+    }
+
+    // 获取合约持仓
+    static holdPosition(data, config) {
+        return Serve.get('/contract/holdPosition', data, config)
+    }
+    // 合约平仓
+    static closePosition(data, config) {
+        return Serve.post('/contract/closePosition', data, config)
+    }
+    // 一键全平
+    static closeAllPosition(data, config) {
+        return Serve.post('/contract/closeAllPosition', data, config)
+    }
+    // 获取当前合约委托
+    static getCurrentEntrust(data, config) {
+        return Serve.get('/contract/getCurrentEntrust', data, config)
+    }
+    // 撤单
+    static cancelEntrust(data, config) {
+        return Serve.post('/contract/cancelEntrust', data, config)
+    }
+    // 历史委托
+    static getHistoryEntrust(data, config) {
+        return Serve.get('/contract/getHistoryEntrust', data, config)
+    }
+    // 获取k线数据
+    static getKline(data, config) {
+        return Serve.get('/contract/getKline', data, config)
+    }
+    // 获取委托明细
+    static getEntrustDealList(data, config) {
+        return Serve.get('/contract/getEntrustDealList', data, config)
+    }
+    // 获取开通状态
+    static openStatus() {
+        return Serve.get('/contract/openStatus')
+    }
+    //  开通永续合约
+    static opening() {
+        return Serve.post('/contract/opening')
+    }
+    static setStrategy(data, config) {
+        return Serve.post('/contract/setStrategy', data, config)
+    }
+    // 委托盈亏分享
+    static entrustShare(data) {
+        return Serve.get('/contract/entrustShare', data, { loading: true })
+    }
+    // 持仓盈亏分享
+    static positionShare(data) {
+        return Serve.get('/contract/positionShare', data, { loading: true })
+    }
+    // 一键全平
+    static onekeyAllFlat(data) {
+        return Serve.post('/contract/onekeyAllFlat', data, { loading: true })
+    }
+    // 一键反向
+    static onekeyReverse(data) {
+        return Serve.post('/contract/onekeyReverse', data, { loading: true })
+    }
+	
+	
+	//秒合约
+	
+	//获取秒合约币种列表
+	static marketListM(data){
+		return Serve.post('/contract/liststype', data, { loading: true })
+	}
+	
+	//获取秒合约币种列表
+	static marketInfoM(data){
+		return Serve.post('/contract/info', data, { loading: true })
+	}
+	static createOrderM(data){
+		return Serve.post('/contract/createOrder', data, { loading: true })
+	}
+	static OrderListM(data){
+		return Serve.post('/contract/orderLists', data, { loading: true })
+	}
+	
+}
+
+export default Contract;

+ 17 - 0
api/convert.js

@@ -0,0 +1,17 @@
+import Serve from '@/api/serve'
+class Convert {
+
+	static walletList(data){
+		return Serve.post('/convert/walletLists', data, { loading: true })
+	}
+	static getPriceRate(data){
+		return Serve.post('/convert/getPriceRate', data, { loading: true })
+	}
+	
+	static setStore(data){
+		return Serve.post('/convert/store', data, { loading: true })	}
+	static getOrderList(data){
+		return Serve.post('/convert/orderList', data, { loading: true })
+	}
+}
+export default Convert;

+ 16 - 0
api/donation.js

@@ -0,0 +1,16 @@
+import Serve from '@/api/serve'
+class Donation {
+	//获取捐款地址
+	static getAddress(data) {
+	    return Serve.post(`/donation/getAddress`, data);
+	}
+	//获取捐款详情
+	static getDetails() {
+	    return Serve.post(`/donation/details`);
+	}
+	//捐款记录
+	static getList	() {
+	    return Serve.post(`/donation/list`);
+	}
+}
+export default Donation;

+ 41 - 0
api/exchange.js

@@ -0,0 +1,41 @@
+import Serve from '@/api/serve'
+
+class Exchange {
+
+    // 获取账户余额
+    static getUserBalance(data) {
+        return Serve.get(`/exchange/getUserCoinBalance`,data);
+    }
+
+    /**
+     * 提交订单
+     * @param {object} data
+     * @param {string} data.direction buy sell
+     * @param {string} data.type 1限价2市价
+     * @param {string} data.symbol 交易对
+     * @param {number} data.entrust_price 限价单价
+     * @param {number} data.amount 限价数量
+     * @param {number} data.trigger_price 条件单单价
+     * @param {number} data.total 市价单总价
+     * 
+    */
+    static storeEntrust(data,config) {
+        return Serve.post(`/exchange/storeEntrust`, data,config);
+    }
+
+    // 获取币种基本信息
+    static getSymbolInfo(data) {
+        return Serve.post(`/user/tradingPairCurrency`, data);
+    }
+
+    // 查询最新资讯
+    static newTrends() {
+        return Serve.get(`/newTrends`);
+    }
+    // 获取汇率
+    static getCurrencyExCny(data){
+        return Serve.get('/market/getCurrencyExCny',data)
+    }
+}
+
+export default Exchange;

+ 25 - 0
api/home.js

@@ -0,0 +1,25 @@
+
+import Serve from '@/api/serve/index'
+
+class Home {
+    // 获取大部分数据
+    static indexList(data,config){
+        return Serve.get('/indexList',data,config)
+    }
+    // 获取自选数据
+    static getCollect(){
+        return Serve.get('/getCollect')
+    }
+
+    /**
+     * 添加自选
+     * @param {object} data
+     * @param {string} data.pair_id
+     * @param {string} data.pair_name
+    */
+    static option(data){
+        return Serve.post('/option',data)
+    }
+}
+
+export default Home;

+ 56 - 0
api/market.js

@@ -0,0 +1,56 @@
+import Serve from '@/api/serve'
+
+class Market {
+
+    /**
+     * 轮播图列表
+     * @param {Object} data 
+     * @param {int} type  请求类型 1是pc(默认) 2是app
+     * @param {int} position  位置 1(币币首页) 2(预留扩展)
+    */
+    static cryptocurrenciesWithdrawal(type, position) {
+
+        return Serve.post(`/market/banner/${type}/${position}`);
+
+    }
+
+    /**
+     * 用户收藏交易对信息 需要先登录
+     */
+    static userFavList() {
+
+        return Serve.get(`/coin/market/collection/list`);
+
+    }
+
+
+    static blogCategoryList() {
+        return Serve.get(`/blogCategory/list`);
+    }
+
+    static blogDetailList(categoryId, params) {
+        return Serve.get(`/blog/list/${categoryId}`, params );
+    }
+
+    static blogDetailContent(id) {
+        return Serve.get(`/blog/detail/${id}`);
+    }
+
+    // 初始化查询市场行情
+    static getMarketList() {
+        return Serve.get(`/exchange/getMarketList`);
+    }
+
+    // 初始化买卖盘数据
+    static getBooks(data) {
+        return Serve.get(`/exchange/getMarketInfo`,data);
+    }
+
+    // 获取币种信息
+    static getCoinInfo(data){
+        return Serve.get(`/exchange/getCoinInfo`,data)
+    }
+
+}
+
+export default Market;

+ 137 - 0
api/member.js

@@ -0,0 +1,137 @@
+import server from "@/api/serve";
+
+class Member {
+    /**
+     * 注册滑块验证码
+     * @param {object} data
+     */
+    static sliderVerify(data) {
+        return server.post(`/sliderVerify`, data);
+    }
+
+    /**
+     * 注册发送手机验证码
+     * @param data {phone,country_code,token}
+     */
+    static sendSmsCode(data) {
+        return server.post(`/register/sendSmsCode`, data);
+    }
+
+    /**
+     * 注册发送验证码
+     * @param data {email,token}
+     */
+    static sendEmailCode(data) {
+        return server.post(`/register/sendEmailCode`, data);
+    }
+
+    /**
+     * 获取国家区号
+     * @param {object} data
+     */
+    static getCountryCode() {
+        return server.get(`/getCountryList`);
+    }
+
+    /**
+     * 注册提交
+     * @param {object} data
+     */
+    static register(data) {
+        return server.post(`/user/register`, data);
+    }
+
+    /**
+     * 登陆发送短信验证码
+     * @param {object} data
+     */
+    static sendSmsCodeBeforeLogin(data) {
+        return server.post(`/login/sendSmsCodeBeforeLogin`, data);
+    }
+
+    /**
+     * 登陆发送邮箱验证码
+     * @param {object} data
+     */
+    static sendEmailCodeBeforeLogin(data) {
+        return server.post(`/login/sendEmailCodeBeforeLogin`, data);
+    }
+
+    /**
+     * 登陆初始化验证
+     * @param {object} data
+     */
+    static login(data) {
+        return server.post(`/user/login`, data);
+    }
+    /**
+     * 已签到列表
+     */
+    static signList() {
+        return server.get(`/user/sign_list`);
+    }
+    /**
+     * 已签到列表
+     * @param {object} data
+     */
+    static sign(data) {
+        return server.post(`/user/sign`, data);
+    }
+
+    /**
+     * 登陆二次验证
+     * @param {object} data
+     */
+    static loginConfirm(data, { loading }) {
+        return server.post(`/user/loginConfirm`, data, { loading });
+    }
+
+    /**
+     * 退出登录
+     */
+    static logout() {
+        return server.post(`/user/logout`);
+    }
+    /**
+     * 上传文件
+     * @param {FormData} data
+     */
+    static uploadImage(data) {
+        return server.uploadFile(`/uploadImage`, data);
+    }
+
+    // 页面底部信息
+    static floor() {
+        return server.get("/floor");
+    }
+    // 移动端logo
+    static mobileLogo() {
+        return server.get("/index/logo", {}, { loading: false });
+    }
+    // 消息通知
+    static myNotifiables(data) {
+        return server.get("/user/myNotifiables", data);
+    }
+    // 消息通知详情
+    static readNotifiable(data) {
+        return server.get("/user/readNotifiable", data);
+    }
+    // 移动端文章
+    static article(data) {
+        return server.get("/article/list", data);
+    }
+    // 文章详情
+    static articleDetail(data) {
+        return server.get("/article/detail", data);
+    }
+    // 获取协议
+    static clause() {
+        return server.get("/login/clause");
+    }
+    // 获取app更新信息
+    static getNewestVersion() {
+        return server.get("/getNewestVersion");
+    }
+}
+
+export default Member;

+ 115 - 0
api/option.js

@@ -0,0 +1,115 @@
+
+import Serve from '@/api/serve'
+
+class Option {
+    // 交易对
+    static getOptionSymbol() {
+        return Serve.get(`/option/getOptionSymbol`);
+    }
+    /**
+     * 获取期权交割记录
+     * @param {object} data
+     * @param {string} data.pair_id
+     * @param {string} data.time_id
+    */
+    static getSceneResultList(data) {
+        return Serve.get(`/option/getSceneResultList`, data)
+    }
+    /**
+     * 获取k线数据
+     * @param {object} data
+     * @param {string} data.symbol
+     * @param {string} data.period
+     * @param {string} data.size
+     * @param {string} data.form
+     * @param {string} data.to
+    */
+    static getKline(data) {
+        // let url =  `https://api.hadax.com/market/history/kline`;
+        let url = `/option/getKline`;
+        return Serve.get(url, data)
+    }
+    /**
+     * 获取可用于期权交易的币种列表
+    */
+    static getBetCoinList() {
+        return Serve.get(`/option/getBetCoinList`)
+    }
+    /**
+   * 获取指定币种的余额
+   * @param {object} data
+   * @param {string} data.coin_id
+  */
+    static getUserCoinBalance(data) {
+        return Serve.get(`/option/getUserCoinBalance`, data)
+    }
+    /**
+     * 获取当前最新期权场景
+     * @param {object} data
+     * @param {string} data.pair_id
+     * @param {string} data.time_id
+    */
+    static sceneDetail(data) {
+        return Serve.get(`/option/sceneDetail`, data)
+    }
+    /**
+     * 获取全部期权场景
+    */
+    static sceneListByPairs() {
+        return Serve.get(`/option/sceneListByPairs`)
+    }
+    /**
+     * 获取当前最新期权场景赔率
+     * @param {object} data
+     * @param {string} data.pair_id
+     * @param {string} data.time_id
+    */
+    static getOddsList(data) {
+        return Serve.get(`/option/getOddsList`, data)
+    }
+    /**
+     * 获取用户期权购买记录
+     * @param {object} data
+     * @param {string} data.status
+     * @param {string} data.pair_id
+     * @param {string} data.time_id
+    */
+    static getOptionHistoryOrders(data) {
+        return Serve.get(`/option/getOptionHistoryOrders`, data)
+    }
+    /**
+     * 购买期权
+     * @param {object} data
+     * @param {string} data.bet_amount
+     * @param {string} data.bet_coin_id
+     * @param {string} data.odds_id
+     * */
+    static betScene(data) {
+        return Serve.post(`/option/betScene`, data)
+    }
+    /**
+     * 获取交易价格组
+     * @param {object} data
+     * @param {string} data.symbol
+     *  
+    */
+    static getNewPriceBook(data) {
+        return Serve.get('/option/getNewPriceBook', data)
+    }
+    /**
+     * 移动端期权列表
+    */
+    static sceneListByTimes() {
+        return Serve.get('/option/sceneListByTimes')
+    }
+    /**
+     * 移动端详情
+     * @param {object} data
+     * @param {string} data.order_id
+    */
+    static getOptionOrderDetail(data) {
+        return Serve.get('/option/getOptionOrderDetail', data)
+    }
+}
+
+export default Option;

+ 95 - 0
api/order.js

@@ -0,0 +1,95 @@
+import Serve from '@/api/serve'
+class Order {
+    /**
+     * 发布委托
+     * @param {object} data
+     * @param {string} data.direction   方向
+     * @param {number} data.type - 类型 
+     * @param {string} data.symbol - 交易对 
+     * @param {number} data.entrust_price - 价格
+     * @param {number} data.amount - 数量
+     * 
+    */
+    static storeEntrust(data) {
+        return Serve.post(`/exchange/storeEntrust`,data);
+    }
+    /**
+     * 获取历史委托 
+     * @param {object} data
+     * @param {string} data.direction   方向
+     * @param {number} data.type - 类型 
+     * @param {string} data.symbol - 交易对 
+     * 
+    */
+    static getHistoryEntrust(data) {
+        return Serve.get(`/exchange/getHistoryEntrust`,data);
+    }
+    /**
+     * 获取当前委托
+     * @param {object} data
+     * @param {string} data.direction   方向
+     * @param {number} data.type - 类型 
+     * @param {string} data.symbol - 交易对 
+     * 
+    */
+    static getCurrentEntrust(data) {
+        return Serve.get(`/exchange/getCurrentEntrust`,data);
+    }
+
+    // 获取止盈止损单
+    static getConditionEntrust(data) {
+        return Serve.get(`/exchange/getConditionEntrust`,data);
+    }
+
+    /**
+     * 获取委托成交记录
+     * @param {object} data
+     * @param {string} data.entrust_id   委托id
+     * @param {number} data.entrust_type - 买入卖出
+     * @param {string} data.symbol - 交易对 
+     * 
+    */
+    static getEntrustTradeRecord(data) {
+        return Serve.get(`/exchange/getEntrustTradeRecord`,data);
+    }
+    
+    /**
+     * 撤单
+     * @param {object} data
+     * @param {string} data.entrust_id   委托id
+     * @param {number} data.entrust_type - 买入卖出
+     * @param {string} data.symbol - 交易对 
+     * 
+    */
+    static cancelEntrust(data) {
+        return Serve.post(`/exchange/cancelEntrust`,data);
+    }
+    /**
+     * 批量撤单
+     * @param {object} data
+     * @param {string} data.symbol - 交易对 
+     * 
+    */
+    static batchCancelEntrust(data) {
+        return Serve.post(`/exchange/batchCancelEntrust`,data);
+    }
+    // 获取交易对
+    static getExchangeSymbol(){
+        return Serve.get('/exchange/getExchangeSymbol')
+    }
+    /**
+     * 期权交易记录
+     * @param {object} [data]
+     * @param {string} data.status
+     * @param {string} data.pair_id
+     * @param {string} data.time_id
+     *  
+    */ 
+    static getOptionHistoryOrders(data){
+        return Serve.get('/option/getOptionHistoryOrders',data)
+    }
+   
+
+}
+
+export default Order;

+ 56 - 0
api/otc.js

@@ -0,0 +1,56 @@
+import Serve from '@/api/serve/index'
+class Otc {
+    static userPayment(data) {
+        return Serve.get(`/userPayment`,data,{loading:true});
+    }
+    static editUserPayment(data) {
+        return Serve.post(`/userPayment/${data.id}`,data,{loading:true});
+    }
+    static getUserPayment(data) {
+        return Serve.post(`/userPayment/${data.id}`,{},{loading:true});
+    }
+    static addUserPayment(data) {
+        return Serve.post(`/userPayment`,data,{loading:true});
+    }
+    static otcTicker(){
+        return Serve.get(`/otc/otcTicker`,{});
+    }
+    static tradingEntrusts(data){
+        return Serve.get(`/otc/tradingEntrusts`,data,{loading:true})
+    }
+    static storeEntrust(data){
+        return Serve.post(`/otc/storeEntrust`,data,{loading:true})
+    }
+    static storeOrder(data){
+        return Serve.post(`/otc/storeOrder`,data,{loading:true})
+    }
+    static myEntrusts(data){
+        return Serve.get(`/otc/myEntrusts`,data,{loading:true})
+    }
+    static myOrders(data){
+        return Serve.get(`/otc/myOrders`,data,{loading:true})
+    }
+    static cancelEntrust(data){
+        return Serve.post(`/otc/cancelEntrust`,data,{loading:true})
+    }
+    static cancelOrder(data){
+        return Serve.post(`/otc/cancelOrder`,data,{loading:true})
+    }
+    static confirmPaidOrder(data){
+        return Serve.post(`/otc/confirmPaidOrder`,data,{loading:true})
+    }
+    static confirmOrder(data){
+        return Serve.post(`/otc/confirmOrder`,data,{loading:true})
+    }
+    static notConfirmOrder(data){
+        return Serve.post(`/otc/notConfirmOrder`,data,{loading:true})
+    }
+    static orderDetail(data){
+        return Serve.get(`/otc/orderDetail`,data,{loading:true})
+    }
+    static otcAccount(data){
+        return Serve.get(`/otc/otcAccount`,data,{loading:true})
+    }
+}
+
+export default Otc;

+ 35 - 0
api/pledge.js

@@ -0,0 +1,35 @@
+import Serve from '@/api/serve/index'
+
+
+class Pledge {
+	static indexList(){
+	    return Serve.get('/pledge/productList');
+	}
+	
+	static getDetail(param){
+		return Serve.get('/pledge/product',param);
+	}
+	
+	// 获取行情
+	static getquotation(param){
+		return Serve.get('/pledge/quotation',param);
+	}
+	
+	/**
+	 * 购买产品
+	 * @param data {id,num}
+	 */
+	static buygoods(data) {
+	    return Serve.post('/pledge/buyProduct', data);
+	}
+	
+	/**
+	 * 购买产品
+	 * @param data {id,num}
+	 */
+	static getOrderList() {
+	    return Serve.get('/pledge/orderList');
+	}
+}
+
+export default Pledge;

+ 73 - 0
api/profile.js

@@ -0,0 +1,73 @@
+import Serve from '@/api/serve/index'
+class Profile {
+    // 获取用户信息
+    static getUserInfo() {
+        return Serve.get(`/user/getUserInfo`);
+    }
+    // 获取实名认证信息
+    static getAuthInfo() {
+        return Serve.get(`/user/getAuthInfo`);
+    }
+    /**
+     * 初级认证 (认证第一步)
+     * @param {object} data
+     * @param {number} data.country_code
+     * @param {number} data.country_id // 区号id
+     * @param {string} data.realname
+     * @param {number} data.id_card //证件号
+     * @param {number} data.type //证件类型
+     * @param {string} data.birthday //出生日期
+     * @param {string} data.address //地址
+     * @param {string} data.city //城市
+     * @param {string} data.extra //额外信息
+     * @param {string} data.postal_code //邮政编码
+     * @param {string} data.phone //手机号
+    */
+    static primaryAuth(data) {
+        return Serve.post(`/user/primaryAuth`, data);
+    }
+    /**
+     * 高级认证(认证第二步)
+     * @param {object} data
+     * @param {string} data.front_img //证件照正面
+     * @param {string} data.back_img   //证件照反面
+     * @param {string} data.hand_img //手持证件照
+    */
+    static topAuth(data) {
+        return Serve.post(`/user/topAuth`, data);
+    }
+
+    /**
+     * 登录记录
+    */
+    static getLoginLogs(data){
+        return Serve.get(`/user/getLoginLogs`, data)
+    }
+    /**
+     * 邀请推广
+    */
+    static generalizeInfo(){
+        return Serve.get(`/generalize/info`,)
+    }
+    /**
+     * 推广记录
+    */
+    static generalizeList(data){
+        return Serve.get(`/generalize/list`,data)
+    }
+    /**
+     * 返佣记录
+    */
+   static rewardLogs(data){
+       return Serve.get('/generalize/rewardLogs',data)
+   }
+    /**
+     * 获取用户等级详情
+    */
+   static getGradeInfo(){
+       return Serve.get('/user/getGradeInfo')
+   }
+    
+}
+
+export default Profile;

+ 101 - 0
api/record.js

@@ -0,0 +1,101 @@
+import {$get,$post,$postFile} from '@/api'
+
+class Record {
+    /**
+     * fundHistory列表
+     * @param {Object} data 
+    */
+   static fundList(data) {
+
+    return $get(`/coin/getAllList`, data);
+
+   }
+
+    /**
+     * 币币用户当前委托记录
+     * @param {Object} data 
+    */
+    static openOrder(data) {
+
+        return $post(`/coin/orders/openOrder`, data);
+
+    }
+    static conditionOrders(data) {
+
+        return $post(`/coin/orders/condition/openOrder`, data);
+
+    }
+
+
+    /**
+     * 币币用户取消接口
+     * @param {Object} data 
+    */
+   static openOrderCancel(data) {
+
+          return $post(`/coin/orders/cancel/${data}`);
+    
+     }
+    
+    static orderConditionCancel(data) {
+        return $post(`/coin/orders/condition/cancel/${data}`)
+    }
+
+     /**
+     * 币币用户筛选接口
+     * @param {Object} data 
+    */
+   static openOrderfilter() {
+
+          return $post(`/coin/orders/filter`);
+    
+     }
+
+
+    /**
+     * 用户返佣记录
+     * @param {Object} data 
+    */
+    static rewardList(data) {
+
+        return $post(`/member/rewardList`, data);
+
+    }
+
+    /**
+     * 币币用户历史委托
+     * @param {Object} data 
+    */
+    static history(data) {
+
+        return $post(`/coin/orders/history`, data);
+
+    }
+
+    /**
+     * 联系我们
+     * @param {Object} data 
+    */
+   static contactUs(data) {
+          return $post(`/contactUs/save` , data);
+     }
+
+     /**
+     * 取消订单
+     * @param {Object} data 
+    */
+   static cancelActiveOrder(data) {
+          return $get(`/futuresOrders/cancelActiveOrder/${data}`);
+     }
+
+    /**
+     * 取消订单
+     * @param {Object} data 
+    */
+   static cancelById(data) {
+          return $get(`/futuresConditionOrders/cancelById/${data}`);
+     }
+
+}
+
+export default Record;

+ 31 - 0
api/resIntercept.js

@@ -0,0 +1,31 @@
+import vue from "vue";
+import router from '@/router'
+const resIntercept = (result) => {
+    let res = result.data
+    let config = result.config
+    return new Promise(
+        function (resolve, reject) {
+            // 是否提示
+            if (typeof config.toast == 'boolean') {
+                if (config.toast) {
+                    vue.prototype.$toast(res.msg)
+                }
+            } else {
+                if (res.code != 200 && res.code != 100) {
+                    vue.prototype.$toast(res.msg)
+                }
+            }
+            // 过滤
+            if (res.code == 200) {//成功
+                resolve(res)
+            } else {//失败
+                reject(res)
+                if (res.code == 100 && !config.notLogin) {
+                    router.push('/InterceptAccount')
+                }
+            }
+        }
+    )
+}
+
+export default resIntercept

+ 247 - 0
api/serve/index.js

@@ -0,0 +1,247 @@
+
+
+import app from "@/app.js"
+let settings = {
+	method: "get",	// 默认请求方法
+	contentType: "application/json", // 传参格式
+	dataType: "json", // 返回值类型
+	baseUrl: app.baseUrl + '/api/app', // 请求根地址
+}
+
+let loadNum = 0; //加载框的数量
+let loadingShow = () => {
+	loadNum++
+	uni.showLoading({
+		title: 'loading...'
+	});
+}
+let loadingHide = () => {
+	loadNum--
+	if (loadNum <= 0) {
+		uni.hideLoading();
+	}
+}
+function x(options = null) {
+
+	// 返回当前实例对象 无需手动return
+	return new x.fn.init(options);
+
+}
+
+x.fn = x.prototype = {
+
+	constructor: x,
+
+	config(options) {
+		// 解构并设置默认值
+		let {
+			baseUrl,
+			url,
+			data,
+			method,
+			contentType,
+			dataType
+		} = options;
+
+		// 请求头参数 写入token和lang
+		let auth = null;
+		if (uni.getStorageSync('token')) {
+			auth = uni.getStorageSync('token');
+		}
+
+		const header = auth ? {
+			'X-Requested-With': 'XMLHttpRequest',
+			lang: uni.getStorageSync('lang') || 'tw',
+			authorization: `bearer ${auth}`
+		} : {
+				'X-Requested-With': 'XMLHttpRequest',
+				lang: uni.getStorageSync('lang') || 'tw',
+			};
+
+		this.header = header;
+
+		// 请求地址解析
+		if (url.startsWith('http')) { // 外部链接
+			this.url = url;
+		} else { // 本地相对路径
+			this.url = baseUrl + url;
+		}
+
+		if (data) this.data = data;
+		if (method) this.method = method;
+		if (contentType) this.contentType = contentType;
+		if (dataType) this.dataType = dataType;
+	},
+
+	init(options) {
+		// 将用户参数 写入配置信息
+		this.config(Object.assign(settings, options));
+		let { config = {} } = options
+		// 提示状态
+		if (config.loading) {
+			loadingShow()
+		}
+		return new Promise((resolve, reject) => {
+			uni.request({
+				url: this.url,
+				data: this.data,
+				method: this.method,
+				header: this.header,
+				dataType: this.dataType,
+				sslVerify: false,
+				success: (res) => {
+					let message = res.data.message
+					let code = res.data.code
+					
+					if (code != 200) {
+						switch (code) {
+							case 1003: // 登陆失效 清除状态 重新登陆
+								// 清除session
+								uni.removeStorageSync('token');
+								uni.redirectTo({
+									url: "/pages/login/index",
+								});
+								break;
+							case 1021:
+								resolve(res.data);
+								break;
+							default:
+								reject(message);
+								break;
+						}
+						if (config.toast !== false&&message) {
+							uni.showToast({
+								title: message,
+								duration: 2000,
+								icon: 'none'
+							});
+						}
+					} else {
+						if(res.data) {
+							resolve(res.data); // 直接返回数据
+							
+						}else {
+							console.log('dd')
+							resolve('success');
+						}
+						
+						if (config.toast&&message) {
+							uni.showToast({
+								title: message,
+								duration: 2000,
+								icon: 'none'
+							});
+						}
+					}
+				},
+				fail: (err) => {
+					reject(err)
+					if (config.toast !== false) {
+						uni.showToast({
+							title: 'error reload!',
+							icon: "none"
+						});
+					}
+					if (err) {
+						throw new Error();
+					}
+				},
+				complete: () => {
+					loadingHide()
+				}
+			})
+		})
+
+	},
+
+	// 使用promise封装同步化的确认框
+	confirmSync(content, fullfilled, rejected = null) {
+		let showCancel = false;
+		if (rejected instanceof Function) {
+			showCancel = true;
+		}
+		return new Promise(function (resolve, reject) {
+			uni.showModal({
+				content,
+				showCancel,
+				success(res) { // confirm or cancel
+					if (res.confirm) {
+						resolve(fullfilled()); // 执行动作 需要返回值 则标记到resolve中
+					} else if (res.cancel && rejected) {
+						reject(rejected()); // 执行动作 需要返回值 则标记到reject中
+					}
+				}
+			})
+		})
+	},
+
+	get(url, data = null, config = {}) {
+		return x({
+			method: "get",
+			url,
+			data,
+			config
+		})
+	},
+
+	post(url, data, config = {}) {
+		return x({
+			method: "post",
+			url,
+			data,
+			config
+		})
+	},
+	// data 为uni的chooseImage
+	uploadFile(url, data, config = {}) {
+		let auth = null;
+		if (uni.getStorageSync('token')) {
+			auth = uni.getStorageSync('token');
+		}
+		let header = {
+			'X-Requested-With': 'XMLHttpRequest',
+			lang: "cn",
+		}
+		if (auth) header.authorization = `bearer ${auth}`;
+		if (config.loading !== false) {
+			loadingShow()
+		}
+		return new Promise((resolve, reject) => {
+			uni.uploadFile({
+				url: settings.baseUrl + url, //仅为示例,非真实的接口地址
+				filePath: data.tempFilePaths[0],
+				name: 'image',
+				formData: {},
+				sslVerify: false,
+				header,
+				success: (res) => {
+					resolve(JSON.parse(res.data))
+				},
+				fail: () => {
+					reject()
+				},
+				complete: () => {
+					loadingHide()
+				}
+			});
+		})
+
+	},
+	head() {
+
+	},
+	put() {
+
+	},
+	// ...
+}
+
+x.fn.init.prototype = x.fn, x.extend = x.fn.extend = function (obj, prop) {
+	if (!prop) { //如果未设置prop 则表示给this扩展一个对象的内容
+		prop = obj;
+		obj = this;
+	}
+	for (var i in prop) obj[i] = prop[i];
+}, x.extend(x.fn);
+
+export default x;

+ 200 - 0
api/serve/market-socket.js

@@ -0,0 +1,200 @@
+
+class Ws {
+    constructor(ws, data, ...args) { // [{url, data, method...},,,,]
+        this._ws = ws;
+        this._data = data;
+        // 待发送的消息列
+        this._msgs = []
+
+        this.socket = this.doLink();
+        this.doOpen();
+        // 订阅/发布模型
+        this._events = {};
+        // 是否保持连接
+        this._isLink = true;
+        // 订阅列表(交易所专用)
+        this.subs = []
+        // 循环检查
+        setInterval(() => {
+            if (this._isLink) {
+                if (this.socket.readyState == 2 || this.socket.readyState == 3) {
+                    this.resetLink()
+                }
+            }
+        }, 3000)
+    }
+    // 重连
+    resetLink() {
+        this.socket = this.doLink(() => {
+            this.Notify({
+                Event: 'resetLink'
+            });
+            this.resetSub()
+        });
+        this.doOpen();
+    }
+    // 连接
+    doLink(call) {
+        let ws = uni.connectSocket({
+            url: this._ws,
+            // 可选参数 设置默认值
+            header: {
+                'content-type': 'application/json'
+            },
+            method: 'GET',
+            success: () => {
+                call && call()
+            }
+        })
+        return ws;
+    }
+    doOpen() {
+        this.socket.onOpen((ev) => {
+            this.onOpen(ev)
+        })
+        this.socket.onMessage((ev) => {
+            this.onMessage(ev)
+        })
+        this.socket.onClose((ev) => {
+            this.onClose(ev)
+        })
+        this.socket.onError((ev) => {
+            this.onError(ev)
+        })
+
+    }
+    // 打开
+    onOpen() {
+        // 打开时重发未发出的消息
+        let list = Object.assign([], this._msgs)
+        list.forEach((item) => {
+            if (this.send(item)) {
+                let idx = this._msgs.indexOf(item)
+                if (idx != -1) {
+                    this._msgs.splice(idx, 1)
+                }
+            }
+        })
+    }
+    // 手动关闭
+    doClose() {
+        this._isLink = false
+        this._events = {}
+        this._msgs = []
+        this.socket.close({
+            success: () => {
+                console.log('socket close success')
+            }
+        })
+    }
+    // 添加监听
+    on(name, handler) {
+        this.subscribe(name, handler);
+    }
+    // 取消监听
+    off(name, handler) {
+        this.unsubscribe(name, handler);
+    }
+    // 关闭事件
+    onClose() {
+        // 是否重新连接
+        if (this._isLink) {
+            setTimeout(() => {
+                this.resetLink()
+            }, 3000)
+        }
+    }
+    // 错误
+    onError(evt) {
+        this.Notify({
+            Event: 'error',
+            Data: evt
+        });
+
+    }
+    // 接受数据
+    onMessage(evt) {
+        try {
+
+            // 解析推送的数据
+            const data = JSON.parse(evt.data);
+
+            // 通知订阅者
+            this.Notify({
+                Event: 'message',
+                Data: data
+            });
+
+        } catch (err) {
+            console.error(' >> Data parsing error:', err);
+            // 通知订阅者
+            this.Notify({
+                Event: 'error',
+                Data: err
+            });
+        }
+    }
+    // 订阅事件的方法
+    subscribe(name, handler) {
+        if (this._events.hasOwnProperty(name)) {
+            this._events[name].push(handler); // 追加事件
+        } else {
+            this._events[name] = [handler]; // 添加事件
+        }
+    }
+    // 取消订阅事件
+    unsubscribe(name, handler) {
+        let start = this._events[name].findIndex(item => item === handler);
+        // 删除该事件
+        this._events[name].splice(start, 1);
+    }
+    // 发布后通知订阅者
+    Notify(entry) {
+        // 检查是否有订阅者 返回队列
+        const cbQueue = this._events[entry.Event];
+        if (cbQueue && cbQueue.length) {
+            for (let callback of cbQueue) {
+                if (callback instanceof Function) callback(entry.Data);
+            }
+        }
+    }
+    // 发送消息
+    send(data) {
+        this.changeSubs(data)
+        if (this.socket.readyState == 1) {
+            this.socket.send({ data: JSON.stringify(data) })
+            return true
+        } else {
+            // 保存到待发送信息
+            if (!this._msgs.includes(data)) {
+                this._msgs.push(data)
+            };
+            return false
+        }
+
+    }
+    // 修改订阅列表(交易所用)
+    changeSubs(data) {
+        if (data.cmd == 'sub') {
+            if (!this.subs.includes(data.msg)) {
+                this.subs.push(data.msg)
+            }
+        } else if (data.cmd == 'unsub') {
+            let idx = this.subs.indexOf(data.msg)
+            if (idx != -1) {
+                this.subs.splice(idx, 1)
+            }
+        }
+    }
+    // 重新订阅(交易所用)
+    resetSub() {
+        let list = Object.assign([], this.subs)
+        list.forEach((item) => {
+            this.send({
+                cmd: 'sub',
+                msg: item
+            })
+        })
+    }
+}
+export default Ws

+ 62 - 0
api/serve/webaxios.js

@@ -0,0 +1,62 @@
+import axios from 'axios'
+import app from '@/app'
+import qs from 'qs';
+
+// 初始化配置
+let setting = {
+    baseURL: app.baseUrl + '/api/app',
+    timeout: 10000,
+    withCredentials: true,
+    crossDomain: true,
+    responseType: 'json',
+    headers: {
+        'content-type': 'application/x-www-form-urlencoded'
+    }
+}
+const server = axios.create(setting)
+
+// 请求拦截
+server.interceptors.request.use(function (config) {
+    if (config.method === 'post') {
+        if (!config.file) {
+            config.data = qs.stringify(config.data)
+        }
+    }
+    config.headers = Object.assign(config.headers, {
+        'X-Requested-With': 'XMLHttpRequest',
+
+    })
+    return config;
+}, function (error) {
+    return Promise.reject(error);
+})
+// 响应拦截
+server.interceptors.response.use(function (response) {
+    return response.data;
+}, function (error) {
+    return Promise.reject(error);
+})
+export default server;
+
+
+const $get = (url, data, config) => {
+    return server.get(url, {
+        params: data,
+        ...config
+    })
+}
+const $post = (url, data, config) => {
+    return server.post(url, data, config)
+}
+const $postFile = (url, data, config) => {
+    let form = new FormData()
+    for (let i in data) {
+        form.append(i, data[i])
+    }
+    let postConfig = {
+        file: true
+    }
+    return server.post(url, form, Object.assign(postConfig, config))
+}
+export { $get, $post, $postFile }
+

+ 197 - 0
api/setting.js

@@ -0,0 +1,197 @@
+import Serve from '@/api/serve'
+class Setting {
+    // 获取用户信息
+    static getUserInfo() {
+        return Serve.get(`/user/getUserInfo`);
+    }
+    /**
+     * 修改用户信息
+     * @param {{username:string,avatar:'url'}} data
+    */
+    static updateUserInfo(data) {
+        return Serve.post(`/user/updateUserInfo`, data);
+    }
+    /**
+     * 关闭手机号/邮箱/谷歌验证
+     * @param {object} data
+     * @param {number} data.type 1:手机 2:邮箱 3:谷歌
+     * @param {number} data.sms_code 手机验证码
+     * @param {number} data.email_code 邮箱验证码
+     * @param {number} data.google_code 谷歌验证码
+    */
+    static disableSmsEmailGoogle(data,{btn}) {
+        return Serve.post(`/user/disableSmsEmailGoogle`, data,{btn});
+    }
+    /**
+     * 开启手机号/邮箱/谷歌验证
+     * @param {object} data
+     * @param {number} data.type 1:手机 2:邮箱 3:谷歌
+     * @param {number} data.sms_code 手机验证码
+     * @param {number} data.email_code 邮箱验证码
+     * @param {number} data.google_code 谷歌验证码
+    */
+    static enableSmsEmailGoogle(data,{btn}) {
+        return Serve.post(`/user/enableSmsEmailGoogle`, data,{btn});
+    }
+    /**
+     * 发送邮箱验证码
+     * @param {object} data
+     * @param {string} data.email 邮箱号 
+    */
+    static sendBindEmailCode(data) {
+        return Serve.post(`/user/sendBindEmailCode`, data);
+    }
+    /**
+     * 登录二次验证开关
+    */
+    static switchSecondVerify() {
+        return Serve.get(`/user/switchSecondVerify`);
+    }
+    /**
+     * 账号安全信息
+    */
+    static accountSecurity() {
+        return Serve.get(`/user/security/home`);
+    }
+    /**
+     * 设置或重置交易密码
+     * @param {object} data
+     * @param {string} data.payword
+     * @param {string} data.payword_confirmation
+     * @param {string} data.sms_code
+     * @param {string} data.email_code
+     * @param {string} data.google_code
+    */
+    static setOrResetPaypwd(data) {
+        return Serve.post(`/user/setOrResetPaypwd`, data);
+    }
+    /**
+     * 设置或重置登录密码
+     * @param {object} data
+     * @param {string} data.password
+     * @param {string} data.password_confirmation
+     * @param {string} data.sms_code
+     * @param {string} data.email_code
+     * @param {string} data.google_code
+    */
+    static updatePassword(data,{btn}) {
+        return Serve.post(`/user/updatePassword`, data,{btn});
+    }
+    /**
+     * 绑定邮箱
+     * @param {object} data
+     * @param {string} data.email
+     * @param {string} data.email_code
+     * @param {string} data.sms_code
+     * @param {string} data.google_code
+    */
+    static bindEmail(data,{btn}) {
+        return Serve.post(`/user/bindEmail`, data,{btn});
+    }
+
+    /**
+     * 绑定手机
+     * @param {object} data
+     * @param {string} data.phone
+     * @param {string} data.country_code  - 手机区号
+     * @param {string} data.sms_code  
+     * @param {string} data.email_code  
+     * @param {string} data.google_code  
+    */
+    static bindPhone(data,{btn}) {
+        return Serve.post(`/user/bindPhone`, data,{btn});
+    }
+
+    /**
+     * 解绑邮箱
+     * @param {object} data 
+     * @param {string} data.sms_code  
+     * @param {string} data.email_code  
+     * @param {string} data.google_code  
+    */
+    static unbindEmail(data) {
+        return Serve.post(`/user/unbindEmail`, data);
+    }
+
+    /**
+     * 解绑手机
+     * @param {object} data  
+     * @param {string} data.sms_code  
+     * @param {string} data.email_code  
+     * @param {string} data.google_code  
+    */
+    static unbindPhone(data) {
+        return Serve.post(`/user/unbindPhone`, data);
+    }
+
+    /**
+     * 忘记登录密码 - 账号确认
+     * @param {object} data  
+     * @param {string} data.account  
+    */
+    static forgetPasswordAttempt(data) {
+        return Serve.post(`/user/forgetPasswordAttempt`, data,{toast:false});
+    }
+    /**
+     * 忘记登录密码 - 提交
+     * @param {object} data
+     * @param {string} data.account
+     * @param {string} data.email_code
+     * @param {string} data.google_code
+     * @param {string} data.password
+     * @param {string} data.password_confirmation
+    */
+    static forgetPassword(data,{btn}) {
+        return Serve.post(`/user/forgetPassword`, data,{btn});
+    }
+
+    /**
+     * 获取谷歌密钥
+    */
+    static getGoogleToken(data) {
+        return Serve.get(`/user/getGoogleToken`, data);
+    }
+    /**
+     * 绑定谷歌
+     * @param {object} data  
+     * @param {string} data.google_token  
+     * @param {string} data.google_code  
+     * @param {string} data.sms_code  
+     * @param {string} data.email_code  
+    */
+    static bindGoogleToken(data,{btn}) {
+        return Serve.post(`/user/bindGoogleToken`, data,{btn});
+    }
+    /**
+     * 解绑谷歌
+     * @param {object} data  
+     * @param {string} data.sms_code  
+     * @param {string} data.google_code  
+     * @param {string} data.email_code  
+    */
+    static unbindGoogleToken(data) {
+        return Serve.post(`/user/unbindGoogleToken`, data);
+    }
+    /**
+     * 发送绑定手机短信验证码
+     * @param {object} data  
+     * @param {string} data.phone
+     * @param {string} data.country_code
+    */
+    static sendBindSmsCode(data) {
+        return Serve.post(`/user/sendBindSmsCode`, data);
+    }
+    /**
+     * 在线获取验证码
+     * @param {object} data  
+     * @param {string} data.type  1:手机 2:邮箱
+    */
+    static getCode(data) {
+        return Serve.post(`/user/getCode`, data);
+    }
+    
+
+
+}
+
+export default Setting;

+ 29 - 0
api/subscride.js

@@ -0,0 +1,29 @@
+
+import Serve from '@/api/serve'
+
+class Subscribe {
+    /**
+     * 请求数据
+    */
+    static subscribeTokenList(data){
+        return Serve.post('/user/subscribeTokenList',data)
+    }
+   static subscribe(data){
+       return Serve.post('/user/subscribe',data)
+   }
+    /**
+     * 提交数据
+     * @param {object} data
+     * @param {string} data.amount
+     * @param {string} data.coin_name
+    */
+   static subscribeNow(data){
+        return Serve.post('/user/subscribeNow',data)
+   }
+   static subscribeRecord(data){
+       return Serve.post('/user/subscribeRecords',data)
+   }
+   Subscride
+}
+
+export default Subscribe;

+ 172 - 0
api/wallet.js

@@ -0,0 +1,172 @@
+
+import Serve from '@/api/serve'
+
+class Wallet {
+ 
+    // 提币记录
+    static withdrawalRecord() {
+        return Serve.post(`/user/withdrawalRecord`);
+    }
+
+    // 充值记录
+    static depositHistory() {
+        return Serve.post(`/user/depositHistory`);
+    }
+
+    // 钱包划转记录
+    static transferRecord() {
+        return Serve.post(`/user/transferRecord`);
+    }
+
+    // 个人资产管理
+    static personalAssets() {
+        return Serve.post(`/user/personalAssets`);
+    }
+
+    // 各个币种的资产
+    static fundAccount(data) {
+        return Serve.post(`/user/fundAccount`, data);
+    }
+
+    // 代币以及对应的余额
+    static tokenList(data) {
+        return Serve.post(`/user/tokenList`, data);
+    }
+
+    // 资金划转
+    static fundsTransfer(data) {
+        return Serve.post(`/user/appFundsTransfer`, data);
+    }
+
+    // 生成充值地址
+    static walletImage(data) {
+        return Serve.post(`/user/walletImage`, data);
+    }
+
+    // 提交充值
+    static recharge(data) {
+        return Serve.post(`/user/recharge`, data);
+    }
+	// 信用卡充值
+	static xinRecharge(data) {
+	    return Serve.post(`/back/sendPay`, data);
+	}
+    // 提交提币
+    static withdraw(data) {
+        return Serve.post(`/user/withdraw`, data);
+    }
+
+    // 提币地址管理
+    static getUserWithdrawAdress(data) {
+        return Serve.post(`/user/withdrawalAddressManagement`, data);
+    }
+    
+    // 编辑提币地址
+    static editUserWithdrawAdress (data) {
+        return Serve.post(`/user/editUserAdress`, data);
+    }
+
+    // 删除提币地址
+    static delUserWithdrawAdress(data) {
+        return Serve.post(`/user/withdrawalAddressManagement`, data);
+    }
+
+    // 添加提币地址
+    static addUserWithdrawAdress(data) {
+        return Serve.post(`/`, data);
+    }
+
+    static withdrawalSelectAddress() {
+        return Serve.post(`/user/withdrawalSelectAddress`);
+    }
+
+    static addWithdrawAddress(data) {
+        return Serve.post(`/user/withdrawalAddressAdd`, data);
+    }
+
+
+    // 查询币种余额
+    static withdrawalBalance(data) {
+        return Serve.post(`/user/withdrawalBalance`, data);
+    }
+
+    // 修改用户地址
+    static withdrawalAddressModify(data) {
+        return Serve.post(`/user/withdrawalAddressModify`, data);
+    }
+
+    // 删除币种地址
+    static withdrawalAddressDeleted(data) {
+        return Serve.post(`/user/withdrawalAddressDeleted`, data);
+    }
+    // 资金余额
+    static appTokenAssets(data){
+        return Serve.post(`/user/appTokenAssets`,data)
+    }
+    // 指定币种划转记录
+    static appTransferRecord(data){
+        return Serve.post(`/user/appTransferRecord`,data)
+    }
+    // 指定币种提币记录
+    static appWithdrawalRecord(data){
+        return Serve.post(`/user/appWithdrawalRecord`,data)
+    }
+    // 指定币种提币记录
+    static appDepositHistory(data){
+        return Serve.post(`/user/appDepositHistory`,data)
+    }
+    // 指定币种币币记录
+    static getWalletLogs(data){
+        return Serve.get(`/user/getWalletLogs`,data)
+    }
+     // 生成钱包地址
+     static createWalletAddress() {
+        return Serve.post(`/user/createWalletAddress`);
+    }
+     // 获取充币地址
+    static collectionType(data) {
+        return Serve.post(`/user/collectionType`, data)
+    }
+     // 获取转换列表
+     static accounts(data){
+        return Serve.get(`/wallet/accounts`, data)
+    }
+    // 获取子账户类别
+    static accountPairList(data){
+        return Serve.get('/wallet/accountPairList',data)
+    }
+    // 获取转换币种列表
+    static coinList(data){
+        return Serve.get('/wallet/coinList',data)
+    }
+    // 获取余额
+    static getBalance(data){
+        return Serve.get('/wallet/getBalance',data)
+    }
+    // 资金划转
+    static transfer(data){
+        return Serve.post('/wallet/transfer',data)
+    }
+    // 合约资金列表
+    static accountList(data){
+        return Serve.get('/contract/accountList',data)
+    }
+    // 合约资金流水
+    static accountFlow(data){
+        return Serve.get('/contract/accountFlow',data)
+    }
+    // 合约资金流水
+    static cancelWithdraw(data){
+        return Serve.post('/user/cancelWithdraw',data,{loading:true})
+    }
+	
+	static fundFiat(data) {
+	    return Serve.post(`/back/fiat`, data);
+	}
+	
+	static getQb(data) {
+		 return Serve.get(`/coin/code`, data);
+	}
+}
+
+export default Wallet;

+ 73 - 0
app.js

@@ -0,0 +1,73 @@
+let config = {};
+if (process.env.NODE_ENV == "production") {
+    //生产环境
+    config = {
+        // ajax地址 bitkan
+        baseUrl: "https://service.gsras.com",
+        // 图片地址 (暂时无用)
+        imgUrl: "https://service.gsras.com/storage",
+        // socket地址
+        socketUrl: "wss://service.gsras.com/ws1",
+        socketUrl1: "wss://service.gsras.com/ws2",
+        // pc端地址
+        pcUrl: "https://service.gsras.com",
+        // app名称
+        appName: "GSR",
+        // 版本
+        version: "1.1.7",
+        // 移动端地址
+        mobile: "https://service.gsras.com",
+    };
+} else {
+	// server.jys.cheni.cc
+    //开发环境
+    config = {
+        // ajax地址 bitkan
+        baseUrl: "https://service.gsras.com",
+        // 图片地址 (暂时无用)
+        imgUrl: "https://service.gsras.com/storage",
+        // socket地址
+        socketUrl: "wss://service.gsras.com/ws1",
+        socketUrl1: "wss://service.gsras.com/ws2",
+        // pc端地址
+        pcUrl: "https://service.gsras.com",
+        // app名称
+        appName: "GSR",
+        // 版本
+        version: "1.1.7",
+        // 移动端地址
+        mobile: "https://service.gsras.com",
+    };
+    // config = {
+    //     baseUrl: 'http://qkladmin2.ruanmeng.top',
+    //     imgUrl: 'http://qkladmin2.ruanmeng.top/upload',
+    //     socketUrl: 'ws://qkladmin2.ruanmeng.top:2346/',
+    //     socketUrl1: 'ws://qkladmin2.ruanmeng.top:2348/',
+    //     // pc端地址
+    //     pcUrl:'http://qklhome2.ruanmeng.top',
+    //     // app名称
+    //     appName: '本地开发',
+    //     // 版本
+    //     version: '0.0.0',
+    //     // 移动端地址
+    //     mobile: ''
+    // }
+    // config = {
+    //     // ajax地址
+    //     baseUrl: 'https://server.7coin.in',
+    //     // 图片地址 (暂时无用)
+    //     imgUrl: 'https://server.7coin.in/upload',
+    //     // socket地址
+    //     socketUrl: 'wss://server.7coin.in:2346/',
+    //     socketUrl1: 'wss://server.7coin.in:2348/',
+    //     // pc端地址
+    //     pcUrl:'https://www.7coin.in',
+    //     // app名称
+    //     appName: '7COIN test',
+    //     // 版本
+    //     version: '1.0.0',
+    //     // 移动端地址
+    //     mobile: 'https://h5.7coin.in'
+    // };
+}
+export default config;

二進制
assets/fontIcon/iconfont.eot


文件差異過大導致無法顯示
+ 43 - 0
assets/fontIcon/iconfont.svg


二進制
assets/fontIcon/iconfont.ttf


二進制
assets/fontIcon/iconfont.woff


二進制
assets/fontIcon/iconfont.woff2


二進制
assets/img/7coin_qidongye.gif


二進制
assets/img/Bitmap3x.png


二進制
assets/img/Fill13x.png


二進制
assets/img/Upload_File3x.png


二進制
assets/img/auth_fanmian.png


二進制
assets/img/auth_shouchi.png


二進制
assets/img/auth_zhengmain.png


二進制
assets/img/fenzu23x.png


二進制
assets/img/fenzu_73x.png


二進制
assets/img/initve.png


二進制
assets/img/invite-1.png


二進制
assets/img/invite-2.png


二進制
assets/img/invite-3.png


二進制
assets/img/invite-4.png


二進制
assets/img/invite-5.png


二進制
assets/img/invite-6.png


二進制
assets/img/invite-bg.png


二進制
assets/img/invite-fy.png


二進制
assets/img/invite-sy.png


二進制
assets/img/invite-tg.png


二進制
assets/img/invite-yq.png


二進制
assets/img/shengji.png


+ 609 - 0
assets/scss/app.scss

@@ -0,0 +1,609 @@
+// 样式重置
+table {
+  border-collapse: collapse;
+
+  th,
+  td {
+    border-color: inherit;
+  }
+}
+
+p {
+  margin: 0;
+}
+
+input {
+  background: transparent;
+  border: none;
+}
+ul {
+  padding: 0;
+  margin: 0;
+  list-style: none;
+}
+// 边框
+$dir1: (top t, right r, bottom b, left l);
+$dir2: (top left tl, top right tr, bottom right br, bottom left bl);
+// 颜色
+$color-list: (
+  plain $plain,
+  dark $black,
+  light $light,
+  gray-1 $gray-1,
+  gray-2 $gray-2,
+  gray-3 $gray-3,
+  gray-4 $gray-4,
+  gray-5 $gray-5,
+  gray-6 $gray-6,
+  gray-7 $gray-7,
+  gray-8 $gray-8,
+  gray-9 $gray-9,
+  danger $red,
+  primary $blue,
+  warning $orange,
+  yellows $yellow,
+  warning-dark $orange-dark,
+  warning-light $orange-light,
+  success $green,
+  buy $buy,
+  sell $sell,
+  theme-1 $theme-1,
+  panel-1 $panel-1,
+  panel-2 $panel-2,
+  panel-3 $panel-3,
+  panel-4 $panel-4,
+  panel-5 $panel-5,
+  panel-6 $panel-6,
+  form-panel-4 $form-panel-4,
+  form-panel-3 $form-panel-3,
+  tab-nav $tab-nav
+);
+// 颜色(不包含css变量)
+$color-list2: (
+  dark $black,
+  gray-1 $gray-1,
+  gray-2 $gray-2,
+  gray-3 $gray-3,
+  gray-4 $gray-4,
+  gray-5 $gray-5,
+  gray-6 $gray-6,
+  gray-7 $gray-7,
+  gray-8 $gray-8,
+  gray-9 $gray-9,
+  danger $red,
+  primary $blue,
+  warning $orange,
+  yellows $yellow,
+  warning-dark $orange-dark,
+  warning-light $orange-light,
+  success $green,
+  buy $buy,
+  sell $sell,
+  theme-1 $theme-1,
+  panel-5 $panel-5,
+  panel-6 $panel-6
+);
+// 间距
+$pad: (
+  base: $padding-base,
+  xs: $padding-xs,
+  sm: $padding-sm,
+  md: $padding-md,
+  lg: $padding-lg,
+  xl: $padding-xl,
+);
+
+// 公用样式
+.d-flex {
+  display: flex;
+}
+
+.d-inline-flex {
+  display: inline-flex;
+}
+
+.d-inline {
+  display: inline;
+}
+
+.d-inline-block {
+  display: inline-block;
+}
+
+.d-block {
+  display: block;
+}
+
+.justify-center {
+  justify-content: center;
+}
+
+.justify-between {
+  justify-content: space-between;
+}
+
+.justify-around {
+  justify-content: space-around;
+}
+
+.justify-start {
+  justify-content: flex-start;
+}
+
+.justify-end {
+  justify-content: flex-end;
+}
+
+.align-center {
+  align-items: center;
+}
+
+.align-stretch {
+  align-items: stretch;
+}
+
+.align-start {
+  align-items: flex-start;
+}
+
+.align-end {
+  align-items: flex-end;
+}
+
+.flex-fill {
+  flex: 1;
+}
+
+.flex-col {
+  flex-direction: column;
+}
+
+.flex-wrap {
+  flex-wrap: wrap;
+}
+
+.flex-shrink {
+  flex-shrink: 0;
+}
+
+.float-r {
+  float: right;
+}
+
+.float-l {
+  float: left;
+}
+
+.clear {
+  &::after {
+    display: block;
+    content: "";
+    clear: both;
+  }
+}
+
+// 字体尺寸
+.fn-xs {
+  font-size: $font-size-xs;
+}
+
+.fn-sm {
+  font-size: $font-size-sm;
+}
+
+.fn-md {
+  font-size: $font-size-md;
+}
+
+.fn-lg {
+  font-size: $font-size-lg;
+}
+
+.fn-bold {
+  font-weight: bold;
+}
+
+.fn-center {
+  text-align: center;
+}
+
+.fn-left {
+  text-align: start;
+}
+
+.fn-right {
+  text-align: end;
+}
+
+.fn-middle {
+  vertical-align: middle;
+}
+
+.fn-wrap {
+  white-space: normal;
+  word-break: break-word;
+}
+
+.fn-nowrap {
+  white-space: nowrap;
+}
+
+$i: 10;
+
+@while $i <=40 {
+  .fn-#{$i} {
+    font-size: $i + px;
+  }
+
+  $i: $i + 1;
+}
+
+$i: 1;
+
+@while $i<=4 {
+  .eps-#{$i} {
+    @include eps($i);
+  }
+
+  $i: $i + 1;
+}
+
+.color-default {
+  color: $text-color;
+}
+
+@each $item1, $item2 in $color-list {
+  .color-#{$item1} {
+    color: $item2;
+  }
+  .bg-#{$item1} {
+    background-color: $item2;
+  }
+  .border-#{$item1} {
+    &::after {
+      border-color: $item2 !important;
+    }
+  }
+  .border-#{$item1}-original {
+    border-color: $item2 !important;
+  }
+}
+@each $item1, $item2 in $color-list2 {
+  .bg-#{$item1}-transparent {
+    background: rgba($item2, 0.1);
+  }
+}
+
+.border {
+  // border: 1px solid $border-color;
+  position: relative;
+
+  &::after {
+    @include harirline-common();
+    border: 1px solid $border-color;
+  }
+}
+.bg-gradient-blue {
+  background: $gradient-blue;
+}
+.bg-gradient-red {
+  background: $gradient-red;
+}
+.bg-gradient-green {
+  background: $gradient-green;
+}
+
+.border-original {
+  border: 1.02px solid $border-color;
+}
+
+.border-original-0 {
+  border-width: 0px;
+}
+
+@each $item1, $item2 in $dir1 {
+  .border-#{$item2} {
+    position: relative;
+
+    &::after {
+      @include harirline-common();
+      border-#{$item1}: 1px solid $border-color;
+    }
+  }
+
+  .border-#{$item2}-original {
+    border-#{$item1}: 1.02px solid $border-color;
+  }
+
+  .border-#{$item2}-0 {
+    &::after {
+      border-#{$item1}: 0 !important;
+    }
+  }
+
+  .border-#{$item2}-original-0 {
+    border-#{$item1}: 0 !important;
+  }
+}
+
+.rounded {
+  border-radius: $border-radius-md;
+}
+
+.rounded-xs {
+  border-radius: $border-radius-xs;
+}
+.rounded-sm {
+  border-radius: $border-radius-sm;
+}
+
+.rounded-md {
+  border-radius: $border-radius-md;
+}
+
+.rounded-lg {
+  border-radius: $border-radius-lg;
+}
+
+.rounded-max {
+  border-radius: $border-radius-max;
+}
+
+@each $item1, $item2, $item3 in $dir2 {
+  .rounded-#{$item3}-xs {
+    border-#{$item1}-#{$item2}-radius: $border-radius-xs;
+  }
+  .rounded-#{$item3}-sm {
+    border-#{$item1}-#{$item2}-radius: $border-radius-sm;
+  }
+
+  .rounded-#{$item3}-md {
+    border-#{$item1}-#{$item2}-radius: $border-radius-md;
+  }
+
+  .rounded-#{$item3}-lg {
+    border-#{$item1}-#{$item2}-radius: $border-radius-lg;
+  }
+
+  .rounded-#{$item3}-max {
+    border-#{$item1}-#{$item2}-radius: $border-radius-max;
+  }
+}
+
+@each $idx, $var in $pad {
+  // 外间距
+  .m-#{$idx} {
+    margin: $var !important;
+  }
+
+  .m-x-#{$idx} {
+    margin-left: $var !important;
+    margin-right: $var !important;
+  }
+
+  .m-y-#{$idx} {
+    margin-top: $var !important;
+    margin-bottom: $var !important;
+  }
+
+  @each $item1, $item2 in $dir1 {
+    .m-#{$item2}-#{$idx} {
+      margin-#{$item1}: $var !important;
+    }
+  }
+
+  // 内间距
+  .p-#{$idx} {
+    padding: $var !important;
+  }
+
+  .p-x-#{$idx} {
+    padding-left: $var !important;
+    padding-right: $var !important;
+  }
+
+  .p-y-#{$idx} {
+    padding-top: $var !important;
+    padding-bottom: $var !important;
+  }
+
+  @each $item1, $item2 in $dir1 {
+    .p-#{$item2}-#{$idx} {
+      padding-#{$item1}: $var !important;
+    }
+  }
+}
+
+.m-x-auto {
+  margin-left: auto;
+  margin-right: auto;
+}
+.m-l-auto {
+  margin-left: auto;
+}
+.m-r-auto {
+  margin-right: auto;
+}
+.box-size {
+  box-sizing: border-box;
+}
+
+// 尺寸
+$i: 1;
+
+@while $i <=100 {
+  .w-#{$i} {
+    width: $i + px;
+  }
+  .min-w-#{$i} {
+    min-width: $i + px;
+  }
+  .max-w-#{$i} {
+    max-width: $i + px;
+  }
+
+  .h-#{$i} {
+    height: $i + px;
+  }
+  .min-h-#{$i} {
+    min-height: $i + px;
+  }
+  .max-h-#{$i} {
+    max-height: $i + px;
+  }
+
+  $i: $i + 1;
+}
+
+$i: 1;
+
+@while $i <=12 {
+  .w-#{$i}\/#{12} {
+    width: #{$i/12 * 100}#{"%"};
+  }
+
+  .h-#{$i}\/#{12} {
+    height: #{$i/12 * 100}#{"%"};
+  }
+
+  $i: $i + 1;
+}
+
+.w-max {
+  width: 100%;
+}
+
+.h-max {
+  height: 100%;
+}
+
+$i: 1;
+
+@while $i <=4 {
+  .line-height-#{$i} {
+    line-height: $i;
+  }
+
+  $i: $i + 1;
+}
+
+// 公共布局
+.layout-page {
+  display: flex;
+  flex-direction: column;
+  height: auto;
+  min-height: 100vh;
+  background: $bg;
+}
+
+.layout-main {
+  overflow: auto;
+  -webkit-overflow-scrolling: touch;
+  // flex: 1;
+}
+
+// 引入字体图标
+@font-face {
+  font-family: "iconfont";
+  src: url("./assets/fontIcon/iconfont.eot?t=1594112878280");
+  /* IE9 */
+  src: url("./assets/fontIcon/iconfont.eot?t=1594112878280#iefix")
+      format("embedded-opentype"),
+    /* IE6-IE8 */ url("./assets/fontIcon/iconfont.woff?t=1594112878280")
+      format("woff"),
+    url("./assets/fontIcon/iconfont.ttf?t=1594112878280") format("truetype"),
+    /* chrome, firefox, opera, Safari, Android, iOS 4.2+ */
+      url("./assets/fontIcon/iconfont.svg?t=1594112878280#iconfont")
+      format("svg");
+  /* iOS 4.1- */
+}
+
+.iconfont {
+  font-family: "iconfont" !important;
+  font-style: normal;
+  -webkit-font-smoothing: antialiased;
+  -moz-osx-font-smoothing: grayscale;
+}
+
+// 富文本容器
+.edit-content {
+  img {
+    max-width: 100%;
+    height: auto;
+  }
+}
+
+.overflow-hidden {
+  overflow: hidden;
+}
+
+.overflow-scroll {
+  overflow: auto;
+}
+
+// 背景选中
+.link-active {
+  &:active {
+    background: $panel-1;
+  }
+}
+
+// 拟态投影 panel-4
+.shadow-panel-4 {
+  box-shadow: var(--mimicry-shadow);
+}
+.box-shadow {
+  box-shadow: $shadow;
+}
+.transition-default {
+  transition: all 0.3s;
+}
+
+navigator {
+  display: inline-block;
+}
+// picker
+.lb-picker-header {
+  &::before,
+  &::after {
+    border: none !important;
+  }
+}
+.lb-picker-header-actions {
+  background-color: $panel-3;
+}
+.uni-picker-view-indicator {
+  &::before,
+  &::after {
+    border: none !important;
+  }
+}
+.lb-picker-content {
+  background-color: $panel-4 !important;
+}
+.uni-picker-view-mask {
+  background: var(--picker-mask);
+  background-position: top, bottom;
+  background-size: 100% 102px;
+  background-repeat: no-repeat;
+}
+.lb-picker-action-confirm-text {
+  color: $green !important;
+}
+
+.app-nav {
+  height: $status-bar;
+}
+.padding-nav {
+  padding-top: $status-bar;
+}
+.bg-form-panel-5 {
+  background-image: var(--form-panel-5);
+}
+.bg-panel-7 {
+  background-image: var(--form-panel-7);
+}
+:focus-visible {
+  outline: 0;
+}
+@import "./vant.scss";

+ 3 - 0
assets/scss/base.scss

@@ -0,0 +1,3 @@
+@import './theme.scss';
+@import './size.scss';
+@import './mixin.scss';

+ 22 - 0
assets/scss/mixin.scss

@@ -0,0 +1,22 @@
+// 边框
+@mixin harirline-common {
+    position: absolute;
+    box-sizing: border-box;
+    display: block;
+    content: ' ';
+    pointer-events: none;
+    border-radius: inherit;
+    top: -50%;
+    right: -50%;
+    bottom: -50%;
+    left: -50%;
+    transform: scale(0.5);
+}
+
+// 溢出省略
+@mixin eps($num:1) {
+    display: -webkit-box;
+    -webkit-box-orient: vertical;
+    -webkit-line-clamp: $num;
+    overflow: hidden;
+}

+ 23 - 0
assets/scss/size.scss

@@ -0,0 +1,23 @@
+// 字体尺寸
+$font-size-xs: 10px;
+$font-size-sm: 12px;
+$font-size-md: 14px;
+$font-size-lg: 16px;
+$font-weight-bold: 500;
+
+$border-radius-xs: 6px;
+$border-radius-sm: 14px;
+$border-radius-md: 20px;
+$border-radius-lg: 26px;
+$border-radius-max: 50%;
+
+// 间距
+$padding-base: 4px;
+$padding-xs: 10px;
+$padding-sm: 12px;
+$padding-md: 15px;
+$padding-lg: 20px;
+$padding-xl: 25px;
+
+// 状态栏高度
+$status-bar: var(--status-bar-height);

+ 72 - 0
assets/scss/theme.scss

@@ -0,0 +1,72 @@
+// -------不可修改↓--------
+$black: #000 !default;
+$white: #fff !default;
+$gray-1: #f7f8fa !default;
+$gray-2: #f2f3f5 !default;
+$gray-3: #ebedf0 !default;
+$gray-4: #dcdee0 !default;
+$gray-5: #c8c9cc !default;
+$gray-6: #888894 !default;
+$gray-7: var(--gray-7, #646566) !default;
+$gray-8: #323233 !default;
+$gray-9: #202635 !default;
+$red: #ee0a24 !default;
+$blue: #1989fa !default;
+$orange: #ff976a !default;
+$yellow: #f8b936 !default;
+$orange-dark: #ed6a0c !default;
+$orange-light: #fffbe8 !default;
+$green: #79d47c !default;
+// 采用字体样式
+$base-font-family: -apple-system, BlinkMacSystemFont, "Helvetica Neue",
+  Helvetica, Segoe UI, Arial, Roboto, "PingFang SC", "Hiragino Sans GB",
+  "Microsoft Yahei", sans-serif;
+$price-integer-font-family: Avenir-Heavy, PingFang SC, Helvetica Neue, Arial,
+  sans-serif;
+// --------------------
+// ------随便你改↓-------
+$plain: var(--plain, #fff);
+
+$theme-1: #f7427b !default;
+// $theme-1: #bf5fc1 !default;
+// $buy: #79d47c !default;
+$buy: #1ec692 !default;
+// $sell: #ea3131 !default;
+$sell: #df3f73 !default;
+$panel-1: var(--panel-1, #2a2a38) !default;
+$panel-2: var(--panel-2, #343445) !default;
+$panel-3: var(--panel-3, #393948) !default;
+$panel-4: var(--panel-4, #484859) !default;
+$panel-5: #202635 !default;
+$panel-6: #646566 !default;
+$form-panel-3: var(--form-panel-3, #393948) !default;
+$form-panel-4: var(--form-panel-4, #484859) !default;
+$text-color: var(--text-color, #9fa6b5) !default;
+$active-color: $gray-2 !default;
+$active-opacity: 0.7 !default;
+$disabled-opacity: 0.5 !default;
+$text-link-color: #576b95 !default;
+$light: var(--light, #333);
+
+$bg-top: var(--bg-top, #383847);
+$bg-bottom: var(--bg-bottom, #242230);
+$bg: linear-gradient(to bottom, $bg-top, $bg-bottom);
+$tab-nav: var(--tab-nav, #31313f);
+
+// 边框
+
+$border-color: var(--border-color, #49495f) !default;
+
+// 阴影
+$shadow: var(--shadow, 0px 0px 33px 0px rgba(34, 34, 44, 0.25));
+
+// 渐变
+$gradient-blue: linear-gradient(94deg, #42cbff 0%, #015afe 100%);
+$gradient-green: linear-gradient(328deg, #07c087 0%, #07c087 100%);
+$gradient-red: linear-gradient(135deg, #ff7c6e 0%, #f7427b 100%);
+
+// 底部阴影
+$tab-nav-shadow: var(
+  --tab-nav-shadow,
+  0px -7px 20px 0px rgba(37, 37, 48, 0.83)
+);

+ 235 - 0
assets/scss/vant.scss

@@ -0,0 +1,235 @@
+:root {
+  --tabs-nav-background-color: transparent;
+  --button-plain-background-color: transparent;
+  // --button-primary-background-color: #79d47c;
+  --button-primary-background-color: #07c087;
+  // --button-primary-border-color: #79d47c;
+  --button-primary-border-color: #07c087;
+  --button-default-background-color: $panel-3;
+  --tab-text-color: #888894;
+  --button-default-border-color: #49495f;
+  --button-default-background-color: transparent;
+}
+.pledgeMain {
+  background: var(--bglight);
+}
+.pledgeRonqi {
+  background: var(--ronqi);
+  overflow: auto;
+}
+
+.pledgeListTitle {
+  color: var(--gray);
+  font-size: 28upx;
+  margin-bottom: 13upx;
+}
+.pledgeFirstTitle {
+  width: 447upx;
+  overflow: hidden;
+  color: var(--dd);
+}
+.pledgeIntroduce {
+  width: 100%;
+  border-radius: 60upx 60upx 15upx 0;
+  background: var(--list);
+}
+.pledgeDetailForm {
+  margin-top: 15upx;
+  padding: 0 30upx;
+  background: var(--list);
+}
+.pledgeDetailList {
+  padding: 30upx 0;
+  border-bottom: 2px solid var(--listline);
+  text-align: left;
+  position: relative;
+}
+.ordermain {
+  padding: 27upx 30upx;
+  background: var(--list);
+  margin-bottom: 20upx;
+}
+.orderTitle {
+  font-size: 30upx;
+  color: var(--dd);
+  margin-bottom: 20upx;
+}
+.cycleDiv_in {
+  width: 160upx;
+  height: 160upx;
+  border-radius: 50%;
+  top: 25upx;
+  margin: auto;
+  position: relative;
+  background: var(--list);
+  z-index: 10;
+}
+
+.cycle_mengban {
+  width: 55px;
+  height: 55px;
+  border-radius: 0 100% 0 0;
+  position: absolute;
+  top: 0;
+  right: 0;
+  background: var(--menban);
+}
+
+.van-toast__loading {
+  color: $theme-1;
+}
+
+// nav-bar
+.van-nav-bar__left {
+  .van-nav-bar__arrow {
+    color: $text-color;
+    font-weight: bold;
+  }
+}
+
+.van-hairline--bottom::after {
+  border-color: $border-color;
+}
+
+.van-nav-bar__content {
+  min-height: var(--nav-bar-height, 44px);
+}
+
+.layout-page {
+  .van-swipe,
+  .van-tab--active {
+    color: var(--nav-tab-active, #fff);
+  }
+
+  .van-nav-bar__title {
+    color: $light;
+  }
+
+  .van-nav-bar {
+    background-color: transparent;
+    &.van-hairline--bottom:after {
+      border-bottom-width: 0;
+    }
+  }
+
+  .van-popup {
+    background-color: $panel-3;
+  }
+  .van-popup--left {
+    background: $bg;
+  }
+  .van-field__input {
+    color: $light;
+  }
+
+  .van-steps {
+    background-color: transparent;
+  }
+
+  .van-stepper__minus,
+  .van-stepper__plus {
+    background-color: $panel-3;
+    color: $light;
+  }
+
+  .van-stepper__input {
+    background-color: $panel-3;
+    color: $light;
+  }
+
+  .van-count-down {
+    color: $light;
+  }
+
+  // search
+  .van-search {
+    background-color: transparent !important;
+
+    .van-search__content {
+      background-color: $panel-4 !important;
+      border-radius: 20px;
+    }
+  }
+  .van-hairline--bottom:after,
+  .van-hairline--left:after,
+  .van-hairline--right:after,
+  .van-hairline--surround:after,
+  .van-hairline--top-bottom:after,
+  .van-hairline--top:after,
+  .van-hairline:after {
+    border-color: var(--border-color);
+  }
+  .van-tag {
+    background-color: transparent;
+    color: $light;
+  }
+  .van-tab {
+    font-size: $font-size-md;
+  }
+  .van-tabs__line {
+    background-color: transparent;
+    height: 6px;
+    &::before {
+      content: "";
+      display: block;
+      position: absolute;
+      left: 50%;
+      top: 0;
+      transform: translateX(-50%);
+      width: 6px;
+      height: 6px;
+      border-radius: 50%;
+      background-color: var(--nav-tab-active, #fff);
+    }
+  }
+  .van-search__action,
+  .van-cell {
+    color: $light;
+  }
+  .van-button--default {
+    .van-button__text {
+      // color: $text-color;
+    }
+    &.van-dialog__confirm {
+      .van-button__text {
+        // color: $blue;
+      }
+    }
+  }
+}
+.vant-toast-index {
+  position: relative;
+  z-index: 99999999;
+}
+// picker
+.van-picker {
+  background-color: $panel-4;
+}
+
+.van-picker-column__item {
+  color: $light;
+}
+
+[class*="van-hairline"]::after {
+  border-color: $border-color;
+}
+
+.van-number-keyboard__keys {
+  color: $gray-9;
+}
+
+.vant-popup-index {
+  position: fixed;
+  z-index: 6;
+}
+
+.default .van-button {
+  color: $black !important;
+}
+
+.layout-page {
+  .van-tabs__wrap--scrollable .van-tabs__nav--complete {
+    padding-left: 0;
+    padding-right: 0;
+  }
+}

二進制
base_info/ww.gsr.keystore


二進制
base_info/wwlogo.png


+ 70 - 0
components/bing-progress/bing-progress.css

@@ -0,0 +1,70 @@
+.bing-progress {
+	position: relative;
+	/* #ifndef APP-NVUE */
+	display: flex;
+	/* #endif */
+	flex-direction: row;
+	align-items: center;
+	justify-content: space-around;
+	overflow: hidden;
+}
+.bp-marea {
+	/* #ifndef APP-NVUE */
+	display: flex;
+	/* #endif */
+	position: absolute;
+	left: 0;
+	top: 0;
+	flex-direction: row;
+	align-items: center;
+	text-align: center;
+	justify-content: space-around;
+	background-color: rgba(0,0,0,0);
+	z-index: 6;
+}
+.bp-mview,
+.bp-handle {
+	position: absolute;
+	/* #ifndef APP-NVUE */
+	display: flex;
+	/* #endif */
+	flex-direction: row;
+	align-items: center;
+	text-align: center;
+	justify-content: center;
+	z-index: 5;
+	overflow: hidden;
+}
+.bp-handle-text {
+	text-align: center;
+	z-index: 5;
+	overflow: hidden;
+}
+.bp-bar_max {
+	position: absolute;
+	/* #ifndef APP-NVUE */
+	display: flex;
+	/* #endif */
+	flex-direction: row;
+	align-items: center;
+	margin: 0;
+	padding: 0;
+	z-index: 1;
+	overflow: hidden;
+}
+.bp-bar_active {
+	position: absolute;
+	z-index: 3;
+	overflow: hidden;
+}
+.bp-bar_sub_active {
+	position: absolute;
+	z-index: 2;
+	overflow: hidden;
+}
+.bp-value {
+	position: absolute;
+	text-align: center;
+	overflow: hidden;
+	z-index: 4;
+}

+ 727 - 0
components/bing-progress/bing-progress.vue

@@ -0,0 +1,727 @@
+<template>
+	<view class="bing-progress" :style="{width:bpWidth,height:bpHeight,borderRadius:borderRadius,
+	backgroundColor:backgroundColor,flexDirection:direction!='vertical'?'row':'column'}">
+		<!-- 进度 -->
+		<!-- #ifdef APP-NVUE -->
+		<div class="bp-bar_max"
+		:style="{width:barMaxWidth,height:barMaxHeight,backgroundColor:noActiveColor,
+		flexDirection:direction!='vertical'?'row':'column',left:barMaxLeft,borderRadius:barBorderRadius}">
+			<div class="bp-bar_sub_active"
+			:style="{width:barSubActiveWidth,height:barSubActiveHeight,backgroundColor:subActiveColor,
+			top:subActiveTop,bottom:subActiveBottom,left:subActiveLeft,right:subActiveRight,borderRadius:isActiveCircular?barBorderRadius:0}"></div>
+			<div class="bp-bar_active"
+			:style="{width:barActiveWidth,height:barActiveHeight,backgroundColor:activeColor,
+			top:activeTop,bottom:activeBottom,left:activeLeft,right:activeRight,borderRadius:isActiveCircular?barBorderRadius:0}"></div>
+		</div>
+		<!-- #endif -->
+		<!-- #ifndef APP-NVUE -->
+		<view class="bp-bar_max"
+		:style="{width:barMaxWidth,height:barMaxHeight,backgroundColor:noActiveColor,borderRadius:barBorderRadius,
+		flexDirection:direction!='vertical'?'row':'column',left:barMaxLeft}">
+			<view class="bp-bar_sub_active"
+			:style="{width:barSubActiveWidth,height:barSubActiveHeight,backgroundColor:subActiveColor,
+			top:subActiveTop,bottom:subActiveBottom,left:subActiveLeft,right:subActiveRight,borderRadius:isActiveCircular?barBorderRadius:0}"></view>
+			<view class="bp-bar_active"
+			:style="{width:barActiveWidth,height:barActiveHeight,backgroundColor:activeColor,
+			top:activeTop,bottom:activeBottom,left:activeLeft,right:activeRight,borderRadius:isActiveCircular?barBorderRadius:0}"></view>
+		</view>
+		<!-- #endif -->
+		<movable-area class="bp-marea" @touchmove.stop.prevent="touchmove" @touchstart="touchstart" @touchcancel="touchend" @touchend="touchend"
+		:style="{width:mareaWidth,height:mareaHeight,left:mareaLeft}">
+			<!-- 拖柄 -->
+			<movable-view class="bp-mview" :direction="direction=='vertical'?'vertical':'horizontal'" :animation="false"
+			 :disabled="true" :x="handleX" :y="handleY" friction="10" damping="100"
+			:style="{width:mhandleWidth,height:mhandleHeight,backgroundColor:handleColor,
+			borderRadius:handleBorderRadius,fontSize:infoFontSize,top:mhandleTop}">
+				<view class="bp-handle" :style="{fontSize:infoFontSize,width:mhandleWidth,height:mhandleHeight,borderRadius:handleBorderRadius}">
+					<image class="bp-handle-img" :src="handleImgUrl" v-if="handleImgUrl" 
+					:style="{fontSize:infoFontSize,width:mhandleWidth,height:mhandleHeight,borderRadius:handleBorderRadius}"></image>
+					<text class="bp-handle-text" v-if="handleImgUrl=='' && infoAlign=='handle' && showInfo" 
+					:style="{fontSize:infoFontSize,color:infoColor,width:mhandleWidth,height:textHeight,borderRadius:'20px'}">{{ infoContent=='subValue'?msubValue:showValue }}{{ infoEndText }}</text>
+				</view>
+			</movable-view>
+		</movable-area>
+		<text class="bp-value" v-if="showValueState() || (infoAlign=='center'&&direction!='vertical' && showInfo)" 
+		:style="{color:infoColor,fontSize:infoFontSize,left:valueLeft,width:valueWidth()+'px'}">{{ infoContent=='subValue'?msubValue:showValue }}{{ infoEndText }}</text>
+	</view>
+</template>
+
+<script>
+	/**
+	 * 进度条,副进度条
+	 */
+	export default {
+		created() {
+			/**
+			 * 获取系统屏幕信息,用于后续单位换算
+			 */
+			const systemInfo = uni.getSystemInfoSync()
+			this.px2rpx = 750 / systemInfo.screenWidth
+			this.screenWidth = systemInfo.screenWidth
+			this.screenHeight = systemInfo.screenHeight
+		},
+		mounted() {
+			// #ifndef APP-NVUE
+			/**
+			 * 非NVUE滑动事件获取到的位置是相对于屏幕的,获取组件位置,用于计算滑块位置
+			 */
+			let query = uni.createSelectorQuery().in(this)
+			query.select('.bing-progress').boundingClientRect(data => {
+				this.mainInfo.top = data.top
+				this.mainInfo.left = data.left
+				this.mainInfo.bottom = data.bottom
+				this.mainInfo.right = data.right
+			}).exec()
+			// #endif
+	
+			this.percent = Math.abs((this.valueFormat(this.value) - this.min) / (this.max - this.min))
+			this.subPercent = Math.abs((this.valueFormat(this.subValue,true) - this.min) / (this.max - this.min))
+			if(this.reverse) {
+				if(this.direction!='vertical') {
+					this.handleX = (1 - this.percent) * this.barMaxLength
+				}
+				else {
+					this.handleY = this.percent * this.barMaxLength
+				}
+			}
+			else {
+				if(this.direction!='vertical') {
+					this.handleX = this.percent * this.barMaxLength
+				}
+				else {
+					this.handleY = (1 - this.percent) * this.barMaxLength
+				}
+			}
+		},
+		/**
+		 * sub表示副进度条属性
+		 */
+		props: {
+			width: {
+				type: String,
+				default: '300px'
+			},
+			strokeWidth: {
+				type: String,
+				default: '30px'
+			},
+			backgroundColor: {
+				type: String,
+				default: 'rgba(0,0,0,0)'
+			},
+			noActiveColor: {
+				type: String,
+				default: "#00ffff"
+			},
+			activeColor: {
+				type: String,
+				default: "#0000ff"
+			},
+			subActiveColor: {
+				type: String,
+				default: "#ffaaaa"
+			},
+			handleColor: {
+				type: String,
+				default: "#ffff00"
+			},
+			infoColor: {
+				type: String,
+				default: "#000000"
+			},
+			// 整个进度条的外边界圆角半径
+			borderRadius: {
+				type: String,
+				default: '5px'
+			},
+			// 进度条内部滑轨圆角半径
+			barBorderRadius: {
+				type: String,
+				default: '5px'
+			},
+			// active and sunActive 是否显示圆角 NVUE默认true,其他默认false
+			// #ifdef APP-NVUE
+			isActiveCircular: {
+				type: Boolean,
+				default: true
+			},
+			// #endif
+			// #ifndef APP-NVUE
+			isActiveCircular: {
+				type: Boolean,
+				default: false
+			},
+			// #endif
+			handleWidth: {
+				type: String,
+				default: '50px'
+			},
+			handleHeight: {
+				type: String,
+				default: '40px'
+			},
+			handleBorderRadius: {
+				type: String,
+				default: '5px'
+			},
+			handleImgUrl: {
+				type: String,
+				default: ''
+			},
+			disabled: {
+				type: Boolean,
+				default: false
+			},
+			direction: {
+				type: String,
+				default: 'horizontal'
+			},
+			infoEndText: {
+				type: String,
+				default: ""
+			},
+			infoFontSize: {
+				type: String,
+				default: '18px'
+			},
+			showInfo: {
+				type: Boolean,
+				default: true
+			},
+			// 进度值显示value还是subValue
+			infoContent: {
+				type: String,
+				default: 'value'
+			},
+			infoAlign: {
+				type: String,
+				default: 'right'
+			},
+			max: {
+				type: Number,
+				default: 100
+			},
+			min: {
+				type: Number,
+				default: 0
+			},
+			value: {
+				type: Number,
+				default: 0
+			},
+			subValue: {
+				type: Number,
+				default: 0
+			},
+			step: {
+				type: Number,
+				default: 1
+			},
+			// 副进度条步长
+			subStep: {
+				type: Number,
+				default: 1
+			},
+			// true连续滑动,false步进,即以step的间隔变化
+			continuous: {
+				type: Boolean,
+				default: true
+			},
+			// 副进度条continuous
+			subContinuous: {
+				type: Boolean,
+				default: true
+			},
+			reverse: {
+				type: Boolean,
+				default: false
+			},
+			
+		},
+		data() {
+			return {
+				handleX: 50,
+				handleY: 0,
+				px2rpx: 1,
+				percent: 0, // 0-1
+				subPercent: 0, // 0-1
+				mainInfo: {
+					left: 0,
+					top: 0,
+					bottom: 0,
+					right: 0
+				},
+				touchState: false,
+				screenHeight: 0,
+				screenWidth: 0,
+				msubValue: 0
+			}
+		},
+		watch: {
+			/**
+			 * @param {Object} newValue
+			 * @param {Object} oldValue
+			 */
+			value(newValue, oldValue) {
+				if(!this.touchState) {
+					newValue = this.valueSetBoundary(newValue)
+					this.percent = Math.abs((newValue - this.min) / (this.max - this.min))
+				}
+			},
+			showValue(newValue, oldValue) {
+				// 步进
+				if(!this.continuous) {
+					if(this.reverse) {
+						if(this.direction!='vertical') {
+							this.handleX = Math.abs(1 - (newValue - this.min) / (this.max - this.min)) * this.barMaxLength
+						}
+						else {
+							this.handleY = Math.abs((newValue - this.min) / (this.max - this.min)) * this.barMaxLength
+						}
+					}
+					else {
+						if(this.direction!='vertical') {
+							this.handleX = Math.abs((newValue - this.min) / (this.max - this.min)) * this.barMaxLength
+						}
+						else {
+							this.handleY = (1 - Math.abs((newValue - this.min) / (this.max - this.min))) * this.barMaxLength
+						}
+					}
+					
+				}
+				this.$emit("change", {type: 'change',value:this.showValue,subValue:this.msubValue})
+				this.$emit("valuechange", {type: 'valuechange',value:this.showValue,subValue:this.msubValue})
+			},
+			percent(newValue, oldValue) {
+				// 连续
+				if(this.continuous) {
+					if(this.reverse) {
+						if(this.direction!='vertical') {
+							this.handleX = (1 - newValue) * this.barMaxLength
+						}
+						else {
+							this.handleY = newValue * this.barMaxLength
+						}
+					}
+					else {
+						if(this.direction!='vertical') {
+							this.handleX = newValue * this.barMaxLength
+						}
+						else {
+							this.handleY = (1 - newValue) * this.barMaxLength
+						}
+					}
+				}
+			},
+			subValue(newValue, oldValue) {
+				newValue = this.valueSetBoundary(newValue)
+				
+				if(this.subContinuous) {
+					this.msubValue = newValue
+				}
+				else {
+					this.msubValue = this.valueFormat(newValue, true)
+				}
+				this.subPercent = Math.abs((newValue - this.min) / (this.max - this.min))
+				this.$emit("change", {type: 'change',value:this.showValue,subValue:this.msubValue})
+				this.$emit("subvaluechange", {type: 'subvaluechange',value:this.showValue,subValue:this.msubValue})
+				
+			},
+		},
+		computed: {
+			bpWidth() {
+				if(this.direction=="vertical") {
+					return this.maxHeight()[2]
+				}
+				return this.sizeDeal(this.width)[2]
+			},
+			bpHeight() {
+				if(this.direction=="vertical") {
+					return this.sizeDeal(this.width)[2]
+				}
+				return this.maxHeight()[2]
+			},
+			mareaWidth() {
+				if(this.direction=="vertical") {
+					return this.maxHeight()[2]
+				}
+				let width = this.sizeDeal(this.width)[0]
+				return (width - this.textWidth()) + 'px'
+			},
+			mareaHeight() {
+				if(this.direction=="vertical") {
+					let width = this.sizeDeal(this.width)[0]
+					return (width - this.textWidth()) + 'px'
+				}
+				return this.maxHeight()[2]
+			},
+			mareaLeft() {
+				if(this.showValueState()) {
+					if(this.infoAlign == 'left') {
+						return this.textWidth() + 'px'
+					}
+				}
+				return 0
+			},
+			barMaxHeight() {
+				if(this.direction=="vertical") {
+					let width = this.sizeDeal(this.width)[0]
+					let handleWidth = this.sizeDeal(this.handleWidth)
+					return (width - this.textWidth() - handleWidth[0]) + 'px'
+				}
+				return this.sizeDeal(this.strokeWidth)[2]
+			},
+			barMaxWidth() {
+				if(this.direction=="vertical") {
+					return this.sizeDeal(this.strokeWidth)[2]
+				}
+				let width = this.sizeDeal(this.width)[0]
+				let handleWidth = this.sizeDeal(this.handleWidth)
+				return (width - this.textWidth() - handleWidth[0]) + 'px'
+			},
+			barMaxLeft() {
+				if(this.showValueState()) {
+					if(this.infoAlign == 'left') {
+						return this.textWidth() + this.sizeDeal(this.handleWidth)[0] / 2 + 'px'
+					}
+				}
+				if(this.direction != 'vertical') {
+					return this.sizeDeal(this.handleWidth)[0] / 2 + 'px'
+				}
+				// vertical
+				return (this.maxHeight()[0] - this.sizeDeal(this.strokeWidth)[0]) / 2 + 'px'
+				
+			},
+			activeRight() {
+				if(this.reverse) {
+					return 0
+				}
+				return 'unset'
+			},
+			activeLeft() {
+				if(this.reverse) {
+					return 'unset'
+				}
+				return 0
+			},
+			activeTop() {
+				if(this.reverse) {
+					return 0
+				}
+				return 'unset'
+			},
+			activeBottom() {
+				if(this.reverse) {
+					return 'unset'
+				}
+				return 0
+			},
+			barActiveWidth() {
+				if(this.direction=="vertical") {
+					return this.sizeDeal(this.strokeWidth)[2]
+				}
+				let percent
+				if(this.continuous) {
+					percent = this.percent
+				}
+				else {
+					percent = Math.abs((this.showValue - this.min) / (this.max - this.min))
+				}
+				return this.barMaxLength * percent + 'px'
+			},
+			barActiveHeight() {
+				if(this.direction=="vertical") {
+					let percent
+					if(this.continuous) {
+						percent = this.percent
+					}
+					else {
+						percent = Math.abs((this.showValue - this.min) / (this.max - this.min))
+					}
+					return this.barMaxLength * percent + 'px'
+				}
+				return this.sizeDeal(this.strokeWidth)[2]
+			},
+			subActiveTop() {
+				if(this.reverse) {
+					return 0
+				}
+				return 'unset'
+			},
+			subActiveBottom() {
+				if(this.reverse) {
+					return 'unset'
+				}
+				return 0
+			},
+			subActiveRight() {
+				if(this.reverse) {
+					return 0
+				}
+				return 'unset'
+			},
+			subActiveLeft() {
+				if(this.reverse) {
+					return 'unset'
+				}
+				return 0
+			},
+			barSubActiveWidth() {
+				if(this.direction == "vertical") {
+					return this.sizeDeal(this.strokeWidth)[2]
+				}
+				if(this.subContinuous) {
+					return this.barMaxLength * this.subPercent + 'px'
+				}
+				else {
+					return this.barMaxLength * Math.abs((this.msubValue - this.min) / (this.max - this.min)) + 'px'
+				}
+				
+			},
+			barSubActiveHeight() {
+				if(this.direction == "vertical") {
+					if(this.subContinuous) {
+						return this.barMaxLength * this.subPercent + 'px'
+					}
+					else {
+						this.barMaxLength * Math.abs((this.msubValue - this.min) / (this.max - this.min)) + 'px'
+					}
+					
+				}
+				return this.sizeDeal(this.strokeWidth)[2]
+			},
+			mhandleWidth() {
+				if(this.direction == "vertical") {
+					return this.sizeDeal(this.handleHeight)[2]
+				}
+				return this.sizeDeal(this.handleWidth)[2]
+			},
+			mhandleHeight() {
+				if(this.direction == "vertical") {
+					return this.sizeDeal(this.handleWidth)[2]
+				}
+				return this.sizeDeal(this.handleHeight)[2]
+			},
+			mhandleTop() {
+				if(this.direction == 'vertical') {
+					return 0
+				}
+				else {
+					// 拖柄垂直居中
+					let handle = this.sizeDeal(this.handleHeight)[0]
+					let top = this.maxHeight()[0] / 2 - handle / 2 + 'px'
+					return top
+				}
+			},
+			showValue() {
+				return this.valueFormat(this.percent * (this.max - this.min) + this.min)
+			},
+			textHeight() {
+				let infoSize = this.sizeDeal(this.infoFontSize)
+				return infoSize[0]*1.2 + infoSize[1]
+			},
+			valueLeft() {
+				if(this.infoAlign=='left') {
+					return 0
+				}
+				else if(this.infoAlign == 'center') {
+					let width = this.sizeDeal(this.width)
+					return width[0]/2 - this.valueWidth()/2 + 'px'
+				}
+				else if(this.infoAlign=='right'){
+					let width = this.sizeDeal(this.width)
+					return width[0] - this.textWidth() + 'px'
+				}
+				return 0
+			},
+			barMaxLength() {
+				let width = this.sizeDeal(this.width)[0]
+				let handleWidth = this.sizeDeal(this.handleWidth)
+				return width - this.textWidth() - handleWidth[0]
+			},
+		},
+		methods: {
+			touchstart(e) {
+				if(!this.disabled) {
+					this.touchState = true
+					let detail = e.changedTouches[0]
+					this.handleMove(detail)
+					this.$emit("dragstart", {type: 'dragstart',value:this.showValue,subValue:this.msubValue})
+				}
+			},
+			touchmove(e) {
+				if(!this.disabled) {
+					e.stopPropagation()
+					let detail = e.changedTouches[0]
+					this.handleMove(detail)
+					this.$emit("dragging", {type: 'dragging',value:this.showValue,subValue:this.msubValue})
+				}
+			},
+			touchend(e) {
+				if(!this.disabled) {
+					let detail = e.changedTouches[0]
+					this.handleMove(detail)
+					this.touchState = false
+					this.$emit("dragend", {type: 'dragend',value:this.showValue,subValue:this.msubValue})
+				}
+			},
+			handleMove(detail) {
+				let width = this.sizeDeal(this.width)[0]
+				let handleWidth = this.sizeDeal(this.handleWidth)
+				let percent
+				if(this.direction!='vertical') {
+					if(this.infoAlign=='left') {
+						// #ifndef APP-NVUE
+						percent = (detail.pageX - this.mainInfo.left - this.textWidth() - handleWidth[0]/2)/ this.barMaxLength
+						// #endif
+						// #ifdef APP-NVUE
+						percent = (detail.pageX  - handleWidth[0]/2)/ this.barMaxLength
+						// #endif
+					}
+					else {
+						// #ifndef APP-NVUE
+						percent = (detail.pageX - this.mainInfo.left - handleWidth[0]/2)/ this.barMaxLength
+						// #endif
+						// #ifdef APP-NVUE
+						percent = (detail.pageX - handleWidth[0]/2)/ this.barMaxLength
+						// #endif
+					}
+				}
+				else {
+					// #ifdef APP-NVUE
+					percent = 1 - (detail.pageY - handleWidth[0]/2- 1) / this.barMaxLength
+					// #endif
+					// #ifndef APP-NVUE
+					percent = 1 - (detail.pageY - this.mainInfo.top - handleWidth[0]/2)/ this.barMaxLength
+					// #endif
+				}
+				percent = percent > 0 ? percent : 0
+				percent = percent < 1 ? percent : 1
+				if(this.reverse) {
+					this.percent = 1 - percent
+				}
+				else {
+					this.percent = percent
+				}
+			},
+			showValueState() {
+				if(this.direction != 'vertical' && this.showInfo && (this.infoAlign=='left' || this.infoAlign=='right')) {
+					return true
+				}
+				return false
+			},
+			valueSetBoundary(value) {
+				// 控制value在合法范围内
+				if(this.max > this.min) {
+					value = value < this.max ? value : this.max
+					value = value > this.min ? value : this.min
+				}
+				else {
+					value = value > this.max ? value : this.max
+					value = value < this.min ? value : this.min
+				}
+				return value
+			},
+			/**
+			 * @param {Object} v
+			 * @param {Object} isSub 是否是副副进度条
+			 */
+			valueFormat (v,isSub){
+				// set step
+				v = this.valueSetBoundary(v)
+				let stepInfo = this.stepInfo(isSub)
+				v = Number(v - this.min).toFixed(stepInfo[1])
+				let step = stepInfo[0] * 10 ** stepInfo[1]
+				let valueE = v * 10 ** stepInfo[1]
+				let remainder = valueE % step
+				let remainderInt = Math.floor(remainder)
+				// 对余数四舍五入0-1
+				let sub = Math.round(remainder / step)
+				let value = (Math.floor(valueE) - remainderInt + sub*step) / (10 ** stepInfo[1])
+				value = Number((value + this.min).toFixed(stepInfo[1]))
+				return value
+			},
+			/**
+			 * @param {Object} v
+			 * @param {Object} isSub 是否是副副进度条
+			 */
+			stepInfo(isSub) {
+				// return step, decimal位数
+				let step 
+				if(isSub) {
+					step = Number(this.subStep)
+				}
+				else {
+					step = Number(this.step)
+				}
+				
+				if (step <= 0 || !step){
+					return [1, 0]
+				}
+				else{
+					let steps = step.toString().split('.')
+					if (steps.length == 1){
+						return [step,0]
+					}
+					else {
+						return [step,steps[1].length]
+					}
+				}
+			},
+			textWidth() {
+				if(this.showValueState()) {
+					let numWidth = this.max.toString().length> this.min.toString().length? this.max.toString().length: this.min.toString().length
+					let textWidth = ((numWidth + this.stepInfo()[1]) * 0.7 + this.infoEndText.length) * this.sizeDeal(this.infoFontSize)[0]
+					return Number(textWidth.toFixed(2))
+				}
+				return 0
+			},
+			valueWidth() {
+				let numWidth = this.max.toString().length> this.min.toString().length? this.max.toString().length: this.min.toString().length
+				let textWidth = ((numWidth + this.stepInfo()[1]) * 0.7 + this.infoEndText.length) * this.sizeDeal(this.infoFontSize)[0]
+				return Number(textWidth.toFixed(2))
+			},
+			maxHeight() {
+				let h = []
+				if (this.direction!='vertical'){ // direction 为 vertical 时不显示info
+					let subt = this.infoEndText.match(/[^\x00-\xff]/g)
+					if (subt){
+						h.push(this.sizeDeal(this.infoFontSize)[0] * 1.1)
+					}
+					else{
+						h.push(this.sizeDeal(this.infoFontSize)[0])
+					}
+				}
+				h.push(this.sizeDeal(this.strokeWidth)[0])
+				h.push(this.sizeDeal(this.handleHeight)[0])
+				h.sort(function(a, b) {
+					return b - a
+				}) // 降序
+				return [h[0], 'px', h[0] + 'px']
+			},
+			sizeDeal(size) {
+				// 分离字体大小和单位,rpx 转 px
+				let s = Number.isNaN(parseFloat(size)) ? 0 : parseFloat(size)
+				let u = size.toString().replace(/[0-9\.]/g, '')
+				if (u == 'rpx') {
+					s /= this.px2rpx
+					u = 'px'
+				}else if (u == 'vw') {
+					u = 'px'
+					s = s / 100 * this.screenWidth
+				} else if(u == 'vh') {
+					u = 'px'
+					s = s / 100 * this.screenHeight
+				} else{
+					u = 'px'
+				}
+ 				
+				return [s, u, s + u]
+			},
+		}
+	}
+</script>
+
+<style scoped>
+	@import "bing-progress.css"
+</style>

+ 454 - 0
components/lb-picker/README.md

@@ -0,0 +1,454 @@
+<p align="center">
+  <a href="https://github.com/liub1934/uni-lb-picker">
+    <img src="https://img.shields.io/github/stars/liub1934/uni-lb-picker">
+  </a>
+  <a href="https://github.com/liub1934/uni-lb-picker/fork">
+    <img src="https://img.shields.io/github/forks/liub1934/uni-lb-picker">
+  </a>
+  <a href="https://github.com/liub1934/uni-lb-picker/issues">
+    <img src="https://img.shields.io/github/issues/liub1934/uni-lb-picker">
+  </a>
+  <a href="https://www.npmjs.com/package/uni-lb-picker">
+    <img src="https://img.shields.io/npm/v/uni-lb-picker">
+  </a>
+  <a href="https://npmcharts.com/compare/uni-lb-picker?minimal=true">
+    <img src="https://img.shields.io/npm/dm/uni-lb-picker">
+  </a>
+  <a href="https://standardjs.com">
+    <img src="https://img.shields.io/badge/code%20style-standard-brightgreen">
+  </a>
+  <a href="https://github.com/liub1934/uni-lb-picker/blob/master/LICENSE">
+    <img src="https://img.shields.io/github/license/liub1934/uni-lb-picker">
+  </a>
+</p>
+
+插件市场里面的 picker 选择器不满足自己的需求,所以自己写了一个简单的 picker 选择器,可扩展、可自定义,一般满足日常需要。  
+Github:[uni-lb-picker](https://github.com/liub1934/uni-lb-picker)  
+插件市场:[uni-lb-picker](https://ext.dcloud.net.cn/plugin?id=1111)  
+H5 Demo:[点击预览](https://github.liubing.me/uni-lb-picker)
+
+> 如果问题最好去 github 反馈,插件市场评论区留下五星好评即可,[点我去反馈](https://github.com/liub1934/uni-lb-picker/issues/new)
+
+> **由于之前`cancel`拼写失误,写成了`cancle`,`v1.08`现已修正,如果之前版本有使用`cancel`事件的,更新后请及时修正。**
+
+## 兼容性
+
+App + H5 + 各平台小程序(快应用及 360 未测试,nvue 待支持)
+
+## 功能
+
+1、单选  
+2、多级联动,非多级联动,理论支持任意级数  
+3、省市区选择,基于多级联动  
+4、自定义选择器头部确定取消按钮颜色及插槽支持  
+5、选择器可视区自定义滚动个数  
+6、自定义数据字段,满足不同人的需求  
+7、自定义选择器样式  
+8、单选及非联动选择支持扁平化的简单数据,如下形式:
+
+```javascript
+// 单选列表
+list1: ['选项1', '选项2', '选项2'],
+// 非联动选择列表
+list2: [
+  ['选项1', '选项2', '选项3'],
+  ['选项11', '选项22', '选项33'],
+  ['选项111', '选项222', '选项333']
+]
+```
+
+## 引入插件
+
+单独引入,在需要使用的页面上 import 引入即可
+
+```html
+<template>
+  <view>
+    <lb-picker></lb-picker>
+  </view>
+</template>
+
+<script>
+  import LbPicker from '@/components/lb-picker'
+  export default {
+    components: {
+      LbPicker
+    }
+  }
+</script>
+```
+
+全局引入,`main.js`中 import 引入并注册即可全局使用
+
+```jsvascript
+import LbPicker from '@/components/lb-picker'
+Vue.component("lb-picker", LbPicker)
+```
+
+easycom 引入
+
+`pages.json`加上如下配置:
+
+```json
+"easycom": {
+  "autoscan": true,
+  "custom": {
+    "lb-picker": "@/components/lb-picker/index.vue"
+  }
+}
+```
+
+npm 安装引入:
+
+```shell
+npm install uni-lb-picker
+```
+
+```jsvascript
+import LbPicker from 'uni-lb-picker'
+```
+
+## 选择器数据格式
+
+### 单选
+
+常规数据
+
+```javascript
+list: [
+  {
+    label: '选项1',
+    value: '1'
+  },
+  {
+    label: '选项2',
+    value: '2'
+  }
+]
+```
+
+扁平化简单数据
+
+```javascript
+list: ['选项1', '选项2']
+```
+
+### 多级联动
+
+```javascript
+list: [
+  {
+    label: '选项1',
+    value: '1',
+    children: [
+      {
+        label: '选项1-1',
+        value: '1-1',
+        children: [
+          {
+            label: '选项1-1-1',
+            value: '1-1-1'
+          }
+        ]
+      }
+    ]
+  }
+]
+```
+
+### 非联动选择
+
+常规数据
+
+```javascript
+list: [
+  [
+    { label: '选项1', value: '1' },
+    { label: '选项2', value: '2' },
+    { label: '选项3', value: '3' }
+  ],
+  [
+    { label: '选项11', value: '11' },
+    { label: '选项22', value: '22' },
+    { label: '选项33', value: '33' }
+  ],
+  [
+    { label: '选项111', value: '111' },
+    { label: '选项222', value: '222' },
+    { label: '选项333', value: '333' }
+  ]
+]
+```
+
+扁平化简单数据
+
+```javascript
+list: [
+  ['选项1', '选项2', '选项3'],
+  ['选项11', '选项22', '选项33'],
+  ['选项111', '选项222', '选项333']
+]
+```
+
+## 调用显示选择器
+
+通过`ref`形式手动调用`show`方法显示,隐藏同理调用`hide`
+
+```html
+<lb-picker ref="picker"></lb-picker>
+```
+
+```javascript
+this.$refs.picker.show() // 显示
+this.$refs.picker.hide() // 隐藏
+```
+
+`v1.1.3`新增,将需要点击的元素包裹在`lb-picker`中即可。
+
+```html
+<lb-picker>
+  <button>点我直接打开选择器</button>
+</lb-picker>
+```
+
+## 绑定值及设置默认值
+
+支持 vue 中`v-model`写法绑定值,无需自己维护选中值的索引。
+
+```javascript
+<lb-picker v-model="value1"></lb-picker>
+<lb-picker v-model="value2"></lb-picker>
+
+data () {
+  return {
+    value1: '' // 单选
+    value2: [] // 多列联动选择
+  }
+}
+```
+
+## 多个选择器
+
+通过设置不同的`ref`,然后调用即可
+
+```javascript
+<lb-picker ref="picker1"></lb-picker>
+<lb-picker ref="picker2"></lb-picker>
+
+this.$refs.picker1.show() // picker1显示
+this.$refs.picker2.show() // picker2显示
+```
+
+## 省市区选择
+
+省市区选择是基于多列联动选择,数据来源:[https://github.com/modood/Administrative-divisions-of-China](https://github.com/modood/Administrative-divisions-of-China),  
+省市区文件位于`/pages/demos/area-data-min.js`,自行引入即可,可参考`demo3省市区选择`,  
+也可使用自己已有的省市区数据,如果数据字段不一样,也可以自定义,参考下方自定义数据字段。
+
+## 自定义数据字段
+
+为了满足不同人的需求,插件支持自定义数据字段名称, 插件默认的数据字段如下形式:
+
+```javascript
+list: [
+  {
+    label: '选择1',
+    value: 1,
+    children: []
+  },
+  {
+    label: '选择1',
+    value: 1,
+    children: []
+  }
+]
+```
+
+如果你的数据字段和上面不一样,如下形式:
+
+```javascript
+list: [
+  {
+    text: '选择1',
+    id: 1,
+    child: []
+  },
+  {
+    text: '选择1',
+    id: 1,
+    child: []
+  }
+]
+```
+
+通过设置参数中的`props`即可,如下所示:
+
+```javascript
+<lb-picker :props="myProps"></lb-picker>
+
+data () {
+  return {
+    myProps: {
+      label: 'text',
+      value: 'id',
+      children: 'child'
+    }
+  }
+}
+```
+
+## 插槽使用
+
+选择器支持一些可自定义化的插槽,如选择器的取消和确定文字按钮,如果需要对其自定义处理的话,比如加个 icon 图标之类的,可使用插槽,使用方法如下:
+
+```html
+<lb-picker>
+  <view slot="cancel-text">我是自定义取消</view>
+  <view slot="confirm-text">我是自定义确定</view>
+</lb-picker>
+```
+
+也可参考示例中的`demo5`,自定义插槽元素样式交给开发者自由调整,插槽仅提供预留位置。
+
+其他插槽见下。
+
+## 参数及事件
+
+### Props
+
+| 参数                    | 说明                                                                                                                               | 类型                | 可选值                                                           | 默认值                                            |
+| :---------------------- | :--------------------------------------------------------------------------------------------------------------------------------- | :------------------ | :--------------------------------------------------------------- | :------------------------------------------------ |
+| value/v-model           | 绑定值,联动选择为 Array 类型                                                                                                      | String/Number/Array | -                                                                | -                                                 |
+| mode                    | 选择器类型,支持单列,多列联动                                                                                                     | String              | selector 单选/multiSelector 多级联动/unlinkedSelector 多级非联动 | selector                                          |
+| list                    | 选择器数据(v1.0.7 单选及非联动多选支持扁平数据:['选项 1', '选项 2'])                                                              | Array               | -                                                                | -                                                 |
+| level                   | 多列联动层级,仅 mode 为 multiSelector 有效                                                                                        | Number              | -                                                                | 2                                                 |
+| props                   | 自定义数据字段                                                                                                                     | Object              | -                                                                | {label:'label',value:'value',children:'children'} |
+| cancel-text             | 取消文字                                                                                                                           | String              | -                                                                | 取消                                              |
+| cancel-color            | 取消文字颜色                                                                                                                       | String              | -                                                                | #999                                              |
+| confirm-text            | 确定文字                                                                                                                           | String              | -                                                                | 确定                                              |
+| confirm-color           | 确定文字颜色                                                                                                                       | String              | -                                                                | #007aff                                           |
+| empty-text              | (v1.0.7 新增)选择器列表为空的时候显示的文字                                                                                        | String              | -                                                                | 暂无数据                                          |
+| empty-color             | (v1.0.7 新增)暂无数据文字颜色                                                                                                      | String              | -                                                                | #999                                              |
+| column-num              | 可视滚动区域内滚动个数,最好设置奇数值                                                                                             | Number              | -                                                                | 5                                                 |
+| radius                  | 选择器顶部圆角,支持 rpx,如 radius="10rpx"                                                                                        | String              | -                                                                | -                                                 |
+| ~~column-style~~        | ~~选择器默认样式(已弃用,见下方自定义样式说明)~~                                                                                   | Object              | -                                                                | -                                                 |
+| ~~active-column-style~~ | ~~选择器选中样式(已弃用,见下方自定义样式说明)~~                                                                                   | Object              | -                                                                | -                                                 |
+| loading                 | 选择器是否显示加载中,可使用 loading 插槽自定义加载效果                                                                            | Boolean             | -                                                                | -                                                 |
+| mask-color              | 遮罩层颜色                                                                                                                         | String              | -                                                                | rgba(0, 0, 0, 0.4)                                |
+| show-mask               | (v1.1.0 新增)是否显示遮罩层                                                                                                        | Boolean             | true/false                                                       | true                                              |
+| close-on-click-mask     | 点击遮罩层是否关闭选择器                                                                                                           | Boolean             | true/false                                                       | true                                              |
+| ~~change-on-init~~      | ~~(v1.0.7 已弃用)初始化时是否触发 change 事件~~                                                                                    | Boolean             | true/false                                                       | -                                                 |
+| dataset                 | (v1.0.7 新增)可以向组件中传递任意的自定义的数据(对象形式数据),如`:dataset="{name:'test'}"`,在`confirm`或`change`事件中可以取到 | Object              | -                                                                | -                                                 |
+| show-header             | (v1.0.8 新增)是否显示选择器头部                                                                                                    | Boolean             | -                                                                | true                                              |
+| inline                  | (v1.0.8 新增)inline 模式,开启后默认显示选择器,无需点击弹出,可以配合`show-header`一起使用                                        | Boolean             | -                                                                | -                                                 |
+| z-index                 | (v1.0.9 新增)选择器层级,遮罩层默认-1                                                                                              | Number              | -                                                                | 999                                               |
+
+### 方法
+
+| 方法名         | 说明                                   | 参数            | 返回值                                                                                                       |
+| :------------- | :------------------------------------- | :-------------- | :----------------------------------------------------------------------------------------------------------- |
+| show           | 打开选择器                             | -               |                                                                                                              |
+| hide           | 关闭选择器                             | -               |                                                                                                              |
+| getColumnsInfo | (v1.1.0 新增)根据 value 获取选择器信息 | 绑定值的`value` | 同`change` `confirm`回调参数,如果传入的`value`获取不到信息则只返回一个含有`dataset`的对象,具体自行打印查看 |
+
+### Events
+
+| 事件名称 | 说明                                     | 回调参数                                                                                                                                                                                                                                                                                                                             |
+| :------- | :--------------------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| show     | 选择器打开时触发                         | -                                                                                                                                                                                                                                                                                                                                    |
+| hide     | 选择器隐藏时触发                         | -                                                                                                                                                                                                                                                                                                                                    |
+| change   | 选择器滚动时触发,此时不会改变绑定的值   | `{ index, item, value, change }` `index`触发滚动后新的索引,单选时是具体的索引值,多列联动选择时为数组。`item`触发滚动后新的的完整内容,包括`label`、`value`等,单选时为对象,多列选择时为数组对象。`value`触发滚动后新的 value 值,单列选择时为具体值,多列联动选择时为数组。`change`触发事件的类型,详情参考下面的 change 事件备注 |
+| confirm  | 点击选择器确定时触发,此时会改变绑定的值 | 同上`change`事件说明                                                                                                                                                                                                                                                                                                                 |
+| cancel   | 点击选择器取消时触发                     | 同上`change`事件说明                                                                                                                                                                                                                                                                                                                 |
+
+### `change` 事件备注
+
+如果绑定的值是空的,`change`触发后里面的内容都是列表的第一项。  
+`change`事件会在以下情况触发:
+
+- 初始化
+- 绑定值 value 变化
+- 选择器 list 列表变化
+- 滚动选择器
+
+以上情况会在回调函数中都可以取到`change`变化的类型,对应上面的情况包括以下:
+
+- `init`
+- `value`
+- `list`
+- `scroll`
+
+根据这些类型大家可以在`change`的时候按需处理自己的业务逻辑,`init`现在指挥在调用选择器弹出的时候触发。  
+下面的说明情况已失效,如需要在页面显示的时候根据`value`的值显示相应的中文,调用`v1.10`新增的方法`getColumnsInfo`,传入绑定的值即可获取到你想要的所有信息。  
+~~比如一种常见的情况,有默认值的时候需要显示默认值的文字,此时可以`change`事件中判断`change`的类型是否是`init`,如果是的话可以取事件回调中的`item`进行显示绑定值对应的文字信息。~~
+
+```javascript
+handleChange (e) {
+  if (e.change === 'init') {
+    console.log(e.item.label) // 单选 选项1
+    console.log(e.item.map(item => item.label).join('-')) // 多选 选项1-选项11
+  }
+}
+```
+
+### 插槽
+
+| 插槽名        | 说明                   |
+| :------------ | :--------------------- |
+| cancel-text   | 选择器取消文字插槽     |
+| action-center | 选择器顶部中间插槽     |
+| confirm-text  | 选择器确定文字插槽     |
+| loading       | 选择器 loading 插槽    |
+| empty         | 选择器 空数据 插槽     |
+| header-top    | 选择器头部顶部插槽     |
+| header-bottom | 选择器头部底部插槽     |
+| picker-top    | 选择器滚动部分顶部插槽 |
+| picker-bottom | 选择器滚动部分底部插槽 |
+
+### 选择器自定义样式
+
+原先的`column-style`和`active-column-style`已弃用,如需修改默认样式及选中样式参考`demo9`
+
+```css
+<style lang="scss" scoped>
+/deep/ .lb-picker {
+  .lb-picker-column-label {
+    color: #f0ad4e;
+  }
+  .lb-picker-column-active {
+    .lb-picker-column-label {
+      color: #007aff;
+      font-weight: 700;
+    }
+  }
+}
+</style>
+```
+
+### 获取选中值的文字
+
+`@confirm`事件中可以拿到:
+
+单选:
+
+```javascript
+handleConfirm (e) {
+  console.log(e.item.label) // 选项1
+}
+```
+
+联动选择:
+
+```javascript
+handleConfirm (e) {
+  console.log(e.item.map(item => item.label).join('-')) // 选项1-选项11
+}
+```
+
+## Tips
+
+微信小程序端,滚动时在 iOS 自带振动反馈,可在系统设置 -> 声音与触感 -> 系统触感反馈中关闭
+
+## 其他
+
+其他功能参考示例 Demo 代码。

文件差異過大導致無法顯示
+ 91 - 0
components/lb-picker/index.vue


+ 46 - 0
components/lb-picker/mixins/index.js

@@ -0,0 +1,46 @@
+import { getColumns } from '../utils'
+export const commonMixin = {
+  data () {
+    return {
+      isConfirmChange: false,
+      indicatorStyle: `height: 34px`
+    }
+  },
+  created () {
+    this.init('init')
+  },
+  methods: {
+    init (changeType) {
+      if (this.list && this.list.length) {
+        const column = getColumns({
+          value: this.value,
+          list: this.list,
+          mode: this.mode,
+          props: this.props,
+          level: this.level
+        })
+        const { columns, value, item, index } = column
+        this.selectValue = value
+        this.selectItem = item
+        this.pickerColumns = columns
+        this.pickerValue = index
+        this.$emit('change', {
+          value: this.selectValue,
+          item: this.selectItem,
+          index: this.pickerValue,
+          change: changeType
+        })
+      }
+    }
+  },
+  watch: {
+    value () {
+      if (!this.isConfirmChange) {
+        this.init('value')
+      }
+    },
+    list () {
+      this.init('list')
+    }
+  }
+}

+ 94 - 0
components/lb-picker/pickers/multi-selector-picker.vue

@@ -0,0 +1,94 @@
+<template>
+  <view class="lb-multi-selector lb-picker-item"
+    :style="{ height: height }">
+    <picker-view :value="pickerValue"
+      :indicator-style="indicatorStyle"
+      :style="{ height: height }"
+      @change="handleChange">
+      <picker-view-column v-for="(column, index) in pickerColumns"
+        :key="index">
+        <view v-for="(item, i) in column || []"
+          :class="[
+            'lb-picker-column',
+            item[props.value] === selectValue[index]
+              ? 'lb-picker-column-active'
+              : ''
+          ]"
+          :key="i">
+          <text class="lb-picker-column-label">
+            {{ item[props.label] || item }}
+          </text>
+        </view>
+      </picker-view-column>
+    </picker-view>
+  </view>
+</template>
+
+<script>
+import { commonMixin } from '../mixins'
+export default {
+  props: {
+    value: Array,
+    list: Array,
+    mode: String,
+    props: Object,
+    level: Number,
+    visible: Boolean,
+    height: String
+  },
+  mixins: [commonMixin],
+  data () {
+    return {
+      pickerValue: [],
+      pickerColumns: [],
+      selectValue: [],
+      selectItem: []
+    }
+  },
+  methods: {
+    handleChange (item) {
+      const pickerValue = item.detail.value
+      const columnIndex = pickerValue.findIndex(
+        (item, i) => item !== this.pickerValue[i]
+      )
+      const valueIndex = pickerValue[columnIndex]
+      this.setPickerChange(pickerValue, valueIndex, columnIndex)
+    },
+    setPickerChange (pickerValue, valueIndex, columnIndex) {
+      for (let i = 0; i < this.level; i++) {
+        if (i > columnIndex) {
+          pickerValue[i] = 0
+          const column =
+            this.pickerColumns[i - 1][valueIndex] ||
+            this.pickerColumns[i - 1][0]
+          this.$set(this.pickerColumns, i, column[this.props.children] || [])
+          valueIndex = 0
+        }
+        this.$set(this.pickerValue, i, pickerValue[i])
+        const selectItem = this.pickerColumns[i][pickerValue[i]]
+        if (selectItem) {
+          this.selectItem[i] = selectItem
+          this.selectValue[i] = selectItem[this.props.value]
+        } else {
+          const spliceNum = this.level - i
+          this.pickerValue.splice(i, spliceNum)
+          this.selectValue.splice(i, spliceNum)
+          this.selectItem.splice(i, spliceNum)
+          this.pickerColumns.splice(i, spliceNum)
+          break
+        }
+      }
+      this.$emit('change', {
+        value: this.selectValue,
+        item: this.selectItem,
+        index: this.pickerValue,
+        change: 'scroll'
+      })
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+@import "../style/picker-item.scss";
+</style>

+ 67 - 0
components/lb-picker/pickers/selector-picker.vue

@@ -0,0 +1,67 @@
+<template>
+  <view class="lb-selector-picker lb-picker-item"
+    :style="{ height: height }">
+    <picker-view :value="pickerValue"
+      :style="{ height: height }"
+      :indicator-style="indicatorStyle"
+      @change="handleChange">
+      <picker-view-column>
+        <view v-for="(item, i) in list"
+          :class="[
+            'lb-picker-column',
+            (item[props.value] || item) === selectValue
+              ? 'lb-picker-column-active'
+              : ''
+          ]"
+          :key="i">
+          <text class="lb-picker-column-label">
+            {{ item[props.label] || item }}
+          </text>
+        </view>
+      </picker-view-column>
+    </picker-view>
+  </view>
+</template>
+
+<script>
+import { isObject } from '../utils'
+import { commonMixin } from '../mixins'
+export default {
+  props: {
+    value: [String, Number],
+    list: Array,
+    mode: String,
+    props: Object,
+    visible: Boolean,
+    height: String
+  },
+  mixins: [commonMixin],
+  data () {
+    return {
+      pickerValue: [],
+      selectValue: '',
+      selectItem: null
+    }
+  },
+  methods: {
+    handleChange (item) {
+      const index = item.detail.value[0] || 0
+      this.selectItem = this.list[index]
+      this.selectValue = isObject(this.selectItem)
+        ? this.selectItem[this.props.value]
+        : this.selectItem
+      this.pickerValue = item.detail.value
+      this.$emit('change', {
+        value: this.selectValue,
+        item: this.selectItem,
+        index: index,
+        change: 'scroll'
+      })
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+@import "../style/picker-item.scss";
+</style>

+ 75 - 0
components/lb-picker/pickers/unlinked-selector-picker.vue

@@ -0,0 +1,75 @@
+<template>
+  <view class="lb-selector-picker lb-picker-item"
+    :style="{ height: height }">
+    <picker-view :value="pickerValue"
+      :indicator-style="indicatorStyle"
+      :style="{ height: height }"
+      @change="handleChange">
+      <picker-view-column v-for="(column, index) in pickerColumns"
+        :key="index">
+        <view v-for="(item, i) in column || []"
+          :class="[
+            'lb-picker-column',
+            (item[props.value] || item) === selectValue[index]
+              ? 'lb-picker-column-active'
+              : ''
+          ]"
+          :key="i">
+          <text class="lb-picker-column-label">
+            {{ item[props.label] || item }}
+          </text>
+        </view>
+      </picker-view-column>
+    </picker-view>
+  </view>
+</template>
+
+<script>
+import { isObject } from '../utils'
+import { commonMixin } from '../mixins'
+export default {
+  props: {
+    value: Array,
+    list: Array,
+    mode: String,
+    props: Object,
+    visible: Boolean,
+    height: String
+  },
+  mixins: [commonMixin],
+  data () {
+    return {
+      pickerValue: [],
+      pickerColumns: [],
+      selectValue: [],
+      selectItem: []
+    }
+  },
+  methods: {
+    handleChange (item) {
+      const pickerValue = item.detail.value
+      const columnIndex = pickerValue.findIndex((item, i) => item !== this.pickerValue[i])
+      if (columnIndex > -1) {
+        const valueIndex = pickerValue[columnIndex]
+        const columnItem = this.list[columnIndex][valueIndex]
+        const valueItem = isObject(columnItem)
+          ? columnItem[this.props.value]
+          : columnItem
+        this.pickerValue = pickerValue
+        this.$set(this.selectValue, columnIndex, valueItem)
+        this.$set(this.selectItem, columnIndex, columnItem)
+        this.$emit('change', {
+          value: this.selectValue,
+          item: this.selectItem,
+          index: this.pickerValue,
+          change: 'scroll'
+        })
+      }
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+@import "../style/picker-item.scss";
+</style>

+ 23 - 0
components/lb-picker/style/picker-item.scss

@@ -0,0 +1,23 @@
+.lb-picker-column {
+  height: 34px;
+  /* #ifndef APP-NVUE */
+  display: flex;
+  box-sizing: border-box;
+  white-space: nowrap;
+  overflow: hidden;
+  /* #endif */
+  flex-direction: row;
+  align-items: center;
+  justify-content: center;
+}
+
+.lb-picker-column-label {
+  font-size: 16px;
+  text-align: center;
+  text-overflow: ellipsis;
+  transition-property: color;
+  transition-duration: 0.3s;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+}

+ 179 - 0
components/lb-picker/style/picker.scss

@@ -0,0 +1,179 @@
+.lb-picker {
+	position: relative;
+}
+
+.lb-picker-mask {
+	background-color: rgba(0, 0, 0, 0.0);
+	position: fixed;
+	top: 0;
+	right: 0;
+	left: 0;
+	bottom: 0;
+}
+
+.lb-picker-mask-animation {
+	transition-property: background-color;
+	transition-duration: 0.3s;
+}
+
+.lb-picker-container {
+	position: relative;
+}
+
+.lb-picker-container-fixed {
+	position: fixed;
+	left: 0;
+	right: 0;
+	bottom: 0;
+	transform: translateY(100%);
+	/* #ifndef APP-PLUS */
+	overflow: hidden;
+	/* #endif */
+}
+
+.lb-picker-container-animation {
+	transition-property: transform;
+	transition-duration: 0.3s;
+}
+
+.lb-picker-container-show {
+	transform: translateY(0);
+}
+
+.lb-picker-header {
+	position: relative;
+	background-color: #fff;
+	/* #ifdef APP-NVUE */
+	border-bottom-width: 1px;
+	border-bottom-style: solid;
+	border-bottom-color: #e5e5e5;
+	border-top-width: 1px;
+	border-top-style: solid;
+	border-top-color: #e5e5e5;
+	/* #endif */
+	/* #ifndef APP-NVUE */
+	box-sizing: border-box;
+	/* #endif */
+
+}
+
+.lb-picker-header-actions {
+	height: 45px;
+	/* #ifndef APP-NVUE */
+	box-sizing: border-box;
+	display: flex;
+	/* #endif */
+	flex-direction: row;
+	justify-content: space-between;
+	flex-wrap: nowrap;
+}
+
+/* #ifndef APP-PLUS */
+.lb-picker-header::before {
+	content: "";
+	position: absolute;
+	left: 0;
+	top: 0;
+	right: 0;
+	height: 1px;
+	clear: both;
+	border-bottom: 1px solid #e5e5e5;
+	color: #e5e5e5;
+	transform-origin: 0 100%;
+	transform: scaleY(0.5);
+}
+
+.lb-picker-header::after {
+	content: "";
+	position: absolute;
+	left: 0;
+	bottom: 0;
+	right: 0;
+	height: 1px;
+	clear: both;
+	border-bottom: 1px solid #e5e5e5;
+	color: #e5e5e5;
+	transform-origin: 0 100%;
+	transform: scaleY(0.5);
+}
+
+/* #endif */
+
+.lb-picker-action {
+	padding-left: 14px;
+	padding-right: 14px;
+	/* #ifndef APP-NVUE */
+	display: flex;
+	/* #endif */
+	flex-direction: row;
+	align-items: center;
+	justify-content: center;
+}
+
+.lb-picker-action-item {
+	text-align: center;
+	height: 45px;
+	/* #ifndef APP-NVUE */
+	display: flex;
+	/* #endif */
+	align-items: center;
+}
+
+.lb-picker-action-cancel-text {
+	font-size: 16px;
+	color: #999;
+}
+
+.lb-picker-action-confirm-text {
+	font-size: 16px;
+	color: #007aff;
+}
+
+.lb-picker-content {
+	position: relative;
+	background-color: #fff;
+}
+
+.lb-picker-content-main {
+	position: relative;
+	/* #ifndef APP-NVUE */
+	display: flex;
+	/* #endif */
+	justify-content: center;
+	flex-direction: column;
+}
+
+.lb-picker-loading,
+.lb-picker-empty {
+	/* #ifndef APP-NVUE */
+	display: flex;
+	/* #endif */
+	justify-content: center;
+	align-items: center;
+}
+
+.lb-picker-empty-text {
+	color: #999;
+	font-size: 16px;
+}
+
+.lb-picker-loading-img {
+	width: 25px;
+	height: 25px;
+	/* #ifndef APP-NVUE */
+	animation: rotating 2s linear infinite;
+	/* #endif */
+}
+
+/* #ifndef APP-NVUE */
+@keyframes rotating {
+	0% {
+		transform: rotate(0deg)
+	}
+
+	to {
+		transform: rotate(1turn)
+	}
+}
+
+/* #endif */

+ 110 - 0
components/lb-picker/utils.js

@@ -0,0 +1,110 @@
+/**
+ * 判断是否是对象
+ *
+ * @export
+ * @param {*} val
+ * @returns true/false
+ */
+export function isObject (val) {
+  return Object.prototype.toString.call(val) === '[object Object]'
+}
+
+/**
+ * 根据value获取columns信息
+ *
+ * @export
+ * @param {*} { value, list, mode, props, level }
+ * @param {number} [type=2] 查询不到value数据返回数据类型 1空值null 2默认第一个选项
+ * @returns
+ */
+export function getColumns ({ value, list, mode, props, level }, type = 2) {
+  let pickerValue = []
+  let pickerColumns = []
+  let selectValue = []
+  let selectItem = []
+  let columnsInfo = null
+  switch (mode) {
+    case 'selector':
+      let index = list.findIndex(item => {
+        return isObject(item) ? item[props.value] === value : item === value
+      })
+      if (index === -1 && type === 1) {
+        columnsInfo = null
+      } else {
+        index = index > -1 ? index : 0
+        selectItem = list[index]
+        selectValue = isObject(selectItem)
+          ? selectItem[props.value]
+          : selectItem
+        pickerColumns = list
+        pickerValue = [index]
+        columnsInfo = {
+          index: pickerValue,
+          value: selectValue,
+          item: selectItem,
+          columns: pickerColumns
+        }
+      }
+      break
+    case 'multiSelector':
+      const setPickerItems = (data = [], index = 0) => {
+        if (!data.length) return
+        const defaultValue = value || []
+        if (index < level) {
+          const value = defaultValue[index] || ''
+          let i = data.findIndex(item => item[props.value] === value)
+          if (i === -1 && type === 1) return
+          i = i > -1 ? i : 0
+          pickerValue[index] = i
+          pickerColumns[index] = data
+          if (data[i]) {
+            selectValue[index] = data[i][props.value]
+            selectItem[index] = data[i]
+            setPickerItems(data[i][props.children] || [], index + 1)
+          }
+        }
+      }
+      setPickerItems(list)
+      if (!selectValue.length && type === 1) {
+        columnsInfo = null
+      } else {
+        columnsInfo = {
+          index: pickerValue,
+          value: selectValue,
+          item: selectItem,
+          columns: pickerColumns
+        }
+      }
+      break
+    case 'unlinkedSelector':
+      list.forEach((item, i) => {
+        let index = item.findIndex(item => {
+          return isObject(item)
+            ? item[props.value] === value[i]
+            : item === value[i]
+        })
+        if (index === -1 && type === 1) return
+        index = index > -1 ? index : 0
+        const columnItem = list[i][index]
+        const valueItem = isObject(columnItem)
+          ? columnItem[props.value]
+          : columnItem
+        pickerValue[i] = index
+        selectValue[i] = valueItem
+        selectItem[i] = columnItem
+      })
+      pickerColumns = list
+      if (!selectValue.length && type === 1) {
+        columnsInfo = null
+      } else {
+        columnsInfo = {
+          index: pickerValue,
+          value: selectValue,
+          item: selectItem,
+          columns: pickerColumns
+        }
+      }
+      break
+  }
+  return columnsInfo
+}

+ 148 - 0
components/uni-badge/uni-badge.vue

@@ -0,0 +1,148 @@
+<template>
+	<text v-if="text" :class="inverted ? 'uni-badge--' + type + ' uni-badge--' + size + ' uni-badge--' + type + '-inverted' : 'uni-badge--' + type + ' uni-badge--' + size" :style="badgeStyle" class="uni-badge" @click="onClick()">{{ text }}</text>
+</template>
+
+<script>
+	/**
+	 * Badge 数字角标
+	 * @description 数字角标一般和其它控件(列表、9宫格等)配合使用,用于进行数量提示,默认为实心灰色背景
+	 * @tutorial https://ext.dcloud.net.cn/plugin?id=21
+	 * @property {String} text 角标内容
+	 * @property {String} type = [default|primary|success|warning|error] 颜色类型
+	 * 	@value default 灰色
+	 * 	@value primary 蓝色
+	 * 	@value success 绿色
+	 * 	@value warning 黄色
+	 * 	@value error 红色
+	 * @property {String} size = [normal|small] Badge 大小
+	 * 	@value normal 一般尺寸
+	 * 	@value small 小尺寸
+	 * @property {String} inverted = [true|false] 是否无需背景颜色
+	 * @event {Function} click 点击 Badge 触发事件
+	 * @example <uni-badge text="1"></uni-badge>
+	 */
+	export default {
+		name: 'UniBadge',
+		props: {
+			type: {
+				type: String,
+				default: 'default'
+			},
+			inverted: {
+				type: Boolean,
+				default: false
+			},
+			text: {
+				type: [String, Number],
+				default: ''
+			},
+			size: {
+				type: String,
+				default: 'normal'
+			}
+		},
+		data() {
+			return {
+				badgeStyle: ''
+			};
+		},
+		watch: {
+			text() {
+				this.setStyle()
+			}
+		},
+		mounted() {
+			this.setStyle()
+		},
+		methods: {
+			setStyle() {
+				this.badgeStyle = `width: ${String(this.text).length * 8 + 12}px`
+			},
+			onClick() {
+				this.$emit('click');
+			}
+		}
+	};
+</script>
+
+<style scoped>
+	.uni-badge {
+		/* #ifndef APP-PLUS */
+		display: flex;
+		box-sizing: border-box;
+		overflow: hidden;
+		/* #endif */
+		justify-content: center;
+		flex-direction: row;
+		height: 20px;
+		line-height: 20px;
+		color: #333;
+		border-radius: 100px;
+		background-color: #f1f1f1;
+		background-color: transparent;
+		text-align: center;
+		font-family: 'Helvetica Neue', Helvetica, sans-serif;
+		font-size: 12px;
+		padding: 0px 6px;
+	}
+
+	.uni-badge--inverted {
+		padding: 0 5px 0 0;
+		color: #f1f1f1;
+	}
+
+	.uni-badge--default {
+		color: #333;
+		background-color: #f1f1f1;
+	}
+
+	.uni-badge--default-inverted {
+		color: #999;
+		background-color: transparent;
+	}
+
+	.uni-badge--primary {
+		color: #fff;
+		background-color: #007aff;
+	}
+
+	.uni-badge--primary-inverted {
+		color: #007aff;
+		background-color: transparent;
+	}
+
+	.uni-badge--success {
+		color: #fff;
+		background-color: #4cd964;
+	}
+
+	.uni-badge--success-inverted {
+		color: #4cd964;
+		background-color: transparent;
+	}
+
+	.uni-badge--warning {
+		color: #fff;
+		background-color: #f0ad4e;
+	}
+
+	.uni-badge--warning-inverted {
+		color: #f0ad4e;
+		background-color: transparent;
+	}
+
+	.uni-badge--error {
+		color: #fff;
+		background-color: #dd524d;
+	}
+
+	.uni-badge--error-inverted {
+		color: #dd524d;
+		background-color: transparent;
+	}
+
+	.uni-badge--small {
+		transform: scale(0.8);
+		transform-origin: center center;
+	}
+</style>

+ 546 - 0
components/uni-calendar/calendar.js

@@ -0,0 +1,546 @@
+/**
+* @1900-2100区间内的公历、农历互转
+* @charset UTF-8
+* @github  https://github.com/jjonline/calendar.js
+* @Author  Jea杨(JJonline@JJonline.Cn)
+* @Time    2014-7-21
+* @Time    2016-8-13 Fixed 2033hex、Attribution Annals
+* @Time    2016-9-25 Fixed lunar LeapMonth Param Bug
+* @Time    2017-7-24 Fixed use getTerm Func Param Error.use solar year,NOT lunar year
+* @Version 1.0.3
+* @公历转农历:calendar.solar2lunar(1987,11,01); //[you can ignore params of prefix 0]
+* @农历转公历:calendar.lunar2solar(1987,09,10); //[you can ignore params of prefix 0]
+*/
+/* eslint-disable */
+var calendar = {
+
+  /**
+      * 农历1900-2100的润大小信息表
+      * @Array Of Property
+      * @return Hex
+      */
+  lunarInfo: [0x04bd8, 0x04ae0, 0x0a570, 0x054d5, 0x0d260, 0x0d950, 0x16554, 0x056a0, 0x09ad0, 0x055d2, // 1900-1909
+    0x04ae0, 0x0a5b6, 0x0a4d0, 0x0d250, 0x1d255, 0x0b540, 0x0d6a0, 0x0ada2, 0x095b0, 0x14977, // 1910-1919
+    0x04970, 0x0a4b0, 0x0b4b5, 0x06a50, 0x06d40, 0x1ab54, 0x02b60, 0x09570, 0x052f2, 0x04970, // 1920-1929
+    0x06566, 0x0d4a0, 0x0ea50, 0x06e95, 0x05ad0, 0x02b60, 0x186e3, 0x092e0, 0x1c8d7, 0x0c950, // 1930-1939
+    0x0d4a0, 0x1d8a6, 0x0b550, 0x056a0, 0x1a5b4, 0x025d0, 0x092d0, 0x0d2b2, 0x0a950, 0x0b557, // 1940-1949
+    0x06ca0, 0x0b550, 0x15355, 0x04da0, 0x0a5b0, 0x14573, 0x052b0, 0x0a9a8, 0x0e950, 0x06aa0, // 1950-1959
+    0x0aea6, 0x0ab50, 0x04b60, 0x0aae4, 0x0a570, 0x05260, 0x0f263, 0x0d950, 0x05b57, 0x056a0, // 1960-1969
+    0x096d0, 0x04dd5, 0x04ad0, 0x0a4d0, 0x0d4d4, 0x0d250, 0x0d558, 0x0b540, 0x0b6a0, 0x195a6, // 1970-1979
+    0x095b0, 0x049b0, 0x0a974, 0x0a4b0, 0x0b27a, 0x06a50, 0x06d40, 0x0af46, 0x0ab60, 0x09570, // 1980-1989
+    0x04af5, 0x04970, 0x064b0, 0x074a3, 0x0ea50, 0x06b58, 0x05ac0, 0x0ab60, 0x096d5, 0x092e0, // 1990-1999
+    0x0c960, 0x0d954, 0x0d4a0, 0x0da50, 0x07552, 0x056a0, 0x0abb7, 0x025d0, 0x092d0, 0x0cab5, // 2000-2009
+    0x0a950, 0x0b4a0, 0x0baa4, 0x0ad50, 0x055d9, 0x04ba0, 0x0a5b0, 0x15176, 0x052b0, 0x0a930, // 2010-2019
+    0x07954, 0x06aa0, 0x0ad50, 0x05b52, 0x04b60, 0x0a6e6, 0x0a4e0, 0x0d260, 0x0ea65, 0x0d530, // 2020-2029
+    0x05aa0, 0x076a3, 0x096d0, 0x04afb, 0x04ad0, 0x0a4d0, 0x1d0b6, 0x0d250, 0x0d520, 0x0dd45, // 2030-2039
+    0x0b5a0, 0x056d0, 0x055b2, 0x049b0, 0x0a577, 0x0a4b0, 0x0aa50, 0x1b255, 0x06d20, 0x0ada0, // 2040-2049
+    /** Add By JJonline@JJonline.Cn**/
+    0x14b63, 0x09370, 0x049f8, 0x04970, 0x064b0, 0x168a6, 0x0ea50, 0x06b20, 0x1a6c4, 0x0aae0, // 2050-2059
+    0x0a2e0, 0x0d2e3, 0x0c960, 0x0d557, 0x0d4a0, 0x0da50, 0x05d55, 0x056a0, 0x0a6d0, 0x055d4, // 2060-2069
+    0x052d0, 0x0a9b8, 0x0a950, 0x0b4a0, 0x0b6a6, 0x0ad50, 0x055a0, 0x0aba4, 0x0a5b0, 0x052b0, // 2070-2079
+    0x0b273, 0x06930, 0x07337, 0x06aa0, 0x0ad50, 0x14b55, 0x04b60, 0x0a570, 0x054e4, 0x0d160, // 2080-2089
+    0x0e968, 0x0d520, 0x0daa0, 0x16aa6, 0x056d0, 0x04ae0, 0x0a9d4, 0x0a2d0, 0x0d150, 0x0f252, // 2090-2099
+    0x0d520], // 2100
+
+  /**
+      * 公历每个月份的天数普通表
+      * @Array Of Property
+      * @return Number
+      */
+  solarMonth: [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31],
+
+  /**
+      * 天干地支之天干速查表
+      * @Array Of Property trans["甲","乙","丙","丁","戊","己","庚","辛","壬","癸"]
+      * @return Cn string
+      */
+  Gan: ['\u7532', '\u4e59', '\u4e19', '\u4e01', '\u620a', '\u5df1', '\u5e9a', '\u8f9b', '\u58ec', '\u7678'],
+
+  /**
+      * 天干地支之地支速查表
+      * @Array Of Property
+      * @trans["子","丑","寅","卯","辰","巳","午","未","申","酉","戌","亥"]
+      * @return Cn string
+      */
+  Zhi: ['\u5b50', '\u4e11', '\u5bc5', '\u536f', '\u8fb0', '\u5df3', '\u5348', '\u672a', '\u7533', '\u9149', '\u620c', '\u4ea5'],
+
+  /**
+      * 天干地支之地支速查表<=>生肖
+      * @Array Of Property
+      * @trans["鼠","牛","虎","兔","龙","蛇","马","羊","猴","鸡","狗","猪"]
+      * @return Cn string
+      */
+  Animals: ['\u9f20', '\u725b', '\u864e', '\u5154', '\u9f99', '\u86c7', '\u9a6c', '\u7f8a', '\u7334', '\u9e21', '\u72d7', '\u732a'],
+
+  /**
+      * 24节气速查表
+      * @Array Of Property
+      * @trans["小寒","大寒","立春","雨水","惊蛰","春分","清明","谷雨","立夏","小满","芒种","夏至","小暑","大暑","立秋","处暑","白露","秋分","寒露","霜降","立冬","小雪","大雪","冬至"]
+      * @return Cn string
+      */
+  solarTerm: ['\u5c0f\u5bd2', '\u5927\u5bd2', '\u7acb\u6625', '\u96e8\u6c34', '\u60ca\u86f0', '\u6625\u5206', '\u6e05\u660e', '\u8c37\u96e8', '\u7acb\u590f', '\u5c0f\u6ee1', '\u8292\u79cd', '\u590f\u81f3', '\u5c0f\u6691', '\u5927\u6691', '\u7acb\u79cb', '\u5904\u6691', '\u767d\u9732', '\u79cb\u5206', '\u5bd2\u9732', '\u971c\u964d', '\u7acb\u51ac', '\u5c0f\u96ea', '\u5927\u96ea', '\u51ac\u81f3'],
+
+  /**
+      * 1900-2100各年的24节气日期速查表
+      * @Array Of Property
+      * @return 0x string For splice
+      */
+  sTermInfo: ['9778397bd097c36b0b6fc9274c91aa', '97b6b97bd19801ec9210c965cc920e', '97bcf97c3598082c95f8c965cc920f',
+    '97bd0b06bdb0722c965ce1cfcc920f', 'b027097bd097c36b0b6fc9274c91aa', '97b6b97bd19801ec9210c965cc920e',
+    '97bcf97c359801ec95f8c965cc920f', '97bd0b06bdb0722c965ce1cfcc920f', 'b027097bd097c36b0b6fc9274c91aa',
+    '97b6b97bd19801ec9210c965cc920e', '97bcf97c359801ec95f8c965cc920f', '97bd0b06bdb0722c965ce1cfcc920f',
+    'b027097bd097c36b0b6fc9274c91aa', '9778397bd19801ec9210c965cc920e', '97b6b97bd19801ec95f8c965cc920f',
+    '97bd09801d98082c95f8e1cfcc920f', '97bd097bd097c36b0b6fc9210c8dc2', '9778397bd197c36c9210c9274c91aa',
+    '97b6b97bd19801ec95f8c965cc920e', '97bd09801d98082c95f8e1cfcc920f', '97bd097bd097c36b0b6fc9210c8dc2',
+    '9778397bd097c36c9210c9274c91aa', '97b6b97bd19801ec95f8c965cc920e', '97bcf97c3598082c95f8e1cfcc920f',
+    '97bd097bd097c36b0b6fc9210c8dc2', '9778397bd097c36c9210c9274c91aa', '97b6b97bd19801ec9210c965cc920e',
+    '97bcf97c3598082c95f8c965cc920f', '97bd097bd097c35b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa',
+    '97b6b97bd19801ec9210c965cc920e', '97bcf97c3598082c95f8c965cc920f', '97bd097bd097c35b0b6fc920fb0722',
+    '9778397bd097c36b0b6fc9274c91aa', '97b6b97bd19801ec9210c965cc920e', '97bcf97c359801ec95f8c965cc920f',
+    '97bd097bd097c35b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa', '97b6b97bd19801ec9210c965cc920e',
+    '97bcf97c359801ec95f8c965cc920f', '97bd097bd097c35b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa',
+    '97b6b97bd19801ec9210c965cc920e', '97bcf97c359801ec95f8c965cc920f', '97bd097bd07f595b0b6fc920fb0722',
+    '9778397bd097c36b0b6fc9210c8dc2', '9778397bd19801ec9210c9274c920e', '97b6b97bd19801ec95f8c965cc920f',
+    '97bd07f5307f595b0b0bc920fb0722', '7f0e397bd097c36b0b6fc9210c8dc2', '9778397bd097c36c9210c9274c920e',
+    '97b6b97bd19801ec95f8c965cc920f', '97bd07f5307f595b0b0bc920fb0722', '7f0e397bd097c36b0b6fc9210c8dc2',
+    '9778397bd097c36c9210c9274c91aa', '97b6b97bd19801ec9210c965cc920e', '97bd07f1487f595b0b0bc920fb0722',
+    '7f0e397bd097c36b0b6fc9210c8dc2', '9778397bd097c36b0b6fc9274c91aa', '97b6b97bd19801ec9210c965cc920e',
+    '97bcf7f1487f595b0b0bb0b6fb0722', '7f0e397bd097c35b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa',
+    '97b6b97bd19801ec9210c965cc920e', '97bcf7f1487f595b0b0bb0b6fb0722', '7f0e397bd097c35b0b6fc920fb0722',
+    '9778397bd097c36b0b6fc9274c91aa', '97b6b97bd19801ec9210c965cc920e', '97bcf7f1487f531b0b0bb0b6fb0722',
+    '7f0e397bd097c35b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa', '97b6b97bd19801ec9210c965cc920e',
+    '97bcf7f1487f531b0b0bb0b6fb0722', '7f0e397bd07f595b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa',
+    '97b6b97bd19801ec9210c9274c920e', '97bcf7f0e47f531b0b0bb0b6fb0722', '7f0e397bd07f595b0b0bc920fb0722',
+    '9778397bd097c36b0b6fc9210c91aa', '97b6b97bd197c36c9210c9274c920e', '97bcf7f0e47f531b0b0bb0b6fb0722',
+    '7f0e397bd07f595b0b0bc920fb0722', '9778397bd097c36b0b6fc9210c8dc2', '9778397bd097c36c9210c9274c920e',
+    '97b6b7f0e47f531b0723b0b6fb0722', '7f0e37f5307f595b0b0bc920fb0722', '7f0e397bd097c36b0b6fc9210c8dc2',
+    '9778397bd097c36b0b70c9274c91aa', '97b6b7f0e47f531b0723b0b6fb0721', '7f0e37f1487f595b0b0bb0b6fb0722',
+    '7f0e397bd097c35b0b6fc9210c8dc2', '9778397bd097c36b0b6fc9274c91aa', '97b6b7f0e47f531b0723b0b6fb0721',
+    '7f0e27f1487f595b0b0bb0b6fb0722', '7f0e397bd097c35b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa',
+    '97b6b7f0e47f531b0723b0b6fb0721', '7f0e27f1487f531b0b0bb0b6fb0722', '7f0e397bd097c35b0b6fc920fb0722',
+    '9778397bd097c36b0b6fc9274c91aa', '97b6b7f0e47f531b0723b0b6fb0721', '7f0e27f1487f531b0b0bb0b6fb0722',
+    '7f0e397bd097c35b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa', '97b6b7f0e47f531b0723b0b6fb0721',
+    '7f0e27f1487f531b0b0bb0b6fb0722', '7f0e397bd07f595b0b0bc920fb0722', '9778397bd097c36b0b6fc9274c91aa',
+    '97b6b7f0e47f531b0723b0787b0721', '7f0e27f0e47f531b0b0bb0b6fb0722', '7f0e397bd07f595b0b0bc920fb0722',
+    '9778397bd097c36b0b6fc9210c91aa', '97b6b7f0e47f149b0723b0787b0721', '7f0e27f0e47f531b0723b0b6fb0722',
+    '7f0e397bd07f595b0b0bc920fb0722', '9778397bd097c36b0b6fc9210c8dc2', '977837f0e37f149b0723b0787b0721',
+    '7f07e7f0e47f531b0723b0b6fb0722', '7f0e37f5307f595b0b0bc920fb0722', '7f0e397bd097c35b0b6fc9210c8dc2',
+    '977837f0e37f14998082b0787b0721', '7f07e7f0e47f531b0723b0b6fb0721', '7f0e37f1487f595b0b0bb0b6fb0722',
+    '7f0e397bd097c35b0b6fc9210c8dc2', '977837f0e37f14998082b0787b06bd', '7f07e7f0e47f531b0723b0b6fb0721',
+    '7f0e27f1487f531b0b0bb0b6fb0722', '7f0e397bd097c35b0b6fc920fb0722', '977837f0e37f14998082b0787b06bd',
+    '7f07e7f0e47f531b0723b0b6fb0721', '7f0e27f1487f531b0b0bb0b6fb0722', '7f0e397bd097c35b0b6fc920fb0722',
+    '977837f0e37f14998082b0787b06bd', '7f07e7f0e47f531b0723b0b6fb0721', '7f0e27f1487f531b0b0bb0b6fb0722',
+    '7f0e397bd07f595b0b0bc920fb0722', '977837f0e37f14998082b0787b06bd', '7f07e7f0e47f531b0723b0b6fb0721',
+    '7f0e27f1487f531b0b0bb0b6fb0722', '7f0e397bd07f595b0b0bc920fb0722', '977837f0e37f14998082b0787b06bd',
+    '7f07e7f0e47f149b0723b0787b0721', '7f0e27f0e47f531b0b0bb0b6fb0722', '7f0e397bd07f595b0b0bc920fb0722',
+    '977837f0e37f14998082b0723b06bd', '7f07e7f0e37f149b0723b0787b0721', '7f0e27f0e47f531b0723b0b6fb0722',
+    '7f0e397bd07f595b0b0bc920fb0722', '977837f0e37f14898082b0723b02d5', '7ec967f0e37f14998082b0787b0721',
+    '7f07e7f0e47f531b0723b0b6fb0722', '7f0e37f1487f595b0b0bb0b6fb0722', '7f0e37f0e37f14898082b0723b02d5',
+    '7ec967f0e37f14998082b0787b0721', '7f07e7f0e47f531b0723b0b6fb0722', '7f0e37f1487f531b0b0bb0b6fb0722',
+    '7f0e37f0e37f14898082b0723b02d5', '7ec967f0e37f14998082b0787b06bd', '7f07e7f0e47f531b0723b0b6fb0721',
+    '7f0e37f1487f531b0b0bb0b6fb0722', '7f0e37f0e37f14898082b072297c35', '7ec967f0e37f14998082b0787b06bd',
+    '7f07e7f0e47f531b0723b0b6fb0721', '7f0e27f1487f531b0b0bb0b6fb0722', '7f0e37f0e37f14898082b072297c35',
+    '7ec967f0e37f14998082b0787b06bd', '7f07e7f0e47f531b0723b0b6fb0721', '7f0e27f1487f531b0b0bb0b6fb0722',
+    '7f0e37f0e366aa89801eb072297c35', '7ec967f0e37f14998082b0787b06bd', '7f07e7f0e47f149b0723b0787b0721',
+    '7f0e27f1487f531b0b0bb0b6fb0722', '7f0e37f0e366aa89801eb072297c35', '7ec967f0e37f14998082b0723b06bd',
+    '7f07e7f0e47f149b0723b0787b0721', '7f0e27f0e47f531b0723b0b6fb0722', '7f0e37f0e366aa89801eb072297c35',
+    '7ec967f0e37f14998082b0723b06bd', '7f07e7f0e37f14998083b0787b0721', '7f0e27f0e47f531b0723b0b6fb0722',
+    '7f0e37f0e366aa89801eb072297c35', '7ec967f0e37f14898082b0723b02d5', '7f07e7f0e37f14998082b0787b0721',
+    '7f07e7f0e47f531b0723b0b6fb0722', '7f0e36665b66aa89801e9808297c35', '665f67f0e37f14898082b0723b02d5',
+    '7ec967f0e37f14998082b0787b0721', '7f07e7f0e47f531b0723b0b6fb0722', '7f0e36665b66a449801e9808297c35',
+    '665f67f0e37f14898082b0723b02d5', '7ec967f0e37f14998082b0787b06bd', '7f07e7f0e47f531b0723b0b6fb0721',
+    '7f0e36665b66a449801e9808297c35', '665f67f0e37f14898082b072297c35', '7ec967f0e37f14998082b0787b06bd',
+    '7f07e7f0e47f531b0723b0b6fb0721', '7f0e26665b66a449801e9808297c35', '665f67f0e37f1489801eb072297c35',
+    '7ec967f0e37f14998082b0787b06bd', '7f07e7f0e47f531b0723b0b6fb0721', '7f0e27f1487f531b0b0bb0b6fb0722'],
+
+  /**
+      * 数字转中文速查表
+      * @Array Of Property
+      * @trans ['日','一','二','三','四','五','六','七','八','九','十']
+      * @return Cn string
+      */
+  nStr1: ['\u65e5', '\u4e00', '\u4e8c', '\u4e09', '\u56db', '\u4e94', '\u516d', '\u4e03', '\u516b', '\u4e5d', '\u5341'],
+
+  /**
+      * 日期转农历称呼速查表
+      * @Array Of Property
+      * @trans ['初','十','廿','卅']
+      * @return Cn string
+      */
+  nStr2: ['\u521d', '\u5341', '\u5eff', '\u5345'],
+
+  /**
+      * 月份转农历称呼速查表
+      * @Array Of Property
+      * @trans ['正','一','二','三','四','五','六','七','八','九','十','冬','腊']
+      * @return Cn string
+      */
+  nStr3: ['\u6b63', '\u4e8c', '\u4e09', '\u56db', '\u4e94', '\u516d', '\u4e03', '\u516b', '\u4e5d', '\u5341', '\u51ac', '\u814a'],
+
+  /**
+      * 返回农历y年一整年的总天数
+      * @param lunar Year
+      * @return Number
+      * @eg:var count = calendar.lYearDays(1987) ;//count=387
+      */
+  lYearDays: function (y) {
+    var i; var sum = 348
+    for (i = 0x8000; i > 0x8; i >>= 1) { sum += (this.lunarInfo[y - 1900] & i) ? 1 : 0 }
+    return (sum + this.leapDays(y))
+  },
+
+  /**
+      * 返回农历y年闰月是哪个月;若y年没有闰月 则返回0
+      * @param lunar Year
+      * @return Number (0-12)
+      * @eg:var leapMonth = calendar.leapMonth(1987) ;//leapMonth=6
+      */
+  leapMonth: function (y) { // 闰字编码 \u95f0
+    return (this.lunarInfo[y - 1900] & 0xf)
+  },
+
+  /**
+      * 返回农历y年闰月的天数 若该年没有闰月则返回0
+      * @param lunar Year
+      * @return Number (0、29、30)
+      * @eg:var leapMonthDay = calendar.leapDays(1987) ;//leapMonthDay=29
+      */
+  leapDays: function (y) {
+    if (this.leapMonth(y)) {
+      return ((this.lunarInfo[y - 1900] & 0x10000) ? 30 : 29)
+    }
+    return (0)
+  },
+
+  /**
+      * 返回农历y年m月(非闰月)的总天数,计算m为闰月时的天数请使用leapDays方法
+      * @param lunar Year
+      * @return Number (-1、29、30)
+      * @eg:var MonthDay = calendar.monthDays(1987,9) ;//MonthDay=29
+      */
+  monthDays: function (y, m) {
+    if (m > 12 || m < 1) { return -1 }// 月份参数从1至12,参数错误返回-1
+    return ((this.lunarInfo[y - 1900] & (0x10000 >> m)) ? 30 : 29)
+  },
+
+  /**
+      * 返回公历(!)y年m月的天数
+      * @param solar Year
+      * @return Number (-1、28、29、30、31)
+      * @eg:var solarMonthDay = calendar.leapDays(1987) ;//solarMonthDay=30
+      */
+  solarDays: function (y, m) {
+    if (m > 12 || m < 1) { return -1 } // 若参数错误 返回-1
+    var ms = m - 1
+    if (ms == 1) { // 2月份的闰平规律测算后确认返回28或29
+      return (((y % 4 == 0) && (y % 100 != 0) || (y % 400 == 0)) ? 29 : 28)
+    } else {
+      return (this.solarMonth[ms])
+    }
+  },
+
+  /**
+     * 农历年份转换为干支纪年
+     * @param  lYear 农历年的年份数
+     * @return Cn string
+     */
+  toGanZhiYear: function (lYear) {
+    var ganKey = (lYear - 3) % 10
+    var zhiKey = (lYear - 3) % 12
+    if (ganKey == 0) ganKey = 10// 如果余数为0则为最后一个天干
+    if (zhiKey == 0) zhiKey = 12// 如果余数为0则为最后一个地支
+    return this.Gan[ganKey - 1] + this.Zhi[zhiKey - 1]
+  },
+
+  /**
+     * 公历月、日判断所属星座
+     * @param  cMonth [description]
+     * @param  cDay [description]
+     * @return Cn string
+     */
+  toAstro: function (cMonth, cDay) {
+    var s = '\u9b54\u7faf\u6c34\u74f6\u53cc\u9c7c\u767d\u7f8a\u91d1\u725b\u53cc\u5b50\u5de8\u87f9\u72ee\u5b50\u5904\u5973\u5929\u79e4\u5929\u874e\u5c04\u624b\u9b54\u7faf'
+    var arr = [20, 19, 21, 21, 21, 22, 23, 23, 23, 23, 22, 22]
+    return s.substr(cMonth * 2 - (cDay < arr[cMonth - 1] ? 2 : 0), 2) + '\u5ea7'// 座
+  },
+
+  /**
+      * 传入offset偏移量返回干支
+      * @param offset 相对甲子的偏移量
+      * @return Cn string
+      */
+  toGanZhi: function (offset) {
+    return this.Gan[offset % 10] + this.Zhi[offset % 12]
+  },
+
+  /**
+      * 传入公历(!)y年获得该年第n个节气的公历日期
+      * @param y公历年(1900-2100);n二十四节气中的第几个节气(1~24);从n=1(小寒)算起
+      * @return day Number
+      * @eg:var _24 = calendar.getTerm(1987,3) ;//_24=4;意即1987年2月4日立春
+      */
+  getTerm: function (y, n) {
+    if (y < 1900 || y > 2100) { return -1 }
+    if (n < 1 || n > 24) { return -1 }
+    var _table = this.sTermInfo[y - 1900]
+    var _info = [
+      parseInt('0x' + _table.substr(0, 5)).toString(),
+      parseInt('0x' + _table.substr(5, 5)).toString(),
+      parseInt('0x' + _table.substr(10, 5)).toString(),
+      parseInt('0x' + _table.substr(15, 5)).toString(),
+      parseInt('0x' + _table.substr(20, 5)).toString(),
+      parseInt('0x' + _table.substr(25, 5)).toString()
+    ]
+    var _calday = [
+      _info[0].substr(0, 1),
+      _info[0].substr(1, 2),
+      _info[0].substr(3, 1),
+      _info[0].substr(4, 2),
+
+      _info[1].substr(0, 1),
+      _info[1].substr(1, 2),
+      _info[1].substr(3, 1),
+      _info[1].substr(4, 2),
+
+      _info[2].substr(0, 1),
+      _info[2].substr(1, 2),
+      _info[2].substr(3, 1),
+      _info[2].substr(4, 2),
+
+      _info[3].substr(0, 1),
+      _info[3].substr(1, 2),
+      _info[3].substr(3, 1),
+      _info[3].substr(4, 2),
+
+      _info[4].substr(0, 1),
+      _info[4].substr(1, 2),
+      _info[4].substr(3, 1),
+      _info[4].substr(4, 2),
+
+      _info[5].substr(0, 1),
+      _info[5].substr(1, 2),
+      _info[5].substr(3, 1),
+      _info[5].substr(4, 2)
+    ]
+    return parseInt(_calday[n - 1])
+  },
+
+  /**
+      * 传入农历数字月份返回汉语通俗表示法
+      * @param lunar month
+      * @return Cn string
+      * @eg:var cnMonth = calendar.toChinaMonth(12) ;//cnMonth='腊月'
+      */
+  toChinaMonth: function (m) { // 月 => \u6708
+    if (m > 12 || m < 1) { return -1 } // 若参数错误 返回-1
+    var s = this.nStr3[m - 1]
+    s += '\u6708'// 加上月字
+    return s
+  },
+
+  /**
+      * 传入农历日期数字返回汉字表示法
+      * @param lunar day
+      * @return Cn string
+      * @eg:var cnDay = calendar.toChinaDay(21) ;//cnMonth='廿一'
+      */
+  toChinaDay: function (d) { // 日 => \u65e5
+    var s
+    switch (d) {
+      case 10:
+        s = '\u521d\u5341'; break
+      case 20:
+        s = '\u4e8c\u5341'; break
+        break
+      case 30:
+        s = '\u4e09\u5341'; break
+        break
+      default :
+        s = this.nStr2[Math.floor(d / 10)]
+        s += this.nStr1[d % 10]
+    }
+    return (s)
+  },
+
+  /**
+      * 年份转生肖[!仅能大致转换] => 精确划分生肖分界线是“立春”
+      * @param y year
+      * @return Cn string
+      * @eg:var animal = calendar.getAnimal(1987) ;//animal='兔'
+      */
+  getAnimal: function (y) {
+    return this.Animals[(y - 4) % 12]
+  },
+
+  /**
+      * 传入阳历年月日获得详细的公历、农历object信息 <=>JSON
+      * @param y  solar year
+      * @param m  solar month
+      * @param d  solar day
+      * @return JSON object
+      * @eg:console.log(calendar.solar2lunar(1987,11,01));
+      */
+  solar2lunar: function (y, m, d) { // 参数区间1900.1.31~2100.12.31
+    // 年份限定、上限
+    if (y < 1900 || y > 2100) {
+      return -1// undefined转换为数字变为NaN
+    }
+    // 公历传参最下限
+    if (y == 1900 && m == 1 && d < 31) {
+      return -1
+    }
+    // 未传参  获得当天
+    if (!y) {
+      var objDate = new Date()
+    } else {
+      var objDate = new Date(y, parseInt(m) - 1, d)
+    }
+    var i; var leap = 0; var temp = 0
+    // 修正ymd参数
+    var y = objDate.getFullYear()
+    var m = objDate.getMonth() + 1
+    var d = objDate.getDate()
+    var offset = (Date.UTC(objDate.getFullYear(), objDate.getMonth(), objDate.getDate()) - Date.UTC(1900, 0, 31)) / 86400000
+    for (i = 1900; i < 2101 && offset > 0; i++) {
+      temp = this.lYearDays(i)
+      offset -= temp
+    }
+    if (offset < 0) {
+      offset += temp; i--
+    }
+
+    // 是否今天
+    var isTodayObj = new Date()
+    var isToday = false
+    if (isTodayObj.getFullYear() == y && isTodayObj.getMonth() + 1 == m && isTodayObj.getDate() == d) {
+      isToday = true
+    }
+    // 星期几
+    var nWeek = objDate.getDay()
+    var cWeek = this.nStr1[nWeek]
+    // 数字表示周几顺应天朝周一开始的惯例
+    if (nWeek == 0) {
+      nWeek = 7
+    }
+    // 农历年
+    var year = i
+    var leap = this.leapMonth(i) // 闰哪个月
+    var isLeap = false
+
+    // 效验闰月
+    for (i = 1; i < 13 && offset > 0; i++) {
+      // 闰月
+      if (leap > 0 && i == (leap + 1) && isLeap == false) {
+        --i
+        isLeap = true; temp = this.leapDays(year) // 计算农历闰月天数
+      } else {
+        temp = this.monthDays(year, i)// 计算农历普通月天数
+      }
+      // 解除闰月
+      if (isLeap == true && i == (leap + 1)) { isLeap = false }
+      offset -= temp
+    }
+    // 闰月导致数组下标重叠取反
+    if (offset == 0 && leap > 0 && i == leap + 1) {
+      if (isLeap) {
+        isLeap = false
+      } else {
+        isLeap = true; --i
+      }
+    }
+    if (offset < 0) {
+      offset += temp; --i
+    }
+    // 农历月
+    var month = i
+    // 农历日
+    var day = offset + 1
+    // 天干地支处理
+    var sm = m - 1
+    var gzY = this.toGanZhiYear(year)
+
+    // 当月的两个节气
+    // bugfix-2017-7-24 11:03:38 use lunar Year Param `y` Not `year`
+    var firstNode = this.getTerm(y, (m * 2 - 1))// 返回当月「节」为几日开始
+    var secondNode = this.getTerm(y, (m * 2))// 返回当月「节」为几日开始
+
+    // 依据12节气修正干支月
+    var gzM = this.toGanZhi((y - 1900) * 12 + m + 11)
+    if (d >= firstNode) {
+      gzM = this.toGanZhi((y - 1900) * 12 + m + 12)
+    }
+
+    // 传入的日期的节气与否
+    var isTerm = false
+    var Term = null
+    if (firstNode == d) {
+      isTerm = true
+      Term = this.solarTerm[m * 2 - 2]
+    }
+    if (secondNode == d) {
+      isTerm = true
+      Term = this.solarTerm[m * 2 - 1]
+    }
+    // 日柱 当月一日与 1900/1/1 相差天数
+    var dayCyclical = Date.UTC(y, sm, 1, 0, 0, 0, 0) / 86400000 + 25567 + 10
+    var gzD = this.toGanZhi(dayCyclical + d - 1)
+    // 该日期所属的星座
+    var astro = this.toAstro(m, d)
+
+    return { 'lYear': year, 'lMonth': month, 'lDay': day, 'Animal': this.getAnimal(year), 'IMonthCn': (isLeap ? '\u95f0' : '') + this.toChinaMonth(month), 'IDayCn': this.toChinaDay(day), 'cYear': y, 'cMonth': m, 'cDay': d, 'gzYear': gzY, 'gzMonth': gzM, 'gzDay': gzD, 'isToday': isToday, 'isLeap': isLeap, 'nWeek': nWeek, 'ncWeek': '\u661f\u671f' + cWeek, 'isTerm': isTerm, 'Term': Term, 'astro': astro }
+  },
+
+  /**
+      * 传入农历年月日以及传入的月份是否闰月获得详细的公历、农历object信息 <=>JSON
+      * @param y  lunar year
+      * @param m  lunar month
+      * @param d  lunar day
+      * @param isLeapMonth  lunar month is leap or not.[如果是农历闰月第四个参数赋值true即可]
+      * @return JSON object
+      * @eg:console.log(calendar.lunar2solar(1987,9,10));
+      */
+  lunar2solar: function (y, m, d, isLeapMonth) { // 参数区间1900.1.31~2100.12.1
+    var isLeapMonth = !!isLeapMonth
+    var leapOffset = 0
+    var leapMonth = this.leapMonth(y)
+    var leapDay = this.leapDays(y)
+    if (isLeapMonth && (leapMonth != m)) { return -1 }// 传参要求计算该闰月公历 但该年得出的闰月与传参的月份并不同
+    if (y == 2100 && m == 12 && d > 1 || y == 1900 && m == 1 && d < 31) { return -1 }// 超出了最大极限值
+    var day = this.monthDays(y, m)
+    var _day = day
+    // bugFix 2016-9-25
+    // if month is leap, _day use leapDays method
+    if (isLeapMonth) {
+      _day = this.leapDays(y, m)
+    }
+    if (y < 1900 || y > 2100 || d > _day) { return -1 }// 参数合法性效验
+
+    // 计算农历的时间差
+    var offset = 0
+    for (var i = 1900; i < y; i++) {
+      offset += this.lYearDays(i)
+    }
+    var leap = 0; var isAdd = false
+    for (var i = 1; i < m; i++) {
+      leap = this.leapMonth(y)
+      if (!isAdd) { // 处理闰月
+        if (leap <= i && leap > 0) {
+          offset += this.leapDays(y); isAdd = true
+        }
+      }
+      offset += this.monthDays(y, i)
+    }
+    // 转换闰月农历 需补充该年闰月的前一个月的时差
+    if (isLeapMonth) { offset += day }
+    // 1900年农历正月一日的公历时间为1900年1月30日0时0分0秒(该时间也是本农历的最开始起始点)
+    var stmap = Date.UTC(1900, 1, 30, 0, 0, 0)
+    var calObj = new Date((offset + d - 31) * 86400000 + stmap)
+    var cY = calObj.getUTCFullYear()
+    var cM = calObj.getUTCMonth() + 1
+    var cD = calObj.getUTCDate()
+
+    return this.solar2lunar(cY, cM, cD)
+  }
+}
+
+export default calendar

+ 151 - 0
components/uni-calendar/uni-calendar-item.vue

@@ -0,0 +1,151 @@
+<template>
+	<view class="uni-calendar-item__weeks-box" :class="{
+		'uni-calendar-item--disable':weeks.disable,
+		'uni-calendar-item--isDay':calendar.fullDate === weeks.fullDate && weeks.isDay,
+		'uni-calendar-item--checked':(calendar.fullDate === weeks.fullDate && !weeks.isDay) ,
+		'uni-calendar-item--multiple': weeks.multiple
+		}" @click="choiceDate(weeks)">
+		<view class="uni-calendar-item__weeks-box-item">
+			<text v-if="selected&&weeks.extraInfo" class="uni-calendar-item__weeks-box-circle"></text>
+			<text class="uni-calendar-item__weeks-box-text" :class="{
+				'uni-calendar-item--isDay-text': weeks.isDay,
+				'uni-calendar-item--isDay':calendar.fullDate === weeks.fullDate && weeks.isDay,
+				'uni-calendar-item--checked':calendar.fullDate === weeks.fullDate && !weeks.isDay,
+				'uni-calendar-item--multiple': weeks.multiple,
+				'uni-calendar-item--disable':weeks.disable,
+				}">{{weeks.date}}</text>
+			<text v-if="!lunar&&!weeks.extraInfo && weeks.isDay" class="uni-calendar-item__weeks-lunar-text" :class="{
+				'uni-calendar-item--isDay-text':weeks.isDay,
+				'uni-calendar-item--isDay':calendar.fullDate === weeks.fullDate && weeks.isDay,
+				'uni-calendar-item--checked':calendar.fullDate === weeks.fullDate && !weeks.isDay,
+				'uni-calendar-item--multiple': weeks.multiple,
+				}">今天</text>
+			<text v-if="lunar&&!weeks.extraInfo" class="uni-calendar-item__weeks-lunar-text" :class="{
+				'uni-calendar-item--isDay-text':weeks.isDay,
+				'uni-calendar-item--isDay':calendar.fullDate === weeks.fullDate && weeks.isDay,
+				'uni-calendar-item--checked':calendar.fullDate === weeks.fullDate && !weeks.isDay,
+				'uni-calendar-item--multiple': weeks.multiple,
+				'uni-calendar-item--disable':weeks.disable,
+				}">{{weeks.isDay?'今天': (weeks.lunar.IDayCn === '初一'?weeks.lunar.IMonthCn:weeks.lunar.IDayCn)}}</text>
+			<text v-if="weeks.extraInfo&&weeks.extraInfo.info" class="uni-calendar-item__weeks-lunar-text" :class="{
+				'uni-calendar-item--extra':weeks.extraInfo.info,
+				'uni-calendar-item--isDay-text':weeks.isDay,
+				'uni-calendar-item--isDay':calendar.fullDate === weeks.fullDate && weeks.isDay,
+				'uni-calendar-item--checked':calendar.fullDate === weeks.fullDate && !weeks.isDay,
+				'uni-calendar-item--multiple': weeks.multiple,
+				'uni-calendar-item--disable':weeks.disable,
+				}">{{weeks.extraInfo.info}}</text>
+		</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
+			}
+		},
+		methods: {
+			choiceDate(weeks) {
+				this.$emit('change', weeks)
+			}
+		}
+	}
+</script>
+
+<style scoped>
+	.uni-calendar-item__weeks-box {
+		flex: 1;
+		/* #ifndef APP-NVUE */
+		display: flex;
+		/* #endif */
+		flex-direction: column;
+		justify-content: center;
+		align-items: center;
+	}
+
+	.uni-calendar-item__weeks-box-text {
+		font-size: 14px;
+		color: #333;
+	}
+
+	.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: 100rpx;
+		height: 100rpx;
+	}
+
+	.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--disable {
+		background-color: rgba(249, 249, 249, 0.3);
+		color: #c0c0c0;
+	}
+
+	.uni-calendar-item--isDay-text {
+		color: #007aff;
+	}
+
+	.uni-calendar-item--isDay {
+		background-color: #007aff;
+		opacity: 0.8;
+		color: #fff;
+	}
+
+	.uni-calendar-item--extra {
+		color: #dd524d;
+		opacity: 0.8;
+	}
+
+	.uni-calendar-item--checked {
+		background-color: #007aff;
+		color: #fff;
+		opacity: 0.8;
+	}
+
+	.uni-calendar-item--multiple {
+		background-color: #007aff;
+		color: #fff;
+		opacity: 0.8;
+	}
+</style>

+ 431 - 0
components/uni-calendar/uni-calendar.vue

@@ -0,0 +1,431 @@
+<template>
+	<view class="uni-calendar" @touchmove.stop.prevent="clean">
+		<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}">
+			<view v-if="!insert" class="uni-calendar__header uni-calendar--fixed-top">
+				<view class="uni-calendar__header-btn-box" @click="close">
+					<text class="uni-calendar__header-text uni-calendar--fixed-width">取消</text>
+				</view>
+				<view class="uni-calendar__header-btn-box" @click="confirm">
+					<text class="uni-calendar__header-text uni-calendar--fixed-width">确定</text>
+				</view>
+			</view>
+			<view class="uni-calendar__header">
+				<view class="uni-calendar__header-btn-box" @click="pre">
+					<view class="uni-calendar__header-btn uni-calendar--left"></view>
+				</view>
+				<text class="uni-calendar__header-text">{{ (nowDate.year||'') +'年'+( nowDate.month||'') +'月'}}</text>
+				<view class="uni-calendar__header-btn-box" @click="next">
+					<view class="uni-calendar__header-btn uni-calendar--right"></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">
+					<view class="uni-calendar__weeks-day">
+						<text class="uni-calendar__weeks-day-text">日</text>
+					</view>
+					<view class="uni-calendar__weeks-day">
+						<text class="uni-calendar__weeks-day-text">一</text>
+					</view>
+					<view class="uni-calendar__weeks-day">
+						<text class="uni-calendar__weeks-day-text">二</text>
+					</view>
+					<view class="uni-calendar__weeks-day">
+						<text class="uni-calendar__weeks-day-text">三</text>
+					</view>
+					<view class="uni-calendar__weeks-day">
+						<text class="uni-calendar__weeks-day-text">四</text>
+					</view>
+					<view class="uni-calendar__weeks-day">
+						<text class="uni-calendar__weeks-day-text">五</text>
+					</view>
+					<view class="uni-calendar__weeks-day">
+						<text class="uni-calendar__weeks-day-text">六</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">
+						<uni-calendar-item :weeks="weeks" :calendar="calendar" :selected="selected" :lunar="lunar" @change="choiceDate"></uni-calendar-item>
+					</view>
+				</view>
+			</view>
+		</view>
+	</view>
+</template>
+
+<script>
+	import Calendar from './util.js';
+	import uniCalendarItem from './uni-calendar-item.vue'
+	/**
+	 * 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 {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: {
+			uniCalendarItem
+		},
+		props: {
+			date: {
+				type: String,
+				default: ''
+			},
+			selected: {
+				type: Array,
+				default () {
+					return []
+				}
+			},
+			lunar: {
+				type: Boolean,
+				default: false
+			},
+			startDate: {
+				type: String,
+				default: ''
+			},
+			endDate: {
+				type: String,
+				default: ''
+			},
+			range: {
+				type: Boolean,
+				default: false
+			},
+			insert: {
+				type: Boolean,
+				default: true
+			},
+			showMonth: {
+				type: Boolean,
+				default: true
+			}
+		},
+		data() {
+			return {
+				show: false,
+				weeks: [],
+				calendar: {},
+				nowDate: '',
+				aniMaskShow: false
+			}
+		},
+		watch: {
+			selected(newVal) {
+				this.cale.setSelectInfo(this.nowDate.fullDate, newVal)
+				this.weeks = this.cale.weeks
+			}
+		},
+		created() {
+			// 获取日历方法实例
+			this.cale = new Calendar({
+				date: this.date,
+				selected: this.selected,
+				startDate: this.startDate,
+				endDate: this.endDate,
+				range: this.range,
+			})
+			this.init(this.cale.date.fullDate)
+		},
+		methods: {
+			// 取消穿透
+			clean() {},
+			init(date) {
+				this.weeks = this.cale.weeks
+				this.nowDate = this.calendar = this.cale.getInfo(date)
+			},
+			open() {
+				this.show = true
+				this.$nextTick(() => {
+					setTimeout(() => {
+						this.aniMaskShow = true
+					}, 50)
+				})
+			},
+			close() {
+				this.aniMaskShow = false
+				this.$nextTick(() => {
+					setTimeout(() => {
+						this.show = false
+					}, 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)
+				})
+			},
+			setEmit(name) {
+				let {
+					year,
+					month,
+					date,
+					fullDate,
+					lunar,
+					extraInfo
+				} = this.calendar
+				this.$emit(name, {
+					range: this.cale.multipleStatus,
+					year,
+					month,
+					date,
+					fulldate: fullDate,
+					lunar,
+					extraInfo: extraInfo || {}
+				})
+			},
+			choiceDate(weeks) {
+				if (weeks.disable) return
+				this.calendar = weeks
+				// 设置多选
+				this.cale.setMultiple(this.calendar.fullDate)
+				this.weeks = this.cale.weeks
+				this.change()
+			},
+			backtoday() {
+				this.cale.setDate(this.date)
+				this.weeks = this.cale.weeks
+				this.nowDate = this.calendar = this.cale.getInfo(this.date)
+				this.change()
+			},
+			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()
+			},
+			setDate(date) {
+				this.cale.setDate(date)
+				this.weeks = this.cale.weeks
+				this.nowDate = this.cale.getInfo(date)
+			}
+		}
+	}
+</script>
+
+<style scoped>
+	.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: 0;
+		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__header {
+		position: relative;
+		/* #ifndef APP-NVUE */
+		display: flex;
+		/* #endif */
+		flex-direction: row;
+		justify-content: center;
+		align-items: center;
+		height: 50px;
+		border-bottom-color: #e5e5e5;
+		border-bottom-style: solid;
+		border-bottom-width: 1px;
+	}
+
+	.uni-calendar--fixed-top {
+		/* #ifndef APP-NVUE */
+		display: flex;
+		/* #endif */
+		flex-direction: row;
+		justify-content: space-between;
+		border-top-color: #e5e5e5;
+		border-top-style: solid;
+		border-top-width: 1px;
+	}
+
+	.uni-calendar--fixed-width {
+		width: 50px;
+		/* padding: 0 15px;
+ */
+	}
+
+	.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: #333;
+		background-color: #f1f1f1;
+	}
+
+	.uni-calendar__header-text {
+		text-align: center;
+		width: 100px;
+		font-size: 14px;
+		color: #333;
+	}
+
+	.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: 10px;
+		height: 10px;
+		border-left-color: #808080;
+		border-left-style: solid;
+		border-left-width: 2px;
+		border-top-color: #555555;
+		border-top-style: solid;
+		border-top-width: 2px;
+	}
+
+	.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: 45px;
+		border-bottom-color: #F5F5F5;
+		border-bottom-style: solid;
+		border-bottom-width: 1px;
+	}
+
+	.uni-calendar__weeks-day-text {
+		font-size: 14px;
+	}
+
+	.uni-calendar__box {
+		position: relative;
+	}
+
+	.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 */
+	}
+</style>

+ 327 - 0
components/uni-calendar/util.js

@@ -0,0 +1,327 @@
+import CALENDAR from './calendar.js'
+
+class Calendar {
+	constructor({
+		date,
+		selected,
+		startDate,
+		endDate,
+		range
+	} = {}) {
+		// 当前日期
+		this.date = this.getDate(date) // 当前初入日期
+		// 打点信息
+		this.selected = selected || [];
+		// 范围开始
+		this.startDate = startDate
+		// 范围结束
+		this.endDate = endDate
+		this.range = range
+		// 多选状态
+		this.multipleStatus = {
+			before: '',
+			after: '',
+			data: []
+		}
+		// 每周日期
+		this.weeks = {}
+
+		this._getWeek(this.date.fullDate)
+	}
+
+	/**
+	 * 获取任意时间
+	 */
+	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,
+				lunar: this.getlunar(full.year, full.month - 1, beforeDate),
+				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)
+			}
+
+			if (this.endDate) {
+				let dateCompAfter = this.dateCompare(fullDate, this.endDate)
+				disableAfter = this.dateCompare(nowDate, dateCompAfter ? this.endDate : fullDate)
+			}
+
+			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,
+				month: full.month,
+				lunar: this.getlunar(full.year, full.month, i),
+				disable: !disableBefore || !disableAfter,
+				isDay
+			}
+			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,
+				lunar: this.getlunar(full.year, Number(full.month) + 1, i),
+				disable: true
+			})
+		}
+		return dateArr
+	}
+	/**
+	 * 设置日期
+	 * @param {Object} date
+	 */
+	setDate(date) {
+		this._getWeek(date)
+	}
+	/**
+	 * 获取当前日期详情
+	 * @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
+		}
+	}
+
+
+	/**
+	 * 获取日期范围内所有日期
+	 * @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
+	}
+	/**
+	 * 计算阴历日期显示
+	 */
+	getlunar(year, month, date) {
+		return CALENDAR.solar2lunar(year, month, date)
+	}
+	/**
+	 * 设置打点
+	 */
+	setSelectInfo(data, value) {
+		this.selected = value
+		this._getWeek(data)
+	}
+
+	/**
+	 *  获取多选状态
+	 */
+	setMultiple(fullDate) {
+		let {
+			before,
+			after
+		} = this.multipleStatus
+		if (!this.range) return
+		if (before && after) {
+			this.multipleStatus.before = ''
+			this.multipleStatus.after = ''
+			this.multipleStatus.data = []
+			this._getWeek(fullDate)
+		} else {
+			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)
+			}
+		}
+	}
+
+	/**
+	 * 获取每周数据
+	 * @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

+ 403 - 0
components/uni-card/uni-card.vue

@@ -0,0 +1,403 @@
+<template>
+	<view class="uni-card uni-border" :class="{ 'uni-card--full': isFull === true || isFull === 'true', 'uni-card--shadow': isShadow === true || isShadow === 'true'}">
+		<!-- 基础 -->
+		<view v-if="mode === 'basic' && title" class="uni-card__header uni-border-bottom" @click.stop="onClick">
+			<view v-if="thumbnail" class="uni-card__header-extra-img-view">
+				<image :src="thumbnail" class="uni-card__header-extra-img" />
+			</view>
+			<text class="uni-card__header-title-text">{{ title }}</text>
+			<text v-if="extra" class="uni-card__header-extra-text">{{ extra }}</text>
+		</view>
+		<!-- 标题 -->
+		<view v-if="mode === 'title'" class="uni-card__title uni-border-bottom" @click.stop="onClick">
+			<view class="uni-card__title-box">
+				<view class="uni-card__title-header">
+					<image class="uni-card__title-header-image" :src="thumbnail" mode="scaleToFill" />
+				</view>
+				<view class="uni-card__title-content">
+					<text class="uni-card__title-content-title uni-ellipsis">{{ title }}</text>
+					<text class="uni-card__title-content-extra uni-ellipsis">{{ subTitle }}</text>
+				</view>
+			</view>
+			<view v-if="extra">
+				<text class="uni-card__header-extra-text">{{ extra }}</text>
+			</view>
+		</view>
+		<!-- 图文 -->
+		<view v-if="mode === 'style'" class="uni-card__thumbnailimage" @click.stop="onClick">
+			<view class="uni-card__thumbnailimage-box">
+				<image class="uni-card__thumbnailimage-image" :src="thumbnail" mode="aspectFill" />
+			</view>
+			<view v-if="title" class="uni-card__thumbnailimage-title"><text class="uni-card__thumbnailimage-title-text">{{ title }}</text></view>
+		</view>
+		<!-- 内容 -->
+		<view class="uni-card__content uni-card__content--pd" @click.stop="onClick">
+			<view v-if="mode === 'style' && extra" class=""><text class="uni-card__content-extra">{{ extra }}</text></view>
+			<slot />
+		</view>
+		<!-- 底部 -->
+		<view v-if="note" class="uni-card__footer uni-border-top">
+			<slot name="footer">
+				<text class="uni-card__footer-text">{{ note }}</text>
+			</slot>
+		</view>
+	</view>
+</template>
+
+<script>
+	/**
+	 * Card 卡片
+	 * @description 卡片视图组件
+	 * @tutorial https://ext.dcloud.net.cn/plugin?id=22
+	 * @property {String} title 标题文字
+	 * @property {String} subTitle 副标题(仅仅mode=title下生效)
+	 * @property {String} extra 标题额外信息
+	 * @property {String} note 标题左侧缩略图
+	 * @property {String} thumbnail 底部信息
+	 * @property {String} mode = [basic|style|title] 卡片模式
+	 * 	@value basic 基础卡片
+	 * 	@value style 图文卡片
+	 * 	@value title 标题卡片
+	 * @property {Boolean} isFull = [true | false] 卡片内容是否通栏,为 true 时将去除padding值
+	 * @property {Boolean} isShadow = [true | false] 卡片内容是否开启阴影
+	 * @event {Function} click 点击 Card 触发事件
+	 * @example <uni-card title="标题文字" thumbnail="https://img-cdn-qiniu.dcloud.net.cn/new-page/uni.png" extra="额外信息" note="Tips">内容主体,可自定义内容及样式</uni-card>
+	 */
+	export default {
+		name: 'UniCard',
+		props: {
+			title: {
+				type: String,
+				default: ''
+			},
+			subTitle: {
+				type: String,
+				default: ''
+			},
+			extra: {
+				type: String,
+				default: ''
+			},
+			note: {
+				type: String,
+				default: ''
+			},
+			thumbnail: {
+				type: String,
+				default: ''
+			},
+			mode: {
+				type: String,
+				default: 'basic'
+			},
+			isFull: {
+				// 内容区域是否通栏
+				type: Boolean,
+				default: false
+			},
+			isShadow: {
+				// 是否开启阴影
+				type: [Boolean, String],
+				default: false
+			}
+		},
+		methods: {
+			onClick() {
+				this.$emit('click')
+			}
+		}
+	}
+</script>
+
+<style scoped>
+	.uni-card {
+		/* #ifndef APP-NVUE */
+		display: flex;
+		flex: 1;
+		box-shadow: 0 0 0 rgba(0, 0, 0, 0);
+		/* #endif */
+		margin: 12px 15px;
+		background-color: #ffffff;
+		position: relative;
+		flex-direction: column;
+		border-radius: 5px;
+		overflow: hidden;
+	}
+
+
+
+	.uni-border {
+		position: relative;
+		/* #ifdef APP-NVUE */
+		border-color: #e5e5e5;
+		border-style: solid;
+		border-width: 0.5px;
+		/* #endif */
+		z-index: 1;
+	}
+
+	/* #ifndef APP-NVUE */
+	.uni-border:after {
+		content: '';
+		position: absolute;
+		bottom: 0;
+		left: 0;
+		top: 0;
+		right: 0;
+		border: 1px solid #e5e5e5;
+		border-radius: 10px;
+		box-sizing: border-box;
+		width: 200%;
+		height: 200%;
+		transform: scale(0.5);
+		transform-origin: left top;
+		z-index: -1;
+	}
+
+	/* #endif */
+
+	.uni-border-bottom {
+		position: relative;
+		/* #ifdef APP-NVUE */
+		border-bottom-color: #e5e5e5;
+		border-bottom-style: solid;
+		border-bottom-width: 0.5px;
+		/* #endif */
+		z-index: 1;
+	}
+
+	/* #ifndef APP-NVUE */
+	.uni-border-bottom:after {
+		content: '';
+		position: absolute;
+		bottom: 0;
+		left: 0;
+		top: 0;
+		right: 0;
+		border-bottom: 1px solid #e5e5e5;
+		box-sizing: border-box;
+		width: 200%;
+		height: 200%;
+		transform: scale(0.5);
+		transform-origin: left top;
+		z-index: -1;
+	}
+
+	/* #endif */
+	.uni-border-top {
+		position: relative;
+		/* #ifdef APP-NVUE */
+		border-top-color: #e5e5e5;
+		border-top-style: solid;
+		border-top-width: 0.5px;
+		/* #endif */
+		z-index: 1;
+	}
+
+	/* #ifndef APP-NVUE */
+	.uni-border-top:after {
+		content: '';
+		position: absolute;
+		bottom: 0;
+		left: 0;
+		top: 0;
+		right: 0;
+		border-top: 1px solid #e5e5e5;
+		box-sizing: border-box;
+		width: 200%;
+		height: 200%;
+		transform: scale(0.5);
+		transform-origin: left top;
+		z-index: -1;
+	}
+
+	/* #endif */
+
+	.uni-card__thumbnailimage {
+		position: relative;
+		flex-direction: column;
+		justify-content: center;
+		height: 150px;
+		overflow: hidden;
+	}
+
+	.uni-card__thumbnailimage-box {
+		/* #ifndef APP-NVUE */
+		display: flex;
+		/* #endif */
+		flex: 1;
+		flex-direction: row;
+		overflow: hidden;
+	}
+
+	.uni-card__thumbnailimage-image {
+		flex: 1;
+	}
+
+	.uni-card__thumbnailimage-title {
+		/* #ifndef APP-NVUE */
+		display: flex;
+		/* #endif */
+		position: absolute;
+		bottom: 0;
+		left: 0;
+		right: 0;
+		flex-direction: row;
+		padding: 8px 12px;
+		background-color: rgba(0, 0, 0, 0.4);
+	}
+
+	.uni-card__thumbnailimage-title-text {
+		flex: 1;
+		font-size: 14px;
+		color: #fff;
+	}
+
+	.uni-card__title {
+		/* #ifndef APP-NVUE */
+		display: flex;
+		/* #endif */
+		flex-direction: row;
+		align-items: center;
+		padding: 10px;
+
+	}
+
+	.uni-card__title-box {
+		/* #ifndef APP-NVUE */
+		display: flex;
+		/* #endif */
+		flex: 1;
+		flex-direction: row;
+		align-items: center;
+		overflow: hidden;
+	}
+
+	.uni-card__title-header {
+		width: 40px;
+		height: 40px;
+		overflow: hidden;
+		border-radius: 5px;
+	}
+
+	.uni-card__title-header-image {
+		width: 40px;
+		height: 40px;
+	}
+
+	.uni-card__title-content {
+		/* #ifndef APP-NVUE */
+		display: flex;
+		/* #endif */
+		flex-direction: column;
+		justify-content: center;
+		flex: 1;
+		padding-left: 10px;
+		height: 40px;
+		overflow: hidden;
+	}
+
+	.uni-card__title-content-title {
+		font-size: 14px;
+		line-height: 22px;
+	}
+
+	.uni-card__title-content-extra {
+		font-size: 12px;
+		line-height: 27px;
+		color: #999;
+	}
+
+	.uni-card__header {
+		/* #ifndef APP-NVUE */
+		display: flex;
+		/* #endif */
+		position: relative;
+		flex-direction: row;
+		padding: 12px;
+		align-items: center;
+	}
+
+	.uni-card__header-title {
+		/* #ifndef APP-NVUE */
+		display: flex;
+		/* #endif */
+		flex-direction: row;
+		margin-right: 8px;
+		justify-content: flex-start;
+		align-items: center;
+	}
+
+	.uni-card__header-title-text {
+		font-size: 16;
+		flex: 1;
+		color: #333;
+	}
+
+	.uni-card__header-extra-img {
+		height: 20px;
+		width: 20px;
+		margin-right: 8px;
+	}
+
+	.uni-card__header-extra-text {
+		flex: 1;
+		margin-left: 8px;
+		font-size: 12px;
+		text-align: right;
+		color: #999;
+	}
+
+	.uni-card__content {
+		color: #333;
+	}
+
+	.uni-card__content--pd {
+		padding: 12px;
+	}
+
+	.uni-card__content-extra {
+		font-size: 14px;
+		padding-bottom: 10px;
+		color: #999;
+	}
+
+	.uni-card__footer {
+		justify-content: space-between;
+		padding: 12px;
+	}
+
+	.uni-card__footer-text {
+		color: #999;
+		font-size: 12px;
+	}
+
+	.uni-card--shadow {
+		position: relative;
+		/* #ifndef APP-NVUE */
+		box-shadow: 0px 0px 5px 1px rgba(0, 0, 0, 0.1);
+		/* #endif */
+	}
+
+	.uni-card--full {
+		margin: 0;
+		border-radius: 0;
+	}
+
+	/* #ifndef APP-NVUE */
+	.uni-card--full:after {
+		border-radius: 0;
+	}
+
+	/* #endif */
+	.uni-ellipsis {
+		/* #ifndef APP-NVUE */
+		overflow: hidden;
+		white-space: nowrap;
+		text-overflow: ellipsis;
+		/* #endif */
+		/* #ifdef APP-NVUE */
+		lines: 1
+			/* #endif */
+	}
+</style>

+ 217 - 0
components/uni-collapse-item/uni-collapse-item.vue

@@ -0,0 +1,217 @@
+<template>
+	<view :class="{ 'uni-collapse-cell--disabled': disabled,'uni-collapse-cell--notdisabled': !disabled, 'uni-collapse-cell--open': isOpen,'uni-collapse-cell--hide':!isOpen }" class="uni-collapse-cell">
+		<view class="uni-collapse-cell__title" @click="onClick">
+			<image v-if="thumb" :src="thumb" class="uni-collapse-cell__title-img" />
+			<text class="uni-collapse-cell__title-text">{{ title }}</text>
+			<!-- #ifdef MP-ALIPAY -->
+			<view :class="{ 'uni-collapse-cell__title-arrow-active': isOpen, 'uni-collapse-cell--animation': showAnimation === true }" class="uni-collapse-cell__title-arrow">
+				<uni-icons color="#bbb" size="20" type="arrowdown" />
+			</view>
+			<!-- #endif -->
+			<!-- #ifndef MP-ALIPAY -->
+			<uni-icons :class="{ 'uni-collapse-cell__title-arrow-active': isOpen, 'uni-collapse-cell--animation': showAnimation === true }" class="uni-collapse-cell__title-arrow" color="#bbb" size="20" type="arrowdown" />
+			<!-- #endif -->
+		</view>
+		<view :class="{'uni-collapse-cell__content--hide':!isOpen}" class="uni-collapse-cell__content">
+			<view :class="{ 'uni-collapse-cell--animation': showAnimation === true }" class="uni-collapse-cell__wrapper" :style="{'transform':isOpen?'translateY(0)':'translateY(-50%)','-webkit-transform':isOpen?'translateY(0)':'translateY(-50%)'}">
+				<slot />
+			</view>
+		</view>
+	</view>
+</template>
+
+<script>
+	import uniIcons from '../uni-icons/uni-icons.vue'
+	/**
+	 * CollapseItem 折叠面板子组件
+	 * @description 折叠面板子组件
+	 * @property {String} title 标题文字
+	 * @property {String} thumb 标题左侧缩略图
+	 * @property {Boolean} disabled = [true|false] 是否展开面板
+	 * @property {Boolean} showAnimation = [true|false] 开启动画
+	 */
+	export default {
+		name: 'UniCollapseItem',
+		components: {
+			uniIcons
+		},
+		props: {
+			title: {
+				// 列表标题
+				type: String,
+				default: ''
+			},
+			name: {
+				// 唯一标识符
+				type: [Number, String],
+				default: 0
+			},
+			disabled: {
+				// 是否禁用
+				type: Boolean,
+				default: false
+			},
+			showAnimation: {
+				// 是否显示动画
+				type: Boolean,
+				default: false
+			},
+			open: {
+				// 是否展开
+				type: Boolean,
+				default: false
+			},
+			thumb: {
+				// 缩略图
+				type: String,
+				default: ''
+			}
+		},
+		data() {
+			return {
+				isOpen: false
+			}
+		},
+		watch: {
+			open(val) {
+				this.isOpen = val
+			}
+		},
+		inject: ['collapse'],
+		created() {
+			this.isOpen = this.open
+			this.nameSync = this.name ? this.name : this.collapse.childrens.length
+			this.collapse.childrens.push(this)
+			if (String(this.collapse.accordion) === 'true') {
+				if (this.isOpen) {
+					let lastEl = this.collapse.childrens[this.collapse.childrens.length - 2]
+					if (lastEl) {
+						this.collapse.childrens[this.collapse.childrens.length - 2].isOpen = false
+					}
+				}
+			}
+		},
+		methods: {
+			onClick() {
+				if (this.disabled) {
+					return
+				}
+				if (String(this.collapse.accordion) === 'true') {
+					this.collapse.childrens.forEach(vm => {
+						if (vm === this) {
+							return
+						}
+						vm.isOpen = false
+					})
+				}
+				this.isOpen = !this.isOpen
+				this.collapse.onChange && this.collapse.onChange()
+				this.$forceUpdate()
+			}
+		}
+	}
+</script>
+
+<style scoped>
+	.uni-collapse-cell {
+		flex-direction: column;
+		border-color: #e5e5e5;
+		border-bottom-width: 1px;
+		border-bottom-style: solid;
+	}
+
+
+	.uni-collapse-cell--hover {
+		background-color: #f1f1f1;
+	}
+
+	.uni-collapse-cell--open {
+		background-color: #f1f1f1;
+	}
+
+	.uni-collapse-cell--disabled {
+		background-color: #f1f1f1;
+		/* opacity: 0.3;
+ */
+	}
+
+
+	.uni-collapse-cell--hide {
+		height: 48px;
+	}
+
+	.uni-collapse-cell--animation {
+		/* transition: transform 0.3s ease;
+ */
+		transition-property: transform;
+		transition-duration: 0.3s;
+		transition-timing-function: ease;
+	}
+
+	.uni-collapse-cell__title {
+		padding: 12px 12px;
+		position: relative;
+		/* #ifndef APP-NVUE */
+		display: flex;
+		width: 100%;
+		box-sizing: border-box;
+		/* #endif */
+		height: 48px;
+		line-height: 24px;
+		flex-direction: row;
+		justify-content: space-between;
+		align-items: center;
+	}
+
+	.uni-collapse-cell__title:active {
+		background-color: #f1f1f1;
+	}
+
+	.uni-collapse-cell__title-img {
+		height: 26px;
+		width: 26px;
+		margin-right: 10px;
+	}
+
+	.uni-collapse-cell__title-arrow {
+		width: 20px;
+		height: 20px;
+		transform: rotate(0deg);
+		transform-origin: center center;
+
+	}
+
+	.uni-collapse-cell__title-arrow-active {
+		transform: rotate(180deg);
+	}
+
+	.uni-collapse-cell__title-text {
+		flex: 1;
+		font-size: 14px;
+		/* #ifndef APP-NVUE */
+		white-space: nowrap;
+		color: inherit;
+		/* #endif */
+		/* #ifdef APP-NVUE */
+		lines: 1;
+		/* #endif */
+		overflow: hidden;
+		text-overflow: ellipsis;
+	}
+
+	.uni-collapse-cell__content {
+		overflow: hidden;
+	}
+
+	.uni-collapse-cell__wrapper {
+		/* #ifndef APP-NVUE */
+		display: flex;
+		/* #endif */
+		flex-direction: column;
+	}
+
+	.uni-collapse-cell__content--hide {
+		height: 0px;
+		line-height: 0px;
+	}
+</style>

+ 59 - 0
components/uni-collapse/uni-collapse.vue

@@ -0,0 +1,59 @@
+<template>
+	<view class="uni-collapse">
+		<slot />
+	</view>
+</template>
+<script>
+	/**
+	 * Collapse 折叠面板
+	 * @description 展示可以折叠 / 展开的内容区域
+	 * @tutorial https://ext.dcloud.net.cn/plugin?id=23
+	 * @property {Boolean} accordion = [true|false] 是否开启手风琴效果是否开启手风琴效果
+	 * @event {Function} change 切换面板时触发,activeNames(Array):展开状态的uniCollapseItem的 name 值
+	 */
+	export default {
+		name: 'UniCollapse',
+		props: {
+			accordion: {
+				// 是否开启手风琴效果
+				type: [Boolean, String],
+				default: false
+			}
+		},
+		data() {
+			return {}
+		},
+		provide() {
+			return {
+				collapse: this
+			}
+		},
+		created() {
+			this.childrens = []
+		},
+		methods: {
+			onChange() {
+				let activeItem = []
+				this.childrens.forEach((vm, index) => {
+					if (vm.isOpen) {
+						activeItem.push(vm.nameSync)
+					}
+				})
+				this.$emit('change', activeItem)
+			}
+		}
+	}
+</script>
+<style scoped>
+	.uni-collapse {
+		/* #ifndef APP-NVUE */
+		width: 100%;
+		display: flex;
+		/* #endif */
+		/* #ifdef APP-NVUE */
+		flex: 1;
+		/* #endif */
+		flex-direction: column;
+		background-color: #ffffff;
+	}
+</style>

+ 212 - 0
components/uni-combox/uni-combox.vue

@@ -0,0 +1,212 @@
+<template>
+	<view class="uni-combox">
+		<view v-if="label" class="uni-combox__label" :style="labelStyle">
+			<text>{{label}}</text>
+		</view>
+		<view class="uni-combox__input-box">
+			<input class="uni-combox__input" type="text" :placeholder="placeholder" v-model="inputVal" @input="onInput" @focus="onFocus" @blur="onBlur" />
+			<uni-icons class="uni-combox__input-arrow" type="arrowdown" size="14" @click="toggleSelector"></uni-icons>
+			<view class="uni-combox__selector" v-if="showSelector">
+				<scroll-view scroll-y="true" class="uni-combox__selector-scroll">
+					<view class="uni-combox__selector-empty" v-if="filterCandidatesLength === 0">
+						<text>{{emptyTips}}</text>
+					</view>
+					<view class="uni-combox__selector-item" v-for="(item,index) in filterCandidates" :key="index" @click="onSelectorClick(index)">
+						<text>{{item}}</text>
+					</view>
+				</scroll-view>
+			</view>
+		</view>
+	</view>
+</template>
+
+<script>
+	import uniIcons from '../uni-icons/uni-icons.vue'
+	/**
+	 * Combox 组合输入框
+	 * @description 组合输入框一般用于既可以输入也可以选择的场景
+	 * @tutorial https://ext.dcloud.net.cn/plugin?id=1261
+	 * @property {String} label 左侧文字
+	 * @property {String} labelWidth 左侧内容宽度
+	 * @property {String} placeholder 输入框占位符
+	 * @property {Array} candidates 候选项列表
+	 * @property {String} emptyTips 筛选结果为空时显示的文字
+	 * @property {String} value 组合框的值
+	 */
+	export default {
+		name: 'uniCombox',
+		components: {
+			uniIcons
+		},
+		props: {
+			label: {
+				type: String,
+				default: ''
+			},
+			labelWidth: {
+				type: String,
+				default: 'auto'
+			},
+			placeholder: {
+				type: String,
+				default: ''
+			},
+			candidates: {
+				type: Array,
+				default () {
+					return []
+				}
+			},
+			emptyTips: {
+				type: String,
+				default: '无匹配项'
+			},
+			value: {
+				type: String,
+				default: ''
+			}
+		},
+		data() {
+			return {
+				showSelector: false,
+				inputVal: ''
+			}
+		},
+		computed: {
+			labelStyle() {
+				if (this.labelWidth === 'auto') {
+					return {}
+				}
+				return {
+					width: this.labelWidth
+				}
+			},
+			filterCandidates() {
+				return this.candidates.filter((item) => {
+					return item.indexOf(this.inputVal) > -1
+				})
+			},
+			filterCandidatesLength() {
+				return this.filterCandidates.length
+			}
+		},
+		watch: {
+			value: {
+				handler(newVal) {
+					this.inputVal = newVal
+				},
+				immediate: true
+			}
+		},
+		methods: {
+			toggleSelector() {
+				this.showSelector = !this.showSelector
+			},
+			onFocus() {
+				this.showSelector = true
+			},
+			onBlur() {
+				setTimeout(() => {
+					this.showSelector = false
+				}, 50)
+			},
+			onSelectorClick(index) {
+				this.inputVal = this.filterCandidates[index]
+				this.showSelector = false
+				this.$emit('input', this.inputVal)
+			},
+			onInput() {
+				setTimeout(() => {
+					this.$emit('input', this.inputVal)
+				})
+			}
+		}
+	}
+</script>
+
+<style scoped>
+	.uni-combox {
+		/* #ifndef APP-NVUE */
+		display: flex;
+		/* #endif */
+		height: 40px;
+		flex-direction: row;
+		align-items: center;
+		/* border-bottom: solid 1px #DDDDDD; */
+	}
+
+	.uni-combox__label {
+		font-size: 16px;
+		line-height: 22px;
+		padding-right: 10px;
+		color: #999999;
+	}
+
+	.uni-combox__input-box {
+		position: relative;
+		/* #ifndef APP-NVUE */
+		display: flex;
+		/* #endif */
+		flex: 1;
+		flex-direction: row;
+		align-items: center;
+	}
+
+	.uni-combox__input {
+		flex: 1;
+		font-size: 16px;
+		height: 22px;
+		line-height: 22px;
+	}
+
+	.uni-combox__input-arrow {
+		padding: 10px;
+	}
+
+	.uni-combox__selector {
+		box-sizing: border-box;
+		position: absolute;
+		top: 42px;
+		left: 0;
+		width: 100%;
+		background-color: #FFFFFF;
+		border-radius: 6px;
+		box-shadow: #DDDDDD 4px 4px 8px, #DDDDDD -4px -4px 8px;
+		z-index: 2;
+	}
+
+	.uni-combox__selector-scroll {
+		max-height: 200px;
+		box-sizing: border-box;
+	}
+
+	.uni-combox__selector::before {
+		content: '';
+		position: absolute;
+		width: 0;
+		height: 0;
+		border-bottom: solid 6px #FFFFFF;
+		border-right: solid 6px transparent;
+		border-left: solid 6px transparent;
+		left: 50%;
+		top: -6px;
+		margin-left: -6px;
+	}
+
+	.uni-combox__selector-empty,
+	.uni-combox__selector-item {
+		/* #ifdef APP-NVUE */
+		display: flex;
+		/* #endif */
+		line-height: 36px;
+		font-size: 14px;
+		text-align: center;
+		border-bottom: solid 1px #DDDDDD;
+		margin: 0px 10px;
+	}
+
+	.uni-combox__selector-empty:last-child,
+	.uni-combox__selector-item:last-child {
+		border-bottom: none;
+	}
+</style>

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

@@ -0,0 +1,200 @@
+<template>
+	<view class="uni-countdown">
+		<text v-if="showDay" :style="{ borderColor: borderColor, color: color, backgroundColor: backgroundColor }" class="uni-countdown__number">{{ d }}</text>
+		<text v-if="showDay" :style="{ color: splitorColor }" class="uni-countdown__splitor">天</text>
+		<text :style="{ borderColor: borderColor, color: color, backgroundColor: backgroundColor }" class="uni-countdown__number">{{ h }}</text>
+		<text :style="{ color: splitorColor }" class="uni-countdown__splitor">{{ showColon ? ':' : '时' }}</text>
+		<text :style="{ borderColor: borderColor, color: color, backgroundColor: backgroundColor }" class="uni-countdown__number">{{ i }}</text>
+		<text :style="{ color: splitorColor }" class="uni-countdown__splitor">{{ showColon ? ':' : '分' }}</text>
+		<text :style="{ borderColor: borderColor, color: color, backgroundColor: backgroundColor }" class="uni-countdown__number">{{ s }}</text>
+		<text v-if="!showColon" :style="{ color: splitorColor }" class="uni-countdown__splitor">秒</text>
+	</view>
+</template>
+<script>
+	/**
+	 * Countdown 倒计时
+	 * @description 倒计时组件
+	 * @tutorial https://ext.dcloud.net.cn/plugin?id=25
+	 * @property {String} backgroundColor 背景色
+	 * @property {String} color 文字颜色
+	 * @property {Number} day 天数
+	 * @property {Number} hour 小时
+	 * @property {Number} minute 分钟
+	 * @property {Number} second 秒
+	 * @property {Boolean} showDay = [true|false] 是否显示天数
+	 * @property {Boolean} showColon = [true|false] 是否以冒号为分隔符
+	 * @property {String} splitorColor 分割符号颜色
+	 * @event {Function} timeup 倒计时时间到触发事件
+	 * @example <uni-countdown :day="1" :hour="1" :minute="12" :second="40"></uni-countdown>
+	 */
+	export default {
+		name: 'UniCountdown',
+		props: {
+			showDay: {
+				type: Boolean,
+				default: true
+			},
+			showColon: {
+				type: Boolean,
+				default: true
+			},
+			backgroundColor: {
+				type: String,
+				default: '#FFFFFF'
+			},
+			borderColor: {
+				type: String,
+				default: '#000000'
+			},
+			color: {
+				type: String,
+				default: '#000000'
+			},
+			splitorColor: {
+				type: String,
+				default: '#000000'
+			},
+			day: {
+				type: Number,
+				default: 0
+			},
+			hour: {
+				type: Number,
+				default: 0
+			},
+			minute: {
+				type: Number,
+				default: 0
+			},
+			second: {
+				type: Number,
+				default: 0
+			}
+		},
+		data() {
+			return {
+				timer: null,
+				syncFlag: false,
+				d: '00',
+				h: '00',
+				i: '00',
+				s: '00',
+				leftTime: 0,
+				seconds: 0
+			}
+		},
+		watch: {
+			day(val) {
+				this.changeFlag()
+			},
+			hour(val) {
+				this.changeFlag()
+			},
+			minute(val) {
+				this.changeFlag()
+			},
+			second(val) {
+				this.changeFlag()
+			}
+		},
+		created: function(e) {
+			this.startData();
+		},
+		beforeDestroy() {
+			clearInterval(this.timer)
+		},
+		methods: {
+			toSeconds(day, hours, minutes, seconds) {
+				return day * 60 * 60 * 24 + hours * 60 * 60 + minutes * 60 + seconds
+			},
+			timeUp() {
+				clearInterval(this.timer)
+				this.$emit('timeup')
+			},
+			countDown() {
+				let seconds = this.seconds
+				let [day, hour, minute, second] = [0, 0, 0, 0]
+				if (seconds > 0) {
+					day = Math.floor(seconds / (60 * 60 * 24))
+					hour = Math.floor(seconds / (60 * 60)) - (day * 24)
+					minute = Math.floor(seconds / 60) - (day * 24 * 60) - (hour * 60)
+					second = Math.floor(seconds) - (day * 24 * 60 * 60) - (hour * 60 * 60) - (minute * 60)
+				} else {
+					this.timeUp()
+				}
+				if (day < 10) {
+					day = '0' + day
+				}
+				if (hour < 10) {
+					hour = '0' + hour
+				}
+				if (minute < 10) {
+					minute = '0' + minute
+				}
+				if (second < 10) {
+					second = '0' + second
+				}
+				this.d = day
+				this.h = hour
+				this.i = minute
+				this.s = second
+			},
+			startData() {
+				this.seconds = this.toSeconds(this.day, this.hour, this.minute, this.second)
+				if (this.seconds <= 0) {
+					return
+				}
+				this.countDown()
+				this.timer = setInterval(() => {
+					this.seconds--
+					if (this.seconds < 0) {
+						this.timeUp()
+						return
+					}
+					this.countDown()
+				}, 1000)
+			},
+			changeFlag() {
+				if (!this.syncFlag) {
+					this.seconds = this.toSeconds(this.day, this.hour, this.minute, this.second)
+					this.startData();
+					this.syncFlag = true;
+				}
+			}
+		}
+	}
+</script>
+<style scoped>
+	.uni-countdown {
+		/* #ifndef APP-NVUE */
+		display: flex;
+		/* #endif */
+		flex-direction: row;
+		justify-content: flex-start;
+		padding: 2rpx 0;
+	}
+
+	.uni-countdown__splitor {
+		/* #ifndef APP-NVUE */
+		display: flex;
+		/* #endif */
+		justify-content: center;
+		line-height: 48rpx;
+		padding: 5rpx;
+		font-size: 12px;
+	}
+
+	.uni-countdown__number {
+		/* #ifndef APP-NVUE */
+		display: flex;
+		/* #endif */
+		justify-content: center;
+		align-items: center;
+		width: 52rpx;
+		height: 48rpx;
+		line-height: 48rpx;
+		margin: 5rpx;
+		text-align: center;
+		font-size: 12px;
+	}
+</style>

+ 170 - 0
components/uni-drawer/uni-drawer.vue

@@ -0,0 +1,170 @@
+<template>
+	<view v-if="visibleSync" :class="{ 'uni-drawer--visible': showDrawer }" class="uni-drawer" @touchmove.stop.prevent="clear">
+		<view class="uni-drawer__mask" :class="{ 'uni-drawer__mask--visible': showDrawer && mask }" @tap="close('mask')" />
+		<view class="uni-drawer__content" :class="{'uni-drawer--right': rightMode,'uni-drawer--left': !rightMode, 'uni-drawer__content--visible': showDrawer}" :style="{width:drawerWidth+'px'}">
+			<slot />
+		</view>
+	</view>
+</template>
+
+<script>
+	/**
+	 * Drawer 抽屉
+	 * @description 抽屉侧滑菜单
+	 * @tutorial https://ext.dcloud.net.cn/plugin?id=26
+	 * @property {Boolean} mask = [true | false] 是否显示遮罩
+	 * @property {Boolean} maskClick = [true | false] 点击遮罩是否关闭
+	 * @property {Boolean} mode = [left | right] Drawer 滑出位置
+	 * 	@value left 从左侧滑出
+	 * 	@value right 从右侧侧滑出
+	 * @property {Number} width 抽屉的宽度 ,仅 vue 页面生效
+	 * @event {Function} close 组件关闭时触发事件
+	 */
+	export default {
+		name: 'UniDrawer',
+		props: {
+			/**
+			 * 显示模式(左、右),只在初始化生效
+			 */
+			mode: {
+				type: String,
+				default: ''
+			},
+			/**
+			 * 蒙层显示状态
+			 */
+			mask: {
+				type: Boolean,
+				default: true
+			},
+			/**
+			 * 遮罩是否可点击关闭
+			 */
+			maskClick: {
+				type: Boolean,
+				default: true
+			},
+			/**
+			 * 抽屉宽度
+			 */
+			width: {
+				type: Number,
+				default: 220
+			}
+		},
+		data() {
+			return {
+				visibleSync: false,
+				showDrawer: false,
+				rightMode: false,
+				watchTimer: null,
+				drawerWidth: 220
+			}
+		},
+		created() {
+			// #ifndef APP-NVUE
+			this.drawerWidth = this.width
+			// #endif
+			this.rightMode = this.mode === 'right'
+		},
+		methods: {
+			clear() {},
+			close(type) {
+				// fixed by mehaotian 抽屉尚未完全关闭或遮罩禁止点击时不触发以下逻辑
+				if ((type === 'mask' && !this.maskClick) || !this.visibleSync) return
+				this._change('showDrawer', 'visibleSync', false)
+			},
+			open() {
+				// fixed by mehaotian 处理重复点击打开的事件
+				if (this.visibleSync) return
+				this._change('visibleSync', 'showDrawer', true)
+			},
+			_change(param1, param2, status) {
+				this[param1] = status
+				if (this.watchTimer) {
+					clearTimeout(this.watchTimer)
+				}
+				this.watchTimer = setTimeout(() => {
+					this[param2] = status
+					this.$emit('change', status)
+				}, status ? 50 : 300)
+			}
+		}
+	}
+</script>
+
+<style scoped>
+	/* 抽屉宽度
+ */
+
+	.uni-drawer {
+		/* #ifndef APP-NVUE */
+		display: block;
+		/* #endif */
+		position: fixed;
+		top: 0;
+		left: 0;
+		right: 0;
+		bottom: 0;
+		overflow: hidden;
+		z-index: 999;
+	}
+
+	.uni-drawer__content {
+		/* #ifndef APP-NVUE */
+		display: block;
+		/* #endif */
+		position: absolute;
+		top: 0;
+		width: 220px;
+		bottom: 0;
+		background-color: #ffffff;
+		transition: transform 0.3s ease;
+	}
+
+	.uni-drawer--left {
+		left: 0;
+		/* #ifdef APP-NVUE */
+		transform: translateX(-220px);
+		/* #endif */
+		/* #ifndef APP-NVUE */
+		transform: translateX(-100%);
+		/* #endif */
+	}
+
+	.uni-drawer--right {
+		right: 0;
+		/* #ifdef APP-NVUE */
+		transform: translateX(220px);
+		/* #endif */
+		/* #ifndef APP-NVUE */
+		transform: translateX(100%);
+		/* #endif */
+	}
+
+	.uni-drawer__content--visible {
+		transform: translateX(0px);
+	}
+
+
+	.uni-drawer__mask {
+		/* #ifndef APP-NVUE */
+		display: block;
+		/* #endif */
+		opacity: 0;
+		position: absolute;
+		top: 0;
+		left: 0;
+		bottom: 0;
+		right: 0;
+		background-color: rgba(0, 0, 0, 0.4);
+		transition: opacity 0.3s;
+	}
+
+	.uni-drawer__mask--visible {
+		/* #ifndef APP-NVUE */
+		display: block;
+		/* #endif */
+		opacity: 1;
+	}
+</style>

+ 428 - 0
components/uni-fab/uni-fab.vue

@@ -0,0 +1,428 @@
+<template>
+	<view>
+		<view v-if="popMenu && (leftBottom||rightBottom||leftTop||rightTop)" :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="widthFix" />
+					<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">
+			<view class="fab-circle-v" :class="{'uni-fab__plus--active': isShow}"></view>
+			<view class="fab-circle-h" :class="{'uni-fab__plus--active': isShow}"></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',
+		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: '#3c3e49'
+				}
+			}
+		},
+		computed: {
+			contentWidth(e) {
+				return (this.content.length + 1) * 55 + 10 + 'px'
+			},
+			contentWidthMin() {
+				return 55 + 'px'
+			},
+			// 动态计算宽度
+			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(newValue, oldValue) {
+				//console.log(JSON.stringify(newValue))
+				this.styles = Object.assign({}, this.styles, newValue)
+			}
+		},
+		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 scoped>
+	.uni-fab {
+		position: fixed;
+		/* #ifndef APP-NVUE */
+		display: flex;
+		/* #endif */
+		justify-content: center;
+		align-items: center;
+		z-index: 10;
+	}
+
+	.uni-fab--active {
+		opacity: 1;
+	}
+
+	.uni-fab--leftBottom {
+		left: 5px;
+		bottom: 20px;
+		/* #ifdef H5 */
+		bottom: calc(20px + var(--window-bottom));
+		/* #endif */
+		padding: 10px;
+	}
+
+	.uni-fab--leftTop {
+		left: 5px;
+		top: 30px;
+		/* #ifdef H5 */
+		top: calc(30px + var(--window-top));
+		/* #endif */
+		padding: 10px;
+	}
+
+	.uni-fab--rightBottom {
+		right: 5px;
+		bottom: 20px;
+		/* #ifdef H5 */
+		bottom: calc(20px + var(--window-bottom));
+		/* #endif */
+		padding: 10px;
+	}
+
+	.uni-fab--rightTop {
+		right: 5px;
+		top: 30px;
+		/* #ifdef H5 */
+		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: 55px;
+		z-index: 11;
+	}
+
+	.uni-fab__circle--leftBottom {
+		left: 15px;
+		bottom: 30px;
+		/* #ifdef H5 */
+		bottom: calc(30px + var(--window-bottom));
+		/* #endif */
+	}
+
+	.uni-fab__circle--leftTop {
+		left: 15px;
+		top: 40px;
+		/* #ifdef H5 */
+		top: calc(40px + var(--window-top));
+		/* #endif */
+	}
+
+	.uni-fab__circle--rightBottom {
+		right: 15px;
+		bottom: 30px;
+		/* #ifdef H5 */
+		bottom: calc(30px + var(--window-bottom));
+		/* #endif */
+	}
+
+	.uni-fab__circle--rightTop {
+		right: 15px;
+		top: 40px;
+		/* #ifdef H5 */
+		top: calc(40px + 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: 3px;
+		height: 31px;
+		left: 26px;
+		top: 12px;
+		background-color: white;
+		transform: rotate(0deg);
+		transition: transform 0.3s;
+	}
+
+	.fab-circle-h {
+		position: absolute;
+		width: 31px;
+		height: 3px;
+		left: 12px;
+		top: 26px;
+		background-color: white;
+		transform: rotate(0deg);
+		transition: transform 0.3s;
+	}
+
+	.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: 0 0 5px 2px rgba(0, 0, 0, 0.2);
+	}
+
+	.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: 25px;
+		height: 25px;
+		margin-bottom: 3px;
+	}
+
+	.uni-fab__item-text {
+		color: #FFFFFF;
+		font-size: 12px;
+	}
+
+	.uni-fab__item--first {
+		width: 55px;
+	}
+</style>

+ 136 - 0
components/uni-fav/uni-fav.vue

@@ -0,0 +1,136 @@
+<template>
+	<view :class="[circle === true || circle === 'true' ? 'uni-fav--circle' : '']" :style="[{ backgroundColor: checked ? bgColorChecked : bgColor }]" @click="onClick" class="uni-fav">
+		<!-- #ifdef MP-ALIPAY -->
+		<view class="uni-fav-star" v-if="!checked && (star === true || star === 'true')">
+			<uni-icons :color="fgColor" :style="{color: checked ? fgColorChecked : fgColor}" size="14" type="star-filled" />
+		</view>
+		<!-- #endif -->
+		<!-- #ifndef MP-ALIPAY -->
+		<uni-icons :color="fgColor" :style="{color: checked ? fgColorChecked : fgColor}" class="uni-fav-star" size="14" type="star-filled" v-if="!checked && (star === true || star === 'true')" />
+		<!-- #endif -->
+		<text :style="{color: checked ? fgColorChecked : fgColor}" class="uni-fav-text">{{ checked ? contentText.contentFav : contentText.contentDefault }}</text>
+	</view>
+</template>
+
+<script>
+	import uniIcons from "../uni-icons/uni-icons.vue";
+
+	/**
+	 * Fav 收藏按钮
+	 * @description 用于收藏功能,可点击切换选中、不选中的状态
+	 * @tutorial https://ext.dcloud.net.cn/plugin?id=864
+	 * @property {Boolean} star = [true|false] 按钮是否带星星
+	 * @property {String} bgColor 未收藏时的背景色
+	 * @property {String} bgColorChecked 已收藏时的背景色
+	 * @property {String} fgColor 未收藏时的文字颜色
+	 * @property {String} fgColorChecked 已收藏时的文字颜色
+	 * @property {Boolean} circle = [true|false] 是否为圆角
+	 * @property {Boolean} checked = [true|false] 是否为已收藏
+	 * @property {Object} contentText = [true|false] 收藏按钮文字
+	 * @event {Function} click 点击 fav按钮触发事件
+	 * @example <uni-fav :checked="true"/>
+	 */
+	export default {
+		name: "UniFav",
+		components: {
+			uniIcons
+		},
+		props: {
+			star: {
+				type: [Boolean, String],
+				default: true
+			},
+			bgColor: {
+				type: String,
+				default: "#eeeeee"
+			},
+			fgColor: {
+				type: String,
+				default: "#666666"
+			},
+			bgColorChecked: {
+				type: String,
+				default: "#007aff"
+			},
+			fgColorChecked: {
+				type: String,
+				default: "#FFFFFF"
+			},
+			circle: {
+				type: [Boolean, String],
+				default: false
+			},
+			checked: {
+				type: Boolean,
+				default: false
+			},
+			contentText: {
+				type: Object,
+				default () {
+					return {
+						contentDefault: "收藏",
+						contentFav: "已收藏"
+					};
+				}
+			}
+		},
+		watch: {
+			checked() {
+				if (uni.report) {
+					if (this.checked) {
+						uni.report("收藏", "收藏");
+					} else {
+						uni.report("取消收藏", "取消收藏");
+					}
+				}
+			}
+		},
+		methods: {
+			onClick() {
+				this.$emit("click");
+			}
+		}
+	};
+</script>
+
+<style scoped>
+	.uni-fav {
+		/* #ifndef APP-NVUE */
+		display: flex;
+		/* #endif */
+		flex-direction: row;
+		align-items: center;
+		justify-content: center;
+		width: 60px;
+		height: 25px;
+		line-height: 25px;
+		text-align: center;
+		border-radius: 3px;
+	}
+
+	.uni-fav--circle {
+		border-radius: 30px;
+	}
+
+	.uni-fav-star {
+		/* #ifndef APP-NVUE */
+		display: flex;
+		/* #endif */
+		height: 25px;
+		line-height: 24px;
+		margin-right: 3px;
+		align-items: center;
+		justify-content: center;
+	}
+
+	.uni-fav-text {
+		/* #ifndef APP-NVUE */
+		display: flex;
+		/* #endif */
+		height: 25px;
+		line-height: 25px;
+		align-items: center;
+		justify-content: center;
+		font-size: 14px;
+	}
+</style>

+ 230 - 0
components/uni-goods-nav/uni-goods-nav.vue

@@ -0,0 +1,230 @@
+<template>
+	<view class="uni-goods-nav">
+		<!-- 底部占位 -->
+		<view class="uni-tab__seat" />
+		<view class="uni-tab__cart-box flex">
+			<view class="flex uni-tab__cart-sub-left">
+				<view v-for="(item,index) in options" :key="index" class="flex uni-tab__cart-button-left uni-tab__shop-cart" @click="onClick(index,item)">
+					<view class="uni-tab__icon">
+						<uni-icons :type="item.icon" size="20" color="#646566"></uni-icons>
+						<!-- <image class="image" :src="item.icon" mode="widthFix" /> -->
+					</view>
+					<text class="uni-tab__text">{{ item.text }}</text>
+					<view class="flex uni-tab__dot-box">
+						<text v-if="item.info" :class="{ 'uni-tab__dots': item.info > 9 }" class="uni-tab__dot " :style="{'backgroundColor':item.infoBackgroundColor?item.infoBackgroundColor:'#ff0000',
+						color:item.infoColor?item.infoColor:'#fff'
+						}">{{ item.info }}</text>
+					</view>
+				</view>
+			</view>
+			<view :class="{'uni-tab__right':fill}" class="flex uni-tab__cart-sub-right ">
+				<view v-for="(item,index) in buttonGroup" :key="index" :style="{backgroundColor:item.backgroundColor,color:item.color}" class="flex uni-tab__cart-button-right" @click="buttonClick(index,item)"><text :style="{color:item.color}" class="uni-tab__cart-button-right-text">{{ item.text }}</text></view>
+			</view>
+		</view>
+	</view>
+</template>
+
+<script>
+	import uniIcons from '../uni-icons/uni-icons.vue'
+	/**
+	 * GoodsNav 商品导航
+	 * @description 商品加入购物车、立即购买等
+	 * @tutorial https://ext.dcloud.net.cn/plugin?id=865
+	 * @property {Array} options 组件参数
+	 * @property {Array} buttonGroup 组件按钮组参数
+	 * @property {Boolean} fill = [true | false] 组件按钮组参数
+	 * @event {Function} click 左侧点击事件
+	 * @event {Function} buttonClick 右侧按钮组点击事件
+	 * @example <uni-goods-nav :fill="true"  options="" buttonGroup="buttonGroup"  @click="" @buttonClick="" />
+	 */
+	export default {
+		name: 'UniGoodsNav',
+		components: {
+			uniIcons
+		},
+		props: {
+			options: {
+				type: Array,
+				default () {
+					return [{
+						icon: 'shop',
+						text: '店铺',
+					}, {
+						icon: 'cart',
+						text: '购物车'
+					}]
+				}
+			},
+			buttonGroup: {
+				type: Array,
+				default () {
+					return [{
+							text: '加入购物车',
+							backgroundColor: '#ffa200',
+							color: '#fff'
+						},
+						{
+							text: '立即购买',
+							backgroundColor: '#ff0000',
+							color: '#fff'
+						}
+					]
+				}
+			},
+			fill: {
+				type: Boolean,
+				default: false
+			}
+		},
+		methods: {
+			onClick(index, item) {
+				this.$emit('click', {
+					index,
+					content: item,
+
+				})
+			},
+			buttonClick(index, item) {
+				if (uni.report) {
+					uni.report(item.text, item.text)
+				}
+				this.$emit('buttonClick', {
+					index,
+					content: item
+				})
+			}
+		}
+	}
+</script>
+
+<style scoped>
+	.flex {
+		/* #ifndef APP-NVUE */
+		display: flex;
+		/* #endif */
+		flex-direction: row;
+	}
+
+	.uni-goods-nav {
+		/* #ifndef APP-NVUE */
+		display: flex;
+		/* #endif */
+		flex: 1;
+		flex-direction: row;
+	}
+
+	.uni-tab__cart-box {
+		flex: 1;
+		height: 50px;
+		background-color: #fff;
+		z-index: 900;
+	}
+
+	.uni-tab__cart-sub-left {
+		padding: 0 5px;
+	}
+
+	.uni-tab__cart-sub-right {
+		flex: 1;
+	}
+
+	.uni-tab__right {
+		margin: 5px 0;
+		margin-right: 10px;
+		border-radius: 100px;
+		overflow: hidden;
+	}
+
+	.uni-tab__cart-button-left {
+		/* #ifndef APP-NVUE */
+		display: flex;
+		/* #endif */
+		/* flex: 1;
+ */
+		position: relative;
+		justify-content: center;
+		align-items: center;
+		flex-direction: column;
+		margin: 0 10px;
+	}
+
+	.uni-tab__icon {
+		width: 18px;
+		height: 18px;
+	}
+
+	.image {
+		width: 18px;
+		height: 18px;
+	}
+
+	.uni-tab__text {
+		margin-top: 3px;
+		font-size: 12px;
+		color: #646566;
+	}
+
+	.uni-tab__cart-button-right {
+		/* #ifndef APP-NVUE */
+		display: flex;
+		flex-direction: column;
+		/* #endif */
+		flex: 1;
+		justify-content: center;
+		align-items: center;
+	}
+
+	.uni-tab__cart-button-right-text {
+		font-size: 14px;
+		color: #fff;
+	}
+
+	.uni-tab__cart-button-right:active {
+		opacity: 0.7;
+	}
+
+	.uni-tab__dot-box {
+		/* #ifndef APP-NVUE */
+		display: flex;
+		flex-direction: column;
+		/* #endif */
+		position: absolute;
+		right: -2px;
+		top: 2px;
+		justify-content: center;
+		align-items: center;
+		/* width: 0;
+ */
+		/* height: 0;
+ */
+	}
+
+	.uni-tab__dot {
+		/* width: 30rpx;
+ */
+		/* height: 30rpx;
+ */
+		padding: 0 4px;
+		line-height: 15px;
+		color: #ffffff;
+		text-align: center;
+		font-size: 12px;
+		background-color: #ff0000;
+		border-radius: 15px;
+	}
+
+	.uni-tab__dots {
+		padding: 0 4px;
+		/* width: auto;
+ */
+		border-radius: 15px;
+	}
+
+	.uni-tab__color-y {
+		background-color: #ffa200;
+	}
+
+	.uni-tab__color-r {
+		background-color: #ff0000;
+	}
+</style>

+ 167 - 0
components/uni-grid-item/uni-grid-item copy.vue

@@ -0,0 +1,167 @@
+<template>
+	<view v-if="width" :style="'width:'+width+';'+(square?'height:'+width:'')" class="uni-grid-item">
+		<view :class="{ 'uni-grid-item--border': showBorder,  'uni-grid-item--border-top': showBorder && index < column, 'uni-highlight': highlight }" :style="{'border-right-color': borderColor ,'border-bottom-color': borderColor ,'border-top-color': borderColor }" class="uni-grid-item__box" @click="_onClick">
+			<slot />
+		</view>
+	</view>
+</template>
+
+<script>
+	/**
+	 * GridItem 宫格
+	 * @description 宫格组件
+	 * @tutorial https://ext.dcloud.net.cn/plugin?id=27
+	 * @property {Number} index 子组件的唯一标识 ,点击gird会返回当前的标识
+	 */
+	export default {
+		name: 'UniGridItem',
+		inject: ['grid'],
+		props: {
+			index: {
+				type: Number,
+				default: 0
+			}
+		},
+		data() {
+			return {
+				column: 0,
+				showBorder: true,
+				square: true,
+				highlight: true,
+				left: 0,
+				top: 0,
+				openNum: 2,
+				width: 0,
+				borderColor: '#e5e5e5'
+			}
+		},
+		created() {
+			this.column = this.grid.column
+			this.showBorder = this.grid.showBorder
+			this.square = this.grid.square
+			this.highlight = this.grid.highlight
+			this.top = this.hor === 0 ? this.grid.hor : this.hor
+			this.left = this.ver === 0 ? this.grid.ver : this.ver
+			this.borderColor = this.grid.borderColor
+			this.grid.children.push(this)
+			// this.grid.init()
+			this.width = this.grid.width
+		},
+		beforeDestroy() {
+			this.grid.children.forEach((item, index) => {
+				if (item === this) {
+					this.grid.children.splice(index, 1)
+				}
+			})
+		},
+		methods: {
+			_onClick() {
+				this.grid.change({
+					detail: {
+						index: this.index
+					}
+				})
+			}
+		}
+	}
+</script>
+
+<style scoped>
+	.uni-grid-item {
+		/* #ifndef APP-NVUE */
+		height: 100%;
+		display: flex;
+		/* #endif */
+	}
+
+	.uni-grid-item__box {
+		/* #ifndef APP-NVUE */
+		display: flex;
+		width: 100%;
+		/* #endif */
+		position: relative;
+		flex: 1;
+		flex-direction: column;
+		/* justify-content: center;
+ */
+		/* align-items: center;
+ */
+	}
+
+	.uni-grid-item--border {
+		position: relative;
+		/* #ifdef APP-NVUE */
+		border-bottom-color: #e5e5e5;
+		border-bottom-style: solid;
+		border-bottom-width: 0.5px;
+		border-right-color: #e5e5e5;
+		border-right-style: solid;
+		border-right-width: 0.5px;
+		/* #endif */
+		/* #ifndef APP-NVUE */
+		z-index: 0;
+		/* #endif */
+	}
+
+	/* #ifndef APP-NVUE */
+	.uni-grid-item--border:after {
+		content: '';
+		position: absolute;
+		bottom: 0;
+		left: 0;
+		top: 0;
+		right: 0;
+		border-bottom-style: solid;
+		border-bottom-width: 1px;
+		border-bottom-color: inherit;
+		border-right-style: solid;
+		border-right-width: 1px;
+		border-right-color: inherit;
+		box-sizing: border-box;
+		width: 200%;
+		height: 200%;
+		transform: scale(0.5);
+		transform-origin: left top;
+		z-index: -1;
+	}
+
+	/* #endif */
+
+	.uni-grid-item--border-top {
+		position: relative;
+		/* #ifdef APP-NVUE */
+		border-top-color: #e5e5e5;
+		border-top-style: solid;
+		border-top-width: 0.5px;
+		/* #endif */
+		/* #ifndef APP-NVUE */
+		height: 100%;
+		box-sizing: border-box;
+		z-index: 0;
+		/* #endif */
+	}
+
+	/* #ifndef APP-NVUE */
+	.uni-grid-item--border-top:after {
+		content: '';
+		position: absolute;
+		bottom: 0;
+		left: 0;
+		top: 0;
+		right: 0;
+		border-top-style: solid;
+		border-top-width: 1px;
+		border-top-color: inherit;
+		box-sizing: border-box;
+		width: 200%;
+		height: 200%;
+		transform: scale(0.5);
+		transform-origin: left top;
+		z-index: -1;
+	}
+
+	/* #endif */
+	.uni-highlight:active {
+		background-color: #f1f1f1;
+	}
+</style>

+ 125 - 0
components/uni-grid-item/uni-grid-item.vue

@@ -0,0 +1,125 @@
+<template>
+	<view v-if="width" :style="'width:'+width+';'+(square?'height:'+width:'')" class="uni-grid-item">
+		<view :class="{ 'uni-grid-item--border': showBorder,  'uni-grid-item--border-top': showBorder && index < column, 'uni-highlight': highlight }" :style="{'border-right-color': borderColor ,'border-bottom-color': borderColor ,'border-top-color': borderColor }" class="uni-grid-item__box" @click="_onClick">
+			<slot />
+		</view>
+	</view>
+</template>
+
+<script>
+	/**
+	 * GridItem 宫格
+	 * @description 宫格组件
+	 * @tutorial https://ext.dcloud.net.cn/plugin?id=27
+	 * @property {Number} index 子组件的唯一标识 ,点击gird会返回当前的标识
+	 */
+	export default {
+		name: 'UniGridItem',
+		inject: ['grid'],
+		props: {
+			index: {
+				type: Number,
+				default: 0
+			}
+		},
+		data() {
+			return {
+				column: 0,
+				showBorder: true,
+				square: true,
+				highlight: true,
+				left: 0,
+				top: 0,
+				openNum: 2,
+				width: 0,
+				borderColor: '#e5e5e5'
+			}
+		},
+		created() {
+			this.column = this.grid.column
+			this.showBorder = this.grid.showBorder
+			this.square = this.grid.square
+			this.highlight = this.grid.highlight
+			this.top = this.hor === 0 ? this.grid.hor : this.hor
+			this.left = this.ver === 0 ? this.grid.ver : this.ver
+			this.borderColor = this.grid.borderColor
+			this.grid.children.push(this)
+			// this.grid.init()
+			this.width = this.grid.width
+		},
+		beforeDestroy() {
+			this.grid.children.forEach((item, index) => {
+				if (item === this) {
+					this.grid.children.splice(index, 1)
+				}
+			})
+		},
+		methods: {
+			_onClick() {
+				this.grid.change({
+					detail: {
+						index: this.index
+					}
+				})
+			}
+		}
+	}
+</script>
+
+<style scoped>
+	.uni-grid-item {
+		/* #ifndef APP-NVUE */
+		height: 100%;
+		display: flex;
+		/* #endif */
+	}
+
+	.uni-grid-item__box {
+		/* #ifndef APP-NVUE */
+		display: flex;
+		width: 100%;
+		/* #endif */
+		position: relative;
+		flex: 1;
+		flex-direction: column;
+		/* justify-content: center;
+ */
+		/* align-items: center;
+ */
+	}
+
+	.uni-grid-item--border {
+		position: relative;
+		/* #ifdef APP-NVUE */
+		border-bottom-color: #e5e5e5;
+		border-bottom-style: solid;
+		border-bottom-width: 0.5px;
+		border-right-color: #e5e5e5;
+		border-right-style: solid;
+		border-right-width: 0.5px;
+		/* #endif */
+		/* #ifndef APP-NVUE */
+		z-index: 0;
+		border-bottom: 1px #e5e5e5 solid;
+		border-right: 1px #e5e5e5 solid;
+		/* #endif */
+	}
+
+	.uni-grid-item--border-top {
+		position: relative;
+		/* #ifdef APP-NVUE */
+		border-top-color: #e5e5e5;
+		border-top-style: solid;
+		border-top-width: 0.5px;
+		/* #endif */
+		/* #ifndef APP-NVUE */
+		border-top: 1px #e5e5e5 solid;
+		z-index: 0;
+		/* #endif */
+	}
+
+
+	.uni-highlight:active {
+		background-color: #f1f1f1;
+	}
+</style>

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

@@ -0,0 +1,142 @@
+<template>
+	<view class="uni-grid-wrap">
+		<view :id="elId" ref="uni-grid" class="uni-grid" :class="{ 'uni-grid--border': showBorder }" :style="{ 'border-left-color':borderColor}">
+			<slot />
+		</view>
+	</view>
+</template>
+
+<script>
+	// #ifdef APP-NVUE
+	const dom = uni.requireNativePlugin('dom');
+	// #endif
+
+	/**
+	 * Grid 宫格
+	 * @description 宫格组件
+	 * @tutorial https://ext.dcloud.net.cn/plugin?id=27
+	 * @property {Number} column 每列显示个数
+	 * @property {String} borderColor 边框颜色
+	 * @property {Boolean} showBorder 是否显示边框
+	 * @property {Boolean} square 是否方形显示
+	 * @property {Boolean} Boolean 点击背景是否高亮
+	 * @event {Function} change 点击 grid 触发,e={detail:{index:0}},index 为当前点击 gird 下标
+	 */
+	export default {
+		name: 'UniGrid',
+		props: {
+			// 每列显示个数
+			column: {
+				type: Number,
+				default: 3
+			},
+			// 是否显示边框
+			showBorder: {
+				type: Boolean,
+				default: true
+			},
+			// 边框颜色
+			borderColor: {
+				type: String,
+				default: '#e5e5e5'
+			},
+			// 是否正方形显示,默认为 true
+			square: {
+				type: Boolean,
+				default: true
+			},
+			highlight: {
+				type: Boolean,
+				default: true
+			}
+		},
+		provide() {
+			return {
+				grid: this
+			}
+		},
+		data() {
+			const elId = `Uni_${Math.ceil(Math.random() * 10e5).toString(36)}`
+			return {
+				elId,
+				width: 0
+			}
+		},
+		created() {
+			this.children = []
+		},
+		mounted() {
+			this.$nextTick(() => {
+				this.init()
+			})
+		},
+		methods: {
+			init() {
+				setTimeout(() => {
+					this._getSize((width) => {
+						this.children.forEach((item, index) => {
+							item.width = width
+						})
+					})
+				}, 50)
+			},
+			change(e) {
+				this.$emit('change', e)
+			},
+			_getSize(fn) {
+				// #ifndef APP-NVUE
+				uni.createSelectorQuery()
+					.in(this)
+					.select(`#${this.elId}`)
+					.boundingClientRect()
+					.exec(ret => {
+						this.width = parseInt((ret[0].width - 1) / this.column) + 'px'
+						fn(this.width)
+					})
+				// #endif
+				// #ifdef APP-NVUE
+				dom.getComponentRect(this.$refs['uni-grid'], (ret) => {
+					this.width = parseInt((ret.size.width - 1) / this.column) + 'px'
+					fn(this.width)
+				})
+				// #endif
+			}
+		}
+	}
+</script>
+
+<style scoped>
+	.uni-grid-wrap {
+		/* #ifndef APP-NVUE */
+		display: flex;
+		/* #endif */
+		flex: 1;
+		flex-direction: column;
+		/* #ifdef H5 */
+		width: 100%;
+		/* #endif */
+	}
+
+	.uni-grid {
+		/* #ifndef APP-NVUE */
+		display: flex;
+		/* #endif */
+		/* flex: 1;
+ */
+		flex-direction: row;
+		flex-wrap: wrap;
+	}
+
+	.uni-grid--border {
+		position: relative;
+		/* #ifdef APP-NVUE */
+		border-left-color: #e5e5e5;
+		border-left-style: solid;
+		border-left-width: 0.5px;
+		/* #endif */
+		/* #ifndef APP-NVUE */
+		z-index: 1;
+		border-left: 1px #e5e5e5 solid;
+		/* #endif */
+	}
+</style>

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

@@ -0,0 +1,132 @@
+export default {
+	"pulldown": "\ue588",
+	"refreshempty": "\ue461",
+	"back": "\ue471",
+	"forward": "\ue470",
+	"more": "\ue507",
+	"more-filled": "\ue537",
+	"scan": "\ue612",
+	"qq": "\ue264",
+	"weibo": "\ue260",
+	"weixin": "\ue261",
+	"pengyouquan": "\ue262",
+	"loop": "\ue565",
+	"refresh": "\ue407",
+	"refresh-filled": "\ue437",
+	"arrowthindown": "\ue585",
+	"arrowthinleft": "\ue586",
+	"arrowthinright": "\ue587",
+	"arrowthinup": "\ue584",
+	"undo-filled": "\ue7d6",
+	"undo": "\ue406",
+	"redo": "\ue405",
+	"redo-filled": "\ue7d9",
+	"bars": "\ue563",
+	"chatboxes": "\ue203",
+	"camera": "\ue301",
+	"chatboxes-filled": "\ue233",
+	"camera-filled": "\ue7ef",
+	"cart-filled": "\ue7f4",
+	"cart": "\ue7f5",
+	"checkbox-filled": "\ue442",
+	"checkbox": "\ue7fa",
+	"arrowleft": "\ue582",
+	"arrowdown": "\ue581",
+	"arrowright": "\ue583",
+	"smallcircle-filled": "\ue801",
+	"arrowup": "\ue580",
+	"circle": "\ue411",
+	"eye-filled": "\ue568",
+	"eye-slash-filled": "\ue822",
+	"eye-slash": "\ue823",
+	"eye": "\ue824",
+	"flag-filled": "\ue825",
+	"flag": "\ue508",
+	"gear-filled": "\ue532",
+	"reload": "\ue462",
+	"gear": "\ue502",
+	"hand-thumbsdown-filled": "\ue83b",
+	"hand-thumbsdown": "\ue83c",
+	"hand-thumbsup-filled": "\ue83d",
+	"heart-filled": "\ue83e",
+	"hand-thumbsup": "\ue83f",
+	"heart": "\ue840",
+	"home": "\ue500",
+	"info": "\ue504",
+	"home-filled": "\ue530",
+	"info-filled": "\ue534",
+	"circle-filled": "\ue441",
+	"chat-filled": "\ue847",
+	"chat": "\ue263",
+	"mail-open-filled": "\ue84d",
+	"email-filled": "\ue231",
+	"mail-open": "\ue84e",
+	"email": "\ue201",
+	"checkmarkempty": "\ue472",
+	"list": "\ue562",
+	"locked-filled": "\ue856",
+	"locked": "\ue506",
+	"map-filled": "\ue85c",
+	"map-pin": "\ue85e",
+	"map-pin-ellipse": "\ue864",
+	"map": "\ue364",
+	"minus-filled": "\ue440",
+	"mic-filled": "\ue332",
+	"minus": "\ue410",
+	"micoff": "\ue360",
+	"mic": "\ue302",
+	"clear": "\ue434",
+	"smallcircle": "\ue868",
+	"close": "\ue404",
+	"closeempty": "\ue460",
+	"paperclip": "\ue567",
+	"paperplane": "\ue503",
+	"paperplane-filled": "\ue86e",
+	"person-filled": "\ue131",
+	"contact-filled": "\ue130",
+	"person": "\ue101",
+	"contact": "\ue100",
+	"images-filled": "\ue87a",
+	"phone": "\ue200",
+	"images": "\ue87b",
+	"image": "\ue363",
+	"image-filled": "\ue877",
+	"location-filled": "\ue333",
+	"location": "\ue303",
+	"plus-filled": "\ue439",
+	"plus": "\ue409",
+	"plusempty": "\ue468",
+	"help-filled": "\ue535",
+	"help": "\ue505",
+	"navigate-filled": "\ue884",
+	"navigate": "\ue501",
+	"mic-slash-filled": "\ue892",
+	"search": "\ue466",
+	"settings": "\ue560",
+	"sound": "\ue590",
+	"sound-filled": "\ue8a1",
+	"spinner-cycle": "\ue465",
+	"download-filled": "\ue8a4",
+	"personadd-filled": "\ue132",
+	"videocam-filled": "\ue8af",
+	"personadd": "\ue102",
+	"upload": "\ue402",
+	"upload-filled": "\ue8b1",
+	"starhalf": "\ue463",
+	"star-filled": "\ue438",
+	"star": "\ue408",
+	"trash": "\ue401",
+	"phone-filled": "\ue230",
+	"compose": "\ue400",
+	"videocam": "\ue300",
+	"trash-filled": "\ue8dc",
+	"download": "\ue403",
+	"chatbubble-filled": "\ue232",
+	"chatbubble": "\ue202",
+	"cloud-download": "\ue8e4",
+	"cloud-upload-filled": "\ue8e5",
+	"cloud-upload": "\ue8e6",
+	"cloud-download-filled": "\ue8e9",
+	"headphones":"\ue8bf",
+	"shop":"\ue609"
+}

文件差異過大導致無法顯示
+ 10 - 0
components/uni-icons/uni-icons.vue


+ 142 - 0
components/uni-indexed-list/uni-indexed-list-item.vue

@@ -0,0 +1,142 @@
+<template>
+	<view>
+		<view v-if="loaded || list.itemIndex < 15" class="uni-indexed-list__title-wrapper">
+			<text v-if="list.items && list.items.length > 0" class="uni-indexed-list__title">{{ list.key }}</text>
+		</view>
+		<view v-if="(loaded || list.itemIndex < 15) && list.items && list.items.length > 0" class="uni-indexed-list__list">
+			<view v-for="(item, index) in list.items" :key="index" class="uni-indexed-list__item" hover-class="uni-indexed-list__item--hover">
+				<view class="uni-indexed-list__item-container" @click="onClick(idx, index)">
+					<view class="uni-indexed-list__item-border" :class="{'uni-indexed-list__item-border--last':index===list.items.length-1}">
+						<view v-if="showSelect" style="margin-right: 20rpx;">
+							<uni-icons :type="item.checked ? 'checkbox-filled' : 'circle'" :color="item.checked ? '#007aff' : '#aaa'" size="24" />
+						</view>
+						<text class="uni-indexed-list__item-content">{{ item.name }}</text>
+					</view>
+				</view>
+			</view>
+		</view>
+	</view>
+</template>
+
+<script>
+	import uniIcons from '../uni-icons/uni-icons.vue'
+	export default {
+		name: 'UniIndexedList',
+		components: {
+			uniIcons
+		},
+		props: {
+			loaded: {
+				type: Boolean,
+				default: false
+			},
+			idx: {
+				type: Number,
+				default: 0
+			},
+			list: {
+				type: Object,
+				default () {
+					return {}
+				}
+			},
+			showSelect: {
+				type: Boolean,
+				default: false
+			}
+		},
+		methods: {
+			onClick(idx, index) {
+				this.$emit("itemClick", {
+					idx,
+					index
+				})
+			}
+		}
+	}
+</script>
+
+<style scoped>
+	.uni-indexed-list__list {
+		background-color: #ffffff;
+		/* #ifndef APP-NVUE */
+		display: flex;
+		/* #endif */
+		flex-direction: column;
+		border-top-style: solid;
+		border-top-width: 1px;
+		border-top-color: #e5e5e5;
+	}
+
+	.uni-indexed-list__item {
+		font-size: 16;
+		/* #ifndef APP-NVUE */
+		display: flex;
+		/* #endif */
+		flex: 1;
+		flex-direction: row;
+		justify-content: space-between;
+		align-items: center;
+	}
+
+	.uni-indexed-list__item-container {
+		padding-left: 15px;
+		flex: 1;
+		position: relative;
+		/* #ifndef APP-NVUE */
+		display: flex;
+		box-sizing: border-box;
+		/* #endif */
+		flex-direction: row;
+		justify-content: space-between;
+		align-items: center;
+	}
+
+	.uni-indexed-list__item-border {
+		flex: 1;
+		position: relative;
+		/* #ifndef APP-NVUE */
+		display: flex;
+		box-sizing: border-box;
+		/* #endif */
+		flex-direction: row;
+		justify-content: space-between;
+		align-items: center;
+		height: 50px;
+		padding: 15px;
+		padding-left: 0;
+		border-bottom-style: solid;
+		border-bottom-width: 1px;
+		border-bottom-color: #e5e5e5;
+	}
+
+	.uni-indexed-list__item-border--last {
+		border-bottom-width: 0px;
+	}
+
+	.uni-indexed-list__item-content {
+		flex: 1;
+		font-size: 14px;
+	}
+
+	.uni-indexed-list {
+		/* #ifndef APP-NVUE */
+		display: flex;
+		/* #endif */
+		flex-direction: row;
+	}
+
+	.uni-indexed-list__title-wrapper {
+		/* #ifndef APP-NVUE */
+		display: flex;
+		width: 100%;
+		/* #endif */
+		background-color: #f7f7f7;
+	}
+
+	.uni-indexed-list__title {
+		padding: 6px 12px;
+		line-height: 24px;
+		font-size: 12px;
+	}
+</style>

+ 317 - 0
components/uni-indexed-list/uni-indexed-list.vue

@@ -0,0 +1,317 @@
+<template>
+	<view class="uni-indexed-list" ref="list" id="list">
+		<!-- #ifdef APP-NVUE -->
+		<list class="uni-indexed-list__scroll" scrollable="true" show-scrollbar="false">
+			<cell v-for="(list, idx) in lists" :key="idx" :ref="'uni-indexed-list-' + idx">
+				<!-- #endif -->
+				<!-- #ifndef APP-NVUE -->
+				<scroll-view :scroll-into-view="scrollViewId" class="uni-indexed-list__scroll" scroll-y>
+					<view v-for="(list, idx) in lists" :key="idx" :id="'uni-indexed-list-' + idx">
+						<!-- #endif -->
+						<uni-indexed-list-item :list="list" :loaded="loaded" :idx="idx" :showSelect="showSelect" @itemClick="onClick"></uni-indexed-list-item>
+						<!-- #ifndef APP-NVUE -->
+					</view>
+				</scroll-view>
+				<!-- #endif -->
+				<!-- #ifdef APP-NVUE -->
+			</cell>
+		</list>
+		<!-- #endif -->
+		<view :class="touchmove ? 'uni-indexed-list__menu--active' : ''" @touchstart="touchStart" @touchmove.stop.prevent="touchMove" @touchend="touchEnd" class="uni-indexed-list__menu">
+			<view v-for="(list, key) in lists" :key="key" class="uni-indexed-list__menu-item">
+				<text class="uni-indexed-list__menu-text" :class="touchmoveIndex == key ? 'uni-indexed-list__menu-text--active' : ''">{{ list.key }}</text>
+			</view>
+		</view>
+		<view v-if="touchmove" class="uni-indexed-list__alert-wrapper">
+			<text class="uni-indexed-list__alert">{{ lists[touchmoveIndex].key }}</text>
+		</view>
+	</view>
+</template>
+<script>
+	import uniIcons from '../uni-icons/uni-icons.vue'
+	import uniIndexedListItem from './uni-indexed-list-item.vue'
+	// #ifdef APP-NVUE
+	const dom = weex.requireModule('dom');
+	// #endif
+	// #ifdef APP-PLUS
+	function throttle(func, delay) {
+		var prev = Date.now();
+		return function() {
+			var context = this;
+			var args = arguments;
+			var now = Date.now();
+			if (now - prev >= delay) {
+				func.apply(context, args);
+				prev = Date.now();
+			}
+		}
+	}
+
+	function touchMove(e) {
+		let pageY = e.touches[0].pageY
+		let index = Math.floor((pageY - this.winOffsetY) / this.itemHeight)
+		if (this.touchmoveIndex === index) {
+			return false
+		}
+		let item = this.lists[index]
+		if (item) {
+			// #ifndef APP-NVUE
+			this.scrollViewId = 'uni-indexed-list-' + index
+			this.touchmoveIndex = index
+			// #endif
+			// #ifdef APP-NVUE
+			dom.scrollToElement(this.$refs['uni-indexed-list-' + index][0], {
+				animated: false
+			})
+			this.touchmoveIndex = index
+			// #endif
+		}
+	}
+	const throttleTouchMove = throttle(touchMove, 40)
+	// #endif
+
+	/**
+	 * IndexedList 索引列表
+	 * @description 用于展示索引列表
+	 * @tutorial https://ext.dcloud.net.cn/plugin?id=375
+	 * @property {Boolean} showSelect = [true|false] 展示模式
+	 * 	@value true 展示模式
+	 * 	@value false 选择模式
+	 * @property {Object} options 索引列表需要的数据对象
+	 * @event {Function} click 点击列表事件 ,返回当前选择项的事件对象
+	 * @example <uni-indexed-list options="" showSelect="false" @click=""></uni-indexed-list>
+	 */
+	export default {
+		name: 'UniIndexedList',
+		components: {
+			uniIcons,
+			uniIndexedListItem
+		},
+		props: {
+			options: {
+				type: Array,
+				default () {
+					return []
+				}
+			},
+			showSelect: {
+				type: Boolean,
+				default: false
+			}
+		},
+		data() {
+			return {
+				lists: [],
+				winHeight: 0,
+				itemHeight: 0,
+				winOffsetY: 0,
+				touchmove: false,
+				touchmoveIndex: -1,
+				scrollViewId: '',
+				touchmoveTimeout: '',
+				loaded: false
+			}
+		},
+		watch: {
+			options: {
+				handler: function() {
+					this.setList()
+				},
+				deep: true
+			}
+		},
+		mounted() {
+			setTimeout(() => {
+				this.setList()
+			}, 50)
+			setTimeout(() => {
+				this.loaded = true
+			}, 300);
+		},
+		methods: {
+			setList() {
+				let index = 0;
+				this.lists = []
+				this.options.forEach((value, index) => {
+					if (value.data.length === 0) {
+						return
+					}
+					let indexBefore = index
+					let items = value.data.map(item => {
+						let obj = {}
+						obj['key'] = value.letter
+						obj['name'] = item
+						obj['itemIndex'] = index
+						index++
+						obj.checked = item.checked ? item.checked : false
+						return obj
+					})
+					this.lists.push({
+						title: value.letter,
+						key: value.letter,
+						items: items,
+						itemIndex: indexBefore
+					})
+				})
+				// #ifndef APP-NVUE
+				uni.createSelectorQuery()
+					.in(this)
+					.select('#list')
+					.boundingClientRect()
+					.exec(ret => {
+						this.winOffsetY = ret[0].top
+						this.winHeight = ret[0].height
+						this.itemHeight = this.winHeight / this.lists.length
+					})
+				// #endif
+				// #ifdef APP-NVUE
+				dom.getComponentRect(this.$refs['list'], (res) => {
+					this.winOffsetY = res.size.top
+					this.winHeight = res.size.height
+					this.itemHeight = this.winHeight / this.lists.length
+				})
+				// #endif
+			},
+			touchStart(e) {
+				this.touchmove = true
+				let pageY = e.touches[0].pageY
+				let index = Math.floor((pageY - this.winOffsetY) / this.itemHeight)
+				let item = this.lists[index]
+				if (item) {
+					this.scrollViewId = 'uni-indexed-list-' + index
+					this.touchmoveIndex = index
+					// #ifdef APP-NVUE
+					dom.scrollToElement(this.$refs['uni-indexed-list-' + index][0], {
+						animated: false
+					})
+					// #endif
+				}
+			},
+			touchMove(e) {
+				// #ifndef APP-PLUS
+				let pageY = e.touches[0].pageY
+				let index = Math.floor((pageY - this.winOffsetY) / this.itemHeight)
+				if (this.touchmoveIndex === index) {
+					return false
+				}
+				let item = this.lists[index]
+				if (item) {
+					this.scrollViewId = 'uni-indexed-list-' + index
+					this.touchmoveIndex = index
+				}
+				// #endif
+				// #ifdef APP-PLUS
+				throttleTouchMove.call(this, e)
+				// #endif
+			},
+			touchEnd() {
+				this.touchmove = false
+				this.touchmoveIndex = -1
+			},
+			onClick(e) {
+				let {
+					idx,
+					index
+				} = e
+				let obj = {}
+				for (let key in this.lists[idx].items[index]) {
+					obj[key] = this.lists[idx].items[index][key]
+				}
+				let select = []
+				if (this.showSelect) {
+					this.lists[idx].items[index].checked = !this.lists[idx].items[index].checked
+					this.lists.forEach((value, idx) => {
+						value.items.forEach((item, index) => {
+							if (item.checked) {
+								let obj = {}
+								for (let key in this.lists[idx].items[index]) {
+									obj[key] = this.lists[idx].items[index][key]
+								}
+								select.push(obj)
+							}
+						})
+					})
+				}
+				this.$emit('click', {
+					item: obj,
+					select: select
+				})
+			}
+		}
+	}
+</script>
+<style scoped>
+	.uni-indexed-list {
+		position: absolute;
+		left: 0;
+		top: 0;
+		right: 0;
+		bottom: 0;
+		/* #ifndef APP-NVUE */
+		display: flex;
+		/* #endif */
+		flex-direction: row;
+	}
+
+	.uni-indexed-list__scroll {
+		flex: 1;
+	}
+
+	.uni-indexed-list__menu {
+		width: 24px;
+		background-color: lightgrey;
+		/* #ifndef APP-NVUE */
+		display: flex;
+		/* #endif */
+		flex-direction: column;
+	}
+
+	.uni-indexed-list__menu-item {
+		/* #ifndef APP-NVUE */
+		display: flex;
+		/* #endif */
+		flex: 1;
+		align-items: center;
+		justify-content: center;
+	}
+
+	.uni-indexed-list__menu-text {
+		line-height: 20px;
+		font-size: 12px;
+		text-align: center;
+		color: #aaa;
+	}
+
+	.uni-indexed-list__menu--active {
+		background-color: rgb(200, 200, 200);
+	}
+
+	.uni-indexed-list__menu-text--active {
+		color: #007aff;
+	}
+
+	.uni-indexed-list__alert-wrapper {
+		position: absolute;
+		left: 0;
+		top: 0;
+		right: 0;
+		bottom: 0;
+		/* #ifndef APP-NVUE */
+		display: flex;
+		/* #endif */
+		flex-direction: row;
+		align-items: center;
+		justify-content: center;
+	}
+
+	.uni-indexed-list__alert {
+		width: 80px;
+		height: 80px;
+		border-radius: 80px;
+		text-align: center;
+		line-height: 80px;
+		font-size: 35px;
+		color: #fff;
+		background-color: rgba(0, 0, 0, 0.5);
+	}
+</style>

+ 80 - 0
components/uni-link/uni-link.vue

@@ -0,0 +1,80 @@
+<template>
+  <text
+    class="uni-link"
+    :class="{
+      'uni-link--withline': showUnderLine === true || showUnderLine === 'true',
+    }"
+    :style="{ color, fontSize: fontSize + 'px' }"
+    @click="openURL"
+    >{{ text }}</text
+  >
+</template>
+
+<script>
+/**
+ * Link 外部网页超链接组件
+ * @description uni-link是一个外部网页超链接组件,在小程序内复制url,在app内打开外部浏览器,在h5端打开新网页
+ * @tutorial https://ext.dcloud.net.cn/plugin?id=1182
+ * @property {String} href 点击后打开的外部网页url
+ * @property {String} text 显示的文字
+ * @property {Boolean} showUnderLine 是否显示下划线
+ * @property {String} copyTips 在小程序端复制链接时显示的提示语
+ * @property {String} color 链接文字颜色
+ * @property {String} fontSize 链接文字大小
+ * @example * <uni-link href="https://ext.dcloud.net.cn" text="https://ext.dcloud.net.cn"></uni-link>
+ */
+export default {
+  name: "uniLink",
+  props: {
+    href: {
+      type: String,
+      default: "",
+    },
+    text: {
+      type: String,
+      default: "",
+    },
+    showUnderLine: {
+      type: [Boolean, String],
+      default: true,
+    },
+    copyTips: {
+      type: String,
+      default: "已自动复制网址,请在手机浏览器里粘贴该网址",
+    },
+    color: {
+      type: String,
+      default: "#999999",
+    },
+    fontSize: {
+      type: [Number, String],
+      default: 14,
+    },
+  },
+  methods: {
+    openURL() {
+      // #ifdef APP-PLUS
+      plus.runtime.openURL(this.href);
+      // #endif
+      // #ifdef H5
+      window.open(this.href);
+      // #endif
+      // #ifdef MP
+      uni.setClipboardData({
+        data: this.href,
+      });
+      uni.showModal({
+        content: this.copyTips,
+        showCancel: false,
+      });
+      // #endif
+    },
+  },
+};
+</script>
+
+<style scoped>
+.uni-link--withline {
+  text-decoration: underline;
+}
+</style>

部分文件因文件數量過多而無法顯示