jwyu 3 年之前
當前提交
c62a278fd7
共有 100 個文件被更改,包括 4912 次插入0 次删除
  1. 2 0
      .gitignore
  2. 21 0
      App.vue
  3. 8 0
      README.md
  4. 14 0
      api/activity.js
  5. 27 0
      api/common.js
  6. 40 0
      api/user.js
  7. 55 0
      custom-tab-bar/index.js
  8. 3 0
      custom-tab-bar/index.json
  9. 7 0
      custom-tab-bar/index.wxml
  10. 39 0
      custom-tab-bar/index.wxss
  11. 18 0
      main.js
  12. 80 0
      manifest.json
  13. 48 0
      mixin/index.js
  14. 166 0
      pages-activity/detail.vue
  15. 48 0
      pages-activity/noAuthority.vue
  16. 二進制
      pages-activity/static/audio-doing.png
  17. 二進制
      pages-activity/static/audio-play.png
  18. 224 0
      pages-applyPermission/applyPermission.vue
  19. 31 0
      pages-applyPermission/applyResult.vue
  20. 126 0
      pages-applyPermission/selectVariety.vue
  21. 二進制
      pages-applyPermission/static/radio-s.png
  22. 二進制
      pages-applyPermission/static/upload.png
  23. 145 0
      pages.json
  24. 281 0
      pages/activity/activity.vue
  25. 14 0
      pages/buy/buy.vue
  26. 14 0
      pages/chart/chart.vue
  27. 222 0
      pages/login.vue
  28. 14 0
      pages/report/report.vue
  29. 102 0
      pages/user/user.vue
  30. 二進制
      static/logo.png
  31. 二進制
      static/tabbar/activity-s.png
  32. 二進制
      static/tabbar/activity.png
  33. 二進制
      static/tabbar/buy-s.png
  34. 二進制
      static/tabbar/buy.png
  35. 二進制
      static/tabbar/chart-s.png
  36. 二進制
      static/tabbar/chart.png
  37. 二進制
      static/tabbar/report-s.png
  38. 二進制
      static/tabbar/report.png
  39. 二進制
      static/tabbar/user-s.png
  40. 二進制
      static/tabbar/user.png
  41. 15 0
      store/index.js
  42. 32 0
      store/modules/user.js
  43. 47 0
      style/common.scss
  44. 39 0
      uni.scss
  45. 44 0
      utils/common.js
  46. 27 0
      utils/config.js
  47. 0 0
      utils/moment-with-locales.min.js
  48. 128 0
      utils/request.js
  49. 19 0
      utils/uni-async.js
  50. 54 0
      utils/upload.js
  51. 11 0
      utils/vantRegister.js
  52. 1 0
      wxcomponents/vant/action-sheet/index.d.ts
  53. 70 0
      wxcomponents/vant/action-sheet/index.js
  54. 8 0
      wxcomponents/vant/action-sheet/index.json
  55. 111 0
      wxcomponents/vant/action-sheet/index.vue
  56. 69 0
      wxcomponents/vant/action-sheet/index.wxml
  57. 1 0
      wxcomponents/vant/action-sheet/index.wxss
  58. 1 0
      wxcomponents/vant/area/index.d.ts
  59. 217 0
      wxcomponents/vant/area/index.js
  60. 6 0
      wxcomponents/vant/area/index.json
  61. 231 0
      wxcomponents/vant/area/index.vue
  62. 20 0
      wxcomponents/vant/area/index.wxml
  63. 8 0
      wxcomponents/vant/area/index.wxs
  64. 1 0
      wxcomponents/vant/area/index.wxss
  65. 1 0
      wxcomponents/vant/button/index.d.ts
  66. 64 0
      wxcomponents/vant/button/index.js
  67. 7 0
      wxcomponents/vant/button/index.json
  68. 90 0
      wxcomponents/vant/button/index.vue
  69. 53 0
      wxcomponents/vant/button/index.wxml
  70. 39 0
      wxcomponents/vant/button/index.wxs
  71. 0 0
      wxcomponents/vant/button/index.wxss
  72. 37 0
      wxcomponents/vant/calendar/calendar.vue
  73. 68 0
      wxcomponents/vant/calendar/calendar.wxml
  74. 1 0
      wxcomponents/vant/calendar/components/header/index.d.ts
  75. 37 0
      wxcomponents/vant/calendar/components/header/index.js
  76. 3 0
      wxcomponents/vant/calendar/components/header/index.json
  77. 64 0
      wxcomponents/vant/calendar/components/header/index.vue
  78. 16 0
      wxcomponents/vant/calendar/components/header/index.wxml
  79. 1 0
      wxcomponents/vant/calendar/components/header/index.wxss
  80. 6 0
      wxcomponents/vant/calendar/components/month/index.d.ts
  81. 154 0
      wxcomponents/vant/calendar/components/month/index.js
  82. 3 0
      wxcomponents/vant/calendar/components/month/index.json
  83. 192 0
      wxcomponents/vant/calendar/components/month/index.vue
  84. 39 0
      wxcomponents/vant/calendar/components/month/index.wxml
  85. 71 0
      wxcomponents/vant/calendar/components/month/index.wxs
  86. 0 0
      wxcomponents/vant/calendar/components/month/index.wxss
  87. 1 0
      wxcomponents/vant/calendar/index.d.ts
  88. 337 0
      wxcomponents/vant/calendar/index.js
  89. 10 0
      wxcomponents/vant/calendar/index.json
  90. 365 0
      wxcomponents/vant/calendar/index.vue
  91. 25 0
      wxcomponents/vant/calendar/index.wxml
  92. 37 0
      wxcomponents/vant/calendar/index.wxs
  93. 1 0
      wxcomponents/vant/calendar/index.wxss
  94. 12 0
      wxcomponents/vant/calendar/utils.d.ts
  95. 83 0
      wxcomponents/vant/calendar/utils.js
  96. 25 0
      wxcomponents/vant/calendar/utils.wxs
  97. 1 0
      wxcomponents/vant/card/index.d.ts
  98. 49 0
      wxcomponents/vant/card/index.js
  99. 6 0
      wxcomponents/vant/card/index.json
  100. 105 0
      wxcomponents/vant/card/index.vue

+ 2 - 0
.gitignore

@@ -0,0 +1,2 @@
+unpackage
+.hbuilderx

+ 21 - 0
App.vue

@@ -0,0 +1,21 @@
+<script>
+	export default {
+		onLaunch: function() {
+			console.log('App Launch')
+			this.$store.dispatch('getUserInfo')
+		},
+		onShow: function() {
+			console.log('App Show')
+		},
+		onHide: function() {
+			console.log('App Hide')
+		}
+	}
+</script>
+
+<style lang='scss'>
+	@import "/wxcomponents/vant/common/index.wxss";
+	/* 页面公共样式 */
+	@import "./style/common.scss"
+	
+</style>

+ 8 - 0
README.md

@@ -0,0 +1,8 @@
+# 弘则研报小程序
+
+  1. 项目使用vant UI 框架,如要更新vant 手动下载官方代码包放入wxcomponents中即可;如要使用vant 请手动在pages.json中添加相应组件。
+  2. 该项目使用自定义tabbar,自定义tabbar在根目录custom-tab-bar(文件名不可修改),原生小程序写法。
+  3. 该项目大图请放在oss中,在utils/config中配置,已使用全局mixin将图片挂在data中
+  4. 分包请按照‘pages-分包模块名’命名
+
+	

+ 14 - 0
api/activity.js

@@ -0,0 +1,14 @@
+// 活动模块
+
+import {httpGet,httpPost} from "@/utils/request.js"
+
+/**
+ * 活动列表
+ * @param {active_state} 活动状态 1-未开始 2-进行中 3-已结束
+ * @param {activity_type} 活动类型 1-线上会议 3-线下沙龙
+ * @param page
+ * @param limit
+ */
+export const apiActivityList=params=>{
+    return httpGet('/activity/getPageList',params)
+}

+ 27 - 0
api/common.js

@@ -0,0 +1,27 @@
+// 公共api 方法
+
+import {httpGet,httpPost} from "@/utils/request.js"
+
+/**
+ * 获取手机验证码
+ * @param mobile 手机号
+ * @param area_num 手机号区号
+ */
+export const apiGetSMSCode=params=>{
+    return httpGet('/user/get_sms_code',params)
+}
+
+/**
+ * 获取邮箱验证码
+ * @param email 邮箱
+ */
+export const apiGetEmailCode=params=>{
+    return httpGet('/user/get_email_code',params)
+}
+
+/**
+ * 获取所有可以申请的品种权限列表
+ */
+export const apiGetPermissionList=params=>{
+    return httpGet('/public/get_apply_variety_list')
+}

+ 40 - 0
api/user.js

@@ -0,0 +1,40 @@
+import {httpGet,httpPost} from "@/utils/request.js"
+
+
+/**
+ * 微信登录
+ * @param code 微信code 
+ */
+export const apiWechatLogin=params=>{
+	return httpGet('/wechat/login',params)
+}
+
+/**
+ * 获取个人信息
+ */
+export const apiUserInfo=()=>{
+	return httpGet('/user/info',{})
+}
+
+/**
+ * 手机号/邮箱登录
+ * @param area_num 手机号区号
+ * @param bind_type 1手机号 2邮箱
+ * @param email
+ * @param mobile
+ * @param verify_code
+ */
+export const apiUserLogin=params=>{
+	return httpPost('/user/login',params)
+}
+
+/**
+ * 用户权限申请
+ * @param business_card_url 名片地址
+ * @param company_name 公司名
+ * @param permission 选择的权限
+ * @param real_name 姓名
+ */
+export const apiApplyPermission=params=>{
+	return httpPost('/user/apply',params)
+}

+ 55 - 0
custom-tab-bar/index.js

@@ -0,0 +1,55 @@
+Component({
+  data: {
+    selected: 'pages/buy/buy',
+    color: "#1F243A",
+    selectedColor: "#E3B377",
+    list: [
+      {
+        pagePath: "pages/buy/buy",
+        text: "已购",
+        iconPath: "../static/tabbar/buy.png",
+        selectedIconPath: "../static/tabbar/buy-s.png",
+        hidden: false,
+      },
+      {
+        pagePath: "pages/report/report",
+        text: "报告",
+        iconPath: "../static/tabbar/report.png",
+        selectedIconPath: "../static/tabbar/report-s.png",
+        hidden: false,
+      },
+      {
+        pagePath: "pages/chart/chart",
+        text: "图库",
+        iconPath: "../static/tabbar/chart.png",
+        selectedIconPath: "../static/tabbar/chart-s.png",
+        hidden: false,
+      },
+      {
+        pagePath: "pages/activity/activity",
+        text: "活动",
+        iconPath: "../static/tabbar/activity.png",
+        selectedIconPath: "../static/tabbar/activity-s.png",
+        hidden: false,
+      },
+      {
+        pagePath: "pages/user/user",
+        text: "我的",
+        iconPath: "../static/tabbar/user.png",
+        selectedIconPath: "../static/tabbar/user-s.png",
+        hidden: false,
+      },
+    ],
+  },
+  attached() {},
+  methods: {
+    switchTab(e) {
+      const data = e.currentTarget.dataset;
+      const url = "/" + data.path;
+      wx.switchTab({ url });
+      // this.setData({
+      //   selected: data.text,
+      // });
+    },
+  },
+});

+ 3 - 0
custom-tab-bar/index.json

@@ -0,0 +1,3 @@
+{
+  "component": true
+}

+ 7 - 0
custom-tab-bar/index.wxml

@@ -0,0 +1,7 @@
+<cover-view class="tab-bar">
+  <cover-view class="tab-bar-border"></cover-view>
+  <cover-view wx:for="{{list}}" wx:key="index" class="tab-bar-item" data-path="{{item.pagePath}}" data-text="{{item.text}}" bindtap="switchTab">
+    <cover-image src="{{selected === item.pagePath ? item.selectedIconPath : item.iconPath}}"></cover-image>
+    <cover-view style="color: {{selected === item.pagePath ? selectedColor : color}}">{{item.text}}</cover-view>
+  </cover-view>
+</cover-view>

+ 39 - 0
custom-tab-bar/index.wxss

@@ -0,0 +1,39 @@
+.tab-bar {
+  position: fixed;
+  bottom: 0;
+  left: 0;
+  right: 0;
+  height: 48px;
+  background: white;
+  display: flex;
+  padding-bottom: env(safe-area-inset-bottom);
+}
+
+.tab-bar-border {
+  background-color: rgba(0, 0, 0, 0.33);
+  position: absolute;
+  left: 0;
+  top: 0;
+  width: 100%;
+  height: 1px;
+  transform: scaleY(0.5);
+}
+
+.tab-bar-item {
+  flex: 1;
+  text-align: center;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  flex-direction: column;
+}
+
+.tab-bar-item cover-image {
+  width: 22px;
+  height: 22px;
+}
+
+.tab-bar-item cover-view {
+  font-size: 10px;
+  margin-top:3px;
+}

+ 18 - 0
main.js

@@ -0,0 +1,18 @@
+import Vue from 'vue'
+import App from './App'
+import store from './store'
+import mixin from './mixin/index'
+
+// vant 全局组件 
+import './utils/vantRegister';
+
+Vue.config.productionTip = false
+
+App.mpType = 'app'
+
+Vue.mixin(mixin);
+const app = new Vue({
+	store,
+    ...App
+})
+app.$mount()

+ 80 - 0
manifest.json

@@ -0,0 +1,80 @@
+{
+    "name" : "",
+    "appid" : "",
+    "description" : "",
+    "versionName" : "1.0.0",
+    "versionCode" : "100",
+    "transformPx" : false,
+    /* 5+App特有相关 */
+    "app-plus" : {
+        "usingComponents" : true,
+        "nvueStyleCompiler" : "uni-app",
+        "compilerVersion" : 3,
+        "splashscreen" : {
+            "alwaysShowBeforeRender" : true,
+            "waiting" : true,
+            "autoclose" : true,
+            "delay" : 0
+        },
+        /* 模块配置 */
+        "modules" : {},
+        /* 应用发布信息 */
+        "distribute" : {
+            /* android打包配置 */
+            "android" : {
+                "permissions" : [
+                    "<uses-permission android:name=\"android.permission.CHANGE_NETWORK_STATE\"/>",
+                    "<uses-permission android:name=\"android.permission.MOUNT_UNMOUNT_FILESYSTEMS\"/>",
+                    "<uses-permission android:name=\"android.permission.VIBRATE\"/>",
+                    "<uses-permission android:name=\"android.permission.READ_LOGS\"/>",
+                    "<uses-permission android:name=\"android.permission.ACCESS_WIFI_STATE\"/>",
+                    "<uses-feature android:name=\"android.hardware.camera.autofocus\"/>",
+                    "<uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\"/>",
+                    "<uses-permission android:name=\"android.permission.CAMERA\"/>",
+                    "<uses-permission android:name=\"android.permission.GET_ACCOUNTS\"/>",
+                    "<uses-permission android:name=\"android.permission.READ_PHONE_STATE\"/>",
+                    "<uses-permission android:name=\"android.permission.CHANGE_WIFI_STATE\"/>",
+                    "<uses-permission android:name=\"android.permission.WAKE_LOCK\"/>",
+                    "<uses-permission android:name=\"android.permission.FLASHLIGHT\"/>",
+                    "<uses-feature android:name=\"android.hardware.camera\"/>",
+                    "<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>"
+                ],
+                "abiFilters" : [ "armeabi-v7a" ]
+            },
+            /* ios打包配置 */
+            "ios" : {},
+            /* SDK配置 */
+            "sdkConfigs" : {}
+        }
+    },
+    /* 快应用特有相关 */
+    "quickapp" : {},
+    /* 小程序特有相关 */
+    "mp-weixin" : {
+        "appid" : "wxb059c872d79b9967",
+        "setting" : {
+            "urlCheck" : false,
+            "es6" : false,
+            "minified" : true,
+            "postcss" : true
+        },
+        "usingComponents" : true
+    },
+    "mp-alipay" : {
+        "usingComponents" : true
+    },
+    "mp-baidu" : {
+        "usingComponents" : true
+    },
+    "mp-toutiao" : {
+        "usingComponents" : true
+    },
+    "uniStatistics" : {
+        "enable" : false
+    },
+    "h5" : {
+        "sdkConfigs" : {
+            "maps" : {}
+        }
+    }
+}

+ 48 - 0
mixin/index.js

@@ -0,0 +1,48 @@
+const moment=require('../utils/moment-with-locales.min')
+moment.locale('zh-cn');
+
+// 引入全局配置的图片资源地址 
+import {globalImgUrls} from "../utils/config"
+
+
+module.exports = {
+  filters: {
+    /**
+     * 活动时间格式化
+     * @param {2021-11-12T09:25:01+08:00} start 开始时间
+     * @param 2021-11-12T09:25:01+08:00 end 结束时间
+     * @returns 2020-06-04 15:30-16:30 星期一
+     */
+    formatActivityTime(start,end){
+      const week=moment(start).format('dddd');
+      const day=moment(start).format('YYYY-MM-DD');
+      const startTime=moment(start).format('HH:mm');
+      const endTime=moment(end).format('HH:mm');
+      return `${day} ${startTime}-${endTime} ${week}`
+    }
+  },
+  data() {
+    return {
+      globalImgUrls:globalImgUrls,// 图片资源
+      userInfo:{},//个人信息
+    };
+  },
+  onLoad() {
+    const page = this.$mp.page;
+    if( page.route==='pages/buy/buy'||
+        page.route=='pages/report/report'||
+        page.route=='pages/chart/chart'||
+        page.route=='pages/activity/activity'||
+        page.route=='pages/user/user'
+    )
+    if (typeof page.getTabBar === "function" && page.getTabBar()) {
+      page.getTabBar().setData({
+        selected: page.route,
+      });
+    }
+  },
+  onShow(){
+    this.userInfo=this.$store.state.user.userInfo
+  },
+  methods: {},
+};

+ 166 - 0
pages-activity/detail.vue

@@ -0,0 +1,166 @@
+<template>
+  <view class="activity-detail">
+        <view class="top-wrap">
+            <view class="status status-before">进行中</view>
+            <view class="title">织造终端双周报</view>
+            <view class="name">主讲人:某某</view>
+            <view class="time">活动时间:2020-06-04 15:30-16:30 星期一</view>
+        </view>
+        <view class="intro-wrap">能源紧缺拉涨成本,坯布局部供应紧张</view>
+        <view class="info-wrap">
+            <view class="flex item" v-for="item in infoList" :key="item.label">
+                <view class="label">{{item.label}}:</view>
+                <view :class="['content',item.color&&'yellow-color']">{{item.text}}</view>
+            </view>
+        </view>
+        <view class="audio-wrap">
+            <view class="audio-item" v-for="item in 4" :key="item">
+                <image class="icon" src="./static/audio-play.png" model="aspectFill"></image>
+                <view class="name">音频123.MP3</view>
+                <view class="time">
+                    <text>05分30秒</text>
+                    <text>| 已听完</text>
+                </view>
+            </view>
+        </view>
+        <view class="btn-wrap">
+            <view class="global-btn-yellow-change btn">报名线下参会(15/20)</view>
+            <view class="global-btn-yellow-change btn">会议提醒</view>
+            <p class="tips">(会前15分钟推送微信消息提醒)</p>
+        </view>
+  </view>
+</template>
+
+<script>
+// 活动详情
+export default {
+  name: "ActivityDetail",
+  data() {
+    return {
+      id: 0, //活动id
+      infoList:[
+          {
+              label:'活动时间',
+              text:'2021-10-18 16:30-17:00 星期一2021-10-18 16:30-17:00 星期一',
+          },
+          {
+              label:'主讲人',
+              text:'某某',
+          },
+          {
+              label:'大陆拨号',
+              text:'021-34994556',
+              color:'yellow'
+          },
+      ]
+    };
+  },
+  onLoad(options) {
+    this.id = options.id;
+    setTimeout(()=>{
+        uni.redirectTo({
+            url: '/pages-activity/noAuthority'
+        });
+    },2000)
+  },
+};
+</script>
+
+<style lang="scss">
+.top-wrap {
+  width: 100%;
+  height: 370rpx;
+  background: linear-gradient(312deg, rgba(0, 0, 0, 0.8) 0%, rgba(43, 43, 43, 0.8) 100%);
+  color: $global-text-color-main;
+  padding-top: 144rpx;
+  padding-left: 40rpx;
+  padding-right: 40rpx;
+  .status {
+    position: absolute;
+    top: 0;
+    left: 0;
+    width: 116rpx;
+    line-height: 44rpx;
+    border-radius: 0px 0px 16rpx 0px;
+    color: $global-text-color-white;
+    text-align: center;
+    font-size: $global-font-size-sm;
+  }
+  .status-before {
+    background-color: #e3b377;
+  }
+  .status-progress {
+    background-color: #3385ff;
+  }
+  .status-end {
+    background-color: #a2a2a2;
+  }
+  .title {
+    font-size: 17px;
+    font-weight: bold;
+  }
+  .name {
+    margin-top: 15rpx;
+    margin-bottom: 40rpx;
+  }
+  .time {
+    opacity: 0.8;
+    font-size: $global-font-size-sm;
+  }
+}
+.intro-wrap{
+    background-color: $global-bg-color-grey;
+    font-size: $global-font-size-lg;
+    font-weight: bold;
+    padding: 14rpx 34rpx;
+}
+.info-wrap{
+    padding: 30rpx 34rpx;
+    .item{
+        margin-bottom: 30rpx;
+        .label{
+            flex-shrink: 0;
+        }
+    }
+    .yellow-color{
+        color: $global-text-color-main;
+    }
+}
+
+.audio-wrap{
+    .audio-item{
+        padding: 36rpx 34rpx;
+        border-bottom: 1px solid $global-border-color;
+        position: relative;
+        .icon{
+            position: absolute;
+            top: 50%;
+            transform: translateY(-50%);
+            right: 45rpx;
+            width: 48rpx;
+            height: 48rpx;
+        }
+        .name{
+            margin-bottom: 10rpx;
+        }
+        .time{
+            font-size: $global-font-size-mini;
+        }
+    }
+}
+
+.btn-wrap{
+    .btn{
+        width: 380rpx;
+        line-height: 70rpx;
+        margin-left: auto;
+        margin-right: auto;
+        margin-top: 40rpx;
+    }
+    .tips{
+        font-size: $global-font-size-sm;
+        color: $global-text-color-999;
+        text-align: center;
+    }
+}
+</style>

+ 48 - 0
pages-activity/noAuthority.vue

@@ -0,0 +1,48 @@
+<template>
+  <view class="noauthority-page">
+      <image class="img" :src="globalImgUrls.img1" mode="widthFix"></image>
+      <view>您暂无权限参加此活动</view>
+      <view>若想参加可以申请开通哦</view>
+      <view class="global-btn-yellow-change btn" @click="handleGoApply">立即申请</view>
+      <view class="global-btn-yellow-plain btn">返回</view>
+  </view>
+</template>
+
+<script>
+export default {
+    name:"noAuthority",
+    data () {
+        return {
+            
+        }
+    },
+    onLoad(){
+    },
+    methods: {
+        handleGoApply(){
+            uni.navigateTo({
+                url:"/pages-activity/applyTrial"
+            })
+        }
+    }
+}
+</script>
+
+<style lang="scss">
+.noauthority-page{
+    padding: 34rpx;
+    text-align: center;
+    font-size: $global-font-size-lg;
+    .img{
+        width: 100%;
+        margin-bottom: 50rpx;
+    }
+    .btn{
+        width: 380rpx;
+        line-height: 70rpx;
+        margin-left: auto;
+        margin-right: auto;
+        margin-top: 40rpx;
+    }
+}
+</style>

二進制
pages-activity/static/audio-doing.png


二進制
pages-activity/static/audio-play.png


+ 224 - 0
pages-applyPermission/applyPermission.vue

@@ -0,0 +1,224 @@
+<template>
+    <view class="apply-trial-page">
+        <view class="tips">请上传您的名片信息,以便工作人员更好地为您服务</view>
+        <view class="flex upload-wrap">
+            <view class="box upload-box" @click="handleUploadCard">
+                <image
+                    class="add-icon"
+                    src="./static/upload.png"
+                    mode="widthFix"
+                    v-if="!cardImg"
+                ></image>
+                <image class="bg-img" :src="cardImg" mode="widthFix" v-if="cardImg"></image>
+                <view class="tips" v-if="!cardImg">上传您的名片</view>
+            </view>
+            <view class="box exp">
+                <view class="tips">名片示例</view>
+            </view>
+        </view>
+
+        <view class="form-wrap">
+            <van-field
+                clearable
+                label="姓名"
+                :value="form.name"
+                placeholder="请输入用户名"
+                :border="false"
+                title-width="70px"
+                @change="inputChange('name', $event)"
+            />
+            <van-field
+                clearable
+                label="公司名"
+                :value="form.companyName"
+                placeholder="请输入公司名"
+                :border="false"
+                title-width="70px"
+                @change="inputChange('companyName', $event)"
+            />
+            <van-field
+                clearable
+                label="手机号"
+                type="number"
+                :value="userInfo.mobile"
+                placeholder="请输入手机号"
+                :border="false"
+                title-width="70px"
+                disabled
+                v-if="!userInfo.email"
+            />
+            <van-field
+                label="关注品种"
+                readonly
+                :value="form.permission"
+                placeholder="请选择关注品种"
+                :border="false"
+                is-link
+                title-width="70px"
+                @click-input="handleGoSelectVariety"
+            />
+        </view>
+
+        <view class="global-btn-yellow-change btn" @click="handleSubmit">提交</view>
+    </view>
+</template>
+
+<script>
+import { uploadImg } from '@/utils/upload'
+import {apiApplyPermission} from '@/api/user'
+export default {
+    name: "ApplyTrial",
+    data() {
+        return {
+            cardImg: '',//上传的名片地址
+            form: {
+                name: "",
+                companyName: '',
+                tel: '',
+                permission: ''
+            },
+        }
+    },
+    onLoad(){
+        this.addEventListenerPermission()
+    },
+    onUnload(){
+		uni.$off('selectPermission')
+	},
+    methods: {
+        // 监听权限选择
+        addEventListenerPermission(){
+            uni.$on('selectPermission',(e)=>{
+                this.form.permission=e.permissionList.join(',')
+            })
+        },
+
+        inputChange(key, event) {
+            this.form[key] = event.detail
+        },
+
+        // 上传名片
+        async handleUploadCard() {
+            const res = await uploadImg()
+            this.cardImg=res
+        },
+
+        // 去选择品种
+        handleGoSelectVariety() {
+            
+            uni.navigateTo({
+                url: "/pages-applyPermission/selectVariety",
+                events:{},
+                success: (res)=> {
+                    // 通过eventChannel向被打开页面传送数据
+                    res.eventChannel.emit('hasPermissionData', { data: this.form.permission })
+                }
+            })
+
+        },
+
+        // 提交
+        async handleSubmit(){
+            let params={
+                business_card_url:this.cardImg,
+                company_name:this.form.companyName,
+                permission:this.form.permission,
+                real_name:this.form.name,
+            }
+            if(!params.company_name){
+                uni.showToast({
+                    title:"请填写公司名",
+                    icon:"none"
+                })
+                return
+            }
+            if(!params.real_name){
+                uni.showToast({
+                    title:"请填写姓名",
+                    icon:"none"
+                })
+                return
+            }
+            if(!params.permission){
+                uni.showToast({
+                    title:"请选择品种",
+                    icon:"none"
+                })
+                return
+            }
+
+            const res=await apiApplyPermission(params)
+            if(res.code===200){
+                uni.showToast({
+                    title:"申请成功",
+                    icon:"none"
+                })
+                this.$store.dispatch('getUserInfo')//重新获取一次个人信息
+                setTimeout(()=>{
+                    uni.redirectTo({
+                        url:"/pages-applyPermission/applyResult"
+                    })
+                },1000)
+            }
+
+        }
+    }
+}
+</script>
+
+<style lang="scss">
+.tips {
+    background-color: #fbf3e8;
+    color: $global-text-color-grey;
+    font-size: $global-font-size-sm;
+    padding: 14rpx 34rpx;
+}
+.upload-wrap {
+    padding: 30rpx 34rpx 40rpx 34rpx;
+    justify-content: space-between;
+    .box {
+        background-color: $global-bg-color-grey;
+        width: 326rpx;
+        height: 260rpx;
+        border-radius: 16rpx;
+        overflow: hidden;
+        position: relative;
+        .tips {
+            background-color: rgba(0, 0, 0, 0.2);
+            color: $global-text-color-white;
+            position: absolute;
+            bottom: 0;
+            left: 0;
+            width: 100%;
+            text-align: center;
+        }
+        .bg-img{
+            width: 100%;
+            height: 100%;
+        }
+    }
+    .upload-box {
+        .add-icon {
+            width: 30%;
+            position: absolute;
+            top: 53rpx;
+            left: 50%;
+            transform: translateX(-50%);
+        }
+    }
+}
+
+.van-cell {
+    border-bottom: 1px solid $global-border-color;
+}
+.form-wrap {
+    border-top: 10rpx solid #f6f6f6;
+}
+
+.btn {
+    width: 380rpx;
+    margin-top: 80rpx;
+    margin-left: auto;
+    margin-right: auto;
+}
+</style>

+ 31 - 0
pages-applyPermission/applyResult.vue

@@ -0,0 +1,31 @@
+<template>
+  <view class="apply-result-page">
+    <image style="width:100px" :src="globalImgUrls.imgSuccess"></image>
+    <view style="margin-top:20px">申请已提交</view>
+    <view style="margin-top:15px">请等待销售人员与您联系</view>
+    <view class="global-btn-yellow-change btn" @click="handleBack">返回</view>
+  </view>
+</template>
+
+<script>
+export default {
+  methods: {
+    handleBack(){
+      uni.navigateTo();
+    }
+  }
+}
+</script>
+
+<style lang="scss">
+.apply-result-page{
+  text-align: center;
+  font-size: 17px;
+  .btn{
+    margin-left: auto;
+    margin-right: auto;
+    margin-top: 140rpx;
+    width: 380rpx;
+  }
+}
+</style>

+ 126 - 0
pages-applyPermission/selectVariety.vue

@@ -0,0 +1,126 @@
+<template>
+  <view class="select-variety-page">
+      <view class="flex variety-list">
+          <view class="variety-item" v-for="(item,index) in list" :key="item.name" @click="handleSelect(index)">
+              <view :class="['variety-radio',item.selected&&'variety-radio-s']"></view>
+              <image class="variety-img" :src="item.pic_url" mode="aspectFill"></image>
+              <view class="variety-name">{{item.name}}</view>
+          </view>
+      </view>
+
+      <view class="global-btn-yellow-change btn" v-if="has" @click="handleConfirm">关注</view>
+      <view class="global-btn-disable btn" v-else>请至少关注一个品种</view>
+  </view>
+</template>
+
+<script>
+import {apiGetPermissionList} from '@/api/common'
+export default {
+    data () {
+        return {
+            list:[]
+        }
+    },
+    computed: {
+      has(){
+          let flag=false
+            this.list.forEach(item=>{
+                if(item.selected) flag=true
+            })
+          return flag
+      }  
+    },
+    onLoad(options){
+        let beforePagePermission=''
+        const eventChannel = this.getOpenerEventChannel();
+        // 监听上个页面传来的已经选择的权限
+        eventChannel.on('hasPermissionData', (data) =>{
+            beforePagePermission=data.data
+        })
+        this.getList(beforePagePermission)
+    },
+    methods: {
+        handleSelect(index){
+            this.list[index].selected=!this.list[index].selected
+        },
+
+        async getList(arr){
+            const hasArr=arr.split(',')||[]
+            const res=await apiGetPermissionList()
+            if(res.code===200){
+                this.list=res.data.map(item=>{
+                    let obj={...item,selected:false}
+                    if(hasArr.find((e)=>e===item.name)){
+                        obj.selected=true
+                    }
+                    return obj
+                })
+            }
+        },
+
+        handleConfirm(){
+            let arr=[]
+            this.list.forEach(item=>{
+                if(item.selected){
+                    arr.push(item.name)
+                }
+            })
+            uni.$emit('selectPermission',{permissionList:arr})
+            uni.navigateBack()
+        }
+    }
+}
+</script>
+
+<style lang="scss">
+.variety-list{
+    padding: 50rpx 68rpx;
+    justify-content: space-between;
+    flex-wrap: wrap;
+    text-decoration: none;
+    .variety-item{
+        margin-bottom: 30rpx;
+        position: relative;
+        width: 188rpx;
+    }
+    .variety-img{
+        width: 188rpx;
+        height: 188rpx;
+        background-color: $global-bg-color-grey;
+        border-radius: 8rpx;
+    }
+    .variety-name{
+        text-align: center;
+    }
+    // 使用伪类 补充一个元素
+    &::after{
+        content: "";
+        height: 0;
+        width: 188rpx;
+    }
+
+    .variety-radio{
+        position: absolute;
+        top: 12rpx;
+        right: 12rpx;
+        width: 20px;
+        height: 20px;
+        border-radius: 50%;
+        border: 1px solid #fff;
+    }
+    .variety-radio-s{
+        background-image: url('./static/radio-s.png');
+        background-size: cover;
+        border: none;
+        background-color: #fff;
+    }
+}
+.btn{
+    height: 80rpx;
+    line-height: 80rpx;
+    border-radius: 40rpx;
+    width: 614rpx;
+    margin-left: auto;
+    margin-right: auto;
+}
+</style>

二進制
pages-applyPermission/static/radio-s.png


二進制
pages-applyPermission/static/upload.png


+ 145 - 0
pages.json

@@ -0,0 +1,145 @@
+{
+	"pages": [ //pages数组中第一项表示应用启动页,参考:https://uniapp.dcloud.io/collocation/pages
+		{
+			"path": "pages/buy/buy",
+			"style": {
+				"navigationBarTitleText": "已购"
+			}
+		},
+		{
+			"path": "pages/report/report",
+			"style": {
+				"navigationBarTitleText": "报告"
+			}
+		},
+		{
+			"path": "pages/chart/chart",
+			"style": {
+				"navigationBarTitleText": "图库"
+			}
+		},
+		{
+			"path": "pages/activity/activity",
+			"style": {
+				"navigationBarTitleText": "活动",
+				"enablePullDownRefresh": true
+			}
+		},
+		{
+			"path": "pages/user/user",
+			"style": {
+				"navigationBarTitleText": "我的"
+			}
+		},
+		{
+			"path":"pages/login",
+			"style":{
+				"navigationBarTitleText":"绑定联系方式"
+			}
+		}
+	],
+	"subPackages":[
+		// 活动模块
+		{
+			"root":"pages-activity",
+			"pages":[
+				{
+					"path":"detail",
+					"style":{
+						"navigationBarTitleText":"活动详情"
+					}
+				},
+				{
+					"path":"noAuthority",
+					"style":{
+						"navigationBarTitleText":"活动详情"
+					}
+				}
+			]
+		},
+		// 申请权限模块
+		{
+			"root":"pages-applyPermission",
+			"pages": [
+				{
+					"path":"applyPermission",
+					"style":{
+						"navigationBarTitleText":"申请试用"
+					}
+				},
+				{
+					"path":"selectVariety",
+					"style":{
+						"navigationBarTitleText":"选择品种"
+					}
+				},
+				{
+					"path":"applyResult",
+					"style":{
+						"navigationBarTitleText":"申请结果"
+					}
+				}
+			]
+		}
+	],
+		
+	"tabBar": {
+		"custom": true,
+		"color": "#1F243A",
+		"selectedColor": "#E3B377",
+		"list": [
+			{
+				"pagePath": "pages/buy/buy",
+				"text": "已购",
+				"iconPath": "./static/tabbar/buy.png",
+				"selectedIconPath": "./static/tabbar/buy-s.png"
+			},
+			{
+				"pagePath": "pages/report/report",
+				"text": "报告",
+				"iconPath": "./static/tabbar/report.png",
+				"selectedIconPath": "./static/tabbar/report-s.png"
+			},
+			{
+				"pagePath": "pages/chart/chart",
+				"text": "图库",
+				"iconPath": "./static/tabbar/chart.png",
+				"selectedIconPath": "./static/tabbar/chart-s.png"
+			},
+			{
+				"pagePath": "pages/activity/activity",
+				"text": "活动",
+				"iconPath": "./static/tabbar/activity.png",
+				"selectedIconPath": "./static/tabbar/activity-s.png"
+			},
+			{
+				"pagePath": "pages/user/user",
+				"text": "我的",
+				"iconPath": "./static/tabbar/user.png",
+				"selectedIconPath": "./static/tabbar/user-s.png"
+			}
+		]
+	},
+	
+		
+	"globalStyle": {
+		"navigationBarTextStyle": "black",
+		"navigationBarTitleText": "弘则研报",
+		"navigationBarBackgroundColor": "#FFFFFF",
+		"backgroundColor": "#FFFFFF",
+		"usingComponents":{
+			"van-button":"/wxcomponents/vant/button/index",
+			"van-toast":"/wxcomponents/vant/toast/index",
+			"van-popup":"/wxcomponents/vant/popup/index",
+			"van-search": "/wxcomponents/vant/search/index",
+			"van-sticky": "/wxcomponents/vant/sticky/index",
+			"van-field": "/wxcomponents/vant/field/index",
+			"van-icon": "/wxcomponents/vant/icon/index",
+			"van-radio": "/wxcomponents/vant/radio/index",
+			"van-tab": "/wxcomponents/vant/tab/index",
+  		"van-tabs": "/wxcomponents/vant/tabs/index",
+			"van-count-down": "/wxcomponents/vant/count-down/index",
+			"van-empty": "/wxcomponents/vant/empty/index"
+		}
+	}
+}

+ 281 - 0
pages/activity/activity.vue

@@ -0,0 +1,281 @@
+<template>
+    <view class="activity-page">
+        <van-sticky style="background: #fff">
+            <view class="search-wrap">
+                <van-search
+                    shape="round"
+                    :value="searchVal"
+                    placeholder="搜索您想要的商品名"
+                />
+            </view>
+            <view class="flex tabs-wrap" @click="tabChange">
+                <view
+                    :class="['tab-item', tabActive === '1' && 'tab-active']"
+                    data-type="1"
+                    >线上会议</view
+                >
+                <view
+                    :class="['tab-item', tabActive === '3' && 'tab-active']"
+                    data-type="3"
+                    >线下沙龙</view
+                >
+            </view>
+            <view class="flex status-wrap" @click="statusChange">
+                <view
+                    :class="[
+                        'status-item',
+                        statusActive === '1' && 'status-active',
+                    ]"
+                    data-status="1"
+                    >本周预告</view
+                >
+                <view
+                    :class="[
+                        'status-item',
+                        statusActive === '2' && 'status-active',
+                    ]"
+                    data-status="2"
+                    >进行中</view
+                >
+                <view
+                    :class="[
+                        'status-item',
+                        statusActive === '3' && 'status-active',
+                    ]"
+                    data-status="3"
+                    >已结束</view
+                >
+            </view>
+        </van-sticky>
+
+        <view class="list" v-if="list.length > 0">
+            <view
+                class="global-list-card item"
+                v-for="item in list"
+                :key="item.activityId"
+                @click="handleGoDetail(item.activityId)"
+            >
+                <view class="status-box status-before">未开始</view>
+                <view class="flex top">
+                    <image
+                        class="avatar"
+                        src="https://bjetxgzv.cdn.bspapp.com/VKCEYUGU-uni-app-doc/6acec660-4f31-11eb-a16f-5b3e54966275.jpg"
+                        mode="aspectFill"
+                    ></image>
+                    <view class="content">
+                        <view class="van-ellipsis title"
+                            >宏观双周电话会宏观双周电话会</view
+                        >
+                        <view class="name">主讲:某某某</view>
+                        <view class="time"
+                            >时间:{{ start | formatActivityTime(end) }}</view
+                        >
+                    </view>
+                </view>
+                <view class="flex bot">
+                    <view>会议提醒</view>
+                    <view>报名线下参会</view>
+                </view>
+            </view>
+        </view>
+        <van-empty description="暂无数据" v-else />
+    </view>
+</template>
+
+<script>
+import { apiActivityList } from '@/api/activity'
+export default {
+    data() {
+        return {
+            start: '2021-11-12T09:25:01+08:00',
+            end: '2021-11-12T12:25:01+08:00',
+            searchVal: '',
+            tabActive: '1',
+            statusActive: '1',
+
+            page: 1,
+            pageSize: 20,
+            list: [],
+            finished: false,
+        }
+    },
+    onLoad() {
+        this.getList()
+    },
+
+    onPullDownRefresh() {
+        this.refreshPage()
+        setTimeout(() => {
+            uni.stopPullDownRefresh()
+        }, 1500)
+    },
+
+    onReachBottom() {
+        if (this.finished) return
+        this.page++
+        this.getList()
+    },
+
+
+    methods: {
+        // 类型切换
+        tabChange(e) {
+            const type = e.target.dataset.type
+            if (type === this.tabActive) return
+            this.tabActive = type
+            this.statusActive = '1'
+            this.refreshPage()
+        },
+
+        // 状态切换
+        statusChange(e) {
+            const status = e.target.dataset.status
+            if (status === this.statusActive) return
+            this.statusActive = status
+            this.refreshPage()
+        },
+
+        // 刷新页面
+        refreshPage() {
+            this.page = 1
+            this.list = []
+            this.finished = false
+            this.getList()
+        },
+
+        handleGoDetail(id) {
+            uni.navigateTo({
+                url: '/pages-activity/detail?id=' + id
+            });
+        },
+
+        async getList() {
+            const res = await apiActivityList({
+                active_state: Number(this.statusActive),
+                activity_type: Number(this.tabActive),
+                page: this.page,
+                limit: this.pageSize
+            })
+            if (res.code === 200) {
+                if(res.data){
+                  this.list = [...this.list, ...res.data]
+                }else{
+                  this.finished=true
+                }
+            }
+        }
+    }
+}
+</script>
+
+<style lang="scss">
+.search-wrap {
+    padding: 0 8rpx;
+    background-color: $global-bg-color;
+}
+.tabs-wrap {
+    background-color: $global-bg-color;
+    box-shadow: $global-tab-shadow-color;
+    align-items: center;
+    text-align: center;
+    font-size: $global-font-size-lg;
+    color: $global-text-color-grey;
+    position: relative;
+    z-index: 10;
+    .tab-item {
+        flex: 1;
+        height: 94rpx;
+        line-height: 94rpx;
+    }
+    .tab-active {
+        font-weight: bold;
+        color: $global-text-color-main;
+        border-bottom: 6rpx solid $global-text-color-main;
+    }
+}
+.status-wrap {
+    background-color: $global-bg-color;
+    padding: 40rpx 34rpx 20rpx 34rpx;
+    .status-item {
+        font-size: $global-font-size-sm;
+        color: #444;
+        min-width: 167rpx;
+        text-align: center;
+        padding: 14rpx;
+        border-radius: 40rpx;
+        background-color: #f6f6f6;
+        margin-right: 40rpx;
+    }
+    .status-active {
+        background: linear-gradient(270deg, #efc896 0%, #d9a35f 100%);
+        color: $global-bg-color;
+    }
+}
+
+.list {
+    padding: 34rpx;
+    .item {
+        position: relative;
+        margin-bottom: 30rpx;
+        .status-box {
+            top: 0;
+            right: 0;
+            position: absolute;
+            width: 132rpx;
+            line-height: 44rpx;
+            text-align: center;
+            font-size: $global-font-size-sm;
+            color: $global-text-color-white;
+        }
+        .status-before {
+            background-color: #e3b377;
+        }
+        .status-progress {
+            background-color: #3385ff;
+        }
+        .status-end {
+            background-color: #a2a2a2;
+        }
+        .top {
+            padding-top: 64rpx;
+            padding-left: 30rpx;
+            padding-right: 30rpx;
+            padding-bottom: 42rpx;
+            .avatar {
+                width: 168rpx;
+                height: 168rpx;
+                border-radius: 50%;
+                margin-right: 30rpx;
+                flex-shrink: 0;
+            }
+            .title {
+                font-size: $global-font-size-lg;
+                font-weight: bold;
+                width: 420rpx;
+                padding-bottom: 10rpx;
+                border-bottom: 1px solid $global-border-color;
+            }
+            .name {
+                color: $global-text-color-grey;
+                margin: 20rpx 0;
+            }
+            .time {
+                font-size: $global-font-size-mini;
+                color: $global-text-color-999;
+            }
+        }
+        .bot {
+            border-top: 1px solid $global-border-color;
+            view {
+                line-height: 72rpx;
+                flex: 1;
+                text-align: center;
+                border-right: 1px solid $global-border-color;
+            }
+            view:last-child {
+                border: none;
+            }
+        }
+    }
+}
+</style>

+ 14 - 0
pages/buy/buy.vue

@@ -0,0 +1,14 @@
+<template>
+  <view>buy</view>
+</template>
+
+<script>
+export default {
+  onLoad(){
+  }
+}
+</script>
+
+<style>
+
+</style>

+ 14 - 0
pages/chart/chart.vue

@@ -0,0 +1,14 @@
+<template>
+  <view>chart</view>
+</template>
+
+<script>
+export default {
+  onLoad(){
+  }
+}
+</script>
+
+<style>
+
+</style>

+ 222 - 0
pages/login.vue

@@ -0,0 +1,222 @@
+<template>
+    <view class="login-page">
+        <van-tabs :active="active" animated @change="onChange">
+            <van-tab title="手机号" name="手机号">
+                <view class="form-wrap tel-wrap">
+                    <van-field
+                        :value="tel"
+                        placeholder="请输入手机号"
+                        :border="false"
+                        center
+                        type="number"
+                        title-width="60px"
+                        @change="inputChange('tel',$event)"
+                    >
+                        <template slot="label">
+                            <picker
+                                mode="selector"
+                                :range="telAreaList"
+                                range-key="name"
+                                :value="telAreaIndex"
+                                @change="telAreaChange"
+                                header-text="请选择您的国际区号"
+                            >
+                                <view>+{{telAreaList[telAreaIndex].value}}<van-icon name="arrow-down" style="margin-left:4px" /></view>
+                            </picker>
+                        </template>
+                    </van-field>
+                    <van-field
+                        :value="verifyCode"
+                        placeholder="请输入4位验证码"
+                        :border="false"
+                        title-width="60px"
+                        label="验证码"
+                        type="number"
+                        center
+                        @change="inputChange('verifyCode',$event)"
+                    >   
+                        <van-button slot="button" size="small" type="primary" v-if="!isSendVerifyCode" @click="handleSendVerifyCode('tel')">发送验证码</van-button>
+                        <van-count-down slot="button" :time="countDownTime" format="ss S" @finish="timeFinished" v-else/>
+                    </van-field>
+                    <view class="global-btn-yellow-change submit-btn" @click="handleSubmit('tel')">提交</view>
+                </view>
+            </van-tab>
+            <van-tab title="邮箱" name="邮箱">
+                <view class="form-wrap email-wrap">
+                    <van-field
+                        :value="email"
+                        placeholder="请输入邮箱地址"
+                        :border="false"
+                        center
+                        title-width="60px"
+                        label="邮箱地址"
+                        @change="inputChange('email',$event)"
+                    />
+                    <van-field
+                        :value="verifyCode"
+                        placeholder="请输入4位验证码"
+                        :border="false"
+                        title-width="60px"
+                        label="验证码"
+                        center
+                        type="number"
+                        @change="inputChange('verifyCode',$event)"
+                    >   
+                        <van-button slot="button" size="small" type="primary" v-if="!isSendVerifyCode" @click="handleSendVerifyCode('email')">发送验证码</van-button>
+                        <van-count-down slot="button" :time="countDownTime" format="ss S" @finish="timeFinished" v-else/>
+                    </van-field>
+                    <view class="global-btn-yellow-change submit-btn" @click="handleSubmit('email')">提交</view>
+                </view>
+            </van-tab>
+        </van-tabs>
+    </view>
+</template>
+
+<script>
+import {apiGetSMSCode,apiGetEmailCode} from '@/api/common'
+import {apiUserLogin} from '@/api/user'
+import {telVerify,emailVerify} from '@/utils/common'
+export default {
+    data() {
+        return {
+            active: "手机号",
+            telAreaList: [
+                {
+                    name: "大陆+86",
+                    value: "86",
+                },
+                {
+                    name: "香港+852",
+                    value: "852",
+                },
+                {
+                    name: "台湾+886",
+                    value: "886",
+                },
+                {
+                    name: "美国+1",
+                    value: "1",
+                },
+                {
+                    name: "新加坡+65",
+                    value: "65",
+                },
+            ], //手机号区号
+            telAreaIndex: 0, //选择手机号区号第几个
+            tel: "",//手机号
+            email:"",//邮箱
+            verifyCode:"",//验证码
+            isSendVerifyCode:false,//是否已经发送验证码
+            countDownTime:60*1000,//倒计时60秒
+        };
+    },
+    methods: {
+        onChange(e) {
+            console.log(e);
+        },
+
+        // 区号改变
+        telAreaChange(e) {
+            this.telAreaIndex = e.detail.value;
+        },
+
+        //倒计时结束
+        timeFinished(){
+            this.isSendVerifyCode=false
+        },
+
+
+        inputChange(key,event){
+			this[key]=event.detail
+		},
+        
+
+        //发送验证码
+        async handleSendVerifyCode(type){
+            let res
+            if(type==='tel'){
+                if(!telVerify(this.tel,this.telAreaList[this.telAreaIndex].value)){
+                    uni.showToast({
+                        title:"请输入正确手机号",
+                        icon:"none"
+                    })
+                    return
+                }
+                res=await apiGetSMSCode({
+                    mobile:this.tel,
+                    area_num:this.telAreaList[this.telAreaIndex].value
+                })
+                
+            }else{
+                if(!emailVerify(this.email)){
+                     uni.showToast({
+                        title:"请输入正确邮箱地址",
+                        icon:"none"
+                    })
+                    return
+                }
+                res=await apiGetEmailCode({
+                    email:this.email
+                })
+            }
+
+            if(res.code===200){
+                this.isSendVerifyCode=true
+                uni.showToast({
+                    title:"验证码已发送",
+                    icon:"success"
+                })
+            }
+        },
+
+        // 提交
+        async handleSubmit(type){
+            let params={}
+            if(type==='tel'){
+                params={
+                    area_num:Number(this.telAreaList[this.telAreaIndex].value),
+                    mobile:this.tel,
+                    verify_code:this.verifyCode,
+                    bind_type:1
+                }
+            }else{
+                params={
+                    email:this.email,
+                    verify_code:this.verifyCode,
+                    bind_type:2
+                }
+            }
+
+            if(!params.verify_code){
+                uni.showToast({
+                    title:"请输入验证码",
+                    icon:"none"
+                })
+                return
+            }
+
+            const res=await apiUserLogin(params)
+            if(res.code===200){
+                uni.navigateBack()
+                this.$store.dispatch('getUserInfo')
+            }
+            
+        }
+    },
+};
+</script>
+
+<style lang="scss">
+.form-wrap {
+    padding: 34rpx;
+}
+.van-cell {
+    border-bottom: 1px solid $global-border-color;
+}
+.submit-btn{
+    width: 400rpx;
+    margin-left: auto;
+    margin-right: auto;
+    margin-top: 100rpx;
+}
+</style>

+ 14 - 0
pages/report/report.vue

@@ -0,0 +1,14 @@
+<template>
+  <view>report</view>
+</template>
+
+<script>
+export default {
+  onLoad(){
+  }
+}
+</script>
+
+<style>
+
+</style>

+ 102 - 0
pages/user/user.vue

@@ -0,0 +1,102 @@
+<template>
+	<view class="user-page">
+		<view class="flex base-info-wrap">
+			<view class="avatar">
+				<open-data type="userAvatarUrl"></open-data>
+			</view>
+			<view>
+				<view style="margin-top:6px;margin-bottom:18px">手机号:{{userInfo.mobile}}</view>
+				<view>姓名:{{userInfo.real_name}}</view>
+			</view>
+		</view>
+		<view class="main-info-wrap">
+			<view class="title">我的信息</view>
+			<view class="info-list">
+				<view class="flex item">
+					<text class="label">公司名称:</text>
+					<text>{{userInfo.company_name}}</text>
+				</view>
+				<view class="flex item">
+					<text class="label">品种权限:</text>
+					<template v-if="userInfo.permission_list.length==0">
+						<text>暂无权限</text>
+						<van-button custom-class="apply-btn" plain round color="#DDAA6A" size="small" @click="handleGoApplyPermission">立即申请</van-button>
+					</template>
+				</view>
+			</view>
+			<view class="tips" v-if="userInfo.permission_list.length!=0">服务截止日期:2022.11.11</view>
+		</view>
+	</view>
+</template>
+
+<script>
+	export default {
+		data() {
+			return {
+			}
+		},
+		onLoad() {
+			
+		},
+		
+		methods: {
+			handleGoApplyPermission(){
+				uni.navigateTo({
+					url:"/pages-applyPermission/applyPermission"
+				})
+			}
+		}
+	}
+</script>
+
+<style lang="scss">
+	.base-info-wrap{
+			width: 100%;
+			height: 393rpx;
+			background-color: #373634;
+			padding-top: 99rpx;
+			padding-left: 50rpx;
+			color: $global-text-color-white;
+			font-size: $global-font-size-lg;
+			font-weight: bold;
+			.avatar{
+				width: 154rpx;
+				height: 154rpx;
+				border-radius: 16rpx;
+				overflow: hidden;
+				margin-right: 40rpx;
+			}
+		}
+	.main-info-wrap{
+		width: calc(100% - 68rpx);
+		min-height: 561rpx;
+		margin-left: auto;
+		margin-right: auto;
+		margin-top: -30rpx;
+		padding: 30rpx;
+		background-color: $global-bg-color;
+		box-shadow: 0px 3rpx 12rpx rgba(0, 0, 0, 0.16);
+		border-radius: 16rpx;
+		.title{
+			font-size: $global-font-size-lg;
+			font-weight: bold;
+			padding-bottom: 30rpx;
+			border-bottom: 1px solid $global-border-color;
+		}
+		.tips{
+			font-size: $global-font-size-sm;
+			color: $global-text-color-999;
+		}
+	}
+	.info-list{
+		min-height: 300rpx;
+		.item{
+			margin-top: 40rpx;
+			.apply-btn{
+				font-size: $global-font-size-sm;
+				height: 48rpx;
+				margin-left: 20rpx;
+			}
+		}
+	}
+</style>

二進制
static/logo.png


二進制
static/tabbar/activity-s.png


二進制
static/tabbar/activity.png


二進制
static/tabbar/buy-s.png


二進制
static/tabbar/buy.png


二進制
static/tabbar/chart-s.png


二進制
static/tabbar/chart.png


二進制
static/tabbar/report-s.png


二進制
static/tabbar/report.png


二進制
static/tabbar/user-s.png


二進制
static/tabbar/user.png


+ 15 - 0
store/index.js

@@ -0,0 +1,15 @@
+import Vue from 'vue'
+import Vuex from 'vuex'
+import user from './modules/user.js'
+Vue.use(Vuex);//vue的插件机制
+
+//Vuex.Store 构造器选项
+const store = new Vuex.Store({
+    state:{},
+	mutations:{},
+	actions:{},
+	modules:{
+		user,
+	}
+})
+export default store

+ 32 - 0
store/modules/user.js

@@ -0,0 +1,32 @@
+import {apiUserInfo} from '@/api/user'
+
+
+const token=uni.getStorageSync('token')||''
+const userModules={
+	state:{
+		token:token,
+		userInfo:{}
+	},
+	mutations: {
+		// 设置token
+		setToken(state,data){
+			state.token=data
+			uni.setStorageSync('token', data);
+		},
+
+		// 设置个人信息
+		setUserInfo(state,data){
+			state.userInfo=data
+		}
+	},
+	actions: {
+		// 获取个人信息
+		async getUserInfo(context){
+			const res=await apiUserInfo()
+			if(res.code===200){
+				context.commit('setUserInfo',res.data)
+			}
+		}
+	}
+}
+export default userModules

+ 47 - 0
style/common.scss

@@ -0,0 +1,47 @@
+/* 全局公共样式 */
+
+page{
+    font-size: $global-font-size-base;
+    color: $global-text-color;
+    width: 100%;
+    min-height: calc(100% - calc(48px + env(safe-area-inset-bottom)));
+    background-color: $global-bg-color;
+    padding-bottom: calc(48px + env(safe-area-inset-bottom));
+}
+view{
+    box-sizing: border-box;
+}
+.flex{
+    display: flex;
+}
+
+/* 卡片列表样式 */
+.global-list-card{
+    border: 1px solid rgba(112, 112, 112, 0.2);
+    box-shadow: 3rpx 3rpx 12rpx rgba(161, 161, 161, 0.14);
+    border-radius: 16rpx;
+    overflow: hidden;
+}
+
+/* 公共按钮 */
+.global-btn-yellow-change{
+    background: linear-gradient(270deg, #EEC795 0%, #D9A360 100%);
+    border-radius: 35rpx;
+    color: $global-text-color-white;
+    text-align: center;
+    line-height: 70rpx;
+}
+.global-btn-yellow-plain{
+    text-align: center;
+    color: $global-text-color-main;
+    border-radius: 35rpx;
+    border: 1px solid $global-text-color-main;
+    line-height: 70rpx;
+}
+.global-btn-disable{
+    text-align: center;
+    color:$global-text-color-white;
+    background-color: #E6E6E6;
+    border-radius: 35rpx;
+    line-height: 70rpx;
+}

+ 39 - 0
uni.scss

@@ -0,0 +1,39 @@
+/**
+ * 这里是uni-app内置的常用样式变量
+ *
+ * uni-app 官方扩展插件及插件市场(https://ext.dcloud.net.cn)上很多三方插件均使用了这些样式变量
+ * 如果你是插件开发者,建议你使用scss预处理,并在插件代码中直接使用这些变量(无需 import 这个文件),方便用户通过搭积木的方式开发整体风格一致的App
+ *
+ */
+
+/**
+ * 如果你是App开发者(插件使用者),你可以通过修改这些变量来定制自己的插件主题,实现自定义主题功能
+ *
+ * 如果你的项目同样使用了scss预处理,你也可以直接在你的 scss 代码中使用如下变量,同时无需 import 这个文件
+ */
+
+/* 文字基本颜色 */
+$global-text-color:#333;//基本色
+$global-text-color-white:#fff;//白色
+$global-text-color-grey:#666;//灰色
+$global-text-color-999:#999;
+$global-text-color-placeholder: #818181;// 输入框placeholder
+$global-text-color-main:#D5AD79;//主黄色
+
+
+/* 背景颜色 */
+$global-bg-color:#ffffff;
+$global-bg-color-grey:#f6f6f6;
+
+/* 边框颜色 */
+$global-border-color:#EBEDF0;
+
+/* 盒阴影 */
+$global-tab-shadow-color:0px 3px 6px rgba(0, 0, 0, 0.08);;
+
+/* 文字尺寸 */
+$global-font-size-mini:10px;
+$global-font-size-sm:12px;
+$global-font-size-base:14px;
+$global-font-size-lg:16px;
+$global-font-size-xlg:18px;

+ 44 - 0
utils/common.js

@@ -0,0 +1,44 @@
+// 公共方法模块
+
+/**
+ * 手机号校验
+ * @param {手机号} tel 
+ * @param {区号} areaNum 默认86
+ * @returns Boolean
+ * 86 正常手机号校验
+ * 非86 位数必须大于8位
+ */
+export const telVerify=(tel,areaNum=86)=>{
+    let flag=false
+    if(areaNum==86&&(/^1[3456789]\d{9}$/.test(tel))){
+        flag=true
+    }
+
+    if(areaNum!=86&&tel.length>8){
+        flag=true
+    }
+    
+    return flag
+}
+
+/**
+ * 邮箱校验
+ * @param {邮箱} email 
+ * @returns Boolean
+ */
+export const emailVerify=(email)=>{
+    let flag=false
+
+    let pattern = /^([A-Za-z0-9_\-\.])+@([A-Za-z0-9_\-\.])+\.([A-Za-z]{2,4})$/;
+    let domains= ["qq.com","163.com","vip.163.com","263.net","yeah.net","sohu.com","sina.cn","sina.com","eyou.com","gmail.com","hotmail.com","42du.cn"];
+    if(pattern.test(email)) {
+        var domain = email.substring(email.indexOf("@")+1);
+        for(var i = 0; i< domains.length; i++) {
+            if(domain == domains[i]) {
+                flag=true
+            }
+        }
+    }
+
+    return flag
+}

+ 27 - 0
utils/config.js

@@ -0,0 +1,27 @@
+// 配置文件
+
+const env=uni.getAccountInfoSync().miniProgram
+console.log(env);
+// 请求根路径
+let baseApiUrl=''
+if(env.envVersion==='develop'){//开发
+    baseApiUrl='http://8.136.199.33:8612'
+}else if(env.envVersion==='trial'){//体验版
+    baseApiUrl='http://8.136.199.33:8612'
+}else if(env.envVersion==='release'){//正式版
+    baseApiUrl='http://8.136.199.33:8612'
+}
+
+
+
+// 配置图片资源
+const globalImgUrls={
+    img1:'https://hongze.oss-accelerate.aliyuncs.com/banner/crm_wxapp_share.jpg',
+    imgSuccess:'https://hongze.oss-accelerate.aliyuncs.com/banner/crm_wxapp_share.jpg',
+}
+
+
+module.exports={
+    baseApiUrl,
+    globalImgUrls
+}

文件差異過大導致無法顯示
+ 0 - 0
utils/moment-with-locales.min.js


+ 128 - 0
utils/request.js

@@ -0,0 +1,128 @@
+import {baseApiUrl} from './config.js'
+import {apiWechatLogin} from '@/api/user'
+import store from '@/store/index'
+
+// 请求错误消息提示
+const showError=error=>{
+	let errMsg=''
+	switch(error.Ret){
+		case 400:
+			errMsg='请求参数错误';
+			break;
+		default:
+			errMsg=error.ErrMsg;
+			break;
+	}
+	uni.showToast({
+		title:errMsg,
+		icon:'none'
+	})
+}
+
+
+// 微信登录
+const wechatLogin=()=>{
+	return new Promise((resolve,reject)=>{
+		uni.login({
+			provider: 'weixin',
+			success: function (loginRes) {
+				apiWechatLogin({code:loginRes.code}).then(res=>{
+					if(res.code===200){
+						store.commit('setToken', res.data.authorization)
+						resolve(res.data)
+					}
+				})
+			},
+			fail:function(loginErr){
+				uni.showToast({
+					title:"微信登录失败",
+					icon:"none"
+				})
+			}
+		})
+	})
+	
+}
+
+let requestList=[]//存放token失效时请求队列
+let isRefreshing=false//是否正在刷新token
+/**
+ * 刷新token
+ */
+const refreshToken=async (url,params,method,resolve)=>{
+	requestList.push(()=>{resolve(http(url,params,method))})
+	if(!isRefreshing){
+		isRefreshing=true
+		const wechatLoginRes=await wechatLogin()
+		console.log(wechatLoginRes);
+		if(!wechatLoginRes.is_bind){
+			uni.navigateTo({
+				url:'/pages/login'
+			})
+			return
+		}
+		// 重新请求队列
+		requestList.map(MT=>{MT()})
+		requestList=[]
+		isRefreshing=false
+	}
+}
+
+let LOADINGCOUNT = 0;// 请求数
+const http=(url,params,method)=>{
+	// 设置loading
+	if (LOADINGCOUNT === 0) {
+	  uni.showLoading({
+	  	title:'加载中...'
+	  })
+	}
+	LOADINGCOUNT++;
+	
+	return new Promise((resolve,reject)=>{
+		uni.request({
+			url:baseApiUrl+url,
+			data:params,
+			method:method,
+			header:{
+				Authorization:store.state.user.token,
+			},
+			success(res) {
+				if(res.data.code!==200){
+					showError(res.data)
+				}
+
+				// 401 token失效
+				if(res.data.code===401){
+					refreshToken(url,params,method,resolve)
+					return 
+				}
+				
+				resolve(res.data)
+			},
+			fail(error) {
+				console.log(error);
+				uni.showToast({
+					title:'服务器错误',
+					icon:'none'
+				})
+			},
+			complete() {
+				// 关闭loading
+				LOADINGCOUNT--;
+				if (LOADINGCOUNT === 0) {
+				  uni.hideLoading()
+				}
+			}
+		})
+	})
+}
+
+// get 请求
+export const httpGet=(url,params)=>{
+	return http(url,params,'GET')
+}
+
+// post 请求
+export const httpPost=(url,params)=>{
+	return http(url,params,'POST')
+}

+ 19 - 0
utils/uni-async.js

@@ -0,0 +1,19 @@
+
+// 使用proxy转换为异步化的uni方法
+const uniAsync = new Proxy({}, {
+	get(target, name) {
+		return (obj) => new Promise((resolve, reject) => {
+			uni[name]({
+				...obj,
+				success: ret => {
+					resolve(ret)
+				},
+				fail: err => {
+					reject(err)
+				}
+			})
+		})
+	}
+})
+
+export default uniAsync

+ 54 - 0
utils/upload.js

@@ -0,0 +1,54 @@
+/**
+ * 上传公共方法
+ */
+
+import uniAsync from "@/utils/uni-async.js"; // uni api async 化
+import {baseApiUrl} from './config.js'
+import store from '@/store/index.js'
+
+/**
+ * 上传文件到服务器
+ */
+const uploadToServer = async (tempFilePath) => {
+  const temres = await uniAsync.uploadFile({
+    url: baseApiUrl + "/public/upload",
+    filePath: tempFilePath,
+    name: "file",
+    header: {
+      Authorization: store.state.user.token,
+    },
+  });
+  const res = JSON.parse(temres.data);
+  if (res.code === 200) {
+    return res.data;
+  }
+};
+
+
+
+/**
+ * 上传图片
+ * count 同时上传张数 默认:1
+ */
+export const uploadImg = async (count = 1) => {
+  const { tempFilePaths } = await uniAsync.chooseImage({ count });
+  uni.showLoading({
+    title: "上传中...",
+  });
+  const uploadResArr = tempFilePaths.map((item) => {
+    return uploadToServer(item);
+  });
+  return new Promise((resolve, reject) => {
+    Promise.all(uploadResArr)
+      .then((res) => {
+        uni.hideLoading();
+        const arr = res.map((item) => {
+          return item;
+        });
+        resolve(arr);
+      })
+      .catch((res) => {
+        uni.hideLoading();
+      });
+  });
+};

+ 11 - 0
utils/vantRegister.js

@@ -0,0 +1,11 @@
+/* —————————————— Vant 组件注册 (需使用this方法的特殊组件): uniapp版 ———————————————— */
+
+import Vue from 'vue'
+
+import Dialog from '../wxcomponents/vant/dialog/dialog';
+import Toast from '../wxcomponents/vant/toast/toast';
+import Notify from '../wxcomponents/vant/notify/notify';
+
+Vue.prototype.$dialog = Dialog;
+Vue.prototype.$toast = Toast;
+Vue.prototype.$notify = Notify;

+ 1 - 0
wxcomponents/vant/action-sheet/index.d.ts

@@ -0,0 +1 @@
+export {};

+ 70 - 0
wxcomponents/vant/action-sheet/index.js

@@ -0,0 +1,70 @@
+import { VantComponent } from '../common/component';
+import { button } from '../mixins/button';
+VantComponent({
+    mixins: [button],
+    props: {
+        show: Boolean,
+        title: String,
+        cancelText: String,
+        description: String,
+        round: {
+            type: Boolean,
+            value: true,
+        },
+        zIndex: {
+            type: Number,
+            value: 100,
+        },
+        actions: {
+            type: Array,
+            value: [],
+        },
+        overlay: {
+            type: Boolean,
+            value: true,
+        },
+        closeOnClickOverlay: {
+            type: Boolean,
+            value: true,
+        },
+        closeOnClickAction: {
+            type: Boolean,
+            value: true,
+        },
+        safeAreaInsetBottom: {
+            type: Boolean,
+            value: true,
+        },
+    },
+    methods: {
+        onSelect(event) {
+            const { index } = event.currentTarget.dataset;
+            const { actions, closeOnClickAction, canIUseGetUserProfile } = this.data;
+            const item = actions[index];
+            if (item) {
+                this.$emit('select', item);
+                if (closeOnClickAction) {
+                    this.onClose();
+                }
+                if (item.openType === 'getUserInfo' && canIUseGetUserProfile) {
+                    wx.getUserProfile({
+                        desc: item.getUserProfileDesc || '  ',
+                        complete: (userProfile) => {
+                            this.$emit('getuserinfo', userProfile);
+                        },
+                    });
+                }
+            }
+        },
+        onCancel() {
+            this.$emit('cancel');
+        },
+        onClose() {
+            this.$emit('close');
+        },
+        onClickOverlay() {
+            this.$emit('click-overlay');
+            this.onClose();
+        },
+    },
+});

+ 8 - 0
wxcomponents/vant/action-sheet/index.json

@@ -0,0 +1,8 @@
+{
+  "component": true,
+  "usingComponents": {
+    "van-icon": "../icon/index",
+    "van-popup": "../popup/index",
+    "van-loading": "../loading/index"
+  }
+}

+ 111 - 0
wxcomponents/vant/action-sheet/index.vue

@@ -0,0 +1,111 @@
+<template>
+<uni-shadow-root class="vant-action-sheet-index"><van-popup :show="show" position="bottom" :round="round" :z-index="zIndex" :overlay="overlay" custom-class="van-action-sheet" :safe-area-inset-bottom="safeAreaInsetBottom" :close-on-click-overlay="closeOnClickOverlay" @close="onClickOverlay">
+  <view v-if="title" class="van-action-sheet__header">
+    {{ title }}
+    <van-icon name="cross" custom-class="van-action-sheet__close" @click="onClose"></van-icon>
+  </view>
+  <view v-if="description" class="van-action-sheet__description van-hairline--bottom">
+    {{ description }}
+  </view>
+  <view v-if="actions && actions.length">
+    
+    <button v-for="(item,index) in (actions)" :key="item.index" :open-type="item.disabled || item.loading || (canIUseGetUserProfile && item.openType === 'getUserInfo') ? '' : item.openType" :style="item.color ? 'color: ' + item.color : ''" :class="(utils.bem('action-sheet__item', { disabled: item.disabled || item.loading }))+' '+(item.className || '')" hover-class="van-action-sheet__item--hover" :data-index="index" @click="_$self[(item.disabled || item.loading ? '' : 'onSelect')||'_$noop']($event)" @getuserinfo="onGetUserInfo" @contact="onContact" @getphonenumber="onGetPhoneNumber" @error="onError" @launchapp="onLaunchApp" @opensetting="onOpenSetting" :lang="lang" :session-from="sessionFrom" :send-message-title="sendMessageTitle" :send-message-path="sendMessagePath" :send-message-img="sendMessageImg" :show-message-card="showMessageCard" :app-parameter="appParameter">
+      <block v-if="(!item.loading)">
+        {{ item.name }}
+        <view v-if="item.subname" class="van-action-sheet__subname">{{ item.subname }}</view>
+      </block>
+      <van-loading v-else custom-class="van-action-sheet__loading" size="22px"></van-loading>
+    </button>
+  </view>
+  <slot></slot>
+  <block v-if="cancelText">
+    <view class="van-action-sheet__gap"></view>
+    <view class="van-action-sheet__cancel" hover-class="van-action-sheet__cancel--hover" hover-stay-time="70" @click="onCancel">
+      {{ cancelText }}
+    </view>
+  </block>
+</van-popup></uni-shadow-root>
+</template>
+<wxs src="../wxs/utils.wxs" module="utils"></wxs>
+<script>
+import VanIcon from '../icon/index.vue'
+import VanPopup from '../popup/index.vue'
+import VanLoading from '../loading/index.vue'
+global['__wxVueOptions'] = {components:{'van-icon': VanIcon,'van-popup': VanPopup,'van-loading': VanLoading}}
+
+global['__wxRoute'] = 'vant/action-sheet/index'
+import { VantComponent } from '../common/component';
+import { button } from '../mixins/button';
+VantComponent({
+    mixins: [button],
+    props: {
+        show: Boolean,
+        title: String,
+        cancelText: String,
+        description: String,
+        round: {
+            type: Boolean,
+            value: true,
+        },
+        zIndex: {
+            type: Number,
+            value: 100,
+        },
+        actions: {
+            type: Array,
+            value: [],
+        },
+        overlay: {
+            type: Boolean,
+            value: true,
+        },
+        closeOnClickOverlay: {
+            type: Boolean,
+            value: true,
+        },
+        closeOnClickAction: {
+            type: Boolean,
+            value: true,
+        },
+        safeAreaInsetBottom: {
+            type: Boolean,
+            value: true,
+        },
+    },
+    methods: {
+        onSelect(event) {
+            const { index } = event.currentTarget.dataset;
+            const { actions, closeOnClickAction, canIUseGetUserProfile } = this.data;
+            const item = actions[index];
+            if (item) {
+                this.$emit('select', item);
+                if (closeOnClickAction) {
+                    this.onClose();
+                }
+                if (item.openType === 'getUserInfo' && canIUseGetUserProfile) {
+                    wx.getUserProfile({
+                        desc: item.getUserProfileDesc || '  ',
+                        complete: (userProfile) => {
+                            this.$emit('getuserinfo', userProfile);
+                        },
+                    });
+                }
+            }
+        },
+        onCancel() {
+            this.$emit('cancel');
+        },
+        onClose() {
+            this.$emit('close');
+        },
+        onClickOverlay() {
+            this.$emit('click-overlay');
+            this.onClose();
+        },
+    },
+});
+export default global['__wxComponents']['vant/action-sheet/index']
+</script>
+<style platform="mp-weixin">
+@import '../common/index.css';.van-action-sheet{color:var(--action-sheet-item-text-color,#323233);max-height:var(--action-sheet-max-height,90%)!important}.van-action-sheet__cancel,.van-action-sheet__item{background-color:var(--action-sheet-item-background,#fff);font-size:var(--action-sheet-item-font-size,16px);line-height:var(--action-sheet-item-line-height,22px);padding:14px 16px;text-align:center}.van-action-sheet__cancel--hover,.van-action-sheet__item--hover{background-color:#f2f3f5}.van-action-sheet__cancel:after,.van-action-sheet__item:after{border-width:0}.van-action-sheet__cancel{color:var(--action-sheet-cancel-text-color,#646566)}.van-action-sheet__gap{background-color:var(--action-sheet-cancel-padding-color,#f7f8fa);display:block;height:var(--action-sheet-cancel-padding-top,8px)}.van-action-sheet__item--disabled{color:var(--action-sheet-item-disabled-text-color,#c8c9cc)}.van-action-sheet__item--disabled.van-action-sheet__item--hover{background-color:var(--action-sheet-item-background,#fff)}.van-action-sheet__subname{color:var(--action-sheet-subname-color,#969799);font-size:var(--action-sheet-subname-font-size,12px);line-height:var(--action-sheet-subname-line-height,20px);margin-top:var(--padding-xs,8px)}.van-action-sheet__header{font-size:var(--action-sheet-header-font-size,16px);font-weight:var(--font-weight-bold,500);line-height:var(--action-sheet-header-height,48px);text-align:center}.van-action-sheet__description{color:var(--action-sheet-description-color,#969799);font-size:var(--action-sheet-description-font-size,14px);line-height:var(--action-sheet-description-line-height,20px);padding:20px var(--padding-md,16px);text-align:center}.van-action-sheet__close{color:var(--action-sheet-close-icon-color,#c8c9cc);font-size:var(--action-sheet-close-icon-size,22px)!important;line-height:inherit!important;padding:var(--action-sheet-close-icon-padding,0 16px);position:absolute!important;right:0;top:0}.van-action-sheet__loading{display:flex!important}
+</style>

+ 69 - 0
wxcomponents/vant/action-sheet/index.wxml

@@ -0,0 +1,69 @@
+<wxs src="../wxs/utils.wxs" module="utils" />
+
+<van-popup
+  show="{{ show }}"
+  position="bottom"
+  round="{{ round }}"
+  z-index="{{ zIndex }}"
+  overlay="{{ overlay }}"
+  custom-class="van-action-sheet"
+  safe-area-inset-bottom="{{ safeAreaInsetBottom }}"
+  close-on-click-overlay="{{ closeOnClickOverlay }}"
+  bind:close="onClickOverlay"
+>
+  <view wx:if="{{ title }}" class="van-action-sheet__header">
+    {{ title }}
+    <van-icon
+      name="cross"
+      custom-class="van-action-sheet__close"
+      bind:click="onClose"
+    />
+  </view>
+  <view wx:if="{{ description }}" class="van-action-sheet__description van-hairline--bottom">
+    {{ description }}
+  </view>
+  <view wx:if="{{ actions && actions.length }}">
+    <!-- button外包一层view,防止actions动态变化,导致渲染时button被打散 -->
+    <button
+      wx:for="{{ actions }}"
+      wx:key="index"
+      open-type="{{ item.disabled || item.loading || (canIUseGetUserProfile && item.openType === 'getUserInfo') ? '' : item.openType }}"
+      style="{{ item.color ? 'color: ' + item.color : '' }}"
+      class="{{ utils.bem('action-sheet__item', { disabled: item.disabled || item.loading }) }} {{ item.className || '' }}"
+      hover-class="van-action-sheet__item--hover"
+      data-index="{{ index }}"
+      bindtap="{{ item.disabled || item.loading ? '' : 'onSelect' }}"
+      bindgetuserinfo="onGetUserInfo"
+      bindcontact="onContact"
+      bindgetphonenumber="onGetPhoneNumber"
+      binderror="onError"
+      bindlaunchapp="onLaunchApp"
+      bindopensetting="onOpenSetting"
+      lang="{{ lang }}"
+      session-from="{{ sessionFrom }}"
+      send-message-title="{{ sendMessageTitle }}"
+      send-message-path="{{ sendMessagePath }}"
+      send-message-img="{{ sendMessageImg }}"
+      show-message-card="{{ showMessageCard }}"
+      app-parameter="{{ appParameter }}"
+    >
+      <block wx:if="{{ !item.loading }}">
+        {{ item.name }}
+        <view wx:if="{{ item.subname }}" class="van-action-sheet__subname" >{{ item.subname }}</view>
+      </block>
+      <van-loading wx:else custom-class="van-action-sheet__loading" size="22px" />
+    </button>
+  </view>
+  <slot />
+  <block wx:if="{{ cancelText }}">
+    <view class="van-action-sheet__gap" />
+    <view
+      class="van-action-sheet__cancel"
+      hover-class="van-action-sheet__cancel--hover"
+      hover-stay-time="70"
+      bind:tap="onCancel"
+    >
+      {{ cancelText }}
+    </view>
+  </block>
+</van-popup>

+ 1 - 0
wxcomponents/vant/action-sheet/index.wxss

@@ -0,0 +1 @@
+@import '../common/index.wxss';.van-action-sheet{color:var(--action-sheet-item-text-color,#323233);max-height:var(--action-sheet-max-height,90%)!important}.van-action-sheet__cancel,.van-action-sheet__item{background-color:var(--action-sheet-item-background,#fff);font-size:var(--action-sheet-item-font-size,16px);line-height:var(--action-sheet-item-line-height,22px);padding:14px 16px;text-align:center}.van-action-sheet__cancel--hover,.van-action-sheet__item--hover{background-color:#f2f3f5}.van-action-sheet__cancel:after,.van-action-sheet__item:after{border-width:0}.van-action-sheet__cancel{color:var(--action-sheet-cancel-text-color,#646566)}.van-action-sheet__gap{background-color:var(--action-sheet-cancel-padding-color,#f7f8fa);display:block;height:var(--action-sheet-cancel-padding-top,8px)}.van-action-sheet__item--disabled{color:var(--action-sheet-item-disabled-text-color,#c8c9cc)}.van-action-sheet__item--disabled.van-action-sheet__item--hover{background-color:var(--action-sheet-item-background,#fff)}.van-action-sheet__subname{color:var(--action-sheet-subname-color,#969799);font-size:var(--action-sheet-subname-font-size,12px);line-height:var(--action-sheet-subname-line-height,20px);margin-top:var(--padding-xs,8px)}.van-action-sheet__header{font-size:var(--action-sheet-header-font-size,16px);font-weight:var(--font-weight-bold,500);line-height:var(--action-sheet-header-height,48px);text-align:center}.van-action-sheet__description{color:var(--action-sheet-description-color,#969799);font-size:var(--action-sheet-description-font-size,14px);line-height:var(--action-sheet-description-line-height,20px);padding:20px var(--padding-md,16px);text-align:center}.van-action-sheet__close{color:var(--action-sheet-close-icon-color,#c8c9cc);font-size:var(--action-sheet-close-icon-size,22px)!important;line-height:inherit!important;padding:var(--action-sheet-close-icon-padding,0 16px);position:absolute!important;right:0;top:0}.van-action-sheet__loading{display:flex!important}

+ 1 - 0
wxcomponents/vant/area/index.d.ts

@@ -0,0 +1 @@
+export {};

+ 217 - 0
wxcomponents/vant/area/index.js

@@ -0,0 +1,217 @@
+import { VantComponent } from '../common/component';
+import { pickerProps } from '../picker/shared';
+import { requestAnimationFrame } from '../common/utils';
+const EMPTY_CODE = '000000';
+VantComponent({
+    classes: ['active-class', 'toolbar-class', 'column-class'],
+    props: Object.assign(Object.assign({}, pickerProps), { value: {
+            type: String,
+            observer(value) {
+                this.code = value;
+                this.setValues();
+            },
+        }, areaList: {
+            type: Object,
+            value: {},
+            observer: 'setValues',
+        }, columnsNum: {
+            type: null,
+            value: 3,
+        }, columnsPlaceholder: {
+            type: Array,
+            observer(val) {
+                this.setData({
+                    typeToColumnsPlaceholder: {
+                        province: val[0] || '',
+                        city: val[1] || '',
+                        county: val[2] || '',
+                    },
+                });
+            },
+        } }),
+    data: {
+        columns: [{ values: [] }, { values: [] }, { values: [] }],
+        typeToColumnsPlaceholder: {},
+    },
+    mounted() {
+        requestAnimationFrame(() => {
+            this.setValues();
+        });
+    },
+    methods: {
+        getPicker() {
+            if (this.picker == null) {
+                this.picker = this.selectComponent('.van-area__picker');
+            }
+            return this.picker;
+        },
+        onCancel(event) {
+            this.emit('cancel', event.detail);
+        },
+        onConfirm(event) {
+            const { index } = event.detail;
+            let { value } = event.detail;
+            value = this.parseValues(value);
+            this.emit('confirm', { value, index });
+        },
+        emit(type, detail) {
+            detail.values = detail.value;
+            delete detail.value;
+            this.$emit(type, detail);
+        },
+        parseValues(values) {
+            const { columnsPlaceholder } = this.data;
+            return values.map((value, index) => {
+                if (value &&
+                    (!value.code || value.name === columnsPlaceholder[index])) {
+                    return Object.assign(Object.assign({}, value), { code: '', name: '' });
+                }
+                return value;
+            });
+        },
+        onChange(event) {
+            var _a;
+            const { index, picker, value } = event.detail;
+            this.code = value[index].code;
+            (_a = this.setValues()) === null || _a === void 0 ? void 0 : _a.then(() => {
+                this.$emit('change', {
+                    picker,
+                    values: this.parseValues(picker.getValues()),
+                    index,
+                });
+            });
+        },
+        getConfig(type) {
+            const { areaList } = this.data;
+            return (areaList && areaList[`${type}_list`]) || {};
+        },
+        getList(type, code) {
+            if (type !== 'province' && !code) {
+                return [];
+            }
+            const { typeToColumnsPlaceholder } = this.data;
+            const list = this.getConfig(type);
+            let result = Object.keys(list).map((code) => ({
+                code,
+                name: list[code],
+            }));
+            if (code != null) {
+                // oversea code
+                if (code[0] === '9' && type === 'city') {
+                    code = '9';
+                }
+                result = result.filter((item) => item.code.indexOf(code) === 0);
+            }
+            if (typeToColumnsPlaceholder[type] && result.length) {
+                // set columns placeholder
+                const codeFill = type === 'province'
+                    ? ''
+                    : type === 'city'
+                        ? EMPTY_CODE.slice(2, 4)
+                        : EMPTY_CODE.slice(4, 6);
+                result.unshift({
+                    code: `${code}${codeFill}`,
+                    name: typeToColumnsPlaceholder[type],
+                });
+            }
+            return result;
+        },
+        getIndex(type, code) {
+            let compareNum = type === 'province' ? 2 : type === 'city' ? 4 : 6;
+            const list = this.getList(type, code.slice(0, compareNum - 2));
+            // oversea code
+            if (code[0] === '9' && type === 'province') {
+                compareNum = 1;
+            }
+            code = code.slice(0, compareNum);
+            for (let i = 0; i < list.length; i++) {
+                if (list[i].code.slice(0, compareNum) === code) {
+                    return i;
+                }
+            }
+            return 0;
+        },
+        setValues() {
+            const picker = this.getPicker();
+            if (!picker) {
+                return;
+            }
+            let code = this.code || this.getDefaultCode();
+            const provinceList = this.getList('province');
+            const cityList = this.getList('city', code.slice(0, 2));
+            const stack = [];
+            const indexes = [];
+            const { columnsNum } = this.data;
+            if (columnsNum >= 1) {
+                stack.push(picker.setColumnValues(0, provinceList, false));
+                indexes.push(this.getIndex('province', code));
+            }
+            if (columnsNum >= 2) {
+                stack.push(picker.setColumnValues(1, cityList, false));
+                indexes.push(this.getIndex('city', code));
+                if (cityList.length && code.slice(2, 4) === '00') {
+                    [{ code }] = cityList;
+                }
+            }
+            if (columnsNum === 3) {
+                stack.push(picker.setColumnValues(2, this.getList('county', code.slice(0, 4)), false));
+                indexes.push(this.getIndex('county', code));
+            }
+            return Promise.all(stack)
+                .catch(() => { })
+                .then(() => picker.setIndexes(indexes))
+                .catch(() => { });
+        },
+        getDefaultCode() {
+            const { columnsPlaceholder } = this.data;
+            if (columnsPlaceholder.length) {
+                return EMPTY_CODE;
+            }
+            const countyCodes = Object.keys(this.getConfig('county'));
+            if (countyCodes[0]) {
+                return countyCodes[0];
+            }
+            const cityCodes = Object.keys(this.getConfig('city'));
+            if (cityCodes[0]) {
+                return cityCodes[0];
+            }
+            return '';
+        },
+        getValues() {
+            const picker = this.getPicker();
+            if (!picker) {
+                return [];
+            }
+            return this.parseValues(picker.getValues().filter((value) => !!value));
+        },
+        getDetail() {
+            const values = this.getValues();
+            const area = {
+                code: '',
+                country: '',
+                province: '',
+                city: '',
+                county: '',
+            };
+            if (!values.length) {
+                return area;
+            }
+            const names = values.map((item) => item.name);
+            area.code = values[values.length - 1].code;
+            if (area.code[0] === '9') {
+                area.country = names[1] || '';
+                area.province = names[2] || '';
+            }
+            else {
+                area.province = names[0] || '';
+                area.city = names[1] || '';
+                area.county = names[2] || '';
+            }
+            return area;
+        },
+        reset(code) {
+            this.code = code || '';
+            return this.setValues();
+        },
+    },
+});

+ 6 - 0
wxcomponents/vant/area/index.json

@@ -0,0 +1,6 @@
+{
+  "component": true,
+  "usingComponents": {
+    "van-picker": "../picker/index"
+  }
+}

+ 231 - 0
wxcomponents/vant/area/index.vue

@@ -0,0 +1,231 @@
+<template>
+<uni-shadow-root class="vant-area-index"><van-picker class="van-area__picker" active-class="active-class" toolbar-class="toolbar-class" column-class="column-class" show-toolbar value-key="name" :title="title" :loading="loading" :columns="computed.displayColumns(columns, columnsNum)" :item-height="itemHeight" :visible-item-count="visibleItemCount" :cancel-button-text="cancelButtonText" :confirm-button-text="confirmButtonText" @change="onChange" @confirm="onConfirm" @cancel="onCancel"></van-picker></uni-shadow-root>
+</template>
+<wxs src="./index.wxs" module="computed"></wxs>
+<script>
+import VanPicker from '../picker/index.vue'
+global['__wxVueOptions'] = {components:{'van-picker': VanPicker}}
+
+global['__wxRoute'] = 'vant/area/index'
+import { VantComponent } from '../common/component';
+import { pickerProps } from '../picker/shared';
+import { requestAnimationFrame } from '../common/utils';
+const EMPTY_CODE = '000000';
+VantComponent({
+    classes: ['active-class', 'toolbar-class', 'column-class'],
+    props: Object.assign(Object.assign({}, pickerProps), { value: {
+            type: String,
+            observer(value) {
+                this.code = value;
+                this.setValues();
+            },
+        }, areaList: {
+            type: Object,
+            value: {},
+            observer: 'setValues',
+        }, columnsNum: {
+            type: null,
+            value: 3,
+        }, columnsPlaceholder: {
+            type: Array,
+            observer(val) {
+                this.setData({
+                    typeToColumnsPlaceholder: {
+                        province: val[0] || '',
+                        city: val[1] || '',
+                        county: val[2] || '',
+                    },
+                });
+            },
+        } }),
+    data: {
+        columns: [{ values: [] }, { values: [] }, { values: [] }],
+        typeToColumnsPlaceholder: {},
+    },
+    mounted() {
+        requestAnimationFrame(() => {
+            this.setValues();
+        });
+    },
+    methods: {
+        getPicker() {
+            if (this.picker == null) {
+                this.picker = this.selectComponent('.van-area__picker');
+            }
+            return this.picker;
+        },
+        onCancel(event) {
+            this.emit('cancel', event.detail);
+        },
+        onConfirm(event) {
+            const { index } = event.detail;
+            let { value } = event.detail;
+            value = this.parseValues(value);
+            this.emit('confirm', { value, index });
+        },
+        emit(type, detail) {
+            detail.values = detail.value;
+            delete detail.value;
+            this.$emit(type, detail);
+        },
+        parseValues(values) {
+            const { columnsPlaceholder } = this.data;
+            return values.map((value, index) => {
+                if (value &&
+                    (!value.code || value.name === columnsPlaceholder[index])) {
+                    return Object.assign(Object.assign({}, value), { code: '', name: '' });
+                }
+                return value;
+            });
+        },
+        onChange(event) {
+            var _a;
+            const { index, picker, value } = event.detail;
+            this.code = value[index].code;
+            (_a = this.setValues()) === null || _a === void 0 ? void 0 : _a.then(() => {
+                this.$emit('change', {
+                    picker,
+                    values: this.parseValues(picker.getValues()),
+                    index,
+                });
+            });
+        },
+        getConfig(type) {
+            const { areaList } = this.data;
+            return (areaList && areaList[`${type}_list`]) || {};
+        },
+        getList(type, code) {
+            if (type !== 'province' && !code) {
+                return [];
+            }
+            const { typeToColumnsPlaceholder } = this.data;
+            const list = this.getConfig(type);
+            let result = Object.keys(list).map((code) => ({
+                code,
+                name: list[code],
+            }));
+            if (code != null) {
+                // oversea code
+                if (code[0] === '9' && type === 'city') {
+                    code = '9';
+                }
+                result = result.filter((item) => item.code.indexOf(code) === 0);
+            }
+            if (typeToColumnsPlaceholder[type] && result.length) {
+                // set columns placeholder
+                const codeFill = type === 'province'
+                    ? ''
+                    : type === 'city'
+                        ? EMPTY_CODE.slice(2, 4)
+                        : EMPTY_CODE.slice(4, 6);
+                result.unshift({
+                    code: `${code}${codeFill}`,
+                    name: typeToColumnsPlaceholder[type],
+                });
+            }
+            return result;
+        },
+        getIndex(type, code) {
+            let compareNum = type === 'province' ? 2 : type === 'city' ? 4 : 6;
+            const list = this.getList(type, code.slice(0, compareNum - 2));
+            // oversea code
+            if (code[0] === '9' && type === 'province') {
+                compareNum = 1;
+            }
+            code = code.slice(0, compareNum);
+            for (let i = 0; i < list.length; i++) {
+                if (list[i].code.slice(0, compareNum) === code) {
+                    return i;
+                }
+            }
+            return 0;
+        },
+        setValues() {
+            const picker = this.getPicker();
+            if (!picker) {
+                return;
+            }
+            let code = this.code || this.getDefaultCode();
+            const provinceList = this.getList('province');
+            const cityList = this.getList('city', code.slice(0, 2));
+            const stack = [];
+            const indexes = [];
+            const { columnsNum } = this.data;
+            if (columnsNum >= 1) {
+                stack.push(picker.setColumnValues(0, provinceList, false));
+                indexes.push(this.getIndex('province', code));
+            }
+            if (columnsNum >= 2) {
+                stack.push(picker.setColumnValues(1, cityList, false));
+                indexes.push(this.getIndex('city', code));
+                if (cityList.length && code.slice(2, 4) === '00') {
+                    [{ code }] = cityList;
+                }
+            }
+            if (columnsNum === 3) {
+                stack.push(picker.setColumnValues(2, this.getList('county', code.slice(0, 4)), false));
+                indexes.push(this.getIndex('county', code));
+            }
+            return Promise.all(stack)
+                .catch(() => { })
+                .then(() => picker.setIndexes(indexes))
+                .catch(() => { });
+        },
+        getDefaultCode() {
+            const { columnsPlaceholder } = this.data;
+            if (columnsPlaceholder.length) {
+                return EMPTY_CODE;
+            }
+            const countyCodes = Object.keys(this.getConfig('county'));
+            if (countyCodes[0]) {
+                return countyCodes[0];
+            }
+            const cityCodes = Object.keys(this.getConfig('city'));
+            if (cityCodes[0]) {
+                return cityCodes[0];
+            }
+            return '';
+        },
+        getValues() {
+            const picker = this.getPicker();
+            if (!picker) {
+                return [];
+            }
+            return this.parseValues(picker.getValues().filter((value) => !!value));
+        },
+        getDetail() {
+            const values = this.getValues();
+            const area = {
+                code: '',
+                country: '',
+                province: '',
+                city: '',
+                county: '',
+            };
+            if (!values.length) {
+                return area;
+            }
+            const names = values.map((item) => item.name);
+            area.code = values[values.length - 1].code;
+            if (area.code[0] === '9') {
+                area.country = names[1] || '';
+                area.province = names[2] || '';
+            }
+            else {
+                area.province = names[0] || '';
+                area.city = names[1] || '';
+                area.county = names[2] || '';
+            }
+            return area;
+        },
+        reset(code) {
+            this.code = code || '';
+            return this.setValues();
+        },
+    },
+});
+export default global['__wxComponents']['vant/area/index']
+</script>
+<style platform="mp-weixin">
+@import '../common/index.css';
+</style>

+ 20 - 0
wxcomponents/vant/area/index.wxml

@@ -0,0 +1,20 @@
+<wxs src="./index.wxs" module="computed" />
+
+<van-picker
+  class="van-area__picker"
+  active-class="active-class"
+  toolbar-class="toolbar-class"
+  column-class="column-class"
+  show-toolbar
+  value-key="name"
+  title="{{ title }}"
+  loading="{{ loading }}"
+  columns="{{ computed.displayColumns(columns, columnsNum) }}"
+  item-height="{{ itemHeight }}"
+  visible-item-count="{{ visibleItemCount }}"
+  cancel-button-text="{{ cancelButtonText }}"
+  confirm-button-text="{{ confirmButtonText }}"
+  bind:change="onChange"
+  bind:confirm="onConfirm"
+  bind:cancel="onCancel"
+/>

+ 8 - 0
wxcomponents/vant/area/index.wxs

@@ -0,0 +1,8 @@
+/* eslint-disable */
+function displayColumns(columns, columnsNum) {
+  return columns.slice(0, +columnsNum);
+}
+
+module.exports = {
+  displayColumns: displayColumns,
+};

+ 1 - 0
wxcomponents/vant/area/index.wxss

@@ -0,0 +1 @@
+@import '../common/index.wxss';

+ 1 - 0
wxcomponents/vant/button/index.d.ts

@@ -0,0 +1 @@
+export {};

+ 64 - 0
wxcomponents/vant/button/index.js

@@ -0,0 +1,64 @@
+import { VantComponent } from '../common/component';
+import { button } from '../mixins/button';
+import { canIUseFormFieldButton } from '../common/version';
+const mixins = [button];
+if (canIUseFormFieldButton()) {
+    mixins.push('wx://form-field-button');
+}
+VantComponent({
+    mixins,
+    classes: ['hover-class', 'loading-class'],
+    data: {
+        baseStyle: '',
+    },
+    props: {
+        formType: String,
+        icon: String,
+        classPrefix: {
+            type: String,
+            value: 'van-icon',
+        },
+        plain: Boolean,
+        block: Boolean,
+        round: Boolean,
+        square: Boolean,
+        loading: Boolean,
+        hairline: Boolean,
+        disabled: Boolean,
+        loadingText: String,
+        customStyle: String,
+        loadingType: {
+            type: String,
+            value: 'circular',
+        },
+        type: {
+            type: String,
+            value: 'default',
+        },
+        dataset: null,
+        size: {
+            type: String,
+            value: 'normal',
+        },
+        loadingSize: {
+            type: String,
+            value: '20px',
+        },
+        color: String,
+    },
+    methods: {
+        onClick(event) {
+            this.$emit('click', event);
+            const { canIUseGetUserProfile, openType, getUserProfileDesc, lang, } = this.data;
+            if (openType === 'getUserInfo' && canIUseGetUserProfile) {
+                wx.getUserProfile({
+                    desc: getUserProfileDesc || '  ',
+                    lang: lang || 'en',
+                    complete: (userProfile) => {
+                        this.$emit('getuserinfo', userProfile);
+                    },
+                });
+            }
+        },
+    },
+});

+ 7 - 0
wxcomponents/vant/button/index.json

@@ -0,0 +1,7 @@
+{
+  "component": true,
+  "usingComponents": {
+    "van-icon": "../icon/index",
+    "van-loading": "../loading/index"
+  }
+}

文件差異過大導致無法顯示
+ 90 - 0
wxcomponents/vant/button/index.vue


+ 53 - 0
wxcomponents/vant/button/index.wxml

@@ -0,0 +1,53 @@
+<wxs src="../wxs/utils.wxs" module="utils" />
+<wxs src="./index.wxs" module="computed" />
+
+<button
+  id="{{ id }}"
+  data-detail="{{ dataset }}"
+  class="custom-class {{ utils.bem('button', [type, size, { block, round, plain, square, loading, disabled, hairline, unclickable: disabled || loading }]) }} {{ hairline ? 'van-hairline--surround' : '' }}"
+  hover-class="van-button--active hover-class"
+  lang="{{ lang }}"
+  form-type="{{ formType }}"
+  style="{{ computed.rootStyle({ plain, color, customStyle }) }}"
+  open-type="{{ disabled || loading || (canIUseGetUserProfile && openType === 'getUserInfo') ? '' : openType }}"
+  business-id="{{ businessId }}"
+  session-from="{{ sessionFrom }}"
+  send-message-title="{{ sendMessageTitle }}"
+  send-message-path="{{ sendMessagePath }}"
+  send-message-img="{{ sendMessageImg }}"
+  show-message-card="{{ showMessageCard }}"
+  app-parameter="{{ appParameter }}"
+  aria-label="{{ ariaLabel }}"
+  bindtap="{{ disabled || loading ? '' : 'onClick' }}"
+  bindgetuserinfo="onGetUserInfo"
+  bindcontact="onContact"
+  bindgetphonenumber="onGetPhoneNumber"
+  binderror="onError"
+  bindlaunchapp="onLaunchApp"
+  bindopensetting="onOpenSetting"
+>
+  <block wx:if="{{ loading }}">
+    <van-loading
+      custom-class="loading-class"
+      size="{{ loadingSize }}"
+      type="{{ loadingType }}"
+      color="{{ computed.loadingColor({ type, color, plain }) }}"
+    />
+    <view wx:if="{{ loadingText }}" class="van-button__loading-text">
+      {{ loadingText }}
+    </view>
+  </block>
+  <block wx:else>
+    <van-icon
+      wx:if="{{ icon }}"
+      size="1.2em"
+      name="{{ icon }}"
+      class-prefix="{{ classPrefix }}"
+      class="van-button__icon"
+      custom-style="line-height: inherit;"
+    />
+    <view class="van-button__text">
+      <slot />
+    </view>
+  </block>
+</button>

+ 39 - 0
wxcomponents/vant/button/index.wxs

@@ -0,0 +1,39 @@
+/* eslint-disable */
+var style = require('../wxs/style.wxs');
+
+function rootStyle(data) {
+  if (!data.color) {
+    return data.customStyle;
+  }
+
+  var properties = {
+    color: data.plain ? data.color : '#fff',
+    background: data.plain ? null : data.color,
+  };
+
+  // hide border when color is linear-gradient
+  if (data.color.indexOf('gradient') !== -1) {
+    properties.border = 0;
+  } else {
+    properties['border-color'] = data.color;
+  }
+
+  return style([properties, data.customStyle]);
+}
+
+function loadingColor(data) {
+  if (data.plain) {
+    return data.color ? data.color : '#c9c9c9';
+  }
+
+  if (data.type === 'default') {
+    return '#c9c9c9';
+  }
+
+  return '#fff';
+}
+
+module.exports = {
+  rootStyle: rootStyle,
+  loadingColor: loadingColor,
+};

文件差異過大導致無法顯示
+ 0 - 0
wxcomponents/vant/button/index.wxss


+ 37 - 0
wxcomponents/vant/calendar/calendar.vue

@@ -0,0 +1,37 @@
+<template>
+<uni-shadow-root class="vant-calendar-calendar"><view class="van-calendar">
+  <header :title="title" :showTitle="showTitle" :subtitle="subtitle" :showSubtitle="showSubtitle" :firstDayOfWeek="firstDayOfWeek" @click-subtitle="onClickSubtitle">
+    <slot name="title" slot="title"></slot>
+  </header>
+
+  <scroll-view class="van-calendar__body" scroll-y :scroll-into-view="scrollIntoView">
+    <month v-for="(item,index) in (computed.getMonths(minDate, maxDate))" :key="item.index" :id="'month'+(index)" class="month" :data-date="item" :date="item" :type="type" :color="color" :minDate="minDate" :maxDate="maxDate" :showMark="showMark" :formatter="formatter" :rowHeight="rowHeight" :currentDate="currentDate" :showSubtitle="showSubtitle" :allowSameDay="allowSameDay" :showMonthTitle="index !== 0 || !showSubtitle" :firstDayOfWeek="firstDayOfWeek" @click="onClickDay"></month>
+  </scroll-view>
+
+  <view :class="utils.bem('calendar__footer', { safeAreaInsetBottom })">
+    <slot name="footer"></slot>
+  </view>
+
+  <view :class="utils.bem('calendar__footer', { safeAreaInsetBottom })">
+    <van-button v-if="showConfirm" round block type="danger" :color="color" custom-class="van-calendar__confirm" :disabled="computed.getButtonDisabled(type, currentDate)" nativeType="text" @click="onConfirm">
+      {{
+        computed.getButtonDisabled(type, currentDate)
+          ? confirmDisabledText
+          : confirmText
+      }}
+    </van-button>
+  </view>
+</view></uni-shadow-root>
+</template>
+
+<script>
+
+global['__wxRoute'] = 'vant/calendar/calendar'
+
+Component({})
+
+export default global['__wxComponents']['vant/calendar/calendar']
+</script>
+<style platform="mp-weixin">
+
+</style>

+ 68 - 0
wxcomponents/vant/calendar/calendar.wxml

@@ -0,0 +1,68 @@
+<view class="van-calendar">
+  <header
+    title="{{ title }}"
+    showTitle="{{ showTitle }}"
+    subtitle="{{ subtitle }}"
+    showSubtitle="{{ showSubtitle }}"
+    firstDayOfWeek="{{ firstDayOfWeek }}"
+    bind:click-subtitle="onClickSubtitle"
+  >
+    <slot name="title" slot="title"></slot>
+  </header>
+
+  <scroll-view
+    class="van-calendar__body"
+    scroll-y
+    scroll-into-view="{{ scrollIntoView }}"
+  >
+    <month
+      wx:for="{{ computed.getMonths(minDate, maxDate) }}"
+      wx:key="index"
+      id="month{{ index }}"
+      class="month"
+      data-date="{{ item }}"
+      date="{{ item }}"
+      type="{{ type }}"
+      color="{{ color }}"
+      minDate="{{ minDate }}"
+      maxDate="{{ maxDate }}"
+      showMark="{{ showMark }}"
+      formatter="{{ formatter }}"
+      rowHeight="{{ rowHeight }}"
+      currentDate="{{ currentDate }}"
+      showSubtitle="{{ showSubtitle }}"
+      allowSameDay="{{ allowSameDay }}"
+      showMonthTitle="{{ index !== 0 || !showSubtitle }}"
+      firstDayOfWeek="{{ firstDayOfWeek }}"
+      bind:click="onClickDay"
+    />
+  </scroll-view>
+
+  <view
+    class="{{ utils.bem('calendar__footer', { safeAreaInsetBottom }) }}"
+  >
+    <slot name="footer"></slot>
+  </view>
+
+  <view
+    class="{{ utils.bem('calendar__footer', { safeAreaInsetBottom }) }}"
+  >
+    <van-button
+      wx:if="{{ showConfirm }}"
+      round
+      block
+      type="danger"
+      color="{{ color }}"
+      custom-class="van-calendar__confirm"
+      disabled="{{ computed.getButtonDisabled(type, currentDate) }}"
+      nativeType="text"
+      bind:click="onConfirm"
+    >
+      {{
+        computed.getButtonDisabled(type, currentDate)
+          ? confirmDisabledText
+          : confirmText
+      }}
+    </van-button>
+  </view>
+</view>

+ 1 - 0
wxcomponents/vant/calendar/components/header/index.d.ts

@@ -0,0 +1 @@
+export {};

+ 37 - 0
wxcomponents/vant/calendar/components/header/index.js

@@ -0,0 +1,37 @@
+import { VantComponent } from '../../../common/component';
+VantComponent({
+    props: {
+        title: {
+            type: String,
+            value: '日期选择',
+        },
+        subtitle: String,
+        showTitle: Boolean,
+        showSubtitle: Boolean,
+        firstDayOfWeek: {
+            type: Number,
+            observer: 'initWeekDay',
+        },
+    },
+    data: {
+        weekdays: [],
+    },
+    created() {
+        this.initWeekDay();
+    },
+    methods: {
+        initWeekDay() {
+            const defaultWeeks = ['日', '一', '二', '三', '四', '五', '六'];
+            const firstDayOfWeek = this.data.firstDayOfWeek || 0;
+            this.setData({
+                weekdays: [
+                    ...defaultWeeks.slice(firstDayOfWeek, 7),
+                    ...defaultWeeks.slice(0, firstDayOfWeek),
+                ],
+            });
+        },
+        onClickSubtitle(event) {
+            this.$emit('click-subtitle', event);
+        },
+    },
+});

+ 3 - 0
wxcomponents/vant/calendar/components/header/index.json

@@ -0,0 +1,3 @@
+{
+  "component": true
+}

+ 64 - 0
wxcomponents/vant/calendar/components/header/index.vue

@@ -0,0 +1,64 @@
+<template>
+<uni-shadow-root class="vant-calendar-components-header-index"><view class="van-calendar__header">
+  <block v-if="showTitle">
+    <view class="van-calendar__header-title"><slot name="title"></slot></view>
+    <view class="van-calendar__header-title">{{ title }}</view>
+  </block>
+
+  <view v-if="showSubtitle" class="van-calendar__header-subtitle" @click="onClickSubtitle">
+    {{ subtitle }}
+  </view>
+
+  <view class="van-calendar__weekdays">
+    <view v-for="(item,index) in (weekdays)" :key="item.index" class="van-calendar__weekday">
+      {{ item }}
+    </view>
+  </view>
+</view></uni-shadow-root>
+</template>
+
+<script>
+
+global['__wxRoute'] = 'vant/calendar/components/header/index'
+import { VantComponent } from '../../../common/component';
+VantComponent({
+    props: {
+        title: {
+            type: String,
+            value: '日期选择',
+        },
+        subtitle: String,
+        showTitle: Boolean,
+        showSubtitle: Boolean,
+        firstDayOfWeek: {
+            type: Number,
+            observer: 'initWeekDay',
+        },
+    },
+    data: {
+        weekdays: [],
+    },
+    created() {
+        this.initWeekDay();
+    },
+    methods: {
+        initWeekDay() {
+            const defaultWeeks = ['日', '一', '二', '三', '四', '五', '六'];
+            const firstDayOfWeek = this.data.firstDayOfWeek || 0;
+            this.setData({
+                weekdays: [
+                    ...defaultWeeks.slice(firstDayOfWeek, 7),
+                    ...defaultWeeks.slice(0, firstDayOfWeek),
+                ],
+            });
+        },
+        onClickSubtitle(event) {
+            this.$emit('click-subtitle', event);
+        },
+    },
+});
+export default global['__wxComponents']['vant/calendar/components/header/index']
+</script>
+<style platform="mp-weixin">
+@import '../../../common/index.css';.van-calendar__header{box-shadow:var(--calendar-header-box-shadow,0 2px 10px hsla(220,1%,50%,.16));flex-shrink:0}.van-calendar__header-subtitle,.van-calendar__header-title{font-weight:var(--font-weight-bold,500);height:var(--calendar-header-title-height,44px);line-height:var(--calendar-header-title-height,44px);text-align:center}.van-calendar__header-title+.van-calendar__header-title,.van-calendar__header-title:empty{display:none}.van-calendar__header-title:empty+.van-calendar__header-title{display:block!important}.van-calendar__weekdays{display:flex}.van-calendar__weekday{flex:1;font-size:var(--calendar-weekdays-font-size,12px);line-height:var(--calendar-weekdays-height,30px);text-align:center}
+</style>

+ 16 - 0
wxcomponents/vant/calendar/components/header/index.wxml

@@ -0,0 +1,16 @@
+<view class="van-calendar__header">
+  <block wx:if="{{ showTitle }}">
+    <view class="van-calendar__header-title"><slot name="title"></slot></view>
+    <view class="van-calendar__header-title">{{ title }}</view>
+  </block>
+
+  <view wx:if="{{ showSubtitle }}" class="van-calendar__header-subtitle" bind:tap="onClickSubtitle">
+    {{ subtitle }}
+  </view>
+
+  <view class="van-calendar__weekdays">
+    <view wx:for="{{ weekdays }}" wx:key="index" class="van-calendar__weekday">
+      {{ item }}
+    </view>
+  </view>
+</view>

+ 1 - 0
wxcomponents/vant/calendar/components/header/index.wxss

@@ -0,0 +1 @@
+@import '../../../common/index.wxss';.van-calendar__header{box-shadow:var(--calendar-header-box-shadow,0 2px 10px hsla(220,1%,50%,.16));flex-shrink:0}.van-calendar__header-subtitle,.van-calendar__header-title{font-weight:var(--font-weight-bold,500);height:var(--calendar-header-title-height,44px);line-height:var(--calendar-header-title-height,44px);text-align:center}.van-calendar__header-title+.van-calendar__header-title,.van-calendar__header-title:empty{display:none}.van-calendar__header-title:empty+.van-calendar__header-title{display:block!important}.van-calendar__weekdays{display:flex}.van-calendar__weekday{flex:1;font-size:var(--calendar-weekdays-font-size,12px);line-height:var(--calendar-weekdays-height,30px);text-align:center}

+ 6 - 0
wxcomponents/vant/calendar/components/month/index.d.ts

@@ -0,0 +1,6 @@
+export interface Day {
+    date: Date;
+    type: string;
+    text: number;
+    bottomInfo?: string;
+}

+ 154 - 0
wxcomponents/vant/calendar/components/month/index.js

@@ -0,0 +1,154 @@
+import { VantComponent } from '../../../common/component';
+import { getMonthEndDay, compareDay, getPrevDay, getNextDay, } from '../../utils';
+VantComponent({
+    props: {
+        date: {
+            type: null,
+            observer: 'setDays',
+        },
+        type: {
+            type: String,
+            observer: 'setDays',
+        },
+        color: String,
+        minDate: {
+            type: null,
+            observer: 'setDays',
+        },
+        maxDate: {
+            type: null,
+            observer: 'setDays',
+        },
+        showMark: Boolean,
+        rowHeight: null,
+        formatter: {
+            type: null,
+            observer: 'setDays',
+        },
+        currentDate: {
+            type: null,
+            observer: 'setDays',
+        },
+        firstDayOfWeek: {
+            type: Number,
+            observer: 'setDays',
+        },
+        allowSameDay: Boolean,
+        showSubtitle: Boolean,
+        showMonthTitle: Boolean,
+    },
+    data: {
+        visible: true,
+        days: [],
+    },
+    methods: {
+        onClick(event) {
+            const { index } = event.currentTarget.dataset;
+            const item = this.data.days[index];
+            if (item.type !== 'disabled') {
+                this.$emit('click', item);
+            }
+        },
+        setDays() {
+            const days = [];
+            const startDate = new Date(this.data.date);
+            const year = startDate.getFullYear();
+            const month = startDate.getMonth();
+            const totalDay = getMonthEndDay(startDate.getFullYear(), startDate.getMonth() + 1);
+            for (let day = 1; day <= totalDay; day++) {
+                const date = new Date(year, month, day);
+                const type = this.getDayType(date);
+                let config = {
+                    date,
+                    type,
+                    text: day,
+                    bottomInfo: this.getBottomInfo(type),
+                };
+                if (this.data.formatter) {
+                    config = this.data.formatter(config);
+                }
+                days.push(config);
+            }
+            this.setData({ days });
+        },
+        getMultipleDayType(day) {
+            const { currentDate } = this.data;
+            if (!Array.isArray(currentDate)) {
+                return '';
+            }
+            const isSelected = (date) => currentDate.some((item) => compareDay(item, date) === 0);
+            if (isSelected(day)) {
+                const prevDay = getPrevDay(day);
+                const nextDay = getNextDay(day);
+                const prevSelected = isSelected(prevDay);
+                const nextSelected = isSelected(nextDay);
+                if (prevSelected && nextSelected) {
+                    return 'multiple-middle';
+                }
+                if (prevSelected) {
+                    return 'end';
+                }
+                return nextSelected ? 'start' : 'multiple-selected';
+            }
+            return '';
+        },
+        getRangeDayType(day) {
+            const { currentDate, allowSameDay } = this.data;
+            if (!Array.isArray(currentDate)) {
+                return '';
+            }
+            const [startDay, endDay] = currentDate;
+            if (!startDay) {
+                return '';
+            }
+            const compareToStart = compareDay(day, startDay);
+            if (!endDay) {
+                return compareToStart === 0 ? 'start' : '';
+            }
+            const compareToEnd = compareDay(day, endDay);
+            if (compareToStart === 0 && compareToEnd === 0 && allowSameDay) {
+                return 'start-end';
+            }
+            if (compareToStart === 0) {
+                return 'start';
+            }
+            if (compareToEnd === 0) {
+                return 'end';
+            }
+            if (compareToStart > 0 && compareToEnd < 0) {
+                return 'middle';
+            }
+            return '';
+        },
+        getDayType(day) {
+            const { type, minDate, maxDate, currentDate } = this.data;
+            if (compareDay(day, minDate) < 0 || compareDay(day, maxDate) > 0) {
+                return 'disabled';
+            }
+            if (type === 'single') {
+                return compareDay(day, currentDate) === 0 ? 'selected' : '';
+            }
+            if (type === 'multiple') {
+                return this.getMultipleDayType(day);
+            }
+            /* istanbul ignore else */
+            if (type === 'range') {
+                return this.getRangeDayType(day);
+            }
+            return '';
+        },
+        getBottomInfo(type) {
+            if (this.data.type === 'range') {
+                if (type === 'start') {
+                    return '开始';
+                }
+                if (type === 'end') {
+                    return '结束';
+                }
+                if (type === 'start-end') {
+                    return '开始/结束';
+                }
+            }
+        },
+    },
+});

+ 3 - 0
wxcomponents/vant/calendar/components/month/index.json

@@ -0,0 +1,3 @@
+{
+  "component": true
+}

文件差異過大導致無法顯示
+ 192 - 0
wxcomponents/vant/calendar/components/month/index.vue


+ 39 - 0
wxcomponents/vant/calendar/components/month/index.wxml

@@ -0,0 +1,39 @@
+<wxs src="./index.wxs" module="computed"></wxs>
+<wxs src="../../../wxs/utils.wxs" module="utils" />
+
+<view class="van-calendar__month" style="{{ computed.getMonthStyle(visible, date, rowHeight) }}">
+  <view wx:if="{{ showMonthTitle }}" class="van-calendar__month-title">
+    {{ computed.formatMonthTitle(date) }}
+  </view>
+
+  <view wx:if="{{ visible }}" class="van-calendar__days">
+    <view wx:if="{{ showMark }}" class="van-calendar__month-mark">
+      {{ computed.getMark(date) }}
+    </view>
+
+    <view
+      wx:for="{{ days }}"
+      wx:key="index"
+      style="{{ computed.getDayStyle(item.type, index, date, rowHeight, color, firstDayOfWeek) }}"
+      class="{{ utils.bem('calendar__day', [item.type]) }} {{ item.className }}"
+      data-index="{{ index }}"
+      bindtap="onClick"
+    >
+      <view wx:if="{{ item.type === 'selected' }}" class="van-calendar__selected-day" style="background: {{ color }}">
+        <view wx:if="{{ item.topInfo }}" class="van-calendar__top-info">{{ item.topInfo }}</view>
+        {{ item.text }}
+        <view wx:if="{{ item.bottomInfo }}" class="van-calendar__bottom-info">
+          {{ item.bottomInfo }}
+        </view>
+      </view>
+
+      <view wx:else>
+        <view wx:if="{{ item.topInfo }}" class="van-calendar__top-info">{{ item.topInfo }}</view>
+        {{ item.text }}
+        <view wx:if="{{ item.bottomInfo }}" class="van-calendar__bottom-info">
+          {{ item.bottomInfo }}
+        </view>
+      </view>
+    </view>
+  </view>
+</view>

+ 71 - 0
wxcomponents/vant/calendar/components/month/index.wxs

@@ -0,0 +1,71 @@
+/* eslint-disable */
+var utils = require('../../utils.wxs');
+
+function getMark(date) {
+  return getDate(date).getMonth() + 1;
+}
+
+var ROW_HEIGHT = 64;
+
+function getDayStyle(type, index, date, rowHeight, color, firstDayOfWeek) {
+  var style = [];
+  var current = getDate(date).getDay() || 7;
+  var offset = current < firstDayOfWeek ? (7 - firstDayOfWeek + current) :
+               current === 7 && firstDayOfWeek === 0 ? 0 :
+               (current - firstDayOfWeek);
+
+  if (index === 0) {
+    style.push(['margin-left', (100 * offset) / 7 + '%']);
+  }
+
+  if (rowHeight !== ROW_HEIGHT) {
+    style.push(['height', rowHeight + 'px']);
+  }
+
+  if (color) {
+    if (
+      type === 'start' ||
+      type === 'end' ||
+      type === 'start-end' ||
+      type === 'multiple-selected' ||
+      type === 'multiple-middle'
+    ) {
+      style.push(['background', color]);
+    } else if (type === 'middle') {
+      style.push(['color', color]);
+    }
+  }
+
+  return style
+    .map(function(item) {
+      return item.join(':');
+    })
+    .join(';');
+}
+
+function formatMonthTitle(date) {
+  date = getDate(date);
+  return date.getFullYear() + '年' + (date.getMonth() + 1) + '月';
+}
+
+function getMonthStyle(visible, date, rowHeight) {
+  if (!visible) {
+    date = getDate(date);
+
+    var totalDay = utils.getMonthEndDay(
+      date.getFullYear(),
+      date.getMonth() + 1
+    );
+    var offset = getDate(date).getDay();
+    var padding = Math.ceil((totalDay + offset) / 7) * rowHeight;
+
+    return 'padding-bottom:' + padding + 'px';
+  }
+}
+
+module.exports = {
+  getMark: getMark,
+  getDayStyle: getDayStyle,
+  formatMonthTitle: formatMonthTitle,
+  getMonthStyle: getMonthStyle
+};

文件差異過大導致無法顯示
+ 0 - 0
wxcomponents/vant/calendar/components/month/index.wxss


+ 1 - 0
wxcomponents/vant/calendar/index.d.ts

@@ -0,0 +1 @@
+export {};

+ 337 - 0
wxcomponents/vant/calendar/index.js

@@ -0,0 +1,337 @@
+import { VantComponent } from '../common/component';
+import { ROW_HEIGHT, getPrevDay, getNextDay, getToday, compareDay, copyDates, calcDateNum, formatMonthTitle, compareMonth, getMonths, getDayByOffset, } from './utils';
+import Toast from '../toast/toast';
+import { requestAnimationFrame } from '../common/utils';
+const initialMinDate = getToday().getTime();
+const initialMaxDate = (() => {
+    const now = getToday();
+    return new Date(now.getFullYear(), now.getMonth() + 6, now.getDate()).getTime();
+})();
+const getTime = (date) => date instanceof Date ? date.getTime() : date;
+VantComponent({
+    props: {
+        title: {
+            type: String,
+            value: '日期选择',
+        },
+        color: String,
+        show: {
+            type: Boolean,
+            observer(val) {
+                if (val) {
+                    this.initRect();
+                    this.scrollIntoView();
+                }
+            },
+        },
+        formatter: null,
+        confirmText: {
+            type: String,
+            value: '确定',
+        },
+        confirmDisabledText: {
+            type: String,
+            value: '确定',
+        },
+        rangePrompt: String,
+        showRangePrompt: {
+            type: Boolean,
+            value: true,
+        },
+        defaultDate: {
+            type: null,
+            observer(val) {
+                this.setData({ currentDate: val });
+                this.scrollIntoView();
+            },
+        },
+        allowSameDay: Boolean,
+        type: {
+            type: String,
+            value: 'single',
+            observer: 'reset',
+        },
+        minDate: {
+            type: Number,
+            value: initialMinDate,
+        },
+        maxDate: {
+            type: Number,
+            value: initialMaxDate,
+        },
+        position: {
+            type: String,
+            value: 'bottom',
+        },
+        rowHeight: {
+            type: null,
+            value: ROW_HEIGHT,
+        },
+        round: {
+            type: Boolean,
+            value: true,
+        },
+        poppable: {
+            type: Boolean,
+            value: true,
+        },
+        showMark: {
+            type: Boolean,
+            value: true,
+        },
+        showTitle: {
+            type: Boolean,
+            value: true,
+        },
+        showConfirm: {
+            type: Boolean,
+            value: true,
+        },
+        showSubtitle: {
+            type: Boolean,
+            value: true,
+        },
+        safeAreaInsetBottom: {
+            type: Boolean,
+            value: true,
+        },
+        closeOnClickOverlay: {
+            type: Boolean,
+            value: true,
+        },
+        maxRange: {
+            type: null,
+            value: null,
+        },
+        firstDayOfWeek: {
+            type: Number,
+            value: 0,
+        },
+        readonly: Boolean,
+    },
+    data: {
+        subtitle: '',
+        currentDate: null,
+        scrollIntoView: '',
+    },
+    created() {
+        this.setData({
+            currentDate: this.getInitialDate(this.data.defaultDate),
+        });
+    },
+    mounted() {
+        if (this.data.show || !this.data.poppable) {
+            this.initRect();
+            this.scrollIntoView();
+        }
+    },
+    methods: {
+        reset() {
+            this.setData({ currentDate: this.getInitialDate() });
+            this.scrollIntoView();
+        },
+        initRect() {
+            if (this.contentObserver != null) {
+                this.contentObserver.disconnect();
+            }
+            const contentObserver = this.createIntersectionObserver({
+                thresholds: [0, 0.1, 0.9, 1],
+                observeAll: true,
+            });
+            this.contentObserver = contentObserver;
+            contentObserver.relativeTo('.van-calendar__body');
+            contentObserver.observe('.month', (res) => {
+                if (res.boundingClientRect.top <= res.relativeRect.top) {
+                    // @ts-ignore
+                    this.setData({ subtitle: formatMonthTitle(res.dataset.date) });
+                }
+            });
+        },
+        limitDateRange(date, minDate = null, maxDate = null) {
+            minDate = minDate || this.data.minDate;
+            maxDate = maxDate || this.data.maxDate;
+            if (compareDay(date, minDate) === -1) {
+                return minDate;
+            }
+            if (compareDay(date, maxDate) === 1) {
+                return maxDate;
+            }
+            return date;
+        },
+        getInitialDate(defaultDate = null) {
+            const { type, minDate, maxDate } = this.data;
+            const now = getToday().getTime();
+            if (type === 'range') {
+                if (!Array.isArray(defaultDate)) {
+                    defaultDate = [];
+                }
+                const [startDay, endDay] = defaultDate || [];
+                const start = this.limitDateRange(startDay || now, minDate, getPrevDay(new Date(maxDate)).getTime());
+                const end = this.limitDateRange(endDay || now, getNextDay(new Date(minDate)).getTime());
+                return [start, end];
+            }
+            if (type === 'multiple') {
+                if (Array.isArray(defaultDate)) {
+                    return defaultDate.map((date) => this.limitDateRange(date));
+                }
+                return [this.limitDateRange(now)];
+            }
+            if (!defaultDate || Array.isArray(defaultDate)) {
+                defaultDate = now;
+            }
+            return this.limitDateRange(defaultDate);
+        },
+        scrollIntoView() {
+            requestAnimationFrame(() => {
+                const { currentDate, type, show, poppable, minDate, maxDate } = this.data;
+                // @ts-ignore
+                const targetDate = type === 'single' ? currentDate : currentDate[0];
+                const displayed = show || !poppable;
+                if (!targetDate || !displayed) {
+                    return;
+                }
+                const months = getMonths(minDate, maxDate);
+                months.some((month, index) => {
+                    if (compareMonth(month, targetDate) === 0) {
+                        this.setData({ scrollIntoView: `month${index}` });
+                        return true;
+                    }
+                    return false;
+                });
+            });
+        },
+        onOpen() {
+            this.$emit('open');
+        },
+        onOpened() {
+            this.$emit('opened');
+        },
+        onClose() {
+            this.$emit('close');
+        },
+        onClosed() {
+            this.$emit('closed');
+        },
+        onClickDay(event) {
+            if (this.data.readonly) {
+                return;
+            }
+            let { date } = event.detail;
+            const { type, currentDate, allowSameDay } = this.data;
+            if (type === 'range') {
+                // @ts-ignore
+                const [startDay, endDay] = currentDate;
+                if (startDay && !endDay) {
+                    const compareToStart = compareDay(date, startDay);
+                    if (compareToStart === 1) {
+                        const { days } = this.selectComponent('.month').data;
+                        days.some((day, index) => {
+                            const isDisabled = day.type === 'disabled' &&
+                                getTime(startDay) < getTime(day.date) &&
+                                getTime(day.date) < getTime(date);
+                            if (isDisabled) {
+                                ({ date } = days[index - 1]);
+                            }
+                            return isDisabled;
+                        });
+                        this.select([startDay, date], true);
+                    }
+                    else if (compareToStart === -1) {
+                        this.select([date, null]);
+                    }
+                    else if (allowSameDay) {
+                        this.select([date, date]);
+                    }
+                }
+                else {
+                    this.select([date, null]);
+                }
+            }
+            else if (type === 'multiple') {
+                let selectedIndex;
+                // @ts-ignore
+                const selected = currentDate.some((dateItem, index) => {
+                    const equal = compareDay(dateItem, date) === 0;
+                    if (equal) {
+                        selectedIndex = index;
+                    }
+                    return equal;
+                });
+                if (selected) {
+                    // @ts-ignore
+                    const cancelDate = currentDate.splice(selectedIndex, 1);
+                    this.setData({ currentDate });
+                    this.unselect(cancelDate);
+                }
+                else {
+                    // @ts-ignore
+                    this.select([...currentDate, date]);
+                }
+            }
+            else {
+                this.select(date, true);
+            }
+        },
+        unselect(dateArray) {
+            const date = dateArray[0];
+            if (date) {
+                this.$emit('unselect', copyDates(date));
+            }
+        },
+        select(date, complete) {
+            if (complete && this.data.type === 'range') {
+                const valid = this.checkRange(date);
+                if (!valid) {
+                    // auto selected to max range if showConfirm
+                    if (this.data.showConfirm) {
+                        this.emit([
+                            date[0],
+                            getDayByOffset(date[0], this.data.maxRange - 1),
+                        ]);
+                    }
+                    else {
+                        this.emit(date);
+                    }
+                    return;
+                }
+            }
+            this.emit(date);
+            if (complete && !this.data.showConfirm) {
+                this.onConfirm();
+            }
+        },
+        emit(date) {
+            this.setData({
+                currentDate: Array.isArray(date) ? date.map(getTime) : getTime(date),
+            });
+            this.$emit('select', copyDates(date));
+        },
+        checkRange(date) {
+            const { maxRange, rangePrompt, showRangePrompt } = this.data;
+            if (maxRange && calcDateNum(date) > maxRange) {
+                if (showRangePrompt) {
+                    Toast({
+                        context: this,
+                        message: rangePrompt || `选择天数不能超过 ${maxRange} 天`,
+                    });
+                }
+                this.$emit('over-range');
+                return false;
+            }
+            return true;
+        },
+        onConfirm() {
+            if (this.data.type === 'range' &&
+                !this.checkRange(this.data.currentDate)) {
+                return;
+            }
+            wx.nextTick(() => {
+                // @ts-ignore
+                this.$emit('confirm', copyDates(this.data.currentDate));
+            });
+        },
+        onClickSubtitle(event) {
+            this.$emit('click-subtitle', event);
+        },
+    },
+});

+ 10 - 0
wxcomponents/vant/calendar/index.json

@@ -0,0 +1,10 @@
+{
+  "component": true,
+  "usingComponents": {
+    "header": "./components/header/index",
+    "month": "./components/month/index",
+    "van-button": "../button/index",
+    "van-popup": "../popup/index",
+    "van-toast": "../toast/index"
+  }
+}

+ 365 - 0
wxcomponents/vant/calendar/index.vue

@@ -0,0 +1,365 @@
+<template>
+<uni-shadow-root class="vant-calendar-index"><van-popup v-if="poppable" :custom-class="'van-calendar__popup--'+(position)" close-icon-class="van-calendar__close-icon" :show="show" :round="round" :position="position" :closeable="showTitle || showSubtitle" :close-on-click-overlay="closeOnClickOverlay" @enter="onOpen" @close="onClose" @after-enter="onOpened" @after-leave="onClosed">
+  <include src="./calendar.wxml"></include>
+</van-popup>
+
+<include v-else src="./calendar.wxml"></include>
+
+<van-toast id="van-toast"></van-toast></uni-shadow-root>
+</template>
+<wxs src="./index.wxs" module="computed"></wxs><wxs src="../wxs/utils.wxs" module="utils"></wxs>
+<script>
+
+const __wxTemplateComponentProps = {}
+import __wxTemplateComponent0 from './calendar.vue'
+
+import Header from './components/header/index.vue'
+import Month from './components/month/index.vue'
+import VanButton from '../button/index.vue'
+import VanPopup from '../popup/index.vue'
+import VanToast from '../toast/index.vue'
+global['__wxVueOptions'] = {components:{'header': Header,'month': Month,'van-button': VanButton,'van-popup': VanPopup,'van-toast': VanToast,}}
+
+global['__wxRoute'] = 'vant/calendar/index'
+import { VantComponent } from '../common/component';
+import { ROW_HEIGHT, getPrevDay, getNextDay, getToday, compareDay, copyDates, calcDateNum, formatMonthTitle, compareMonth, getMonths, getDayByOffset, } from './utils';
+import Toast from '../toast/toast';
+import { requestAnimationFrame } from '../common/utils';
+const initialMinDate = getToday().getTime();
+const initialMaxDate = (() => {
+    const now = getToday();
+    return new Date(now.getFullYear(), now.getMonth() + 6, now.getDate()).getTime();
+})();
+const getTime = (date) => date instanceof Date ? date.getTime() : date;
+VantComponent({
+    props: {
+        title: {
+            type: String,
+            value: '日期选择',
+        },
+        color: String,
+        show: {
+            type: Boolean,
+            observer(val) {
+                if (val) {
+                    this.initRect();
+                    this.scrollIntoView();
+                }
+            },
+        },
+        formatter: null,
+        confirmText: {
+            type: String,
+            value: '确定',
+        },
+        confirmDisabledText: {
+            type: String,
+            value: '确定',
+        },
+        rangePrompt: String,
+        showRangePrompt: {
+            type: Boolean,
+            value: true,
+        },
+        defaultDate: {
+            type: null,
+            observer(val) {
+                this.setData({ currentDate: val });
+                this.scrollIntoView();
+            },
+        },
+        allowSameDay: Boolean,
+        type: {
+            type: String,
+            value: 'single',
+            observer: 'reset',
+        },
+        minDate: {
+            type: Number,
+            value: initialMinDate,
+        },
+        maxDate: {
+            type: Number,
+            value: initialMaxDate,
+        },
+        position: {
+            type: String,
+            value: 'bottom',
+        },
+        rowHeight: {
+            type: null,
+            value: ROW_HEIGHT,
+        },
+        round: {
+            type: Boolean,
+            value: true,
+        },
+        poppable: {
+            type: Boolean,
+            value: true,
+        },
+        showMark: {
+            type: Boolean,
+            value: true,
+        },
+        showTitle: {
+            type: Boolean,
+            value: true,
+        },
+        showConfirm: {
+            type: Boolean,
+            value: true,
+        },
+        showSubtitle: {
+            type: Boolean,
+            value: true,
+        },
+        safeAreaInsetBottom: {
+            type: Boolean,
+            value: true,
+        },
+        closeOnClickOverlay: {
+            type: Boolean,
+            value: true,
+        },
+        maxRange: {
+            type: null,
+            value: null,
+        },
+        firstDayOfWeek: {
+            type: Number,
+            value: 0,
+        },
+        readonly: Boolean,
+    },
+    data: {
+        subtitle: '',
+        currentDate: null,
+        scrollIntoView: '',
+    },
+    created() {
+        this.setData({
+            currentDate: this.getInitialDate(this.data.defaultDate),
+        });
+    },
+    mounted() {
+        if (this.data.show || !this.data.poppable) {
+            this.initRect();
+            this.scrollIntoView();
+        }
+    },
+    methods: {
+        reset() {
+            this.setData({ currentDate: this.getInitialDate() });
+            this.scrollIntoView();
+        },
+        initRect() {
+            if (this.contentObserver != null) {
+                this.contentObserver.disconnect();
+            }
+            const contentObserver = this.createIntersectionObserver({
+                thresholds: [0, 0.1, 0.9, 1],
+                observeAll: true,
+            });
+            this.contentObserver = contentObserver;
+            contentObserver.relativeTo('.van-calendar__body');
+            contentObserver.observe('.month', (res) => {
+                if (res.boundingClientRect.top <= res.relativeRect.top) {
+                    // @ts-ignore
+                    this.setData({ subtitle: formatMonthTitle(res.dataset.date) });
+                }
+            });
+        },
+        limitDateRange(date, minDate = null, maxDate = null) {
+            minDate = minDate || this.data.minDate;
+            maxDate = maxDate || this.data.maxDate;
+            if (compareDay(date, minDate) === -1) {
+                return minDate;
+            }
+            if (compareDay(date, maxDate) === 1) {
+                return maxDate;
+            }
+            return date;
+        },
+        getInitialDate(defaultDate = null) {
+            const { type, minDate, maxDate } = this.data;
+            const now = getToday().getTime();
+            if (type === 'range') {
+                if (!Array.isArray(defaultDate)) {
+                    defaultDate = [];
+                }
+                const [startDay, endDay] = defaultDate || [];
+                const start = this.limitDateRange(startDay || now, minDate, getPrevDay(new Date(maxDate)).getTime());
+                const end = this.limitDateRange(endDay || now, getNextDay(new Date(minDate)).getTime());
+                return [start, end];
+            }
+            if (type === 'multiple') {
+                if (Array.isArray(defaultDate)) {
+                    return defaultDate.map((date) => this.limitDateRange(date));
+                }
+                return [this.limitDateRange(now)];
+            }
+            if (!defaultDate || Array.isArray(defaultDate)) {
+                defaultDate = now;
+            }
+            return this.limitDateRange(defaultDate);
+        },
+        scrollIntoView() {
+            requestAnimationFrame(() => {
+                const { currentDate, type, show, poppable, minDate, maxDate } = this.data;
+                // @ts-ignore
+                const targetDate = type === 'single' ? currentDate : currentDate[0];
+                const displayed = show || !poppable;
+                if (!targetDate || !displayed) {
+                    return;
+                }
+                const months = getMonths(minDate, maxDate);
+                months.some((month, index) => {
+                    if (compareMonth(month, targetDate) === 0) {
+                        this.setData({ scrollIntoView: `month${index}` });
+                        return true;
+                    }
+                    return false;
+                });
+            });
+        },
+        onOpen() {
+            this.$emit('open');
+        },
+        onOpened() {
+            this.$emit('opened');
+        },
+        onClose() {
+            this.$emit('close');
+        },
+        onClosed() {
+            this.$emit('closed');
+        },
+        onClickDay(event) {
+            if (this.data.readonly) {
+                return;
+            }
+            let { date } = event.detail;
+            const { type, currentDate, allowSameDay } = this.data;
+            if (type === 'range') {
+                // @ts-ignore
+                const [startDay, endDay] = currentDate;
+                if (startDay && !endDay) {
+                    const compareToStart = compareDay(date, startDay);
+                    if (compareToStart === 1) {
+                        const { days } = this.selectComponent('.month').data;
+                        days.some((day, index) => {
+                            const isDisabled = day.type === 'disabled' &&
+                                getTime(startDay) < getTime(day.date) &&
+                                getTime(day.date) < getTime(date);
+                            if (isDisabled) {
+                                ({ date } = days[index - 1]);
+                            }
+                            return isDisabled;
+                        });
+                        this.select([startDay, date], true);
+                    }
+                    else if (compareToStart === -1) {
+                        this.select([date, null]);
+                    }
+                    else if (allowSameDay) {
+                        this.select([date, date]);
+                    }
+                }
+                else {
+                    this.select([date, null]);
+                }
+            }
+            else if (type === 'multiple') {
+                let selectedIndex;
+                // @ts-ignore
+                const selected = currentDate.some((dateItem, index) => {
+                    const equal = compareDay(dateItem, date) === 0;
+                    if (equal) {
+                        selectedIndex = index;
+                    }
+                    return equal;
+                });
+                if (selected) {
+                    // @ts-ignore
+                    const cancelDate = currentDate.splice(selectedIndex, 1);
+                    this.setData({ currentDate });
+                    this.unselect(cancelDate);
+                }
+                else {
+                    // @ts-ignore
+                    this.select([...currentDate, date]);
+                }
+            }
+            else {
+                this.select(date, true);
+            }
+        },
+        unselect(dateArray) {
+            const date = dateArray[0];
+            if (date) {
+                this.$emit('unselect', copyDates(date));
+            }
+        },
+        select(date, complete) {
+            if (complete && this.data.type === 'range') {
+                const valid = this.checkRange(date);
+                if (!valid) {
+                    // auto selected to max range if showConfirm
+                    if (this.data.showConfirm) {
+                        this.emit([
+                            date[0],
+                            getDayByOffset(date[0], this.data.maxRange - 1),
+                        ]);
+                    }
+                    else {
+                        this.emit(date);
+                    }
+                    return;
+                }
+            }
+            this.emit(date);
+            if (complete && !this.data.showConfirm) {
+                this.onConfirm();
+            }
+        },
+        emit(date) {
+            this.setData({
+                currentDate: Array.isArray(date) ? date.map(getTime) : getTime(date),
+            });
+            this.$emit('select', copyDates(date));
+        },
+        checkRange(date) {
+            const { maxRange, rangePrompt, showRangePrompt } = this.data;
+            if (maxRange && calcDateNum(date) > maxRange) {
+                if (showRangePrompt) {
+                    Toast({
+                        context: this,
+                        message: rangePrompt || `选择天数不能超过 ${maxRange} 天`,
+                    });
+                }
+                this.$emit('over-range');
+                return false;
+            }
+            return true;
+        },
+        onConfirm() {
+            if (this.data.type === 'range' &&
+                !this.checkRange(this.data.currentDate)) {
+                return;
+            }
+            wx.nextTick(() => {
+                // @ts-ignore
+                this.$emit('confirm', copyDates(this.data.currentDate));
+            });
+        },
+        onClickSubtitle(event) {
+            this.$emit('click-subtitle', event);
+        },
+    },
+});
+export default global['__wxComponents']['vant/calendar/index']
+</script>
+<style platform="mp-weixin">
+@import '../common/index.css';.van-calendar{background-color:var(--calendar-background-color,#fff);display:flex;flex-direction:column;height:var(--calendar-height,100%)}.van-calendar__close-icon{top:11px}.van-calendar__popup--bottom,.van-calendar__popup--top{height:var(--calendar-popup-height,80%)}.van-calendar__popup--left,.van-calendar__popup--right{height:100%}.van-calendar__body{-webkit-overflow-scrolling:touch;flex:1;overflow:auto}.van-calendar__footer{flex-shrink:0;padding:0 var(--padding-md,16px)}.van-calendar__footer--safe-area-inset-bottom{padding-bottom:env(safe-area-inset-bottom)}.van-calendar__footer+.van-calendar__footer,.van-calendar__footer:empty{display:none}.van-calendar__footer:empty+.van-calendar__footer{display:block!important}.van-calendar__confirm{height:var(--calendar-confirm-button-height,36px)!important;line-height:var(--calendar-confirm-button-line-height,34px)!important;margin:var(--calendar-confirm-button-margin,7px 0)!important}
+</style>

+ 25 - 0
wxcomponents/vant/calendar/index.wxml

@@ -0,0 +1,25 @@
+<wxs src="./index.wxs" module="computed" />
+<wxs src="../wxs/utils.wxs" module="utils" />
+
+<import src="./calendar.wxml" />
+
+<van-popup
+  wx:if="{{ poppable }}"
+  custom-class="van-calendar__popup--{{ position }}"
+  close-icon-class="van-calendar__close-icon"
+  show="{{ show }}"
+  round="{{ round }}"
+  position="{{ position }}"
+  closeable="{{ showTitle || showSubtitle }}"
+  close-on-click-overlay="{{ closeOnClickOverlay }}"
+  bind:enter="onOpen"
+  bind:close="onClose"
+  bind:after-enter="onOpened"
+  bind:after-leave="onClosed"
+>
+  <include src="./calendar.wxml" />
+</van-popup>
+
+<include wx:else src="./calendar.wxml" />
+
+<van-toast id="van-toast" />

+ 37 - 0
wxcomponents/vant/calendar/index.wxs

@@ -0,0 +1,37 @@
+/* eslint-disable */
+var utils = require('./utils.wxs');
+
+function getMonths(minDate, maxDate) {
+  var months = [];
+  var cursor = getDate(minDate);
+
+  cursor.setDate(1);
+
+  do {
+    months.push(cursor.getTime());
+    cursor.setMonth(cursor.getMonth() + 1);
+  } while (utils.compareMonth(cursor, getDate(maxDate)) !== 1);
+
+  return months;
+}
+
+function getButtonDisabled(type, currentDate) {
+  if (currentDate == null) {
+    return true;
+  }
+
+  if (type === 'range') {
+    return !currentDate[0] || !currentDate[1];
+  }
+
+  if (type === 'multiple') {
+    return !currentDate.length;
+  }
+
+  return !currentDate;
+}
+
+module.exports = {
+  getMonths: getMonths,
+  getButtonDisabled: getButtonDisabled
+};

+ 1 - 0
wxcomponents/vant/calendar/index.wxss

@@ -0,0 +1 @@
+@import '../common/index.wxss';.van-calendar{background-color:var(--calendar-background-color,#fff);display:flex;flex-direction:column;height:var(--calendar-height,100%)}.van-calendar__close-icon{top:11px}.van-calendar__popup--bottom,.van-calendar__popup--top{height:var(--calendar-popup-height,80%)}.van-calendar__popup--left,.van-calendar__popup--right{height:100%}.van-calendar__body{-webkit-overflow-scrolling:touch;flex:1;overflow:auto}.van-calendar__footer{flex-shrink:0;padding:0 var(--padding-md,16px)}.van-calendar__footer--safe-area-inset-bottom{padding-bottom:env(safe-area-inset-bottom)}.van-calendar__footer+.van-calendar__footer,.van-calendar__footer:empty{display:none}.van-calendar__footer:empty+.van-calendar__footer{display:block!important}.van-calendar__confirm{height:var(--calendar-confirm-button-height,36px)!important;line-height:var(--calendar-confirm-button-line-height,34px)!important;margin:var(--calendar-confirm-button-margin,7px 0)!important}

+ 12 - 0
wxcomponents/vant/calendar/utils.d.ts

@@ -0,0 +1,12 @@
+export declare const ROW_HEIGHT = 64;
+export declare function formatMonthTitle(date: Date): string;
+export declare function compareMonth(date1: Date | number, date2: Date | number): 1 | -1 | 0;
+export declare function compareDay(day1: Date | number, day2: Date | number): 1 | -1 | 0;
+export declare function getDayByOffset(date: Date, offset: number): Date;
+export declare function getPrevDay(date: Date): Date;
+export declare function getNextDay(date: Date): Date;
+export declare function getToday(): Date;
+export declare function calcDateNum(date: [Date, Date]): number;
+export declare function copyDates(dates: Date | Date[]): Date | Date[];
+export declare function getMonthEndDay(year: number, month: number): number;
+export declare function getMonths(minDate: number, maxDate: number): number[];

+ 83 - 0
wxcomponents/vant/calendar/utils.js

@@ -0,0 +1,83 @@
+export const ROW_HEIGHT = 64;
+export function formatMonthTitle(date) {
+    if (!(date instanceof Date)) {
+        date = new Date(date);
+    }
+    return `${date.getFullYear()}年${date.getMonth() + 1}月`;
+}
+export function compareMonth(date1, date2) {
+    if (!(date1 instanceof Date)) {
+        date1 = new Date(date1);
+    }
+    if (!(date2 instanceof Date)) {
+        date2 = new Date(date2);
+    }
+    const year1 = date1.getFullYear();
+    const year2 = date2.getFullYear();
+    const month1 = date1.getMonth();
+    const month2 = date2.getMonth();
+    if (year1 === year2) {
+        return month1 === month2 ? 0 : month1 > month2 ? 1 : -1;
+    }
+    return year1 > year2 ? 1 : -1;
+}
+export function compareDay(day1, day2) {
+    if (!(day1 instanceof Date)) {
+        day1 = new Date(day1);
+    }
+    if (!(day2 instanceof Date)) {
+        day2 = new Date(day2);
+    }
+    const compareMonthResult = compareMonth(day1, day2);
+    if (compareMonthResult === 0) {
+        const date1 = day1.getDate();
+        const date2 = day2.getDate();
+        return date1 === date2 ? 0 : date1 > date2 ? 1 : -1;
+    }
+    return compareMonthResult;
+}
+export function getDayByOffset(date, offset) {
+    date = new Date(date);
+    date.setDate(date.getDate() + offset);
+    return date;
+}
+export function getPrevDay(date) {
+    return getDayByOffset(date, -1);
+}
+export function getNextDay(date) {
+    return getDayByOffset(date, 1);
+}
+export function getToday() {
+    const today = new Date();
+    today.setHours(0, 0, 0, 0);
+    return today;
+}
+export function calcDateNum(date) {
+    const day1 = new Date(date[0]).getTime();
+    const day2 = new Date(date[1]).getTime();
+    return (day2 - day1) / (1000 * 60 * 60 * 24) + 1;
+}
+export function copyDates(dates) {
+    if (Array.isArray(dates)) {
+        return dates.map((date) => {
+            if (date === null) {
+                return date;
+            }
+            return new Date(date);
+        });
+    }
+    return new Date(dates);
+}
+export function getMonthEndDay(year, month) {
+    return 32 - new Date(year, month - 1, 32).getDate();
+}
+export function getMonths(minDate, maxDate) {
+    const months = [];
+    const cursor = new Date(minDate);
+    cursor.setDate(1);
+    do {
+        months.push(cursor.getTime());
+        cursor.setMonth(cursor.getMonth() + 1);
+    } while (compareMonth(cursor, maxDate) !== 1);
+    return months;
+}

+ 25 - 0
wxcomponents/vant/calendar/utils.wxs

@@ -0,0 +1,25 @@
+/* eslint-disable */
+function getMonthEndDay(year, month) {
+  return 32 -  getDate(year, month - 1, 32).getDate();
+}
+
+function compareMonth(date1, date2) {
+  date1 = getDate(date1);
+  date2 = getDate(date2);
+
+  var year1 = date1.getFullYear();
+  var year2 = date2.getFullYear();
+  var month1 = date1.getMonth();
+  var month2 = date2.getMonth();
+
+  if (year1 === year2) {
+    return month1 === month2 ? 0 : month1 > month2 ? 1 : -1;
+  }
+
+  return year1 > year2 ? 1 : -1;
+}
+
+module.exports = {
+  getMonthEndDay: getMonthEndDay,
+  compareMonth: compareMonth
+};

+ 1 - 0
wxcomponents/vant/card/index.d.ts

@@ -0,0 +1 @@
+export {};

+ 49 - 0
wxcomponents/vant/card/index.js

@@ -0,0 +1,49 @@
+import { link } from '../mixins/link';
+import { VantComponent } from '../common/component';
+VantComponent({
+    classes: [
+        'num-class',
+        'desc-class',
+        'thumb-class',
+        'title-class',
+        'price-class',
+        'origin-price-class',
+    ],
+    mixins: [link],
+    props: {
+        tag: String,
+        num: String,
+        desc: String,
+        thumb: String,
+        title: String,
+        price: {
+            type: String,
+            observer: 'updatePrice',
+        },
+        centered: Boolean,
+        lazyLoad: Boolean,
+        thumbLink: String,
+        originPrice: String,
+        thumbMode: {
+            type: String,
+            value: 'aspectFit',
+        },
+        currency: {
+            type: String,
+            value: '¥',
+        },
+    },
+    methods: {
+        updatePrice() {
+            const { price } = this.data;
+            const priceArr = price.toString().split('.');
+            this.setData({
+                integerStr: priceArr[0],
+                decimalStr: priceArr[1] ? `.${priceArr[1]}` : '',
+            });
+        },
+        onClickThumb() {
+            this.jumpLink('thumbLink');
+        },
+    },
+});

+ 6 - 0
wxcomponents/vant/card/index.json

@@ -0,0 +1,6 @@
+{
+  "component": true,
+  "usingComponents": {
+    "van-tag": "../tag/index"
+  }
+}

+ 105 - 0
wxcomponents/vant/card/index.vue

@@ -0,0 +1,105 @@
+<template>
+<uni-shadow-root class="vant-card-index"><view class="custom-class van-card">
+  <view :class="utils.bem('card__header', { center: centered })">
+    <view class="van-card__thumb" @click="onClickThumb">
+      <image v-if="thumb" :src="thumb" :mode="thumbMode" :lazy-load="lazyLoad" class="van-card__img thumb-class"></image>
+      <slot v-else name="thumb"></slot>
+      <van-tag v-if="tag" mark type="danger" custom-class="van-card__tag">
+        {{ tag }}
+      </van-tag>
+      <slot v-else name="tag"></slot>
+    </view>
+
+    <view :class="'van-card__content '+(utils.bem('card__content', { center: centered }))">
+      <view>
+        <view v-if="title" class="van-card__title title-class">{{ title }}</view>
+        <slot v-else name="title"></slot>
+
+        <view v-if="desc" class="van-card__desc desc-class">{{ desc }}</view>
+        <slot v-else name="desc"></slot>
+
+        <slot name="tags"></slot>
+      </view>
+
+      <view class="van-card__bottom">
+        <slot name="price-top"></slot>
+        <view v-if="price || price === 0" class="van-card__price price-class">
+          <text>{{ currency }}</text>
+          <text class="van-card__price-integer">{{ integerStr }}</text>
+          <text class="van-card__price-decimal">{{ decimalStr }}</text>
+        </view>
+        <slot v-else name="price"></slot>
+        <view v-if="originPrice || originPrice === 0" class="van-card__origin-price origin-price-class">{{ currency }} {{ originPrice }}</view>
+        <slot v-else name="origin-price"></slot>
+        <view v-if="num" class="van-card__num num-class">x {{ num }}</view>
+        <slot v-else name="num"></slot>
+        <slot name="bottom"></slot>
+      </view>
+    </view>
+  </view>
+
+  <view class="van-card__footer">
+    <slot name="footer"></slot>
+  </view>
+</view></uni-shadow-root>
+</template>
+<wxs src="../wxs/utils.wxs" module="utils"></wxs>
+<script>
+import VanTag from '../tag/index.vue'
+global['__wxVueOptions'] = {components:{'van-tag': VanTag}}
+
+global['__wxRoute'] = 'vant/card/index'
+import { link } from '../mixins/link';
+import { VantComponent } from '../common/component';
+VantComponent({
+    classes: [
+        'num-class',
+        'desc-class',
+        'thumb-class',
+        'title-class',
+        'price-class',
+        'origin-price-class',
+    ],
+    mixins: [link],
+    props: {
+        tag: String,
+        num: String,
+        desc: String,
+        thumb: String,
+        title: String,
+        price: {
+            type: String,
+            observer: 'updatePrice',
+        },
+        centered: Boolean,
+        lazyLoad: Boolean,
+        thumbLink: String,
+        originPrice: String,
+        thumbMode: {
+            type: String,
+            value: 'aspectFit',
+        },
+        currency: {
+            type: String,
+            value: '¥',
+        },
+    },
+    methods: {
+        updatePrice() {
+            const { price } = this.data;
+            const priceArr = price.toString().split('.');
+            this.setData({
+                integerStr: priceArr[0],
+                decimalStr: priceArr[1] ? `.${priceArr[1]}` : '',
+            });
+        },
+        onClickThumb() {
+            this.jumpLink('thumbLink');
+        },
+    },
+});
+export default global['__wxComponents']['vant/card/index']
+</script>
+<style platform="mp-weixin">
+@import '../common/index.css';.van-card{background-color:var(--card-background-color,#fafafa);box-sizing:border-box;color:var(--card-text-color,#323233);font-size:var(--card-font-size,12px);padding:var(--card-padding,8px 16px);position:relative}.van-card__header{display:flex}.van-card__header--center{align-items:center;justify-content:center}.van-card__thumb{flex:none;height:var(--card-thumb-size,88px);margin-right:var(--padding-xs,8px);position:relative;width:var(--card-thumb-size,88px)}.van-card__thumb:empty{display:none}.van-card__img{border-radius:8px;height:100%;width:100%}.van-card__content{display:flex;flex:1;flex-direction:column;justify-content:space-between;min-height:var(--card-thumb-size,88px);min-width:0;position:relative}.van-card__content--center{justify-content:center}.van-card__desc,.van-card__title{word-wrap:break-word}.van-card__title{font-weight:700;line-height:var(--card-title-line-height,16px)}.van-card__desc{color:var(--card-desc-color,#646566);line-height:var(--card-desc-line-height,20px)}.van-card__bottom{line-height:20px}.van-card__price{color:var(--card-price-color,#ee0a24);display:inline-block;font-size:var(--card-price-font-size,12px);font-weight:700}.van-card__price-integer{font-size:var(--card-price-integer-font-size,16px)}.van-card__price-decimal,.van-card__price-integer{font-family:var(--card-price-font-family,Avenir-Heavy,PingFang SC,Helvetica Neue,Arial,sans-serif)}.van-card__origin-price{color:var(--card-origin-price-color,#646566);display:inline-block;font-size:var(--card-origin-price-font-size,10px);margin-left:5px;text-decoration:line-through}.van-card__num{float:right}.van-card__tag{left:0;position:absolute!important;top:2px}.van-card__footer{flex:none;text-align:right;width:100%}
+</style>

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